from nominatim.api.logging import log
from nominatim.api.types import AnyPoint, DataLayer, ReverseDetails, GeometryFormat, Bbox
from nominatim.db.sqlalchemy_types import Geometry
-import nominatim.db.sqlalchemy_functions as snfn
# In SQLAlchemy expression which compare with NULL need to be expressed with
# the equal sign.
t.c.importance, t.c.wikipedia,
t.c.parent_place_id, t.c.rank_address, t.c.rank_search,
centroid,
+ t.c.linked_place_id, t.c.admin_level,
distance.label('distance'),
t.c.geometry.ST_Expand(0).label('bbox'))
def _is_address_point(table: SaFromClause) -> SaColumn:
return sa.and_(table.c.rank_address == 30,
sa.or_(table.c.housenumber != None,
- table.c.name.has_key('addr:housename')))
+ sa.func.JsonHasKey(table.c.name, 'addr:housename')))
def _get_closest(*rows: Optional[SaRow]) -> Optional[SaRow]:
coordinate.
"""
- def __init__(self, conn: SearchConnection, params: ReverseDetails) -> None:
+ def __init__(self, conn: SearchConnection, params: ReverseDetails,
+ restrict_to_country_areas: bool = False) -> None:
self.conn = conn
self.params = params
+ self.restrict_to_country_areas = restrict_to_country_areas
self.bind_params: Dict[str, Any] = {'max_rank': params.max_rank}
col = sa.func.ST_SimplifyPreserveTopology(col, self.params.geometry_simplification)
if self.params.geometry_output & GeometryFormat.GEOJSON:
- out.append(sa.func.ST_AsGeoJSON(col).label('geometry_geojson'))
+ out.append(sa.func.ST_AsGeoJSON(col, 7).label('geometry_geojson'))
if self.params.geometry_output & GeometryFormat.TEXT:
out.append(sa.func.ST_AsText(col).label('geometry_text'))
if self.params.geometry_output & GeometryFormat.KML:
- out.append(sa.func.ST_AsKML(col).label('geometry_kml'))
+ out.append(sa.func.ST_AsKML(col, 7).label('geometry_kml'))
if self.params.geometry_output & GeometryFormat.SVG:
- out.append(sa.func.ST_AsSVG(col).label('geometry_svg'))
+ out.append(sa.func.ST_AsSVG(col, 0, 7).label('geometry_svg'))
return sql.add_columns(*out)
inner = sa.select(t, sa.literal(0.0).label('distance'))\
.where(t.c.rank_search.between(5, MAX_RANK_PARAM))\
.where(t.c.geometry.intersects(WKT_PARAM))\
- .where(snfn.select_index_placex_geometry_reverse_lookuppolygon('placex'))\
+ .where(sa.func.PlacexGeometryReverseLookuppolygon())\
.order_by(sa.desc(t.c.rank_search))\
.limit(50)\
.subquery('area')
.where(t.c.rank_search > address_rank)\
.where(t.c.rank_search <= MAX_RANK_PARAM)\
.where(t.c.indexed_status == 0)\
- .where(snfn.select_index_placex_geometry_reverse_lookupplacenode('placex'))\
- .where(t.c.geometry
- .ST_Buffer(sa.func.reverse_place_diameter(t.c.rank_search))
- .intersects(WKT_PARAM))\
+ .where(sa.func.IntersectsReverseDistance(t, WKT_PARAM))\
.order_by(sa.desc(t.c.rank_search))\
.limit(50)\
.subquery('places')
return _select_from_placex(inner, False)\
.join(touter, touter.c.geometry.ST_Contains(inner.c.geometry))\
.where(touter.c.place_id == address_id)\
- .where(inner.c.distance < sa.func.reverse_place_diameter(inner.c.rank_search))\
+ .where(sa.func.IsBelowReverseDistance(inner.c.distance, inner.c.rank_search))\
.order_by(sa.desc(inner.c.rank_search), inner.c.distance)\
.limit(1)
.where(t.c.indexed_status == 0)\
.where(t.c.linked_place_id == None)\
.where(self._filter_by_layer(t))\
- .where(t.c.geometry
- .ST_Buffer(sa.func.reverse_place_diameter(t.c.rank_search))
- .intersects(WKT_PARAM))\
+ .where(t.c.geometry.intersects(sa.func.ST_Expand(WKT_PARAM, 0.001)))\
.order_by(sa.desc(t.c.rank_search))\
+ .order_by('distance')\
.limit(50)\
.subquery()
return _get_closest(address_row, other_row)
- async def lookup_country(self) -> Optional[SaRow]:
+ async def lookup_country_codes(self) -> List[str]:
""" Lookup the country for the current search.
"""
log().section('Reverse lookup by country code')
t = self.conn.t.country_grid
- sql: SaLambdaSelect = sa.select(t.c.country_code).distinct()\
+ sql = sa.select(t.c.country_code).distinct()\
.where(t.c.geometry.ST_Contains(WKT_PARAM))
- ccodes = tuple((r[0] for r in await self.conn.execute(sql, self.bind_params)))
+ ccodes = [cast(str, r[0]) for r in await self.conn.execute(sql, self.bind_params)]
log().var_dump('Country codes', ccodes)
+ return ccodes
+
+
+ async def lookup_country(self, ccodes: List[str]) -> Optional[SaRow]:
+ """ Lookup the country for the current search.
+ """
+ if not ccodes:
+ ccodes = await self.lookup_country_codes()
if not ccodes:
return None
.where(t.c.rank_search <= MAX_RANK_PARAM)\
.where(t.c.indexed_status == 0)\
.where(t.c.country_code.in_(ccodes))\
- .where(snfn.select_index_placex_geometry_reverse_lookupplacenode('placex'))\
- .where(t.c.geometry
- .ST_Buffer(sa.func.reverse_place_diameter(t.c.rank_search))
- .intersects(WKT_PARAM))\
+ .where(sa.func.IntersectsReverseDistance(t, WKT_PARAM))\
.order_by(sa.desc(t.c.rank_search))\
.limit(50)\
.subquery('area')
return _select_from_placex(inner, False)\
- .where(inner.c.distance < sa.func.reverse_place_diameter(inner.c.rank_search))\
+ .where(sa.func.IsBelowReverseDistance(inner.c.distance, inner.c.rank_search))\
.order_by(sa.desc(inner.c.rank_search), inner.c.distance)\
.limit(1)
- sql = sa.lambda_stmt(_base_query)
+ sql: SaLambdaSelect = sa.lambda_stmt(_base_query)
if self.has_geometries():
sql = self._add_geometry_columns(sql, sa.literal_column('area.geometry'))
row, tmp_row_func = await self.lookup_street_poi()
if row is not None:
row_func = tmp_row_func
- if row is None and self.max_rank > 4:
- row = await self.lookup_area()
- if row is None and self.layer_enabled(DataLayer.ADDRESS):
- row = await self.lookup_country()
+
+ if row is None:
+ if self.restrict_to_country_areas:
+ ccodes = await self.lookup_country_codes()
+ if not ccodes:
+ return None
+ else:
+ ccodes = []
+
+ if self.max_rank > 4:
+ row = await self.lookup_area()
+ if row is None and self.layer_enabled(DataLayer.ADDRESS):
+ row = await self.lookup_country(ccodes)
result = row_func(row, nres.ReverseResult)
if result is not None: