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

@splnlss/3d-story-viewer

v0.8.6

Published

A browser-based 3D map and story viewer built with Vite, Svelte, Three.js, and `3d-tiles-renderer`.

Readme

3D Story Viewer

A browser-based 3D map and story viewer built with Vite, Svelte, Three.js, and 3d-tiles-renderer.

The package exports:

  • Viewer: the main 3D viewer class
  • ViewerEvents: viewer lifecycle and transition event names
  • computeElevation: helper for fetching elevation data

Requirements

  • Node.js
  • Yarn
  • A browser with WebGL support

Install

yarn install

Run Locally

yarn dev

The dev server is configured by Vite to serve the app from src/ over HTTPS. Open the local URL printed by Vite.

Build

yarn build

Build output is written to build/. The package entry points are configured in package.json:

  • @splnlss/3d-story-viewer -> build/viewer.js
  • @splnlss/3d-story-viewer/computeElevation -> build/computeElevation.js

Preview Build

yarn preview

Basic Usage

import { Viewer } from '@splnlss/3d-story-viewer';

const container = document.getElementById('app');
const viewer = new Viewer(container, {
    cesiumToken: 'your-cesium-token',
    googleMapsToken: 'your-google-maps-token',
});

viewer.loadEnv('/assets/viewer/neutral.hdr');
viewer.defineContentLayers([
    {
        id: 'example-layer',
        name: 'Example Layer',
        type: '3d-tiles',
        labels: ['default'],
        data: {
            url: 'https://example.com/tileset.json',
        },
        options: {
            renderOrder: 1,
        },
    },
]);
viewer.enableContentLayers(['default']);
viewer.alignCamera(-105.18896, 39.93, 9000);

Geospatial Precision Contract

Longitude and latitude are WGS84 decimal degrees. Altitude is WGS84 ellipsoidal height in meters. Caller-supplied positions are accepted from -12,000 through 100,000 meters. Internal conversions do not clamp or range-limit derived heights, so orbiting above 100 km and changing view mode remain valid. ECEF positions and local east-north-up (ENU) translations are meters; camera pitch, yaw, and roll and local-model XYZ rotations are degrees. Local scale is positive and dimensionless. 3D Tiles and mesh longitude/latitude offsets are degrees, and their altitude offsets are ellipsoidal meters. Missing offset members default independently to zero, while an explicit zero is preserved.

Nonzero mesh offsets require options.offsetOrigin, an explicit WGS84 { longitude, latitude, altitude } reference point for calculating the ECEF translation. The GLB root is never assumed to be ECEF. The offset translates the existing geometry; it does not georeference a local model. A missing or invalid origin emits CONTENT_LAYER_ERROR and leaves the loaded mesh attached without the offset.

Ungeoreferenced GLB models can instead use options.transform. Its anchor is WGS84 longitude/latitude in decimal degrees and ellipsoidal altitude in meters. translationENU is east/north/up in meters, rotationDegrees is a model-local XYZ Euler rotation in degrees, and scale is positive and dimensionless. The default [90, 0, 0] rotation maps glTF +Y Up to ENU +Up; set an explicit rotation when the model's authored axes differ. A local transform cannot be combined with legacy offset or offsetOrigin. Existing ECEF GLBs without a transform keep their coordinates.

All accepted geospatial and camera values must be finite. Labels require longitude, latitude, and altitude on every update. At an exact pole, ECEF to geodetic conversion returns longitude 0 degrees because longitude is mathematically undefined there. ECEF (0, 0, 0) has no geodetic solution and is rejected.

Within this declared domain, the viewer adds less than 2 cm of numerical displacement. WGS84 reference conversions and round trips are bounded to 1 mm; centimeter ENU changes and locally rebased GeoJSON buffers through a 50 km feature extent are bounded to 5 mm. This numerical guarantee does not certify source survey accuracy, terrain geometry, asset georeferencing, vertical datum conversion, or screen-pixel measurement.

Migrating From 0.5.x

Version 0.6.0 requires all three label and GeoJSON coordinates. Camera targets, labels, and Gaussian anchors reject non-finite or out-of-range inputs. GeoJSON features exceeding 50 km from their local origin are rejected. Update callers that relied on missing coordinates defaulting to zero, and provide offsetOrigin for nonzero mesh offsets. Perimeters support Polygon outer rings only; interior rings and MultiPolygon geometry remain outside this precision change.

Gaussian Splat Layers

Gaussian splat content layers support ply, sog, spz, and rad data. Every layer uses an explicit WGS84 anchor plus an optional local ENU transform:

viewer.defineContentLayers([
    {
        id: 'site-scan',
        name: 'Site scan',
        type: 'gaussian-splat',
        labels: ['default'],
        data: { url: 'https://assets.example.com/site.rad', format: 'rad' },
        options: {
            transform: {
                anchor: { longitude: -105.1, latitude: 40.1, altitude: 1600 },
                translationENU: [0, 0, 0],
                rotationDegrees: [0, 0, 0],
                scale: 1,
            },
        },
    },
]);

PLY, SOG, and SPZ files stream directly and report byte progress. RAD uses Spark's paged loader: becoming ready means its metadata is valid and LOD streaming can begin, while detail continues to load in response to the camera. RAD hosts must support HTTP byte ranges and cross-origin GET/HEAD requests. Expose Content-Length, Content-Range, and Accept-Ranges; relative RAD sidecar files must use the same policy.

Local GLB Placement and Targeted Updates

const localBarn = {
    id: 'barn', name: 'Barn', type: 'mesh', labels: ['barn'],
    data: { url: 'https://assets.example.com/barn.glb' },
    options: {
        transform: {
            anchor: { longitude: -105.2, latitude: 39.96, altitude: 1700 },
            translationENU: [0, 0, 0], rotationDegrees: [90, 0, 0], scale: 1,
        },
    },
};
viewer.replaceContentLayer(localBarn.id, localBarn); // add or replace one layer
viewer.replaceContentLayer(localBarn.id, null); // remove one layer

replaceContentLayer preserves unrelated layer objects and resources. It reports an invalid layer definition through CONTENT_LAYER_ERROR; an invalid ID or a mismatched replacement ID throws synchronously. A failed replacement leaves the previous layer in place. Existing defineContentLayers calls still replace the full set while retaining the current enabled-label selection.

Migrating From 0.8.4

Version 0.8.5 adds optional local GLB placement and targeted content replacement. Existing mesh and 3D Tiles definitions keep their 0.8.4 coordinates and offsets. Callers adding local GLBs should supply a WGS84 anchor and review the glTF Y-up default in the scene. Transformed local GLBs render in the terrain depth pass with their authored glTF materials and the viewer's shared Draco/KTX2 decoders. Their render order is fixed to 0 so a catalog overlay order cannot make them show through terrain. GLBs without a transform retain the legacy layer-6 patch rendering and coordinate behavior.

Environment Variables

Viewer tokens can be passed directly in the constructor or provided through Vite environment variables:

VITE_CESIUM_TOKEN=...
VITE_GOOGLE_MAPS_TOKEN=...

Demo App

src/index.html contains a simple demo that:

  • creates a Viewer
  • loads public/assets/viewer/neutral.hdr
  • defines layers from utils/mapLayers.js
  • enables the default layer group
  • registers number-pad shortcuts for sample camera positions and time frames

Useful Files

  • src/main.js: public module exports
  • src/viewer/index.js: main viewer implementation
  • src/viewer/ContentLayer/: supported content layer types
  • utils/mapLayers.js: example layer definitions
  • utils/elevation/: elevation utilities
  • public/assets/viewer/: static viewer assets

Camera translation speed

viewer.movementSpeed sets camera-relative translation along the WGS84 ECEF basis in metres per second (default 100). viewer.boostMultiplier is the dimensionless multiplier while the configured BOOST key is held (default 5). Both are writable number fields. Consumers should supply finite, non-negative values. Mouse zoom/orbit and scripted camera transitions are independent.

Changing viewer.keysEnabled clears held keys and movement flags; resuming requires a fresh key press. This lets popovers and text inputs suspend movement without leaving the camera moving or resuming from a key typed in the control.