Re-implement Travel Forecast under OpenMeteo

This commit is contained in:
mrkmntal 2026-04-07 16:51:50 -04:00
commit ff30f68013
2 changed files with 40 additions and 22 deletions

View file

@ -1,13 +1,14 @@
// travel forecast display // travel forecast display
import STATUS from './status.mjs'; import STATUS from './status.mjs';
import { safeJson, safePromiseAll } from './utils/fetch.mjs'; import { safePromiseAll } from './utils/fetch.mjs';
import { getSmallIcon } from './icons.mjs'; import { getSmallIconFromWmoCode } from './icons.mjs';
import { DateTime } from '../vendor/auto/luxon.mjs'; import { DateTime } from '../vendor/auto/luxon.mjs';
import WeatherDisplay from './weatherdisplay.mjs'; import WeatherDisplay from './weatherdisplay.mjs';
import { registerDisplay } from './navigation.mjs'; import { registerDisplay } from './navigation.mjs';
import settings from './settings.mjs';
import calculateScrollTiming from './utils/scroll-timing.mjs'; import calculateScrollTiming from './utils/scroll-timing.mjs';
import { debugFlag } from './utils/debug.mjs'; import { debugFlag } from './utils/debug.mjs';
import { temperature } from './utils/units.mjs';
import { getAggregatedOpenMeteoForecast } from './utils/weather.mjs';
class TravelForecast extends WeatherDisplay { class TravelForecast extends WeatherDisplay {
constructor(navId, elemId, defaultActive) { constructor(navId, elemId, defaultActive) {
@ -34,28 +35,18 @@ class TravelForecast extends WeatherDisplay {
async getData(weatherParameters, refresh) { async getData(weatherParameters, refresh) {
// super checks for enabled // super checks for enabled
if (!super.getData(weatherParameters, refresh)) return; if (!super.getData(weatherParameters, refresh)) return;
if (!this.weatherParameters?.supportsNoaaDisplays) {
this.data = [];
this.timing.totalScreens = 0;
this.setStatus(STATUS.loaded);
return;
}
// clear stored data if not refresh // clear stored data if not refresh
if (!refresh) { if (!refresh) {
this.previousData = []; this.previousData = [];
} }
const temperatureConverter = temperature();
const forecastPromises = TravelCities.map(async (city, index) => { const forecastPromises = TravelCities.map(async (city, index) => {
try { try {
// get point then forecast
if (!city.point) throw new Error('No pre-loaded point');
let forecast; let forecast;
forecast = await safeJson(`https://api.weather.gov/gridpoints/${city.point.wfo}/${city.point.x},${city.point.y}/forecast`, { forecast = await getAggregatedOpenMeteoForecast(city.Latitude, city.Longitude);
data: {
units: settings.units.value,
},
});
if (forecast) { if (forecast) {
// store for the next run // store for the next run
@ -73,15 +64,23 @@ class TravelForecast extends WeatherDisplay {
} }
return { name: city.Name, error: true }; return { name: city.Name, error: true };
} }
// determine today or tomorrow (shift periods by 1 if tomorrow)
const todayShift = forecast.properties.periods[0].isDaytime ? 0 : 1; const [todayKey, tomorrowKey] = Object.keys(forecast.aggregatedForecast);
const todayForecast = forecast.aggregatedForecast[todayKey];
const tomorrowForecast = forecast.aggregatedForecast[tomorrowKey] ?? todayForecast;
if (!todayForecast) {
throw new Error('No aggregated travel forecast available');
}
const firstHour = todayForecast.hours[0] ?? {};
const today = Boolean(firstHour.is_day ?? 1);
// return a pared-down forecast // return a pared-down forecast
return { return {
today: todayShift === 0, today,
high: forecast.properties.periods[todayShift].temperature, high: temperatureConverter(todayForecast.temperature_2m_max),
low: forecast.properties.periods[todayShift + 1].temperature, low: temperatureConverter(tomorrowForecast.temperature_2m_min),
name: city.Name, name: city.Name,
icon: getSmallIcon(forecast.properties.periods[todayShift].icon), icon: getSmallIconFromWmoCode(todayForecast.weather_code, today),
}; };
} catch (error) { } catch (error) {
console.error(`Unexpected error getting Travel Forecast for ${city.Name}: ${error.message}`); console.error(`Unexpected error getting Travel Forecast for ${city.Name}: ${error.message}`);

View file

@ -30,6 +30,24 @@ const getOpenMeteoForecast = async (lat, lon) => {
return forecast; return forecast;
}; };
const getAggregatedOpenMeteoForecast = async (lat, lon) => {
const forecast = await getOpenMeteoForecast(lat, lon);
if (!forecast) return false;
const aggregatedForecast = aggregateWeatherForecastData(forecast);
if (!aggregatedForecast) {
if (debugFlag('verbose-failures')) {
console.warn(`Unable to aggregate Open-Meteo forecast for ${lat},${lon}`);
}
return false;
}
return {
forecast,
aggregatedForecast,
};
};
const weatherConditions = [ const weatherConditions = [
{ codes: [0], text: ['Clear sky'] }, { codes: [0], text: ['Clear sky'] },
{ codes: [1, 2, 3], text: ['Mainly clear', 'Partly cloudy', 'Overcast'] }, { codes: [1, 2, 3], text: ['Mainly clear', 'Partly cloudy', 'Overcast'] },
@ -124,6 +142,7 @@ const aggregateWeatherForecastData = (forecastResponse) => {
export { export {
getPoint, getPoint,
getOpenMeteoForecast, getOpenMeteoForecast,
getAggregatedOpenMeteoForecast,
aggregateWeatherForecastData, aggregateWeatherForecastData,
getConditionText, getConditionText,
}; };