Storm cards show live GOES infrared satellite; replace the dead RainViewer satellite toggle
Publish Container / publish (push) Successful in 2m19s
Publish Container / publish (push) Successful in 2m19s
RainViewer's satellite feed is empty, so the Clouds toggle never drew anything and ground radar doesn't reach open ocean. Use NASA GIBS GOES-West/East infrared tiles instead, auto-enabled when flying to a storm, default off otherwise. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a185d10e1c
commit
13985344ed
2 files changed
+41
-13
No files matched your search
+40
-12
@@ -170,6 +170,14 @@ async function flyToHurricane(storm) {
|
|||||||
if (cb) cb.checked = true;
|
if (cb) cb.checked = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ground radar doesn't reach open ocean, so show infrared satellite (the storm's actual cloud
|
||||||
|
// structure). Goes through the layer checkbox so state and storage stay in sync.
|
||||||
|
const sat = document.querySelector('#tab-unified-map input[data-layer="satellite"]');
|
||||||
|
if (sat && !sat.checked) {
|
||||||
|
sat.checked = true;
|
||||||
|
sat.dispatchEvent(new Event('change'));
|
||||||
|
}
|
||||||
|
|
||||||
await new Promise(r => setTimeout(r, 450));
|
await new Promise(r => setTimeout(r, 450));
|
||||||
|
|
||||||
const map = mapsOverview.map;
|
const map = mapsOverview.map;
|
||||||
@@ -178,7 +186,7 @@ async function flyToHurricane(storm) {
|
|||||||
// flyTo works regardless, so just make sure the canvas has its real size first.
|
// flyTo works regardless, so just make sure the canvas has its real size first.
|
||||||
map.resize();
|
map.resize();
|
||||||
|
|
||||||
map.flyTo({ center: [lon, lat], zoom: 4, duration: 900 });
|
map.flyTo({ center: [lon, lat], zoom: 4, duration: 900, padding: { bottom: 200 } });
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
new maplibregl.Popup({ closeButton: true, maxWidth: '260px' })
|
new maplibregl.Popup({ closeButton: true, maxWidth: '260px' })
|
||||||
@@ -230,7 +238,7 @@ async function flyToHurricane(storm) {
|
|||||||
const lats = allCoords.map(c => c[1]);
|
const lats = allCoords.map(c => c[1]);
|
||||||
map.fitBounds(
|
map.fitBounds(
|
||||||
[[Math.min(...lons), Math.min(...lats)], [Math.max(...lons), Math.max(...lats)]],
|
[[Math.min(...lons), Math.min(...lats)], [Math.max(...lons), Math.max(...lats)]],
|
||||||
{ padding: 60, duration: 600, maxZoom: 5 }
|
{ padding: { top: 70, left: 50, right: 50, bottom: 260 }, duration: 600, maxZoom: 5 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,6 +334,33 @@ function mapReady(map, timeoutMs = 4000) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Infrared satellite from NASA GIBS (GOES-West + GOES-East, newest image, no key). Unlike ground radar it
|
||||||
|
// reaches open ocean, so a hurricane's cloud shield is visible. East draws over West where they overlap.
|
||||||
|
const GOES_LAYERS = [['goes-west', 'GOES-West_ABI_Band13_Clean_Infrared'], ['goes-east', 'GOES-East_ABI_Band13_Clean_Infrared']];
|
||||||
|
async function renderCloudOverlay(map) {
|
||||||
|
if (!map) return;
|
||||||
|
await mapReady(map);
|
||||||
|
const on = !!state.unifiedMapLayers.satellite;
|
||||||
|
const stamp = Math.floor(Date.now() / 600000); // GIBS updates ~every 10 min; bust the tile cache then
|
||||||
|
try {
|
||||||
|
GOES_LAYERS.forEach(([id, layer]) => {
|
||||||
|
if (!on) { if (map.getLayer(id)) map.setLayoutProperty(id, 'visibility', 'none'); return; }
|
||||||
|
if (map.getLayer(id)) { map.setLayoutProperty(id, 'visibility', 'visible'); }
|
||||||
|
else {
|
||||||
|
map.addSource(id, { type: 'raster', tileSize: 256, maxzoom: 6, attribution: 'NASA GIBS',
|
||||||
|
tiles: [`https://gibs.earthdata.nasa.gov/wmts/epsg3857/best/${layer}/default/GoogleMapsCompatible_Level6/{z}/{y}/{x}.png?t=${stamp}`] });
|
||||||
|
// sit under the animated radar frames so rain still reads on top of the clouds
|
||||||
|
const beforeId = map.getStyle().layers.find(l => l.id.startsWith('rv-radar-'))?.id;
|
||||||
|
map.addLayer({ id, type: 'raster', source: id, paint: { 'raster-opacity': 0.8, 'raster-fade-duration': 0 } }, beforeId);
|
||||||
|
}
|
||||||
|
if (map.__cloudStamp !== stamp && map.getSource(id)?.setTiles) {
|
||||||
|
map.getSource(id).setTiles([`https://gibs.earthdata.nasa.gov/wmts/epsg3857/best/${layer}/default/GoogleMapsCompatible_Level6/{z}/{y}/{x}.png?t=${stamp}`]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (on) map.__cloudStamp = stamp;
|
||||||
|
} catch { /* style swapped mid-update; next refresh retries */ }
|
||||||
|
}
|
||||||
|
|
||||||
async function renderHurricaneOverlay(map) {
|
async function renderHurricaneOverlay(map) {
|
||||||
if (!map) return;
|
if (!map) return;
|
||||||
const tok = (map.__hurrTok = (map.__hurrTok || 0) + 1); // a newer refresh supersedes this one
|
const tok = (map.__hurrTok = (map.__hurrTok || 0) + 1); // a newer refresh supersedes this one
|
||||||
@@ -501,7 +536,7 @@ const state = {
|
|||||||
showExtendedForecast: false,
|
showExtendedForecast: false,
|
||||||
unifiedMapLayers: {
|
unifiedMapLayers: {
|
||||||
radar: localStorage.getItem(STORAGE_KEYS.showRadarLayer) !== 'false',
|
radar: localStorage.getItem(STORAGE_KEYS.showRadarLayer) !== 'false',
|
||||||
satellite: localStorage.getItem(STORAGE_KEYS.showSatelliteLayer) !== 'false',
|
satellite: localStorage.getItem(STORAGE_KEYS.showSatelliteLayer) === 'true',
|
||||||
lightning: localStorage.getItem(STORAGE_KEYS.showLightningLayer) !== 'false',
|
lightning: localStorage.getItem(STORAGE_KEYS.showLightningLayer) !== 'false',
|
||||||
wind: localStorage.getItem(STORAGE_KEYS.showWindLayer) !== 'false',
|
wind: localStorage.getItem(STORAGE_KEYS.showWindLayer) !== 'false',
|
||||||
aqi: localStorage.getItem(STORAGE_KEYS.showAqiLayer) !== 'false',
|
aqi: localStorage.getItem(STORAGE_KEYS.showAqiLayer) !== 'false',
|
||||||
@@ -1367,6 +1402,7 @@ function refreshMapsOverview() {
|
|||||||
renderAlertMarker(state.currentCity);
|
renderAlertMarker(state.currentCity);
|
||||||
renderTempMarker(state.currentCity);
|
renderTempMarker(state.currentCity);
|
||||||
renderSunMoonMarker(state.currentCity);
|
renderSunMoonMarker(state.currentCity);
|
||||||
|
renderCloudOverlay(mapsOverview.map);
|
||||||
renderHurricaneOverlay(mapsOverview.map);
|
renderHurricaneOverlay(mapsOverview.map);
|
||||||
renderWildfireOverlay(mapsOverview.map);
|
renderWildfireOverlay(mapsOverview.map);
|
||||||
renderSPCOverlay(mapsOverview.map);
|
renderSPCOverlay(mapsOverview.map);
|
||||||
@@ -2574,15 +2610,7 @@ document.querySelectorAll('#tab-unified-map input[type="checkbox"][data-layer]')
|
|||||||
if (!enabled && unifiedMap?.stopPlay) unifiedMap.stopPlay();
|
if (!enabled && unifiedMap?.stopPlay) unifiedMap.stopPlay();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (layerName === 'satellite') {
|
if (layerName === 'satellite') renderCloudOverlay(mapsOverview.map);
|
||||||
if (enabled) {
|
|
||||||
if (unifiedMapMode !== 'satellite') unifiedMapModeButton.click();
|
|
||||||
setCloudLayerVisible(true);
|
|
||||||
} else {
|
|
||||||
setCloudLayerVisible(false);
|
|
||||||
if (unifiedMapMode !== 'radar') unifiedMapModeButton.click();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state.currentCity) refreshMapsOverview();
|
if (state.currentCity) refreshMapsOverview();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -292,7 +292,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<label class="toggle-control">
|
<label class="toggle-control">
|
||||||
<span class="toggle-label">☁️ Clouds</span>
|
<span class="toggle-label">🛰 Satellite (IR)</span>
|
||||||
<span class="switch-toggle">
|
<span class="switch-toggle">
|
||||||
<input type="checkbox" id="toggle-satellite" data-layer="satellite">
|
<input type="checkbox" id="toggle-satellite" data-layer="satellite">
|
||||||
<span class="toggle-slider"></span>
|
<span class="toggle-slider"></span>
|
||||||
|
|||||||
Reference in new issue
Block a user