GRIB2 in pure TypeScript
@azohra/meteo.grib is a GRIB2 decoder in pure TypeScript, written because
the forecast engine needs grid template 3.1 (rotated latitude-longitude —
every ECCC HRDPS, RDPS, REPS, and RAQDPS field) and multi-field messages
(NCEP’s paired U/V submessages), and no maintained JavaScript decoder
provides either.
The core is browser-safe by construction — no node: imports, no ambient
I/O, no WASM. Node callers get JPEG 2000 from the separate
@azohra/meteo.grib/j2k-node subpath; see
JPEG 2000 and the pool.
Decode a real field
Section titled “Decode a real field”test/fixtures/
holds real provider messages. This decodes a committed HRDPS 2 m
temperature field — a rotated-grid, JPEG 2000-packed message, the
combination that motivated the package — and samples one launch:
// decode-fixture.mjs — run inside grib/ after `pnpm build`import { readFileSync } from "node:fs";import { decodeFieldValues, nearestGridpoint, parseFields, parseGrid, splitMessages,} from "./dist/index.js";import { createNodeJ2kDecoder } from "./dist/j2k-node.js";
const bytes = readFileSync("test/fixtures/hrdps-continental-tmp-2m.grib2");const [field] = parseFields(splitMessages(bytes)[0]);const grid = parseGrid(field.section3); // rotated lat-lon (GDT 3.1)const decodeJ2k = await createNodeJ2kDecoder(); // every ECCC field is JPEG 2000const { values } = decodeFieldValues(field, { decodeJ2k });const site = nearestGridpoint(grid, 49.3634, -117.2361); // a launch near Nelson, BCconsole.log(`${grid.kind} ${grid.ni}x${grid.nj} = ${values.length} points`);console.log(site);console.log(`2 m temperature: ${(values[site.index] - 273.15).toFixed(2)} C`);rotated 2540x1290 = 3276600 points{ index: 879425, latitude: 49.3642714812993, longitude: -117.23441055371016, distanceKm: 0.1560769875776412}2 m temperature: 23.05 CConsumers inside the workspace (the forecast engine) import the same surface
as @azohra/meteo.grib and @azohra/meteo.grib/j2k-node.
The documentation
Section titled “The documentation”Each page is the single authority for its topic:
| Page | Covers |
|---|---|
| What it decodes | Grid templates, packing, multi-field messages, bitmaps, wind rotation, the .idx byte-range helpers |
| The ecCodes gate | The bit-for-bit acceptance philosophy and the twenty-message golden corpus |
| JPEG 2000 and the pool | Codec options, the region-decode sampled path, the codeblock-parallel strategy, worker-pool sizing |
Layout
Section titled “Layout”src/bytes.ts big-endian octet and MSB-first bitstream primitives (package-private)src/message.ts the section walk: messages, multi-field submessages, identificationsrc/product.ts section 4 product definitionssrc/grid.ts section 3 grids — regular, rotated, Lambert — with analytic inversessrc/nearest.ts O(1) nearest-gridpoint lookup, great-circle distance reportedsrc/decode.ts sections 5–7: simple and complex unpacking, bitmaps, scaling, the J2K seamsrc/wind.ts grid-relative → earth-relative wind rotation, Lambert cone constantsrc/sphere.ts rotated-pole coordinate transformssrc/idx.ts NOMADS .idx parsing and ranged-fetch helperssrc/index.ts the browser-safe barrelsrc/j2k-node.ts Node-only JPEG 2000 wiring: in-process decoder and worker poolsrc/j2k-worker.ts the codecs and the pool's worker entry — shipped in disttest/ module suites, the ecCodes golden gate, and the @azohra/meteo.j2k gates; fixtures/ is the frozen corpus, fixtures-idx/ the NOMADS .idx excerptstools/ decode, pool, and codec benchesBuilt on
Section titled “Built on”ecCodes (Apache-2.0, ECMWF) is the oracle — the golden corpus is its
answers, and the decode arithmetic follows its exact semantics. wgrib2’s
unpk_complex.c (public domain, Wesley Ebisuzaki) guided complex
packing; grib2class (MIT, archmoj) served as a pure-JS cross-check.
JPEG 2000 comes from the workspace’s own @azohra/meteo.j2k by
default, with @cornerstonejs/codec-openjpeg (MIT, the cornerstone.js
team — carrying OpenJPEG itself) as the selectable fallback. numpy’s
pairwise summation (BSD-3-Clause) is ported in the golden suite.
Thanks, all.