Files
wtr/lib/server-current-weather.ts
zv 2182297adc
All checks were successful
CI / Lint, typecheck and build (push) Successful in 9m54s
feat: add global weather support
2026-06-14 15:59:14 +02:00

114 lines
4.3 KiB
TypeScript

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);
}