unify weather data fetching
This commit is contained in:
@@ -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 }));
|
||||
|
||||
@@ -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++) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user