61 lines
2 KiB
TypeScript
61 lines
2 KiB
TypeScript
|
|
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;
|
||
|
|
}
|