mirror of
https://github.com/osm-search/Nominatim.git
synced 2026-03-12 05:44:06 +00:00
115 lines
4.6 KiB
Python
115 lines
4.6 KiB
Python
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
#
|
|
# This file is part of Nominatim. (https://nominatim.org)
|
|
#
|
|
# Copyright (C) 2025 by the Nominatim developer community.
|
|
# For a full list of authors see the git log.
|
|
"""
|
|
Implementation of category search.
|
|
"""
|
|
from typing import List
|
|
|
|
import sqlalchemy as sa
|
|
|
|
from . import base
|
|
from ..db_search_fields import SearchData
|
|
from ... import results as nres
|
|
from ...typing import SaBind, SaRow, SaSelect, SaLambdaSelect
|
|
from ...sql.sqlalchemy_types import Geometry
|
|
from ...connection import SearchConnection
|
|
from ...types import SearchDetails, Bbox
|
|
|
|
|
|
LIMIT_PARAM: SaBind = sa.bindparam('limit')
|
|
VIEWBOX_PARAM: SaBind = sa.bindparam('viewbox', type_=Geometry)
|
|
NEAR_PARAM: SaBind = sa.bindparam('near', type_=Geometry)
|
|
NEAR_RADIUS_PARAM: SaBind = sa.bindparam('near_radius')
|
|
|
|
|
|
class PoiSearch(base.AbstractSearch):
|
|
""" Category search in a geographic area.
|
|
"""
|
|
def __init__(self, sdata: SearchData) -> None:
|
|
super().__init__(sdata.penalty)
|
|
self.qualifiers = sdata.qualifiers
|
|
self.countries = sdata.countries
|
|
|
|
async def lookup(self, conn: SearchConnection,
|
|
details: SearchDetails) -> nres.SearchResults:
|
|
""" Find results for the search in the database.
|
|
"""
|
|
bind_params = {
|
|
'limit': details.max_results,
|
|
'viewbox': details.viewbox,
|
|
'near': details.near,
|
|
'near_radius': details.near_radius,
|
|
'excluded': details.excluded
|
|
}
|
|
|
|
t = conn.t.placex
|
|
|
|
rows: List[SaRow] = []
|
|
|
|
if details.near and details.near_radius is not None and details.near_radius < 0.2:
|
|
# simply search in placex table
|
|
def _base_query() -> SaSelect:
|
|
return base.select_placex(t) \
|
|
.add_columns((-t.c.centroid.ST_Distance(NEAR_PARAM))
|
|
.label('importance'))\
|
|
.where(t.c.linked_place_id == None) \
|
|
.where(t.c.geometry.within_distance(NEAR_PARAM, NEAR_RADIUS_PARAM)) \
|
|
.order_by(t.c.centroid.ST_Distance(NEAR_PARAM)) \
|
|
.limit(LIMIT_PARAM)
|
|
|
|
classtype = self.qualifiers.values
|
|
if len(classtype) == 1:
|
|
cclass, ctype = classtype[0]
|
|
sql: SaLambdaSelect = sa.lambda_stmt(
|
|
lambda: _base_query().where(t.c.class_ == cclass)
|
|
.where(t.c.type == ctype))
|
|
else:
|
|
sql = _base_query().where(sa.or_(*(sa.and_(t.c.class_ == cls, t.c.type == typ)
|
|
for cls, typ in classtype)))
|
|
|
|
if self.countries:
|
|
sql = sql.where(t.c.country_code.in_(self.countries.values))
|
|
|
|
if details.viewbox is not None and details.bounded_viewbox:
|
|
sql = sql.where(t.c.geometry.intersects(VIEWBOX_PARAM))
|
|
|
|
rows.extend(await conn.execute(sql, bind_params))
|
|
else:
|
|
# use the class type tables
|
|
for category in self.qualifiers.values:
|
|
table = await conn.get_class_table(*category)
|
|
if table is not None:
|
|
sql = base.select_placex(t)\
|
|
.add_columns(t.c.importance)\
|
|
.join(table, t.c.place_id == table.c.place_id)\
|
|
.where(t.c.class_ == category[0])\
|
|
.where(t.c.type == category[1])
|
|
|
|
if details.viewbox is not None and details.bounded_viewbox:
|
|
sql = sql.where(table.c.centroid.intersects(VIEWBOX_PARAM))
|
|
|
|
if details.near and details.near_radius is not None:
|
|
sql = sql.order_by(table.c.centroid.ST_Distance(NEAR_PARAM))\
|
|
.where(table.c.centroid.within_distance(NEAR_PARAM,
|
|
NEAR_RADIUS_PARAM))
|
|
|
|
if self.countries:
|
|
sql = sql.where(t.c.country_code.in_(self.countries.values))
|
|
|
|
sql = sql.limit(LIMIT_PARAM)
|
|
rows.extend(await conn.execute(sql, bind_params))
|
|
|
|
results = nres.SearchResults()
|
|
for row in rows:
|
|
result = nres.create_from_placex_row(row, nres.SearchResult)
|
|
assert result
|
|
result.accuracy = self.penalty + self.qualifiers.get_penalty((row.class_, row.type))
|
|
result.bbox = Bbox.from_wkb(row.bbox)
|
|
results.append(result)
|
|
|
|
return results
|