Files
2026-04-22 21:00:37 +00:00

109 lines
4.2 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
export const dynamic = 'force-dynamic';
import { NextResponse } from "next/server";
// Convert WMO weather codes to wttr.in-compatible codes for emoji mapping
function wmoToWttrCode(wmo: number): string {
if (wmo === 0) return "113"; // Clear sunny
if (wmo === 1) return "116"; // Mainly clear
if (wmo === 2) return "116"; // Partly cloudy
if (wmo === 3) return "122"; // Overcast
if (wmo >= 45 && wmo <= 48) return "143"; // Fog
if (wmo >= 51 && wmo <= 55) return "185"; // Drizzle
if (wmo >= 56 && wmo <= 57) return "281"; // Freezing drizzle
if (wmo >= 61 && wmo <= 65) return "305"; // Rain
if (wmo >= 66 && wmo <= 67) return "281"; // Freezing rain
if (wmo >= 71 && wmo <= 77) return "227"; // Snow
if (wmo >= 80 && wmo <= 82) return "305"; // Rain showers
if (wmo >= 85 && wmo <= 86) return "260"; // Snow showers
if (wmo === 95) return "200"; // Thunderstorm
if (wmo >= 96 && wmo <= 99) return "389"; // Thunderstorm with hail
return "116";
}
function wmoToDesc(wmo: number): string {
if (wmo === 0) return "Ясно";
if (wmo === 1) return "Преим. ясно";
if (wmo === 2) return "Переменная облачность";
if (wmo === 3) return "Пасмурно";
if (wmo === 45 || wmo === 48) return "Туман";
if (wmo >= 51 && wmo <= 55) return "Морось";
if (wmo >= 61 && wmo <= 65) return "Дождь";
if (wmo >= 71 && wmo <= 77) return "Снег";
if (wmo >= 80 && wmo <= 82) return "Ливень";
if (wmo >= 85 && wmo <= 86) return "Снегопад";
if (wmo === 95) return "Гроза";
if (wmo >= 96 && wmo <= 99) return "Гроза с градом";
return "Облачно";
}
export async function GET(req: Request) {
try {
const { searchParams } = new URL(req.url);
const lat = searchParams.get("lat") || "59.9343";
const lon = searchParams.get("lon") || "30.3351";
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 8000);
const url = "https://api.open-meteo.com/v1/forecast?" + new URLSearchParams({
latitude: lat,
longitude: lon,
current: "temperature_2m,relative_humidity_2m,apparent_temperature,weather_code,wind_speed_10m",
daily: "weather_code,temperature_2m_max,temperature_2m_min,apparent_temperature_max,apparent_temperature_min,precipitation_probability_max,wind_speed_10m_max,relative_humidity_2m_max",
timezone: "Europe/Moscow",
forecast_days: "7",
});
const res = await fetch(url, {
signal: controller.signal,
cache: "no-store",
headers: { "Accept": "application/json" },
});
clearTimeout(timeout);
if (!res.ok) throw new Error(`Open-Meteo responded ${res.status}`);
const data = await res.json();
const current = data.current;
const daily = data.daily;
const forecast = (daily.time || []).map((date: string, i: number) => ({
date,
maxTemp: String(Math.round(daily.temperature_2m_max[i])),
minTemp: String(Math.round(daily.temperature_2m_min[i])),
desc: wmoToDesc(daily.weather_code[i]),
weatherCode: wmoToWttrCode(daily.weather_code[i]),
feelsLikeMax: String(Math.round(daily.apparent_temperature_max?.[i] ?? 0)),
feelsLikeMin: String(Math.round(daily.apparent_temperature_min?.[i] ?? 0)),
precipProb: String(daily.precipitation_probability_max?.[i] ?? 0),
windSpeed: String(Math.round((daily.wind_speed_10m_max?.[i] ?? 0) / 3.6)),
humidity: String(daily.relative_humidity_2m_max?.[i] ?? 0),
}));
return NextResponse.json({
temp: String(Math.round(current.temperature_2m)),
feelsLike: String(Math.round(current.apparent_temperature)),
humidity: String(current.relative_humidity_2m),
desc: wmoToDesc(current.weather_code),
weatherCode: wmoToWttrCode(current.weather_code),
windSpeed: String(Math.round(current.wind_speed_10m / 3.6)),
forecast,
});
} catch (e) {
console.error("Weather fetch error:", e);
return NextResponse.json(
{
temp: "—",
feelsLike: "—",
humidity: "—",
desc: "Нет данных",
weatherCode: "116",
windSpeed: "—",
forecast: [],
error: String(e),
},
{ status: 200 }
);
}
}