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
import STATUS from './status.mjs';
import { safeJson, safePromiseAll } from './utils/fetch.mjs';
import { getSmallIcon } from './icons.mjs';
import { safePromiseAll } from './utils/fetch.mjs';
import { getSmallIconFromWmoCode } from './icons.mjs';
import { DateTime } from '../vendor/auto/luxon.mjs';
import WeatherDisplay from './weatherdisplay.mjs';
import { registerDisplay } from './navigation.mjs';
import settings from './settings.mjs';
import calculateScrollTiming from './utils/scroll-timing.mjs';
import { debugFlag } from './utils/debug.mjs';
import { temperature } from './utils/units.mjs';
import { getAggregatedOpenMeteoForecast } from './utils/weather.mjs';
class TravelForecast extends WeatherDisplay {
constructor(navId, elemId, defaultActive) {
@ -34,28 +35,18 @@ class TravelForecast extends WeatherDisplay {
async getData(weatherParameters, refresh) {
// super checks for enabled
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
if (!refresh) {
this.previousData = [];
}
const temperatureConverter = temperature();
const forecastPromises = TravelCities.map(async (city, index) => {
try {
// get point then forecast
if (!city.point) throw new Error('No pre-loaded point');
let forecast;
forecast = await safeJson(`https://api.weather.gov/gridpoints/${city.point.wfo}/${city.point.x},${city.point.y}/forecast`, {
data: {
units: settings.units.value,
},
});
forecast = await getAggregatedOpenMeteoForecast(city.Latitude, city.Longitude);
if (forecast) {
// store for the next run
@ -73,15 +64,23 @@ class TravelForecast extends WeatherDisplay {
}
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 {
today: todayShift === 0,
high: forecast.properties.periods[todayShift].temperature,
low: forecast.properties.periods[todayShift + 1].temperature,
today,
high: temperatureConverter(todayForecast.temperature_2m_max),
low: temperatureConverter(tomorrowForecast.temperature_2m_min),
name: city.Name,
icon: getSmallIcon(forecast.properties.periods[todayShift].icon),
icon: getSmallIconFromWmoCode(todayForecast.weather_code, today),
};
} catch (error) {
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;
};
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 = [
{ codes: [0], text: ['Clear sky'] },
{ codes: [1, 2, 3], text: ['Mainly clear', 'Partly cloudy', 'Overcast'] },
@ -124,6 +142,7 @@ const aggregateWeatherForecastData = (forecastResponse) => {
export {
getPoint,
getOpenMeteoForecast,
getAggregatedOpenMeteoForecast,
aggregateWeatherForecastData,
getConditionText,
};