| 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() |
|
|