From 527052aa70b34424370636c2122c922572305fec Mon Sep 17 00:00:00 2001 From: terraputix Date: Mon, 16 Feb 2026 11:36:49 +0100 Subject: [PATCH] timezone fixes and default parameters --- src/lib/services/weather.ts | 10 ++-- src/lib/utils/echarts/options.ts | 41 ++++++++++++++- .../weather/compare/[location]/+page.svelte | 12 ++++- src/routes/weather/options.ts | 52 ++++++++++++++++--- .../week/[location]/MeteogramCharts.svelte | 17 +++++- 5 files changed, 114 insertions(+), 18 deletions(-) diff --git a/src/lib/services/weather.ts b/src/lib/services/weather.ts index cbe1142..e758ee6 100644 --- a/src/lib/services/weather.ts +++ b/src/lib/services/weather.ts @@ -387,7 +387,7 @@ export async function fetchWeekForecast(params: WeekForecastParams): Promise { - const forecastApiParams: Record = { + const forecastApiParams: Record = { latitude: String(params.latitude), longitude: String(params.longitude), hourly: params.hourlyVariables.join(','), @@ -395,7 +395,8 @@ export async function fetchModelComparison( daily: 'sunrise,sunset', temperature_unit: params.temperature_unit ?? 'celsius', wind_speed_unit: params.wind_speed_unit ?? 'kmh', - precipitation_unit: params.precipitation_unit ?? 'mm' + precipitation_unit: params.precipitation_unit ?? 'mm', + timezone: params.timezone }; const responses = await fetchWeatherApi(FORECAST_URL, forecastApiParams); @@ -493,7 +494,7 @@ export async function fetchEnsembleForecast( ): Promise { const forecastDays = params.forecast_days ?? 14; - const ensembleParams: Record = { + const ensembleParams: Record = { latitude: String(params.latitude), longitude: String(params.longitude), hourly: params.hourlyVariables.join(','), @@ -501,7 +502,8 @@ export async function fetchEnsembleForecast( forecast_days: String(forecastDays), temperature_unit: params.temperature_unit ?? 'celsius', wind_speed_unit: params.wind_speed_unit ?? 'kmh', - precipitation_unit: params.precipitation_unit ?? 'mm' + precipitation_unit: params.precipitation_unit ?? 'mm', + timezone: params.timezone }; const dailyParams: Record = { diff --git a/src/lib/utils/echarts/options.ts b/src/lib/utils/echarts/options.ts index 8ad2ea5..5c8b11b 100644 --- a/src/lib/utils/echarts/options.ts +++ b/src/lib/utils/echarts/options.ts @@ -131,7 +131,7 @@ export function buildTooltip( colors?: ThemeColors ): Record { const c = colors ?? getThemeColors(); - const { unit } = options; + const { unit, timezone } = options; return { trigger: 'axis', @@ -142,7 +142,15 @@ export function buildTooltip( backgroundColor: c.tooltipBg, color: c.text, borderColor: c.tooltipBorder, - borderWidth: 1 + borderWidth: 1, + formatter: timezone + ? (params: { axisDimension: string; value: number }) => { + if (params.axisDimension === 'x') { + return formatZoned(new Date(params.value), timezone, 'EEE d MMM HH:mm'); + } + return params.value.toFixed(1); + } + : undefined } }, backgroundColor: c.tooltipBg, @@ -150,6 +158,27 @@ export function buildTooltip( textStyle: { color: c.text }, + formatter: timezone + ? ( + params: Array<{ + axisValue: number; + seriesName: string; + marker: string; + value: number | number[] | null; + }> + ) => { + if (!params || params.length === 0) return ''; + const date = new Date(params[0].axisValue); + let html = `${formatZoned(date, timezone, 'EEE d MMM HH:mm')}
`; + params.forEach((item) => { + if (item.seriesName === 'Daylight' || item.seriesName === 'Current Time') return; + const val = Array.isArray(item.value) ? item.value[1] : item.value; + if (val === null || val === undefined) return; + html += `${item.marker} ${item.seriesName}: ${val.toFixed(1)} ${unit}
`; + }); + return html; + } + : undefined, valueFormatter: (value: number) => { if (value === null || value === undefined) return '-'; return value.toFixed(1) + ' ' + unit; @@ -197,6 +226,14 @@ export function buildTimeXAxis(timezone?: string, colors?: ThemeColors): Record< color: c.axisLine } }, + axisPointer: { + label: { + formatter: timezone + ? (params: { value: number }) => + formatZoned(new Date(params.value), timezone, 'EEE d MMM HH:mm') + : undefined + } + }, axisLabel: { color: c.text, hideOverlap: true, diff --git a/src/routes/weather/compare/[location]/+page.svelte b/src/routes/weather/compare/[location]/+page.svelte index 8d3e90f..342b963 100644 --- a/src/routes/weather/compare/[location]/+page.svelte +++ b/src/routes/weather/compare/[location]/+page.svelte @@ -53,13 +53,21 @@ let params = $state({ ...defaultParameters, - hourly: ['temperature_2m', 'rain', 'relative_humidity_2m'], + hourly: [ + 'temperature_2m', + 'rain', + 'relative_humidity_2m', + 'wind_speed_10m', + 'wind_direction_10m' + ], models: [ + 'ecmwf_ifs', 'ecmwf_ifs025', 'meteofrance_seamless', 'ukmo_seamless', 'icon_seamless', - 'gem_seamless' + 'gem_seamless', + 'gfs_seamless' ] }); diff --git a/src/routes/weather/options.ts b/src/routes/weather/options.ts index 7e52522..4ec05b6 100644 --- a/src/routes/weather/options.ts +++ b/src/routes/weather/options.ts @@ -7,16 +7,52 @@ export const defaultParameters = { export const models = [ { value: 'best_match', label: 'Best match' }, - { value: 'gfs_seamless', label: 'NCEP GFS Seamless' }, - { value: 'jma_seamless', label: 'JMA Seamless' }, + { value: 'ecmwf_ifs', label: 'ECMWF IFS' }, + { value: 'ecmwf_ifs025', label: 'ECMWF IFS 0.25' }, + { value: 'ecmwf_aifs025_single', label: 'ECMWF AIFS 0.25 Single' }, + { value: 'cma_grapes_global', label: 'CMA GRAPES Global' }, + { value: 'bom_access_global', label: 'BOM ACCESS Global' }, { value: 'kma_seamless', label: 'KMA Seamless' }, - { value: 'icon_seamless', label: 'DWD ICON Seamless' }, + { value: 'kma_ldps', label: 'KMA LDPS' }, + { value: 'kma_gdps', label: 'KMA GDPS' }, + { value: 'meteofrance_seamless', label: 'Meteo-France Seamless' }, + { value: 'meteofrance_arpege_world', label: 'Meteo-France ARPEGE World' }, + { value: 'meteofrance_arpege_europe', label: 'Meteo-France ARPEGE Europe' }, + { value: 'meteofrance_arome_france', label: 'Meteo-France AROME France' }, + { value: 'meteofrance_arome_france_hd', label: 'Meteo-France AROME France HD' }, + { value: 'knmi_seamless', label: 'KNMI Seamless' }, + { value: 'knmi_harmonie_arome_europe', label: 'KNMI Harmonie Arome Europe' }, + { value: 'knmi_harmonie_arome_netherlands', label: 'KNMI Harmonie Arome Netherlands' }, + { value: 'dmi_seamless', label: 'DMI Seamless' }, + { value: 'dmi_harmonie_arome_europe', label: 'DMI Harmonie Arome Europe' }, + { value: 'ukmo_seamless', label: 'UKMO Seamless' }, + { value: 'ukmo_global_deterministic_10km', label: 'UKMO Global Deterministic 10km' }, + { value: 'ukmo_uk_deterministic_2km', label: 'UKMO UK Deterministic 2km' }, + { value: 'meteoswiss_icon_seamless', label: 'MeteoSwiss ICON Seamless' }, + { value: 'meteoswiss_icon_ch2', label: 'MeteoSwiss ICON CH2' }, + { value: 'meteoswiss_icon_ch1', label: 'MeteoSwiss ICON CH1' }, + { value: 'metno_nordic', label: 'MET Norway Nordic' }, + { value: 'metno_seamless', label: 'MET Norway Seamless' }, + { value: 'gem_hrdps_west', label: 'GEM HRDPS West' }, + { value: 'gem_regional', label: 'GEM Regional' }, + { value: 'gem_global', label: 'GEM Global' }, { value: 'gem_seamless', label: 'GEM Seamless' }, - { value: 'meteofrance_seamless', label: 'Météo-France Seamless' }, - { value: 'metno_seamless', label: 'MET Norway Seamless (with ECMWF)' }, - { value: 'knmi_seamless', label: 'KNMI Seamless (with ECMWF)' }, - { value: 'dmi_seamless', label: 'DMI Seamless (with ECMWF)' }, - { value: 'ukmo_seamless', label: 'UK Met Office Seamless' } + { value: 'jma_seamless', label: 'JMA Seamless' }, + { value: 'jma_msm', label: 'JMA MSM' }, + { value: 'jma_gsm', label: 'JMA GSM' }, + { value: 'gfs_seamless', label: 'GFS Seamless' }, + { value: 'gfs_global', label: 'GFS Global' }, + { value: 'gfs_hrrr', label: 'GFS HRRR' }, + { value: 'gfs_graphcast025', label: 'GFS Graphcast 0.25' }, + { value: 'ncep_nbm_conus', label: 'NCEP NBM CONUS' }, + { value: 'ncep_nam_conus', label: 'NCEP NAM CONUS' }, + { value: 'ncep_aigfs025', label: 'NCEP AIGFS 0.25' }, + { value: 'ncep_hgefs025_ensemble_mean', label: 'NCEP HG-EFS 0.25 Ensemble Mean' }, + { value: 'icon_seamless', label: 'ICON Seamless (DWD)' }, + { value: 'icon_global', label: 'ICON Global' }, + { value: 'icon_eu', label: 'ICON EU' }, + { value: 'icon_d2', label: 'ICON-D2' }, + { value: 'italia_meteo_arpae_icon_2i', label: 'Italia Meteo ARPAE ICON 2i' } ]; export const hourly = [ diff --git a/src/routes/weather/week/[location]/MeteogramCharts.svelte b/src/routes/weather/week/[location]/MeteogramCharts.svelte index 1a666dd..bbcb480 100644 --- a/src/routes/weather/week/[location]/MeteogramCharts.svelte +++ b/src/routes/weather/week/[location]/MeteogramCharts.svelte @@ -171,7 +171,14 @@ }); const tooltipBase = ( - formatter: (params: Record[]) => string + formatter: ( + params: Array<{ + axisValue: number; + seriesName: string; + marker: string; + value: number | number[] | null; + }> + ) => string ): Record => ({ trigger: 'axis', axisPointer: { @@ -181,7 +188,13 @@ backgroundColor: colors.tooltipBg, color: colors.text, borderColor: colors.tooltipBorder, - borderWidth: 1 + borderWidth: 1, + formatter: (params: { axisDimension: string; value: number }) => { + if (params.axisDimension === 'x') { + return formatZoned(new Date(params.value), data.timezone, 'EEE d MMM HH:mm'); + } + return params.value.toFixed(1); + } } }, backgroundColor: colors.tooltipBg,