wasm-spatial-core
    Preparing search index...

    wasm-spatial-core

    wasm-spatial-core

    Generate Cesium 3D Tiles in the browser. Drag in a LAS / GeoTIFF / GLB, get back a streaming tileset.json + pnts / quantized-mesh tiles β€” no server, no upload, no Cesium ion quota. The data never leaves the user's machine.

    CI npm version License: MIT

    Tests WASM

    wasm-spatial-core demo

    🌐 Live Demo Β· πŸ“¦ npm Β· πŸ“– API Docs Β· πŸ“Š Head-to-head vs py3dtiles Β· πŸ—ΊοΈ Roadmap

    πŸ§ͺ 30-second try (drop a .las file, get a Cesium tileset):

    <script type="module">
    import init, { parsePointCloudAuto, generateTileset }
    from 'https://esm.run/wasm-spatial-core';
    await init();

    // User drops a LAS file β†’ parse β†’ octree β†’ 3D Tiles.
    // All in the browser. Bytes never touch a server.
    const cloud = parsePointCloudAuto(lasFileBytes);
    const tileset = generateTileset(cloud.positions(), 50000, 10);
    // β†’ { tilesetJson, tiles: Uint8Array[], ... } β€” feed straight into Cesium.C3DTileset
    </script>

    Building something with wasm-spatial-core? Add your project here β€” open an issue using the feature request template or edit this section directly.

    (No production adopters listed yet β€” be the first. This section grows as the project gets real-world use.)


    If you've ever tried to view a customer's LAS scan in Cesium, you've hit one of:

    • Cesium ion wants you to upload their data β€” GDPR / ITAR / NDA say no.
    • Desktop tools (CloudCompare, Q2C) need install + per-machine license + manual steps.
    • Server-side pipelines (PDAL, untwine) mean infra cost + a backend to maintain.
    • Just-plain-JS parsers choke at 1M+ points (multi-second hangs).

    wasm-spatial-core is a Rust β†’ WebAssembly engine that does the whole LAS β†’ octree β†’ 3D Tiles pipeline client-side. The output is byte-compatible with what Cesium.C3DTileset consumes β€” so you can build a zero-backend, zero-upload Cesium viewer that runs on a static host.

    Cesium ion (SaaS) Desktop tools wasm-spatial-core
    Customer data leaves the browser ☁️ yes (uploaded) πŸ’» no (local) πŸ’» no (local)
    Deployable on intranet / air-gap ❌ per-machine βœ… static files
    Per-GB / per-user cost βœ… yes license fee ❌ free (MIT)
    Works inside a web app βœ… ❌ βœ…
    Streams multi-GB scans partial n/a βœ… COPC range fetch

    Not a PROJ / QGIS replacement. CRS coverage is intentionally focused (WGS-84 / Web Mercator / UTM / China offsets). For arbitrary EPSG reprojection, pre-transform with PROJ and feed WGS-84 / ENU.


    Core (the killer use case):

    • πŸš€ LAS / PLY / OBJ β†’ 3D Tiles (pnts) β€” octree partition + Draco optional
    • πŸ”οΈ GeoTIFF β†’ Quantized-Mesh Terrain β€” Cesium CesiumTerrainProvider-compatible (proven by headless test, see W3.6 in ROADMAP_V2.md)
    • πŸ“‘ COPC range-fetch streaming β€” multi-GB scans without loading the whole file

    Also in the box (used internally, exposed as public APIs):

    • πŸ—ΊοΈ Coordinate transforms: WGS-84 ↔ GCJ-02 / BD-09 / Web Mercator / UTM (~30–250Γ— faster than JS equivalents β€” see PERFORMANCE.md)
    • 🧊 Spatial IR + mesh edit: GLB ingest, OBB split, plane clip, QEM decimation
    • ⚑ WebGPU compute kernels (with WASM fallback)
    • πŸ”’ Zero server, zero upload, zero runtime dependencies

    npm install wasm-spatial-core ships a prebuilt 1.2 MB WASM binary β€” no native deps, no postinstall, works on any static host (GitHub Pages, S3, nginx, even python -m http.server).


    Live, drag-and-drop, no backend:

    Demo What it shows
    Cesium 3D Tiles Drop a .las / .tif / .glb β†’ octree β†’ 3D Tiles β†’ Cesium globe. The killer demo.
    COPC streaming Range-fetch a multi-GB .copc scan byte-range by byte-range (no full download).
    Terrain viewer GeoTIFF β†’ quantized-mesh, with cut / flatten / fill edits.
    Demo hub All examples + benchmarks.

    Run any of them locally with npm run demo.


    npm install wasm-spatial-core
    
    import init, { parsePointCloudAuto, generateTileset } from 'wasm-spatial-core';
    // Cesium loaded separately (CDN or npm) β€” we only emit the tile bytes.
    await init();

    // 1. User drops a .las file. Parse it.
    const cloud = parsePointCloudAuto(lasFileBytes);
    // cloud.positions() β†’ Float32Array [x,y,z, ...]

    // 2. Build octree β†’ 3D Tiles (pnts + tileset.json).
    const tileset = generateTileset(cloud.positions(), 50000 /* max pts/node */, 10 /* depth */);
    // tileset.tilesetJson() β†’ string
    // tileset.tile(i) β†’ Uint8Array (one pnts blob per tile)

    // 3. Hand the tiles to Cesium via blob URLs (no server needed).
    const json = JSON.parse(tileset.tilesetJson());
    for (let i = 0; i < tileset.tileCount; i++) {
    const url = URL.createObjectURL(new Blob([tileset.tile(i)]));
    json.root.content.uri = json.root.content.uri.replace(`tile_${i}.pnts`, url);
    // (in practice, walk the tree and rewrite each leaf's content.uri)
    }
    const cesiumTileset = await Cesium.C3DTileset.fromUrl(
    URL.createObjectURL(new Blob([JSON.stringify(json)], { type: 'application/json' })),
    );
    viewer.scene.primitives.add(cesiumTileset);

    That's the whole story β€” no backend, no upload, no ion token. For a working drag-and-drop demo, see the Point Cloud + Cesium example.

    Optional: Draco compression (~5Γ— smaller tiles)
    npm install draco3d
    
    import { compressTilesetWithDraco } from 'wasm-spatial-core';
    import { createEncoderModule } from 'draco3d';

    const encoder = await createEncoderModule({});
    const compressed = compressTilesetWithDraco(tileset, encoder, { quantizationBits: 11 });
    // Typical ratio: ~20% of original size, position-color pairing preserved.

    npm install wasm-spatial-core ships a prebuilt WASM binary compiled with point-cloud + geotiff. That gives you:

    Included in npm Not in npm (custom wasm-pack build)
    LAS, PLY, OBJ, PCD parsing LAZ / COPC (laz-support)
    Octree + 3D Tiles (pnts) E57 (e57-support)
    GeoTIFF β†’ quantized-mesh terrain Terrain deformation (terrain-edit)
    Coordinates, GeoJSON, MVT, spatial analysis Spatial IR + GLB ingest (mesh-ingest)
    Mesh QEM / clip / OBB split (mesh-edit, needs mesh-ingest)
    WebGPU compute kernels (webgpu)

    Format counts: 10+ read/write paths in the default npm build (LAS/PLY/OBJ/PCD, GeoJSON, MVT, WKT/WKB, GeoTIFF, GPX, TopoJSON, 3D Tiles/glTF output, …). 15+ when optional format features are enabled (LAZ/COPC, E57, GLB ingest, …).

    Runtime checks: supportsLaz(), supportsGeotiff(), lazStatus().

    CI runs cargo test --all-features β€” 857 tests pass (plus 34 #[ignore]d performance benchmarks) across the full feature matrix. The live count is printed by CI on every run; see the latest rust job log for the current number.


    LAS / PLY / OBJ  (npm default)
    LAZ / COPC / E57 (optional build features β€” see table above)
    β”‚
    β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ WASM Parser β”‚ Browser-side; format set depends on build features
    β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
    β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ Octree Build β”‚ 8-way spatial partitioning
    β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
    β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ pnts Encoder β”‚ 3D Tiles Point Cloud binary
    β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
    β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ tileset.json β”‚ β”‚ Draco Compress β”‚ Optional (~20% ratio)
    β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
    β”‚ β”‚
    β–Ό β–Ό
    Cesium / Three.js β€” interactive 3D
    GeoTIFF (.tif)
    β”‚
    β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ WASM Parser β”‚ Float32/16/8, strip/tile, DEFLATE
    β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
    β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ Quantized-Mesh β”‚ Cesium terrain binary format
    β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
    β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ tileset.json β”‚ LOD pyramid (zoom 0..N)
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

    The whole reason this exists: do in-browser what previously needed a server. On Apple M2, single-thread WASM (no WebGPU):

    Dataset Points Parse Octree + Tileset Total
    sample.las 1K β€” < 1 ms β€”
    Synthetic 500K 36 ms 166 ms 205 ms
    Synthetic 10M 1.1 s 3.6 s 4.8 s
    Synthetic 100M 0.4 s 6.8 s 8.5 s

    100M-point number is native release (Rust); WASM is ~1.5Γ— slower but still well under 30 seconds. Compare: potree (JS) takes ~3 s just for octree at 1M.

    Coordinate conversion (the same engine, different workload):

    Operation Pure JS WASM Speedup
    WGS84 β†’ GCJ-02 ~1,200 ms ~45 ms ~27Γ—
    WGS84 β†’ Mercator ~800 ms ~12 ms ~67Γ—

    See PERFORMANCE.md for full methodology + comparison with potree / three.js Octree / las-js.


    Format Read Feature Flag
    LAS (1.2–1.4, Format 0–6) βœ… point-cloud
    LAZ (compressed) βœ… laz-support
    COPC (Cloud Optimized) βœ… laz-support
    PLY (ASCII + binary) βœ… point-cloud
    OBJ βœ… point-cloud
    PCD (ASCII + binary) βœ… point-cloud
    E57 βœ… e57-support
    Format Read Write
    GeoJSON βœ… βœ…
    MVT (Vector Tiles) βœ… βœ…
    WKT / WKB βœ… βœ…
    GeoTIFF (Terrain) βœ… β€”
    glTF 2.0 / GLB β€” βœ…
    3D Tiles (pnts) β€” βœ…
    3D Tiles (b3dm) β€” βœ…
    3D Tiles (quantized-mesh) β€” βœ…
    System Direction
    WGS-84 ↔ GCJ-02 / BD-09 βœ…
    WGS-84 ↔ Web Mercator (EPSG:3857) βœ…
    WGS-84 ↔ CGCS2000 βœ…
    WGS-84 ↔ UTM βœ…

    R-Tree / Octree indexing, bounding box / KNN queries, haversine / vincenty distance, polygon boolean ops, Douglas-Peucker simplification, convex / concave hull, DBSCAN / grid clustering, TIN interpolation, and more.


    import { loadSpatialCore } from 'wasm-spatial-core';
    const wasm = await loadSpatialCore();

    // Auto-detect format
    const cloud = wasm.parsePointCloudAuto(bytes);
    console.log(cloud.count()); // point count
    console.log(cloud.positions()); // Float32Array [x,y,z,...]
    console.log(cloud.colors()); // Uint8Array [r,g,b,...] | null

    // Octree
    const octree = wasm.buildOctree(cloud.positions(), 50000, 10);
    console.log(octree.nodeCount()); // node count
    console.log(octree.depth()); // tree depth

    // 3D Tiles tileset
    const tileset = wasm.generateTileset(cloud.positions(), 50000, 10);
    console.log(tileset.tileCount()); // tile count
    console.log(tileset.tilesetJson()); // tileset.json string
    import { compressTilesetWithDraco, buildDracoTileset } from 'wasm-spatial-core';
    import { createEncoderModule } from 'draco3d';

    const encoderModule = await createEncoderModule({});

    // Compress all tiles
    const results = compressTilesetWithDraco(tileset, encoderModule, {
    quantizationBits: 11, // 8–18, default 11
    encodeSpeed: 5, // 0–10, default 5
    decodeSpeed: 5, // 0–10, default 5
    compressColors: false, // also compress RGB (default false)
    });

    // Or build a complete compressed tileset
    const { tiles, totalCompressedSize, compressionRatio } =
    buildDracoTileset(tileset, encoderModule);
    const coords = new Float64Array([116.404, 39.915, 121.474, 31.230]);
    const gcj02 = wasm.batchWgs84ToGcj02(coords); // batch transform
    wasm.batchWgs84ToGcj02InPlace(mutable); // zero-copy in-place
    const [zone, easting, northing] = wasm.wgs84ToUtm(116.404, 39.915);
    // Chunked output: parses the full JSON first, then emits coordinate batches
    // (progress callbacks + lower peak coord memory β€” not byte-stream input).
    wasm.parseGeoJsonStream(hugeGeojson, 500, (chunk, processed, total) => { /* ... */ });

    // Lower memory per iteration: one feature at a time (input string still required)
    const iter = wasm.parseGeoJsonLazy(hugeGeojson);

    πŸ“– Full API Docs


    git clone https://github.com/reed-soul/wasm-spatial-core.git
    cd wasm-spatial-core

    # Point cloud + GeoTIFF
    wasm-pack build --target web --release --out-dir pkg -- --features point-cloud,geotiff

    # Run demos
    npm run demo
    Feature In npm Default crate Description
    single-thread βœ… βœ… Zero-config, works everywhere
    point-cloud βœ… ❌ LAS/PLY/OBJ/PCD + octree + 3D Tiles
    geotiff βœ… ❌ GeoTIFF terrain + quantized-mesh
    multi-thread ❌ ❌ Web Workers + SharedArrayBuffer
    laz-support ❌ ❌ LAZ/COPC decompression (+ ~400 KB WASM)
    e57-support ❌ ❌ E57 format
    terrain-edit ❌ ❌ Heightfield flatten/deform (requires geotiff)
    mesh-ingest ❌ ❌ Spatial IR + GLB ingest (Wave 2)
    mesh-edit ❌ ❌ Mesh QEM / OBB split (requires mesh-ingest)
    draco-support ❌ ❌ Draco compression API (JS-side via draco3d)

    Doc Scope
    VISION.md Product vision β€” Web3D spatial compute engine (core only)
    ROADMAP_V2.md Active plan β€” Waves 1–5 (runtime, IR, terrain, GPU, mesh edit)
    docs/ENGINE_BOUNDARY.md What is in the engine vs your application
    ROADMAP_V1.md βœ… Completed β€” point cloud β†’ 3D Tiles browser pipeline

    V1 highlights (done): LAS β†’ octree β†’ 3D Tiles (npm) Β· LAZ/COPC/E57 (optional builds) Β· GeoTIFF terrain Β· Draco Β· multi-thread WASM Β· Node.js batch API

    V2 next: spatial IR Β· terrain/mesh edit (source available; not in default npm) Β· WebGPU Β· incremental tiles β€” see issue templates (start at W2)


    See CONTRIBUTING.md.


    MIT License β€” Β© 2026 Zhiqi Weilai


    Built with πŸ¦€ Rust + πŸ•ΈοΈ WebAssembly

    Native spatial computing in every browser.