Troubleshooting - Jangmyun/copcesium GitHub Wiki
Troubleshooting
A checklist for the failures people actually hit, grouped by symptom. Open the browser devtools console first — copcesium logs node-level failures there rather than throwing (a single bad node shouldn't kill the stream). Only problems with the file as a whole (no Range support, an unreadable header) make load() reject.
Nothing renders at all
CORS errors in the console
Access to fetch … has been blocked by CORS policy, or load() rejecting with Failed to read the COPC header. Check that the URL is correct and that CORS access is allowed.
The .copc.laz file is cross-origin and the host isn't sending CORS headers. The server must send Access-Control-Allow-Origin permitting your app's origin (and allow the Range request header). On S3 this is a bucket CORS policy; on nginx an add_header Access-Control-Allow-Origin block. Same-origin hosting avoids this entirely.
Optionally, add Access-Control-Expose-Headers: Content-Range too. Without it, copcesium can't read the file size from Range responses and spends one extra HEAD request on it (used only for stats.fileBytes).
Range Requests not supported
load() rejects with Server did not honor an HTTP Range Request (expected 206 Partial Content, got 200), or you see 200 OK instead of 206 Partial Content in the Network tab.
copcesium fetches byte ranges, not whole files, and checks for this up front with a 1-byte probe. The host must support Range headers and answer 206 Partial Content. S3, most CDNs, and static hosts do this by default; some app servers and proxies strip it. Check with:
curl -I -H "Range: bytes=0-100" https://your-host/file.copc.laz
You want HTTP/… 206 and an Accept-Ranges: bytes header back.
The camera never moved to the data
If autoFrame is false, load() won't fly to the dataset — you're looking at empty globe elsewhere. Either set autoFrame: true (default) or call dataSource.zoomTo() yourself.
Not actually a COPC file
Plain LAS/LAZ has no octree index and won't stream. Verify with pdal info file --metadata and look for copc: true. See Converting to COPC.
Everything is filtered out
If classificationFilter doesn't include any code the file actually uses, every point is dropped in the shader. Set dataSource.classificationFilter = undefined to draw everything again.
Some nodes fail to load
Console: RangeFetcher: server returned HTTP 4xx …
A node's Range Request was refused. A 4xx is not retried, because it won't fix itself — typically a pre-signed URL that has expired (403) or a file replaced on the server while the viewer was open (416). Reload with a fresh URL.
5xx and network errors are retried automatically (3 attempts with backoff) and only show up here when all attempts fail. A failed node is requested again on a later LoD pass if it's still needed.
Detail stops increasing in one area, with Hierarchy page "…" warnings
Large COPC files split their octree index into hierarchy sub-pages, which copcesium loads on demand. Hierarchy page "…" failed (attempt n/3), will retry means a page failed and will be retried with backoff. Giving up on hierarchy page "…" means it failed for good (3 attempts, or straight away on a permanent 4xx), and the subtree under it won't refine any further this session. Check the server logs for that byte range, then reload.
If there are no warnings, see Stops refining when zoomed in below.
Cloud renders but in the wrong place
Wrong country / offset horizontally
The horizontal CRS was misdetected or missing from the file's WKT. Check the file's WKT (pdal info --metadata); if it's absent or an odd dialect, override explicitly:
{ proj: 'EPSG:XXXX', projDef: '+proj=…' }
See Coordinate Systems for how detection works and where to get proj4 strings.
Floating in the air or buried underground
Two common causes:
- Feet not scaled to meters —
zFactorwasn't detected. Pass it explicitly (zFactor: 0.3048for international feet,0.30480061for US survey feet). - Orthometric vs. ellipsoidal height mismatch — the data uses a geoid-based vertical datum. Apply a constant
geoidOffset(meters) to line it up with the terrain, or adjustdataSource.heightOffsetlive to find the value without reloading.
A tiny dot or an absurdly huge blob
xyFactor is wrong — almost always a feet-based CRS that wasn't detected, so bounding spheres are mis-sized. Pass xyFactor explicitly (same value as the linear unit, e.g. 0.3048).
Console: "proj4 transform produced a non-finite coordinate"
proj4 returned Infinity/NaN for a point outside the projection's valid domain — the source CRS is wrong for this data. Re-check proj/projDef against what the file actually is.
Looks wrong
'intensity' mode is nearly black or washed out
By default intensityRange grows to [0, highest intensity seen] as nodes load, so one outlier can wash out the rest. Pin it to the range your data actually uses, e.g. dataSource.intensityRange = [0, 4000]. Assign undefined to return to auto.
Translucent points blend in the wrong order
Expected with opacity < 1: points are alpha-blended without a per-point depth sort, so overlapping points may composite out of order. Keep opacity at 1 when that matters.
Gaps between points up close
Raise pixelSize, or switch to pointSizeMode: 'attenuated' so near points grow and far points shrink. Tune attenuationFactor and minPixelSize/maxPixelSize per dataset (see Options & Tuning).
Performance problems
Read dataSource.stats first. transferredBytes vs. fileBytes shows how much is being pulled, pendingNodes shows whether the pipeline is keeping up, and the fetch / decode / upload p50/p95 timings show which stage is slow. The same stages also appear as copcesium fetch / decode / upload measures in the DevTools Performance timeline.
Stuttering / slow to refine while flying
- Lower
maxVisibleNodes/maxPointsand/or raisesseThresholdto cut per-pass work. - Raise
debounceMs(e.g.200) so fast fly-throughs don't trigger constant re-selection. - If
decodeis the slow stage, lowerconcurrencyon a CPU-bound machine. - If
fetchis the slow stage and the link is high-latency, raisemaxConcurrentRequests(notconcurrency).
Stops refining when zoomed in
Usually the render budget: the pass stopped at maxVisibleNodes or maxPoints before reaching deeper nodes. Raise whichever one you're hitting. If the console shows hierarchy page warnings, see above.
High memory use
- Set
maxCacheBytes— a byte budget on the LRU cache, on top ofmaxCacheNodes. It's the most direct memory lever when nodes vary widely in size. - Lower
maxCacheNodes. - Lower
maxPoints/maxVisibleNodesto cap what's on screen at once. classificationFilterhides points but does not free their memory.- See the presets in Options & Tuning.
Too much bandwidth
Prefetching loads off-screen nodes ahead of a pan. Lower prefetchFrustumFactor / maxPrefetchNodes, or set prefetchFrustumFactor: 1 to disable it. Lowering maxConcurrentRequests makes copcesium gentler on the origin.
Re-fetches everything when I zoom back out
Raise maxCacheNodes (and maxCacheBytes, if set) so nodes you backtrack over stay cached instead of being evicted and re-decoded.
Bundler / build issues
Worker or WASM 404 in a consuming app
This should not happen — copcesium inlines the Worker and its laz-perf WASM into the single published .mjs at build time precisely to avoid it. If you see a 404 for a worker chunk or .wasm, you're likely importing from source (src/) rather than the built package, or a bundler is rewriting the inlined asset. Import from the package entry (copcesium), not a deep path.
404s for Cesium's own Workers/, Assets/, Widgets/ or ThirdParty/ are a Cesium setup issue, not a copcesium one — see the README's Setup section (vite-plugin-cesium, or copying those folders and setting CESIUM_BASE_URL).
"require is not defined" / CommonJS import fails
copcesium is ESM-only — there is no CommonJS build, because constructing the inlined Worker needs import.meta.url semantics that require() can't provide. Use import, and ensure your bundler/Node treats the package as ESM.
Using it without a bundler
Use an import map that resolves both cesium and copcesium to their published npm files (e.g. jsDelivr /npm/ paths), with the same Cesium version everywhere. Avoid CDNs that rebuild packages: esm.sh, for one, can resolve Cesium's @cesium/engine to a newer release and fail with a SyntaxError about a missing export. The README's Setup section has a full example.
Cesium version conflicts
cesium is a peer dependency (>=1.100.0) — copcesium uses whichever copy your app installs. If low-level GPU APIs behave oddly, confirm you have a single Cesium instance (not two copies from a dependency mismatch).
Still stuck?
Open an issue with the COPC URL (or its WKT), your CopcDataSource.load() options, a dataSource.stats snapshot, and the console output: https://github.com/Jangmyun/copcesium/issues