-
Notifications
You must be signed in to change notification settings - Fork 1
Options and Tuning
Every field on the third argument to CopcDataSource.load() is optional. This page groups them by what you're trying to trade off — visual quality, memory, bandwidth/latency, and coordinates — and gives concrete guidance for each.
const dataSource = await CopcDataSource.load(url, viewer, {
sseThreshold: 250,
maxVisibleNodes: 100,
maxCacheNodes: 150,
concurrency: 5,
pixelSize: 2,
// ...
});| Option | Default | Trades off |
|---|---|---|
proj |
'EPSG:4326' |
Source CRS id. Auto-detected from WKT when omitted. See Coordinate Systems. |
projDef |
null |
proj4 definition string for proj when proj4 doesn't already know it. |
geoidOffset |
0 |
Meters added to every height (local geoid–ellipsoid separation). |
zFactor |
auto | Z-unit → meters. Detected from the WKT vertical unit. |
xyFactor |
auto | XY-unit → meters (bounding-sphere sizing). |
concurrency |
5 |
Parallel Worker threads. Also the default for maxConcurrentRequests. Bandwidth/CPU vs. responsiveness. |
maxConcurrentRequests |
= concurrency
|
HTTP Range Requests in flight at once, set independently of concurrency. |
debounceMs |
100 |
Min interval between full LoD passes. Responsiveness vs. churn. |
maxCacheNodes |
150 |
LRU cap by node count. Memory vs. re-fetch on backtrack. |
maxCacheBytes |
unset | LRU cap by estimated bytes, on top of maxCacheNodes. Whichever limit is hit first evicts. |
maxVisibleNodes |
100 |
Nodes per LoD pass. Detail vs. memory/CPU. |
maxPoints |
5,000,000 |
Total points across selected nodes per LoD pass, on top of maxVisibleNodes. Detail vs. memory/CPU. |
pixelSize |
2 |
Point size in px. Live-adjustable. |
sseThreshold |
250 |
SSE (px) to subdivide. Detail vs. everything. Live-adjustable. |
autoFrame |
true |
Whether load() flies the camera to the dataset. |
colorMode |
'rgb' |
How points are coloured ('rgb' | 'intensity' | 'classification' | 'elevation'). Live-adjustable, no re-decode. |
opacity |
1 |
Alpha multiplier, 0-1. Below 1 switches to translucent (no per-point depth sort). Live-adjustable. |
classificationFilter |
all codes | LAS classification codes to draw; everything else dropped. Live-adjustable. |
intensityRange |
auto | Raw intensity values at the two ends of the 'intensity' ramp. Grows to [0, highest seen] unless pinned. |
heightOffset (live property, not a load option)
|
0 |
Meters shifted along local "up" post-load, for correcting a residual geoid/vertical-datum mismatch without re-loading. Live-adjustable via dataSource.heightOffset. |
Screen-space error is roughly how many pixels the gap between points would span at the current distance. A node is subdivided into its children when its SSE exceeds this threshold.
-
Lower (e.g.
100) → subdivides sooner → denser points, more nodes loaded, more memory and bandwidth. -
Higher (e.g.
400) → coarser, cheaper, faster. -
Default
250is a balance for typical aerial/drone datasets.
It is live-adjustable — dataSource.sseThreshold = 150 triggers an immediate re-selection with no reload. Wire it to a slider (the example viewer does).
Point size in pixels. Also live via dataSource.pixelSize. For sparse clouds a larger pixelSize (3–4) fills gaps and reads as more solid; for dense clouds keep it at 1–2 to avoid a mushy blob. This changes only a GPU uniform — no re-fetch.
Total nodes kept in memory (loaded or hidden) before the least-recently-used unselected node is torn down. Selected nodes are pinned and never evicted.
- Raise it if users backtrack a lot (orbiting, zooming in and out): a larger cache avoids re-fetching+re-decoding nodes they just left.
- Lower it on memory-constrained devices.
- Rule of thumb: keep it comfortably above
maxVisibleNodes(default 150 vs 100) so the current view plus some history fits.
maxCacheNodes alone is a poor proxy for memory since a node's point count varies widely across a hierarchy — two datasets with the same node cap can hold very different amounts of actual data. maxCacheBytes (unset by default) bounds the same LRU by estimated size instead, evicting on whichever of the two limits is hit first. Each node's size is estimated as pointCount * 21 bytes (the fixed per-point buffer layout: positions 12B + colors 4B + intensities 2B + classifications 1B + elevations 2B). Set it when you know a hard memory budget (e.g. a kiosk device); leave it unset otherwise, since a sensible value depends on the dataset's typical points-per-node.
Hard cap on how many nodes a single LoD pass will select. Because selection is a max-heap by SSE, hitting the cap keeps the highest-error (most visually important) nodes and drops the rest. Raise it for more on-screen detail at the cost of memory and per-frame CPU; lower it to guarantee a budget on weak hardware.
maxVisibleNodes caps node count, but nodes don't hold a uniform number of points across a hierarchy, so the same node budget can mean very different rendering cost (draw calls, GPU memory, points on screen) on different datasets. maxPoints (default 5,000,000) bounds the render set directly by total point count across selected nodes, whichever of the two limits is hit first.
colorMode, opacity, classificationFilter, and intensityRange are covered in depth in the README's Styling section. All four are live-adjustable setters on CopcDataSource — a style change is a GPU uniform update, never a re-fetch or re-decode, so tune these freely without worrying about network/CPU cost.
Number of Worker threads decoding in parallel (default 5). Also the default width for maxConcurrentRequests when that option is left unset.
- Higher → more parallel CPU-bound LAZ decode work, but more memory (each Worker carries its own laz-perf WASM instance).
- Lower → gentler on the CPU.
- Ignored when you pass your own
workerPool(the pool's size wins).
How many HTTP Range Requests may be in flight at once, independent of concurrency. Decoding is CPU-bound and saturates at a handful of workers; fetching is latency-bound, so a high-RTT link can usefully keep far more requests in flight than there are decode workers. Raising concurrency to buy fetch parallelism used to also spawn that many Workers — memory spent on a stage that was never the bottleneck. Set maxConcurrentRequests higher than concurrency on high-latency links to widen fetching without widening decode.
Minimum interval between full LoD re-selection passes while the camera is moving (a lighter frustum-only visibility check still runs every frame, and a full pass always fires on moveEnd).
- Higher (e.g.
200) → less selection churn and fewer speculative loads during fast fly-throughs. - Lower (e.g.
50) → snappier refinement mid-motion, more work.
proj, projDef, zFactor, xyFactor, and geoidOffset are covered in depth in Coordinate Systems. Short version:
- Leave them unset and let WKT auto-detection do its job.
- Override
proj/projDefonly when the file's WKT is missing or unrecognized. - Auto-detected
zFactor/xyFactorstill apply on top of an explicitproj/projDef, because a compound CRS's vertical unit can differ from the CRS you override with. - Use
geoidOffsetif heights sit consistently above/below the terrain (orthometric vs. ellipsoidal datum mismatch). -
heightOffsetis the live equivalent for correcting a mismatch discovered after load, without re-loading:dataSource.heightOffset = -20shifts every already-loaded (and future) node along its local "up" by translating the model matrix, not the geometry, so it applies instantly with no re-decode.
dataSource.stats is a read-only getter (CopcStats) that reports what a session has actually spent, so "only the nodes the camera can see are fetched" is a number instead of an assertion:
| Field | What it reports |
|---|---|
fileBytes |
Total COPC file size, read from a range response's Content-Range. |
requestCount |
HTTP range responses received (merged sibling fetches count once). |
transferredBytes |
Bytes actually received — the Range-support probe, the header, hierarchy pages, and node point data combined. |
pendingNodes |
Nodes currently fetching, decoding, or uploading. 0 means the current view is fully resolved. |
fetch / decode / upload
|
Per-stage StageTiming — count (nodes completed, unbounded) plus p50/p95 latency in ms over a rolling window of the most recent 256 nodes. |
upload.count normally trails decode.count: a node that finished decoding but was never drawn (camera moved on, or it lost a budget cut) never reaches the GPU, so it never reaches the upload stage. Comparing transferredBytes against fileBytes gives a rough sense of how much of a large dataset a given camera path actually touched.
Weak device / mobile
{ sseThreshold: 350, maxVisibleNodes: 60, maxCacheNodes: 90, concurrency: 3 }Quality workstation
{ sseThreshold: 150, maxVisibleNodes: 160, maxCacheNodes: 250, concurrency: 8 }Bandwidth-constrained origin
{ concurrency: 2, debounceMs: 200, sseThreshold: 300 }