fix: use Nominatim reverse geocoding for accurate country detection in atlas

Bounding boxes overlap for neighboring countries (e.g. Munich matched
Austria instead of Germany). Now uses Nominatim reverse geocoding with
in-memory cache as primary fallback, bounding boxes only as last resort.
This commit is contained in:
Maurice
2026-03-31 21:58:20 +02:00
parent fbe3b5b17e
commit 36cd2feca5

View File

@@ -1,4 +1,5 @@
import express, { Request, Response } from 'express';
import fetch from 'node-fetch';
import { db } from '../db/database';
import { authenticate } from '../middleware/auth';
import { AuthRequest, Trip, Place } from '../types';
@@ -6,6 +7,30 @@ import { AuthRequest, Trip, Place } from '../types';
const router = express.Router();
router.use(authenticate);
// Geocode cache: rounded coords -> country code
const geocodeCache = new Map<string, string | null>();
function roundKey(lat: number, lng: number): string {
return `${lat.toFixed(3)},${lng.toFixed(3)}`;
}
async function reverseGeocodeCountry(lat: number, lng: number): Promise<string | null> {
const key = roundKey(lat, lng);
if (geocodeCache.has(key)) return geocodeCache.get(key)!;
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' },
});
if (!res.ok) return null;
const data = await res.json() as { address?: { country_code?: string } };
const code = data.address?.country_code?.toUpperCase() || null;
geocodeCache.set(key, code);
return code;
} catch {
return null;
}
}
const COUNTRY_BOXES: Record<string, [number, number, number, number]> = {
AF:[60.5,29.4,75,38.5],AL:[19,39.6,21.1,42.7],DZ:[-8.7,19,12,37.1],AD:[1.4,42.4,1.8,42.7],AO:[11.7,-18.1,24.1,-4.4],
AR:[-73.6,-55.1,-53.6,-21.8],AM:[43.4,38.8,46.6,41.3],AU:[112.9,-43.6,153.6,-10.7],AT:[9.5,46.4,17.2,49],AZ:[44.8,38.4,50.4,41.9],
@@ -83,7 +108,7 @@ const CONTINENT_MAP: Record<string, string> = {
SE:'Europe',CH:'Europe',TH:'Asia',TR:'Europe',UA:'Europe',AE:'Asia',GB:'Europe',US:'North America',VN:'Asia',NG:'Africa',
};
router.get('/stats', (req: Request, res: Response) => {
router.get('/stats', async (req: Request, res: Response) => {
const authReq = req as AuthRequest;
const userId = authReq.user.id;
@@ -109,6 +134,9 @@ router.get('/stats', (req: Request, res: Response) => {
const countrySet = new Map<string, CountryEntry>();
for (const place of places) {
let code = getCountryFromAddress(place.address);
if (!code && place.lat && place.lng) {
code = await reverseGeocodeCountry(place.lat, place.lng);
}
if (!code && place.lat && place.lng) {
code = getCountryFromCoords(place.lat, place.lng);
}