File size: 4,154 Bytes
d69447e |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
"""
Geocoding service for FleetMind
Handles address validation with HERE API and smart mock fallback
"""
import os
import requests
import logging
from typing import Dict, Optional
logger = logging.getLogger(__name__)
# Common city coordinates for mock geocoding
CITY_COORDINATES = {
"san francisco": (37.7749, -122.4194),
"sf": (37.7749, -122.4194),
"new york": (40.7128, -74.0060),
"nyc": (40.7128, -74.0060),
"los angeles": (34.0522, -118.2437),
"la": (34.0522, -118.2437),
"chicago": (41.8781, -87.6298),
"houston": (29.7604, -95.3698),
"phoenix": (33.4484, -112.0740),
"philadelphia": (39.9526, -75.1652),
"san antonio": (29.4241, -98.4936),
"san diego": (32.7157, -117.1611),
"dallas": (32.7767, -96.7970),
"austin": (30.2672, -97.7431),
"seattle": (47.6062, -122.3321),
"boston": (42.3601, -71.0589),
"denver": (39.7392, -104.9903),
"miami": (25.7617, -80.1918),
"atlanta": (33.7490, -84.3880),
"portland": (45.5152, -122.6784),
}
class GeocodingService:
"""Handle address geocoding with HERE API and smart mock fallback"""
def __init__(self):
self.here_api_key = os.getenv("HERE_API_KEY", "")
self.use_mock = not self.here_api_key or self.here_api_key.startswith("your_")
if self.use_mock:
logger.info("Geocoding: Using mock (HERE_API_KEY not configured)")
else:
logger.info("Geocoding: Using HERE Maps API")
def geocode(self, address: str) -> Dict:
"""
Geocode address, using mock if API unavailable
Args:
address: Street address to geocode
Returns:
Dict with keys: lat, lng, formatted_address, confidence
"""
if self.use_mock:
return self._geocode_mock(address)
else:
try:
return self._geocode_here(address)
except Exception as e:
logger.error(f"HERE API failed: {e}, falling back to mock")
return self._geocode_mock(address)
def _geocode_here(self, address: str) -> Dict:
"""Real HERE API geocoding"""
url = "https://geocode.search.hereapi.com/v1/geocode"
params = {
"q": address,
"apiKey": self.here_api_key
}
response = requests.get(url, params=params, timeout=10)
response.raise_for_status()
data = response.json()
if not data.get("items"):
# No results found, fall back to mock
logger.warning(f"HERE API found no results for: {address}")
return self._geocode_mock(address)
# Get first result
item = data["items"][0]
position = item["position"]
return {
"lat": position["lat"],
"lng": position["lng"],
"formatted_address": item.get("address", {}).get("label", address),
"confidence": "high (HERE API)"
}
def _geocode_mock(self, address: str) -> Dict:
"""
Smart mock geocoding for testing
Tries to detect city name and use approximate coordinates
"""
address_lower = address.lower()
# Try to find a city match
for city, coords in CITY_COORDINATES.items():
if city in address_lower:
logger.info(f"Mock geocoding detected city: {city}")
return {
"lat": coords[0],
"lng": coords[1],
"formatted_address": address,
"confidence": f"medium (mock - {city})"
}
# Default to San Francisco if no city detected
logger.info("Mock geocoding: Using default SF coordinates")
return {
"lat": 37.7749,
"lng": -122.4194,
"formatted_address": address,
"confidence": "low (mock - default)"
}
def get_status(self) -> str:
"""Get geocoding service status"""
if self.use_mock:
return "⚠️ Using mock geocoding (add HERE_API_KEY for real)"
else:
return "✅ HERE Maps API connected"
|