mirror of
https://github.com/osm-search/Nominatim.git
synced 2026-03-11 13:24:07 +00:00
split SearchResult type
Use adapted types for the different result types. This makes it easier to have adapted output formatting and means there are only result fields that are filled.
This commit is contained in:
@@ -28,5 +28,5 @@ from .results import (SourceTable as SourceTable,
|
|||||||
AddressLines as AddressLines,
|
AddressLines as AddressLines,
|
||||||
WordInfo as WordInfo,
|
WordInfo as WordInfo,
|
||||||
WordInfos as WordInfos,
|
WordInfos as WordInfos,
|
||||||
SearchResult as SearchResult)
|
DetailedResult as DetailedResult)
|
||||||
from .localization import (Locales as Locales)
|
from .localization import (Locales as Locales)
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ from nominatim.api.connection import SearchConnection
|
|||||||
from nominatim.api.status import get_status, StatusResult
|
from nominatim.api.status import get_status, StatusResult
|
||||||
from nominatim.api.lookup import get_place_by_id
|
from nominatim.api.lookup import get_place_by_id
|
||||||
from nominatim.api.types import PlaceRef, LookupDetails
|
from nominatim.api.types import PlaceRef, LookupDetails
|
||||||
from nominatim.api.results import SearchResult
|
from nominatim.api.results import DetailedResult
|
||||||
|
|
||||||
|
|
||||||
class NominatimAPIAsync:
|
class NominatimAPIAsync:
|
||||||
@@ -127,7 +127,7 @@ class NominatimAPIAsync:
|
|||||||
|
|
||||||
|
|
||||||
async def lookup(self, place: PlaceRef,
|
async def lookup(self, place: PlaceRef,
|
||||||
details: LookupDetails) -> Optional[SearchResult]:
|
details: LookupDetails) -> Optional[DetailedResult]:
|
||||||
""" Get detailed information about a place in the database.
|
""" Get detailed information about a place in the database.
|
||||||
|
|
||||||
Returns None if there is no entry under the given ID.
|
Returns None if there is no entry under the given ID.
|
||||||
@@ -168,7 +168,7 @@ class NominatimAPI:
|
|||||||
|
|
||||||
|
|
||||||
def lookup(self, place: PlaceRef,
|
def lookup(self, place: PlaceRef,
|
||||||
details: LookupDetails) -> Optional[SearchResult]:
|
details: LookupDetails) -> Optional[DetailedResult]:
|
||||||
""" Get detailed information about a place in the database.
|
""" Get detailed information about a place in the database.
|
||||||
"""
|
"""
|
||||||
return self._loop.run_until_complete(self._async_api.lookup(place, details))
|
return self._loop.run_until_complete(self._async_api.lookup(place, details))
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
Implementation of place lookup by ID.
|
Implementation of place lookup by ID.
|
||||||
"""
|
"""
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
import datetime as dt
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
|
||||||
@@ -137,7 +138,7 @@ async def find_in_postcode(conn: SearchConnection, place: ntyp.PlaceRef,
|
|||||||
|
|
||||||
|
|
||||||
async def get_place_by_id(conn: SearchConnection, place: ntyp.PlaceRef,
|
async def get_place_by_id(conn: SearchConnection, place: ntyp.PlaceRef,
|
||||||
details: ntyp.LookupDetails) -> Optional[nres.SearchResult]:
|
details: ntyp.LookupDetails) -> Optional[nres.DetailedResult]:
|
||||||
""" Retrieve a place with additional details from the database.
|
""" Retrieve a place with additional details from the database.
|
||||||
"""
|
"""
|
||||||
log().function('get_place_by_id', place=place, details=details)
|
log().function('get_place_by_id', place=place, details=details)
|
||||||
@@ -146,32 +147,35 @@ async def get_place_by_id(conn: SearchConnection, place: ntyp.PlaceRef,
|
|||||||
raise ValueError("lookup only supports geojosn polygon output.")
|
raise ValueError("lookup only supports geojosn polygon output.")
|
||||||
|
|
||||||
row = await find_in_placex(conn, place, details)
|
row = await find_in_placex(conn, place, details)
|
||||||
|
log().var_dump('Result (placex)', row)
|
||||||
if row is not None:
|
if row is not None:
|
||||||
result = nres.create_from_placex_row(row)
|
result = nres.create_from_placex_row(row, nres.DetailedResult)
|
||||||
log().var_dump('Result', result)
|
else:
|
||||||
await nres.add_result_details(conn, result, details)
|
row = await find_in_osmline(conn, place, details)
|
||||||
return result
|
log().var_dump('Result (osmline)', row)
|
||||||
|
if row is not None:
|
||||||
|
result = nres.create_from_osmline_row(row, nres.DetailedResult)
|
||||||
|
else:
|
||||||
|
row = await find_in_postcode(conn, place, details)
|
||||||
|
log().var_dump('Result (postcode)', row)
|
||||||
|
if row is not None:
|
||||||
|
result = nres.create_from_postcode_row(row, nres.DetailedResult)
|
||||||
|
else:
|
||||||
|
row = await find_in_tiger(conn, place, details)
|
||||||
|
log().var_dump('Result (tiger)', row)
|
||||||
|
if row is not None:
|
||||||
|
result = nres.create_from_tiger_row(row, nres.DetailedResult)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
row = await find_in_osmline(conn, place, details)
|
# add missing details
|
||||||
if row is not None:
|
assert result is not None
|
||||||
result = nres.create_from_osmline_row(row)
|
result.parent_place_id = row.parent_place_id
|
||||||
log().var_dump('Result', result)
|
result.linked_place_id = getattr(row, 'linked_place_id', None)
|
||||||
await nres.add_result_details(conn, result, details)
|
indexed_date = getattr(row, 'indexed_date', None)
|
||||||
return result
|
if indexed_date is not None:
|
||||||
|
result.indexed_date = indexed_date.replace(tzinfo=dt.timezone.utc)
|
||||||
|
|
||||||
row = await find_in_postcode(conn, place, details)
|
await nres.add_result_details(conn, result, details)
|
||||||
if row is not None:
|
|
||||||
result = nres.create_from_postcode_row(row)
|
|
||||||
log().var_dump('Result', result)
|
|
||||||
await nres.add_result_details(conn, result, details)
|
|
||||||
return result
|
|
||||||
|
|
||||||
row = await find_in_tiger(conn, place, details)
|
return result
|
||||||
if row is not None:
|
|
||||||
result = nres.create_from_tiger_row(row)
|
|
||||||
log().var_dump('Result', result)
|
|
||||||
await nres.add_result_details(conn, result, details)
|
|
||||||
return result
|
|
||||||
|
|
||||||
# Nothing found under this ID.
|
|
||||||
return None
|
|
||||||
|
|||||||
@@ -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
|
internal use only. That's why they are implemented as free-standing functions
|
||||||
instead of member functions.
|
instead of member functions.
|
||||||
"""
|
"""
|
||||||
from typing import Optional, Tuple, Dict, Sequence
|
from typing import Optional, Tuple, Dict, Sequence, TypeVar, Type
|
||||||
import enum
|
import enum
|
||||||
import dataclasses
|
import dataclasses
|
||||||
import datetime as dt
|
import datetime as dt
|
||||||
@@ -69,16 +69,15 @@ WordInfos = Sequence[WordInfo]
|
|||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass
|
@dataclasses.dataclass
|
||||||
class SearchResult:
|
class BaseResult:
|
||||||
""" Data class collecting all available information about a search result.
|
""" Data class collecting information common to all
|
||||||
|
types of search results.
|
||||||
"""
|
"""
|
||||||
source_table: SourceTable
|
source_table: SourceTable
|
||||||
category: Tuple[str, str]
|
category: Tuple[str, str]
|
||||||
centroid: Point
|
centroid: Point
|
||||||
|
|
||||||
place_id : Optional[int] = None
|
place_id : Optional[int] = None
|
||||||
parent_place_id: Optional[int] = None
|
|
||||||
linked_place_id: Optional[int] = None
|
|
||||||
osm_object: Optional[Tuple[str, int]] = None
|
osm_object: Optional[Tuple[str, int]] = None
|
||||||
admin_level: int = 15
|
admin_level: int = 15
|
||||||
|
|
||||||
@@ -96,8 +95,6 @@ class SearchResult:
|
|||||||
|
|
||||||
country_code: Optional[str] = None
|
country_code: Optional[str] = None
|
||||||
|
|
||||||
indexed_date: Optional[dt.datetime] = None
|
|
||||||
|
|
||||||
address_rows: Optional[AddressLines] = None
|
address_rows: Optional[AddressLines] = None
|
||||||
linked_rows: Optional[AddressLines] = None
|
linked_rows: Optional[AddressLines] = None
|
||||||
parented_rows: Optional[AddressLines] = None
|
parented_rows: Optional[AddressLines] = None
|
||||||
@@ -106,10 +103,6 @@ class SearchResult:
|
|||||||
|
|
||||||
geometry: Dict[str, str] = dataclasses.field(default_factory=dict)
|
geometry: Dict[str, str] = dataclasses.field(default_factory=dict)
|
||||||
|
|
||||||
def __post_init__(self) -> None:
|
|
||||||
if self.indexed_date is not None and self.indexed_date.tzinfo is None:
|
|
||||||
self.indexed_date = self.indexed_date.replace(tzinfo=dt.timezone.utc)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def lat(self) -> float:
|
def lat(self) -> float:
|
||||||
""" Get the latitude (or y) of the center point of the place.
|
""" Get the latitude (or y) of the center point of the place.
|
||||||
@@ -131,93 +124,138 @@ class SearchResult:
|
|||||||
"""
|
"""
|
||||||
return self.importance or (0.7500001 - (self.rank_search/40.0))
|
return self.importance or (0.7500001 - (self.rank_search/40.0))
|
||||||
|
|
||||||
|
BaseResultT = TypeVar('BaseResultT', bound=BaseResult)
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
|
class DetailedResult(BaseResult):
|
||||||
|
""" A search result with more internal information from the database
|
||||||
|
added.
|
||||||
|
"""
|
||||||
|
parent_place_id: Optional[int] = None
|
||||||
|
linked_place_id: Optional[int] = None
|
||||||
|
indexed_date: Optional[dt.datetime] = None
|
||||||
|
|
||||||
|
|
||||||
def _filter_geometries(row: SaRow) -> Dict[str, str]:
|
def _filter_geometries(row: SaRow) -> Dict[str, str]:
|
||||||
return {k[9:]: v for k, v in row._mapping.items() # pylint: disable=W0212
|
return {k[9:]: v for k, v in row._mapping.items() # pylint: disable=W0212
|
||||||
if k.startswith('geometry_')}
|
if k.startswith('geometry_')}
|
||||||
|
|
||||||
|
|
||||||
def create_from_placex_row(row: SaRow) -> SearchResult:
|
def create_from_placex_row(row: Optional[SaRow],
|
||||||
""" Construct a new SearchResult and add the data from the result row
|
class_type: Type[BaseResultT]) -> Optional[BaseResultT]:
|
||||||
from the placex table.
|
""" Construct a new result and add the data from the result row
|
||||||
|
from the placex table. 'class_type' defines the type of result
|
||||||
|
to return. Returns None if the row is None.
|
||||||
"""
|
"""
|
||||||
return SearchResult(source_table=SourceTable.PLACEX,
|
if row is None:
|
||||||
place_id=row.place_id,
|
return None
|
||||||
parent_place_id=row.parent_place_id,
|
|
||||||
linked_place_id=row.linked_place_id,
|
return class_type(source_table=SourceTable.PLACEX,
|
||||||
osm_object=(row.osm_type, row.osm_id),
|
place_id=row.place_id,
|
||||||
category=(row.class_, row.type),
|
osm_object=(row.osm_type, row.osm_id),
|
||||||
admin_level=row.admin_level,
|
category=(row.class_, row.type),
|
||||||
names=row.name,
|
admin_level=row.admin_level,
|
||||||
address=row.address,
|
names=row.name,
|
||||||
extratags=row.extratags,
|
address=row.address,
|
||||||
housenumber=row.housenumber,
|
extratags=row.extratags,
|
||||||
postcode=row.postcode,
|
housenumber=row.housenumber,
|
||||||
wikipedia=row.wikipedia,
|
postcode=row.postcode,
|
||||||
rank_address=row.rank_address,
|
wikipedia=row.wikipedia,
|
||||||
rank_search=row.rank_search,
|
rank_address=row.rank_address,
|
||||||
importance=row.importance,
|
rank_search=row.rank_search,
|
||||||
country_code=row.country_code,
|
importance=row.importance,
|
||||||
indexed_date=getattr(row, 'indexed_date'),
|
country_code=row.country_code,
|
||||||
centroid=Point.from_wkb(row.centroid.data),
|
centroid=Point.from_wkb(row.centroid.data),
|
||||||
geometry=_filter_geometries(row))
|
geometry=_filter_geometries(row))
|
||||||
|
|
||||||
|
|
||||||
def create_from_osmline_row(row: SaRow) -> SearchResult:
|
def create_from_osmline_row(row: Optional[SaRow],
|
||||||
""" Construct a new SearchResult and add the data from the result row
|
class_type: Type[BaseResultT]) -> Optional[BaseResultT]:
|
||||||
from the osmline table.
|
""" Construct a new result and add the data from the result row
|
||||||
|
from the address interpolation table osmline. 'class_type' defines
|
||||||
|
the type of result to return. Returns None if the row is None.
|
||||||
|
|
||||||
|
If the row contains a housenumber, then the housenumber is filled out.
|
||||||
|
Otherwise the result contains the interpolation information in extratags.
|
||||||
"""
|
"""
|
||||||
return SearchResult(source_table=SourceTable.OSMLINE,
|
if row is None:
|
||||||
place_id=row.place_id,
|
return None
|
||||||
parent_place_id=row.parent_place_id,
|
|
||||||
osm_object=('W', row.osm_id),
|
hnr = getattr(row, 'housenumber', None)
|
||||||
category=('place', 'houses'),
|
|
||||||
address=row.address,
|
res = class_type(source_table=SourceTable.OSMLINE,
|
||||||
postcode=row.postcode,
|
place_id=row.place_id,
|
||||||
extratags={'startnumber': str(row.startnumber),
|
osm_object=('W', row.osm_id),
|
||||||
'endnumber': str(row.endnumber),
|
category=('place', 'houses' if hnr is None else 'house'),
|
||||||
'step': str(row.step)},
|
address=row.address,
|
||||||
country_code=row.country_code,
|
postcode=row.postcode,
|
||||||
indexed_date=getattr(row, 'indexed_date'),
|
country_code=row.country_code,
|
||||||
centroid=Point.from_wkb(row.centroid.data),
|
centroid=Point.from_wkb(row.centroid.data),
|
||||||
geometry=_filter_geometries(row))
|
geometry=_filter_geometries(row))
|
||||||
|
|
||||||
|
if hnr is None:
|
||||||
|
res.extratags = {'startnumber': str(row.startnumber),
|
||||||
|
'endnumber': str(row.endnumber),
|
||||||
|
'step': str(row.step)}
|
||||||
|
else:
|
||||||
|
res.housenumber = str(hnr)
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
def create_from_tiger_row(row: SaRow) -> SearchResult:
|
def create_from_tiger_row(row: Optional[SaRow],
|
||||||
""" Construct a new SearchResult and add the data from the result row
|
class_type: Type[BaseResultT]) -> Optional[BaseResultT]:
|
||||||
from the Tiger table.
|
""" Construct a new result and add the data from the result row
|
||||||
|
from the Tiger data interpolation table. 'class_type' defines
|
||||||
|
the type of result to return. Returns None if the row is None.
|
||||||
|
|
||||||
|
If the row contains a housenumber, then the housenumber is filled out.
|
||||||
|
Otherwise the result contains the interpolation information in extratags.
|
||||||
"""
|
"""
|
||||||
return SearchResult(source_table=SourceTable.TIGER,
|
if row is None:
|
||||||
place_id=row.place_id,
|
return None
|
||||||
parent_place_id=row.parent_place_id,
|
|
||||||
category=('place', 'houses'),
|
hnr = getattr(row, 'housenumber', None)
|
||||||
postcode=row.postcode,
|
|
||||||
extratags={'startnumber': str(row.startnumber),
|
res = class_type(source_table=SourceTable.TIGER,
|
||||||
'endnumber': str(row.endnumber),
|
place_id=row.place_id,
|
||||||
'step': str(row.step)},
|
category=('place', 'houses' if hnr is None else 'house'),
|
||||||
country_code='us',
|
postcode=row.postcode,
|
||||||
centroid=Point.from_wkb(row.centroid.data),
|
country_code='us',
|
||||||
geometry=_filter_geometries(row))
|
centroid=Point.from_wkb(row.centroid.data),
|
||||||
|
geometry=_filter_geometries(row))
|
||||||
|
|
||||||
|
if hnr is None:
|
||||||
|
res.extratags = {'startnumber': str(row.startnumber),
|
||||||
|
'endnumber': str(row.endnumber),
|
||||||
|
'step': str(row.step)}
|
||||||
|
else:
|
||||||
|
res.housenumber = str(hnr)
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
def create_from_postcode_row(row: SaRow) -> SearchResult:
|
def create_from_postcode_row(row: Optional[SaRow],
|
||||||
""" Construct a new SearchResult and add the data from the result row
|
class_type: Type[BaseResultT]) -> Optional[BaseResultT]:
|
||||||
from the postcode centroid table.
|
""" Construct a new result and add the data from the result row
|
||||||
|
from the postcode table. 'class_type' defines
|
||||||
|
the type of result to return. Returns None if the row is None.
|
||||||
"""
|
"""
|
||||||
return SearchResult(source_table=SourceTable.POSTCODE,
|
if row is None:
|
||||||
place_id=row.place_id,
|
return None
|
||||||
parent_place_id=row.parent_place_id,
|
|
||||||
category=('place', 'postcode'),
|
return class_type(source_table=SourceTable.POSTCODE,
|
||||||
names={'ref': row.postcode},
|
place_id=row.place_id,
|
||||||
rank_search=row.rank_search,
|
category=('place', 'postcode'),
|
||||||
rank_address=row.rank_address,
|
names={'ref': row.postcode},
|
||||||
country_code=row.country_code,
|
rank_search=row.rank_search,
|
||||||
centroid=Point.from_wkb(row.centroid.data),
|
rank_address=row.rank_address,
|
||||||
indexed_date=row.indexed_date,
|
country_code=row.country_code,
|
||||||
geometry=_filter_geometries(row))
|
centroid=Point.from_wkb(row.centroid.data),
|
||||||
|
geometry=_filter_geometries(row))
|
||||||
|
|
||||||
|
|
||||||
async def add_result_details(conn: SearchConnection, result: SearchResult,
|
async def add_result_details(conn: SearchConnection, result: BaseResult,
|
||||||
details: LookupDetails) -> None:
|
details: LookupDetails) -> None:
|
||||||
""" Retrieve more details from the database according to the
|
""" Retrieve more details from the database according to the
|
||||||
parameters specified in 'details'.
|
parameters specified in 'details'.
|
||||||
@@ -262,7 +300,7 @@ def _result_row_to_address_row(row: SaRow) -> AddressLine:
|
|||||||
distance=row.distance)
|
distance=row.distance)
|
||||||
|
|
||||||
|
|
||||||
async def complete_address_details(conn: SearchConnection, result: SearchResult) -> None:
|
async def complete_address_details(conn: SearchConnection, result: BaseResult) -> None:
|
||||||
""" Retrieve information about places that make up the address of the result.
|
""" Retrieve information about places that make up the address of the result.
|
||||||
"""
|
"""
|
||||||
housenumber = -1
|
housenumber = -1
|
||||||
@@ -292,6 +330,7 @@ async def complete_address_details(conn: SearchConnection, result: SearchResult)
|
|||||||
for row in await conn.execute(sql):
|
for row in await conn.execute(sql):
|
||||||
result.address_rows.append(_result_row_to_address_row(row))
|
result.address_rows.append(_result_row_to_address_row(row))
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=consider-using-f-string
|
# pylint: disable=consider-using-f-string
|
||||||
def _placex_select_address_row(conn: SearchConnection,
|
def _placex_select_address_row(conn: SearchConnection,
|
||||||
centroid: Point) -> SaSelect:
|
centroid: Point) -> SaSelect:
|
||||||
@@ -308,7 +347,7 @@ def _placex_select_address_row(conn: SearchConnection,
|
|||||||
""" % centroid).label('distance'))
|
""" % centroid).label('distance'))
|
||||||
|
|
||||||
|
|
||||||
async def complete_linked_places(conn: SearchConnection, result: SearchResult) -> None:
|
async def complete_linked_places(conn: SearchConnection, result: BaseResult) -> None:
|
||||||
""" Retrieve information about places that link to the result.
|
""" Retrieve information about places that link to the result.
|
||||||
"""
|
"""
|
||||||
result.linked_rows = []
|
result.linked_rows = []
|
||||||
@@ -322,7 +361,7 @@ async def complete_linked_places(conn: SearchConnection, result: SearchResult) -
|
|||||||
result.linked_rows.append(_result_row_to_address_row(row))
|
result.linked_rows.append(_result_row_to_address_row(row))
|
||||||
|
|
||||||
|
|
||||||
async def complete_keywords(conn: SearchConnection, result: SearchResult) -> None:
|
async def complete_keywords(conn: SearchConnection, result: BaseResult) -> None:
|
||||||
""" Retrieve information about the search terms used for this place.
|
""" Retrieve information about the search terms used for this place.
|
||||||
"""
|
"""
|
||||||
t = conn.t.search_name
|
t = conn.t.search_name
|
||||||
@@ -342,7 +381,7 @@ async def complete_keywords(conn: SearchConnection, result: SearchResult) -> Non
|
|||||||
result.address_keywords.append(WordInfo(*row))
|
result.address_keywords.append(WordInfo(*row))
|
||||||
|
|
||||||
|
|
||||||
async def complete_parented_places(conn: SearchConnection, result: SearchResult) -> None:
|
async def complete_parented_places(conn: SearchConnection, result: BaseResult) -> None:
|
||||||
""" Retrieve information about places that the result provides the
|
""" Retrieve information about places that the result provides the
|
||||||
address for.
|
address for.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -92,8 +92,8 @@ def _add_parent_rows_grouped(writer: JsonWriter, rows: napi.AddressLines,
|
|||||||
writer.end_object().next()
|
writer.end_object().next()
|
||||||
|
|
||||||
|
|
||||||
@dispatch.format_func(napi.SearchResult, 'details-json')
|
@dispatch.format_func(napi.DetailedResult, 'json')
|
||||||
def _format_search_json(result: napi.SearchResult, options: Mapping[str, Any]) -> str:
|
def _format_search_json(result: napi.DetailedResult, options: Mapping[str, Any]) -> str:
|
||||||
locales = options.get('locales', napi.Locales())
|
locales = options.get('locales', napi.Locales())
|
||||||
geom = result.geometry.get('geojson')
|
geom = result.geometry.get('geojson')
|
||||||
centroid = result.centroid.to_geojson()
|
centroid = result.centroid.to_geojson()
|
||||||
|
|||||||
@@ -210,8 +210,7 @@ async def details_endpoint(api: napi.NominatimAPIAsync, params: ASGIAdaptor) ->
|
|||||||
raise params.error('No place with that OSM ID found.', status=404)
|
raise params.error('No place with that OSM ID found.', status=404)
|
||||||
|
|
||||||
output = formatting.format_result(
|
output = formatting.format_result(
|
||||||
result,
|
result, 'json',
|
||||||
'details-json',
|
|
||||||
{'locales': locales,
|
{'locales': locales,
|
||||||
'group_hierarchy': params.get_bool('group_hierarchy', False),
|
'group_hierarchy': params.get_bool('group_hierarchy', False),
|
||||||
'icon_base_url': params.config().MAPICON_URL})
|
'icon_base_url': params.config().MAPICON_URL})
|
||||||
|
|||||||
@@ -282,7 +282,7 @@ class APIDetails:
|
|||||||
if result:
|
if result:
|
||||||
output = api_output.format_result(
|
output = api_output.format_result(
|
||||||
result,
|
result,
|
||||||
'details-json',
|
'json',
|
||||||
{'locales': locales,
|
{'locales': locales,
|
||||||
'group_hierarchy': args.group_hierarchy})
|
'group_hierarchy': args.group_hierarchy})
|
||||||
# reformat the result, so it is pretty-printed
|
# reformat the result, so it is pretty-printed
|
||||||
|
|||||||
@@ -59,14 +59,14 @@ def test_status_format_json_full():
|
|||||||
assert result == '{"status":0,"message":"OK","data_updated":"2010-02-07T20:20:03+00:00","software_version":"%s","database_version":"5.6"}' % (NOMINATIM_VERSION, )
|
assert result == '{"status":0,"message":"OK","data_updated":"2010-02-07T20:20:03+00:00","software_version":"%s","database_version":"5.6"}' % (NOMINATIM_VERSION, )
|
||||||
|
|
||||||
|
|
||||||
# SearchResult
|
# DetailedResult
|
||||||
|
|
||||||
def test_search_details_minimal():
|
def test_search_details_minimal():
|
||||||
search = napi.SearchResult(napi.SourceTable.PLACEX,
|
search = napi.DetailedResult(napi.SourceTable.PLACEX,
|
||||||
('place', 'thing'),
|
('place', 'thing'),
|
||||||
napi.Point(1.0, 2.0))
|
napi.Point(1.0, 2.0))
|
||||||
|
|
||||||
result = api_impl.format_result(search, 'details-json', {})
|
result = api_impl.format_result(search, 'json', {})
|
||||||
|
|
||||||
assert json.loads(result) == \
|
assert json.loads(result) == \
|
||||||
{'category': 'place',
|
{'category': 'place',
|
||||||
@@ -83,8 +83,8 @@ def test_search_details_minimal():
|
|||||||
|
|
||||||
|
|
||||||
def test_search_details_full():
|
def test_search_details_full():
|
||||||
import_date = dt.datetime(2010, 2, 7, 20, 20, 3, 0)
|
import_date = dt.datetime(2010, 2, 7, 20, 20, 3, 0, tzinfo=dt.timezone.utc)
|
||||||
search = napi.SearchResult(
|
search = napi.DetailedResult(
|
||||||
source_table=napi.SourceTable.PLACEX,
|
source_table=napi.SourceTable.PLACEX,
|
||||||
category=('amenity', 'bank'),
|
category=('amenity', 'bank'),
|
||||||
centroid=napi.Point(56.947, -87.44),
|
centroid=napi.Point(56.947, -87.44),
|
||||||
@@ -106,7 +106,7 @@ def test_search_details_full():
|
|||||||
indexed_date = import_date
|
indexed_date = import_date
|
||||||
)
|
)
|
||||||
|
|
||||||
result = api_impl.format_result(search, 'details-json', {})
|
result = api_impl.format_result(search, 'json', {})
|
||||||
|
|
||||||
assert json.loads(result) == \
|
assert json.loads(result) == \
|
||||||
{'place_id': 37563,
|
{'place_id': 37563,
|
||||||
@@ -140,12 +140,12 @@ def test_search_details_full():
|
|||||||
('ST_Polygon', True),
|
('ST_Polygon', True),
|
||||||
('ST_MultiPolygon', True)])
|
('ST_MultiPolygon', True)])
|
||||||
def test_search_details_no_geometry(gtype, isarea):
|
def test_search_details_no_geometry(gtype, isarea):
|
||||||
search = napi.SearchResult(napi.SourceTable.PLACEX,
|
search = napi.DetailedResult(napi.SourceTable.PLACEX,
|
||||||
('place', 'thing'),
|
('place', 'thing'),
|
||||||
napi.Point(1.0, 2.0),
|
napi.Point(1.0, 2.0),
|
||||||
geometry={'type': gtype})
|
geometry={'type': gtype})
|
||||||
|
|
||||||
result = api_impl.format_result(search, 'details-json', {})
|
result = api_impl.format_result(search, 'json', {})
|
||||||
js = json.loads(result)
|
js = json.loads(result)
|
||||||
|
|
||||||
assert js['geometry'] == {'type': 'Point', 'coordinates': [1.0, 2.0]}
|
assert js['geometry'] == {'type': 'Point', 'coordinates': [1.0, 2.0]}
|
||||||
@@ -153,12 +153,12 @@ def test_search_details_no_geometry(gtype, isarea):
|
|||||||
|
|
||||||
|
|
||||||
def test_search_details_with_geometry():
|
def test_search_details_with_geometry():
|
||||||
search = napi.SearchResult(napi.SourceTable.PLACEX,
|
search = napi.DetailedResult(napi.SourceTable.PLACEX,
|
||||||
('place', 'thing'),
|
('place', 'thing'),
|
||||||
napi.Point(1.0, 2.0),
|
napi.Point(1.0, 2.0),
|
||||||
geometry={'geojson': '{"type":"Point","coordinates":[56.947,-87.44]}'})
|
geometry={'geojson': '{"type":"Point","coordinates":[56.947,-87.44]}'})
|
||||||
|
|
||||||
result = api_impl.format_result(search, 'details-json', {})
|
result = api_impl.format_result(search, 'json', {})
|
||||||
js = json.loads(result)
|
js = json.loads(result)
|
||||||
|
|
||||||
assert js['geometry'] == {'type': 'Point', 'coordinates': [56.947, -87.44]}
|
assert js['geometry'] == {'type': 'Point', 'coordinates': [56.947, -87.44]}
|
||||||
@@ -166,10 +166,10 @@ def test_search_details_with_geometry():
|
|||||||
|
|
||||||
|
|
||||||
def test_search_details_with_address_minimal():
|
def test_search_details_with_address_minimal():
|
||||||
search = napi.SearchResult(napi.SourceTable.PLACEX,
|
search = napi.DetailedResult(napi.SourceTable.PLACEX,
|
||||||
('place', 'thing'),
|
('place', 'thing'),
|
||||||
napi.Point(1.0, 2.0),
|
napi.Point(1.0, 2.0),
|
||||||
address_rows=[
|
address_rows=[
|
||||||
napi.AddressLine(place_id=None,
|
napi.AddressLine(place_id=None,
|
||||||
osm_object=None,
|
osm_object=None,
|
||||||
category=('bnd', 'note'),
|
category=('bnd', 'note'),
|
||||||
@@ -180,9 +180,9 @@ def test_search_details_with_address_minimal():
|
|||||||
isaddress=False,
|
isaddress=False,
|
||||||
rank_address=10,
|
rank_address=10,
|
||||||
distance=0.0)
|
distance=0.0)
|
||||||
])
|
])
|
||||||
|
|
||||||
result = api_impl.format_result(search, 'details-json', {})
|
result = api_impl.format_result(search, 'json', {})
|
||||||
js = json.loads(result)
|
js = json.loads(result)
|
||||||
|
|
||||||
assert js['address'] == [{'localname': '',
|
assert js['address'] == [{'localname': '',
|
||||||
@@ -194,10 +194,10 @@ def test_search_details_with_address_minimal():
|
|||||||
|
|
||||||
|
|
||||||
def test_search_details_with_address_full():
|
def test_search_details_with_address_full():
|
||||||
search = napi.SearchResult(napi.SourceTable.PLACEX,
|
search = napi.DetailedResult(napi.SourceTable.PLACEX,
|
||||||
('place', 'thing'),
|
('place', 'thing'),
|
||||||
napi.Point(1.0, 2.0),
|
napi.Point(1.0, 2.0),
|
||||||
address_rows=[
|
address_rows=[
|
||||||
napi.AddressLine(place_id=3498,
|
napi.AddressLine(place_id=3498,
|
||||||
osm_object=('R', 442),
|
osm_object=('R', 442),
|
||||||
category=('bnd', 'note'),
|
category=('bnd', 'note'),
|
||||||
@@ -209,9 +209,9 @@ def test_search_details_with_address_full():
|
|||||||
isaddress=True,
|
isaddress=True,
|
||||||
rank_address=10,
|
rank_address=10,
|
||||||
distance=0.034)
|
distance=0.034)
|
||||||
])
|
])
|
||||||
|
|
||||||
result = api_impl.format_result(search, 'details-json', {})
|
result = api_impl.format_result(search, 'json', {})
|
||||||
js = json.loads(result)
|
js = json.loads(result)
|
||||||
|
|
||||||
assert js['address'] == [{'localname': 'Trespass',
|
assert js['address'] == [{'localname': 'Trespass',
|
||||||
|
|||||||
84
test/python/api/test_results.py
Normal file
84
test/python/api/test_results.py
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
#
|
||||||
|
# This file is part of Nominatim. (https://nominatim.org)
|
||||||
|
#
|
||||||
|
# Copyright (C) 2023 by the Nominatim developer community.
|
||||||
|
# For a full list of authors see the git log.
|
||||||
|
"""
|
||||||
|
Tests for result datatype helper functions.
|
||||||
|
"""
|
||||||
|
import struct
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import pytest_asyncio
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
from nominatim.api import SourceTable, DetailedResult, Point
|
||||||
|
import nominatim.api.results as nresults
|
||||||
|
|
||||||
|
class FakeCentroid:
|
||||||
|
def __init__(self, x, y):
|
||||||
|
self.data = struct.pack("=biidd", 1, 0x20000001, 4326,
|
||||||
|
x, y)
|
||||||
|
|
||||||
|
class FakeRow:
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
for k, v in kwargs.items():
|
||||||
|
setattr(self, k, v)
|
||||||
|
self._mapping = kwargs
|
||||||
|
|
||||||
|
|
||||||
|
def test_minimal_detailed_result():
|
||||||
|
res = DetailedResult(SourceTable.PLACEX,
|
||||||
|
('amenity', 'post_box'),
|
||||||
|
Point(23.1, 0.5))
|
||||||
|
|
||||||
|
assert res.lon == 23.1
|
||||||
|
assert res.lat == 0.5
|
||||||
|
assert res.calculated_importance() == pytest.approx(0.0000001)
|
||||||
|
|
||||||
|
def test_detailed_result_custom_importance():
|
||||||
|
res = DetailedResult(SourceTable.PLACEX,
|
||||||
|
('amenity', 'post_box'),
|
||||||
|
Point(23.1, 0.5),
|
||||||
|
importance=0.4563)
|
||||||
|
|
||||||
|
assert res.calculated_importance() == 0.4563
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('func', (nresults.create_from_placex_row,
|
||||||
|
nresults.create_from_osmline_row,
|
||||||
|
nresults.create_from_tiger_row,
|
||||||
|
nresults.create_from_postcode_row))
|
||||||
|
def test_create_row_none(func):
|
||||||
|
assert func(None, DetailedResult) is None
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('func', (nresults.create_from_osmline_row,
|
||||||
|
nresults.create_from_tiger_row))
|
||||||
|
def test_create_row_with_housenumber(func):
|
||||||
|
row = FakeRow(place_id = 2345, osm_id = 111, housenumber = 4,
|
||||||
|
address = None, postcode = '99900', country_code = 'xd',
|
||||||
|
centroid = FakeCentroid(0, 0))
|
||||||
|
|
||||||
|
res = func(row, DetailedResult)
|
||||||
|
|
||||||
|
assert res.housenumber == '4'
|
||||||
|
assert res.extratags is None
|
||||||
|
assert res.category == ('place', 'house')
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('func', (nresults.create_from_osmline_row,
|
||||||
|
nresults.create_from_tiger_row))
|
||||||
|
def test_create_row_without_housenumber(func):
|
||||||
|
row = FakeRow(place_id=2345, osm_id=111,
|
||||||
|
startnumber=1, endnumber=11, step=2,
|
||||||
|
address=None, postcode='99900', country_code='xd',
|
||||||
|
centroid=FakeCentroid(0, 0))
|
||||||
|
|
||||||
|
res = func(row, DetailedResult)
|
||||||
|
|
||||||
|
assert res.housenumber is None
|
||||||
|
assert res.extratags == {'startnumber': '1', 'endnumber': '11', 'step': '2'}
|
||||||
|
assert res.category == ('place', 'houses')
|
||||||
@@ -79,8 +79,8 @@ class TestCliDetailsCall:
|
|||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def setup_status_mock(self, monkeypatch):
|
def setup_status_mock(self, monkeypatch):
|
||||||
result = napi.SearchResult(napi.SourceTable.PLACEX, ('place', 'thing'),
|
result = napi.DetailedResult(napi.SourceTable.PLACEX, ('place', 'thing'),
|
||||||
napi.Point(1.0, -3.0))
|
napi.Point(1.0, -3.0))
|
||||||
|
|
||||||
monkeypatch.setattr(napi.NominatimAPI, 'lookup',
|
monkeypatch.setattr(napi.NominatimAPI, 'lookup',
|
||||||
lambda *args: result)
|
lambda *args: result)
|
||||||
|
|||||||
Reference in New Issue
Block a user