-
Notifications
You must be signed in to change notification settings - Fork 1
Coordinate Systems
CesiumJS renders on a WGS84 globe in ECEF (Earth-centred, Earth-fixed) meters. COPC point coordinates almost never arrive that way — they're in a projected CRS (a UTM zone, a state plane, a national grid), often in feet, and sometimes with a separate vertical datum. copcesium's job is to turn those into lon/lat/height and then ECEF, correctly and automatically.
src/crs/detectCrs.ts runs on every load(), reading the COPC file's WKT VLR (exposed by copc.js as copc.wkt):
-
Extract the horizontal CRS. If the WKT is a compound
COMPD_CS["…", PROJCS[…], VERT_CS[…]], the innerPROJCS/GEOGCS(or WKT2PROJCRS/GEOGCRS) block is pulled out by bracket counting. proj4 can't parseCOMPD_CS, so this step is essential. -
Geographic short-circuit. If the horizontal CRS is already geographic (
GEOGCS/GEOGCRS/GEODCRS), the data is lon/lat degrees — returnEPSG:4326directly. -
Feed the WKT straight to proj4.
proj4.defs(name, crsWkt)— proj4 parses most WKT1 dialects natively. This is the primary path and needs no lookup table. -
Fallback: EPSG code from the WKT. If proj4 can't parse the dialect (common with WKT2), the
ID["EPSG", n]/AUTHORITY["EPSG","n"]code is extracted and resolved against a built-in offline table (src/crs/projections.ts). -
Give up gracefully. If nothing resolves, return
nulland let the caller fall back to the default or an explicit override.
The EPSG code in step 4 is deliberately searched only within the extracted horizontal block, not the whole string — otherwise a compound CRS's vertical datum code (e.g. NAVD88) could be mistaken for the horizontal CRS.
proj4 applies the horizontal linear unit to X/Y, but Z is passed through untouched. A file in US survey feet whose heights aren't scaled will float thousands of meters too high. copcesium reads the WKT's linear/vertical unit and exposes it as:
-
zFactor— multiplies every height into meters (feet →0.3048, US survey foot →0.30480061…). -
xyFactor— the XY unit in meters, used for bounding-sphere sizing (so LoD math stays correct in feet-based CRSes).
Both are auto-detected from the WKT vertical/linear unit, and — importantly — they still apply even when you override proj/projDef, because a compound CRS's vertical unit can differ from the horizontal CRS you're supplying. To set them yourself, pass zFactor/xyFactor explicitly; an explicit value always wins over detection.
WGS84 heights are ellipsoidal; many datasets store orthometric heights (above a geoid like NAVD88 or EGM2008). The two differ by the local geoid separation — often tens of meters. copcesium doesn't ship a geoid model, so if your cloud sits consistently above or below the terrain, pass a constant:
{ geoidOffset: -20 } // meters added to every heightThis is a single global offset, not a per-point geoid grid — good enough when the dataset covers a limited area (the separation varies slowly).
Pass proj + projDef when the WKT is missing, truncated, or an unrecognized dialect:
const dataSource = await CopcDataSource.load(url, viewer, {
proj: 'EPSG:2992', // Oregon State Plane North (ft)
projDef:
'+proj=lcc +lat_1=43 +lat_2=45.5 +lat_0=41.75 +lon_0=-120.5' +
' +x_0=399999.9999999999 +y_0=0 +datum=NAD83 +units=ft +no_defs',
geoidOffset: -20,
});If proj4 already knows the code (it ships EPSG:4326 and EPSG:4269 only), projDef can be omitted. For anything else, supply the proj4 string — grab it from epsg.io (the "proj4" export on any CRS page).
src/crs/projections.ts is a hand-curated offline EPSG → proj4 map. It exists so detection works without network calls to epsg.io, and only for step 4 above (when proj4's own WKT parsing doesn't recognize a dialect). It covers:
- WGS84 / NAD83 / ETRS89 UTM zones (generated in a loop, not hand-listed)
- US State Plane (NAD83, both meter and foot variants)
- Korean CRSes (Korea 2000 belts
5185–5188, unified5179, legacy Bessel5174–5177) - Japanese (JGD2000/2011 plane-rectangular + UTM), Australian (GDA94/2020 MGA), and common European national grids
If your CRS isn't in the table and proj4 can't parse its WKT, detection returns null — pass proj/projDef explicitly. Because the primary path is "feed WKT to proj4," a CRS absent from the table still works fine as long as its WKT is a dialect proj4 understands.
-
Cloud is on the globe but in the wrong country → wrong horizontal CRS. Check the file's WKT; override
proj/projDef. -
Cloud is in the right place but floating / underground → wrong
zFactor(feet not scaled) or a geoid/ellipsoid mismatch (geoidOffset). -
Cloud is a tiny dot or absurdly huge → wrong
xyFactor, usually a feet CRS not detected. -
Nothing renders and the console shows non-finite coordinate errors → proj4 produced
Infinityfor out-of-domain input; the source CRS is likely wrong for this data.
See Troubleshooting for the full checklist.