]> git.openstreetmap.org Git - nominatim.git/blobdiff - nominatim/api/types.py
implement token assignment
[nominatim.git] / nominatim / api / types.py
index 344fd91bffb376d2a781daa1a03701719540f6af..0e4340fee02970a20e486f6d62c3d76e11609461 100644 (file)
@@ -7,11 +7,13 @@
 """
 Complex datatypes used by the Nominatim API.
 """
-from typing import Optional, Union, Tuple, NamedTuple
+from typing import Optional, Union, Tuple, NamedTuple, TypeVar, Type, Dict, Any
 import dataclasses
 import enum
 from struct import unpack
 
+from nominatim.errors import UsageError
+
 @dataclasses.dataclass
 class PlaceID:
     """ Reference an object by Nominatim's internal ID.
@@ -85,6 +87,8 @@ class Point(NamedTuple):
 
 AnyPoint = Union[Point, Tuple[float, float]]
 
+WKB_BBOX_HEADER_LE = b'\x01\x03\x00\x00\x20\xE6\x10\x00\x00\x01\x00\x00\x00\x05\x00\x00\x00'
+WKB_BBOX_HEADER_BE = b'\x00\x20\x00\x00\x03\x00\x00\x10\xe6\x00\x00\x00\x01\x00\x00\x00\x05'
 
 class Bbox:
     """ A bounding box in WSG84 projection.
@@ -134,9 +138,9 @@ class Bbox:
 
         if len(wkb) != 97:
             raise ValueError("WKB must be a bounding box polygon")
-        if wkb.startswith(b'\x01\x03\x00\x00\x20\xE6\x10\x00\x00\x01\x00\x00\x00\x05\x00\x00\x00'):
+        if wkb.startswith(WKB_BBOX_HEADER_LE):
             x1, y1, _, _, x2, y2 = unpack('<dddddd', wkb[17:65])
-        elif wkb.startswith(b'\x00\x20\x00\x00\x03\x00\x00\x10\xe6\x00\x00\x00\x01\x00\x00\x00\x05'):
+        elif wkb.startswith(WKB_BBOX_HEADER_BE):
             x1, y1, _, _, x2, y2 = unpack('>dddddd', wkb[17:65])
         else:
             raise ValueError("WKB has wrong header")
@@ -144,6 +148,7 @@ class Bbox:
         return Bbox(min(x1, x2), min(y1, y2), max(x1, x2), max(y1, y2))
 
 
+    @staticmethod
     def from_point(pt: Point, buffer: float) -> 'Bbox':
         """ Return a Bbox around the point with the buffer added to all sides.
         """
@@ -161,10 +166,22 @@ class GeometryFormat(enum.Flag):
     TEXT = enum.auto()
 
 
+class DataLayer(enum.Flag):
+    """ Layer types that can be selected for reverse and forward search.
+    """
+    POI = enum.auto()
+    ADDRESS = enum.auto()
+    RAILWAY = enum.auto()
+    MANMADE = enum.auto()
+    NATURAL = enum.auto()
+
+
+TParam = TypeVar('TParam', bound='LookupDetails') # pylint: disable=invalid-name
+
 @dataclasses.dataclass
 class LookupDetails:
     """ Collection of parameters that define the amount of details
-        returned with a search result.
+        returned with a lookup or details result.
     """
     geometry_output: GeometryFormat = GeometryFormat.NONE
     """ Add the full geometry of the place to the result. Multiple
@@ -191,12 +208,39 @@ class LookupDetails:
         more the geometry gets simplified.
     """
 
+    @classmethod
+    def from_kwargs(cls: Type[TParam], kwargs: Dict[str, Any]) -> TParam:
+        """ Load the data fields of the class from a dictionary.
+            Unknown entries in the dictionary are ignored, missing ones
+            get the default setting.
 
-class DataLayer(enum.Flag):
-    """ Layer types that can be selected for reverse and forward search.
+            The function supports type checking and throws a UsageError
+            when the value does not fit.
+        """
+        def _check_field(v: Any, field: 'dataclasses.Field[Any]') -> Any:
+            if v is None:
+                return field.default_factory() \
+                       if field.default_factory != dataclasses.MISSING \
+                       else field.default
+            if field.metadata and 'transform' in field.metadata:
+                return field.metadata['transform'](v)
+            if not isinstance(v, field.type):
+                raise UsageError(f"Parameter '{field.name}' needs to be of {field.type!s}.")
+            return v
+
+        return cls(**{f.name: _check_field(kwargs[f.name], f)
+                      for f in dataclasses.fields(cls) if f.name in kwargs})
+
+
+@dataclasses.dataclass
+class ReverseDetails(LookupDetails):
+    """ Collection of parameters for the reverse call.
+    """
+    max_rank: int = dataclasses.field(default=30,
+                                      metadata={'transform': lambda v: max(0, min(v, 30))}
+                                     )
+    """ Highest address rank to return.
+    """
+    layers: DataLayer = DataLayer.ADDRESS | DataLayer.POI
+    """ Filter which kind of data to include.
     """
-    POI = enum.auto()
-    ADDRESS = enum.auto()
-    RAILWAY = enum.auto()
-    MANMADE = enum.auto()
-    NATURAL = enum.auto()