]> git.openstreetmap.org Git - nominatim.git/blobdiff - nominatim/api/types.py
avoid fallback country lookup when places are excluded
[nominatim.git] / nominatim / api / types.py
index ff7457ec01ce3bd52df97176966d2c8ce3ae4306..87568a09ac59cd96f2185c61cc44f42ab4e85b02 100644 (file)
@@ -15,6 +15,9 @@ import enum
 import math
 from struct import unpack
 
 import math
 from struct import unpack
 
+from geoalchemy2 import WKTElement
+import geoalchemy2.functions
+
 from nominatim.errors import UsageError
 
 # pylint: disable=no-member,too-many-boolean-expressions,too-many-instance-attributes
 from nominatim.errors import UsageError
 
 # pylint: disable=no-member,too-many-boolean-expressions,too-many-instance-attributes
@@ -119,6 +122,12 @@ class Point(NamedTuple):
         return Point(x, y)
 
 
         return Point(x, y)
 
 
+    def sql_value(self) -> WKTElement:
+        """ Create an SQL expression for the point.
+        """
+        return WKTElement(f'POINT({self.x} {self.y})', srid=4326)
+
+
 
 AnyPoint = Union[Point, Tuple[float, float]]
 
 
 AnyPoint = Union[Point, Tuple[float, float]]
 
@@ -163,12 +172,26 @@ class Bbox:
         return self.coords[2]
 
 
         return self.coords[2]
 
 
+    @property
+    def area(self) -> float:
+        """ Return the area of the box in WGS84.
+        """
+        return (self.coords[2] - self.coords[0]) * (self.coords[3] - self.coords[1])
+
+
+    def sql_value(self) -> Any:
+        """ Create an SQL expression for the box.
+        """
+        return geoalchemy2.functions.ST_MakeEnvelope(*self.coords, 4326)
+
+
     def contains(self, pt: Point) -> bool:
         """ Check if the point is inside or on the boundary of the box.
         """
         return self.coords[0] <= pt[0] and self.coords[1] <= pt[1]\
                and self.coords[2] >= pt[0] and self.coords[3] >= pt[1]
 
     def contains(self, pt: Point) -> bool:
         """ Check if the point is inside or on the boundary of the box.
         """
         return self.coords[0] <= pt[0] and self.coords[1] <= pt[1]\
                and self.coords[2] >= pt[0] and self.coords[3] >= pt[1]
 
+
     @staticmethod
     def from_wkb(wkb: Optional[bytes]) -> 'Optional[Bbox]':
         """ Create a Bbox from a bounding box polygon as returned by
     @staticmethod
     def from_wkb(wkb: Optional[bytes]) -> 'Optional[Bbox]':
         """ Create a Bbox from a bounding box polygon as returned by
@@ -273,16 +296,17 @@ def format_excluded(ids: Any) -> List[int]:
     """
     plist: Sequence[str]
     if isinstance(ids, str):
     """
     plist: Sequence[str]
     if isinstance(ids, str):
-        plist = ids.split(',')
+        plist = [s.strip() for s in ids.split(',')]
     elif isinstance(ids, abc.Sequence):
         plist = ids
     else:
         raise UsageError("Parameter 'excluded' needs to be a comma-separated list "
                          "or a Python list of numbers.")
     elif isinstance(ids, abc.Sequence):
         plist = ids
     else:
         raise UsageError("Parameter 'excluded' needs to be a comma-separated list "
                          "or a Python list of numbers.")
-    if any(not isinstance(i, int) or (isinstance(i, str) and not i.isdigit()) for i in plist):
+    if not all(isinstance(i, int) or
+               (isinstance(i, str) and (not i or i.isdigit())) for i in plist):
         raise UsageError("Parameter 'excluded' only takes place IDs.")
 
         raise UsageError("Parameter 'excluded' only takes place IDs.")
 
-    return [int(id) for id in plist if id]
+    return [int(id) for id in plist if id] or [0]
 
 
 def format_categories(categories: List[Tuple[str, str]]) -> List[Tuple[str, str]]:
 
 
 def format_categories(categories: List[Tuple[str, str]]) -> List[Tuple[str, str]]:
@@ -377,7 +401,8 @@ class SearchDetails(LookupDetails):
                                      )
     """ Highest address rank to return.
     """
                                      )
     """ Highest address rank to return.
     """
-    layers: Optional[DataLayer] = None
+    layers: Optional[DataLayer] = dataclasses.field(default=None,
+                                                    metadata={'transform': lambda r : r})
     """ Filter which kind of data to include. When 'None' (the default) then
         filtering by layers is disabled.
     """
     """ Filter which kind of data to include. When 'None' (the default) then
         filtering by layers is disabled.
     """
@@ -404,7 +429,8 @@ class SearchDetails(LookupDetails):
                                               metadata={'transform': Point.from_param})
     """ Order results by distance to the given point.
     """
                                               metadata={'transform': Point.from_param})
     """ Order results by distance to the given point.
     """
-    near_radius: Optional[float] = None
+    near_radius: Optional[float] = dataclasses.field(default=None,
+                                              metadata={'transform': lambda r : r})
     """ Use near point as a filter and drop results outside the given
         radius. Radius is given in degrees WSG84.
     """
     """ Use near point as a filter and drop results outside the given
         radius. Radius is given in degrees WSG84.
     """
@@ -418,7 +444,7 @@ class SearchDetails(LookupDetails):
         if self.viewbox is not None:
             xext = (self.viewbox.maxlon - self.viewbox.minlon)/2
             yext = (self.viewbox.maxlat - self.viewbox.minlat)/2
         if self.viewbox is not None:
             xext = (self.viewbox.maxlon - self.viewbox.minlon)/2
             yext = (self.viewbox.maxlat - self.viewbox.minlat)/2
-            self.viewbox_x2 = Bbox(self.viewbox.minlon - xext, self.viewbox.maxlon - yext,
+            self.viewbox_x2 = Bbox(self.viewbox.minlon - xext, self.viewbox.minlat - yext,
                                    self.viewbox.maxlon + xext, self.viewbox.maxlat + yext)
 
 
                                    self.viewbox.maxlon + xext, self.viewbox.maxlat + yext)