Files
wtr/lib/location-utils.ts
zv ee55521803
All checks were successful
CI / Lint, typecheck and build (push) Successful in 9m56s
chore: add prettier formatting
2026-06-14 20:26:56 +02:00

130 lines
4.6 KiB
TypeScript

import type { MeteoStationPosition, SynopStation } from "@/types/imgw";
import type { LocationSearchResult, SelectedLocation } from "@/types/location";
import { getCountyTerytForLocation } from "@/lib/warning-regions";
import { getWeatherRegionForCountryCode } from "@/types/weather-region";
const stationAliases: Record<string, string> = {
gdansk: "gdanskrebiechowo",
gorzow: "gorzowwielkopolski",
katowice: "katowicemuchowiec",
kielce: "kielcesukow",
kolo: "koloradoszewice",
kolobrzeg: "kolobrzegdzwirzyno",
krakow: "krakowbalice",
lublin: "lublinradawiec",
lodz: "lodzlublinek",
poznan: "poznanlawica",
resko: "reskosmolsko",
rzeszow: "rzeszowjasionka",
warszawa: "warszawaokecie",
};
function normalizeName(value: string) {
return value
.replace(/[Łł]/g, "l")
.normalize("NFD")
.replace(/[\u0300-\u036f]/g, "")
.replace(/[^a-zA-Z0-9]/g, "")
.toLowerCase();
}
function distanceKm(latitudeA: number, longitudeA: number, latitudeB: number, longitudeB: number) {
const earthRadiusKm = 6371;
const toRadians = (value: number) => (value * Math.PI) / 180;
const latitudeDistance = toRadians(latitudeB - latitudeA);
const longitudeDistance = toRadians(longitudeB - longitudeA);
const a =
Math.sin(latitudeDistance / 2) ** 2 +
Math.cos(toRadians(latitudeA)) * Math.cos(toRadians(latitudeB)) * Math.sin(longitudeDistance / 2) ** 2;
return earthRadiusKm * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
}
export interface LocatedSynopStation extends SynopStation {
latitude: number;
longitude: number;
}
export function locateSynopStations(stations: SynopStation[], positions: MeteoStationPosition[]) {
const positionsByName = new Map(positions.map((position) => [normalizeName(position.name), position]));
return stations.flatMap<LocatedSynopStation>((station) => {
const normalizedStation = normalizeName(station.name);
const position = positionsByName.get(stationAliases[normalizedStation] ?? normalizedStation);
return position ? [{ ...station, latitude: position.latitude, longitude: position.longitude }] : [];
});
}
export function findNearestSynopStation(
location: Pick<LocationSearchResult, "name" | "province" | "district" | "latitude" | "longitude">,
stations: LocatedSynopStation[],
): SelectedLocation | null {
const nearest = stations.reduce<{ station: LocatedSynopStation; distanceKm: number } | null>((best, station) => {
const distance = distanceKm(location.latitude, location.longitude, station.latitude, station.longitude);
return !best || distance < best.distanceKm ? { station, distanceKm: distance } : best;
}, null);
if (!nearest) return null;
return {
name: location.name,
province: location.province,
district: location.district,
country: "Polska",
countryCode: "PL",
admin1: location.province,
admin2: location.district,
timezone: "Europe/Warsaw",
region: "PL",
countyTeryt: getCountyTerytForLocation(location.province, location.district, location.name),
latitude: location.latitude,
longitude: location.longitude,
stationId: nearest.station.id,
stationName: nearest.station.name,
distanceKm: Math.round(nearest.distanceKm),
};
}
export function createSelectedLocation(
location: Pick<
LocationSearchResult,
| "name"
| "province"
| "district"
| "country"
| "countryCode"
| "admin1"
| "admin2"
| "timezone"
| "region"
| "latitude"
| "longitude"
>,
stations: LocatedSynopStation[],
): SelectedLocation {
const countryCode = location.countryCode?.toUpperCase() ?? null;
const region = location.region ?? getWeatherRegionForCountryCode(countryCode);
const nearest =
region === "PL"
? stations.reduce<{ station: LocatedSynopStation; distanceKm: number } | null>((best, station) => {
const distance = distanceKm(location.latitude, location.longitude, station.latitude, station.longitude);
return !best || distance < best.distanceKm ? { station, distanceKm: distance } : best;
}, null)
: null;
return {
name: location.name,
province: location.province,
district: location.district,
country: location.country,
countryCode,
admin1: location.admin1,
admin2: location.admin2,
timezone: location.timezone,
region,
countyTeryt:
region === "PL" ? getCountyTerytForLocation(location.province, location.district, location.name) : null,
latitude: location.latitude,
longitude: location.longitude,
stationId: nearest?.station.id ?? null,
stationName: nearest?.station.name ?? null,
distanceKm: nearest ? Math.round(nearest.distanceKm) : null,
};
}