| 1 | # |
|---|
| 2 | # Copyright (c) 2008-2011 Camptocamp. |
|---|
| 3 | # All rights reserved. |
|---|
| 4 | # |
|---|
| 5 | # Redistribution and use in source and binary forms, with or without |
|---|
| 6 | # modification, are permitted provided that the following conditions |
|---|
| 7 | # are met: |
|---|
| 8 | # 1. Redistributions of source code must retain the above copyright |
|---|
| 9 | # notice, this list of conditions and the following disclaimer. |
|---|
| 10 | # 2. Redistributions in binary form must reproduce the above copyright |
|---|
| 11 | # notice, this list of conditions and the following disclaimer in the |
|---|
| 12 | # documentation and/or other materials provided with the distribution. |
|---|
| 13 | # 3. Neither the name of Camptocamp nor the names of its contributors may |
|---|
| 14 | # be used to endorse or promote products derived from this software |
|---|
| 15 | # without specific prior written permission. |
|---|
| 16 | # |
|---|
| 17 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
|---|
| 18 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
|---|
| 19 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
|---|
| 20 | # DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY |
|---|
| 21 | # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
|---|
| 22 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
|---|
| 23 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
|---|
| 24 | # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|---|
| 25 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
|---|
| 26 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|---|
| 27 | # |
|---|
| 28 | |
|---|
| 29 | """ This module includes unit tests for protocol.py using PostGIS as database""" |
|---|
| 30 | |
|---|
| 31 | import unittest |
|---|
| 32 | from nose.tools import eq_, ok_, raises |
|---|
| 33 | |
|---|
| 34 | from sqlalchemy.ext.declarative import declarative_base |
|---|
| 35 | from sqlalchemy import (create_engine, MetaData, Column, Integer, Numeric, Boolean) |
|---|
| 36 | from sqlalchemy import orm |
|---|
| 37 | |
|---|
| 38 | from geoalchemy import (Point, Polygon, GeometryColumn, GeometryDDL) |
|---|
| 39 | |
|---|
| 40 | from mapfish.sqlalchemygeom import GeometryTableMixIn |
|---|
| 41 | from mapfish.protocol import Protocol, create_geom_filter |
|---|
| 42 | from test_protocol import FakeRequest, FakeResponse |
|---|
| 43 | |
|---|
| 44 | from webob.exc import HTTPNotFound |
|---|
| 45 | from exceptions import Exception |
|---|
| 46 | from geojson import Feature, FeatureCollection, GeoJSON |
|---|
| 47 | |
|---|
| 48 | |
|---|
| 49 | engine = create_engine('postgresql://gis:gis@localhost/gis', echo=True) |
|---|
| 50 | metadata = MetaData(engine) |
|---|
| 51 | sm = orm.sessionmaker(autoflush=True, autocommit=False, bind=engine) |
|---|
| 52 | session = orm.scoped_session(sm) |
|---|
| 53 | |
|---|
| 54 | |
|---|
| 55 | Base = declarative_base(metadata=metadata) |
|---|
| 56 | |
|---|
| 57 | class Spot(Base, GeometryTableMixIn): |
|---|
| 58 | __tablename__ = 'spots' |
|---|
| 59 | |
|---|
| 60 | spot_id = Column(Integer, primary_key=True) |
|---|
| 61 | spot_height = Column(Numeric(asdecimal=False)) |
|---|
| 62 | spot_goodness = Column(Boolean) |
|---|
| 63 | spot_location = GeometryColumn(Point(2)) |
|---|
| 64 | |
|---|
| 65 | GeometryDDL(Spot.__table__) |
|---|
| 66 | |
|---|
| 67 | class Lake(Base, GeometryTableMixIn): |
|---|
| 68 | __tablename__ = 'lakes' |
|---|
| 69 | |
|---|
| 70 | id = Column(Integer, primary_key=True) |
|---|
| 71 | depth = Column(Numeric(asdecimal=False)) |
|---|
| 72 | geom = GeometryColumn(Polygon(2)) |
|---|
| 73 | |
|---|
| 74 | GeometryDDL(Lake.__table__) |
|---|
| 75 | |
|---|
| 76 | class Test(unittest.TestCase): |
|---|
| 77 | |
|---|
| 78 | |
|---|
| 79 | def setUp(self): |
|---|
| 80 | |
|---|
| 81 | metadata.drop_all() |
|---|
| 82 | metadata.create_all() |
|---|
| 83 | |
|---|
| 84 | # Insert some points into the database |
|---|
| 85 | session.add_all([ |
|---|
| 86 | Spot(spot_height=420.40, spot_location='POINT(0 0)', spot_goodness=True), |
|---|
| 87 | Spot(spot_height=102.34, spot_location='POINT(10 10)', spot_goodness=True), |
|---|
| 88 | Spot(spot_height=388.62, spot_location='POINT(10 11)', spot_goodness=True), |
|---|
| 89 | Spot(spot_height=1454.66, spot_location='POINT(40 34)', spot_goodness=True), |
|---|
| 90 | Spot(spot_height=54.66, spot_location='POINT(5 5)', spot_goodness=True), |
|---|
| 91 | Spot(spot_height=333.12, spot_location='POINT(2 3)', spot_goodness=True), |
|---|
| 92 | Spot(spot_height=783.55, spot_location='POINT(38 34)', spot_goodness=True), |
|---|
| 93 | Spot(spot_height=3454.67, spot_location='POINT(-134 45)', spot_goodness=True), |
|---|
| 94 | Spot(spot_height=6454.23, spot_location=None, spot_goodness=False) |
|---|
| 95 | ]) |
|---|
| 96 | |
|---|
| 97 | session.commit() |
|---|
| 98 | |
|---|
| 99 | |
|---|
| 100 | def tearDown(self): |
|---|
| 101 | session.rollback() |
|---|
| 102 | metadata.drop_all() |
|---|
| 103 | |
|---|
| 104 | |
|---|
| 105 | def test_protocol_create(self): |
|---|
| 106 | """Create a new point""" |
|---|
| 107 | proto = Protocol(session, Spot) |
|---|
| 108 | |
|---|
| 109 | request = FakeRequest({}) |
|---|
| 110 | request.body = '{"type": "FeatureCollection", "features": [{"type": "Feature", "properties": {"spot_height": 12.0, "spot_goodness": true}, "geometry": {"type": "Point", "coordinates": [45, 5]}}]}' |
|---|
| 111 | |
|---|
| 112 | response = FakeResponse() |
|---|
| 113 | collection = proto.create(request, response) |
|---|
| 114 | eq_(response.status, 201) |
|---|
| 115 | eq_(len(collection.features), 1) |
|---|
| 116 | feature0 = collection.features[0] |
|---|
| 117 | eq_(feature0.id, 10) |
|---|
| 118 | eq_(feature0.geometry.coordinates, (45.0, 5.0)) |
|---|
| 119 | eq_(feature0.properties["spot_height"], 12) |
|---|
| 120 | eq_(feature0.properties["spot_goodness"], True) |
|---|
| 121 | |
|---|
| 122 | new_spot = session.query(Spot).filter(Spot.spot_height==12.0).one() |
|---|
| 123 | ok_(new_spot is not None) |
|---|
| 124 | eq_(session.scalar(new_spot.spot_location.wkt), u'POINT(45 5)') |
|---|
| 125 | eq_(new_spot.spot_goodness, True) |
|---|
| 126 | |
|---|
| 127 | |
|---|
| 128 | def test_protocol_create_and_update(self): |
|---|
| 129 | """Create a new point and also update an already existing point""" |
|---|
| 130 | |
|---|
| 131 | old_spot = session.query(Spot).filter(Spot.spot_height==102.34).one() |
|---|
| 132 | |
|---|
| 133 | proto = Protocol(session, Spot) |
|---|
| 134 | |
|---|
| 135 | request = FakeRequest({}) |
|---|
| 136 | request.body = '{"type": "FeatureCollection", "features": [\ |
|---|
| 137 | {"type": "Feature", "properties": {"spot_height": 12.0}, "geometry": {"type": "Point", "coordinates": [45, 5]}},\ |
|---|
| 138 | {"type": "Feature", "id": ' + str(old_spot.spot_id) + ', "properties": {}, "geometry": {"type": "Point", "coordinates": [1, 1]}}]}' |
|---|
| 139 | |
|---|
| 140 | response = FakeResponse() |
|---|
| 141 | collection = proto.create(request, response) |
|---|
| 142 | eq_(response.status, 201) |
|---|
| 143 | eq_(len(collection.features), 2) |
|---|
| 144 | feature0 = collection.features[0] |
|---|
| 145 | eq_(feature0.id, 10) |
|---|
| 146 | eq_(feature0.geometry.coordinates, (45.0, 5.0)) |
|---|
| 147 | eq_(feature0.properties["spot_height"], 12) |
|---|
| 148 | feature1 = collection.features[1] |
|---|
| 149 | eq_(feature1.id, old_spot.spot_id) |
|---|
| 150 | eq_(feature1.geometry.coordinates, (1, 1)) |
|---|
| 151 | |
|---|
| 152 | new_spot = session.query(Spot).filter(Spot.spot_height==12.0).one() |
|---|
| 153 | ok_(new_spot is not None) |
|---|
| 154 | eq_(session.scalar(new_spot.spot_location.wkt), u'POINT(45 5)') |
|---|
| 155 | |
|---|
| 156 | updated_spot = session.query(Spot).filter(Spot.spot_height==102.34).one() |
|---|
| 157 | ok_(updated_spot is not None) |
|---|
| 158 | ok_(old_spot is updated_spot) |
|---|
| 159 | eq_(updated_spot.spot_height, 102.34) |
|---|
| 160 | eq_(session.scalar(updated_spot.spot_location.wkt), u'POINT(1 1)') |
|---|
| 161 | |
|---|
| 162 | |
|---|
| 163 | @raises(Exception) |
|---|
| 164 | def test_protocol_create_fails(self): |
|---|
| 165 | """Try to create a feature without geometry""" |
|---|
| 166 | proto = Protocol(session, Spot) |
|---|
| 167 | |
|---|
| 168 | request = FakeRequest({}) |
|---|
| 169 | request.body = '{"type": "FeatureCollection", "features": [{"type": "Feature", "properties": {"spot_height": 12.0}}]}' |
|---|
| 170 | |
|---|
| 171 | proto.create(request, FakeResponse()) |
|---|
| 172 | |
|---|
| 173 | |
|---|
| 174 | def test_protocol_update(self): |
|---|
| 175 | """Update an existing point""" |
|---|
| 176 | proto = Protocol(session, Spot) |
|---|
| 177 | id = 1 |
|---|
| 178 | |
|---|
| 179 | request = FakeRequest({}) |
|---|
| 180 | request.body = '{"type": "Feature", "id": ' + str(id) + ', "properties": {}, "geometry": {"type": "Point", "coordinates": [1, 1]}}' |
|---|
| 181 | |
|---|
| 182 | response = FakeResponse() |
|---|
| 183 | feature = proto.update(request, response, id) |
|---|
| 184 | eq_(response.status, 201) |
|---|
| 185 | eq_(feature.id, 1) |
|---|
| 186 | eq_(feature.geometry.coordinates, (1.0, 1.0)) |
|---|
| 187 | |
|---|
| 188 | spot = session.query(Spot).get(id) |
|---|
| 189 | ok_(spot is not None) |
|---|
| 190 | eq_(session.scalar(spot.spot_location.wkt), u'POINT(1 1)') |
|---|
| 191 | |
|---|
| 192 | |
|---|
| 193 | @raises(HTTPNotFound) |
|---|
| 194 | def test_protocol_update_fails(self): |
|---|
| 195 | """Try to update a not-existing feature""" |
|---|
| 196 | proto = Protocol(session, Spot) |
|---|
| 197 | id = -1 |
|---|
| 198 | |
|---|
| 199 | request = FakeRequest({}) |
|---|
| 200 | request.body = '{"type": "Feature", "id": ' + str(id) + ', "properties": {}, "geometry": {"type": "Point", "coordinates": [1, 1]}}' |
|---|
| 201 | |
|---|
| 202 | response = FakeResponse() |
|---|
| 203 | proto.update(request, response, id) |
|---|
| 204 | |
|---|
| 205 | |
|---|
| 206 | def test_protocol_delete(self): |
|---|
| 207 | """Delete an existing point""" |
|---|
| 208 | proto = Protocol(session, Spot) |
|---|
| 209 | id = 1 |
|---|
| 210 | |
|---|
| 211 | request = FakeRequest({}) |
|---|
| 212 | response = FakeResponse() |
|---|
| 213 | |
|---|
| 214 | proto.delete(request, response, id) |
|---|
| 215 | eq_(response.status, 204) |
|---|
| 216 | |
|---|
| 217 | spot = session.query(Spot).get(id) |
|---|
| 218 | ok_(spot is None) |
|---|
| 219 | |
|---|
| 220 | |
|---|
| 221 | @raises(HTTPNotFound) |
|---|
| 222 | def test_protocol_delete_fails(self): |
|---|
| 223 | """Try to delete a not-existing point""" |
|---|
| 224 | proto = Protocol(session, Spot) |
|---|
| 225 | |
|---|
| 226 | proto.delete(FakeRequest({}), FakeResponse(), -1) |
|---|
| 227 | |
|---|
| 228 | |
|---|
| 229 | def test_protocol_count(self): |
|---|
| 230 | """Get the feature count""" |
|---|
| 231 | proto = Protocol(session, Spot) |
|---|
| 232 | |
|---|
| 233 | eq_(proto.count(FakeRequest({})), '9') |
|---|
| 234 | |
|---|
| 235 | |
|---|
| 236 | def test_protocol_count_filter_box(self): |
|---|
| 237 | """Get the feature count with a box as filter""" |
|---|
| 238 | proto = Protocol(session, Spot) |
|---|
| 239 | request = FakeRequest({}) |
|---|
| 240 | |
|---|
| 241 | request.params['bbox'] = '-10,-10,10,10' |
|---|
| 242 | eq_(proto.count(request), '4') |
|---|
| 243 | |
|---|
| 244 | request.params['tolerance'] = '1' |
|---|
| 245 | eq_(proto.count(request), '5') |
|---|
| 246 | |
|---|
| 247 | # reproject the bbox |
|---|
| 248 | request.params['bbox'] = '-12.3364241712925,-10.0036833569465,7.66304367998925,9.9979519038951' |
|---|
| 249 | request.params['epsg'] = '4807' |
|---|
| 250 | request.params['tolerance'] = '0' |
|---|
| 251 | eq_(proto.count(request), '4') |
|---|
| 252 | |
|---|
| 253 | |
|---|
| 254 | def test_protocol_count_filter_within(self): |
|---|
| 255 | """Get the feature count with a point as filter""" |
|---|
| 256 | proto = Protocol(session, Spot) |
|---|
| 257 | request = FakeRequest({}) |
|---|
| 258 | |
|---|
| 259 | request.params['lat'] = '0' |
|---|
| 260 | request.params['lon'] = '0' |
|---|
| 261 | eq_(proto.count(request), '1') |
|---|
| 262 | |
|---|
| 263 | request.params['tolerance'] = '10' |
|---|
| 264 | eq_(proto.count(request), '3') |
|---|
| 265 | |
|---|
| 266 | |
|---|
| 267 | def test_protocol_count_filter_geometry(self): |
|---|
| 268 | """Get the feature count with a geometry as filter""" |
|---|
| 269 | proto = Protocol(session, Spot) |
|---|
| 270 | request = FakeRequest({}) |
|---|
| 271 | |
|---|
| 272 | request.params['geometry'] = '{"type": "Polygon", "coordinates": [[ [-10, -1], [10, -1], [0, 10], [-10, -1] ]]}' |
|---|
| 273 | eq_(proto.count(request), '2') |
|---|
| 274 | |
|---|
| 275 | request.params['tolerance'] = '10' |
|---|
| 276 | eq_(proto.count(request), '5') |
|---|
| 277 | |
|---|
| 278 | |
|---|
| 279 | def test_protocol_count_queryable(self): |
|---|
| 280 | """Count all features that match a filter""" |
|---|
| 281 | proto = Protocol(session, Spot) |
|---|
| 282 | request = FakeRequest({}) |
|---|
| 283 | request.params['queryable'] = 'spot_height' |
|---|
| 284 | request.params['spot_height__gte'] = '1454.66' |
|---|
| 285 | |
|---|
| 286 | eq_(proto.count(request), '3') |
|---|
| 287 | |
|---|
| 288 | |
|---|
| 289 | def test_protocol_count_custom_filter(self): |
|---|
| 290 | """Count all features that match a custom filter""" |
|---|
| 291 | session.add_all([ |
|---|
| 292 | Lake(depth=20, geom='POLYGON((-88.7968950764331 43.2305732929936,-88.7935511273885 43.1553344394904,-88.716640299363 43.1570064140127,-88.7250001719745 43.2339172420382,-88.7968950764331 43.2305732929936))'), |
|---|
| 293 | Lake(depth=5, geom='POLYGON((-88.1147292993631 42.7540605095542,-88.1548566878981 42.7824840764331,-88.1799363057325 42.7707802547771,-88.188296178344 42.7323248407643,-88.1832802547771 42.6955414012739,-88.1565286624204 42.6771496815287,-88.1448248407643 42.6336783439491,-88.131449044586 42.5718152866242,-88.1013535031847 42.565127388535,-88.1080414012739 42.5868630573248,-88.1164012738854 42.6119426751592,-88.1080414012739 42.6520700636943,-88.0980095541401 42.6838375796178,-88.0846337579618 42.7139331210191,-88.1013535031847 42.7423566878981,-88.1147292993631 42.7540605095542))'), |
|---|
| 294 | Lake(depth=120, geom='POLYGON((-89.0694267515924 43.1335987261147,-89.1078821656051 43.1135350318471,-89.1329617834395 43.0884554140127,-89.1312898089172 43.0466560509554,-89.112898089172 43.0132165605096,-89.0694267515924 42.9898089171975,-89.0343152866242 42.953025477707,-89.0209394904459 42.9179140127389,-89.0042197452229 42.8961783439491,-88.9774681528663 42.8644108280255,-88.9440286624204 42.8292993630573,-88.9072452229299 42.8142515923567,-88.8687898089172 42.815923566879,-88.8687898089172 42.815923566879,-88.8102707006369 42.8343152866242,-88.7734872611465 42.8710987261147,-88.7517515923567 42.9145700636943,-88.7433917197452 42.9730891719745,-88.7517515923567 43.0299363057325,-88.7734872611465 43.0867834394905,-88.7885352038217 43.158678388535,-88.8738057324841 43.1620222929936,-88.947372611465 43.1937898089172,-89.0042197452229 43.2138535031847,-89.0410031847134 43.2389331210191,-89.0710987261147 43.243949044586,-89.0660828025478 43.2238853503185,-89.0543789808917 43.203821656051,-89.0376592356688 43.175398089172,-89.0292993630573 43.1519904458599,-89.0376592356688 43.1369426751592,-89.0393312101911 43.1386146496815,-89.0393312101911 43.1386146496815,-89.0510350318471 43.1335987261147,-89.0694267515924 43.1335987261147))'), |
|---|
| 295 | Lake(depth=450, geom='POLYGON((-88.9122611464968 43.038296178344,-88.9222929936306 43.0399681528663,-88.9323248407643 43.0282643312102,-88.9206210191083 43.0182324840764,-88.9105891719745 43.0165605095542,-88.9005573248408 43.0232484076433,-88.9072452229299 43.0282643312102,-88.9122611464968 43.038296178344))') |
|---|
| 296 | ]) |
|---|
| 297 | session.commit(); |
|---|
| 298 | |
|---|
| 299 | proto = Protocol(session, Lake) |
|---|
| 300 | |
|---|
| 301 | from sqlalchemy.sql import and_ |
|---|
| 302 | |
|---|
| 303 | request = FakeRequest({}) |
|---|
| 304 | request.params['bbox'] = '-90,40,-80,45' |
|---|
| 305 | |
|---|
| 306 | filter = create_geom_filter(request, Lake) |
|---|
| 307 | |
|---|
| 308 | compare_filter = Lake.geom.area >= 0.1 |
|---|
| 309 | filter = and_(filter, compare_filter) |
|---|
| 310 | |
|---|
| 311 | eq_(proto.count(request, filter=filter), '1') |
|---|
| 312 | |
|---|
| 313 | |
|---|
| 314 | def test_protocol_read_all(self): |
|---|
| 315 | """Return all features""" |
|---|
| 316 | proto = Protocol(session, Spot) |
|---|
| 317 | |
|---|
| 318 | collection = proto.read(FakeRequest({})) |
|---|
| 319 | ok_(collection is not None) |
|---|
| 320 | ok_(isinstance(collection, FeatureCollection)) |
|---|
| 321 | eq_(len(collection.features), 9) |
|---|
| 322 | |
|---|
| 323 | |
|---|
| 324 | def test_protocol_read_one(self): |
|---|
| 325 | """Return one feature""" |
|---|
| 326 | proto = Protocol(session, Spot) |
|---|
| 327 | |
|---|
| 328 | feature = proto.read(FakeRequest({}), id=1) |
|---|
| 329 | ok_(feature is not None) |
|---|
| 330 | ok_(isinstance(feature, Feature)) |
|---|
| 331 | eq_(feature.id, 1) |
|---|
| 332 | eq_(feature.geometry.coordinates, (0.0, 0.0)) |
|---|
| 333 | eq_(feature.properties["spot_height"], 420.39999999999998) |
|---|
| 334 | eq_(feature.properties["spot_goodness"], True) |
|---|
| 335 | |
|---|
| 336 | |
|---|
| 337 | def test_protocol_read_one_null(self): |
|---|
| 338 | """Return one null feature""" |
|---|
| 339 | proto = Protocol(session, Spot) |
|---|
| 340 | |
|---|
| 341 | feature = proto.read(FakeRequest({}), id=9) |
|---|
| 342 | ok_(feature is not None) |
|---|
| 343 | ok_(isinstance(feature, Feature)) |
|---|
| 344 | eq_(feature.id, 9) |
|---|
| 345 | # make use of __geo_interface__ property since 'geometry' |
|---|
| 346 | # value is not the same in various versions of geojson lib |
|---|
| 347 | ok_(feature.__geo_interface__['geometry'] is None) |
|---|
| 348 | ok_(feature.__geo_interface__['bbox'] is None) |
|---|
| 349 | |
|---|
| 350 | |
|---|
| 351 | @raises(HTTPNotFound) |
|---|
| 352 | def test_protocol_read_one_fails(self): |
|---|
| 353 | """Try to get a single point with a wrong primary key""" |
|---|
| 354 | proto = Protocol(session, Spot) |
|---|
| 355 | |
|---|
| 356 | proto.read(FakeRequest({}), id=-1) |
|---|
| 357 | |
|---|
| 358 | |
|---|
| 359 | def test_within_distance(self): |
|---|
| 360 | """Test the PostGIS implementation for within_distance""" |
|---|
| 361 | from mapfish.sqlalchemygeom import within_distance |
|---|
| 362 | |
|---|
| 363 | eq_(session.scalar(within_distance('POINT(-88.9139332929936 42.5082802993631)', |
|---|
| 364 | 'POINT(-88.9139332929936 35.5082802993631)', 10)), True) |
|---|
| 365 | ok_(session.scalar(within_distance('Point(0 0)', 'Point(0 0)', 0))) |
|---|
| 366 | ok_(session.scalar(within_distance('Point(0 0)', |
|---|
| 367 | 'Polygon((-5 -5, 5 -5, 5 5, -5 5, -5 -5))', 0))) |
|---|
| 368 | ok_(session.scalar(within_distance('Point(5 5)', |
|---|
| 369 | 'Polygon((-5 -5, 5 -5, 5 5, -5 5, -5 -5))', 0))) |
|---|
| 370 | ok_(session.scalar(within_distance('Point(6 5)', |
|---|
| 371 | 'Polygon((-5 -5, 5 -5, 5 5, -5 5, -5 -5))', 1))) |
|---|
| 372 | ok_(session.scalar(within_distance('Polygon((0 0, 1 0, 1 8, 0 8, 0 0))', |
|---|
| 373 | 'Polygon((-5 -5, 5 -5, 5 5, -5 5, -5 -5))', 0))) |
|---|