feat: prefer IMGW ALARO forecast data
This commit is contained in:
@@ -1,53 +1,61 @@
|
||||
import type { DailyForecast, HourlyForecast, RawForecastSeries, RawWeatherForecast, WeatherForecast } from "@/types/forecast";
|
||||
import type { DailyForecast, ForecastSource, HourlyForecast, WeatherForecast } from "@/types/forecast";
|
||||
|
||||
function asArray(value: unknown): unknown[] {
|
||||
return Array.isArray(value) ? value : [];
|
||||
}
|
||||
|
||||
function readString(series: RawForecastSeries, key: keyof RawForecastSeries, index: number) {
|
||||
const value = asArray(series[key])[index];
|
||||
return typeof value === "string" && value ? value : null;
|
||||
}
|
||||
|
||||
function readNumber(series: RawForecastSeries, key: keyof RawForecastSeries, index: number) {
|
||||
const value = asArray(series[key])[index];
|
||||
function readNumber(value: unknown) {
|
||||
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
||||
}
|
||||
|
||||
function normalizeHourlyForecast(series: RawForecastSeries = {}): HourlyForecast[] {
|
||||
return asArray(series.time).flatMap((_, index) => {
|
||||
const time = readString(series, "time", index);
|
||||
function readString(value: unknown) {
|
||||
return typeof value === "string" && value ? value : null;
|
||||
}
|
||||
|
||||
function isForecastSource(value: unknown): value is ForecastSource {
|
||||
return value === "imgw-alaro" || value === "open-meteo";
|
||||
}
|
||||
|
||||
function normalizeSources(value: unknown): ForecastSource[] {
|
||||
return Array.isArray(value) ? value.filter(isForecastSource) : [];
|
||||
}
|
||||
|
||||
function normalizeHourlyForecast(value: unknown): HourlyForecast[] {
|
||||
return Array.isArray(value) ? value.flatMap((candidate) => {
|
||||
if (!candidate || typeof candidate !== "object") return [];
|
||||
const row = candidate as Partial<HourlyForecast>;
|
||||
const time = readString(row.time);
|
||||
if (!time) return [];
|
||||
return [{
|
||||
time,
|
||||
temperature: readNumber(series, "temperature_2m", index),
|
||||
feelsLike: readNumber(series, "apparent_temperature", index),
|
||||
precipitationProbability: readNumber(series, "precipitation_probability", index),
|
||||
precipitation: readNumber(series, "precipitation", index),
|
||||
weatherCode: readNumber(series, "weather_code", index),
|
||||
windSpeed: readNumber(series, "wind_speed_10m", index),
|
||||
temperature: readNumber(row.temperature),
|
||||
feelsLike: readNumber(row.feelsLike),
|
||||
precipitationProbability: readNumber(row.precipitationProbability),
|
||||
precipitation: readNumber(row.precipitation),
|
||||
weatherCode: readNumber(row.weatherCode),
|
||||
windSpeed: readNumber(row.windSpeed),
|
||||
source: isForecastSource(row.source) ? row.source : "open-meteo",
|
||||
}];
|
||||
});
|
||||
}) : [];
|
||||
}
|
||||
|
||||
function normalizeDailyForecast(series: RawForecastSeries = {}): DailyForecast[] {
|
||||
return asArray(series.time).flatMap((_, index) => {
|
||||
const date = readString(series, "time", index);
|
||||
function normalizeDailyForecast(value: unknown): DailyForecast[] {
|
||||
return Array.isArray(value) ? value.flatMap((candidate) => {
|
||||
if (!candidate || typeof candidate !== "object") return [];
|
||||
const row = candidate as Partial<DailyForecast>;
|
||||
const date = readString(row.date);
|
||||
if (!date) return [];
|
||||
return [{
|
||||
date,
|
||||
temperatureMax: readNumber(series, "temperature_2m_max", index),
|
||||
temperatureMin: readNumber(series, "temperature_2m_min", index),
|
||||
precipitationProbability: readNumber(series, "precipitation_probability_max", index),
|
||||
precipitation: readNumber(series, "precipitation_sum", index),
|
||||
weatherCode: readNumber(series, "weather_code", index),
|
||||
sunrise: readString(series, "sunrise", index),
|
||||
sunset: readString(series, "sunset", index),
|
||||
temperatureMax: readNumber(row.temperatureMax),
|
||||
temperatureMin: readNumber(row.temperatureMin),
|
||||
precipitationProbability: readNumber(row.precipitationProbability),
|
||||
precipitation: readNumber(row.precipitation),
|
||||
weatherCode: readNumber(row.weatherCode),
|
||||
sunrise: readString(row.sunrise),
|
||||
sunset: readString(row.sunset),
|
||||
sources: normalizeSources(row.sources),
|
||||
}];
|
||||
});
|
||||
}) : [];
|
||||
}
|
||||
|
||||
function normalizeForecast(raw: RawWeatherForecast): WeatherForecast {
|
||||
function normalizeForecast(raw: Partial<WeatherForecast>): WeatherForecast {
|
||||
const latitude = Number(raw.latitude);
|
||||
const longitude = Number(raw.longitude);
|
||||
if (!Number.isFinite(latitude) || !Number.isFinite(longitude)) throw new Error("Forecast service returned invalid coordinates.");
|
||||
@@ -57,6 +65,7 @@ function normalizeForecast(raw: RawWeatherForecast): WeatherForecast {
|
||||
timezone: typeof raw.timezone === "string" ? raw.timezone : "Europe/Warsaw",
|
||||
hourly: normalizeHourlyForecast(raw.hourly),
|
||||
daily: normalizeDailyForecast(raw.daily),
|
||||
sources: normalizeSources(raw.sources),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -64,5 +73,5 @@ export async function fetchForecast(latitude: number, longitude: number, signal?
|
||||
const params = new URLSearchParams({ latitude: String(latitude), longitude: String(longitude) });
|
||||
const response = await fetch(`/api/forecast?${params}`, { signal });
|
||||
if (!response.ok) throw new Error("Unable to load forecast.");
|
||||
return normalizeForecast(await response.json() as RawWeatherForecast);
|
||||
return normalizeForecast(await response.json() as Partial<WeatherForecast>);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user