add international forecast support with Open-Meteo
This commit is contained in:
parent
91cc2bd663
commit
7098414f67
19 changed files with 566 additions and 899 deletions
|
|
@ -1,6 +1,13 @@
|
|||
import { safeJson } from './fetch.mjs';
|
||||
import { debugFlag } from './debug.mjs';
|
||||
|
||||
const OPEN_METEO_FORECAST_PARAMETERS = [
|
||||
'daily=temperature_2m_max,temperature_2m_min,uv_index_max',
|
||||
'hourly=temperature_2m,relative_humidity_2m,dew_point_2m,apparent_temperature,precipitation_probability,precipitation,rain,showers,snowfall,snow_depth,weather_code,pressure_msl,surface_pressure,cloud_cover,visibility,uv_index,is_day,sunshine_duration,wind_speed_10m,wind_direction_10m,wind_gusts_10m',
|
||||
'timezone=auto',
|
||||
'models=best_match',
|
||||
].join('&');
|
||||
|
||||
const getPoint = async (lat, lon) => {
|
||||
const point = await safeJson(`https://api.weather.gov/points/${lat.toFixed(4)},${lon.toFixed(4)}`);
|
||||
if (!point) {
|
||||
|
|
@ -12,7 +19,111 @@ const getPoint = async (lat, lon) => {
|
|||
return point;
|
||||
};
|
||||
|
||||
export {
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
getPoint,
|
||||
const getOpenMeteoForecast = async (lat, lon) => {
|
||||
const forecast = await safeJson(`https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lon}&${OPEN_METEO_FORECAST_PARAMETERS}`);
|
||||
if (!forecast) {
|
||||
if (debugFlag('verbose-failures')) {
|
||||
console.warn(`Unable to get Open-Meteo forecast for ${lat},${lon}`);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return forecast;
|
||||
};
|
||||
|
||||
const weatherConditions = [
|
||||
{ codes: [0], text: ['Clear sky'] },
|
||||
{ codes: [1, 2, 3], text: ['Mainly clear', 'Partly cloudy', 'Overcast'] },
|
||||
{ codes: [45, 48], text: ['Fog', 'Depositing rime fog'] },
|
||||
{ codes: [51, 53, 55], text: ['Light Drizzle', 'Moderate Drizzle', 'Dense Drizzle'] },
|
||||
{ codes: [56, 57], text: ['Light Freezing Drizzle', 'Dense Freezing Drizzle'] },
|
||||
{ codes: [61, 63, 65], text: ['Slight Rain', 'Moderate Rain', 'Heavy Rain'] },
|
||||
{ codes: [66, 67], text: ['Light Freezing Rain', 'Heavy Freezing Rain'] },
|
||||
{ codes: [71, 73, 75], text: ['Slight Snow Fall', 'Moderate Snow Fall', 'Heavy Snow Fall'] },
|
||||
{ codes: [77], text: ['Snow Grains'] },
|
||||
{ codes: [80, 81, 82], text: ['Slight Rain Showers', 'Moderate Rain Showers', 'Violent Rain Showers'] },
|
||||
{ codes: [85, 86], text: ['Slight Snow Showers', 'Heavy Snow Showers'] },
|
||||
{ codes: [95], text: ['Thunderstorm'] },
|
||||
{ codes: [96, 99], text: ['Thunderstorm with Slight Hail', 'Thunderstorm with Heavy Hail'] },
|
||||
];
|
||||
|
||||
const getConditionText = (code) => {
|
||||
const condition = weatherConditions.find((item) => item.codes.includes(Number(code)));
|
||||
if (!condition) {
|
||||
console.warn(`Unable to determine weather condition from code: ${code}`);
|
||||
return 'Unknown Conditions';
|
||||
}
|
||||
|
||||
const index = condition.codes.findIndex((item) => item === Number(code));
|
||||
return condition.text[index];
|
||||
};
|
||||
|
||||
const aggregateWeatherForecastData = (forecastResponse) => {
|
||||
if (!forecastResponse?.hourly || !forecastResponse?.daily) {
|
||||
console.warn('aggregateWeatherForecastData: missing hourly or daily forecast data.');
|
||||
return null;
|
||||
}
|
||||
|
||||
const { hourly, daily } = forecastResponse;
|
||||
const keys = Object.keys(hourly).filter((key) => key !== 'time');
|
||||
const dailyData = {};
|
||||
|
||||
hourly.time.forEach((timestamp, index) => {
|
||||
const date = timestamp.split('T')[0];
|
||||
|
||||
if (!dailyData[date]) {
|
||||
dailyData[date] = { hours: [], weather_code_counts: {} };
|
||||
keys.forEach((key) => {
|
||||
dailyData[date][key] = { sum: 0, count: 0 };
|
||||
});
|
||||
}
|
||||
|
||||
const hourData = { time: timestamp };
|
||||
keys.forEach((key) => {
|
||||
const value = hourly[key][index];
|
||||
hourData[key] = value;
|
||||
if (value !== null) {
|
||||
dailyData[date][key].sum += value;
|
||||
dailyData[date][key].count += 1;
|
||||
}
|
||||
});
|
||||
|
||||
if (hourly.weather_code?.[index] !== undefined && hourly.weather_code[index] !== null) {
|
||||
const weatherCode = hourly.weather_code[index];
|
||||
dailyData[date].weather_code_counts[weatherCode] = (dailyData[date].weather_code_counts[weatherCode] || 0) + 1;
|
||||
}
|
||||
|
||||
dailyData[date].hours.push(hourData);
|
||||
});
|
||||
|
||||
const dailyAverages = {};
|
||||
Object.entries(dailyData).forEach(([date, data]) => {
|
||||
dailyAverages[date] = { hours: data.hours };
|
||||
keys.forEach((key) => {
|
||||
const { sum, count } = data[key];
|
||||
dailyAverages[date][key] = count > 0 ? sum / count : null;
|
||||
});
|
||||
|
||||
const weatherCodes = Object.entries(data.weather_code_counts);
|
||||
if (weatherCodes.length > 0) {
|
||||
[dailyAverages[date].weather_code] = weatherCodes.reduce((a, b) => (b[1] > a[1] ? b : a));
|
||||
}
|
||||
});
|
||||
|
||||
daily.time.forEach((date, index) => {
|
||||
if (!dailyAverages[date]) {
|
||||
dailyAverages[date] = { hours: [] };
|
||||
}
|
||||
dailyAverages[date].temperature_2m_max = daily.temperature_2m_max[index];
|
||||
dailyAverages[date].temperature_2m_min = daily.temperature_2m_min[index];
|
||||
dailyAverages[date].uv_index_max = daily.uv_index_max[index];
|
||||
});
|
||||
|
||||
return dailyAverages;
|
||||
};
|
||||
|
||||
export {
|
||||
getPoint,
|
||||
getOpenMeteoForecast,
|
||||
aggregateWeatherForecastData,
|
||||
getConditionText,
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue