SPEC-P1: Extended Weather Fields & Current-Adjusted Routing#25
Merged
SL-Mar merged 15 commits intodevelopmentfrom Feb 12, 2026
Merged
SPEC-P1: Extended Weather Fields & Current-Adjusted Routing#25SL-Mar merged 15 commits intodevelopmentfrom
SL-Mar merged 15 commits intodevelopmentfrom
Conversation
Add SST, visibility, ice concentration, and swell decomposition to the weather data pipeline, optimization engines, and frontend map overlays. Backend: - seawater_density (UNESCO 1983) and seawater_viscosity (Sharqawy 2010) feed SST-corrected resistance into Holtrop-Mennen model - Tiered COLREG Rule 6 visibility speed caps (fog/poor/moderate) - Ice penalty zone (5% = 2x cost) alongside existing 15% exclusion - Swell decomposition fallback (0.8/0.6 × total Hs) in GridWeatherProvider - GET /api/weather/swell endpoint, SST piggyback on /api/weather/wind - Extended OptimizationLegModel with per-leg swell/ice/vis/sst fields - CMEMS SST (thetao), ice (siconc), GFS visibility fetch + synthetic fallback Frontend: - 4 new map overlay buttons (Ice, Visibility, SST, Swell) with toggle - Canvas tile color scales for each extended layer in WeatherGridLayer - WeatherLegend supports all 7 layer types with correct gradient ranges - API client methods and TypeScript interfaces for extended field data Tests: 21 new unit tests covering all SPEC-P1 requirements (all passing) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace NaN values from CMEMS/GFS with sensible defaults on the API side, add client-side NaN transparency guard, fix visibility km→m unit mismatch, clear stale layer data on switch, and add data source labels for new layers. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Switch CMEMS_ICE_DATASET from the hourly physics dataset (PT1H-m) to the daily one (P1D-m) which actually contains the siconc variable. The hourly dataset caused a silent fallback to synthetic latitude-band ice data. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Ice layer now uses WMO/TD-No. 1215 6-stop ramp (blue→green→yellow→orange→red) instead of 3-band cutoff at 15%, fixing flat red blob on Baltic 82% ice data. SST adds near-freezing 2°C stop for Nordic cold-water differentiation. Visibility and swell refactored to shared interpolateColorRamp() utility. Wind/wave unchanged. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Ice layer now supports the same forecast timeline as wind, waves, and currents. The CMEMS P1D-m dataset provides daily ice concentration (siconc) forecasts for 10 days, fetched on demand and stored compressed in PostgreSQL for persistence across restarts. Backend: fetch_ice_forecast() in copernicus.py, ingest/retrieve via weather_ingestion.py and db_weather_provider.py, three new API endpoints (status/prefetch/frames) in main.py with file cache + DB rebuild. Frontend: IceForecastFrames types in api.ts, ice mode in ForecastTimeline with daily slider (Day 0-9), pass-through in MapComponent, frame extraction callback in page.tsx. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Instead of hardcoding forecast hours, the timeline now derives available hours from the frame keys returned by the backend. This ensures the slider range, labels, and play loop match the actual forecast data in the database for all layers (wind, waves, currents, ice). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…dcoded 10 CMEMS ice dataset may have fewer than 10 days of forecast data available. The status endpoint was hardcoding total_hours=10, so when only 9 frames existed the completion check (9 >= 10) never passed and the frontend polling loop ran forever. Now total_hours reflects the actual number of frames, and complete is determined by whether the prefetch task has finished. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Instead of calling Leaflet's redraw() which destroys all tiles and recreates them (causing a visible flash), the WeatherGridLayer now uses a refreshTiles() method that iterates existing tile canvases and repaints their pixels in-place. DOM elements stay in the tree so there is no blank frame between updates. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The wind forecast handler only updated particle velocity data but never reconstructed WindFieldData for the heatmap grid layer. Now each velocity frame is converted to a 2D u/v grid (preserving the ocean mask from the initial load) so the heatmap colors change per hour, matching the behavior of wave/ice forecast layers. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Root cause: pygrib was missing from requirements.txt — GRIB files downloaded fine but couldn't be parsed, so fetch_wind_data returned None for all frames. Also fixes GFS 6-hour cycle rollover: when the latest GFS run changes between prefetch and frame retrieval, endpoints now fall back to the best cached run by scanning the GRIB cache directory. Frontend passes captured viewport bounds to loadWindFrames to prevent bbox mismatch from map panning. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All tile rendering (heatmap, wind arrows, wave crests) now happens on an offscreen composite canvas. The visible tile only updates via a single atomic drawImage call at the end, so old content stays visible while the new frame renders — no more clear-then-repaint flash. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The composite approach created an extra 256x256 canvas per tile causing freezes. Instead, simply move clearRect from the start of paintTile to right before drawImage so old content stays visible during offscreen rendering — same flicker fix without the overhead. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wind frames endpoint now serves from a pre-built JSON file cache (~68ms) instead of re-parsing 41 GRIB files on every request (~5s+). The cache is built once at the end of the prefetch background task, matching the pattern used by wave/current/ice forecast endpoints. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The VelocityParticleLayer had data in its useEffect dependency array, causing React's cleanup to destroy and recreate the entire leaflet-velocity layer on every forecast frame change. The setData smooth-update path was dead code — never reachable because cleanup always nulled the layer ref. Split into two effects (matching WeatherGridLayer pattern): - Effect 1: layer lifecycle on [type, zoom, hasData] — no data content dep - Effect 2: smooth data update on [data] — calls setData() on existing layer Also replace broken clearRect suppression with persistent snapshot bridge: snapshot is created once on first setData call, kept visible during rapid scrubbing (debounced removal), faded out 350ms after last call. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- page.tsx: Cache WindFieldData per (GFS run, hour) to avoid O(ny*nx) array reconstruction on every frame change — instant on replay loops - ForecastTimeline: rAF-throttle slider input so rapid scrubbing renders once per browser frame; replace setInterval playback with rAF loop aligned to paint cycle (prevents queue buildup) - WeatherGridLayer: rAF-throttle refreshTiles to collapse multiple rapid data changes into a single tile repaint per frame - VelocityParticleLayer: Track and cancel both fade/remove timers on each setData; reset mid-fade snapshots to opaque; reduce fade delay from 350ms to 200ms for better particle visibility during playback Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
GET /api/weather/swell, SST piggyback on/api/weather/wind), extended per-leg route response fieldsTest plan
pytest tests/unit/test_spec_p1.py— 21/21 passingpytest tests/unit/test_vessel_model.py— 15/16 passing (1 pre-existing failure unrelated to this PR)next build— compiles successfully with no TypeScript errors🤖 Generated with Claude Code