forked from hans/Nominatim
python lookup: add function for simple lookups
This commit is contained in:
@@ -12,7 +12,7 @@ import datetime as dt
|
||||
|
||||
import sqlalchemy as sa
|
||||
|
||||
from nominatim.typing import SaColumn, SaLabel, SaRow
|
||||
from nominatim.typing import SaColumn, SaRow, SaSelect
|
||||
from nominatim.api.connection import SearchConnection
|
||||
import nominatim.api.types as ntyp
|
||||
import nominatim.api.results as nres
|
||||
@@ -20,23 +20,12 @@ from nominatim.api.logging import log
|
||||
|
||||
RowFunc = Callable[[Optional[SaRow], Type[nres.BaseResultT]], Optional[nres.BaseResultT]]
|
||||
|
||||
def _select_column_geometry(column: SaColumn,
|
||||
geometry_output: ntyp.GeometryFormat) -> SaLabel:
|
||||
""" Create the appropriate column expression for selecting a
|
||||
geometry for the details response.
|
||||
"""
|
||||
if geometry_output & ntyp.GeometryFormat.GEOJSON:
|
||||
return sa.literal_column(f"""
|
||||
ST_AsGeoJSON(CASE WHEN ST_NPoints({column.name}) > 5000
|
||||
THEN ST_SimplifyPreserveTopology({column.name}, 0.0001)
|
||||
ELSE {column.name} END)
|
||||
""").label('geometry_geojson')
|
||||
GeomFunc = Callable[[SaSelect, SaColumn], SaSelect]
|
||||
|
||||
return sa.func.ST_GeometryType(column).label('geometry_type')
|
||||
|
||||
|
||||
async def find_in_placex(conn: SearchConnection, place: ntyp.PlaceRef,
|
||||
details: ntyp.LookupDetails) -> Optional[SaRow]:
|
||||
add_geometries: GeomFunc) -> Optional[SaRow]:
|
||||
""" Search for the given place in the placex table and return the
|
||||
base information.
|
||||
"""
|
||||
@@ -49,8 +38,7 @@ async def find_in_placex(conn: SearchConnection, place: ntyp.PlaceRef,
|
||||
t.c.importance, t.c.wikipedia, t.c.indexed_date,
|
||||
t.c.parent_place_id, t.c.rank_address, t.c.rank_search,
|
||||
t.c.linked_place_id,
|
||||
t.c.centroid,
|
||||
_select_column_geometry(t.c.geometry, details.geometry_output))
|
||||
t.c.centroid)
|
||||
|
||||
if isinstance(place, ntyp.PlaceID):
|
||||
sql = sql.where(t.c.place_id == place.place_id)
|
||||
@@ -65,11 +53,11 @@ async def find_in_placex(conn: SearchConnection, place: ntyp.PlaceRef,
|
||||
else:
|
||||
return None
|
||||
|
||||
return (await conn.execute(sql)).one_or_none()
|
||||
return (await conn.execute(add_geometries(sql, t.c.geometry))).one_or_none()
|
||||
|
||||
|
||||
async def find_in_osmline(conn: SearchConnection, place: ntyp.PlaceRef,
|
||||
details: ntyp.LookupDetails) -> Optional[SaRow]:
|
||||
add_geometries: GeomFunc) -> Optional[SaRow]:
|
||||
""" Search for the given place in the osmline table and return the
|
||||
base information.
|
||||
"""
|
||||
@@ -78,8 +66,7 @@ async def find_in_osmline(conn: SearchConnection, place: ntyp.PlaceRef,
|
||||
sql = sa.select(t.c.place_id, t.c.osm_id, t.c.parent_place_id,
|
||||
t.c.indexed_date, t.c.startnumber, t.c.endnumber,
|
||||
t.c.step, t.c.address, t.c.postcode, t.c.country_code,
|
||||
t.c.linegeo.ST_Centroid().label('centroid'),
|
||||
_select_column_geometry(t.c.linegeo, details.geometry_output))
|
||||
t.c.linegeo.ST_Centroid().label('centroid'))
|
||||
|
||||
if isinstance(place, ntyp.PlaceID):
|
||||
sql = sql.where(t.c.place_id == place.place_id)
|
||||
@@ -94,14 +81,17 @@ async def find_in_osmline(conn: SearchConnection, place: ntyp.PlaceRef,
|
||||
else:
|
||||
return None
|
||||
|
||||
return (await conn.execute(sql)).one_or_none()
|
||||
return (await conn.execute(add_geometries(sql, t.c.linegeo))).one_or_none()
|
||||
|
||||
|
||||
async def find_in_tiger(conn: SearchConnection, place: ntyp.PlaceRef,
|
||||
details: ntyp.LookupDetails) -> Optional[SaRow]:
|
||||
add_geometries: GeomFunc) -> Optional[SaRow]:
|
||||
""" Search for the given place in the table of Tiger addresses and return
|
||||
the base information. Only lookup by place ID is supported.
|
||||
"""
|
||||
if not isinstance(place, ntyp.PlaceID):
|
||||
return None
|
||||
|
||||
log().section("Find in TIGER table")
|
||||
t = conn.t.tiger
|
||||
parent = conn.t.placex
|
||||
@@ -109,61 +99,54 @@ async def find_in_tiger(conn: SearchConnection, place: ntyp.PlaceRef,
|
||||
parent.c.osm_type, parent.c.osm_id,
|
||||
t.c.startnumber, t.c.endnumber, t.c.step,
|
||||
t.c.postcode,
|
||||
t.c.linegeo.ST_Centroid().label('centroid'),
|
||||
_select_column_geometry(t.c.linegeo, details.geometry_output))
|
||||
t.c.linegeo.ST_Centroid().label('centroid'))\
|
||||
.where(t.c.place_id == place.place_id)\
|
||||
.join(parent, t.c.parent_place_id == parent.c.place_id, isouter=True)
|
||||
|
||||
if isinstance(place, ntyp.PlaceID):
|
||||
sql = sql.where(t.c.place_id == place.place_id)\
|
||||
.join(parent, t.c.parent_place_id == parent.c.place_id, isouter=True)
|
||||
else:
|
||||
return None
|
||||
|
||||
return (await conn.execute(sql)).one_or_none()
|
||||
return (await conn.execute(add_geometries(sql, t.c.linegeo))).one_or_none()
|
||||
|
||||
|
||||
async def find_in_postcode(conn: SearchConnection, place: ntyp.PlaceRef,
|
||||
details: ntyp.LookupDetails) -> Optional[SaRow]:
|
||||
add_geometries: GeomFunc) -> Optional[SaRow]:
|
||||
""" Search for the given place in the postcode table and return the
|
||||
base information. Only lookup by place ID is supported.
|
||||
"""
|
||||
if not isinstance(place, ntyp.PlaceID):
|
||||
return None
|
||||
|
||||
log().section("Find in postcode table")
|
||||
t = conn.t.postcode
|
||||
sql = sa.select(t.c.place_id, t.c.parent_place_id,
|
||||
t.c.rank_search, t.c.rank_address,
|
||||
t.c.indexed_date, t.c.postcode, t.c.country_code,
|
||||
t.c.geometry.label('centroid'),
|
||||
_select_column_geometry(t.c.geometry, details.geometry_output))
|
||||
t.c.geometry.label('centroid')) \
|
||||
.where(t.c.place_id == place.place_id)
|
||||
|
||||
if isinstance(place, ntyp.PlaceID):
|
||||
sql = sql.where(t.c.place_id == place.place_id)
|
||||
else:
|
||||
return None
|
||||
|
||||
return (await conn.execute(sql)).one_or_none()
|
||||
return (await conn.execute(add_geometries(sql, t.c.geometry))).one_or_none()
|
||||
|
||||
|
||||
async def find_in_all_tables(conn: SearchConnection, place: ntyp.PlaceRef,
|
||||
details: ntyp.LookupDetails
|
||||
add_geometries: GeomFunc
|
||||
) -> Tuple[Optional[SaRow], RowFunc[nres.BaseResultT]]:
|
||||
""" Search for the given place in all data tables
|
||||
and return the base information.
|
||||
"""
|
||||
row = await find_in_placex(conn, place, details)
|
||||
row = await find_in_placex(conn, place, add_geometries)
|
||||
log().var_dump('Result (placex)', row)
|
||||
if row is not None:
|
||||
return row, nres.create_from_placex_row
|
||||
|
||||
row = await find_in_osmline(conn, place, details)
|
||||
row = await find_in_osmline(conn, place, add_geometries)
|
||||
log().var_dump('Result (osmline)', row)
|
||||
if row is not None:
|
||||
return row, nres.create_from_osmline_row
|
||||
|
||||
row = await find_in_postcode(conn, place, details)
|
||||
row = await find_in_postcode(conn, place, add_geometries)
|
||||
log().var_dump('Result (postcode)', row)
|
||||
if row is not None:
|
||||
return row, nres.create_from_postcode_row
|
||||
|
||||
row = await find_in_tiger(conn, place, details)
|
||||
row = await find_in_tiger(conn, place, add_geometries)
|
||||
log().var_dump('Result (tiger)', row)
|
||||
return row, nres.create_from_tiger_row
|
||||
|
||||
@@ -172,13 +155,24 @@ async def get_detailed_place(conn: SearchConnection, place: ntyp.PlaceRef,
|
||||
details: ntyp.LookupDetails) -> Optional[nres.DetailedResult]:
|
||||
""" Retrieve a place with additional details from the database.
|
||||
"""
|
||||
log().function('get_place_by_id', place=place, details=details)
|
||||
log().function('get_detailed_place', place=place, details=details)
|
||||
|
||||
if details.geometry_output and details.geometry_output != ntyp.GeometryFormat.GEOJSON:
|
||||
raise ValueError("lookup only supports geojosn polygon output.")
|
||||
|
||||
if details.geometry_output & ntyp.GeometryFormat.GEOJSON:
|
||||
def _add_geometry(sql: SaSelect, column: SaColumn) -> SaSelect:
|
||||
return sql.add_columns(sa.literal_column(f"""
|
||||
ST_AsGeoJSON(CASE WHEN ST_NPoints({column.name}) > 5000
|
||||
THEN ST_SimplifyPreserveTopology({column.name}, 0.0001)
|
||||
ELSE {column.name} END)
|
||||
""").label('geometry_geojson'))
|
||||
else:
|
||||
def _add_geometry(sql: SaSelect, column: SaColumn) -> SaSelect:
|
||||
return sql.add_columns(sa.func.ST_GeometryType(column).label('geometry_type'))
|
||||
|
||||
row_func: RowFunc[nres.DetailedResult]
|
||||
row, row_func = await find_in_all_tables(conn, place, details)
|
||||
row, row_func = await find_in_all_tables(conn, place, _add_geometry)
|
||||
|
||||
if row is None:
|
||||
return None
|
||||
@@ -198,3 +192,48 @@ async def get_detailed_place(conn: SearchConnection, place: ntyp.PlaceRef,
|
||||
await nres.add_result_details(conn, result, details)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
async def get_simple_place(conn: SearchConnection, place: ntyp.PlaceRef,
|
||||
details: ntyp.LookupDetails) -> Optional[nres.SearchResult]:
|
||||
""" Retrieve a place as a simple search result from the database.
|
||||
"""
|
||||
log().function('get_simple_place', place=place, details=details)
|
||||
|
||||
def _add_geometry(sql: SaSelect, col: SaColumn) -> SaSelect:
|
||||
if not details.geometry_output:
|
||||
return sql
|
||||
|
||||
out = []
|
||||
|
||||
if details.geometry_simplification > 0.0:
|
||||
col = col.ST_SimplifyPreserveTopology(details.geometry_simplification)
|
||||
|
||||
if details.geometry_output & ntyp.GeometryFormat.GEOJSON:
|
||||
out.append(col.ST_AsGeoJSON().label('geometry_geojson'))
|
||||
if details.geometry_output & ntyp.GeometryFormat.TEXT:
|
||||
out.append(col.ST_AsText().label('geometry_text'))
|
||||
if details.geometry_output & ntyp.GeometryFormat.KML:
|
||||
out.append(col.ST_AsKML().label('geometry_kml'))
|
||||
if details.geometry_output & ntyp.GeometryFormat.SVG:
|
||||
out.append(col.ST_AsSVG().label('geometry_svg'))
|
||||
|
||||
return sql.add_columns(*out)
|
||||
|
||||
|
||||
row_func: RowFunc[nres.SearchResult]
|
||||
row, row_func = await find_in_all_tables(conn, place, _add_geometry)
|
||||
|
||||
if row is None:
|
||||
return None
|
||||
|
||||
result = row_func(row, nres.SearchResult)
|
||||
assert result is not None
|
||||
|
||||
# add missing details
|
||||
assert result is not None
|
||||
result.bbox = getattr(row, 'bbox', None)
|
||||
|
||||
await nres.add_result_details(conn, result, details)
|
||||
|
||||
return result
|
||||
|
||||
@@ -173,6 +173,19 @@ class ReverseResults(List[ReverseResult]):
|
||||
"""
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class SearchResult(BaseResult):
|
||||
""" A search result for forward geocoding.
|
||||
"""
|
||||
bbox: Optional[Bbox] = None
|
||||
|
||||
|
||||
class SearchResults(List[SearchResult]):
|
||||
""" Sequence of forward lookup results ordered by relevance.
|
||||
May be empty when no result was found.
|
||||
"""
|
||||
|
||||
|
||||
def _filter_geometries(row: SaRow) -> Dict[str, str]:
|
||||
return {k[9:]: v for k, v in row._mapping.items() # pylint: disable=W0212
|
||||
if k.startswith('geometry_')}
|
||||
|
||||
Reference in New Issue
Block a user