130 lines
4.6 KiB
TypeScript
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,
|
|
};
|
|
}
|