Radar velocity + derived rotation/hail products (BV/SRV, NROT, MESH, VIL) #128
Labels
No labels
area:ai
area:ci-cd
area:notifications
area:observability
area:public-pages
backlog
bug
duplicate
enhancement
help wanted
invalid
question
type:decision
type:feature
type:infra
type:maintenance
type:security
v1.0.1
v1.1.0
v1.2.0
v1.3.0
v2.0.0
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
rbrooks/WeatherBot#128
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Parked, not forgotten. Originally filed to capture the v1.7.0
CHANGELOG.md"Notes" entry, which recorded this reasoning but had no tracking issue. Substantially revised 2026-07-27 after a design pass — see "What changed" at the bottom.Not included in v1.7.0
This is really two problems, not one
Derived gridded products (MESH, VIL, NROT) — tractable
These are not per-radar polar data. They come from MRMS (Multi-Radar Multi-Sensor), which already mosaics ~160 radars onto a regular CONUS lat/lon grid (~0.01°, ~2-minute cadence) and publishes GRIB2. There is no polar-to-cartesian problem: decode GRIB2 → reproject to Web Mercator → apply a color table → emit PNG. Standard
cfgrib/pygrib+rasterio+ PIL work.Note a correction to the original framing: MESH and NROT are MRMS products, not NEXRAD Level III. Level III has its own older hail index products, but "MESH" as normally meant is MRMS. VIL exists in both. v1.7.0 already added MRMS precipitation accumulation as a layer, so MRMS is a known source in this codebase.
Velocity (BV/SRV) — the genuinely hard half
Doppler velocity is measured relative to the emitting radar. A CONUS velocity mosaic is meteorologically meaningless — you cannot average two radars viewing the same storm from different angles. That, not licensing, is the actual reason no velocity tile service exists anywhere. It is not an oversight to be routed around.
So velocity must be rendered per site. That happens to fit this app's model well:
Location.nearest_radaralready stores the site id, resolved at location creation.If built from scratch, per site: fetch Level II from the public AWS open-data bucket (
noaa-nexrad-level2, ~5–10 MB per volume scan, new scan every ~4–6 min in precip mode) → decode (MetPy, orPy-ARTfor more capability at the cost of a large scientific stack) → select the lowest elevation sweep → dealias (raw Doppler folds at the Nyquist limit, so a strong outbound wind wraps and displays as inbound — the step that makes naive velocity rendering actively misleading rather than merely ugly) → project polar to Web Mercator accounting for beam geometry and earth curvature → colorize to GRLevelX/NWS conventions → cache and serve.Two shortcuts if ever built here: prefer Level III over Level II (the NWS RPG has already dealiased it and already computed storm-relative velocity using its own storm motion vector, so SRV comes free), and do not build a tile pyramid —
radar.pyalready fetches one PNG per location and disk-caches it, so render one image per location per product and use a LeafletImageOverlayrather than aTileLayer.The binding constraint if built in-process
FastAPI, APScheduler, the Discord bot and the Matrix client all share one asyncio event loop. Radial decode and rasterization are CPU-bound work measured in seconds. Running that in-process would stall the event loop and delay alert polling — the app's life-safety function — to make a picture prettier. That trade is not acceptable. It would need a
ProcessPoolExecutoror, better, a separate service.However:
rbrooks/Radomealready does all of thisRadome — a self-hosted NEXRAD Level II viewer — already implements every product this issue asks for. It should be treated as an integration target, not a thing to rebuild.
From
specs/radome-spec-03-api.md, the tile API serves per-tilt products:GET /api/tiles/{site_id}/{scan_id}/{product}/{tilt}.png— productsREF,VEL,SRM,SW,ZDR,CC,PHI,KDP,HC,RR,NROT,AZDIVand multi-tilt aggregates:
GET /api/tiles/{site_id}/{scan_id}/{product}.png—CREF,VIL,VILD,ET18,ET30,ET50plus MRMS:
GET /api/mrms/{product}/{timestamp}.png, withMRMS_MESHnamed explicitly in the spec's example response.Mapping this issue's wish list onto what already exists:
VELSRM— and Radome has storm-motion vector CRUD, incl. auto-populate from NWS Level 3 STI, which is exactly what SRV needsNROT(per-tilt)MRMS_MESHVIL/VILDThe output contract is also already the right shape. Tiles are transparent RGBA PNGs served with an
X-Radar-Bounds: {"north":…,"south":…,"east":…,"west":…}header andCache-Control: public, max-age=3600, plus a metadata-only variant at…/{tilt}/metareturning bounds and URL. That is precisely a LeafletImageOverlaycontract, and this dashboard is already Leaflet — so client-side integration is close to free wherever auth permits.Real blockers for integration
specs/radome-spec-06-render-latency.md, on a 4-core host a single site's 12 sweep renders cost ~380s sequentially; withPARALLEL_SWEEPS=3/KDT_WORKERS=2that drops to ~160s, giving ~2 min to first partial tile and ~4 min to a complete scan. At a ~4–6 minute scan cadence, one site roughly saturates a 4-core box continuously. Radome is architected aroundPRIMARY_SITE+SECONDARY_SITESwith an on-demand queue — not all ~160 sites. WeatherBot locations can span many radars. This is the same server-capacity constraint that has Radome paused.X-Authentik-Usernamefor audit. WeatherBot also uses Authentik, but server-side WeatherBot→Radome calls would need to traverse forward auth (service token or bypass rule), and browser-side embedding needs the viewer's Authentik session to cover Radome's origin. Public pages could never show these products — they are unauthenticated by definition.Location.nearest_radaris already the site id); the question is purely whether that site is being rendered.Caveat: the above is read from Radome's specs. Spec 06 cites production timing from 2026-05-18 logs against commit
ca72619, so the core render path is demonstrably real, but several spec sections are marked pre-Phase-3 or future (on-demand worker for uncached sites, B2 archive replay). Verify implementation state against the code before planning against any of it.Recommended path
RADOME_BASE_URLsetting, a product picker entry, and a LeafletImageOverlayfed byX-Radar-Bounds— small, given the contract already fits.What changed
The original version of this issue concluded that these products would require building a dedicated radar-rendering pipeline, on the basis that no free web-tile source exists and premium Weather Pulse data is desktop GRLevelX format. Both those facts still hold. What the design pass added is that
rbrooks/Radomealready implements every one of these products, so the question is no longer "build a pipeline?" but "integrate, once Radome has the capacity" — a much smaller and differently-shaped problem.