Radar: switch to RainViewer (IEM blocks heavy tile use), cleaner Map tab
Publish Container / publish (push) Successful in 2m40s
Publish Container / publish (push) Successful in 2m40s
- /api/radar/times serves RainViewer frames (cached 2 min), IEM NEXRAD only as fallback; sources honour maxNativeZoom so tiles overzoom instead of going blank - Map tab opens with radar + location dot only; wind/AQI/alerts/temp/sun/lightning are opt-in, readout chips follow their toggles - Bolder location dot - Satellite (GIBS) layer: new storage key, never persisted by storm taps, radar always draws above it Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
13985344ed
commit
2efc945192
3 files changed
+62
-33
No files matched your search
+30
-25
@@ -909,12 +909,38 @@ app.get('/api/alerts', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Radar/satellite tile configuration
|
||||
// Radar tile configuration.
|
||||
// Primary: RainViewer (global composite, 10-min frames, free, tiles load straight from their CDN).
|
||||
// Fallback: IEM NEXRAD (US only). IEM blocks IPs that pull too many tiles and answers with a "sorry"
|
||||
// redirect, so it must not be the only source. Satellite imagery is separate (NASA GIBS, client-side).
|
||||
let rainviewerCache = { at: 0, data: null };
|
||||
async function rainviewerFrames() {
|
||||
if (rainviewerCache.data && Date.now() - rainviewerCache.at < 120000) return rainviewerCache.data;
|
||||
const r = await fetchWithRetry('https://api.rainviewer.com/public/weather-maps.json', { attempts: 2, timeoutMs: 8000 });
|
||||
if (!r.ok) throw new Error(`RainViewer ${r.status}`);
|
||||
const j = await r.json();
|
||||
const host = j.host || 'https://tilecache.rainviewer.com';
|
||||
const frames = (j.radar?.past || []).slice(-12).map(f => ({
|
||||
time: f.time,
|
||||
url: `${host}${f.path}/256/{z}/{x}/{y}/2/1_1.png`, // colour scheme 2, smoothed, snow shown
|
||||
isForecast: false,
|
||||
maxNativeZoom: 7, // RainViewer's free tiles stop at z7; MapLibre overzooms beyond that
|
||||
}));
|
||||
if (!frames.length) throw new Error('RainViewer returned no frames');
|
||||
rainviewerCache = { at: Date.now(), data: frames };
|
||||
return frames;
|
||||
}
|
||||
|
||||
app.get('/api/radar/times', async (req, res) => {
|
||||
try {
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
try {
|
||||
const past = await rainviewerFrames();
|
||||
return res.json({ provider: 'rainviewer', radar: { past }, satellite: { infrared: [] } });
|
||||
} catch (e) {
|
||||
console.error('RainViewer unavailable, falling back to IEM:', e.message);
|
||||
}
|
||||
|
||||
// IEM NEXRAD tile service (US radar, public/free) with historical offsets for animation.
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const radarLayerBase = 'https://mesonet.agron.iastate.edu/cache/tile.py/1.0.0';
|
||||
const radarOffsetsMin = [55, 50, 45, 40, 35, 30, 25, 20, 15, 10, 5, 0];
|
||||
const radarPast = radarOffsetsMin.map(offset => {
|
||||
@@ -926,28 +952,7 @@ app.get('/api/radar/times', async (req, res) => {
|
||||
maxNativeZoom: 12,
|
||||
};
|
||||
});
|
||||
|
||||
// IEM GOES East cloud/satellite frames for animation.
|
||||
const satOffsetsMin = [30, 25, 20, 15, 10, 5, 0];
|
||||
const satelliteInfrared = satOffsetsMin.map(offset => {
|
||||
const layer = offset === 0 ? 'goes_east' : `goes_east_m${String(offset).padStart(2, '0')}m`;
|
||||
return {
|
||||
time: now - (offset * 60),
|
||||
url: `https://mesonet.agron.iastate.edu/cache/tile.py/1.0.0/${layer}/{z}/{x}/{y}.png`,
|
||||
isForecast: false,
|
||||
maxNativeZoom: 10,
|
||||
};
|
||||
});
|
||||
|
||||
res.json({
|
||||
provider: 'iem-radar-sat',
|
||||
radar: {
|
||||
past: radarPast,
|
||||
},
|
||||
satellite: {
|
||||
infrared: satelliteInfrared,
|
||||
},
|
||||
});
|
||||
res.json({ provider: 'iem-radar', radar: { past: radarPast }, satellite: { infrared: [] } });
|
||||
} catch (e) {
|
||||
console.error('Radar config error:', e.message);
|
||||
res.status(500).json({ error: 'Radar times failed' });
|
||||
|
||||
Reference in new issue
Block a user