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/geoid

v0.3.2

Published

Bitruvius SDK geoid: compact EGM96 undulation grid and lookup, converting WGS84 ellipsoidal heights to orthometric (MSL) so global datasets seat on terrain without a hand-tuned geoidN

Readme

@bitruvius/geoid

EGM96 geoid undulation, sampled per point, so heights from different sources land on the same ground.

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 exists

Most 3D datasets store WGS84 ellipsoidal height (h). Terrain and DEMs are published against mean sea level, as orthometric height (H). The two differ by the geoid undulation N, where h = H + N.

N is neither small nor constant. In the grid shipped here it runs from +84.6 m over New Guinea to -107.0 m in the Indian Ocean south of India. The traditional fix was a hand-tuned geoidN constant: correct for the one city it was measured in, then copied into the next project, where it sank the data by tens of metres. A dataset that can be flown anywhere needs N looked up at the place it is actually being drawn.

Three things pushed that lookup out of the SDK and into a package of its own.

It ships a data asset, not only code. The grid is a 508 KB binary. It is published as a subpath export (@bitruvius/geoid/egm96-0.5deg.bin) so bundlers emit it as a fetchable asset and hosts can serve it from their own CDN, rather than every consumer carrying it inside the main SDK bundle whether or not a layer ever needs absolute vertical placement.

The grid has to be a singleton. Loading is module state. One in-flight fetch is shared by every layer on the page, a failed attempt is remembered so a map with dozens of layers retries once rather than once per layer, and setEgm96GeoidUrl() redirects the source once for everybody. Two vendored copies of this logic would mean two fetches, two grids, and two answers to the same question.

It knows nothing about rendering or formats. No DOM, no WebGL, no MapLibre, no tile format, and no dependencies at all: just arithmetic over an Int16Array. That is what lets the decode path (in a worker) and the placement path (on the main thread) call the same function.

What is in it

Six functions and one grid file.

import { ensureEgm96Geoid, geoidHeight } from '@bitruvius/geoid';

await ensureEgm96Geoid();                  // once per session, coalesced, never throws
const N = geoidHeight(40.75, -73.99);      // about -32 m near New York
const orthometric = ellipsoidalHeight - N; // H = h - N

| API | Behaviour | | --- | --- | | ensureEgm96Geoid(url?) | Resolves the grid shipped beside this module and loads it on demand. Idempotent, coalesces concurrent callers, and resolves false instead of rejecting, so a render loop degrades rather than breaks. | | loadEgm96Geoid(url) | Explicit one-time fetch and install from a URL you supply. Throws on a bad response or a grid of the wrong shape. | | setGeoidGrid(data) | Synchronous install from bytes you already hold: a bundler import, fs.readFile, or a prefetch. | | setEgm96GeoidUrl(url) | Points the automatic loader at an explicit URL, for hosts whose bundler or CSP cannot resolve the built-in one. | | geoidHeight(lat, lng) | The lookup. Synchronous, bilinear, longitude wrapping, latitude clamped at the poles. Returns 0 (ellipsoid treated as MSL) when no grid is installed. | | isGeoidLoaded() | Whether a grid is currently installed. |

The grid is 720 x 361 at 0.5 degree spacing, int16 hundredths of a metre, row-major, built by scripts/build-grid.mjs from the public-domain NGA EGM96 15-minute grid (the GeographicLib PGM distribution). Downsampling costs at most about 0.18 m against that source, and bilinear interpolation holds worldwide error near 0.3 m. Against a guessed constant, that is a rounding error.

How it fits

@bitruvius/sdk-maplibre is the consumer. Its vertical-datum resolver reads the vertical reference a service declares (I3S heightModelInfo, 3D Tiles ESRI_crs.vcsWkid), and when a dataset's heights are absolute it loads this grid, samples N at the dataset's anchor, and seats the ground at h - N. If the grid cannot be fetched, the layer drapes onto terrain instead and warns that it did.

EGM96 specifically, because the DEM tiles a MapLibre map renders as terrain carry orthometric heights referenced to mean sea level, and MapLibre draws those values as if they were heights above the ellipsoid. Seating a dataset at h - N from this grid therefore lands it on the terrain as drawn. It is also why an explicit geoidN override in the SDK is applied as a difference from this grid rather than as an absolute shift: the layers resolve the dataset's own undulation first, so an override that happens to be the conventional EGM96 constant for that city moves nothing instead of sinking the data twice.

Should you install it directly

Usually not. Install the SDK and the grid loads itself, with no geoid vocabulary required of you.

Take the direct dependency in two cases:

  • Your bundler or CSP cannot resolve the asset beside the module. Host the .bin yourself and call setEgm96GeoidUrl(yourUrl) before you add layers. The SDK's drape warning names this call for exactly that reason.
  • You need the undulation outside the map: converting survey heights, auditing a dataset's vertical datum, or preparing data server-side.
npm i @bitruvius/geoid

Zero dependencies, ESM only, Node 18 or newer.

Trademarks

Esri, ArcGIS and I3S are trademarks of Environmental Systems Research Institute, Inc. Cesium and 3D Tiles are trademarks of Cesium GS, 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. WebGL is a trademark of The Khronos Group Inc.

Bitruvius is not affiliated with, sponsored by, or endorsed by any of them, and no such relationship is implied.

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.