unify weather data fetching

This commit is contained in:
terraputix
2026-02-15 20:29:39 +01:00
parent 92e38efebb
commit 71c8a57515
5 changed files with 845 additions and 181 deletions
+30 -49
View File
@@ -7,14 +7,9 @@
import {
buildAverageSeries,
buildCurrentTimeSeries,
buildDaylightMarkAreas,
buildDaylightSeries,
buildSpreadSeries,
calculateAverage,
calculateSpread,
composeChartOption,
convertTimestamps,
findUnit,
getThemeColors
} from '$lib/utils/echarts';
@@ -23,6 +18,12 @@
import { Label } from '$lib/components/ui/label';
import { Switch } from '$lib/components/ui/switch';
import {
type EnsembleForecastResult,
type MarkArea,
fetchEnsembleForecast
} from '$lib/services/weather';
import { defaultParameters } from '../options';
import type * as echarts from 'echarts';
@@ -52,11 +53,10 @@
// ─── Cached API Response ────────────────────────────────────────────────────
interface FetchedData {
hourly: Record<string, unknown>;
hourly_units: Record<string, string>;
utc_offset_seconds: number;
markAreas: Array<[{ xAxis: number; itemStyle: { color: string } }, { xAxis: number }]>;
ensembleResult: EnsembleForecastResult;
timestamps: number[];
utc_offset_seconds: number;
markAreas: MarkArea[];
}
let fetchedData: FetchedData | null = $state(null);
@@ -92,35 +92,22 @@
chartInstances = [];
chartComponents = [];
const [dataDaily, dataReq] = await Promise.all([
fetch(
`https://api.open-meteo.com/v1/forecast?latitude=${location.latitude}&longitude=${location.longitude}&timeformat=unixtime&daily=sunset,sunrise&forecast_days=14`
),
fetch(
`https://ensemble-api.open-meteo.com/v1/ensemble?latitude=${location.latitude}&longitude=${location.longitude}&hourly=${hourlyVars.join(',')}&models=${modelList.join(',')}&timeformat=unixtime&forecast_days=14`
)
]);
const [wd, data] = await Promise.all([dataDaily.json(), dataReq.json()]);
let markAreas: FetchedData['markAreas'] = [];
if ('daily' in wd && 'sunrise' in wd.daily && 'sunset' in wd.daily) {
markAreas = buildDaylightMarkAreas(
wd.daily.sunrise,
wd.daily.sunset,
data.utc_offset_seconds
);
}
const timestamps = convertTimestamps(data.hourly.time, data.utc_offset_seconds);
const result: EnsembleForecastResult = await fetchEnsembleForecast({
latitude: location.latitude!,
longitude: location.longitude!,
hourlyVariables: hourlyVars,
models: modelList,
forecast_days: 14,
temperature_unit: params.temperature_unit as 'celsius' | 'fahrenheit',
wind_speed_unit: params.wind_speed_unit as 'kmh' | 'ms' | 'mph' | 'kn',
precipitation_unit: params.precipitation_unit as 'mm' | 'inch'
});
fetchedData = {
hourly: data.hourly,
hourly_units: data.hourly_units,
utc_offset_seconds: data.utc_offset_seconds,
markAreas,
timestamps
ensembleResult: result,
timestamps: result.timestamps,
utc_offset_seconds: result.utcOffsetSeconds,
markAreas: result.markAreas
};
loading = false;
@@ -134,32 +121,26 @@
$effect(() => {
if (!fetchedData) return;
const {
hourly: hourlyData,
hourly_units,
utc_offset_seconds,
markAreas,
timestamps
} = fetchedData;
const { ensembleResult, timestamps, utc_offset_seconds, markAreas } = fetchedData;
const _showLegend = showLegend;
const colors = getThemeColors();
const variableCount = params.hourly?.length || 0;
const timeLength = (hourlyData.time as number[]).length;
const newOptions: Array<Record<string, unknown>> = [];
for (let vi = 0; vi < variableCount; vi++) {
const variable = params.hourly![vi];
const unit = findUnit(hourly_units, hourlyData, variable);
const varData = ensembleResult.variables[variable];
if (!varData) continue;
const { average } = calculateAverage(hourlyData, variable, timeLength);
const { minValues, maxValues } = calculateSpread(hourlyData, variable, timeLength);
const unit = varData.unit;
const { average, min: minValues, max: maxValues } = varData;
const series: Array<Record<string, unknown>> = [];
const spreadData: Array<[number, number, number]> = minValues.map(
(min, index) =>
[timestamps[index], min ?? 0, maxValues[index] ?? 0] as [number, number, number]
(minVal, index) =>
[timestamps[index], minVal ?? 0, maxValues[index] ?? 0] as [number, number, number]
);
series.push(...buildSpreadSeries({ variable, spreadData }));
+22 -34
View File
@@ -8,12 +8,10 @@
import {
buildAverageSeries,
buildCurrentTimeSeries,
buildDaylightMarkAreas,
buildDaylightSeries,
buildModelSeries,
calculateAverage,
composeChartOption,
convertTimestamps,
findUnit,
getThemeColors
} from '$lib/utils/echarts';
@@ -24,6 +22,12 @@
import { Label } from '$lib/components/ui/label';
import { Switch } from '$lib/components/ui/switch';
import {
type MarkArea,
type ModelCompareResult,
fetchModelComparison
} from '$lib/services/weather';
import { hourly, models as modelsFlat } from '../options';
import { defaultParameters } from '../options';
@@ -65,7 +69,7 @@
hourly: Record<string, unknown>;
hourly_units: Record<string, string>;
utc_offset_seconds: number;
markAreas: Array<[{ xAxis: number; itemStyle: { color: string } }, { xAxis: number }]>;
markAreas: MarkArea[];
timestamps: number[];
}
@@ -102,38 +106,22 @@
chartInstances = [];
chartComponents = [];
const dataReq = await fetch(
`https://api.open-meteo.com/v1/forecast?latitude=${location.latitude}&longitude=${location.longitude}&hourly=${hourlyVars.join(',')}&models=${modelList.join(',')}&timeformat=unixtime&daily=sunset,sunrise`
);
const data = await dataReq.json();
let markAreas: FetchedData['markAreas'] = [];
if ('daily' in data) {
let dailyFirstModelKey: string[] | string = Object.keys(data.daily)[1].split('_');
dailyFirstModelKey.shift();
dailyFirstModelKey = dailyFirstModelKey.join('_');
const sunriseKey = 'sunrise_' + dailyFirstModelKey;
const sunsetKey = 'sunset_' + dailyFirstModelKey;
if (sunriseKey in data.daily && sunsetKey in data.daily) {
markAreas = buildDaylightMarkAreas(
data.daily[sunriseKey],
data.daily[sunsetKey],
data.utc_offset_seconds
);
}
}
const timestamps = convertTimestamps(data.hourly.time, data.utc_offset_seconds);
const result: ModelCompareResult = await fetchModelComparison({
latitude: location.latitude!,
longitude: location.longitude!,
hourlyVariables: hourlyVars,
models: modelList,
temperature_unit: params.temperature_unit as 'celsius' | 'fahrenheit',
wind_speed_unit: params.wind_speed_unit as 'kmh' | 'ms' | 'mph' | 'kn',
precipitation_unit: params.precipitation_unit as 'mm' | 'inch'
});
fetchedData = {
hourly: data.hourly,
hourly_units: data.hourly_units,
utc_offset_seconds: data.utc_offset_seconds,
markAreas,
timestamps
hourly: result.hourlyFlat,
hourly_units: result.hourlyUnitsFlat,
utc_offset_seconds: result.utcOffsetSeconds,
markAreas: result.markAreas,
timestamps: result.timestamps
};
loading = false;
@@ -158,7 +146,7 @@
const colors = getThemeColors();
const variableCount = params.hourly?.length || 0;
const timeLength = (hourlyData.time as number[]).length;
const timeLength = timestamps.length;
const newOptions: Array<Record<string, unknown>> = [];
for (let vi = 0; vi < variableCount; vi++) {
+29 -98
View File
@@ -7,13 +7,7 @@
import { storedLocation } from '$lib/stores/settings';
import {
buildCurrentTimeSeries,
buildDaylightMarkAreas,
buildDaylightSeries,
convertTimestamps,
getThemeColors
} from '$lib/utils/echarts';
import { buildCurrentTimeSeries, buildDaylightSeries, getThemeColors } from '$lib/utils/echarts';
import { pad } from '$lib/utils/index';
import { ChartContainer, ChartToolbar, EChart } from '$lib/components/charts';
@@ -21,6 +15,14 @@
import { Label } from '$lib/components/ui/label';
import * as Select from '$lib/components/ui/select';
import {
type MarkArea,
type WeekDailyData,
type WeekForecastResult,
type WeekHourlyData,
fetchWeekForecast
} from '$lib/services/weather';
import { defaultParameters, models } from '../../options';
import { getColor } from '../../utils/colors';
import weatherCodes from '../../utils/weather-codes';
@@ -61,42 +63,16 @@
// ─── Fetched Data ───────────────────────────────────────────────────────────
interface HourlyData {
time: number[];
temperature_2m: number[];
precipitation: number[];
precipitation_probability: number[];
weather_code: number[];
windspeed_10m: number[];
winddirection_10m: number[];
cloud_cover: number[];
relative_humidity_2m: number[];
}
interface DailyData {
time: string[];
weather_code: number[];
temperature_2m_max: number[];
temperature_2m_min: number[];
sunrise: number[];
sunset: number[];
sunshine_duration: number[];
precipitation_sum: number[];
windspeed_10m_max: number[];
windgusts_10m_max: number[];
winddirection_10m_dominant: number[];
}
interface FetchedHourly {
hourly: HourlyData;
hourly: WeekHourlyData;
utc_offset_seconds: number;
timestamps: number[];
hourlyDates: Date[];
markAreas: Array<[{ xAxis: number; itemStyle: { color: string } }, { xAxis: number }]>;
markAreas: MarkArea[];
}
interface FetchedDaily {
daily: DailyData;
daily: WeekDailyData;
dailyDates: Date[];
}
@@ -276,73 +252,28 @@
chartInstances = [];
chartComponents = [];
const model = modelList[0];
const hourlyVars = [
'temperature_2m',
'precipitation',
'precipitation_probability',
'weather_code',
'windspeed_10m',
'winddirection_10m',
'cloud_cover',
'relative_humidity_2m'
].join(',');
const dailyVars = [
'weather_code',
'temperature_2m_max',
'temperature_2m_min',
'sunrise',
'sunset',
'sunshine_duration',
'precipitation_sum',
'windspeed_10m_max',
'windgusts_10m_max',
'winddirection_10m_dominant'
].join(',');
const baseParams = `latitude=${loc.latitude}&longitude=${loc.longitude}&temperature_unit=${params.temperature_unit}&wind_speed_unit=${params.wind_speed_unit}&precipitation_unit=${params.precipitation_unit}`;
const modelParam = model === 'best_match' ? '' : `&models=${model}`;
const [hourlyResp, dailyResp] = await Promise.all([
fetch(
`https://api.open-meteo.com/v1/forecast?${baseParams}&hourly=${hourlyVars}${modelParam}&timeformat=unixtime&forecast_days=6&past_days=1&daily=sunrise,sunset`
),
fetch(
`https://api.open-meteo.com/v1/forecast?${baseParams}&daily=${dailyVars}${modelParam}&timeformat=unixtime&forecast_days=6&past_days=1`
)
]);
const [hourlyJson, dailyJson] = await Promise.all([hourlyResp.json(), dailyResp.json()]);
const utcOffset = hourlyJson.utc_offset_seconds ?? 0;
const timestamps = convertTimestamps(hourlyJson.hourly.time, utcOffset);
const hourlyDates = timestamps.map((t: number) => new Date(t));
let markAreas: FetchedHourly['markAreas'] = [];
if (hourlyJson.daily?.sunrise && hourlyJson.daily?.sunset) {
markAreas = buildDaylightMarkAreas(
hourlyJson.daily.sunrise,
hourlyJson.daily.sunset,
utcOffset
);
}
const result: WeekForecastResult = await fetchWeekForecast({
latitude: loc.latitude!,
longitude: loc.longitude!,
model: modelList[0],
temperature_unit: params.temperature_unit as 'celsius' | 'fahrenheit',
wind_speed_unit: params.wind_speed_unit as 'kmh' | 'ms' | 'mph' | 'kn',
precipitation_unit: params.precipitation_unit as 'mm' | 'inch',
forecast_days: 6,
past_days: 1
});
fetchedHourly = {
hourly: hourlyJson.hourly,
utc_offset_seconds: utcOffset,
timestamps,
hourlyDates,
markAreas
hourly: result.hourly,
utc_offset_seconds: result.utcOffsetSeconds,
timestamps: result.hourlyTimestamps,
hourlyDates: result.hourlyDates,
markAreas: result.markAreas
};
const dailyDates = (dailyJson.daily.time as number[]).map(
(t: number) => new Date((t + (dailyJson.utc_offset_seconds ?? 0)) * 1000)
);
fetchedDaily = {
daily: dailyJson.daily,
dailyDates
daily: result.daily,
dailyDates: result.dailyDates
};
loading = false;