From 16da230813d79c2d69aaa84f6f312214a70cbfb3 Mon Sep 17 00:00:00 2001 From: TheOnlyMace <0815cracky@gmail.com> Date: Sun, 28 Jun 2026 23:07:29 +0200 Subject: [PATCH] Enhance geocoding service and frontend search functionality. Refactor geocoding logic to improve postal code handling and response parsing. Update frontend searchPlaces function to include postal code in results display and adjust input validation. Modify CSS for improved search results styling. --- api/app/services/geocoding.py | 102 ++++++++++++++++++++++------------ frontend/app.js | 5 +- frontend/style.css | 6 +- 3 files changed, 74 insertions(+), 39 deletions(-) diff --git a/api/app/services/geocoding.py b/api/app/services/geocoding.py index 53b212f..ff6679d 100644 --- a/api/app/services/geocoding.py +++ b/api/app/services/geocoding.py @@ -1,17 +1,78 @@ from __future__ import annotations import hashlib +import re from typing import Any from app.config import CACHE_TTL_GEOCODE, NOMINATIM_BASE_URL, NOMINATIM_USER_AGENT from app.http_client import get_client from app.database import get_cached_json, set_cached_json +_POSTAL_ONLY = re.compile(r"^\d{4,5}$") +_POSTAL_CITY = re.compile(r"^(\d{4,5})\s+(.+)$") + def _query_hash(query: str) -> str: return hashlib.sha256(query.strip().lower().encode("utf-8")).hexdigest() +def _parse_nominatim_item(item: dict[str, Any]) -> dict[str, Any]: + address = item.get("address") or {} + label = item.get("display_name", "") + place = ( + address.get("city") + or address.get("town") + or address.get("village") + or address.get("municipality") + or address.get("county") + or label.split(",")[0] + ) + return { + "label": label, + "place": place, + "lat": float(item["lat"]), + "lon": float(item["lon"]), + "type": item.get("type"), + "postcode": address.get("postcode"), + "county": address.get("county"), + "state": address.get("state"), + } + + +def _country_codes_for_postal(postal: str) -> str: + if len(postal) == 5: + return "de" + return "at,ch" + + +def _build_search_params(query: str, limit: int) -> dict[str, Any]: + base = { + "format": "jsonv2", + "addressdetails": 1, + "limit": limit, + } + city_match = _POSTAL_CITY.match(query) + if city_match: + postal, city = city_match.groups() + return { + **base, + "postalcode": postal, + "city": city.strip(), + "countrycodes": _country_codes_for_postal(postal), + } + if _POSTAL_ONLY.match(query): + return { + **base, + "postalcode": query, + "countrycodes": _country_codes_for_postal(query), + } + return { + **base, + "q": query, + "countrycodes": "de,at,ch", + } + + async def _nominatim_get(path: str, params: dict[str, Any]) -> Any: headers = {"User-Agent": NOMINATIM_USER_AGENT, "Accept-Language": "de"} client = get_client() @@ -21,7 +82,10 @@ async def _nominatim_get(path: str, params: dict[str, Any]) -> Any: async def search_places(query: str, limit: int = 8) -> list[dict[str, Any]]: - if len(query.strip()) < 2: + query = query.strip() + if len(query) < 2: + return [] + if query.isdigit() and len(query) < 4: return [] qhash = _query_hash(f"search:{query}:{limit}") @@ -29,40 +93,8 @@ async def search_places(query: str, limit: int = 8) -> list[dict[str, Any]]: if cached is not None: return cached - raw = await _nominatim_get( - "/search", - { - "q": query, - "format": "jsonv2", - "addressdetails": 1, - "limit": limit, - "countrycodes": "de,at,ch", - }, - ) - results = [] - for item in raw: - address = item.get("address") or {} - label = item.get("display_name", "") - place = ( - address.get("city") - or address.get("town") - or address.get("village") - or address.get("municipality") - or address.get("county") - or label.split(",")[0] - ) - results.append( - { - "label": label, - "place": place, - "lat": float(item["lat"]), - "lon": float(item["lon"]), - "type": item.get("type"), - "postcode": address.get("postcode"), - "county": address.get("county"), - "state": address.get("state"), - } - ) + raw = await _nominatim_get("/search", _build_search_params(query, limit)) + results = [_parse_nominatim_item(item) for item in raw] await set_cached_json( "geocode_cache", diff --git a/frontend/app.js b/frontend/app.js index dc2fd8e..ac0ca8b 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -758,7 +758,7 @@ function toggleWarningsOnMap() { } async function searchPlaces(query) { - if (query.length < 2) { + if (query.length < 2 || (/^\d+$/.test(query) && query.length < 4)) { el("searchResults").classList.add("hidden"); return; } @@ -767,7 +767,7 @@ async function searchPlaces(query) { const results = data.results || []; el("searchResults").innerHTML = results.length ? results.map((r, i) => `