Skip to content

Kanahiro/maplibre-copc-layer

Repository files navigation

maplibre-copc-layer

npm version license

A MapLibre GL JS custom layer for streaming and rendering Cloud-Optimized Point Cloud (COPC) data, powered by Three.js.

Only the tiles visible on screen are fetched via SSE-based LOD, enabling smooth visualization of massive point clouds in the browser.

Live Demo

Features

  • Streaming LOD — Screen-space error based level-of-detail fetches only what you see
  • Web Worker — COPC decoding and coordinate reprojection run off the main thread
  • LRU cache — Configurable node count and memory limits
  • Ambient Occlusion — SSAO post-processing for depth perception
  • Color modes — RGB, height ramp, intensity, classification, and white
  • Custom color expressions — User-defined linear/discrete color ramps for height and intensity
  • Filtering — By classification, intensity range, or bounding box (WGS84)

Install

npm install maplibre-copc-layer maplibre-gl three

Usage

import maplibregl from 'maplibre-gl';
import { CopcLayer } from 'maplibre-copc-layer';

const map = new maplibregl.Map({
  container: 'map',
  style: 'https://demotiles.maplibre.org/style.json',
  center: [139.7, 35.7],
  zoom: 14,
});

const layer = new CopcLayer('https://example.com/pointcloud.copc.laz', {
  colorMode: 'rgb',
  pointSize: 4,
  enableSSAO: true,
  onInitialized: ({ bounds }) => map.flyTo({
    center: [(bounds.minx + bounds.maxx) / 2, (bounds.miny + bounds.maxy) / 2],
    zoom: 16,
  }),
});

map.on('load', () => map.addLayer(layer));

API

new CopcLayer(url, options?, layerId?)

Option Type Default Description
pointSize number 6 Point size in pixels
colorMode 'rgb' | 'height' | 'intensity' | 'classification' | 'white' 'rgb' Coloring mode
heightColor ColorExpression auto Color ramp for height mode. Default: blue→yellow→red across data bounds
intensityColor ColorExpression auto Color ramp for intensity mode. Default: black→white (0–1)
classificationColors Record<number, RGBColor> ASPRS defaults Classification code colors (0–1 RGB)
filter PointFilter {} Filter points by classification, intensity range, or bounding box
alwaysShowRoot boolean false Always show root node even when SSE is below threshold
sseThreshold number 8 SSE threshold for LOD — lower loads more detail
depthTest boolean true Enable depth testing
maxCacheSize number 100 Max cached nodes
maxCacheMemory number 104857600 Max cache memory in bytes (100 MB)
enableSSAO boolean false Enable Screen Space Ambient Occlusion
ssaoStrength number 1.0 SSAO effect strength
ssaoRadius number 8.0 SSAO sampling radius in pixels
debug boolean false Enable debug logging
onInitialized (msg) => void Called with { nodeCount, bounds } after COPC header loads. bounds contains minx/maxx/miny/maxy/minz/maxz in WGS84

Methods

Method Description
setPointSize(size) Update point size
setColorMode(mode) Switch color mode without reloading data
setHeightColor(expr) Update height color expression (instant, no reload)
setIntensityColor(expr) Update intensity color expression (instant, no reload)
setClassificationColors(colors) Update classification colors (instant, no reload)
setSseThreshold(threshold) Update SSE threshold
setDepthTest(enabled) Toggle depth testing
setSSAOEnabled(enabled) Toggle Screen Space Ambient Occlusion
setSSAOParameters({ strength?, radius? }) Update SSAO parameters
setFilter(filter) Update point filter (classification / intensity / bbox)
getFilter() Get current point filter
setCacheConfig(config) Update cache limits at runtime
clearCache() Clear all cached nodes
isLoading() Whether data is currently being fetched
getNodeStats() Returns { loaded, visible } node counts

Examples

Height-based coloring with custom color ramp

ColorExpression uses a MapLibre Style-like syntax: ["linear", stop, color, stop, color, ...] or ["discrete", ...].

const layer = new CopcLayer('https://example.com/pointcloud.copc.laz', {
  colorMode: 'height',
  // Linear interpolation: blue at 0m, green at 50m, red at 100m
  heightColor: ['linear', 0, [0, 0, 1], 50, [0, 1, 0], 100, [1, 0, 0]],
});

Discrete height coloring

const layer = new CopcLayer('https://example.com/pointcloud.copc.laz', {
  colorMode: 'height',
  // Step function: blue below 50m, green 50-100m, red above 100m
  heightColor: ['discrete', 0, [0, 0, 1], 50, [0, 1, 0], 100, [1, 0, 0]],
});

Custom intensity coloring

const layer = new CopcLayer('https://example.com/pointcloud.copc.laz', {
  colorMode: 'intensity',
  // Intensity values are normalized 0-1
  intensityColor: ['linear', 0, [0, 0, 0.2], 0.5, [1, 1, 0], 1, [1, 0, 0]],
});

Updating colors at runtime (no data reload)

// Switch color mode instantly
layer.setColorMode('height');

// Update height color ramp — reflected immediately
layer.setHeightColor(['linear', 0, [1, 1, 1], 200, [1, 0, 0]]);

// Update classification colors
layer.setClassificationColors({
  2: [0.4, 0.2, 0.1],  // Ground: brown
  6: [0.8, 0.1, 0.1],  // Building: red
});

Filtering points

const layer = new CopcLayer('https://example.com/pointcloud.copc.laz', {
  filter: {
    // Show only ground and buildings
    classification: new Set([2, 6]),
    // Intensity range (0-1)
    intensityRange: [0.1, 0.9],
    // Bounding box in WGS84
    bbox: { minx: 139.7, maxx: 139.8, miny: 35.6, maxy: 35.7 },
  },
});

// Update filter at runtime
layer.setFilter({
  classification: new Set([2, 3, 4, 5, 6]),
});

Ambient Occlusion

const layer = new CopcLayer('https://example.com/pointcloud.copc.laz', {
  enableSSAO: true,
  ssaoStrength: 1.0,
  ssaoRadius: 8.0,
});

Development

pnpm install
pnpm dev       # Dev server with demo app
pnpm test      # Run tests
pnpm build     # Build library

Third-Party Notices

This project bundles laz-perf (Apache License 2.0).

License

Apache-2.0