1""" 2 3This module defines a ``Comparator`` class for use with geometry and geography 4objects. This is where spatial operators, like ``&&``, ``&<``, are defined. 5Spatial operators very often apply to the bounding boxes of geometries. For 6example, ``geom1 && geom2`` indicates if geom1's bounding box intersects 7geom2's. 8 9Examples 10-------- 11 12Select the objects whose bounding boxes are to the left of the 13bounding box of ``POLYGON((-5 45,5 45,5 -45,-5 -45,-5 45))``:: 14 15 select([table]).where(table.c.geom.to_left( 16 'POLYGON((-5 45,5 45,5 -45,-5 -45,-5 45))')) 17 18The ``<<`` and ``>>`` operators are a bit specific, because they have 19corresponding Python operator (``__lshift__`` and ``__rshift__``). The 20above ``SELECT`` expression can thus be rewritten like this:: 21 22 select([table]).where( 23 table.c.geom << 'POLYGON((-5 45,5 45,5 -45,-5 -45,-5 45))') 24 25Operators can also be used when using the ORM. For example:: 26 27 Session.query(Cls).filter( 28 Cls.geom << 'POLYGON((-5 45,5 45,5 -45,-5 -45,-5 45))') 29 30Now some other examples with the ``<#>`` operator. 31 32Select the ten objects that are the closest to ``POINT(0 0)`` (typical 33closed neighbors problem):: 34 35 select([table]).order_by(table.c.geom.distance_box('POINT(0 0)')).limit(10) 36 37Using the ORM:: 38 39 Session.query(Cls).order_by(Cls.geom.distance_box('POINT(0 0)')).limit(10) 40 41Reference 42--------- 43""" 44 45from sqlalchemy import types as sqltypes 46from sqlalchemy.types import UserDefinedType 47from sqlalchemy.dialects.postgresql import DOUBLE_PRECISION 48from sqlalchemy.sql import operators 49try: 50 from sqlalchemy.sql.functions import _FunctionGenerator 51except ImportError: # SQLA < 0.9 # pragma: no cover 52 from sqlalchemy.sql.expression import _FunctionGenerator 53 54 55INTERSECTS = operators.custom_op('&&') 56INTERSECTS_ND = operators.custom_op('&&&') 57OVERLAPS_OR_TO_LEFT = operators.custom_op('&<') 58OVERLAPS_OR_TO_RIGHT = operators.custom_op('&>') 59OVERLAPS_OR_BELOW = operators.custom_op('&<|') 60TO_LEFT = operators.custom_op('<<') 61BELOW = operators.custom_op('<<|') 62TO_RIGHT = operators.custom_op('>>') 63CONTAINED = operators.custom_op('@') 64OVERLAPS_OR_ABOVE = operators.custom_op('|&>') 65ABOVE = operators.custom_op('|>>') 66CONTAINS = operators.custom_op('~') 67SAME = operators.custom_op('~=') 68DISTANCE_CENTROID = operators.custom_op('<->') 69DISTANCE_BOX = operators.custom_op('<#>') 70 71 72class BaseComparator(UserDefinedType.Comparator): 73 """ 74 A custom comparator base class. It adds the ability to call spatial 75 functions on columns that use this kind of comparator. It also defines 76 functions that map to operators supported by ``Geometry``, ``Geography`` 77 and ``Raster`` columns. 78 79 This comparator is used by the :class:`geoalchemy2.types.Raster`. 80 """ 81 82 key = None 83 84 def __getattr__(self, name): 85 86 # Function names that don't start with "ST_" are rejected. 87 # This is not to mess up with SQLAlchemy's use of 88 # hasattr/getattr on Column objects. 89 90 if not name.lower().startswith('st_'): 91 raise AttributeError 92 93 # We create our own _FunctionGenerator here, and use it in place of 94 # SQLAlchemy's "func" object. This is to be able to "bind" the 95 # function to the SQL expression. See also GenericFunction. 96 97 func_ = _FunctionGenerator(expr=self.expr) 98 return getattr(func_, name) 99 100 def intersects(self, other): 101 """ 102 The ``&&`` operator. A's BBOX intersects B's. 103 """ 104 return self.operate(INTERSECTS, other, result_type=sqltypes.Boolean) 105 106 def overlaps_or_to_left(self, other): 107 """ 108 The ``&<`` operator. A's BBOX overlaps or is to the left of B's. 109 """ 110 return self.operate(OVERLAPS_OR_TO_LEFT, other, 111 result_type=sqltypes.Boolean) 112 113 def overlaps_or_to_right(self, other): 114 """ 115 The ``&>`` operator. A's BBOX overlaps or is to the right of B's. 116 """ 117 return self.operate(OVERLAPS_OR_TO_RIGHT, other, 118 result_type=sqltypes.Boolean) 119 120 121class Comparator(BaseComparator): 122 """ 123 A custom comparator class. Used in :class:`geoalchemy2.types.Geometry` 124 and :class:`geoalchemy2.types.Geography`. 125 126 This is where spatial operators like ``<<`` and ``<->`` are defined. 127 """ 128 129 def overlaps_or_below(self, other): 130 """ 131 The ``&<|`` operator. A's BBOX overlaps or is below B's. 132 """ 133 return self.operate(OVERLAPS_OR_BELOW, other, 134 result_type=sqltypes.Boolean) 135 136 def to_left(self, other): 137 """ 138 The ``<<`` operator. A's BBOX is strictly to the left of B's. 139 """ 140 return self.operate(TO_LEFT, other, result_type=sqltypes.Boolean) 141 142 def __lshift__(self, other): 143 """ 144 The ``<<`` operator. A's BBOX is strictly to the left of B's. 145 Same as ``to_left``, so:: 146 147 table.c.geom << 'POINT(1 2)' 148 149 is the same as:: 150 151 table.c.geom.to_left('POINT(1 2)') 152 """ 153 return self.to_left(other) 154 155 def below(self, other): 156 """ 157 The ``<<|`` operator. A's BBOX is strictly below B's. 158 """ 159 return self.operate(BELOW, other, result_type=sqltypes.Boolean) 160 161 def to_right(self, other): 162 """ 163 The ``>>`` operator. A's BBOX is strictly to the right of B's. 164 """ 165 return self.operate(TO_RIGHT, other, result_type=sqltypes.Boolean) 166 167 def __rshift__(self, other): 168 """ 169 The ``>>`` operator. A's BBOX is strictly to the left of B's. 170 Same as `to_`right``, so:: 171 172 table.c.geom >> 'POINT(1 2)' 173 174 is the same as:: 175 176 table.c.geom.to_right('POINT(1 2)') 177 """ 178 return self.to_right(other) 179 180 def contained(self, other): 181 """ 182 The ``@`` operator. A's BBOX is contained by B's. 183 """ 184 return self.operate(CONTAINED, other, result_type=sqltypes.Boolean) 185 186 def overlaps_or_above(self, other): 187 """ 188 The ``|&>`` operator. A's BBOX overlaps or is above B's. 189 """ 190 return self.operate(OVERLAPS_OR_ABOVE, other, 191 result_type=sqltypes.Boolean) 192 193 def above(self, other): 194 """ 195 The ``|>>`` operator. A's BBOX is strictly above B's. 196 """ 197 return self.operate(ABOVE, other, result_type=sqltypes.Boolean) 198 199 def contains(self, other, **kw): 200 """ 201 The ``~`` operator. A's BBOX contains B's. 202 """ 203 return self.operate(CONTAINS, other, result_type=sqltypes.Boolean) 204 205 def same(self, other): 206 """ 207 The ``~=`` operator. A's BBOX is the same as B's. 208 """ 209 return self.operate(SAME, other, result_type=sqltypes.Boolean) 210 211 def distance_centroid(self, other): 212 """ 213 The ``<->`` operator. The distance between two points. 214 """ 215 return self.operate(DISTANCE_CENTROID, other, 216 result_type=DOUBLE_PRECISION) 217 218 def distance_box(self, other): 219 """ 220 The ``<#>`` operator. The distance between bounding box of two 221 geometries. 222 """ 223 return self.operate(DISTANCE_BOX, other, result_type=DOUBLE_PRECISION) 224 225 def intersects_nd(self, other): 226 """ 227 The ``&&&`` operator returns TRUE if the n-D bounding box of geometry A 228 intersects the n-D bounding box of geometry B. 229 """ 230 return self.operate(INTERSECTS_ND, other, result_type=sqltypes.Boolean) 231