feat: add configurable people maps
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.
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
"""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']
|
||||
Reference in New Issue
Block a user