Return nearest parish/polygon for points in the sea near the coast
When a point falls in the sea within a country boundary (e.g. Scotland Q22 or England Q21) but not within any specific parish, find the nearest civil parish or high-admin-level polygon within ~0.15 degrees (~10-17km). - Add get_nearest_scotland_code() for Scottish civil parishes - Add Polygon.nearest() for general OSM polygon lookup - Use .limit(1).scalar() in scotland.py for cleaner returns
This commit is contained in:
parent
7235df4cad
commit
90b009fc90
3 changed files with 74 additions and 3 deletions
|
|
@ -65,6 +65,32 @@ class Polygon(Base):
|
||||||
return q # type: ignore
|
return q # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def nearest(
|
||||||
|
cls,
|
||||||
|
lat: float,
|
||||||
|
lon: float,
|
||||||
|
min_admin_level: int = 7,
|
||||||
|
max_degrees: float = 0.15,
|
||||||
|
) -> "Polygon | None":
|
||||||
|
"""Find nearest polygon with high admin_level within distance.
|
||||||
|
|
||||||
|
Uses degree-based filter (~0.15° ≈ 10-17km at UK latitudes) to
|
||||||
|
leverage the spatial index on the geometry column.
|
||||||
|
"""
|
||||||
|
point = func.ST_SetSRID(func.ST_MakePoint(lon, lat), 4326)
|
||||||
|
return (
|
||||||
|
cls.query.filter(
|
||||||
|
cls.admin_level.isnot(None),
|
||||||
|
cls.admin_level.regexp_match(r"^\d+$"),
|
||||||
|
cast(cls.admin_level, Integer) >= min_admin_level,
|
||||||
|
func.ST_DWithin(cls.way, point, max_degrees),
|
||||||
|
)
|
||||||
|
.order_by(func.ST_Distance(cls.way, point))
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Scotland(Base):
|
class Scotland(Base):
|
||||||
"""Civil parishes in Scotland."""
|
"""Civil parishes in Scotland."""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,24 @@ from geocode.model import Scotland
|
||||||
def get_scotland_code(lat: float, lon: float) -> str | None:
|
def get_scotland_code(lat: float, lon: float) -> str | None:
|
||||||
"""Find civil parish in Scotland for given lat/lon."""
|
"""Find civil parish in Scotland for given lat/lon."""
|
||||||
point = func.ST_Transform(func.ST_SetSRID(func.ST_MakePoint(lon, lat), 4326), 27700)
|
point = func.ST_Transform(func.ST_SetSRID(func.ST_MakePoint(lon, lat), 4326), 27700)
|
||||||
result = (
|
return (
|
||||||
session.query(Scotland.code)
|
session.query(Scotland.code)
|
||||||
.filter(func.ST_Contains(Scotland.geom, point))
|
.filter(func.ST_Contains(Scotland.geom, point))
|
||||||
.first()
|
.limit(1)
|
||||||
|
.scalar()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_nearest_scotland_code(
|
||||||
|
lat: float, lon: float, max_degrees: float = 0.15
|
||||||
|
) -> str | None:
|
||||||
|
"""Find nearest civil parish in Scotland within distance."""
|
||||||
|
point = func.ST_SetSRID(func.ST_MakePoint(lon, lat), 4326)
|
||||||
|
geom_4326 = func.ST_Transform(Scotland.geom, 4326)
|
||||||
|
return (
|
||||||
|
session.query(Scotland.code)
|
||||||
|
.filter(func.ST_DWithin(geom_4326, point, max_degrees))
|
||||||
|
.order_by(func.ST_Distance(geom_4326, point))
|
||||||
|
.limit(1)
|
||||||
|
.scalar()
|
||||||
)
|
)
|
||||||
return result[0] if result else None
|
|
||||||
|
|
|
||||||
30
lookup.py
30
lookup.py
|
|
@ -136,6 +136,36 @@ def lat_lon_to_wikidata(lat: float, lon: float) -> dict[str, typing.Any]:
|
||||||
if admin_level >= 7:
|
if admin_level >= 7:
|
||||||
return {"elements": elements, "result": result}
|
return {"elements": elements, "result": result}
|
||||||
|
|
||||||
|
# Point is in Scotland but not in a specific parish — try nearest parish
|
||||||
|
if result.get("wikidata") == "Q22":
|
||||||
|
nearby_code = scotland.get_nearest_scotland_code(lat, lon)
|
||||||
|
if nearby_code:
|
||||||
|
rows = wikidata.lookup_scottish_parish_in_wikidata(nearby_code)
|
||||||
|
add_missing_commons_cat(rows)
|
||||||
|
hit = wikidata.commons_from_rows(rows)
|
||||||
|
if hit:
|
||||||
|
nearby_result = wikidata.build_dict(hit, lat, lon)
|
||||||
|
if not nearby_result.get("missing"):
|
||||||
|
return {"elements": elements, "result": nearby_result}
|
||||||
|
|
||||||
|
# Point is in a broad area (e.g. country) — try nearest specific polygon
|
||||||
|
nearby = model.Polygon.nearest(lat, lon)
|
||||||
|
if nearby and nearby.tags:
|
||||||
|
tags: typing.Mapping[str, str] = nearby.tags
|
||||||
|
al = get_admin_level(tags)
|
||||||
|
hit = (
|
||||||
|
hit_from_wikidata_tag(tags)
|
||||||
|
or hit_from_ref_gss_tag(tags)
|
||||||
|
or hit_from_name(tags, lat, lon)
|
||||||
|
)
|
||||||
|
if hit:
|
||||||
|
hit["admin_level"] = al
|
||||||
|
hit["element"] = nearby.osm_id
|
||||||
|
hit["geojson"] = typing.cast(str, nearby.geojson_str)
|
||||||
|
nearby_result = wikidata.build_dict(hit, lat, lon)
|
||||||
|
if not nearby_result.get("missing"):
|
||||||
|
return {"elements": elements, "result": nearby_result}
|
||||||
|
|
||||||
row = wikidata.geosearch(lat, lon)
|
row = wikidata.geosearch(lat, lon)
|
||||||
if row:
|
if row:
|
||||||
hit = wikidata.commons_from_rows([row])
|
hit = wikidata.commons_from_rows([row])
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue