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.