diff options
| author | St33v <github@f3rr3t.com> | 2026-06-09 13:04:04 +1000 |
|---|---|---|
| committer | St33v <github@f3rr3t.com> | 2026-06-09 13:04:04 +1000 |
| commit | f058e83da43b0b661b45a7bd4c82b49c57e61d93 (patch) | |
| tree | effc21ef57b00b45c8caa29424772f7fbbde03d8 /doc/bom-radar-spec.md | |
| parent | c132b3985e24e557549544a1d10fb0daababdfb1 (diff) | |
Add IDR713 radar fetch and APNG loop
radarFetch.sh fetches BOM IDR713 transparencies (24h cache) and the
last six 6-minute echo frames, composites them via cached lower/upper
plates, and publishes an APNG loop to /srv/www/radar/.
systemd: radar.{service,timer} on a 6-min cadence, with retry units
mirroring the synoptic pattern.
nginx: new radar.pestrel.com vhost (still commented in setup.sh until
DNS propagation is confirmed).
setup.sh provisions radar dirs, installs radar units, enables timer.
deploy.sh accepts synoptic|radar arg.
Parameterised on product code; adding another radar is a one-line
config change.
Spec: doc/bom-radar-spec.md.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Diffstat (limited to 'doc/bom-radar-spec.md')
| -rw-r--r-- | doc/bom-radar-spec.md | 249 |
1 files changed, 249 insertions, 0 deletions
diff --git a/doc/bom-radar-spec.md b/doc/bom-radar-spec.md new file mode 100644 index 0000000..3123a16 --- /dev/null +++ b/doc/bom-radar-spec.md @@ -0,0 +1,249 @@ +--- +title: "BOM Radar Display — Sydney (IDR713)" +date-created: "2026-06-09" +version: "0.1" +type: "spec" +author: + - "st33v" + - "claude-opus-4-8" +model: + - "claude-opus-4-8" +tldr: "Extend the existing bomsynoptic app to fetch, composite and serve Sydney radar (IDR713) from BOM anonymous FTP. Layer model: static transparencies (cached) plus a rolling buffer of the last six 6-minute rain-echo frames, served as a loopable sequence alongside the synoptic chart." +status: "draft" +spec-kind: "feature" +target-repo: "bomsynoptic" +target-machine: "cremonde" +dependencies: [] +design-chat: "https://claude.ai/chat/..." +phase: "1" +phases-planned: 1 +implementing-agent: "claude-code" +reader-targets: + - "claude-code" + - "st33v" +tags: + - "bom" + - "radar" + - "ftp" +--- + +# BOM Radar Display — Sydney (IDR713) + +## 1. Purpose + +The `bomsynoptic` app currently fetches and serves the BOM MSLP synoptic +chart on a six-hourly cadence. This spec adds a second product class: +**rain radar**, beginning with the **128 km Sydney (Terrey Hills)** radar, +product code **`IDR713`**. + +Unlike the synoptic chart — a single self-contained image — a radar view is a +*composite* of several PNG layers that must be stacked in the correct order, +and the rain-echo layer refreshes roughly every six minutes. The deliverable +is the current radar image plus a short loop of the last half-dozen frames, +served in the same manner as the existing synoptic chart. + +Brisbane (Mt Stapylton) and other radars are explicitly **out of scope** for +this phase; the design must make duplication to a new product code trivial +(§9). + +## 2. How BOM radar works (background for the implementer) + +BOM does not publish a single ready-made radar image. It publishes a set of +**transparent PNG layers**, all sharing identical pixel dimensions, intended +to be alpha-composited in a fixed order. Two distinct cadences apply: + +- **Static layers** (basemap, topography, range rings, place labels, feature + overlays, legend) change only rarely — when BOM revises the basemap. Fetch + once, cache, refresh occasionally (e.g. daily). +- **Dynamic layers** (the rain-echo frames) are emitted **every ~6 minutes**. + Each frame is a separate timestamped file. Several recent frames are present + on the server at any moment; we take the most recent *N*. + +All files live under BOM's **anonymous FTP** service. Availability is +best-effort — BOM explicitly does not guarantee it — so resilience matters +(§8). + +## 3. FTP source + +Anonymous FTP, no credentials beyond user `anonymous` (empty / any password). + +| Layer class | Directory | +|---|---| +| Rain-echo frames (dynamic) | `ftp://ftp.bom.gov.au/anon/gen/radar/` | +| Transparencies (static) | `ftp://ftp.bom.gov.au/anon/gen/radar_transparencies/` | + +### 3.1 Dynamic frames + +Filename pattern in `/anon/gen/radar/`: + +``` +IDR713.T.<YYYYMMDDHHMM>.png +``` + +- The timestamp is **UTC**. +- Because the timestamp is fixed-width and big-endian, a plain lexical sort of + the matching filenames is also a chronological sort. +- Selection: list the directory, filter to the `IDR713.T.` prefix, sort, take + the last **6** (configurable as `FRAME_COUNT`, default 6). + +### 3.2 Static transparencies + +Files in `/anon/gen/radar_transparencies/`, all prefixed `IDR713.`: + +| File | Role | +|---|---| +| `IDR713.background.png` | base map: land/sea, coastline | +| `IDR713.topography.png` | terrain shading | +| `IDR713.range.png` | range rings | +| `IDR713.locations.png` | town / city labels | +| `IDR713.roads.png` | roads (optional overlay) | +| `IDR713.rail.png` | rail (optional overlay) | +| `IDR713.waterways.png` | rivers (optional overlay) | +| `IDR713.catchments.png` | catchment boundaries (optional) | +| `IDR713.wthrDistricts.png`| weather district boundaries (optional) | +| `IDR713.legend.0.png` | colour-scale legend | + +Not every transparency exists for every radar. The implementer should fetch +the directory listing once and treat any missing optional layer as absent +rather than failing. + +## 4. Compositing model + +Alpha-composite **bottom → top** in this order. Optional feature overlays sit +between topography and the radar echoes or above them per taste; the +constraint that matters is that **labels and range rings render on top of the +rain** so they stay legible. + +``` +1. IDR713.background.png ← bottom (opaque base) +2. IDR713.topography.png +3. (optional) catchments / waterways / roads / rail / wthrDistricts +4. IDR713.T.<timestamp>.png ← the rain echoes (per frame) +5. IDR713.range.png +6. IDR713.locations.png ← top (labels) +``` + +The **legend** (`IDR713.legend.0.png`) is a separate strip. Either composite +it into a fixed corner/margin of the output, or serve it as a sibling asset +the front-end positions beside the loop. Recommend compositing it in so the +served image is self-describing. + +Implementation note: build the **static base** (layers 1–3 + the on-top +overlays 5–6 are split around the echo, so cache them as two precomposited +plates — a *lower plate* under the echo and an *upper plate* over it). For +each frame: `lower_plate → echo → upper_plate`. This reduces per-frame work to +two composites instead of seven. + +Suggested tooling: Pillow (`PIL.Image.alpha_composite`) keeps the dependency +footprint small and matches the likely existing stack; ImageMagick is an +acceptable alternative if already present. + +## 5. Fetch cadence & rolling buffer + +- **Echo frames:** poll every **6 minutes** (`POLL_INTERVAL`, default 360 s). + On each poll, fetch only frames not already held locally (compare against + the buffer). Maintain a rolling buffer of the last `FRAME_COUNT` (6) frames; + evict older raw frames and their composites. +- **Transparencies:** refresh on startup and then daily (`TRANSPARENCY_TTL`, + default 24 h). Survive a failed refresh by keeping the cached plates. +- Do not hammer the server: the 6-minute poll matches BOM's emission rate. + Skip a poll cleanly if the previous one is still running. + +Note the contrast with the existing synoptic job: synoptic is six-**hourly**; +radar is six-**minute**. The scheduler must accommodate both — reuse the +existing scheduling mechanism if it generalises, otherwise run radar on its +own timer. + +## 6. Outputs + +Produce two things per cycle: + +1. **Latest still** — the most recent frame composited over the plates. + Stable filename (e.g. `idr713-latest.png`) so the served URL never + changes. +2. **Loop** — the last 6 composited frames. Serve either as: + - an **animated APNG/GIF** (`idr713-loop.png` / `.gif`) — simplest for the + front-end, single asset, frame timing baked in; or + - a **numbered sequence** (`idr713.0.png … idr713.5.png`) plus a small JSON + manifest of frame timestamps, leaving animation to client-side JS. + +Recommend the **sequence + manifest** route: it mirrors how BOM's own viewer +works, lets the front-end control frame rate and pausing, and avoids +re-encoding a whole animation every six minutes. The manifest also lets the UI +print the real observation time (converted from UTC to the user's / Sydney +local time) under each frame. + +## 7. Storage layout (suggested) + +``` +<data-root>/radar/idr713/ + transparencies/ # cached static PNGs, refreshed daily + frames/ # raw echo PNGs, rolling buffer of 6 + out/ + idr713-latest.png + idr713.0.png … idr713.5.png + manifest.json # [{seq, utc, local, src_filename}, …] +``` + +Mirror whatever convention the synoptic side already uses for its served +directory; the above is a fallback if there's no established pattern. + +## 8. Resilience + +- **Missing / partial frames:** if a poll returns fewer than 6 frames, serve + what exists; never blank the display. +- **FTP unreachable:** retain and keep serving the last good composites; log + and retry next cycle. BOM does not guarantee availability. +- **Stale data:** stamp the manifest with the newest frame's UTC time so the + UI can grey out / warn if the latest echo is older than, say, 20 minutes. +- **Corrupt download:** validate each PNG opens before it enters the buffer; + discard and retry otherwise. +- **Dimension mismatch:** if a transparency and the echo frames disagree on + size (can happen across a BOM basemap revision), refetch transparencies + before compositing. + +## 9. Generalising to other radars + +Parameterise on the product code. A single config entry should be enough to +add Brisbane (Mt Stapylton) later: + +```yaml +radars: + - id: "IDR713" # Sydney (Terrey Hills) 128 km + enabled: true + - id: "IDR663" # Brisbane (Mt Stapylton) 128 km — confirm code before enabling + enabled: false +``` + +Everything else — directories, filename patterns, layer set, compositing +order — is identical across radars, differing only by the `IDRnnn` stem. +**Do not hardcode `IDR713`.** (The Brisbane code above is a placeholder; +st33v will confirm the exact product before that radar is switched on.) + +## 10. Copyright / terms of use + +BOM anonymous-FTP products are licensed for personal or in-organisation use, +**not** commercial use, and republishing is meant to go through Registered +User services. Since `st33v.com` is a public-facing surface, this is worth a +deliberate decision rather than an oversight. Options: attribute BOM clearly +and treat the personal/non-commercial display as within bounds, gate the radar +behind the internal (WireGuard) surface only, or register. **Flag for st33v — +do not silently publish.** + +## 11. Out of scope (this phase) + +- Brisbane and all radars other than IDR713 (config-ready, disabled). +- Doppler wind / velocity products and the rainfall-accumulation products. +- Historical / archived frames beyond the rolling 6-frame buffer. +- Any front-end beyond serving the still, the sequence, and the manifest in + the same idiom as the existing synoptic chart. + +## 12. Open questions for Claude Code + +1. Does the existing scheduler generalise to a 6-minute timer, or does radar + need its own? +2. What's the established served-assets directory and URL pattern on the + synoptic side — reuse it. +3. Pillow already a dependency, or introduce it? +4. Loop-as-sequence (recommended) vs animated single asset — confirm against + the existing front-end's capabilities. |
