Skip to content

Add AEMET regional radar provider (Spain / western Mediterranean) - #2

Open
fakehec wants to merge 2 commits into
OpenFairWind:mainfrom
fakehec:aemet-provider
Open

fakehec wants to merge 2 commits into
OpenFairWind:mainfrom
fakehec:aemet-provider

Conversation

@fakehec

@fakehec fakehec commented Sep 9, 2026

Copy link
Copy Markdown

Summary

Adds a display-tier radar provider for AEMET (Spanish State Meteorological Agency) OpenData regional radar — first site Palma de Mallorca (pm, Balearic Islands). This closes the Iberian / western-Mediterranean gap left by Radar-DPC (Italy), DWD (Germany) and the EUMETNET OPERA open composite (which carries no Spanish contribution).

Follows up on the discussion in #1.

What it does

The AEMET regional product is a rendered palette GIF PPI reflectivity image (no gridded native data, no georeference, no per-frame timestamp in the API). The provider:

  • decodes the legend palette back to reflectivity dBZ, quantised to the 12–72 dBZ legend steps;
  • geolocates it with a coastline-matched linear EPSG:4326 fit (~3–5 km, best near the disk centre; derivation in scripts/aemet-derive-georef.js, validated against island capes with an independent check on Cabrera, not used in the fit);
  • strips the static overlay furniture — banner, legend, and the coastline that shares the 48 dBZ colour — via a multi-frame-intersection mask (scripts/aemet-build-mask.js);
  • serves EPSG:3857 tiles.

Scope and spec compliance

  • Display tier only. Per docs/radar-provider-specification.md §7 ("MUST NOT fabricate raw acquisition by saving rendered PNGs … MUST NOT fabricate storm cells from an upstream product whose semantics do not define them"), this provider deliberately advertises no downloadRaw/cellsFromRaw. Whether a documented, clearly-labelled palette-decoded cellsFromRaw would be acceptable — or whether there is a gridded AEMET route we missed — is exactly the open question in Radar provider for Spain / western Mediterranean (AEMET) — coverage gap and data-format constraints #1; happy to follow your steer before adding it.
  • Live-only. The API exposes only the latest frame (no time parameter, no archive), so it advertises no timeline and rejects historical tile(time) rather than silently substituting the newest frame (§8).
  • Auth/secrets. The API key travels only in the metadata request; the served datos URL is keyless, so it never reaches a tile/chart URL or log (§12).

Included

  • providers/aemet.js, lib/aemet-provider.js (network/orchestration), lib/aemet-raster.js (deterministic decode/georef/render, no runtime deps).
  • New dependency omggif (single-file MIT GIF decoder).
  • Deterministic tests — raster, adapter contract and network paths — using a synthetic fixture (no sample data committed).
  • docs/aemet-provider.md, README + CHANGELOG entries, and reproducibility scripts.

Adding sibling radars

Each AEMET site is a mechanical addition: derive its coastline-matched georef + overlay mask (the scripts are parameterised by radar code) and add the product entry — no engine or UI changes. Only sites with a shipped georef + mask are exposed.

Testing

npm test and npm run lint are clean. The provider auto-registers via discoverAdapters, stays opt-in (recommended.enabled = false, needs an API key), and has been validated live against the AEMET OpenData API.

Adds a display-tier radar provider for AEMET's OpenData regional radar,
first site Palma de Mallorca (Balearic Islands), closing the Iberian
coverage gap left by Radar-DPC (Italy), DWD (Germany) and the EUMETNET
OPERA open composite (no Spanish contribution).

The upstream product is a rendered palette GIF PPI reflectivity image,
not gridded native data. This provider:
- decodes the legend palette back to reflectivity dBZ, quantised to the
  12..72 dBZ legend steps;
- geolocates it with a coastline-matched linear EPSG:4326 fit
  (~3-5 km, best near the disk centre; validated against island capes
  and an independent check on Cabrera);
- removes the static overlay furniture (banner, legend, and the
  coastline that shares the 48 dBZ colour) via a multi-frame
  intersection mask;
- serves EPSG:3857 tiles.

Live-only (the API exposes no archive) and, per
docs/radar-provider-specification.md section 7, it deliberately
advertises no downloadRaw/cellsFromRaw from a rendered product.

Adds dependency omggif (GIF decode). Deterministic raster, contract and
network tests use a synthetic fixture (no sample data committed);
reproducibility scripts (scripts/aemet-*.js) and docs/aemet-provider.md
included.
renderTile sampled the reflectivity grid with nearest-neighbour rounding,
so at display zoom each ~1 km source cell rendered as a hard block with
jagged, aliased edges. Replace it with a bilinear warp that interpolates
dBZ only across valid (non-NODATA) neighbours — NODATA never bleeds into
the average — and drives the alpha from the covered fraction of the 2x2
footprint, so echo edges feather out instead of blocking, matching the
look of raster radar sources like RainViewer.

Display-only: cell detection runs on the raw dbz grid (lib/aemet-cells.js),
not on this rendered raster, so storm warnings are unaffected.

Adds a regression test asserting the render produces partial-alpha edge
pixels (which nearest-neighbour cannot).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ET9sdhUU4uyew792nfV2Sa
@fakehec

fakehec commented Sep 22, 2026

Copy link
Copy Markdown
Author

Pushed a follow-up commit that improves the tile rendering: renderTile warped the reflectivity grid with nearest-neighbour rounding, so at display zoom each ~1 km source cell showed as a hard, aliased block. It now uses a coverage-weighted bilinear warp — dBZ is interpolated only across valid (non-NODATA) neighbours (NODATA never bleeds in) and the covered fraction of the 2×2 footprint drives the alpha, so echo edges feather out like a raster source (RainViewer-style) instead of blocking.

This is display-only: cell detection runs on the raw dbz grid (lib/aemet-cells.js), not on the rendered raster, so storm warnings are unchanged. Added a regression test asserting the render produces partial-alpha edge pixels (which nearest-neighbour cannot). Verified against real AEMET regional frames on a live Signal K install; before/after was a clear, artefact-free improvement.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant