feat: add global weather support
All checks were successful
CI / Lint, typecheck and build (push) Successful in 9m54s
All checks were successful
CI / Lint, typecheck and build (push) Successful in 9m54s
This commit is contained in:
113
lib/server-current-weather.ts
Normal file
113
lib/server-current-weather.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user