Project site · Documentation · Logbook · Feed reference · Contributing
Six packages under the @azohra scope, one workspace; each package
versions independently and keeps its own changelog. The layers stand
alone: read published documents without running a builder, bring the
typed data into a custom UI, or run the engine end to end.
| Package | Home | What it provides |
|---|---|---|
@azohra/meteo.forecast |
forecast/ |
The forecast engine and meteo CLI: fetches ECCC and NOAA model fields, samples each catalogued site, derives soaring quantities, and publishes versioned documents plus append-only history. |
@azohra/meteo.briefing |
briefing/ |
Reads a published forecast and tells you what kind of day it is: zod contract and types, pure derivations, typed findings, cross-model and through-time comparison, transport guards, history loaders — and the Meteogram renderer (/meteogram). |
@azohra/meteo.station |
station/ |
Live weather stations: one wire contract, vendor adapters (WindNerd, Tempest, Campbell), a mountable feed handler, a framework-free client layer, and peer React and custom-element bindings. |
@azohra/meteo.core |
core/ |
The shared foundation: units, angle and wind-vector math (one sign convention platform-wide), zod primitives, the transport failure vocabulary, schema-artifact tooling. |
@azohra/meteo.grib |
grib/ |
A pure-TypeScript GRIB2 decoder — rotated and Lambert grids, complex packing, multi-field messages, .idx helpers — validated bit-for-bit against ecCodes golden fixtures. |
@azohra/meteo.j2k |
j2k/ |
A pure-TypeScript JPEG 2000 decoder scoped to exactly the codestream subset ECCC ships; the production codec behind @azohra/meteo.grib. |
pnpm add @azohra/meteo.briefing # read, analyze, render published forecast documents
pnpm add @azohra/meteo.forecast # run the engine and publish your own
pnpm add @azohra/meteo.station # live weather-station feeds and components
pnpm add @azohra/meteo.core # shared units, wind math, zod primitives
pnpm add @azohra/meteo.grib # decode GRIB2 provider bytes
pnpm add @azohra/meteo.j2k # decode the JPEG 2000 inside ECCC GRIB2Point curl at a published forecast document:
curl -sS https://meteo.azohra.com/data-sample/hrdps-continental/sites/test-hill.json \
| jq '.hours[] | {validAt} + .derived'The sample is one real HRDPS run over the synthetic sites, truncated to its first eight hourly steps.
Forecasts include surface conditions, winds and temperatures aloft, thermal
velocity, boundary-layer top, cloud base, and usable-lift top.
forecast/models.json declares each model's
capabilities and semantics; the
feed reference
records provider sources and verification dates.
In TypeScript, each capability is an explicit subpath:
import { parseSiteForecastJson } from "@azohra/meteo.briefing/contract";
import { buildMeteogramScene, renderMeteogramSvg } from "@azohra/meteo.briefing/meteogram";
const forecastUrl =
"https://meteo.azohra.com/data-sample/hrdps-continental/sites/test-hill.json";
const response = await fetch(forecastUrl);
const forecast = parseSiteForecastJson(await response.text());
if (!forecast) throw new Error("forecast failed contract validation");
const svg = renderMeteogramSvg(
buildMeteogramScene(forecast, { timeZone: "America/Vancouver" }),
);Documents are launch-agnostic — one forecast serves every launch its grid cell covers; a launch marker and its measured elevation are render inputs. The TypeScript documentation covers the contract, transport, derivations, analysis, scene graph, rendering tokens, and ensemble documents; the reading guide explains every mark on the chart, illustrated by the committed synthetic scenarios.
@azohra/meteo.station reads live weather stations through one wire contract:
vendor adapters normalize WindNerd, WeatherFlow Tempest, and Campbell
Scientific loggers into it — or your own hardware, through
defineStationAdapter. A mountable Request → Response handler serves the
whole inventory as a single feed, and hooks and components render it
natively, in your design system, with no vendor iframe:
import { StationFeedProvider, useStation, StationCard } from "@azohra/meteo.station/react";
import "@azohra/meteo.station/styles.css";
function LiveWind() {
const { feed, receivedAtMs } = useStation("/api/wind", "launch");
if (!feed) return null;
return (
<div className="meteo-root">
<StationFeedProvider feed={feed} receivedAtMs={receivedAtMs}
thresholds={{ unit: "kmh", values: [12, 20, 28] }}>
<StationCard />
</StationFeedProvider>
</div>
);
}The custom-element binding (@azohra/meteo.station/elements) is a full peer of
the React one, framework-free and held byte-identical by a parity suite.
The station documentation covers
the wire contract, adapters, the client data layer, both bindings, and
theming.
Beneath the engine sit two decoders written for this workspace and published on their own:
@azohra/meteo.gribdecodes GRIB2 in pure TypeScript — rotated lat-lon and Lambert grids, complex packing, multi-field messages, NOMADS.idxbyte-range helpers — written because no maintained JavaScript decoder covered the grids ECCC and NOAA actually ship. Its acceptance gate is bit-for-bit: twenty real messages harvested from every live feed the forecast engine reads, each decoded to exact equality against ecCodes — one inexact double is a decoder bug, never a tolerance question.@azohra/meteo.j2kdecodes the JPEG 2000 codestreams inside ECCC's GRIB2, scoped to exactly the subset those feeds ship — every marker, MQ context, and lifting step cites its clause of ITU-T T.800, and anything outside the subset fails loudly with a named error. It decodes only the codeblocks your sites touch: sampling a few gridpoints costs ~16× less per core than a full decode and is bit-identical to it. Full decodes parallelize too, fanning one field's independent codeblocks across a worker pool — single-threaded, whole-image OpenJPEG can do neither.
Both cores are browser-safe by construction: no node: imports, no
ambient I/O, no WASM. The measured region-decode and per-core tables
behind the codec decision are documented in the
JPEG 2000 performance notes.
The engine publishes static site forecasts to any storage you control, at stable paths:
<your root>/models.json
<your root>/<model>/manifest.json
<your root>/<model>/sites/<slug>.json
pnpm exec meteo forecast build --model hrdps-continental --sites ./sites.json --output ./public/data --dry-runThe publisher documentation
covers launch catalogues, output paths, smoke caps, full builds, and the
history flag (--history, on by default), which appends month archives
beside the current documents.
Builders move real provider volume — per-model transports and transfer
costs are recorded in the
feed reference —
and must not run more often than their model publishes.
An operator's pipeline — the cron schedule, the site catalogue, the bucket
credentials — is your own repository composing the engine; the platform ships
no production instance. Adding a launch is a catalogue entry (slug, name,
coordinates, timezone — identity only) plus one command:
pnpm exec meteo forecast terrain --sites ./sites.json, which measures
elevation, terrain, and land cover into a committed site-context.json.
meteo by Azohra descends from canadarasp — the first derivations here were ports of its constants — and follows soaringmeteo in publishing open soaring forecasts.
meteo by Azohra is solo-maintained. If a change looks worth making, open an issue to discuss it before sending a pull request.
ECCC source data is used under the Environment and Climate Change Canada Data Server End-use Licence; derived forecasts retain its attribution requirement. NOAA HRRR, GFS, and NAM data are public-domain products distributed through the Open Data Dissemination program. Code is MIT licensed.
Made with ♥ in Nelson, BC — by a pilot, for pilots.