Custom city supplement: add Ipswich QLD, label cities with region

custom_cities.json is merged into the search index for towns missing
from the geonames 15k+ dataset; labels now include the region code
when available to disambiguate same-named cities.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
type-two 2026-09-03 16:46:09 +10:00
parent 338614ee06
commit c5f848733a
2 changed files with 43 additions and 3 deletions

13
custom_cities.json Normal file
View File

@ -0,0 +1,13 @@
[
{
"name": "Ipswich",
"country": "Australia",
"cc": "AU",
"admin": "QLD",
"lat": -27.61679,
"lon": 152.76083,
"tz": "Australia/Brisbane",
"pop": 238000,
"alt": ["ipswich qld", "ipswich queensland"]
}
]

33
geo.py
View File

@ -1,4 +1,6 @@
"""City search (offline, geonamescache) + historical timezone resolution.""" """City search (offline, geonamescache + custom_cities.json) + historical timezone resolution."""
import json
import os
from datetime import datetime from datetime import datetime
from zoneinfo import ZoneInfo from zoneinfo import ZoneInfo
@ -12,10 +14,33 @@ _countries = {c["iso"]: c["name"] for c in _gc.get_countries().values()}
_CITY_INDEX = None _CITY_INDEX = None
def _custom_cities():
"""Supplemental cities missing from the geonames 15k+ dataset (custom_cities.json)."""
path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "custom_cities.json")
if not os.path.exists(path):
return []
with open(path) as f:
rows = []
for c in json.load(f):
rows.append({
"name": c["name"],
"search": c["name"].lower(),
"alt": [a.lower() for a in c.get("alt", [])],
"country": c["country"],
"cc": c["cc"],
"admin": c.get("admin", ""),
"lat": float(c["lat"]),
"lon": float(c["lon"]),
"pop": int(c.get("pop", 0)),
"tz": c["tz"],
})
return rows
def _index(): def _index():
global _CITY_INDEX global _CITY_INDEX
if _CITY_INDEX is None: if _CITY_INDEX is None:
rows = [] rows = _custom_cities()
for c in _gc.get_cities().values(): for c in _gc.get_cities().values():
rows.append({ rows.append({
"name": c["name"], "name": c["name"],
@ -49,7 +74,9 @@ def search_cities(q, limit=12):
rows = (starts + contains)[:limit] rows = (starts + contains)[:limit]
return [{"name": r["name"], "country": r["country"], "cc": r["cc"], return [{"name": r["name"], "country": r["country"], "cc": r["cc"],
"admin": r["admin"], "lat": r["lat"], "lon": r["lon"], "tz": r["tz"], "admin": r["admin"], "lat": r["lat"], "lon": r["lon"], "tz": r["tz"],
"label": f'{r["name"]}, {r["country"]}'} for r in rows] "label": (f'{r["name"]}, {r["admin"]}, {r["country"]}'
if r["admin"] and not r["admin"].isdigit()
else f'{r["name"]}, {r["country"]}')} for r in rows]
def resolve_offset(lat, lon, year, month, day, hour, minute, tz_hint=None): def resolve_offset(lat, lon, year, month, day, hour, minute, tz_hint=None):