Skip to content

fix(radar): decode FMI precipitation as a rate, not metres of water - #1008

Merged
eetu merged 1 commit into
mainfrom
fix/radar-precipitation-units
Jul 26, 2026
Merged

eetu merged 1 commit into
mainfrom
fix/radar-precipitation-units

Conversation

@eetu

@eetu eetu commented Jul 26, 2026

Copy link
Copy Markdown
Owner

The rain tab's forecast frames rendered essentially blank while FMI's own app
showed a clear precipitation field for the same hour.

Root cause

Precipitation1h is GRIB2 parameter 0-1-7 — precipitation rate in
kg m⁻² s⁻¹ (mm/s)
, not metres of water. The decoder multiplied by 1000 instead
of 3600, so every value came out 3.6× low, was rounded to 0.1 mm, and then had
anything below the ramp's 0.1 threshold dropped. Net effect: only cells above
~0.36 mm/h drew at all, and everything up to 3.6 mm/h drew in the palest blue.

It also explains why the "ei sadetta ennusteessa" hint never appeared on those
frames — frame.max was non-zero (0.72 for the reference hour), so the frame
wasn't classified as dry even though it painted almost nothing.

Verification

Checked against FMI's own point-forecast API rather than only unit tests:

  • The Tampere cell decodes to 2.7778e-5; opendata.fmi.fi/timeseries reports
    0.1 mm for that hour. × 3600 = 0.1 ✔, × 1000 = 0.028 ✘. Every decoded
    value is an exact multiple of 0.1/3600.
  • 10 of 12 forecast hours at the saved location now match Precipitation1h
    exactly; the two outliers differ by 0.1 mm, which is nearest-cell pick vs FMI's
    bilinear interpolation.
  • 6 off-centre probes match exactly. Worth doing because Tampere sits mid-box,
    where a flipped-rows bug would be invisible — these confirm orientation too.
  • The reference frame now covers ~78% of the visible map in structured
    0.1–1 mm/h precipitation, peaking at 1.4 mm/h.

For the observed/forecast ramp unification, tested through halo's own proxy: the
styled tile returns 9/9 halo ramp colours with zero foreign colours; the unstyled
tile returns FMI's palette instead (proving the SLD is what's doing the work);
and a deliberately broken SLD falls back to a tile byte-identical to the
unstyled one.

cargo test 24 + 18 green (4 new radar tests), yarn validate clean.

Also in here

  • Sharper grid. The old 9.0° × 3.4° box produced 401 native cells, tripping
    MAX_GRID_SIDE and halving the grid to 201×76 — of which only ~22% of the
    width was ever on screen at the default zoom. Now 4.4° × 2.4°, shipping at
    native ~2.5 km resolution (196×107, stride 1).
  • Retuned ramp to 0.1–30 mm/h; Finnish rain is mostly 0.1–5 mm/h, which the
    old 0.1–40 ramp spent entirely on blues.
  • Mercator row placement. imageOverlay stretches linearly in the
    projection, but the grid is regular in latitude, so rows landed ~2.6 km too far
    north mid-box. Measured placement error drops 2.57 km → 1.26 km, the residual
    being half-cell quantisation.
  • One ramp across the whole timeline. Observed frames move from
    Radar:suomi_dbz_eureffin to Radar:suomi_rr_eureffin (rain rate, band =
    mm/h × 100) and are restyled with halo's own ramp via SLD_BODY, generated
    from PRECIP_STOPS. Previously the palette jumped as you scrubbed past "now"
    and the legend described only half the timeline — dBZ can only be labelled
    qualitatively, and FMI's own style names its bins
    kohtalainen/sakea/hyvin sakea. Retuning PRECIP_STOPS later now updates both
    halves automatically.
  • Proxy fallback. If a tile carrying SLD_BODY comes back as a WMS
    ServiceException (XML, sometimes with a 200), the proxy retries once without
    it, so the radar degrades to FMI's palette rather than an empty map.
  • Location marker — accent ring + dot at the saved coordinates, in
    markerPane so frames never paint over it and non-interactive so it doesn't
    eat the long-press scrub gesture.

Worth knowing

  • halo's forecast will still read lighter than FMI's app at short lead times.
    That view is a radar extrapolation nowcast; this is the HARMONIE model, and
    HARMONIE genuinely forecast only 0.1–0.4 mm over Tampere for the hour in
    question — FMI's human-edited pal_skandinavia agrees. FMI's open WMS
    publishes no nowcast layer (all 116 Radar:* layers are observed products), so
    there is nothing to switch to. That gap is meteorology, not code.
  • rr shows slightly less fine texture than dBZ. Reflectivity picks up faint
    echoes the rain-rate product doesn't, and halo's ramp starts at 0.1 mm/h where
    FMI's rr style starts at 0.07 — so the very lightest returns now read as dry.
    That's the cost of both halves being the same quantity.
  • Observed frames are slightly lighter than before, since RADAR_OPACITY
    (0.75) and FORECAST_OPACITY (0.6) collapsed into one OVERLAY_OPACITY;
    differing alphas would have re-introduced a jump at "now".
  • The SLD depends on FMI keeping dynamic styling enabled. The fallback covers
    them turning it off — if the observed frames ever show FMI's louder cyan/green
    palette, that's it firing (retrying with FMI's own style in the log).

FMI encodes `Precipitation1h` as GRIB2 parameter 0-1-7 — precipitation rate in
kg m⁻² s⁻¹ (mm/s) — but the decoder treated it as metres of water and multiplied
by 1000 instead of 3600. Every value came out 3.6× low and was then rounded to
0.1 mm, which zeroed anything under 0.18 mm/h and left the forecast half of the
timeline all but blank: only cells above ~0.36 mm/h rendered at all, and
everything up to 3.6 mm/h drew in the palest blue.

Confirmed against FMI's own point forecast — the Tampere cell decodes to
2.7778e-5 and opendata.fmi.fi/timeseries reports 0.1 mm for that hour
(× 3600 = 0.1, × 1000 = 0.028). Every decoded value is an exact multiple of
0.1/3600, so the source is 0.1 mm/h-quantised and rounding the converted result
stays lossless.

Alongside the fix:

- Request a 4.4° × 2.4° box instead of 9.0° × 3.4°. The old one produced 401
  native cells, tripping MAX_GRID_SIDE and halving the grid to 201×76 — of which
  only ~22% of the width was ever on screen at the default zoom. It now ships at
  native ~2.5 km resolution, 196×107.
- Retune PRECIP_STOPS to span 0.1–30 mm/h. Finnish rain is mostly 0.1–5 mm/h,
  which the old 0.1–40 ramp spent entirely on blues.
- Resample the forecast overlay's rows into Mercator. imageOverlay stretches
  linearly in the projection but the grid is regular in latitude, so rows landed
  ~2.6 km too far north mid-box.
- Switch the observed frames to Radar:suomi_rr_eureffin (rain rate, band =
  mm/h × 100) and restyle them with halo's own ramp via SLD_BODY, so one legend
  describes the whole timeline instead of the palette jumping at "now". The dBZ
  composite could only ever be labelled qualitatively — FMI's own style names its
  bins kohtalainen/sakea/hyvin sakea. Both halves now share one opacity and the
  same per-pixel alpha.
- Fall back to FMI's built-in style when a tile carrying SLD_BODY comes back as a
  ServiceException, so the radar degrades to their palette rather than a blank
  map.
- Mark the saved location with an accent ring and dot so the centre point stays
  findable under the overlay.
@eetu
eetu merged commit 1fa86f6 into main Jul 26, 2026
3 checks passed
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