import { normalizeImgwCurrentWeather } from "@/lib/imgw-current-api"; import { toNumber } from "@/lib/weather-utils"; import type { CurrentWeatherCondition, ImgwCurrentWeather, RawImgwHybridWeatherResponse } from "@/types/imgw-current"; import type { WeatherRegion } from "@/types/weather-region"; const IMGW_HYBRID_URL = "https://meteo.imgw.pl/api/v1/forecast/fcapi"; // This browser token is published by the official meteo.imgw.pl frontend. const IMGW_HYBRID_TOKEN = "p4DXKjsYadfBV21TYrDk"; const OPEN_METEO_FORECAST_URL = "https://api.open-meteo.com/v1/forecast"; interface RawOpenMeteoCurrentResponse { current?: { time?: string; temperature_2m?: unknown; apparent_temperature?: unknown; relative_humidity_2m?: unknown; precipitation?: unknown; rain?: unknown; showers?: unknown; snowfall?: unknown; weather_code?: unknown; cloud_cover?: unknown; pressure_msl?: unknown; wind_speed_10m?: unknown; wind_direction_10m?: unknown; }; } function getOpenMeteoCondition(weatherCode: number | null, rain: number | null, showers: number | null, snowfall: number | null): CurrentWeatherCondition { if (weatherCode !== null && weatherCode >= 95) return "thunderstorm"; if ((snowfall ?? 0) > 0 || (weatherCode !== null && weatherCode >= 71 && weatherCode <= 77)) return "snow"; if ((rain ?? 0) > 0 || (showers ?? 0) > 0 || (weatherCode !== null && weatherCode >= 51 && weatherCode <= 67) || (weatherCode !== null && weatherCode >= 80 && weatherCode <= 82)) return "rain"; return null; } function normalizeOpenMeteoCurrent(payload: RawOpenMeteoCurrentResponse): ImgwCurrentWeather | null { const current = payload.current; if (!current?.time) return null; const measuredAt = new Date(current.time).toISOString(); const weatherCode = toNumber(current.weather_code); const rain = toNumber(current.rain); const showers = toNumber(current.showers); const snowfall = toNumber(current.snowfall); const precipitation = toNumber(current.precipitation); return { coverage: "global-model", region: "GLOBAL", source: "OPEN_METEO", sourceLabel: "Open-Meteo", sourceType: "model", isOfficial: false, fetchedAt: new Date().toISOString(), measuredAt, temperature: toNumber(current.temperature_2m), feelsLike: toNumber(current.apparent_temperature), windSpeed: toNumber(current.wind_speed_10m), windDirection: toNumber(current.wind_direction_10m), humidity: toNumber(current.relative_humidity_2m), pressure: toNumber(current.pressure_msl), precipitation10m: precipitation, rainfall10m: rain ?? showers ?? precipitation, snowfall10m: snowfall, cloudCover: toNumber(current.cloud_cover), weatherCode, condition: getOpenMeteoCondition(weatherCode, rain, showers, snowfall), }; } export async function fetchImgwHybridCurrentWeather(latitude: number, longitude: number) { const params = new URLSearchParams({ token: IMGW_HYBRID_TOKEN, lat: String(latitude), lon: String(longitude), m: "hybrid", }); const response = await fetch(`${IMGW_HYBRID_URL}?${params}`, { next: { revalidate: 120 } }); if (!response.ok) throw new Error("IMGW Hybrid service is unavailable."); return await response.json() as RawImgwHybridWeatherResponse; } export async function fetchServerCurrentWeather(latitude: number, longitude: number, region: WeatherRegion) { if (region === "PL") { return normalizeImgwCurrentWeather(await fetchImgwHybridCurrentWeather(latitude, longitude)); } const params = new URLSearchParams({ latitude: String(latitude), longitude: String(longitude), current: [ "temperature_2m", "apparent_temperature", "relative_humidity_2m", "precipitation", "rain", "showers", "snowfall", "weather_code", "cloud_cover", "pressure_msl", "wind_speed_10m", "wind_direction_10m", ].join(","), timezone: "auto", wind_speed_unit: "ms", precipitation_unit: "mm", timeformat: "iso8601", forecast_days: "1", }); const response = await fetch(`${OPEN_METEO_FORECAST_URL}?${params}`, { next: { revalidate: 120 } }); if (!response.ok) throw new Error("Open-Meteo current conditions are unavailable."); return normalizeOpenMeteoCurrent(await response.json() as RawOpenMeteoCurrentResponse); }