@bitruvius/i3s
v0.3.1
Published
Bitruvius SDK pure Esri I3S streaming engine (point cloud, integrated mesh, 3D object, point feature, building scene layers): codec- and renderer-agnostic node-page traversal, cache, scheduler, and ENU packing (no WebGL/DOM/MapLibre)
Readme
@bitruvius/i3s
The I3S streaming brain: node-page traversal, cache and scheduler, with no renderer and no codec attached.
Indexed 3D Scene Layers (I3S) is an OGC Community Standard. This package implements the published specification, so it streams any conformant service rather than one vendor's deployment of it.
Internal building block. This package exists so that
@bitruvius/sdk-maplibreand the Bitruvius codecs can resolve their dependencies on npm. It has no standalone product story. Unless you are deliberately building against it, install the SDK instead.
Why it is separate
Streaming an I3S service sits between two things that must not import each other. On one side
are the codecs that turn a node's bytes into geometry (@bitruvius/turbo-lepcc,
@bitruvius/bvc, @bitruvius/draco). On the other is the renderer that owns the GPU. Put the
traversal logic in either one and the other has to depend on it: every codec would drag in a
renderer, or the renderer would drag in every codec.
So the engine keeps two seams and lives on its own.
Codec seam. The engine itself decodes nothing. Point-cloud nodes go through an injected
I3sPointCloudDecoder, mesh geometry through an injected DracoGeometryDecoder, KTX2 textures
through an injected Ktx2Transcoder. Every one of those contracts is declared in
@bitruvius/geo-core, so a codec package depends on the contract and never on this engine, and
no codec can reach the traversal.
That is the seam, not a claim that the package is codec-free. The PCSL point-cloud path really
does import no codec: @bitruvius/turbo-lepcc and @bitruvius/bvc are never dependencies, and
you supply the decoder. The mesh and point-feature paths are different, because this package also
ships the batteries-included worker decoders (WorkerI3sMeshDecoder,
WorkerI3sPointFeatureDecoder) that construct DracoDecoder and Ktx2BasisTranscoder for you and
resolve their wasm. That is why @bitruvius/draco and @bitruvius/ktx2 are runtime dependencies
here. Use I3sBuiltinMeshDecoder or a decoder of your own and you supply those seams yourself, but
they are still installed.
Renderer seam. Output is lifecycle callbacks, not draw calls. onTileReady hands you a tile
already projected and requantized into the shared ENU grid, and you return an opaque GPU handle;
onTileRelease gives that handle back when the node is evicted. No WebGL, no DOM, no MapLibre
inside.
Two things follow from that shape. The parsers are pure and worker-safe, so symbology resolution
and attribute decode run in the decode worker instead of on the frame thread. And the cache,
fetch scheduler and frustum math come from @bitruvius/tiles3d, so the I3S and 3D Tiles paths
behave the same way under memory pressure rather than drifting apart.
What is in it
- Layer bootstrap.
openI3sPointCloudLayer,openI3sMeshLayerandopenI3sPointLayerfetch and parse the scene layer document, build the node-page client (or, on a legacy store, resolvenodes/rootdirectly) and hand back the root node ready to traverse.parseBuildingLayerreads a Building Scene Layer sublayer tree and its filters. - Node tree. Two eras, one engine. A modern 1.7+ service is paged by
NodePageClient, which resolves a node id to its page (floor(id / nodesPerPage)), reads it atid % nodesPerPage, and dedupes in-flight page fetches. A legacy 1.4 to 1.6 service has no node pages at all: the bootstrap returnsnodePages: nulland the engine walks the3dNodeIndexDocumenttree by href instead, picking up the per-nodesharedResourcematerial that 1.7 later moved into the layer document. Both produce the same node shape, so nothing above branches on store version. - Traversal. The point-cloud engine uses
traverseI3s: screen-space-error REPLACE refine over a density-derived geometric error (densityGeometricError, point spacing from OBB footprint area and vertex count), frustum cull, breadth-first with a near-first and foveated priority, far-field memory-bias coarsening, and a no-holes ancestor fallback so an unready path is covered by its nearest ready ancestor. The mesh and point-feature engines refine on Esri's own metric instead (shouldRefineMesh/meshScreenDiameterPx): projected screen diameter against the node's publishedmaxScreenThreshold, with the angle-dependent factor that stops a pitched view from over-refining to the horizon. - Engines.
I3sPointCloudEngine(PCSL),I3sMeshEngine(IntegratedMesh, 3DObject, BSL) andI3sPointFeatureEngine(discrete point features with symbology). Each runs the same per-frame loop from its own refine rule: select, page children, schedule fetches, decode, emit, evict. - Decode.
I3sBuiltinMeshDecoderandI3sBuiltinPointFeatureDecoder, theirWorker*counterparts that run the whole decode off the render thread with zero-copy transfers, the bounded-concurrencyI3sDecodePool,decodeI3sPaaGeometryfor 1.7+ services that ship no Draco buffer, andparseDdsfor DDS texture sets. - Symbology and attributes.
parseDrawingInfoandselectSymbolbuild the symbol table once so per-feature selection is a map lookup, plus labeling info, style-root and web-style resolution, Esri model resources, primitive meshes,decodeFeatureAttribute,fetchClassificationStatsandmeasurePointGroundEz. - Projected coordinate systems. Esri publishes a numeric
wkidand no WKT, soreprojectForWkidderives the UTM families arithmetically and tabulates the national and state grids where a 3D service plausibly exists. Anything unlisted returns null and is reported by name rather than silently misplaced.
What driving it looks like
Bootstrap the layer, derive the ENU anchor from the root node, then call update once per frame
with the camera. The engine hands you decoded tiles through callbacks and never draws anything
itself.
import { openI3sMeshLayer, I3sMeshEngine, WorkerI3sMeshDecoder } from '@bitruvius/i3s';
import { sceneAnchorFromEcef } from '@bitruvius/tiles3d';
const fetchJson = async (url: string) => (await fetch(url)).json();
const { layer, nodePages, root } = await openI3sMeshLayer(serviceUrl, fetchJson, { geoidN: 0 });
const anchor = sceneAnchorFromEcef(root.centerEcef);
const engine = new I3sMeshEngine(layer, nodePages, root, anchor, new WorkerI3sMeshDecoder(), {
onTileReady: (tile, placement) => renderer.upload(tile, placement), // returns a GPU handle
onTileRelease: (_id, handle) => renderer.release(handle),
});
// per frame:
engine.update(camera);
// on teardown:
engine.dispose();nodePages is null for a legacy 1.4 to 1.6 service and the engine walks the node-index tree
instead. Swap WorkerI3sMeshDecoder for I3sBuiltinMeshDecoder and you decode on the calling
thread with seams you supply.
Who should depend on it
Almost nobody. If you are putting I3S content on a MapLibre map, install
@bitruvius/sdk-maplibre, which wires
these engines to a renderer, a codec and a camera for you, and is the supported surface.
Depend on this package directly only if you are binding I3S to a renderer of your own, or you want the parsers headless (scene layer documents, BSL sublayer trees, symbology, CRS resolution) in Node or a worker where there is no GPU at all. The exports track what the SDK needs and can change between minor versions.
Trademarks
I3S and 3D Tiles are OGC Community Standards. OGC is a trademark of the Open Geospatial Consortium. Esri, ArcGIS, I3S and LEPCC are trademarks of Environmental Systems Research Institute, Inc. Cesium and 3D Tiles are trademarks of Cesium GS, Inc. Draco is a trademark of Google LLC. KTX, glTF and WebGL are trademarks of The Khronos Group Inc. MapLibre is a trademark of the MapLibre organization. All other marks are the property of their respective owners.
These names are used solely to describe the data formats this software interoperates with. Bitruvius is not affiliated with, sponsored by, or endorsed by any of them, and no such relationship is implied. Implementing a published specification is not a claim of certification: Bitruvius has not undergone OGC compliance testing for any standard.
License
Proprietary. The full terms ship as LICENSE inside this package, and are readable
before installing at cdn.bitruvius.com/legal/sdk-license-v1.txt.
© Bitruvius, Inc.
