From c62a03e778aa52a96d7f133a335e252d7b40f356 Mon Sep 17 00:00:00 2001 From: grabowski Date: Mon, 10 Aug 2026 17:22:27 +0700 Subject: [PATCH] feat: flooded zones render as water and auto-surface When P.1's current level reaches a zone's trigger level the zone fills water-blue (solid border, .62 opacity) instead of the amber risk ramp, and the zone layer adds itself to the map automatically - no toggle needed during an actual flood. A manual hide sets a session flag so auto-show does not fight the user. --- src/static/dashboard.html | 45 +++++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/src/static/dashboard.html b/src/static/dashboard.html index 21dfcdb..6b0de96 100644 --- a/src/static/dashboard.html +++ b/src/static/dashboard.html @@ -627,12 +627,16 @@ const st = zoneStage(zone); const p = st ? st.p_exceed : 0; const flooded = state.p1Now != null && state.p1Now >= level; + if (flooded) { + // river has reached this zone's trigger level: render as water + return { color: '#1b5e88', weight: 2, dashArray: null, fillColor: '#2f86c8', fillOpacity: .62 }; + } return { - color: flooded ? '#7a1512' : '#8a5a2b', - weight: flooded ? 2.5 : 1.2, - dashArray: flooded ? null : '4 4', + color: '#8a5a2b', + weight: 1.2, + dashArray: '4 4', fillColor: ZONE_COLORS[zone] || '#f9e3a1', - fillOpacity: flooded ? .72 : .38 + .3 * Math.min(1, p), + fillOpacity: .38 + .3 * Math.min(1, p), }; } @@ -650,15 +654,9 @@ ${flooded ? '' : ''}`; } - async function toggleFloodZones() { - if (!state.map) return; + async function showFloodZones(fly) { + if (!state.map || zoneLayer) return; const button = $('zones-toggle'); - if (zoneLayer) { - state.map.removeLayer(zoneLayer); - zoneLayer = null; - if (button) button.textContent = 'Show flood zones on map'; - return; - } try { if (!zoneData) { const response = await fetch('/static/flood-zones-p1.geojson'); @@ -668,16 +666,35 @@ zoneLayer = L.geoJSON(zoneData, { style: zoneStyle }) .bindPopup((layer) => zonePopupHtml(layer.feature)) .addTo(state.map); - state.map.flyToBounds(zoneLayer.getBounds(), { maxZoom: 13, duration: .8 }); if (button) button.textContent = 'Hide flood zones'; - document.getElementById('station-map').scrollIntoView({ behavior: 'smooth', block: 'nearest' }); + if (fly) { + state.map.flyToBounds(zoneLayer.getBounds(), { maxZoom: 13, duration: .8 }); + document.getElementById('station-map').scrollIntoView({ behavior: 'smooth', block: 'nearest' }); + } } catch (error) { if (button) button.textContent = 'Flood zones unavailable'; } } + function toggleFloodZones() { + if (zoneLayer) { + state.map.removeLayer(zoneLayer); + zoneLayer = null; + state.zonesUserHidden = true; // don't auto-show again this session + const button = $('zones-toggle'); + if (button) button.textContent = 'Show flood zones on map'; + } else { + state.zonesUserHidden = false; + showFloodZones(true); + } + } + function restyleFloodZones() { if (zoneLayer) zoneLayer.setStyle(zoneStyle); + // Flooding in progress: surface the zones even if the layer is off, + // unless the user explicitly hid it. + const anyFlooded = state.p1Now != null && (state.p1Stages || []).some((s) => state.p1Now >= s.level); + if (anyFlooded && !zoneLayer && !state.zonesUserHidden) showFloodZones(false); } function pointInRing(lat, lon, ring) {