Skip to main content

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
}
}