JavaScript / TypeScript library for parsing meteorological and geospatial data formats — GRIB2, NetCDF3, NetCDF4 / HDF5, Zarr v2, and TIFF / GeoTIFF (including Cloud-Optimized GeoTIFFs) — in the browser, Web Workers, and Node 18+. Powered by a pure-C engine compiled to WebAssembly via Emscripten.
Until published to npm:
npm install git+https://github.com/uihilab/SciWrid-Toolkit.gitYou do not need Python or Emscripten to install, use, or bundle this library - the compiled
sciwrid.wasmships in the package. Python + Emscripten are only needed to recompile the WASM from its C sources.
End users do not need to install peer compression libraries — h5wasm (NetCDF4) and numcodecs (Zarr blosc/zstd/lz4) are lazy-loaded from jsdelivr on first use.
| Format | Extensions | Notes |
|---|---|---|
| GRIB2 | .grb2, .grib2 |
Grid templates 0, 30, 40, 101; simple + complex packing; Section-6 bitmaps (masked points → NaN) |
| NetCDF3 Classic | .nc3 |
Full CF coordinate support |
| NetCDF4 / HDF5 | .nc, .nc4 |
Loads h5wasm from CDN on first use |
| Zarr v2 (zip) | .zip, .zarr |
Compressors: null, gzip, zlib, blosc, zstd, lz4. Filters: shuffle only (fixedscaleoffset, delta, … not yet supported). |
| TIFF / GeoTIFF | .tif, .tiff |
UInt8/UInt16/Int16/Float32; LZW + Deflate; horizontal + floating-point predictors; WGS84 / UTM / sinusoidal; strip + tile; COG over HTTP Range (scan/extract only read the IFD + needed tile). |
import { scan, extract, extractGrid, gridToGeoTIFF, gridToImageData, trim } from 'sciwrid-toolkit';
// 1. Inspect a file
const meta = await scan(file); // file: Uint8Array | Blob | File | URL | string
console.log(meta.format, meta.variables.map(v => v.name));
// 1b. Where on Earth the file's grid actually is — use this instead of
// assuming an extent. [minLon, minLat, maxLon, maxLat] for every format
// (Leaflet wants lat first: [[bbox[1], bbox[0]], [bbox[3], bbox[2]]]).
// Optional: absent when the coordinates can't be derived, so check it.
if (meta.bbox) console.log('extent', meta.bbox);
// 2. Point extract
const point = await extract(file, {
variable: 'TMP', lat: 40.7, lon: -74.0, t1: 0, t2: 0,
});
// 2b. Select a timestep by date instead of index (nearest match):
const atNoon = await extract(file, { variable: 'TMP', date: '2026-04-14T12:00:00Z' });
// 3. Bounding-box grid (parallel workers, abortable, with progress)
const grid = await extractGrid(file, {
variable: 'TMP',
bbox: [-100, 30, -80, 45], // [minLon, minLat, maxLon, maxLat]
width: 256, height: 256,
workers: 5,
onProgress: ({ done, total }) => console.log(`${done}/${total}`),
});
// grid.data is a Float32Array of length width*height (row 0 = maxLat)
// 4. Save as Float32 WGS84 GeoTIFF
const tiff = gridToGeoTIFF(grid); // Uint8Array
// 4b. Render it for a web map — Float32 grid → colored RGBA, ready for a
// <canvas> or a MapLibre ImageSource (zero-dependency, Node + browser)
const img = gridToImageData(grid, { ramp: 'viridis' }); // { width, height, data }
const canvas = Object.assign(document.createElement('canvas'), { width: img.width, height: img.height });
canvas.getContext('2d').putImageData(new ImageData(img.data, img.width, img.height), 0, 0);
const [w, s, e, n] = grid.bbox; // the grid carries back the bbox it was cut to
map.addSource('data', { type: 'image', url: canvas.toDataURL('image/png'),
coordinates: [[w, n], [e, n], [e, s], [w, s]] });
map.addLayer({ id: 'data', type: 'raster', source: 'data' });
// Need a PNG instead (server-side, <img> src)? `await gridToPNG(grid, { ramp })`.
// Full drop-a-file MapLibre demo: examples/map-demo.html (npm run demo:web)
// 5. Trim a huge file in-place — keep only what you need, same format out
const trimmed = await trim(file, {
variables: ['TMP', 'UGRD'], // names from scan().variable_names
t1: 0, t2: 23, // optional time-axis slice
});
// trimmed.bytes is a Uint8Array of the same format as the input.
// GRIB2 / NetCDF3 / Zarr are byte-cut (no decode); NetCDF4 uses h5wasm.Full API reference: docs/API.md. TypeScript types ship with the package — no @types needed.
All public symbols come from the package root:
import {
// Functional API (recommended)
detectFormat, scan, extract, extractOutput,
extractGrid, extractGridOutput, gridToJSON, gridToGeoTIFF,
gridToImageData, gridToPNG, // map rendering: Float32 grid → RGBA / PNG
RAMPS, resolveRamp, sampleRamp, // color ramps (viridis/plasma/grayscale/RdBu)
trim,
// Class API (advanced — reuse one instance across many extracts)
SciWridToolkit,
// Typed errors (all extend SciWridError)
SciWridError, UnsupportedFormatError, VariableNotFoundError,
SourceError, ExtractError, TrimError, UnsupportedCRSError,
} from 'sciwrid-toolkit';Anything inside wasm/ is internal and may change without notice.
sciwrid-toolkit/
├── index.js ← public entry point
├── index.d.ts ← TypeScript types
├── lib/ ← JavaScript library source (internal — do not import directly)
│ ├── sciwrid-lib.js class implementation
│ ├── sciwrid-api.js functional API
│ ├── sciwrid-api.d.ts TypeScript types
│ ├── errors.js typed error classes
│ ├── grid-output.js GeoTIFF / JSON serialisers
│ ├── render/ color ramps + Float32 grid → RGBA / PNG
│ ├── trim/ in-place file trimming (per-format)
│ ├── tiff/ TIFF / GeoTIFF reader (+ COG over HTTP Range)
│ ├── zarr/ Zarr v2 reader (zip, compressors, filters)
│ ├── kerchunk/ Kerchunk / reference-store reader
│ ├── time-decoder.js CF time-axis decoding
│ └── time-select.js date → nearest timestep selection
├── wasm/ ← WASM artifacts + C build (internal)
│ ├── sciwrid.wasm compiled C core (~193 KB)
│ ├── sciwrid.js Emscripten loader
│ ├── wasm_api.c C bindings
│ └── build.py build script
├── worker/ ← Web Worker for parallel bbox extraction
├── examples/ ← api-demo.html, map-demo.html, library-usage.html, testfile/
├── scripts/ ← serve.js + test-*.js / demo-*.js runners
├── docs/ ← API.md
│
│ ── native C side (compiled to WASM; not shipped in the npm package) ──
├── core/ ← C engine: dispatch, cursor, query, errors, geo math
├── formats/ ← per-format C readers (grib2, hdf5, netcdf, raw)
├── helper/ ← GRIB2 conversion helpers
└── tools/ ← native offline CLIs (normalize_refs, query) + Makefile
npm run demo:web # serves examples/ on localhost (api-demo, map-demo, library-usage)
npm run demo:grib2 # CLI: scan + extract a sample GRIB2 file
npm run demo:netcdf3 # CLI: scan + extract a sample NetCDF3 file
npm run demo:netcdf4 # CLI: scan + extract a sample NetCDF4 file
npm run demo:zarr # CLI: scan + extract a sample Zarr fileCLI demo scripts live under examples/testfile/.
Use npm run build:zarr-fixture to regenerate the rich deflated Zarr fixture
at examples/testfile/sample-zarr-rich.zarr.zip.
Smoke tests:
npm run test:api # functional API
npm run test:grid # extractGrid (parallel bbox)
npm run test:zarr # Zarr path
npm run test:tiff # TIFF / GeoTIFF
npm run test:tiff-range # COG over HTTP Range
npm run test:trim # trim() across all formats
npm run test:time # CF time-axis decoding
npm run test:time-select# date → nearest-timestep selection
npm run test:render # color ramps + gridToImageData / gridToPNG
npm run test:kerchunk # Kerchunk reference storeThe demo:web server hosts several pages:
api-demo.html— drop a.grb2/.nc/.zip/.zarr/.tiffile and runscan/extract/extractGridinteractively (heat-map canvas, progress, abort, GeoTIFF / JSON download).map-demo.html— drop a file and render it on a MapLibre map (date coverage in the sidebar).library-usage.html— minimal copy-paste usage example.
- GRIB2 — grid templates 0, 20, 30, 40, 101; simple + complex packing; Section-6 bitmaps (masked points →
NaN); parameter names + units from WMO Code Table 4.2, including the centre-local ranges NCEP, ECMWF, DWD, Rome and Helsinki define. - NetCDF3 Classic — full CF coordinate support.
- NetCDF4 / HDF5 — via lazy-loaded
h5wasm. - Zarr v2 (zip) — compressors
null,gzip,zlib,blosc,zstd,lz4. - TIFF / GeoTIFF — UInt8/16, Int16, Float32; LZW + Deflate; horizontal + floating-point predictors; WGS84 / UTM / sinusoidal; strip + tile; COG over HTTP Range.
- Point + bbox extraction —
extract,extractGrid(parallel workers, abortable, progress). - Geographic extent —
scan()reportsmeta.bboxfor GRIB2, NetCDF3, NetCDF4/HDF5, Zarr, Parquet and TIFF, so a map can place the grid without guessing. Projected grids (polar stereographic, Lambert) report the envelope of their real lat/lon, not a four-corner box. - CF time axis — decode timesteps; select a timestep by
date(nearest match);timeRange/ per-axis start–end exposed byscan. - Output —
gridToGeoTIFF,gridToJSON,gridToImageData/gridToPNG(viridis / plasma / grayscale / RdBu ramps). trim()— in-place file trimming across GRIB2 / NetCDF3 / NetCDF4 / Zarr.
- Zarr filters (
fixedscaleoffset,delta, …). - Zarr v3 — reads fine, but
trim()refuses it. meta.bboxfor GRIB2 grid templates outside 0 / 20 / 30 / 40.- GRIB2 interval products: only template 4.8 is stamped at the interval end; 4.9–4.14 still report the reference time.
- Improve the
api-demoUX so users can comfortably test the library end-to-end — clearer scan/extract flows, better feedback, and easier inspection of results. - Heavy testing on large files — exercise the existing pipeline (streaming scan, COG HTTP Range reads, parallel
extractGrid,trim) against big real-world inputs to validate performance and memory behavior.
The library ships a prebuilt sciwrid.wasm in the repo, so neither end
users nor most contributors need Python or Emscripten.
There are two independent stages:
| Command | What it does | When you need it | Requires |
|---|---|---|---|
npm run build |
Bundles the JS and copies the prebuilt .wasm into dist/ (the publishable package). |
Every time you publish to npm. | Node 18+ only |
npm run build:wasm |
Recompiles the C sources to sciwrid.wasm. |
Only when you change the C in formats/ or wasm/. |
Emscripten (emcc) on PATH |
# Package for npm (pure Node - no Python, no Emscripten):
npm run build # writes dist/
# Recompile the WASM (only when C sources changed):
npm run build:wasm # writes wasm/sciwrid.wasm (commit the result)The repo is kept un-built: dist/ is gitignored and regenerated by
npm run build (which also runs automatically on npm publish).
MIT.