]> git.openstreetmap.org Git - nominatim.git/blobdiff - nominatim/api/results.py
python lookup: add function for simple lookups
[nominatim.git] / nominatim / api / results.py
index 84d4ced92669ee656d3cc71bc80ce14fc52a4979..98b13380726e98361bc2571c85bd78928c1afc12 100644 (file)
@@ -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
+from typing import Optional, Tuple, Dict, Sequence, TypeVar, Type, List
 import enum
 import dataclasses
 import datetime as dt
@@ -22,6 +22,7 @@ from nominatim.typing import SaSelect, SaRow
 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
 
 # This file defines complex result data classes.
 # pylint: disable=too-many-instance-attributes
@@ -46,16 +47,36 @@ class AddressLine:
     names: Dict[str, str]
     extratags: Optional[Dict[str, str]]
 
-    local_name: Optional[str] = None
-
     admin_level: Optional[int]
     fromarea: bool
     isaddress: bool
     rank_address: int
     distance: float
 
+    local_name: Optional[str] = None
+
+
+class AddressLines(List[AddressLine]):
+    """ Sequence of address lines order in descending order by their rank.
+    """
+
+    def localize(self, locales: Locales) -> List[str]:
+        """ Set the local name of address parts according to the chosen
+            locale. Return the list of local names without duplications.
+
+            Only address parts that are marked as isaddress are localized
+            and returned.
+        """
+        label_parts: List[str] = []
+
+        for line in self:
+            if line.isaddress and line.names:
+                line.local_name = locales.display_name(line.names)
+                if not label_parts or label_parts[-1] != line.local_name:
+                    label_parts.append(line.local_name)
+
+        return label_parts
 
-AddressLines = Sequence[AddressLine]
 
 
 @dataclasses.dataclass
@@ -81,7 +102,6 @@ class BaseResult:
 
     place_id : Optional[int] = None
     osm_object: Optional[Tuple[str, int]] = None
-    admin_level: int = 15
 
     names: Optional[Dict[str, str]] = None
     address: Optional[Dict[str, str]] = None
@@ -135,6 +155,7 @@ class DetailedResult(BaseResult):
     """
     parent_place_id: Optional[int] = None
     linked_place_id: Optional[int] = None
+    admin_level: int = 15
     indexed_date: Optional[dt.datetime] = None
 
 
@@ -146,6 +167,25 @@ class ReverseResult(BaseResult):
     bbox: Optional[Bbox] = None
 
 
+class ReverseResults(List[ReverseResult]):
+    """ Sequence of reverse lookup results ordered by distance.
+        May be empty when no result was found.
+    """
+
+
+@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_')}
@@ -164,7 +204,6 @@ def create_from_placex_row(row: Optional[SaRow],
                       place_id=row.place_id,
                       osm_object=(row.osm_type, row.osm_id),
                       category=(row.class_, row.type),
-                      admin_level=row.admin_level,
                       names=row.name,
                       address=row.address,
                       extratags=row.extratags,
@@ -229,6 +268,7 @@ def create_from_tiger_row(row: Optional[SaRow],
 
     res = class_type(source_table=SourceTable.TIGER,
                      place_id=row.place_id,
+                     osm_object=(row.osm_type, row.osm_id),
                      category=('place', 'houses' if hnr is None else 'house'),
                      postcode=row.postcode,
                      country_code='us',
@@ -289,8 +329,8 @@ def _result_row_to_address_row(row: SaRow) -> AddressLine:
     """ Create a new AddressLine from the results of a datbase query.
     """
     extratags: Dict[str, str] = getattr(row, 'extratags', {})
-    if 'place_type' in row:
-        extratags['place_type'] = row.place_type
+    if hasattr(row, 'place_type') and row.place_type:
+        extratags['place'] = row.place_type
 
     names = row.name
     if getattr(row, 'housenumber', None) is not None:
@@ -336,7 +376,7 @@ async def complete_address_details(conn: SearchConnection, result: BaseResult) -
     sql = sa.select(sfn).order_by(sa.column('rank_address').desc(),
                                   sa.column('isaddress').desc())
 
-    result.address_rows = []
+    result.address_rows = AddressLines()
     for row in await conn.execute(sql):
         result.address_rows.append(_result_row_to_address_row(row))
 
@@ -360,7 +400,7 @@ def _placex_select_address_row(conn: SearchConnection,
 async def complete_linked_places(conn: SearchConnection, result: BaseResult) -> None:
     """ Retrieve information about places that link to the result.
     """
-    result.linked_rows = []
+    result.linked_rows = AddressLines()
     if result.source_table != SourceTable.PLACEX:
         return
 
@@ -395,7 +435,7 @@ async def complete_parented_places(conn: SearchConnection, result: BaseResult) -
     """ Retrieve information about places that the result provides the
         address for.
     """
-    result.parented_rows = []
+    result.parented_rows = AddressLines()
     if result.source_table != SourceTable.PLACEX:
         return