nearby
This commit is contained in:
+85
-10
@@ -14,6 +14,7 @@ import { Unit } from '@openmeteo/sdk/unit';
|
||||
import { fetchWeatherApi } from 'openmeteo';
|
||||
|
||||
import { type DaylightBand, buildDaylightBands } from '$lib/charts/bands';
|
||||
import * as m from '$lib/paraglide/messages';
|
||||
|
||||
import type { VariableWithValues } from '@openmeteo/sdk/variable-with-values';
|
||||
import type { VariablesWithTime } from '@openmeteo/sdk/variables-with-time';
|
||||
@@ -313,8 +314,8 @@ export function humanizeWeatherError(err: unknown): FriendlyWeatherError {
|
||||
msg.includes('network request failed')
|
||||
) {
|
||||
return {
|
||||
title: "Couldn't reach the weather service",
|
||||
hint: 'Check your internet connection and try again.',
|
||||
title: m.err_network_title(),
|
||||
hint: m.err_network_hint(),
|
||||
detail: raw
|
||||
};
|
||||
}
|
||||
@@ -325,21 +326,21 @@ export function humanizeWeatherError(err: unknown): FriendlyWeatherError {
|
||||
msg.includes('coordinates')
|
||||
) {
|
||||
return {
|
||||
title: 'No data for this location with the selected model',
|
||||
hint: 'Regional weather models only cover their own area — "Best match" picks a suitable model automatically.',
|
||||
title: m.err_nodata_title(),
|
||||
hint: m.err_nodata_hint(),
|
||||
detail: raw
|
||||
};
|
||||
}
|
||||
if (msg.includes('invalid') || msg.includes('cannot be') || msg.includes('bad request')) {
|
||||
return {
|
||||
title: 'The weather service rejected the request',
|
||||
hint: 'Try different settings, or switch the model back to "Best match".',
|
||||
title: m.err_rejected_title(),
|
||||
hint: m.err_rejected_hint(),
|
||||
detail: raw
|
||||
};
|
||||
}
|
||||
return {
|
||||
title: 'Loading the weather data failed',
|
||||
hint: 'Try again in a moment. If it keeps happening, switch the model to "Best match".',
|
||||
title: m.err_generic_title(),
|
||||
hint: m.err_generic_hint(),
|
||||
detail: raw
|
||||
};
|
||||
}
|
||||
@@ -936,6 +937,8 @@ export interface HistoricalForecastParams extends WeatherLocation, WeatherUnitPa
|
||||
end_date: string;
|
||||
/** Hourly API variables to request; defaults to the core week set. */
|
||||
hourlyVariables?: string[];
|
||||
/** Reanalysis to read from; omitted lets the API pick. */
|
||||
model?: string;
|
||||
}
|
||||
|
||||
export interface HistoricalForecastResult {
|
||||
@@ -995,7 +998,8 @@ export async function fetchHistoricalWeather(
|
||||
temperature_unit: params.temperature_unit ?? 'celsius',
|
||||
wind_speed_unit: params.wind_speed_unit ?? 'kmh',
|
||||
precipitation_unit: params.precipitation_unit ?? 'mm',
|
||||
timezone: params.timezone
|
||||
timezone: params.timezone,
|
||||
models: params.model && params.model !== 'best_match' ? params.model : undefined
|
||||
};
|
||||
|
||||
const cleanParams: Record<string, string> = {};
|
||||
@@ -1201,6 +1205,8 @@ export interface SeasonalForecastParams extends WeatherLocation, WeatherUnitPara
|
||||
dailyVariables?: string[];
|
||||
/** Lead time in days; the API allows at most 216. */
|
||||
forecast_days?: number;
|
||||
/** Seasonal model; omitted lets the API pick. */
|
||||
model?: string;
|
||||
}
|
||||
|
||||
export interface SeasonalForecastResult {
|
||||
@@ -1273,7 +1279,8 @@ export async function fetchSeasonalForecast(
|
||||
temperature_unit: params.temperature_unit ?? 'celsius',
|
||||
wind_speed_unit: params.wind_speed_unit ?? 'kmh',
|
||||
precipitation_unit: params.precipitation_unit ?? 'mm',
|
||||
timezone: params.timezone
|
||||
timezone: params.timezone,
|
||||
models: params.model && params.model !== 'best_match' ? params.model : undefined
|
||||
};
|
||||
|
||||
const cleanParams: Record<string, string> = {};
|
||||
@@ -1372,3 +1379,71 @@ export async function fetchSeasonalForecast(
|
||||
timezone
|
||||
};
|
||||
}
|
||||
|
||||
// ─── Nearby cities snapshot ─────────────────────────────────────────────────────
|
||||
|
||||
export interface NearbyDaily {
|
||||
/** local calendar date ("yyyy-MM-dd") -> that day's summary for this city */
|
||||
byDate: Record<string, { weatherCode: number; max: number; min: number; precipitation: number }>;
|
||||
}
|
||||
|
||||
export interface NearbySnapshotParams extends WeatherUnitParams {
|
||||
points: { latitude: number; longitude: number }[];
|
||||
past_days?: number;
|
||||
forecast_days?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a daily summary for several locations in one request - the forecast
|
||||
* API takes comma-separated coordinates and answers with one response per
|
||||
* point, in order.
|
||||
*
|
||||
* Deliberately runs on best_match: the nearby list can reach well past the
|
||||
* domain of whatever regional model the page is showing, and a row of dashes
|
||||
* is worse than a row from a model that covers everywhere.
|
||||
*/
|
||||
export async function fetchNearbyDaily(
|
||||
params: NearbySnapshotParams
|
||||
): Promise<(NearbyDaily | null)[]> {
|
||||
if (params.points.length === 0) return [];
|
||||
|
||||
const apiParams: Record<string, string> = {
|
||||
latitude: params.points.map((p) => p.latitude).join(','),
|
||||
longitude: params.points.map((p) => p.longitude).join(','),
|
||||
daily: 'weather_code,temperature_2m_max,temperature_2m_min,precipitation_sum',
|
||||
temperature_unit: params.temperature_unit ?? 'celsius',
|
||||
wind_speed_unit: params.wind_speed_unit ?? 'kmh',
|
||||
precipitation_unit: params.precipitation_unit ?? 'mm',
|
||||
past_days: String(params.past_days ?? 3),
|
||||
forecast_days: String(params.forecast_days ?? 16),
|
||||
timezone: 'auto'
|
||||
};
|
||||
|
||||
const responses = await fetchWeatherApi(FORECAST_URL, apiParams);
|
||||
|
||||
return params.points.map((_, i) => {
|
||||
const response = responses[i];
|
||||
const dailyBlock = response?.daily();
|
||||
if (!dailyBlock) return null;
|
||||
|
||||
const utcOffsetSeconds = response.utcOffsetSeconds();
|
||||
const codes = getValues(dailyBlock.variables(0)!);
|
||||
const max = getValues(dailyBlock.variables(1)!);
|
||||
const min = getValues(dailyBlock.variables(2)!);
|
||||
const precip = getValues(dailyBlock.variables(3)!);
|
||||
|
||||
// Same convention as the seasonal fetch: shift by the response's own
|
||||
// offset, then read the calendar date off the ISO string.
|
||||
const byDate: NearbyDaily['byDate'] = {};
|
||||
getTimestamps(dailyBlock).forEach((t, d) => {
|
||||
const key = new Date(t + utcOffsetSeconds * 1000).toISOString().slice(0, 10);
|
||||
byDate[key] = {
|
||||
weatherCode: codes[d],
|
||||
max: max[d],
|
||||
min: min[d],
|
||||
precipitation: precip[d]
|
||||
};
|
||||
});
|
||||
return { byDate };
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user