From 952872ab927bec4fac4f0eabb826889c95c179ec Mon Sep 17 00:00:00 2001 From: mrkmntal Date: Thu, 9 Apr 2026 22:13:59 -0400 Subject: [PATCH] Restore US Hazard/severe weather behavior --- server/scripts/index.mjs | 34 +++++++++++++++++++++++---- server/scripts/modules/hazards.mjs | 21 +++++++++++++---- server/scripts/modules/navigation.mjs | 15 ++++++++---- 3 files changed, 55 insertions(+), 15 deletions(-) diff --git a/server/scripts/index.mjs b/server/scripts/index.mjs index 040627b..edd632c 100644 --- a/server/scripts/index.mjs +++ b/server/scripts/index.mjs @@ -189,11 +189,35 @@ const init = async () => { const normalizeArcGisLocation = (rawLocation = {}, fallbackLabel = '') => { const attributes = rawLocation.attributes ?? rawLocation.address ?? {}; - const countryCode = attributes.CountryCode ?? attributes.countryCode ?? attributes.country_code ?? null; - const country = attributes.Country ?? attributes.countryName ?? attributes.country ?? null; - const state = attributes.RegionAbbr ?? attributes.Region ?? attributes.Subregion ?? attributes.region ?? ''; - const city = attributes.City ?? attributes.CityName ?? attributes.MetroArea ?? rawLocation.name ?? ''; - const label = fallbackLabel || rawLocation.name || [city, state || country].filter(Boolean).join(', '); + const label = fallbackLabel || rawLocation.name || attributes.LongLabel || attributes.Match_addr || ''; + const labelParts = label.split(',').map((part) => part.trim()).filter(Boolean); + const fallbackCountryCode = labelParts[labelParts.length - 1] === 'USA' ? 'USA' : null; + const fallbackCountry = fallbackCountryCode ? 'United States' : null; + const fallbackState = labelParts.length >= 2 && /^[A-Z]{2,3}$/.test(labelParts[labelParts.length - 2]) ? labelParts[labelParts.length - 2] : ''; + const fallbackCity = labelParts[0] ?? rawLocation.name ?? ''; + + const countryCode = attributes.CountryCode + ?? attributes.countryCode + ?? attributes.country_code + ?? fallbackCountryCode + ?? null; + const country = attributes.CntryName + ?? attributes.Country + ?? attributes.countryName + ?? attributes.country + ?? fallbackCountry + ?? null; + const state = attributes.RegionAbbr + ?? attributes.Region + ?? attributes.Subregion + ?? attributes.region + ?? fallbackState; + const city = attributes.City + ?? attributes.CityName + ?? attributes.PlaceName + ?? attributes.MetroArea + ?? rawLocation.name + ?? fallbackCity; return { city, diff --git a/server/scripts/modules/hazards.mjs b/server/scripts/modules/hazards.mjs index fb1b9c6..575f167 100644 --- a/server/scripts/modules/hazards.mjs +++ b/server/scripts/modules/hazards.mjs @@ -18,6 +18,8 @@ const hazardModifiers = { 'Severe Thunderstorm Warning': 1, }; +const getAlertSignature = (alerts = []) => alerts.map((alert) => alert.id).sort().join('|'); + class Hazards extends WeatherDisplay { constructor(navId, elemId, defaultActive) { // special height and width for scrolling @@ -34,6 +36,7 @@ class Hazards extends WeatherDisplay { // take note of the already-shown alert ids this.viewedAlerts = new Set(); this.viewedGetCount = 0; + this.alertSignature = ''; // cache for scroll calculations // This cache is essential because baseCountChange() is called 25 times per second (every 40ms) @@ -52,7 +55,7 @@ class Hazards extends WeatherDisplay { async getData(weatherParameters, refresh) { // super checks for enabled const superResult = super.getData(weatherParameters, refresh); - if (!this.weatherParameters?.supportsNoaaDisplays) { + if (!this.weatherParameters?.supportsNoaaAlerts) { this.data = []; this.timing.totalScreens = 0; this.getDataCallback(); @@ -75,6 +78,7 @@ class Hazards extends WeatherDisplay { } try { + const previousSignature = this.alertSignature; // get the forecast using centralized safe handling const url = new URL('https://api.weather.gov/alerts/active'); url.searchParams.append('point', `${this.weatherParameters.latitude},${this.weatherParameters.longitude}`); @@ -94,6 +98,14 @@ class Hazards extends WeatherDisplay { const filteredAlerts = sortedAlerts.filter((hazard) => hazard.properties.severity !== 'Unknown' && (!hasImmediate || (hazard.properties.urgency === 'Immediate'))); this.data = filteredAlerts; } + this.alertSignature = getAlertSignature(this.data); + const alertsChanged = previousSignature !== this.alertSignature; + if (alertsChanged) { + this.viewedAlerts.clear(); + if (this.data.length > 0) { + postMessage({ type: 'current-weather-scroll', method: 'reload' }); + } + } // every 10 times through the get process (10 minutes), reset the viewed messages if (this.viewedGetCount >= 10) { @@ -137,10 +149,11 @@ class Hazards extends WeatherDisplay { const list = this.elem.querySelector('.hazard-lines'); list.innerHTML = ''; - // filter viewed alerts + // Prefer new alerts, but keep active alerts visible even after they've been viewed once. const unViewed = this.data.filter((data) => !this.viewedAlerts.has(data.id)); + const alertsToDisplay = unViewed.length > 0 ? unViewed : this.data; - const lines = unViewed.map((data) => { + const lines = alertsToDisplay.map((data) => { const fillValues = {}; const description = data.properties.description .replaceAll('\n\n', '

') @@ -230,8 +243,6 @@ class Hazards extends WeatherDisplay { const superValue = super.screenIndexFromBaseCount(); // false is returned when we reach the end of the scroll if (superValue === false) { - // set total screens to zero to take this out of the rotation - this.timing.totalScreens = 0; // note the ids shown this?.data?.forEach((alert) => this.viewedAlerts.add(alert.id)); } diff --git a/server/scripts/modules/navigation.mjs b/server/scripts/modules/navigation.mjs index c220738..cbf4af9 100644 --- a/server/scripts/modules/navigation.mjs +++ b/server/scripts/modules/navigation.mjs @@ -95,18 +95,22 @@ const getWeather = async (latLon, haveDataCallback) => { try { const supportsNoaaDisplays = isUsLocation(location); + const shouldTryNoaaPoint = supportsNoaaDisplays || !location.countryCode; let point = null; let stations = null; let stationId = ''; - if (supportsNoaaDisplays) { + if (shouldTryNoaaPoint) { point = await getPoint(latLon.lat, latLon.lon); - if (point?.properties?.observationStations) { - stations = await safeJson(point.properties.observationStations); - stationId = stations?.features?.[0]?.properties?.stationIdentifier ?? ''; - } } + if (supportsNoaaDisplays && point?.properties?.observationStations) { + stations = await safeJson(point.properties.observationStations); + stationId = stations?.features?.[0]?.properties?.stationIdentifier ?? ''; + } + + const supportsNoaaAlerts = !!(isUsLocation(location) || point); + const state = location.state || point?.properties?.relativeLocation?.properties?.state || ''; let city = location.city || point?.properties?.relativeLocation?.properties?.city || localStorage.getItem('latLonQuery') || ''; if (stationId && stationId in StationInfo) { @@ -124,6 +128,7 @@ const getWeather = async (latLon, haveDataCallback) => { weatherParameters.countryCode = location.countryCode ?? ''; weatherParameters.timeZone = openMeteoForecast.timezone; weatherParameters.forecast = aggregatedForecast; + weatherParameters.supportsNoaaAlerts = supportsNoaaAlerts; weatherParameters.supportsNoaaDisplays = !!(supportsNoaaDisplays && point && stations?.features?.length); weatherParameters.zoneId = point?.properties?.forecastZone?.substr(-6) ?? ''; weatherParameters.radarId = point?.properties?.radarStation?.substr(-3) ?? '';