maplibre-copc-layer 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +124 -0
- package/dist/index.d.mts +176 -0
- package/dist/index.mjs +645 -0
- package/package.json +52 -0
- package/src/cache-manager.ts +184 -0
- package/src/copclayer.ts +704 -0
- package/src/globe-control.ts +70 -0
- package/src/index.ts +13 -0
- package/src/shaders/edl.frag.glsl +49 -0
- package/src/shaders/edl.vert.glsl +6 -0
- package/src/shaders/points.frag.glsl +19 -0
- package/src/shaders/points.vert.glsl +21 -0
- package/src/shaders/shaders.d.ts +4 -0
- package/src/worker/index.ts +347 -0
- package/src/worker/sse.ts +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# maplibre-copc-layer
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/maplibre-copc-layer)
|
|
4
|
+
[](https://github.com/spatialty-io/maplibre-copc-layer/blob/main/LICENSE)
|
|
5
|
+
|
|
6
|
+
Render massive point clouds on [MapLibre GL JS](https://maplibre.org/) — powered by [COPC](https://copc.io/) and [Three.js](https://threejs.org/).
|
|
7
|
+
|
|
8
|
+
Stream [Cloud-Optimized Point Cloud (COPC)](https://copc.io/) data directly into MapLibre as a custom layer. Only the tiles visible on screen are fetched and rendered, enabling smooth visualization of billion-point datasets in the browser.
|
|
9
|
+
|
|
10
|
+
## Highlights
|
|
11
|
+
|
|
12
|
+
- **Streaming LOD** — Screen-space error (SSE) based level-of-detail fetches only what you see
|
|
13
|
+
- **Web Worker parsing** — COPC decoding and coordinate reprojection run off the main thread
|
|
14
|
+
- **LRU cache** — Configurable node count and memory limits keep the GPU lean
|
|
15
|
+
- **Eye-Dome Lighting** — EDL post-processing for depth perception without normals
|
|
16
|
+
- **Color modes** — RGB, height ramp, intensity, and flat white
|
|
17
|
+
- **Zero config** — Drop in a single `CopcLayer` class and go
|
|
18
|
+
|
|
19
|
+
## Demo
|
|
20
|
+
|
|
21
|
+
**[Live Demo](https://maplibre-copc-layer.spatialty.workers.dev/?copc=https%3A%2F%2Fgsvrg.ipri.aist.go.jp%2F3ddb-pds%2Fcopc%2F114112.copc.laz#17.95/35.657894/139.746455/-83.4/60)**
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install maplibre-copc-layer
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Peer dependencies:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install maplibre-gl three
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Quick Start
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
import maplibregl from 'maplibre-gl';
|
|
39
|
+
import { CopcLayer } from 'maplibre-copc-layer';
|
|
40
|
+
|
|
41
|
+
const map = new maplibregl.Map({
|
|
42
|
+
container: 'map',
|
|
43
|
+
style: 'https://demotiles.maplibre.org/style.json',
|
|
44
|
+
center: [139.7, 35.7],
|
|
45
|
+
zoom: 14,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const layer = new CopcLayer('https://example.com/pointcloud.copc.laz', {
|
|
49
|
+
colorMode: 'rgb',
|
|
50
|
+
pointSize: 4,
|
|
51
|
+
enableEDL: true,
|
|
52
|
+
onInitialized: ({ center }) => map.flyTo({ center, zoom: 16 }),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
map.on('load', () => map.addLayer(layer));
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## API
|
|
59
|
+
|
|
60
|
+
### `CopcLayer`
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
new CopcLayer(url: string, options?: CopcLayerOptions, layerId?: string)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
#### Options
|
|
67
|
+
|
|
68
|
+
| Option | Type | Default | Description |
|
|
69
|
+
|---|---|---|---|
|
|
70
|
+
| `pointSize` | `number` | `6` | Point size in pixels |
|
|
71
|
+
| `colorMode` | `'rgb' \| 'height' \| 'intensity' \| 'white'` | `'rgb'` | Coloring mode |
|
|
72
|
+
| `sseThreshold` | `number` | `8` | SSE threshold for LOD — lower values load more detail |
|
|
73
|
+
| `depthTest` | `boolean` | `true` | Enable depth testing |
|
|
74
|
+
| `maxCacheSize` | `number` | `100` | Max cached nodes |
|
|
75
|
+
| `maxCacheMemory` | `number` | `104857600` | Max cache memory in bytes (100 MB) |
|
|
76
|
+
| `enableEDL` | `boolean` | `false` | Enable Eye-Dome Lighting |
|
|
77
|
+
| `edlStrength` | `number` | `0.4` | EDL effect strength |
|
|
78
|
+
| `edlRadius` | `number` | `1.5` | EDL sampling radius |
|
|
79
|
+
| `wasmPath` | `string` | `undefined` | Custom path to `laz-perf.wasm` |
|
|
80
|
+
| `debug` | `boolean` | `false` | Enable debug logging |
|
|
81
|
+
| `onInitialized` | `(msg) => void` | — | Called with `{ nodeCount, center }` after COPC header loads |
|
|
82
|
+
|
|
83
|
+
#### Methods
|
|
84
|
+
|
|
85
|
+
| Method | Description |
|
|
86
|
+
|---|---|
|
|
87
|
+
| `setPointSize(size)` | Update point size |
|
|
88
|
+
| `setSseThreshold(threshold)` | Update SSE threshold |
|
|
89
|
+
| `setDepthTest(enabled)` | Toggle depth testing |
|
|
90
|
+
| `setEDLEnabled(enabled)` | Toggle Eye-Dome Lighting |
|
|
91
|
+
| `updateEDLParameters({ strength?, radius? })` | Update EDL parameters |
|
|
92
|
+
| `updateCacheConfig(config)` | Update cache limits at runtime |
|
|
93
|
+
| `clearCache()` | Clear all cached nodes |
|
|
94
|
+
| `getPointSize()` | Get current point size |
|
|
95
|
+
| `getColorMode()` | Get current color mode |
|
|
96
|
+
| `getSseThreshold()` | Get current SSE threshold |
|
|
97
|
+
| `getDepthTest()` | Get depth test state |
|
|
98
|
+
| `getEDLParameters()` | Get EDL parameters |
|
|
99
|
+
| `getOptions()` | Get all current options |
|
|
100
|
+
| `isLoading()` | Whether data is currently being fetched |
|
|
101
|
+
| `getNodeStats()` | Returns `{ loaded, visible }` node counts |
|
|
102
|
+
|
|
103
|
+
### `GlobeControl`
|
|
104
|
+
|
|
105
|
+
A MapLibre `IControl` that toggles between Mercator and Globe projections.
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
import { GlobeControl } from 'maplibre-copc-layer';
|
|
109
|
+
|
|
110
|
+
map.addControl(new GlobeControl());
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Development
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
pnpm install
|
|
117
|
+
pnpm dev # Dev server with demo app
|
|
118
|
+
pnpm test # Run tests
|
|
119
|
+
pnpm build # Build library
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## License
|
|
123
|
+
|
|
124
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import maplibregl from "maplibre-gl";
|
|
2
|
+
import * as THREE from "three";
|
|
3
|
+
|
|
4
|
+
//#region src/cache-manager.d.ts
|
|
5
|
+
interface CachedNodeData {
|
|
6
|
+
nodeId: string;
|
|
7
|
+
positions: Float64Array;
|
|
8
|
+
colors: Float32Array;
|
|
9
|
+
pointCount: number;
|
|
10
|
+
geometry?: THREE.BufferGeometry;
|
|
11
|
+
points?: THREE.Points;
|
|
12
|
+
materialConfig: {
|
|
13
|
+
colorMode: string;
|
|
14
|
+
pointSize: number;
|
|
15
|
+
depthTest: boolean;
|
|
16
|
+
};
|
|
17
|
+
lastAccessed: number;
|
|
18
|
+
sizeBytes: number;
|
|
19
|
+
}
|
|
20
|
+
interface CacheManagerOptions {
|
|
21
|
+
maxNodes?: number;
|
|
22
|
+
maxMemoryBytes?: number;
|
|
23
|
+
debug?: boolean;
|
|
24
|
+
}
|
|
25
|
+
declare class CacheManager {
|
|
26
|
+
private cache;
|
|
27
|
+
private memoryUsage;
|
|
28
|
+
private options;
|
|
29
|
+
constructor(options?: CacheManagerOptions);
|
|
30
|
+
get(nodeId: string): CachedNodeData | null;
|
|
31
|
+
set(nodeData: CachedNodeData, protectedNodes?: Set<string>): void;
|
|
32
|
+
has(nodeId: string): boolean;
|
|
33
|
+
delete(nodeId: string): boolean;
|
|
34
|
+
clear(): void;
|
|
35
|
+
updateOptions(newOptions: Partial<CacheManagerOptions>, protectedNodes?: Set<string>): void;
|
|
36
|
+
getCachedNodeIds(): string[];
|
|
37
|
+
size(): number;
|
|
38
|
+
static estimateNodeSize(positions: Float64Array | Float32Array, colors: Float32Array): number;
|
|
39
|
+
static createNodeData(nodeId: string, positions: Float64Array, colors: Float32Array, materialConfig: CachedNodeData['materialConfig']): CachedNodeData;
|
|
40
|
+
private ensureCacheLimits;
|
|
41
|
+
private disposeNodeResources;
|
|
42
|
+
private formatBytes;
|
|
43
|
+
private log;
|
|
44
|
+
}
|
|
45
|
+
//#endregion
|
|
46
|
+
//#region src/copclayer.d.ts
|
|
47
|
+
type ColorMode = 'rgb' | 'height' | 'intensity' | 'white';
|
|
48
|
+
interface CopcLayerOptions {
|
|
49
|
+
pointSize?: number;
|
|
50
|
+
colorMode?: ColorMode;
|
|
51
|
+
maxCacheSize?: number;
|
|
52
|
+
sseThreshold?: number;
|
|
53
|
+
depthTest?: boolean;
|
|
54
|
+
maxCacheMemory?: number;
|
|
55
|
+
debug?: boolean;
|
|
56
|
+
enableEDL?: boolean;
|
|
57
|
+
edlStrength?: number;
|
|
58
|
+
edlRadius?: number;
|
|
59
|
+
/** Path to laz-perf.wasm file for LAZ decompression */
|
|
60
|
+
wasmPath?: string;
|
|
61
|
+
onInitialized?: (message: {
|
|
62
|
+
nodeCount: number;
|
|
63
|
+
center: [number, number];
|
|
64
|
+
}) => void;
|
|
65
|
+
}
|
|
66
|
+
interface NodeStats {
|
|
67
|
+
loaded: number;
|
|
68
|
+
visible: number;
|
|
69
|
+
}
|
|
70
|
+
declare class CopcLayer implements maplibregl.CustomLayerInterface {
|
|
71
|
+
readonly id: string;
|
|
72
|
+
readonly type: 'custom';
|
|
73
|
+
readonly renderingMode: '3d';
|
|
74
|
+
readonly url: string;
|
|
75
|
+
map?: maplibregl.Map;
|
|
76
|
+
readonly camera: THREE.Camera;
|
|
77
|
+
readonly scene: THREE.Scene;
|
|
78
|
+
renderer?: THREE.WebGLRenderer;
|
|
79
|
+
readonly worker: Worker;
|
|
80
|
+
readonly cacheManager: CacheManager;
|
|
81
|
+
private readonly options;
|
|
82
|
+
private visibleNodes;
|
|
83
|
+
private workerInitialized;
|
|
84
|
+
private pendingRequests;
|
|
85
|
+
private requestQueue;
|
|
86
|
+
private lastCameraPosition;
|
|
87
|
+
private sceneCenter;
|
|
88
|
+
private colorTarget?;
|
|
89
|
+
private depthTarget?;
|
|
90
|
+
private edlMaterial?;
|
|
91
|
+
private edlQuadScene?;
|
|
92
|
+
private edlQuadCamera?;
|
|
93
|
+
private readonly _tempMatrix1;
|
|
94
|
+
private readonly _tempMatrix2;
|
|
95
|
+
private _lastEdlWidth;
|
|
96
|
+
private _lastEdlHeight;
|
|
97
|
+
private _lastUpdatePointsTime;
|
|
98
|
+
constructor(url: string, options?: CopcLayerOptions, layerId?: string);
|
|
99
|
+
private setupWorkerMessageHandlers;
|
|
100
|
+
private handleNodeLoaded;
|
|
101
|
+
private updateVisibleNodes;
|
|
102
|
+
private requestNodeData;
|
|
103
|
+
private needsMaterialUpdate;
|
|
104
|
+
private updateNodeMaterial;
|
|
105
|
+
private rebuildAllMaterials;
|
|
106
|
+
private removeFromRequestQueue;
|
|
107
|
+
private cancelAllPendingRequests;
|
|
108
|
+
private prioritizeNodeRequests;
|
|
109
|
+
onAdd(map: maplibregl.Map, gl: WebGLRenderingContext): Promise<void>;
|
|
110
|
+
updateCacheConfig(config: Partial<CopcLayerOptions>): void;
|
|
111
|
+
setPointSize(size: number): void;
|
|
112
|
+
setSseThreshold(threshold: number): void;
|
|
113
|
+
setDepthTest(enabled: boolean): void;
|
|
114
|
+
/**
|
|
115
|
+
* Compute camera altitude in meters above sea level.
|
|
116
|
+
* MapLibre's getCameraAltitude() returns NaN in Globe mode because
|
|
117
|
+
* the Globe transform's internal _pixelPerMeter is never initialized.
|
|
118
|
+
* We replicate the formula using publicly accessible values.
|
|
119
|
+
*/
|
|
120
|
+
private computeCameraAltitude;
|
|
121
|
+
private updatePoints;
|
|
122
|
+
render(_gl: WebGLRenderingContext, options: maplibregl.CustomRenderMethodInput): void;
|
|
123
|
+
private shouldUpdateSceneCenter;
|
|
124
|
+
onRemove(_map: maplibregl.Map, _gl: WebGLRenderingContext): void;
|
|
125
|
+
private setupEDL;
|
|
126
|
+
private updateEDLSize;
|
|
127
|
+
private createPointMaterial;
|
|
128
|
+
getPointSize(): number;
|
|
129
|
+
getColorMode(): ColorMode;
|
|
130
|
+
getSseThreshold(): number;
|
|
131
|
+
getDepthTest(): boolean;
|
|
132
|
+
getOptions(): Readonly<CopcLayerOptions>;
|
|
133
|
+
isLoading(): boolean;
|
|
134
|
+
getNodeStats(): NodeStats;
|
|
135
|
+
clearCache(): void;
|
|
136
|
+
setEDLEnabled(enabled: boolean): void;
|
|
137
|
+
updateEDLParameters(params: {
|
|
138
|
+
strength?: number;
|
|
139
|
+
radius?: number;
|
|
140
|
+
}): void;
|
|
141
|
+
getEDLParameters(): {
|
|
142
|
+
enabled: boolean;
|
|
143
|
+
strength: number;
|
|
144
|
+
radius: number;
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
//#endregion
|
|
148
|
+
//#region src/globe-control.d.ts
|
|
149
|
+
/**
|
|
150
|
+
* A MapLibre control that toggles between Mercator and Globe projections.
|
|
151
|
+
*/
|
|
152
|
+
declare class GlobeControl implements maplibregl.IControl {
|
|
153
|
+
private map?;
|
|
154
|
+
private container?;
|
|
155
|
+
private button?;
|
|
156
|
+
private isGlobe;
|
|
157
|
+
onAdd(map: maplibregl.Map): HTMLElement;
|
|
158
|
+
onRemove(): void;
|
|
159
|
+
getDefaultPosition(): maplibregl.ControlPosition;
|
|
160
|
+
private toggle;
|
|
161
|
+
private updateIcon;
|
|
162
|
+
}
|
|
163
|
+
//#endregion
|
|
164
|
+
//#region src/worker/sse.d.ts
|
|
165
|
+
type Vec3 = [number, number, number];
|
|
166
|
+
/**
|
|
167
|
+
* Calculates Screen Space Error (SSE) for a point cloud node.
|
|
168
|
+
* Higher SSE = more visible error = should render with higher detail.
|
|
169
|
+
*
|
|
170
|
+
* @param maxDistance - Optional maximum effective distance. When the camera is
|
|
171
|
+
* extremely far away (e.g. Globe View at low zoom), the distance is capped
|
|
172
|
+
* to this value so that SSE does not approach 0.
|
|
173
|
+
*/
|
|
174
|
+
declare function computeScreenSpaceError(cameraCenter: Vec3, center: Vec3, fov: number, geometricError: number, screenHeight: number, maxDistance?: number): number;
|
|
175
|
+
//#endregion
|
|
176
|
+
export { CacheManager, type CacheManagerOptions, type CachedNodeData, type ColorMode, CopcLayer, type CopcLayerOptions, GlobeControl, type NodeStats, type Vec3, computeScreenSpaceError };
|