mirror of
https://github.com/osm-search/Nominatim.git
synced 2026-03-07 02:24:08 +00:00
add API functions for search functions
Search is now split into three functions: for free-text search, for structured search and for search by category. Note that the free-text search does not have as many hidden features like coordinate search. Use the search parameters for that.
This commit is contained in:
@@ -11,7 +11,7 @@ Data classes are part of the public API while the functions are for
|
||||
internal use only. That's why they are implemented as free-standing functions
|
||||
instead of member functions.
|
||||
"""
|
||||
from typing import Optional, Tuple, Dict, Sequence, TypeVar, Type, List
|
||||
from typing import Optional, Tuple, Dict, Sequence, TypeVar, Type, List, Any
|
||||
import enum
|
||||
import dataclasses
|
||||
import datetime as dt
|
||||
@@ -23,7 +23,6 @@ from nominatim.api.types import Point, Bbox, LookupDetails
|
||||
from nominatim.api.connection import SearchConnection
|
||||
from nominatim.api.logging import log
|
||||
from nominatim.api.localization import Locales
|
||||
from nominatim.api.search.query_analyzer_factory import make_query_analyzer
|
||||
|
||||
# This file defines complex result data classes.
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
@@ -147,6 +146,7 @@ class BaseResult:
|
||||
"""
|
||||
return self.importance or (0.7500001 - (self.rank_search/40.0))
|
||||
|
||||
|
||||
BaseResultT = TypeVar('BaseResultT', bound=BaseResult)
|
||||
|
||||
@dataclasses.dataclass
|
||||
@@ -332,24 +332,28 @@ def create_from_country_row(row: Optional[SaRow],
|
||||
country_code=row.country_code)
|
||||
|
||||
|
||||
async def add_result_details(conn: SearchConnection, result: BaseResult,
|
||||
async def add_result_details(conn: SearchConnection, results: List[BaseResultT],
|
||||
details: LookupDetails) -> None:
|
||||
""" Retrieve more details from the database according to the
|
||||
parameters specified in 'details'.
|
||||
"""
|
||||
log().section('Query details for result')
|
||||
if details.address_details:
|
||||
log().comment('Query address details')
|
||||
await complete_address_details(conn, result)
|
||||
if details.linked_places:
|
||||
log().comment('Query linked places')
|
||||
await complete_linked_places(conn, result)
|
||||
if details.parented_places:
|
||||
log().comment('Query parent places')
|
||||
await complete_parented_places(conn, result)
|
||||
if details.keywords:
|
||||
log().comment('Query keywords')
|
||||
await complete_keywords(conn, result)
|
||||
if results:
|
||||
log().section('Query details for result')
|
||||
if details.address_details:
|
||||
log().comment('Query address details')
|
||||
await complete_address_details(conn, results)
|
||||
if details.linked_places:
|
||||
log().comment('Query linked places')
|
||||
for result in results:
|
||||
await complete_linked_places(conn, result)
|
||||
if details.parented_places:
|
||||
log().comment('Query parent places')
|
||||
for result in results:
|
||||
await complete_parented_places(conn, result)
|
||||
if details.keywords:
|
||||
log().comment('Query keywords')
|
||||
for result in results:
|
||||
await complete_keywords(conn, result)
|
||||
|
||||
|
||||
def _result_row_to_address_row(row: SaRow) -> AddressLine:
|
||||
@@ -377,35 +381,60 @@ def _result_row_to_address_row(row: SaRow) -> AddressLine:
|
||||
distance=row.distance)
|
||||
|
||||
|
||||
async def complete_address_details(conn: SearchConnection, result: BaseResult) -> None:
|
||||
async def complete_address_details(conn: SearchConnection, results: List[BaseResultT]) -> None:
|
||||
""" Retrieve information about places that make up the address of the result.
|
||||
"""
|
||||
housenumber = -1
|
||||
if result.source_table in (SourceTable.TIGER, SourceTable.OSMLINE):
|
||||
if result.housenumber is not None:
|
||||
housenumber = int(result.housenumber)
|
||||
elif result.extratags is not None and 'startnumber' in result.extratags:
|
||||
# details requests do not come with a specific house number
|
||||
housenumber = int(result.extratags['startnumber'])
|
||||
def get_hnr(result: BaseResult) -> Tuple[int, int]:
|
||||
housenumber = -1
|
||||
if result.source_table in (SourceTable.TIGER, SourceTable.OSMLINE):
|
||||
if result.housenumber is not None:
|
||||
housenumber = int(result.housenumber)
|
||||
elif result.extratags is not None and 'startnumber' in result.extratags:
|
||||
# details requests do not come with a specific house number
|
||||
housenumber = int(result.extratags['startnumber'])
|
||||
assert result.place_id
|
||||
return result.place_id, housenumber
|
||||
|
||||
sfn = sa.func.get_addressdata(result.place_id, housenumber)\
|
||||
.table_valued( # type: ignore[no-untyped-call]
|
||||
sa.column('place_id', type_=sa.Integer),
|
||||
'osm_type',
|
||||
sa.column('osm_id', type_=sa.BigInteger),
|
||||
sa.column('name', type_=conn.t.types.Composite),
|
||||
'class', 'type', 'place_type',
|
||||
sa.column('admin_level', type_=sa.Integer),
|
||||
sa.column('fromarea', type_=sa.Boolean),
|
||||
sa.column('isaddress', type_=sa.Boolean),
|
||||
sa.column('rank_address', type_=sa.SmallInteger),
|
||||
sa.column('distance', type_=sa.Float))
|
||||
sql = sa.select(sfn).order_by(sa.column('rank_address').desc(),
|
||||
sa.column('isaddress').desc())
|
||||
data: List[Tuple[Any, ...]] = [get_hnr(r) for r in results if r.place_id]
|
||||
|
||||
result.address_rows = AddressLines()
|
||||
if not data:
|
||||
return
|
||||
|
||||
values = sa.values(sa.column('place_id', type_=sa.Integer),
|
||||
sa.column('housenumber', type_=sa.Integer),
|
||||
name='places',
|
||||
literal_binds=True).data(data)
|
||||
|
||||
sfn = sa.func.get_addressdata(values.c.place_id, values.c.housenumber)\
|
||||
.table_valued( # type: ignore[no-untyped-call]
|
||||
sa.column('place_id', type_=sa.Integer),
|
||||
'osm_type',
|
||||
sa.column('osm_id', type_=sa.BigInteger),
|
||||
sa.column('name', type_=conn.t.types.Composite),
|
||||
'class', 'type', 'place_type',
|
||||
sa.column('admin_level', type_=sa.Integer),
|
||||
sa.column('fromarea', type_=sa.Boolean),
|
||||
sa.column('isaddress', type_=sa.Boolean),
|
||||
sa.column('rank_address', type_=sa.SmallInteger),
|
||||
sa.column('distance', type_=sa.Float),
|
||||
joins_implicitly=True)
|
||||
|
||||
sql = sa.select(values.c.place_id.label('result_place_id'), sfn)\
|
||||
.order_by(values.c.place_id,
|
||||
sa.column('rank_address').desc(),
|
||||
sa.column('isaddress').desc())
|
||||
|
||||
current_result = None
|
||||
for row in await conn.execute(sql):
|
||||
result.address_rows.append(_result_row_to_address_row(row))
|
||||
if current_result is None or row.result_place_id != current_result.place_id:
|
||||
for result in results:
|
||||
if result.place_id == row.result_place_id:
|
||||
current_result = result
|
||||
break
|
||||
else:
|
||||
assert False
|
||||
current_result.address_rows = AddressLines()
|
||||
current_result.address_rows.append(_result_row_to_address_row(row))
|
||||
|
||||
|
||||
# pylint: disable=consider-using-f-string
|
||||
@@ -440,6 +469,9 @@ async def complete_linked_places(conn: SearchConnection, result: BaseResult) ->
|
||||
|
||||
async def complete_keywords(conn: SearchConnection, result: BaseResult) -> None:
|
||||
""" Retrieve information about the search terms used for this place.
|
||||
|
||||
Requires that the query analyzer was initialised to get access to
|
||||
the word table.
|
||||
"""
|
||||
t = conn.t.search_name
|
||||
sql = sa.select(t.c.name_vector, t.c.nameaddress_vector)\
|
||||
@@ -448,7 +480,6 @@ async def complete_keywords(conn: SearchConnection, result: BaseResult) -> None:
|
||||
result.name_keywords = []
|
||||
result.address_keywords = []
|
||||
|
||||
await make_query_analyzer(conn)
|
||||
t = conn.t.meta.tables['word']
|
||||
sel = sa.select(t.c.word_id, t.c.word_token, t.c.word)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user