"""
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.
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.
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")
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.
"""
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
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()