COPC vs EPT for Web Delivery
TL;DR: Both move about the same bytes for the same query. COPC does it in a handful of range requests against one object; EPT does it in hundreds of requests against thousands of files. Choose COPC for publication and interactive reading, EPT when the dataset is revised in pieces by a system that owns its own storage.
# Context and Motivation
This guide is part of COPC and Cloud-Native Point Cloud Formats. Both formats solve the same problem — answering a spatial, level-of-detail query without downloading everything — and they solve it with the same data structure, an octree whose nodes each hold a spatially even sample of their subtree. The difference is entirely in how that octree is stored, and every practical consequence follows from that one choice.
EPT stores each node as its own file, with a JSON manifest describing the tree. COPC stores every node as a byte range inside one LAZ file, with the tree in a variable length record at the front. The first is a directory; the second is a file.
# Prerequisites and Assumptions
| Requirement | Detail |
|---|---|
| PDAL | 2.4+ — readers.ept and readers.copc both ship in the standard build |
| A hosting target | object storage or a static web server that honours range requests |
| An access pattern | how often the data is read, and how often it changes |
| A client inventory | which viewers and libraries your consumers already use |
# Step-by-Step Implementation
# Step 1 — Characterise the access pattern
Two numbers decide this: reads per month and revisions per month. Their ratio picks the format more reliably than any feature comparison.
# Step 2 — Count the requests, not just the bytes
Over a wide-area network a request costs 30–80 ms of latency regardless of size. Two hundred requests is fifteen seconds of latency before a single byte of useful work.
# Step 3 — Check what your clients speak
Browser viewers increasingly read COPC natively over HTTPS with no server component. Desktop GIS support is broader for both. Legacy tooling reads neither and needs plain LAS.
# Step 4 — Test both against a real query
The comparison script below runs the same window and resolution against each and reports points, time and request count.
# Complete Working Example
"""Time the same spatial query against an EPT dataset and a COPC file."""
from __future__ import annotations
import json
import time
import pdal
WINDOW = "([512000, 513000], [4783000, 4784000])"
RESOLUTION = 2.0
def run(reader_type: str, filename: str) -> dict:
spec = json.dumps({"pipeline": [{
"type": reader_type,
"filename": filename,
"bounds": WINDOW,
"resolution": RESOLUTION,
}]})
started = time.perf_counter()
pipeline = pdal.Pipeline(spec)
n = pipeline.execute()
return {
"reader": reader_type,
"points": n,
"seconds": round(time.perf_counter() - started, 2),
}
def compare(ept_url: str, copc_url: str) -> dict:
ept = run("readers.ept", ept_url)
copc = run("readers.copc", copc_url)
# The two formats sample the octree the same way, so the point counts should
# be close. A large divergence means the two datasets were built differently.
ratio = copc["points"] / max(ept["points"], 1)
if not 0.8 < ratio < 1.25:
raise AssertionError(
f"point counts diverge too far to compare fairly: {ept['points']} vs {copc['points']}"
)
return {"ept": ept, "copc": copc,
"speedup": round(ept["seconds"] / max(copc["seconds"], 1e-6), 2)}
if __name__ == "__main__":
print(json.dumps(compare(
ept_url="https://example.org/data/region/ept.json",
copc_url="https://example.org/data/region.copc.laz",
), indent=2))# Key Parameter Table
| Dimension | EPT | COPC |
|---|---|---|
| Storage unit | directory of thousands of files | one file |
| Index | ept.json plus hierarchy files |
a VLR inside the file |
| Requests for one query | 100–300 | 4–8 |
| Bytes for one query | comparable | comparable |
| Partial update | rewrite affected node files | rewrite the whole file |
| Copy or checksum | directory sync | one object |
| Signed URL or expiry | per file, or a prefix policy | one URL |
| Read by plain LAS tools | no | yes |
# Verification
Both return comparable point counts. Asserted in the example. A large divergence means the two datasets were built with different octree parameters, and any timing comparison between them is meaningless.
Request counts match expectation. Capture them at the proxy or with the object store’s access log. If COPC is issuing hundreds of requests, resolution was probably not set.
The client actually works. A format that your viewer cannot open is not a candidate however good the numbers are.
# Gotchas and Edge Cases
A COPC file cannot be updated in place. The octree ordering is the file layout, so revising one region means rebuilding the object. For a dataset under continuous revision that is the argument for EPT, and it is a strong one.
EPT’s file count is an operational cost. Thousands of objects per dataset makes lifecycle policies, replication and cost attribution harder, and some object stores charge per request in a way that shows up.
Neither is an archival format. Both are derived products. Keep the source tiles — the conversion is reproducible, the original acquisition is not.
Do not benchmark over localhost. On a local disk the two formats are within a few percent. The entire difference is round trips, so measure over the link your users will use.
# Frequently Asked Questions
Do COPC and EPT transfer different amounts of data?
Not meaningfully. They store the same octree with the same node sampling, so a given query pulls a similar number of bytes from either. The difference is that EPT needs one request per node — often two or three hundred — while COPC needs a handful of range requests against one object.
When is EPT still the better choice?
When the dataset is revised in pieces. Because each node is its own file, updating a region rewrites a few small objects rather than a multi-gigabyte file. If your data changes more often than it is read, that outweighs the request-count penalty.
Can a browser read COPC without a server?
Yes, provided the host honours HTTP range requests. The client fetches the header, reads the index from a variable length record, and then requests the byte ranges it needs. That is the property that has made COPC the common choice for web delivery.
Should either format replace my archive?
No. Both are derived products optimised for reading. Keep the original tiles: the conversion to COPC or EPT is reproducible from them, and nothing reproduces the acquisition.
# Related
- COPC and Cloud-Native Point Cloud Formats — the parent guide to the octree both formats use
- Converting LAZ Tiles to COPC with PDAL — producing the COPC side of this comparison
- Querying a COPC File by Bounds and Resolution — the options that make either format fast
- Writing Cloud Optimized GeoTIFFs to S3 — the same idea applied to rasters
- Point Cloud Data Standards and Fundamentals — the section overview