wayfarer-weather/lib/weather/geocode.ts

61 lines
2 KiB
TypeScript
Raw Permalink Normal View History

2026-10-31 21:20:16 -05:00
import { fetchJson } from "./http";
import type { Place } from "./types";
/**
* Turns a city name into coordinates (and coordinates back into a name).
*
* OpenWeather's geocoder is used when its key is set; otherwise we fall back
* to Open-Meteo's free geocoder so search still works with zero keys.
*/
interface OwmPlace {
name: string;
state?: string;
country: string;
lat: number;
lon: number;
}
interface OpenMeteoGeocodeResponse {
results?: { name: string; admin1?: string; country_code: string; latitude: number; longitude: number }[];
}
const OWM_GEO = "https://api.openweathermap.org/geo/1.0";
function fromOwm(place: OwmPlace): Place {
return { name: place.name, region: place.state, country: place.country, lat: place.lat, lon: place.lon };
}
export async function searchPlaces(query: string): Promise<Place[]> {
const key = process.env.OPENWEATHER_API_KEY?.trim();
const q = encodeURIComponent(query.slice(0, 120));
if (key) {
const places = await fetchJson<OwmPlace[]>(`${OWM_GEO}/direct?q=${q}&limit=5&appid=${key}`, "OpenWeather");
return places.map(fromOwm);
}
const data = await fetchJson<OpenMeteoGeocodeResponse>(
`https://geocoding-api.open-meteo.com/v1/search?name=${q}&count=5`,
"Open-Meteo",
);
return (data.results ?? []).map((r) => ({
name: r.name,
region: r.admin1,
country: r.country_code,
lat: r.latitude,
lon: r.longitude,
}));
}
/** Used by "Use my location". Without an OpenWeather key we can't name the spot. */
export async function reversePlace(lat: number, lon: number): Promise<Place> {
const fallback: Place = { name: "Your location", lat, lon };
const key = process.env.OPENWEATHER_API_KEY?.trim();
if (!key) return fallback;
const places = await fetchJson<OwmPlace[]>(`${OWM_GEO}/reverse?lat=${lat}&lon=${lon}&limit=1&appid=${key}`, "OpenWeather");
// Keep the user's exact coordinates rather than the city centre's.
return places[0] ? { ...fromOwm(places[0]), lat, lon } : fallback;
}