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;
|
||||
}
|
||||
|
||||
// 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));
|
||||
|
||||
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.
|
||||
map.resize();
|
||||
|
||||
map.flyTo({ center: [lon, lat], zoom: 4, duration: 900 });
|
||||
map.flyTo({ center: [lon, lat], zoom: 4, duration: 900, padding: { bottom: 200 } });
|
||||
|
||||
setTimeout(() => {
|
||||
new maplibregl.Popup({ closeButton: true, maxWidth: '260px' })
|
||||
@@ -230,7 +238,7 @@ async function flyToHurricane(storm) {
|
||||
const lats = allCoords.map(c => c[1]);
|
||||
map.fitBounds(
|
||||
[[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) {
|
||||
if (!map) return;
|
||||
const tok = (map.__hurrTok = (map.__hurrTok || 0) + 1); // a newer refresh supersedes this one
|
||||
@@ -501,7 +536,7 @@ const state = {
|
||||
showExtendedForecast: false,
|
||||
unifiedMapLayers: {
|
||||
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',
|
||||
wind: localStorage.getItem(STORAGE_KEYS.showWindLayer) !== 'false',
|
||||
aqi: localStorage.getItem(STORAGE_KEYS.showAqiLayer) !== 'false',
|
||||
@@ -1367,6 +1402,7 @@ function refreshMapsOverview() {
|
||||
renderAlertMarker(state.currentCity);
|
||||
renderTempMarker(state.currentCity);
|
||||
renderSunMoonMarker(state.currentCity);
|
||||
renderCloudOverlay(mapsOverview.map);
|
||||
renderHurricaneOverlay(mapsOverview.map);
|
||||
renderWildfireOverlay(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 (layerName === 'satellite') {
|
||||
if (enabled) {
|
||||
if (unifiedMapMode !== 'satellite') unifiedMapModeButton.click();
|
||||
setCloudLayerVisible(true);
|
||||
} else {
|
||||
setCloudLayerVisible(false);
|
||||
if (unifiedMapMode !== 'radar') unifiedMapModeButton.click();
|
||||
}
|
||||
}
|
||||
if (layerName === 'satellite') renderCloudOverlay(mapsOverview.map);
|
||||
|
||||
if (state.currentCity) refreshMapsOverview();
|
||||
});
|
||||
|
||||
@@ -292,7 +292,7 @@
|
||||
</span>
|
||||
</label>
|
||||
<label class="toggle-control">
|
||||
<span class="toggle-label">☁️ Clouds</span>
|
||||
<span class="toggle-label">🛰 Satellite (IR)</span>
|
||||
<span class="switch-toggle">
|
||||
<input type="checkbox" id="toggle-satellite" data-layer="satellite">
|
||||
<span class="toggle-slider"></span>
|
||||
|
||||
Reference in new issue
Block a user