From bb87c836b682a9943f90d2ab588875f811b16de6 Mon Sep 17 00:00:00 2001 From: mrkmntal Date: Tue, 7 Apr 2026 18:32:07 -0400 Subject: [PATCH] Added the in-memory TTL caching for RainViewer and OpenMeteo --- server/scripts/modules/radar.mjs | 37 ++++++++++++++++++++---- server/scripts/modules/utils/weather.mjs | 23 +++++++++++++-- 2 files changed, 52 insertions(+), 8 deletions(-) diff --git a/server/scripts/modules/radar.mjs b/server/scripts/modules/radar.mjs index f22eaf7..51ced9d 100644 --- a/server/scripts/modules/radar.mjs +++ b/server/scripts/modules/radar.mjs @@ -11,9 +11,37 @@ import { clearMarkers, } from './utils/leaflet-weather-map.mjs'; -class Radar extends WeatherDisplay { - static metadataUrl = 'https://api.rainviewer.com/public/weather-maps.json'; +const RADAR_METADATA_URL = 'https://api.rainviewer.com/public/weather-maps.json'; +const RADAR_METADATA_CACHE_TTL_MS = 2 * 60 * 1000; +let radarMetadataCache = null; +const getRadarMetadataCached = async (stillWaiting) => { + const now = Date.now(); + if (radarMetadataCache && (now - radarMetadataCache.fetchedAt) < RADAR_METADATA_CACHE_TTL_MS) { + return radarMetadataCache.data; + } + + const radarMetadata = await safeJson(RADAR_METADATA_URL, { + retryCount: 2, + stillWaiting, + }); + + if (radarMetadata?.host && radarMetadata?.radar?.past?.length) { + radarMetadataCache = { + data: radarMetadata, + fetchedAt: now, + }; + return radarMetadata; + } + + if (radarMetadataCache) { + return radarMetadataCache.data; + } + + return null; +}; + +class Radar extends WeatherDisplay { constructor(navId, elemId) { super(navId, elemId, 'Local Radar'); @@ -48,10 +76,7 @@ class Radar extends WeatherDisplay { this.updateLocationMarker(); await this.updateNearbyMarkers(); - const radarMetadata = await safeJson(Radar.metadataUrl, { - retryCount: 2, - stillWaiting: () => this.stillWaiting(), - }); + const radarMetadata = await getRadarMetadataCached(() => this.stillWaiting()); const frames = radarMetadata?.radar?.past?.slice(-this.maxFrames) ?? []; if (!frames.length || !radarMetadata?.host) { diff --git a/server/scripts/modules/utils/weather.mjs b/server/scripts/modules/utils/weather.mjs index 5a67a4f..395cbad 100644 --- a/server/scripts/modules/utils/weather.mjs +++ b/server/scripts/modules/utils/weather.mjs @@ -15,6 +15,9 @@ const OPEN_METEO_RADAR_OBSERVATION_PARAMETERS = [ 'models=best_match', ].join('&'); +const OPEN_METEO_OBSERVATION_CACHE_TTL_MS = 10 * 60 * 1000; +const openMeteoObservationCache = new Map(); + const getPoint = async (lat, lon) => { const point = await safeJson(`https://api.weather.gov/points/${lat.toFixed(4)},${lon.toFixed(4)}`); if (!point) { @@ -56,15 +59,24 @@ const getAggregatedOpenMeteoForecast = async (lat, lon) => { }; const getOpenMeteoObservationSnapshot = async (lat, lon) => { + const cacheKey = `${lat.toFixed(4)},${lon.toFixed(4)}`; + const cachedEntry = openMeteoObservationCache.get(cacheKey); + const now = Date.now(); + if (cachedEntry && (now - cachedEntry.fetchedAt) < OPEN_METEO_OBSERVATION_CACHE_TTL_MS) { + return cachedEntry.data; + } + const forecast = await safeJson(`https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lon}&${OPEN_METEO_RADAR_OBSERVATION_PARAMETERS}`); if (!forecast?.hourly?.time?.length) { if (debugFlag('verbose-failures')) { console.warn(`Unable to get Open-Meteo radar observation snapshot for ${lat},${lon}`); } + if (cachedEntry) { + return cachedEntry.data; + } return false; } - const now = Date.now(); let nearestIndex = 0; let nearestDelta = Number.POSITIVE_INFINITY; @@ -76,13 +88,20 @@ const getOpenMeteoObservationSnapshot = async (lat, lon) => { } }); - return { + const snapshot = { time: forecast.hourly.time[nearestIndex], temperature: forecast.hourly.temperature_2m?.[nearestIndex] ?? null, weatherCode: forecast.hourly.weather_code?.[nearestIndex] ?? 0, isDay: Boolean(forecast.hourly.is_day?.[nearestIndex] ?? 1), timezone: forecast.timezone, }; + + openMeteoObservationCache.set(cacheKey, { + data: snapshot, + fetchedAt: now, + }); + + return snapshot; }; const weatherConditions = [