Skip to Content

Geo Services

Service layer for geographic data access, geocoding, and coordinate-based city resolution.

GeoDatabase

PostgreSQL-based geographic database with LRU caching. Use the singleton instance for best performance.

from django_cfg.apps.tools.geo.services import get_geo_db db = get_geo_db() # Singleton with caching

get_country(code)

Get country by ISO2 code. Cached (LRU 300).

country = db.get_country("KR") # CountryDTO(id=116, name="South Korea", iso2="KR", emoji="🇰🇷") country = db.get_country("XX") # None

search_countries(term, limit)

Search countries by name or ISO code.

results = db.search_countries("korea", limit=5) # [CountryDTO(name="South Korea"), CountryDTO(name="North Korea")]

get_city(city_id)

Get city by ID. Cached (LRU 5000).

city = db.get_city(1835848) # CityDTO(id=1835848, name="Seoul", latitude=37.5665, longitude=126.978)

search_cities(term, country_code, limit)

Smart multi-word search with relevance ranking.

# Single word cities = db.search_cities("bali", limit=10) # Multi-word (city + context) cities = db.search_cities("seminyak bali indonesia", limit=10) # Ranks "Seminyak, Bali, Indonesia" higher # With country filter cities = db.search_cities("seoul", country_code="KR", limit=10)

Relevance ranking:

  1. Context match (country/state matches other words)
  2. Exact name match
  3. Name starts with term
  4. Name contains term

get_nearby_cities(lat, lng, radius_km, limit)

Find cities within radius using geodesic distance.

nearby = db.get_nearby_cities( latitude=-8.6908, longitude=115.1688, radius_km=50, limit=10 ) for result in nearby: print(f"{result.city.name}: {result.distance_km:.1f} km") # Denpasar: 2.3 km # Kuta: 5.1 km # Sanur: 8.7 km

get_statistics()

Get database statistics.

stats = db.get_statistics() # {"countries": 250, "states": 5084, "cities": 150853}

resolve_city_from_coordinates()

Most important utility - finds nearest city from coordinates.

from django_cfg.apps.tools.geo.services import resolve_city_from_coordinates # Find nearest city within default radius (50km) city_id = resolve_city_from_coordinates(-8.6908, 115.1688) # 56263 (Denpasar) # Custom radius city_id = resolve_city_from_coordinates(-8.6908, 115.1688, radius_km=100) # No city found (middle of ocean) city_id = resolve_city_from_coordinates(0.0, 0.0) # None

Use in Data Ingestion

from django_cfg.apps.tools.geo.services import resolve_city_from_coordinates def ingest_property(data): """Auto-populate geo field from coordinates.""" geo = None if data.get('latitude') and data.get('longitude'): geo = resolve_city_from_coordinates( float(data['latitude']), float(data['longitude']), radius_km=50, ) return Property.objects.create( latitude=data.get('latitude'), longitude=data.get('longitude'), geo=geo, # ... other fields )

Use with LLM-Estimated Coordinates

# LLM can estimate coordinates from location names # Even approximate coordinates work with 50km radius # LLM extracts: "Villa in Seminyak, Bali" # LLM estimates: lat=-8.691, lng=115.168 city_id = resolve_city_from_coordinates(-8.691, 115.168) # Still finds Denpasar/Seminyak area

GeocodingService

Geocoding and reverse geocoding with caching.

from django_cfg.apps.tools.geo.services import get_geocoding_service service = get_geocoding_service()

geocode(address)

Convert address to coordinates.

result = service.geocode("Seoul, South Korea") # GeocodingResult( # latitude=37.5665, # longitude=126.978, # display_name="Seoul, South Korea", # confidence=1.0, # )

reverse_geocode(lat, lng)

Convert coordinates to address.

result = service.reverse_geocode(37.5665, 126.978) # ReverseGeocodingResult( # display_name="Seoul, South Korea", # city_id=1835848, # city_name="Seoul", # )

autocomplete(query, limit)

Fast autocomplete using Photon API.

results = service.autocomplete("bali indo", limit=5) # [AutocompleteResult(text="Bali, Indonesia", lat=-8.34, lng=115.09)]

Utility Functions

from django_cfg.apps.tools.geo.services import ( format_location, resolve_location, parse_coordinates, validate_coordinates, coordinates_to_string, )

format_location(city, country)

Format location for display.

format_location(city=city_dto, country=country_dto) # "Seoul, South Korea" format_location(city=city_dto, country=country_dto, include_flag=True) # "🇰🇷 Seoul, South Korea"

resolve_location(city_id)

Resolve full location hierarchy from city ID.

location = resolve_location(1835848) # LocationDTO( # city=CityDTO(name="Seoul"), # state=StateDTO(name="Seoul"), # country=CountryDTO(name="South Korea"), # )

parse_coordinates(text)

Parse coordinates from string.

parse_coordinates("37.5665, 126.978") # (37.5665, 126.978) parse_coordinates("(37.5665, 126.978)") # (37.5665, 126.978) parse_coordinates("invalid") # None

validate_coordinates(lat, lng)

Check if coordinates are valid.

validate_coordinates(37.5665, 126.978) # True validate_coordinates(91.0, 0.0) # False (lat > 90)

Caching

GeoDatabase Cache

LRU in-memory cache for fast repeated lookups:

DataCache SizeTTL
Countries300Session
States1000Session
Cities5000Session

Geocoding Cache

Django cache (Redis/Memcached) for API responses:

OperationTTL
geocode30 days
reverse_geocode7 days
autocomplete1 hour

Best Practices

DO: Use Singleton

# GOOD - Uses cached singleton db = get_geo_db() service = get_geocoding_service()

DON’T: Create New Instances

# BAD - No caching benefit db = GeoDatabase()

DO: Batch Operations

# GOOD - Cache warms up db = get_geo_db() for prop in properties: city = db.get_city(prop.geo) # Cached after first call

DON’T: N+1 Queries

# BAD - Direct ORM for prop in properties: city = City.objects.get(id=prop.geo)

See Also

Last updated on