npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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-maplibre and 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, openI3sMeshLayer and openI3sPointLayer fetch and parse the scene layer document, build the node-page client (or, on a legacy store, resolve nodes/root directly) and hand back the root node ready to traverse. parseBuildingLayer reads 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 at id % nodesPerPage, and dedupes in-flight page fetches. A legacy 1.4 to 1.6 service has no node pages at all: the bootstrap returns nodePages: null and the engine walks the 3dNodeIndexDocument tree by href instead, picking up the per-node sharedResource material 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 published maxScreenThreshold, with the angle-dependent factor that stops a pitched view from over-refining to the horizon.
  • Engines. I3sPointCloudEngine (PCSL), I3sMeshEngine (IntegratedMesh, 3DObject, BSL) and I3sPointFeatureEngine (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. I3sBuiltinMeshDecoder and I3sBuiltinPointFeatureDecoder, their Worker* counterparts that run the whole decode off the render thread with zero-copy transfers, the bounded-concurrency I3sDecodePool, decodeI3sPaaGeometry for 1.7+ services that ship no Draco buffer, and parseDds for DDS texture sets.
  • Symbology and attributes. parseDrawingInfo and selectSymbol build 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, fetchClassificationStats and measurePointGroundEz.
  • Projected coordinate systems. Esri publishes a numeric wkid and no WKT, so reprojectForWkid derives 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.