I’ve been working on weather/climate data visualization for a few years.
GRIB/NetCDF are good file formats for data distribution. Zarr is good for large-scale datasets, especially for machine learning cases. But none of these can provide a smooth experience of weather data animation in the browser.
The traditional solution is to use XYZ map tiles and update them as the time frame changes, which introduces a significant image downloading and rendering cost, even with grayscale tiles. So I designed another solution. Data animation is like video, so by borrowing the design ideas and infrastructure of the video industry, a weather data animation problem reduces to video stream distribution and rendering. In practice that’s a Rust WASM decoder for the frames and a WebGL2 layer that does the inverse Mercator projection and palette lookup on the GPU; 10 m wind goes through a GPU particle layer.
The size difference is the main payoff. For one GFS run, the same two variables come to ~3,206 MB as precolored tile pyramids (zoom 0–4), against ~138 MB of source GRIB2 — and ~65 MB as .xue.
Now NOAA’s GFS 0.25° / GFS surface flux / HRRR and ECMWF IFS 0.25° have been deployed, running on GitHub Actions and uploading to Cloudflare R2 (no CDN cost).
Playback streams the bundle over HTTP range requests (206) as it goes, so a stable connection matters more than a fast one.
The format is specified in docs/format.md, and I’d be curious how this compares with how people here approach the same problem.
- https://xue.ringsaturn.me/
- GitHub - ringsaturn/xue: ❄️ A binary format for animating weather forecasts in browser · GitHub
Note: the container is modeled on Zarr (chunked, one zstd frame per chunk, range-addressable), and v2 corresponds to a sharded Zarr v3 uint8 array with a temporal delta codec. The difference is layout: analysis-oriented Zarr chunking (long time chunks, whole-level spatial chunks, float32) makes browser animation costly. The next step is a Zarr layout convention aimed at direct visualization: small spatial tiles × a few consecutive steps per chunk, uint8 codes, codebook and GRIB2 identity in attrs. In that layout the frontend reads the store directly, and the single-file container is one packaging of the same bytes.
