fix(atlas): prevent Nominatim 429 rate limiting (#576)

- Swap resolve order: try local bbox lookup before Nominatim reverse
  geocode — eliminates most external API calls
- Add global throttling (1.1s min between requests) to
  reverseGeocodeCountry so /stats can't flood Nominatim
- Update User-Agent header to include repo URL per Nominatim policy
This commit is contained in:
Maurice
2026-04-13 20:16:36 +02:00
parent c0c59b6d80
commit 1f68ba1ea1
+11 -4
View File
@@ -171,12 +171,19 @@ export const CONTINENT_MAP: Record<string, string> = {
// ── Geocoding helpers ───────────────────────────────────────────────────────
let lastNominatimCall = 0;
export async function reverseGeocodeCountry(lat: number, lng: number): Promise<string | null> {
const key = roundKey(lat, lng);
if (geocodeCache.has(key)) return geocodeCache.get(key)!;
// Nominatim rate limit: max 1 req/sec
const now = Date.now();
const elapsed = now - lastNominatimCall;
if (elapsed < 1100) await new Promise(r => setTimeout(r, 1100 - elapsed));
lastNominatimCall = Date.now();
try {
const res = await fetch(`https://nominatim.openstreetmap.org/reverse?lat=${lat}&lon=${lng}&format=json&zoom=3&accept-language=en`, {
headers: { 'User-Agent': 'TREK Travel Planner' },
headers: { 'User-Agent': 'TREK Travel Planner (https://github.com/mauriceboe/TREK)' },
});
if (!res.ok) return null;
const data = await res.json() as { address?: { country_code?: string } };
@@ -215,15 +222,15 @@ export function getCountryFromAddress(address: string | null): string | null {
return null;
}
// ── Resolve a place to a country code (address -> geocode -> bbox) ──────────
// ── Resolve a place to a country code (address -> bbox -> geocode) ──────────
async function resolveCountryCode(place: Place): Promise<string | null> {
let code = getCountryFromAddress(place.address);
if (!code && place.lat && place.lng) {
code = await reverseGeocodeCountry(place.lat, place.lng);
code = getCountryFromCoords(place.lat, place.lng);
}
if (!code && place.lat && place.lng) {
code = getCountryFromCoords(place.lat, place.lng);
code = await reverseGeocodeCountry(place.lat, place.lng);
}
return code;
}