SDK JS
    Preparing search index...

    SDK JS

    High-performance, modular JavaScript and TypeScript for decoding and rendering imagery and massive 3D geospatial data on the web, at native speed, in any browser.

    This is the API reference, generated from the source. Every exported type, class, function and option in the published packages is documented here. If you are looking for guides, tutorials and the interactive playground, start at the Developer Portal.

    The codecs are the engine room. Each one is written in pure Rust and compiled to WebAssembly, SIMD-accelerated and runnable off the main thread, and each turns a compressed payload into GPU-ready typed arrays directly in the browser. No server-side transcode step, no native toolchain, no plugin.

    Imagery. turbo-webp (WebP), turbo-jxl (JPEG XL), turbo-lerc (Esri LERC elevation and scientific rasters) and ript (scientific imagery). Decode a tile in a few kilobytes of JavaScript and hand the pixels straight to WebGL, or to a canvas, or to your own analysis code. Nothing here requires a map.

    3D geospatial. turbo-spz (SPZ v1 through v4 gaussian splats), turbo-lepcc (Esri LEPCC point clouds) and bvc, the Bitruvius container, which carries quantized splats and point clouds in one format.

    Every codec stands alone. Take one, take several, or let a viewer SDK drive them for you.

    Renderers and streaming engines layered on a shared, viewer-agnostic core. The MapLibre GL JS integration ships today: drop a single custom layer into a map and it streams a tileset georeferenced, decodes it off-thread, places it against terrain, and holds the working set under a texture-safe memory budget.

    3D Gaussian splats, LiDAR point clouds, photogrammetry meshes, and OGC 3D Tiles / Esri I3S scenes, georeferenced into your map with a single layer. Because the rendering, streaming and codec layers depend on no specific viewer, the same core is designed to power additional viewer integrations.

    npm install @bitruvius/sdk-maplibre
    

    maplibre-gl is a peer dependency. Supported range: >=5 <7. Version 6 is the default target and what the SDK is developed against.

    import * as maplibregl from 'maplibre-gl';
    import { prepareMapLibre, Tiles3DSplatLayer } from '@bitruvius/sdk-maplibre';

    await prepareMapLibre(maplibregl);

    const map = new maplibregl.Map({ container: 'map', style, pitch: 60 });
    map.on('load', () => {
    map.addLayer(
    new Tiles3DSplatLayer({
    id: 'splats',
    url: 'https://…/tileset.json', // any OGC 3D-Tiles gaussian-splat tileset
    }),
    );
    });

    One import and one addLayer. Decode runs in a Web Worker pool, placement auto-detects flat versus 3D terrain, and vertical datums resolve from the source metadata so orthometric and ellipsoidal datasets line up without you supplying a geoid.

    prepareMapLibre is a one-line pre-flight for maplibre-gl v6, which loads its worker as a separate module that many bundlers do not emit. Without it, every worker-backed source fails silently. It is idempotent, and a no-op on v5.

    Every release is published to an immutable, versioned CDN tree at https://cdn.bitruvius.com/sdk/v<version>/. Pin a version and its bytes never change.

    <script type="module">
    import * as maplibregl from 'https://unpkg.com/maplibre-gl@^6.0.0/dist/maplibre-gl.mjs';
    const { prepareMapLibre, Tiles3DMeshLayer } = await import(
    'https://cdn.bitruvius.com/sdk/v0.1.1/bundles/bitruvius-sdk-maplibre.esm.min.js'
    );

    await prepareMapLibre(maplibregl); // before the first `new maplibregl.Map(…)`
    const map = new maplibregl.Map({ container: 'map', style, pitch: 60 });
    map.on('load', () => map.addLayer(new Tiles3DMeshLayer({ id: 'city', url: 'https://…/tileset.json' })));
    </script>

    Pair the stylesheet from the same pinned spec: https://unpkg.com/maplibre-gl@^6.0.0/dist/maplibre-gl.css.

    maplibre-gl v6 is ESM-only, so the UMD <script src> build is gone and you pass the namespace to the SDK entry points that take one. On v5 the classic UMD script tag plus IIFE bundle pairing keeps working unchanged. CDN objects are served with Access-Control-Allow-Origin: * and Cross-Origin-Resource-Policy: cross-origin, so cross-origin ESM, worker and wasm loading work without per-consumer workarounds.

    Each release also ships a manifest.json next to the bundles, listing every published file for build-time resolution.

    Vector layers can be projected onto your 3D content, the way a desktop GIS does it: a parcel boundary, a flood zone or a site plan climbs the buildings and the mesh instead of being hidden underneath them or slicing through.

    import { enableVectorDrape } from '@bitruvius/sdk-maplibre';

    const drape = enableVectorDrape(map, { layers: [meshLayer.id] });

    Any fill, line, circle, raster or heatmap layer sitting above your 3D layer in the style order is draped automatically. That ordering is the whole control surface: move a layer above the 3D layer and it drapes, move it below and it does not. Symbol layers are deliberately excluded, because labels are meant to stay upright in screen space rather than being painted onto a wall.

    The controller lets you retarget or disable it at runtime:

    drape.addLayer(anotherMeshLayer.id);
    drape.setOpacity(0.6);
    drape.setEnabled(false);
    drape.dispose();

    Two things worth knowing before you rely on it. The projection is nadir, so it is exact on ground and roofs and stretches on near-vertical faces, which is the same trade-off Esri makes for draped overlays. And the drape is captured into an atlas (2048 x 2048 by default, atlasSize), so very fine linework across a wide extent can soften. Both are tunable rather than fixed.

    Receivers are the mesh layers today: 3D Tiles and I3S meshes, buildings and integrated mesh. Splats and point clouds are not drape receivers.

    A 3D Object layer carries the service's attribute table, so buildings can be coloured by what they are rather than by what the capture happened to look like, and narrowed down to the ones you care about.

    import { I3sMeshLayer } from '@bitruvius/sdk-maplibre';

    const layer = new I3sMeshLayer({
    id: 'buildings',
    url: 'https://…/SceneServer',
    symbology: {
    type: 'classBreaks',
    field: 'height',
    breaks: [
    { maxValue: 10, color: [0, 0.83, 1] },
    { maxValue: 30, color: [1, 0.85, 0.3] },
    { maxValue: 1e9, color: [1, 0.45, 0.2] },
    ],
    defaultColor: [0.42, 0.45, 0.5],
    },
    definitionExpression: "building = 'apartments' AND height <= 20",
    });

    Both change at runtime, and both compose: the filter decides which features are visible, the symbology decides what the visible ones look like.

    await layer.setSymbology({
    type: 'uniqueValue',
    field: 'building',
    values: [{ value: 'retail', color: [1, 0.45, 0.2] }],
    defaultColor: [0.42, 0.45, 0.5],
    });
    await layer.setFilter('height > 40');
    await layer.setFilter("building IN ('retail', 'commercial')", { mode: 'xray' });
    await layer.setFilter(null); // clear

    definitionExpression is the same SQL shape ArcGIS uses, so an expression copied from a scene layer works unchanged: comparisons, AND / OR / NOT, IN, LIKE, BETWEEN and IS NULL. Fields are matched across storage types, so a value published as 3 and one published as "3" compare equal, and floats compare within f32 round-trip error rather than exactly. mode: 'xray' dims what the filter excludes instead of removing it, when the surrounding context still needs to read.

    Two things make this cheap enough to drive from a UI. Styling is applied per feature on the GPU, so switching schemes costs no re-download and no re-tessellation. And only the columns your symbology and filter actually reference are fetched, per node, and cached — a filter on one field never pulls the rest of the attribute table. Discover what a service publishes with layer.getAttributeFields().

    The stack is modular and loosely coupled, so you take only what you need. The MapLibre integration is the single @bitruvius/sdk-maplibre package. Beneath it sits a viewer-agnostic stack of renderers, streaming engines and runtimes designed for reuse across integrations, plus the standalone codecs.

    Two opt-in extras exist that you import directly only when you need them: @bitruvius/geoid for explicit control over the orthometric (mean sea level) datum, and @bitruvius/esri-auth for secured ArcGIS services.

    Package Role
    @bitruvius/sdk-maplibre The MapLibre GL JS integration: custom layers for 3D-Tiles and Esri I3S splats, point clouds and textured meshes, instanced models, Esri LERC terrain, and a geographically accurate sun and time-of-day control, with datum-aware and terrain-aware placement
    Package Role
    @bitruvius/splats Gaussian-splat renderer: EWA splatting, spherical harmonics, depth-sorted
    @bitruvius/ptcloud LiDAR point-cloud renderer with RGB, intensity and classification color modes
    @bitruvius/mesh Textured OGC 3D-Tiles and I3S mesh renderer
    @bitruvius/raster Raster visualization: colormap, hillshade, 3D terrain
    @bitruvius/point-symbols Instanced icon and billboard symbology with GPU picking
    Package Role
    @bitruvius/tiles3d OGC 3D-Tiles streaming engine for splat, mesh and point payloads. Pure, and agnostic to both decoder and renderer
    @bitruvius/i3s Esri I3S streaming engine for point clouds and integrated mesh
    @bitruvius/gltf glTF 2.0 / GLB and OGC 3D-Tiles container parser, pure TypeScript
    @bitruvius/tiles3d-mesh Composed, off-main-thread 3D-Tiles mesh decoder
    @bitruvius/tiles3d-points Composed, off-main-thread 3D-Tiles point-cloud decoder

    Standalone pure-Rust to WebAssembly decoders, SIMD-accelerated and off-main-thread. Use any one on its own, or let the MapLibre SDK drive them for you.

    Package Role
    @bitruvius/turbo-spz SPZ gaussian-splat decode, v1 through v4
    @bitruvius/bvc BVC container decode: quantized splats and point clouds
    @bitruvius/turbo-lepcc Esri LEPCC point-cloud decode
    @bitruvius/turbo-lerc Esri LERC raster decode
    @bitruvius/ript RIPT scientific-raster decode
    @bitruvius/turbo-webp WebP image decode
    @bitruvius/turbo-jxl JPEG XL image decode
    Package Role
    @bitruvius/draco Draco geometry decode seam for @bitruvius/gltf
    @bitruvius/ktx2 KTX2 and Basis texture transcode seam for @bitruvius/gltf
    @bitruvius/meshopt meshopt decode seam for @bitruvius/gltf
    Package Role
    @bitruvius/geoid EGM96 geoid undulation, converting between WGS84 ellipsoidal and orthometric (MSL) heights
    @bitruvius/esri-auth Opt-in ArcGIS authentication for secured services
    @bitruvius/foundation Domain-agnostic base: task and worker scheduling, capability detection, config, errors, request-auth seam
    @bitruvius/geo-core Geospatial foundation: decode, render and viewer contracts, ENU/ECEF/mercator math, dequant reference
    @bitruvius/render-runtime WebGL2 plumbing: programs, buffers, textures, depth sort
    @bitruvius/codec-runtime wasm loading and capability-gated decode-backend selection

    Decoders return quantized structure-of-arrays buffers. Unpacking and dequantization happen in the WebGL2 render shaders, so the SDK uploads compact bytes and the GPU expands them. Decode runs in-process or on a SIMD Web Worker pool, capability-gated at runtime. Encoders are a separate licensed capability and are never shipped to the browser: this SDK is decode-and-render only.

    Everything the SDK throws on purpose is a BitruviusError carrying a stable BVX_* string code. Branch on err.code, never on message text or class names, since shipped bundles are minified. Use the BitruviusError.is type guard rather than instanceof, because it is safe across realms and duplicate bundles.

    Browse the packages in the sidebar, or jump to the Developer Portal for guides and the Playground for live, editable examples.