CityTrack / Backend /services /geocoding.py
vxrachit's picture
Backend v1 finished
45efbb3
import aiohttp
from typing import Optional
from dataclasses import dataclass
from Backend.core.logging import get_logger
logger = get_logger(__name__)
@dataclass
class LocationInfo:
city: Optional[str] = None
locality: Optional[str] = None
district: Optional[str] = None
state: Optional[str] = None
country: Optional[str] = None
pincode: Optional[str] = None
full_address: Optional[str] = None
class GeocodingService:
NOMINATIM_URL = "https://nominatim.openstreetmap.org/reverse"
async def reverse_geocode(self, latitude: float, longitude: float) -> LocationInfo:
params = {
"lat": latitude,
"lon": longitude,
"format": "json",
"addressdetails": 1,
"zoom": 18,
}
headers = {
"User-Agent": "CityIssueResolutionAgent/1.0"
}
try:
async with aiohttp.ClientSession() as session:
async with session.get(
self.NOMINATIM_URL,
params=params,
headers=headers,
timeout=aiohttp.ClientTimeout(total=10)
) as response:
if response.status == 200:
data = await response.json()
return self._parse_response(data)
else:
logger.warning(f"Geocoding failed: {response.status}")
return LocationInfo()
except Exception as e:
logger.error(f"Geocoding error: {e}")
return LocationInfo()
def _parse_response(self, data: dict) -> LocationInfo:
address = data.get("address", {})
city = (
address.get("city") or
address.get("town") or
address.get("municipality") or
address.get("village") or
address.get("suburb")
)
locality = (
address.get("suburb") or
address.get("neighbourhood") or
address.get("quarter") or
address.get("borough")
)
district = (
address.get("county") or
address.get("district") or
address.get("state_district")
)
state = address.get("state")
country = address.get("country")
pincode = address.get("postcode")
full_address = data.get("display_name")
return LocationInfo(
city=city,
locality=locality,
district=district,
state=state,
country=country,
pincode=pincode,
full_address=full_address,
)
async def get_city_from_coordinates(self, latitude: float, longitude: float) -> Optional[str]:
location = await self.reverse_geocode(latitude, longitude)
return location.city or location.locality or location.district
geocoding_service = GeocodingService()