From 36cd2feca5cd24e8595b2a9dd707aa81607a8f2a Mon Sep 17 00:00:00 2001 From: Maurice Date: Tue, 31 Mar 2026 21:58:20 +0200 Subject: [PATCH] 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. --- server/src/routes/atlas.ts | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/server/src/routes/atlas.ts b/server/src/routes/atlas.ts index a34c0fa..76c9ff4 100644 --- a/server/src/routes/atlas.ts +++ b/server/src/routes/atlas.ts @@ -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(); + +function roundKey(lat: number, lng: number): string { + return `${lat.toFixed(3)},${lng.toFixed(3)}`; +} + +async function reverseGeocodeCountry(lat: number, lng: number): Promise { + 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 = { 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 = { 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(); 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); }