Skip to content

Repository files navigation

meteo by Azohra

meteo by Azohra — six TypeScript packages for forecasts, live stations, and provider-byte decoding

MIT licence

Project site · Documentation · Logbook · Feed reference · Contributing

The platform

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.

Install

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 GRIB2

Read a forecast

A Meteogram rendered by @azohra/meteo.briefing from a committed synthetic scenario: pressure, precipitation, cloud, thermal velocity, and CAPE strips above a time-height wind field with boundary layer, usable lift, and cloud base arcs over a convective day

Point 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.

Live stations

A live station card rendered by @azohra/meteo.station: instrument dial and six-hour graded wind history for Launch Ridge

@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.

The decoders

Beneath the engine sit two decoders written for this workspace and published on their own:

  • @azohra/meteo.grib decodes GRIB2 in pure TypeScript — rotated lat-lon and Lambert grids, complex packing, multi-field messages, NOMADS .idx byte-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.j2k decodes 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.

Run your own

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-run

The 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.

Lineage

meteo by Azohra descends from canadarasp — the first derivations here were ports of its constants — and follows soaringmeteo in publishing open soaring forecasts.

Contributing

meteo by Azohra is solo-maintained. If a change looks worth making, open an issue to discuss it before sending a pull request.

Licence

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.

About

Forecasts and live wind, built in the open.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages