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 — zFactor wasn't detected. Pass it explicitly (zFactor: 0.3048 for international feet, 0.30480061 for 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 adjust dataSource.heightOffset live 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 / maxPoints and/or raise sseThreshold to cut per-pass work.
  • Raise debounceMs (e.g. 200) so fast fly-throughs don't trigger constant re-selection.
  • If decode is the slow stage, lower concurrency on a CPU-bound machine.
  • If fetch is the slow stage and the link is high-latency, raise maxConcurrentRequests (not concurrency).

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 of maxCacheNodes. It's the most direct memory lever when nodes vary widely in size.
  • Lower maxCacheNodes.
  • Lower maxPoints / maxVisibleNodes to cap what's on screen at once.
  • classificationFilter hides 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