Skip to content

thlee33/copc_cesiumjs

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

COPC × CesiumJS

Stream Cloud Optimized Point Clouds (COPC .laz) directly in CesiumJS, and convert plain .las / .laz into COPC server-side with PDAL.

Demo Page : https://web-ten-xi-ca7nuj2n4c.vercel.app/
COPC exmaple 1(COPC.IO) : https://s3.amazonaws.com/hobu-lidar/sofi.copc.laz
COPC exmaple 2(COPC.IO) : https://s3.amazonaws.com/hobu-lidar/autzen-classified.copc.laz

YouTube : https://youtu.be/-Vy1g-Bm-O4

예시화면

License: MIT

CesiumJS ships native support only for pnts (3D Tiles) point clouds. COPC is a different, increasingly common format: a single LAZ file whose chunks are arranged as an octree and indexed by a hierarchy VLR, so a viewer can fetch just the nodes it needs through HTTP range requests — no tiling pipeline, no server-side index, attributes fully preserved. It's the same format QGIS writes when it indexes a .las for fast rendering.

This project brings COPC to the browser and gives you a one-command path from raw .las to a streamable .copc.laz.


Table of contents


Features

  • 🌍 Native COPC streaming in Cesium — octree traversal with screen-space-error LOD; only visible, sufficiently-detailed nodes are fetched and rendered.
  • 📡 HTTP range requests — reads a COPC file in place from any range-capable host (S3, nginx, the bundled server…). No pre-tiling.
  • 🧩 laz-perf (WASM) decoding — LAZ chunks are decompressed in the browser.
  • 🗺️ Automatic reprojection — reads the CRS from the file's WKT VLR and reprojects to the WGS84 globe with proj4 (UTM and other projected CRSs work out of the box).
  • 🎨 Attribute-aware rendering — RGB when present, intensity as grayscale fallback.
  • 📐 Terrain anchoring & height offset — depth-tests points against the globe so the cloud stays locked to the ground (no "swimming"), with a terrain on/off toggle and a height-offset control for vertical-datum mismatches.
  • 🔄 .las → COPC conversion server — Express endpoint wrapping pdal translate … --writers.copc.forward=all, preserving all attributes & SRS.
  • 📦 Reusable library — drop CopcPointCloud into any Cesium app; the demo is just a thin UI around it.

System architecture

┌──────────────────────────── Browser ─────────────────────────────┐
│                                                                   │
│   Control panel (index.html / main.js)                            │
│        │  URL                          │ .las/.laz file           │
│        ▼                               ▼                          │
│   CopcPointCloud ──────────┐      (upload, multipart)             │
│        │ 1. range reads     │            │                        │
│        │ 2. octree LOD      │            │                        │
│        │ 3. decode (WASM)   │            │                        │
│        │ 4. reproject       │            │                        │
│        ▼                    │            │                        │
│   Cesium Viewer (points)    │            │                        │
└─────────────────────────────┼────────────┼────────────────────────┘
                              │            │
            HTTP Range (bytes)│            │ POST /api/convert
                              │            ▼
                    ┌─────────┴──────── Conversion server ──────────┐
                    │  Express (server/index.js)                    │
                    │      │                                        │
                    │      ▼  pdal translate --writers.copc.forward=all
                    │   PDAL  ────────────────▶  <name>.copc.laz    │
                    │      │                          │             │
                    │      └── /files (range) ◀───────┘             │
                    │          /download                            │
                    └───────────────────────────────────────────────┘

Two independent data paths:

  1. VisualizeCopcPointCloud opens a .copc.laz URL and streams it. This path is 100% client-side and works against any range-capable host.
  2. Convert — the browser uploads a .las/.laz; the server runs PDAL, writes a .copc.laz, and serves it back (with range support) so path 1 can stream it.

How it works

COPC streaming & level of detail

A COPC file embeds an octree. Each node (D-X-Y-Z: depth + xyz index) holds a spatially-decimated subset of points, and the file's hierarchy VLR maps node keys to byte ranges. CopcPointCloud:

  1. Reads the header + root hierarchy page via copc.
  2. On every camera change, walks the octree from the root and computes each node's screen-space error — roughly how many pixels its point spacing projects to. Nodes above the error threshold are refined into their 8 children; deeper hierarchy pages are fetched lazily as needed.
  3. For each selected node it issues a single HTTP range request for that node's compressed bytes, decodes them with laz-perf, reprojects, and adds them to the scene as a PointPrimitiveCollection.
  4. An LRU budget (maxLoadedNodes) evicts off-screen nodes to bound memory.

Decoding

LAZ chunks are decompressed by laz-perf, compiled to WebAssembly. The WASM binary is served as a static asset (/laz-perf.wasm) and located via locateFile, sidestepping bundler path issues.

Coordinate reference systems

Cesium positions everything on the WGS84 ellipsoid, so native point coordinates must be reprojected. CopcPointCloud reads the WKT VLR (exposed by copc.js as copc.wkt) and builds a proj4 transform to EPSG:4326, then to ECEF via Cartesian3.fromDegrees. Files without a CRS VLR are treated as already lon/lat.

.las → COPC conversion

The server shells out to PDAL:

pdal translate <input>.las <output>.copc.laz --writers.copc.forward=all

The .copc.laz extension selects PDAL's COPC writer; forward=all carries through scale/offset, SRS, VLRs, and every point dimension from the source.


Repository structure

distribute/
├── README.md                # You are here
├── LICENSE                  # MIT
├── package.json             # Local dev scripts + frontend & server dependencies
├── Dockerfile               # Conversion-server image (Node + PDAL) for Cloud Run
├── .dockerignore
├── DEPLOY-cloudrun.md        # Step-by-step Cloud Run deploy guide (Korean)
├── .env.example             # Configuration template
├── .gitignore
├── index.html               # Demo app shell + control panel (Korean UI)
├── vite.config.js           # Vite config (vite-plugin-cesium, dep optimization)
│
├── public/
│   └── laz-perf.wasm         # LAZ decoder, served at /laz-perf.wasm
│
├── src/
│   ├── main.js               # Demo UI wiring (load URL, upload→convert→view)
│   ├── style.css             # Control-panel styling
│   └── lib/                   # ── Reusable, framework-agnostic library ──
│       ├── copc-cesium.js     # CopcPointCloud: octree LOD streaming + render
│       ├── crs.js             # WKT → WGS84 reprojection (proj4)
│       └── http-getter.js     # HTTP range "getter" for copc.js
│
├── server/
│   ├── index.js              # Express conversion server (PDAL wrapper, TTL cleanup)
│   └── package.json          # Server-only deps (lean Docker image)
│
└── scripts/
    └── pdal-docker.sh        # Run PDAL via Docker when not installed locally

File-by-file reference

Library (src/lib/) — the reusable core

File Responsibility
copc-cesium.js The CopcPointCloud class. Owns the whole client pipeline: opens the COPC file, ingests hierarchy pages, selects nodes by screen-space error, fetches/decodes/reprojects points, renders each node as a Cesium PointPrimitiveCollection, and evicts nodes under a memory budget. Also lazy-loads the shared laz-perf WASM instance.
crs.js resolveCrs(getter, copc) — turns the file's CRS (from copc.wkt) into a forward(x, y) → [lon, lat] function via proj4. Falls back to pass-through when no WKT is present.
http-getter.js createHttpGetter(url) — returns the (begin, end) → Uint8Array function copc.js uses to read bytes, implemented as a single HTTP Range request per call.

Demo app (src/, index.html)

File Responsibility
index.html Page shell: the #cesiumContainer and a floating control panel (COPC URL input + Load button, file picker, status/download areas).
src/main.js Creates the Cesium Viewer, wires the panel: loadUrl() instantiates CopcPointCloud; convertAndLoad() uploads a file to the server, then streams the result. Also handles the terrain toggle and height-offset controls, and enables globe.depthTestAgainstTerrain so points are occluded by terrain (anchored to the ground) instead of drawing on top. Exposes window.viewer for debugging.
src/style.css Styling for the control panel.

Server (server/)

File Responsibility
server/index.js Express app. POST /api/convert accepts a multipart upload (extension preserved so PDAL can pick a reader), runs pdal translate … --writers.copc.forward=all, and returns stream/download URLs. Serves outputs from /files with range support and /download with a friendly filename. Enforces a MAX_UPLOAD_MB cap (→ HTTP 413) and a TTL_MINUTES sweeper that deletes outputs not downloaded in time. GET /health reports status.
server/package.json Server-only dependency manifest (express, cors, multer, dotenv) so the Docker image stays lean (no frontend deps).

Build / ops

File Responsibility
vite.config.js Registers vite-plugin-cesium (handles Cesium's static assets) and pre-bundles laz-perf/copc/proj4 so their CommonJS builds expose proper ESM named exports.
Dockerfile Builds the conversion-server image on node:20-bookworm-slim + Debian's pdal package (COPC writer, with GDAL/PROJ data bundled). Cloud Build compiles this when you run gcloud run deploy --source ..
public/laz-perf.wasm The laz-perf WebAssembly binary, served at the site root and referenced via locateFile.
scripts/pdal-docker.sh A drop-in pdal replacement using the official pdal/pdal Docker image, for environments without a local PDAL. Point the server at it with PDAL_BIN=./scripts/pdal-docker.sh.

Getting started

Prerequisites

  • Node.js 18+
  • PDAL on PATH (only for the conversion feature). Check with pdal --version. No local PDAL? Use Docker via scripts/pdal-docker.sh (see below).

Install & run

npm install
npm run dev          # viewer on :5173, conversion server on :3000

Open http://localhost:5173:

  • Visualize: paste a .copc.laz URL → Load.
  • Convert: pick a .las/.laz → it uploads, converts, displays, and offers a download link.

Run pieces individually with npm run web / npm run server.

Without a local PDAL

PDAL_BIN=./scripts/pdal-docker.sh npm run server

Cannot find proj.db? Some PDAL builds (e.g. the one bundled with QGIS) need their PROJ data dir pointed out. Set PROJ_DATA to that install's share/proj (and GDAL_DATA to share/gdal). See .env.example.


Library API (CopcPointCloud)

import { Viewer } from 'cesium';
import { CopcPointCloud } from './src/lib/copc-cesium.js';

const viewer = new Viewer('cesiumContainer');
const cloud = new CopcPointCloud(viewer, 'https://host/data.copc.laz', {
  maxScreenSpaceError: 16,
  pointSize: 2,
  maxLoadedNodes: 180,
});

await cloud.load();   // open file, read octree, start streaming
cloud.zoomTo();       // frame the dataset
// …
cloud.destroy();      // remove primitives + listeners

Constructor new CopcPointCloud(viewer, url, options?)

Option Default Meaning
maxScreenSpaceError 16 Refinement threshold in px. Higher = fewer points / faster.
pointSize 2 Rendered point size in px.
maxLoadedNodes 180 LRU memory budget (resident octree nodes).
heightOffset 0 Metres added to every point's height. Use to seat a cloud whose Z is orthometric (MSL) onto Cesium's ellipsoidal terrain — in Korea the geoid offset is ≈ +24..25 m.
onPoint Optional (x, y, z, rgba) => void callback per decoded point.

Methods: load(), zoomTo(), destroy(). Properties: boundingSphere, crs ({ wkt, forward }), copc (parsed metadata).

Static: CopcPointCloud.lazPerfWasmUrl — override the WASM URL (default /laz-perf.wasm) before the first load().


Server API

Base URL defaults to http://localhost:3000.

Method & path Description
POST /api/convert Multipart upload, field file (.las/.laz). Returns { url, downloadUrl, bytes, expiresInMinutes }. url streams the COPC (range support); downloadUrl forces a download. Returns 413 if the file exceeds MAX_UPLOAD_MB.
GET /files/:name The converted .copc.laz, served with Accept-Ranges: bytes. Removed after TTL_MINUTES.
GET /download/:name Same file with a Content-Disposition attachment name.
GET /health { ok, pdal, maxUploadMb, ttlMinutes } for liveness checks.

Example:

curl -F "file=@cloud.las" http://localhost:3000/api/convert
# {"url":"/files/<id>__cloud.copc.laz","downloadUrl":"/download/<id>__cloud.copc.laz","bytes":777221}

Configuration

Copy .env.example.env:

Variable Default Scope Purpose
VITE_SERVER_URL (empty) web Conversion server URL the browser calls. Blank = upload disabled (viewer-only).
VITE_CESIUM_ION_TOKEN web Optional Ion token for world terrain/imagery.
PORT 3000 (Cloud Run injects 8080) server Listen port.
PDAL_BIN pdal server PDAL binary or wrapper script.
MAX_UPLOAD_MB 30 server Upload size limit. On Cloud Run the effective ceiling is ~30 MB regardless (see Limitations); other hosts can raise it.
TTL_MINUTES 30 server Auto-delete converted files this long after creation.
ALLOWED_ORIGIN * server Restrict CORS to your frontend origin.
DATA_DIR OS temp dir server Where uploads/outputs are written (ephemeral on Cloud Run).
PROJ_DATA / GDAL_DATA server PROJ/GDAL data dirs if PDAL can't find proj.db (not needed with the Docker image).

Deployment

The reference deployment is frontend on Vercel + conversion server on Google Cloud Run, linked by the VITE_SERVER_URL env var.

  • Viewer → Vercel (or any static host). The frontend builds to static files (npm run builddist/). A ready-to-deploy copy with a beginner guide lives in the sibling web/ folder.

  • Conversion server → Google Cloud Run. PDAL is a native binary, so it cannot run on Vercel's serverless functions. The included Dockerfile packages Node + PDAL; gcloud run deploy --source . lets Cloud Build compile and host it (scales to zero → no idle cost). Full step-by-step guide: DEPLOY-cloudrun.md.

    Recommended Cloud Run flags bound memory/cost and keep results ephemeral:

    gcloud run deploy copc-convert --source . \
      --region asia-northeast3 --memory 4Gi --cpu 2 \
      --concurrency 1 --max-instances 2 --timeout 300 \
      --allow-unauthenticated \
      --set-env-vars MAX_UPLOAD_MB=30,TTL_MINUTES=30

Any other container host (Render, Fly.io, Cloud Run, a VM) works too — it just needs to run the Dockerfile.


Limitations & notes

  • Conversion needs PDAL — there is no pure-browser COPC writer; conversion is intentionally server-side to preserve all attributes reliably.
  • Rendering uses Cesium's PointPrimitiveCollection, which is great for the bounded, LOD-selected working set but is not a GPU-instanced renderer. Very dense views are kept in check by maxScreenSpaceError / maxLoadedNodes. A custom WebGL primitive would scale further — PRs welcome.
  • CRS support covers files with a WKT VLR (the common case), including compound CRSs (COMPD_CS → horizontal PROJCS is extracted) and feet/other linear units (heights are scaled to metres). Exotic CRSs still depend on proj4's coverage.
  • Upload size on Cloud Run is ~30 MB. Cloud Run rejects request bodies over ~32 MiB at the platform layer, so the convert-upload path is capped at 30 MB (MAX_UPLOAD_MB). This is a conversion limit only — viewing an existing COPC via URL streams with no size limit. Larger conversions need HTTP/2 or a bucket-upload flow, or running the server on a host without that cap.
  • Range + CORS — the host serving a .copc.laz must allow HTTP range requests and cross-origin reads. The bundled server does both.

Tech stack


Contributing

Issues and PRs are welcome — especially a GPU-instanced point renderer, EPT support, classification/elevation color ramps, and broader CRS handling. Please keep the library (src/lib/) framework-agnostic and dependency-light.


License

MIT

About

COPC 포인트 클라우드가 표출 및 변환되는 CesiumJS 기반 디지털 트윈

Topics

Resources

License

Stars

1 star

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors