Geocoding Guide
Note: Geocoding functionality has been re-enabled with an extended cache duration of 1 year to minimize external API calls.
The Tracker GraphQL API includes a geocoding system that efficiently converts location coordinates into human-readable addresses while minimizing external API calls.
Overview
Making Geocoding Requests
GraphQL Mutation
mutation RequestGeocoding($locationIds: [ID!]!) {
requestGeocoding(locationIds: $locationIds) {
accepted
message
}
}
Example Request
const response = await client.mutate({
mutation: REQUEST_GEOCODING,
variables: {
locationIds: ["loc_123", "loc_124"],
},
});
Real-time Updates
Geocoding results are sent via Socket.IO events:
socket.on("geocoding_complete", (data) => {
console.log("Location updated:", data);
// {
// locationId: "loc_123",
// nearestCity: "San Francisco",
// country: "United States",
// timestamp: 1635789600
// }
});
Caching Strategy
Cache Key Format
f"geocoding:{round(latitude, 2)}:{round(longitude, 2)}"
Cache Entry Example
{
"nearestCity": "San Francisco",
"country": "United States",
"coordinates": {
"latitude": 37.77,
"longitude": -122.41
},
"timestamp": 1635789600,
"ttl": 31536000 // 365 days (1 year)
}
Implementation Details
1. Request Processing
async def process_geocoding_request(location_ids: List[str]):
"""Process a batch of geocoding requests."""
for location_id in location_ids:
# Get location data
location = await get_location(location_id)
# Check cache first
cache_key = generate_cache_key(
location.latitude,
location.longitude
)
cached = await cache.get(cache_key)
if cached:
await update_location(location_id, cached)
continue
# Add to queue if not cached
await queue.add_task('geocoding', {
'location_id': location_id,
'coordinates': {
'latitude': location.latitude,
'longitude': location.longitude
}
})
2. Worker Processing
async def process_geocoding_task(task: dict):
"""Process a single geocoding task."""
try:
# Get coordinates
coords = task['coordinates']
# Perform geocoding
result = await geocoding_service.geocode(
coords['latitude'],
coords['longitude']
)
# Cache result
await cache_geocoding_result(coords, result)
# Update location
await update_location(task['location_id'], result)
# Notify client
await notify_client(task['location_id'], result)
except Exception as e:
logger.error(f"Geocoding error: {e}")
await handle_geocoding_error(task, str(e))
Error Handling
Common Errors
class GeocodingError(Exception):
"""Base class for geocoding errors."""
pass
class RateLimitError(GeocodingError):
"""Raised when geocoding API rate limit is hit."""
pass
class InvalidCoordinatesError(GeocodingError):
"""Raised when coordinates are invalid."""
pass
Error Recovery
async def handle_geocoding_error(task: dict, error: str):
"""Handle geocoding task errors."""
if isinstance(error, RateLimitError):
# Requeue with backoff
await queue.add_task(
'geocoding',
task,
delay=exponential_backoff(task.get('attempts', 0))
)
else:
# Log and notify of permanent failure
await mark_geocoding_failed(task['location_id'], error)
Best Practices
1. Rate Limiting
- Implement exponential backoff
- Respect API provider limits
- Queue requests appropriately
def calculate_backoff(attempts: int) -> int:
"""Calculate backoff time in seconds."""
return min(300, (2 ** attempts) * 10)
2. Batch Processing
- Group nearby coordinates
- Process in batches when possible
- Optimize cache usage
async def batch_geocoding_requests(locations: List[dict]):
"""Group and process locations efficiently."""
batches = group_by_proximity(locations)
for batch in batches:
await process_batch(batch)
3. Cache Management
- Round coordinates for better cache hits
- Implement cache warming for common areas
- Regular cache cleanup
def round_coordinates(lat: float, lon: float) -> Tuple[float, float]:
"""Round coordinates for caching."""
return (round(lat, 2), round(lon, 2))
Monitoring
Metrics to Track
GEOCODING_METRICS = {
'requests_total': Counter('geocoding_requests_total', 'Total requests'),
'cache_hits': Counter('geocoding_cache_hits', 'Cache hits'),
'errors': Counter('geocoding_errors', 'Errors by type'),
'processing_time': Histogram('geocoding_processing_seconds', 'Processing time')
}
Health Checks
async def check_geocoding_health():
"""Check geocoding system health."""
return {
'queue_size': await queue.size(),
'cache_size': await cache.size(),
'error_rate': await calculate_error_rate(),
'average_processing_time': await get_average_processing_time()
}
Configuration
GEOCODING_CONFIG = {
'cache_ttl': 31536000, # 365 days (1 year)
'batch_size': 100,
'max_retries': 3,
'coordinate_precision': 2,
'rate_limit': {
'requests_per_second': 50,
'burst_size': 100
}
}