Files
2026-05-31 20:43:27 -04:00

108 lines
3.9 KiB
Python

from dataclasses import dataclass
from typing import Any, Dict, Optional
from django.conf import settings
from adventures.providers.geocoding.google import reverse_geocode as google_reverse
from adventures.providers.geocoding.osm import reverse_geocode as osm_reverse
from adventures.utils.geocoding_utils import (
_extract_google_location_name,
_extract_osm_location_name,
_fetch_google_nearby_place_name,
_parse_google_address_components,
extractIsoCode,
)
@dataclass
class ReverseGeocodeResult:
data: Dict[str, Any]
provider_used: Optional[str]
def _normalize_google_geocode_payload(payload: Dict[str, Any], lat: float, lon: float, api_key: str) -> Dict[str, Any]:
results = (payload or {}).get("results", []) or []
if not results:
return {"error": "No location found for the given coordinates."}
nearby_place_name = _fetch_google_nearby_place_name(lat, lon, api_key)
location_name = _extract_google_location_name(results, nearby_place_name=nearby_place_name)
first_result = results[0]
address_result = next(
(result for result in results if "plus_code" not in result.get("types", [])),
first_result,
)
return {
"name": first_result.get("formatted_address"),
"location_name": location_name,
"address": _parse_google_address_components(address_result.get("address_components", [])),
}
def _reverse_geocode_quality(result: Dict[str, Any]) -> int:
if not result or result.get("error"):
return 0
score = 0
if result.get("region_id"):
score += 3
if result.get("city_id"):
score += 2
if result.get("country_id"):
score += 1
if result.get("location_name"):
score += 1
if result.get("display_name"):
score += 1
return score
def reverse_geocode(lat: float, lon: float, user) -> ReverseGeocodeResult:
google_result = None
api_key = getattr(settings, "GOOGLE_MAPS_API_KEY", None)
if api_key:
google_payload = google_reverse(lat, lon, api_key)
if not google_payload.error:
normalized_google = _normalize_google_geocode_payload(
google_payload.data, lat=lat, lon=lon, api_key=api_key
)
if normalized_google.get("error"):
google_result = normalized_google
else:
google_result = extractIsoCode(user, normalized_google)
if isinstance(google_result, dict) and "error" not in google_result:
google_result["provider_used"] = "google"
google_result["provider"] = "google"
if google_result and not google_result.get("error"):
if _reverse_geocode_quality(google_result) >= 5:
return ReverseGeocodeResult(data=google_result, provider_used="google")
osm_payload = osm_reverse(lat, lon)
osm_result = None
if not osm_payload.error:
osm_data = osm_payload.data
osm_data["location_name"] = _extract_osm_location_name(osm_data)
osm_result = extractIsoCode(user, osm_data)
if isinstance(osm_result, dict) and "error" not in osm_result:
osm_result["provider_used"] = "osm"
osm_result["provider"] = "osm"
if osm_result and not osm_result.get("error"):
if google_result and not google_result.get("error"):
google_score = _reverse_geocode_quality(google_result)
osm_score = _reverse_geocode_quality(osm_result)
best = google_result if google_score >= osm_score else osm_result
used = "google" if best is google_result else "osm"
return ReverseGeocodeResult(data=best, provider_used=used)
return ReverseGeocodeResult(data=osm_result, provider_used="osm")
if google_result:
return ReverseGeocodeResult(data=google_result, provider_used="google")
return ReverseGeocodeResult(data=osm_result or {"error": "Unable to reverse geocode location."}, provider_used=None)