d4ec85add4
Add independent map profiles, offline city lookup, bundled world artwork and collision-free portrait placement with optional person details. Deploy with updated requirements, migrations 0029-0031 and collectstatic.
36 lines
1.5 KiB
Python
36 lines
1.5 KiB
Python
"""Offline city resolution. No requests or personal data leave the server."""
|
|
from functools import lru_cache
|
|
import unicodedata
|
|
|
|
from django.core.exceptions import ValidationError
|
|
|
|
|
|
def normalize(value):
|
|
return ''.join(c for c in unicodedata.normalize('NFKD', value.casefold().strip()) if not unicodedata.combining(c))
|
|
|
|
|
|
@lru_cache(maxsize=1)
|
|
def gazetteer():
|
|
import geonamescache
|
|
return geonamescache.GeonamesCache()
|
|
|
|
|
|
def resolve_city(country, city):
|
|
data = gazetteer()
|
|
country_key = normalize(country)
|
|
codes = [c['iso'] for c in data.get_countries().values() if country_key in (normalize(c['name']), c['iso'].lower(), c['iso3'].lower())]
|
|
if not codes:
|
|
raise ValidationError({'country': 'Enter a country name or ISO code, e.g. Canada or CA.'})
|
|
city_key = normalize(city)
|
|
country_cities = [c for c in data.get_cities().values() if c['countrycode'] in codes]
|
|
# Prefer the city's canonical name over another town's historical aliases.
|
|
# E.g. Tehran is also listed as an alias of Tiran in GeoNames.
|
|
matches = [c for c in country_cities if normalize(c['name']) == city_key]
|
|
if not matches:
|
|
matches = [c for c in country_cities if city_key in {
|
|
normalize(alias) for alias in c.get('alternatenames', [])
|
|
}]
|
|
if len(matches) != 1:
|
|
raise ValidationError({'city': 'City not found or ambiguous. Use coordinates for small towns or cities with the same name.'})
|
|
return matches[0]['latitude'], matches[0]['longitude']
|