web/lib/django/contrib/gis/tests/geoapp/tests.py
changeset 0 0d40e90630ef
child 29 cc9b7e14412b
equal deleted inserted replaced
-1:000000000000 0:0d40e90630ef
       
     1 import os, unittest
       
     2 from django.contrib.gis import gdal
       
     3 from django.contrib.gis.db.backend import SpatialBackend
       
     4 from django.contrib.gis.geos import *
       
     5 from django.contrib.gis.measure import Distance
       
     6 from django.contrib.gis.tests.utils import no_oracle, no_postgis, no_spatialite
       
     7 from models import Country, City, PennsylvaniaCity, State
       
     8 
       
     9 if not SpatialBackend.spatialite:
       
    10     from models import Feature, MinusOneSRID
       
    11 
       
    12 # TODO: Some tests depend on the success/failure of previous tests, these should
       
    13 # be decoupled.  This flag is an artifact of this problem, and makes debugging easier;
       
    14 # specifically, the DISABLE flag will disables all tests, allowing problem tests to
       
    15 # be examined individually.
       
    16 DISABLE = False
       
    17 
       
    18 class GeoModelTest(unittest.TestCase):
       
    19 
       
    20     def test01_initial_sql(self):
       
    21         "Testing geographic initial SQL."
       
    22         if DISABLE: return
       
    23         if SpatialBackend.oracle:
       
    24             # Oracle doesn't allow strings longer than 4000 characters
       
    25             # in SQL files, and I'm stumped on how to use Oracle BFILE's
       
    26             # in PLSQL, so we set up the larger geometries manually, rather
       
    27             # than relying on the initial SQL.
       
    28 
       
    29             # Routine for returning the path to the data files.
       
    30             data_dir = os.path.join(os.path.dirname(__file__), 'sql')
       
    31             def get_file(wkt_file):
       
    32                 return os.path.join(data_dir, wkt_file)
       
    33             State(name='Puerto Rico', poly=None).save()
       
    34             State(name='Colorado', poly=fromfile(get_file('co.wkt'))).save()
       
    35             State(name='Kansas', poly=fromfile(get_file('ks.wkt'))).save()
       
    36             Country(name='Texas', mpoly=fromfile(get_file('tx.wkt'))).save()
       
    37             Country(name='New Zealand', mpoly=fromfile(get_file('nz.wkt'))).save()
       
    38 
       
    39         # Ensuring that data was loaded from initial SQL.
       
    40         self.assertEqual(2, Country.objects.count())
       
    41         self.assertEqual(8, City.objects.count())
       
    42         self.assertEqual(3, State.objects.count())
       
    43 
       
    44     def test02_proxy(self):
       
    45         "Testing Lazy-Geometry support (using the GeometryProxy)."
       
    46         if DISABLE: return
       
    47         ## Testing on a Point
       
    48         pnt = Point(0, 0)
       
    49         nullcity = City(name='NullCity', point=pnt)
       
    50         nullcity.save()
       
    51 
       
    52         # Making sure TypeError is thrown when trying to set with an
       
    53         #  incompatible type.
       
    54         for bad in [5, 2.0, LineString((0, 0), (1, 1))]:
       
    55             try:
       
    56                 nullcity.point = bad
       
    57             except TypeError:
       
    58                 pass
       
    59             else:
       
    60                 self.fail('Should throw a TypeError')
       
    61 
       
    62         # Now setting with a compatible GEOS Geometry, saving, and ensuring
       
    63         #  the save took, notice no SRID is explicitly set.
       
    64         new = Point(5, 23)
       
    65         nullcity.point = new
       
    66 
       
    67         # Ensuring that the SRID is automatically set to that of the
       
    68         #  field after assignment, but before saving.
       
    69         self.assertEqual(4326, nullcity.point.srid)
       
    70         nullcity.save()
       
    71 
       
    72         # Ensuring the point was saved correctly after saving
       
    73         self.assertEqual(new, City.objects.get(name='NullCity').point)
       
    74 
       
    75         # Setting the X and Y of the Point
       
    76         nullcity.point.x = 23
       
    77         nullcity.point.y = 5
       
    78         # Checking assignments pre & post-save.
       
    79         self.assertNotEqual(Point(23, 5), City.objects.get(name='NullCity').point)
       
    80         nullcity.save()
       
    81         self.assertEqual(Point(23, 5), City.objects.get(name='NullCity').point)
       
    82         nullcity.delete()
       
    83 
       
    84         ## Testing on a Polygon
       
    85         shell = LinearRing((0, 0), (0, 100), (100, 100), (100, 0), (0, 0))
       
    86         inner = LinearRing((40, 40), (40, 60), (60, 60), (60, 40), (40, 40))
       
    87 
       
    88         # Creating a State object using a built Polygon
       
    89         ply = Polygon(shell, inner)
       
    90         nullstate = State(name='NullState', poly=ply)
       
    91         self.assertEqual(4326, nullstate.poly.srid) # SRID auto-set from None
       
    92         nullstate.save()
       
    93 
       
    94         ns = State.objects.get(name='NullState')
       
    95         self.assertEqual(ply, ns.poly)
       
    96 
       
    97         # Testing the `ogr` and `srs` lazy-geometry properties.
       
    98         if gdal.HAS_GDAL:
       
    99             self.assertEqual(True, isinstance(ns.poly.ogr, gdal.OGRGeometry))
       
   100             self.assertEqual(ns.poly.wkb, ns.poly.ogr.wkb)
       
   101             self.assertEqual(True, isinstance(ns.poly.srs, gdal.SpatialReference))
       
   102             self.assertEqual('WGS 84', ns.poly.srs.name)
       
   103 
       
   104         # Changing the interior ring on the poly attribute.
       
   105         new_inner = LinearRing((30, 30), (30, 70), (70, 70), (70, 30), (30, 30))
       
   106         ns.poly[1] = new_inner
       
   107         ply[1] = new_inner
       
   108         self.assertEqual(4326, ns.poly.srid)
       
   109         ns.save()
       
   110         self.assertEqual(ply, State.objects.get(name='NullState').poly)
       
   111         ns.delete()
       
   112 
       
   113     @no_oracle # Oracle does not support KML.
       
   114     @no_spatialite # SpatiaLite does not support KML.
       
   115     def test03a_kml(self):
       
   116         "Testing KML output from the database using GeoQuerySet.kml()."
       
   117         if DISABLE: return
       
   118         # Should throw a TypeError when trying to obtain KML from a
       
   119         #  non-geometry field.
       
   120         qs = City.objects.all()
       
   121         self.assertRaises(TypeError, qs.kml, 'name')
       
   122 
       
   123         # The reference KML depends on the version of PostGIS used
       
   124         # (the output stopped including altitude in 1.3.3).
       
   125         major, minor1, minor2 = SpatialBackend.version
       
   126         ref_kml1 = '<Point><coordinates>-104.609252,38.255001,0</coordinates></Point>'
       
   127         ref_kml2 = '<Point><coordinates>-104.609252,38.255001</coordinates></Point>'
       
   128         if major == 1:
       
   129             if minor1 > 3 or (minor1 == 3 and minor2 >= 3): ref_kml = ref_kml2
       
   130             else: ref_kml = ref_kml1
       
   131         else:
       
   132             ref_kml = ref_kml2
       
   133 
       
   134         # Ensuring the KML is as expected.
       
   135         ptown1 = City.objects.kml(field_name='point', precision=9).get(name='Pueblo')
       
   136         ptown2 = City.objects.kml(precision=9).get(name='Pueblo')
       
   137         for ptown in [ptown1, ptown2]:
       
   138             self.assertEqual(ref_kml, ptown.kml)
       
   139 
       
   140     @no_spatialite # SpatiaLite does not support GML.
       
   141     def test03b_gml(self):
       
   142         "Testing GML output from the database using GeoQuerySet.gml()."
       
   143         if DISABLE: return
       
   144         # Should throw a TypeError when tyring to obtain GML from a
       
   145         #  non-geometry field.
       
   146         qs = City.objects.all()
       
   147         self.assertRaises(TypeError, qs.gml, field_name='name')
       
   148         ptown1 = City.objects.gml(field_name='point', precision=9).get(name='Pueblo')
       
   149         ptown2 = City.objects.gml(precision=9).get(name='Pueblo')
       
   150 
       
   151         import re
       
   152         if SpatialBackend.oracle:
       
   153             # No precision parameter for Oracle :-/
       
   154             gml_regex = re.compile(r'^<gml:Point srsName="SDO:4326" xmlns:gml="http://www.opengis.net/gml"><gml:coordinates decimal="\." cs="," ts=" ">-104.60925\d+,38.25500\d+ </gml:coordinates></gml:Point>')
       
   155             for ptown in [ptown1, ptown2]:
       
   156                 self.failUnless(gml_regex.match(ptown.gml))
       
   157         else:
       
   158             gml_regex = re.compile(r'^<gml:Point srsName="EPSG:4326"><gml:coordinates>-104\.60925\d+,38\.255001</gml:coordinates></gml:Point>')
       
   159             for ptown in [ptown1, ptown2]:
       
   160                 self.failUnless(gml_regex.match(ptown.gml))
       
   161 
       
   162     @no_spatialite
       
   163     @no_oracle
       
   164     def test03c_geojson(self):
       
   165         "Testing GeoJSON output from the database using GeoQuerySet.geojson()."
       
   166         if DISABLE: return
       
   167         # PostGIS only supports GeoJSON on 1.3.4+
       
   168         if not SpatialBackend.geojson:
       
   169             return
       
   170 
       
   171         major, minor1, minor2 = SpatialBackend.version
       
   172         if major >=1 and minor1 >= 4:
       
   173             pueblo_json = '{"type":"Point","coordinates":[-104.609252,38.255001]}'
       
   174             houston_json = '{"type":"Point","crs":{"type":"name","properties":{"name":"EPSG:4326"}},"coordinates":[-95.363151,29.763374]}'
       
   175             victoria_json = '{"type":"Point","bbox":[-123.30519600,48.46261100,-123.30519600,48.46261100],"coordinates":[-123.305196,48.462611]}'
       
   176             chicago_json = '{"type":"Point","crs":{"type":"name","properties":{"name":"EPSG:4326"}},"bbox":[-87.65018,41.85039,-87.65018,41.85039],"coordinates":[-87.65018,41.85039]}'
       
   177         else:
       
   178             pueblo_json = '{"type":"Point","coordinates":[-104.60925200,38.25500100]}'
       
   179             houston_json = '{"type":"Point","crs":{"type":"EPSG","properties":{"EPSG":4326}},"coordinates":[-95.36315100,29.76337400]}'
       
   180             victoria_json = '{"type":"Point","bbox":[-123.30519600,48.46261100,-123.30519600,48.46261100],"coordinates":[-123.30519600,48.46261100]}'
       
   181             chicago_json = '{"type":"Point","crs":{"type":"EPSG","properties":{"EPSG":4326}},"bbox":[-87.65018,41.85039,-87.65018,41.85039],"coordinates":[-87.65018,41.85039]}'
       
   182             
       
   183         # Precision argument should only be an integer
       
   184         self.assertRaises(TypeError, City.objects.geojson, precision='foo')
       
   185         
       
   186         # Reference queries and values.
       
   187         # SELECT ST_AsGeoJson("geoapp_city"."point", 8, 0) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Pueblo';
       
   188         self.assertEqual(pueblo_json, City.objects.geojson().get(name='Pueblo').geojson)
       
   189 
       
   190         # 1.3.x: SELECT ST_AsGeoJson("geoapp_city"."point", 8, 1) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Houston';
       
   191         # 1.4.x: SELECT ST_AsGeoJson("geoapp_city"."point", 8, 2) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Houston';
       
   192         # This time we want to include the CRS by using the `crs` keyword.
       
   193         self.assertEqual(houston_json, City.objects.geojson(crs=True, model_att='json').get(name='Houston').json)
       
   194 
       
   195         # 1.3.x: SELECT ST_AsGeoJson("geoapp_city"."point", 8, 2) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Victoria';
       
   196         # 1.4.x: SELECT ST_AsGeoJson("geoapp_city"."point", 8, 1) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Houston';
       
   197         # This time we include the bounding box by using the `bbox` keyword.
       
   198         self.assertEqual(victoria_json, City.objects.geojson(bbox=True).get(name='Victoria').geojson)
       
   199 
       
   200         # 1.(3|4).x: SELECT ST_AsGeoJson("geoapp_city"."point", 5, 3) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Chicago';
       
   201         # Finally, we set every available keyword.
       
   202         self.assertEqual(chicago_json, City.objects.geojson(bbox=True, crs=True, precision=5).get(name='Chicago').geojson)
       
   203         
       
   204     @no_oracle
       
   205     def test03d_svg(self):
       
   206         "Testing SVG output using GeoQuerySet.svg()."
       
   207         if DISABLE: return
       
   208         self.assertRaises(TypeError, City.objects.svg, precision='foo')
       
   209         # SELECT AsSVG(geoapp_city.point, 0, 8) FROM geoapp_city WHERE name = 'Pueblo';
       
   210         svg1 = 'cx="-104.609252" cy="-38.255001"'
       
   211         # Even though relative, only one point so it's practically the same except for
       
   212         # the 'c' letter prefix on the x,y values.
       
   213         svg2 = svg1.replace('c', '')
       
   214         self.assertEqual(svg1, City.objects.svg().get(name='Pueblo').svg)
       
   215         self.assertEqual(svg2, City.objects.svg(relative=5).get(name='Pueblo').svg)
       
   216 
       
   217     def test04_transform(self):
       
   218         "Testing the transform() GeoManager method."
       
   219         if DISABLE: return
       
   220         # Pre-transformed points for Houston and Pueblo.
       
   221         htown = fromstr('POINT(1947516.83115183 6322297.06040572)', srid=3084)
       
   222         ptown = fromstr('POINT(992363.390841912 481455.395105533)', srid=2774)
       
   223         prec = 3 # Precision is low due to version variations in PROJ and GDAL.
       
   224 
       
   225         # Asserting the result of the transform operation with the values in
       
   226         #  the pre-transformed points.  Oracle does not have the 3084 SRID.
       
   227         if not SpatialBackend.oracle:
       
   228             h = City.objects.transform(htown.srid).get(name='Houston')
       
   229             self.assertEqual(3084, h.point.srid)
       
   230             self.assertAlmostEqual(htown.x, h.point.x, prec)
       
   231             self.assertAlmostEqual(htown.y, h.point.y, prec)
       
   232 
       
   233         p1 = City.objects.transform(ptown.srid, field_name='point').get(name='Pueblo')
       
   234         p2 = City.objects.transform(srid=ptown.srid).get(name='Pueblo')
       
   235         for p in [p1, p2]:
       
   236             self.assertEqual(2774, p.point.srid)
       
   237             self.assertAlmostEqual(ptown.x, p.point.x, prec)
       
   238             self.assertAlmostEqual(ptown.y, p.point.y, prec)
       
   239 
       
   240     @no_spatialite # SpatiaLite does not have an Extent function
       
   241     def test05_extent(self):
       
   242         "Testing the `extent` GeoQuerySet method."
       
   243         if DISABLE: return
       
   244         # Reference query:
       
   245         # `SELECT ST_extent(point) FROM geoapp_city WHERE (name='Houston' or name='Dallas');`
       
   246         #   =>  BOX(-96.8016128540039 29.7633724212646,-95.3631439208984 32.7820587158203)
       
   247         expected = (-96.8016128540039, 29.7633724212646, -95.3631439208984, 32.782058715820)
       
   248 
       
   249         qs = City.objects.filter(name__in=('Houston', 'Dallas'))
       
   250         extent = qs.extent()
       
   251 
       
   252         for val, exp in zip(extent, expected):
       
   253             self.assertAlmostEqual(exp, val, 4)
       
   254 
       
   255     @no_oracle
       
   256     @no_spatialite # SpatiaLite does not have a MakeLine function
       
   257     def test06_make_line(self):
       
   258         "Testing the `make_line` GeoQuerySet method."
       
   259         if DISABLE: return
       
   260         # Ensuring that a `TypeError` is raised on models without PointFields.
       
   261         self.assertRaises(TypeError, State.objects.make_line)
       
   262         self.assertRaises(TypeError, Country.objects.make_line)
       
   263         # Reference query:
       
   264         # SELECT AsText(ST_MakeLine(geoapp_city.point)) FROM geoapp_city;
       
   265         ref_line = GEOSGeometry('LINESTRING(-95.363151 29.763374,-96.801611 32.782057,-97.521157 34.464642,174.783117 -41.315268,-104.609252 38.255001,-95.23506 38.971823,-87.650175 41.850385,-123.305196 48.462611)', srid=4326)
       
   266         self.assertEqual(ref_line, City.objects.make_line())
       
   267 
       
   268     def test09_disjoint(self):
       
   269         "Testing the `disjoint` lookup type."
       
   270         if DISABLE: return
       
   271         ptown = City.objects.get(name='Pueblo')
       
   272         qs1 = City.objects.filter(point__disjoint=ptown.point)
       
   273         self.assertEqual(7, qs1.count())
       
   274 
       
   275         if not (SpatialBackend.postgis or SpatialBackend.spatialite):
       
   276             # TODO: Do NULL columns bork queries on PostGIS?  The following
       
   277             # error is encountered:
       
   278             #  psycopg2.ProgrammingError: invalid memory alloc request size 4294957297
       
   279             #
       
   280             # Similarly, on SpatiaLite Puerto Rico is also returned (could be a
       
   281             # manifestation of
       
   282             qs2 = State.objects.filter(poly__disjoint=ptown.point)
       
   283             self.assertEqual(1, qs2.count())
       
   284             self.assertEqual('Kansas', qs2[0].name)
       
   285 
       
   286     def test10_contains_contained(self):
       
   287         "Testing the 'contained', 'contains', and 'bbcontains' lookup types."
       
   288         if DISABLE: return
       
   289         # Getting Texas, yes we were a country -- once ;)
       
   290         texas = Country.objects.get(name='Texas')
       
   291 
       
   292         # Seeing what cities are in Texas, should get Houston and Dallas,
       
   293         #  and Oklahoma City because 'contained' only checks on the
       
   294         #  _bounding box_ of the Geometries.
       
   295         if not SpatialBackend.oracle:
       
   296             qs = City.objects.filter(point__contained=texas.mpoly)
       
   297             self.assertEqual(3, qs.count())
       
   298             cities = ['Houston', 'Dallas', 'Oklahoma City']
       
   299             for c in qs: self.assertEqual(True, c.name in cities)
       
   300 
       
   301         # Pulling out some cities.
       
   302         houston = City.objects.get(name='Houston')
       
   303         wellington = City.objects.get(name='Wellington')
       
   304         pueblo = City.objects.get(name='Pueblo')
       
   305         okcity = City.objects.get(name='Oklahoma City')
       
   306         lawrence = City.objects.get(name='Lawrence')
       
   307 
       
   308         # Now testing contains on the countries using the points for
       
   309         #  Houston and Wellington.
       
   310         tx = Country.objects.get(mpoly__contains=houston.point) # Query w/GEOSGeometry
       
   311         nz = Country.objects.get(mpoly__contains=wellington.point.hex) # Query w/EWKBHEX
       
   312         self.assertEqual('Texas', tx.name)
       
   313         self.assertEqual('New Zealand', nz.name)
       
   314 
       
   315         # Spatialite 2.3 thinks that Lawrence is in Puerto Rico (a NULL geometry).
       
   316         if not SpatialBackend.spatialite:
       
   317             ks = State.objects.get(poly__contains=lawrence.point)
       
   318             self.assertEqual('Kansas', ks.name)
       
   319 
       
   320         # Pueblo and Oklahoma City (even though OK City is within the bounding box of Texas)
       
   321         #  are not contained in Texas or New Zealand.
       
   322         self.assertEqual(0, len(Country.objects.filter(mpoly__contains=pueblo.point))) # Query w/GEOSGeometry object
       
   323         self.assertEqual(0, len(Country.objects.filter(mpoly__contains=okcity.point.wkt))) # Qeury w/WKT
       
   324 
       
   325         # OK City is contained w/in bounding box of Texas.
       
   326         if not SpatialBackend.oracle:
       
   327             qs = Country.objects.filter(mpoly__bbcontains=okcity.point)
       
   328             self.assertEqual(1, len(qs))
       
   329             self.assertEqual('Texas', qs[0].name)
       
   330 
       
   331     def test11_lookup_insert_transform(self):
       
   332         "Testing automatic transform for lookups and inserts."
       
   333         if DISABLE: return
       
   334         # San Antonio in 'WGS84' (SRID 4326)
       
   335         sa_4326 = 'POINT (-98.493183 29.424170)'
       
   336         wgs_pnt = fromstr(sa_4326, srid=4326) # Our reference point in WGS84
       
   337 
       
   338         # Oracle doesn't have SRID 3084, using 41157.
       
   339         if SpatialBackend.oracle:
       
   340             # San Antonio in 'Texas 4205, Southern Zone (1983, meters)' (SRID 41157)
       
   341             # Used the following Oracle SQL to get this value:
       
   342             #  SELECT SDO_UTIL.TO_WKTGEOMETRY(SDO_CS.TRANSFORM(SDO_GEOMETRY('POINT (-98.493183 29.424170)', 4326), 41157)) FROM DUAL;
       
   343             nad_wkt  = 'POINT (300662.034646583 5416427.45974934)'
       
   344             nad_srid = 41157
       
   345         else:
       
   346             # San Antonio in 'NAD83(HARN) / Texas Centric Lambert Conformal' (SRID 3084)
       
   347             nad_wkt = 'POINT (1645978.362408288754523 6276356.025927528738976)' # Used ogr.py in gdal 1.4.1 for this transform
       
   348             nad_srid = 3084
       
   349 
       
   350         # Constructing & querying with a point from a different SRID. Oracle
       
   351         # `SDO_OVERLAPBDYINTERSECT` operates differently from
       
   352         # `ST_Intersects`, so contains is used instead.
       
   353         nad_pnt = fromstr(nad_wkt, srid=nad_srid)
       
   354         if SpatialBackend.oracle:
       
   355             tx = Country.objects.get(mpoly__contains=nad_pnt)
       
   356         else:
       
   357             tx = Country.objects.get(mpoly__intersects=nad_pnt)
       
   358         self.assertEqual('Texas', tx.name)
       
   359 
       
   360         # Creating San Antonio.  Remember the Alamo.
       
   361         sa = City(name='San Antonio', point=nad_pnt)
       
   362         sa.save()
       
   363 
       
   364         # Now verifying that San Antonio was transformed correctly
       
   365         sa = City.objects.get(name='San Antonio')
       
   366         self.assertAlmostEqual(wgs_pnt.x, sa.point.x, 6)
       
   367         self.assertAlmostEqual(wgs_pnt.y, sa.point.y, 6)
       
   368 
       
   369         # If the GeometryField SRID is -1, then we shouldn't perform any
       
   370         # transformation if the SRID of the input geometry is different.
       
   371         # SpatiaLite does not support missing SRID values.
       
   372         if not SpatialBackend.spatialite:
       
   373             m1 = MinusOneSRID(geom=Point(17, 23, srid=4326))
       
   374             m1.save()
       
   375             self.assertEqual(-1, m1.geom.srid)
       
   376 
       
   377     def test12_null_geometries(self):
       
   378         "Testing NULL geometry support, and the `isnull` lookup type."
       
   379         if DISABLE: return
       
   380         # Querying for both NULL and Non-NULL values.
       
   381         nullqs = State.objects.filter(poly__isnull=True)
       
   382         validqs = State.objects.filter(poly__isnull=False)
       
   383 
       
   384         # Puerto Rico should be NULL (it's a commonwealth unincorporated territory)
       
   385         self.assertEqual(1, len(nullqs))
       
   386         self.assertEqual('Puerto Rico', nullqs[0].name)
       
   387 
       
   388         # The valid states should be Colorado & Kansas
       
   389         self.assertEqual(2, len(validqs))
       
   390         state_names = [s.name for s in validqs]
       
   391         self.assertEqual(True, 'Colorado' in state_names)
       
   392         self.assertEqual(True, 'Kansas' in state_names)
       
   393 
       
   394         # Saving another commonwealth w/a NULL geometry.
       
   395         nmi = State.objects.create(name='Northern Mariana Islands', poly=None)
       
   396         self.assertEqual(nmi.poly, None)
       
   397 
       
   398         # Assigning a geomery and saving -- then UPDATE back to NULL.
       
   399         nmi.poly = 'POLYGON((0 0,1 0,1 1,1 0,0 0))'
       
   400         nmi.save()
       
   401         State.objects.filter(name='Northern Mariana Islands').update(poly=None)
       
   402         self.assertEqual(None, State.objects.get(name='Northern Mariana Islands').poly)
       
   403 
       
   404     @no_oracle # No specific `left` or `right` operators in Oracle.
       
   405     @no_spatialite # No `left` or `right` operators in SpatiaLite.
       
   406     def test13_left_right(self):
       
   407         "Testing the 'left' and 'right' lookup types."
       
   408         if DISABLE: return
       
   409         # Left: A << B => true if xmax(A) < xmin(B)
       
   410         # Right: A >> B => true if xmin(A) > xmax(B)
       
   411         #  See: BOX2D_left() and BOX2D_right() in lwgeom_box2dfloat4.c in PostGIS source.
       
   412 
       
   413         # Getting the borders for Colorado & Kansas
       
   414         co_border = State.objects.get(name='Colorado').poly
       
   415         ks_border = State.objects.get(name='Kansas').poly
       
   416 
       
   417         # Note: Wellington has an 'X' value of 174, so it will not be considered
       
   418         #  to the left of CO.
       
   419 
       
   420         # These cities should be strictly to the right of the CO border.
       
   421         cities = ['Houston', 'Dallas', 'San Antonio', 'Oklahoma City',
       
   422                   'Lawrence', 'Chicago', 'Wellington']
       
   423         qs = City.objects.filter(point__right=co_border)
       
   424         self.assertEqual(7, len(qs))
       
   425         for c in qs: self.assertEqual(True, c.name in cities)
       
   426 
       
   427         # These cities should be strictly to the right of the KS border.
       
   428         cities = ['Chicago', 'Wellington']
       
   429         qs = City.objects.filter(point__right=ks_border)
       
   430         self.assertEqual(2, len(qs))
       
   431         for c in qs: self.assertEqual(True, c.name in cities)
       
   432 
       
   433         # Note: Wellington has an 'X' value of 174, so it will not be considered
       
   434         #  to the left of CO.
       
   435         vic = City.objects.get(point__left=co_border)
       
   436         self.assertEqual('Victoria', vic.name)
       
   437 
       
   438         cities = ['Pueblo', 'Victoria']
       
   439         qs = City.objects.filter(point__left=ks_border)
       
   440         self.assertEqual(2, len(qs))
       
   441         for c in qs: self.assertEqual(True, c.name in cities)
       
   442 
       
   443     def test14_equals(self):
       
   444         "Testing the 'same_as' and 'equals' lookup types."
       
   445         if DISABLE: return
       
   446         pnt = fromstr('POINT (-95.363151 29.763374)', srid=4326)
       
   447         c1 = City.objects.get(point=pnt)
       
   448         c2 = City.objects.get(point__same_as=pnt)
       
   449         c3 = City.objects.get(point__equals=pnt)
       
   450         for c in [c1, c2, c3]: self.assertEqual('Houston', c.name)
       
   451 
       
   452     def test15_relate(self):
       
   453         "Testing the 'relate' lookup type."
       
   454         if DISABLE: return
       
   455         # To make things more interesting, we will have our Texas reference point in
       
   456         # different SRIDs.
       
   457         pnt1 = fromstr('POINT (649287.0363174 4177429.4494686)', srid=2847)
       
   458         pnt2 = fromstr('POINT(-98.4919715741052 29.4333344025053)', srid=4326)
       
   459 
       
   460         # Not passing in a geometry as first param shoud
       
   461         # raise a type error when initializing the GeoQuerySet
       
   462         self.assertRaises(TypeError, Country.objects.filter, mpoly__relate=(23, 'foo'))
       
   463         # Making sure the right exception is raised for the given
       
   464         # bad arguments.
       
   465         for bad_args, e in [((pnt1, 0), TypeError), ((pnt2, 'T*T***FF*', 0), ValueError)]:
       
   466             qs = Country.objects.filter(mpoly__relate=bad_args)
       
   467             self.assertRaises(e, qs.count)
       
   468 
       
   469         # Relate works differently for the different backends.
       
   470         if SpatialBackend.postgis or SpatialBackend.spatialite:
       
   471             contains_mask = 'T*T***FF*'
       
   472             within_mask = 'T*F**F***'
       
   473             intersects_mask = 'T********'
       
   474         elif SpatialBackend.oracle:
       
   475             contains_mask = 'contains'
       
   476             within_mask = 'inside'
       
   477             # TODO: This is not quite the same as the PostGIS mask above
       
   478             intersects_mask = 'overlapbdyintersect'
       
   479 
       
   480         # Testing contains relation mask.
       
   481         self.assertEqual('Texas', Country.objects.get(mpoly__relate=(pnt1, contains_mask)).name)
       
   482         self.assertEqual('Texas', Country.objects.get(mpoly__relate=(pnt2, contains_mask)).name)
       
   483 
       
   484         # Testing within relation mask.
       
   485         ks = State.objects.get(name='Kansas')
       
   486         self.assertEqual('Lawrence', City.objects.get(point__relate=(ks.poly, within_mask)).name)
       
   487 
       
   488         # Testing intersection relation mask.
       
   489         if not SpatialBackend.oracle:
       
   490             self.assertEqual('Texas', Country.objects.get(mpoly__relate=(pnt1, intersects_mask)).name)
       
   491             self.assertEqual('Texas', Country.objects.get(mpoly__relate=(pnt2, intersects_mask)).name)
       
   492             self.assertEqual('Lawrence', City.objects.get(point__relate=(ks.poly, intersects_mask)).name)
       
   493 
       
   494     def test16_createnull(self):
       
   495         "Testing creating a model instance and the geometry being None"
       
   496         if DISABLE: return
       
   497         c = City()
       
   498         self.assertEqual(c.point, None)
       
   499 
       
   500     def test17_unionagg(self):
       
   501         "Testing the `unionagg` (aggregate union) GeoManager method."
       
   502         if DISABLE: return
       
   503         tx = Country.objects.get(name='Texas').mpoly
       
   504         # Houston, Dallas, San Antonio -- Oracle has different order.
       
   505         union1 = fromstr('MULTIPOINT(-98.493183 29.424170,-96.801611 32.782057,-95.363151 29.763374)')
       
   506         union2 = fromstr('MULTIPOINT(-96.801611 32.782057,-95.363151 29.763374,-98.493183 29.424170)')
       
   507         qs = City.objects.filter(point__within=tx)
       
   508         self.assertRaises(TypeError, qs.unionagg, 'name')
       
   509         # Using `field_name` keyword argument in one query and specifying an
       
   510         # order in the other (which should not be used because this is
       
   511         # an aggregate method on a spatial column)
       
   512         u1 = qs.unionagg(field_name='point')
       
   513         u2 = qs.order_by('name').unionagg()
       
   514         tol = 0.00001
       
   515         if SpatialBackend.oracle:
       
   516             union = union2
       
   517         else:
       
   518             union = union1
       
   519         self.assertEqual(True, union.equals_exact(u1, tol))
       
   520         self.assertEqual(True, union.equals_exact(u2, tol))
       
   521         qs = City.objects.filter(name='NotACity')
       
   522         self.assertEqual(None, qs.unionagg(field_name='point'))
       
   523 
       
   524     @no_spatialite # SpatiaLite does not support abstract geometry columns
       
   525     def test18_geometryfield(self):
       
   526         "Testing GeometryField."
       
   527         if DISABLE: return
       
   528         Feature(name='Point', geom=Point(1, 1)).save()
       
   529         Feature(name='LineString', geom=LineString((0, 0), (1, 1), (5, 5))).save()
       
   530         Feature(name='Polygon', geom=Polygon(LinearRing((0, 0), (0, 5), (5, 5), (5, 0), (0, 0)))).save()
       
   531         Feature(name='GeometryCollection',
       
   532                 geom=GeometryCollection(Point(2, 2), LineString((0, 0), (2, 2)),
       
   533                                         Polygon(LinearRing((0, 0), (0, 5), (5, 5), (5, 0), (0, 0))))).save()
       
   534 
       
   535         f_1 = Feature.objects.get(name='Point')
       
   536         self.assertEqual(True, isinstance(f_1.geom, Point))
       
   537         self.assertEqual((1.0, 1.0), f_1.geom.tuple)
       
   538         f_2 = Feature.objects.get(name='LineString')
       
   539         self.assertEqual(True, isinstance(f_2.geom, LineString))
       
   540         self.assertEqual(((0.0, 0.0), (1.0, 1.0), (5.0, 5.0)), f_2.geom.tuple)
       
   541 
       
   542         f_3 = Feature.objects.get(name='Polygon')
       
   543         self.assertEqual(True, isinstance(f_3.geom, Polygon))
       
   544         f_4 = Feature.objects.get(name='GeometryCollection')
       
   545         self.assertEqual(True, isinstance(f_4.geom, GeometryCollection))
       
   546         self.assertEqual(f_3.geom, f_4.geom[2])
       
   547 
       
   548     def test19_centroid(self):
       
   549         "Testing the `centroid` GeoQuerySet method."
       
   550         if DISABLE: return
       
   551         qs = State.objects.exclude(poly__isnull=True).centroid()
       
   552         if SpatialBackend.oracle:
       
   553             tol = 0.1
       
   554         elif SpatialBackend.spatialite:
       
   555             tol = 0.000001
       
   556         else:
       
   557             tol = 0.000000001
       
   558         for s in qs:
       
   559             self.assertEqual(True, s.poly.centroid.equals_exact(s.centroid, tol))
       
   560 
       
   561     def test20_pointonsurface(self):
       
   562         "Testing the `point_on_surface` GeoQuerySet method."
       
   563         if DISABLE: return
       
   564         # Reference values.
       
   565         if SpatialBackend.oracle:
       
   566             # SELECT SDO_UTIL.TO_WKTGEOMETRY(SDO_GEOM.SDO_POINTONSURFACE(GEOAPP_COUNTRY.MPOLY, 0.05)) FROM GEOAPP_COUNTRY;
       
   567             ref = {'New Zealand' : fromstr('POINT (174.616364 -36.100861)', srid=4326),
       
   568                    'Texas' : fromstr('POINT (-103.002434 36.500397)', srid=4326),
       
   569                    }
       
   570 
       
   571         elif SpatialBackend.postgis or SpatialBackend.spatialite:
       
   572             # Using GEOSGeometry to compute the reference point on surface values
       
   573             # -- since PostGIS also uses GEOS these should be the same.
       
   574             ref = {'New Zealand' : Country.objects.get(name='New Zealand').mpoly.point_on_surface,
       
   575                    'Texas' : Country.objects.get(name='Texas').mpoly.point_on_surface
       
   576                    }
       
   577         for cntry in Country.objects.point_on_surface():
       
   578             if SpatialBackend.spatialite:
       
   579                 # XXX This seems to be a WKT-translation-related precision issue?
       
   580                 tol = 0.00001
       
   581             else: tol = 0.000000001
       
   582             self.assertEqual(True, ref[cntry.name].equals_exact(cntry.point_on_surface, tol))
       
   583 
       
   584     @no_oracle
       
   585     def test21_scale(self):
       
   586         "Testing the `scale` GeoQuerySet method."
       
   587         if DISABLE: return
       
   588         xfac, yfac = 2, 3
       
   589         qs = Country.objects.scale(xfac, yfac, model_att='scaled')
       
   590         for c in qs:
       
   591             for p1, p2 in zip(c.mpoly, c.scaled):
       
   592                 for r1, r2 in zip(p1, p2):
       
   593                     for c1, c2 in zip(r1.coords, r2.coords):
       
   594                         # XXX The low precision is for SpatiaLite
       
   595                         self.assertAlmostEqual(c1[0] * xfac, c2[0], 5)
       
   596                         self.assertAlmostEqual(c1[1] * yfac, c2[1], 5)
       
   597 
       
   598     @no_oracle
       
   599     def test22_translate(self):
       
   600         "Testing the `translate` GeoQuerySet method."
       
   601         if DISABLE: return
       
   602         xfac, yfac = 5, -23
       
   603         qs = Country.objects.translate(xfac, yfac, model_att='translated')
       
   604         for c in qs:
       
   605             for p1, p2 in zip(c.mpoly, c.translated):
       
   606                 for r1, r2 in zip(p1, p2):
       
   607                     for c1, c2 in zip(r1.coords, r2.coords):
       
   608                         # XXX The low precision is for SpatiaLite
       
   609                         self.assertAlmostEqual(c1[0] + xfac, c2[0], 5)
       
   610                         self.assertAlmostEqual(c1[1] + yfac, c2[1], 5)
       
   611 
       
   612     def test23_numgeom(self):
       
   613         "Testing the `num_geom` GeoQuerySet method."
       
   614         if DISABLE: return
       
   615         # Both 'countries' only have two geometries.
       
   616         for c in Country.objects.num_geom(): self.assertEqual(2, c.num_geom)
       
   617         for c in City.objects.filter(point__isnull=False).num_geom():
       
   618             # Oracle will return 1 for the number of geometries on non-collections,
       
   619             # whereas PostGIS will return None.
       
   620             if SpatialBackend.postgis: self.assertEqual(None, c.num_geom)
       
   621             else: self.assertEqual(1, c.num_geom)
       
   622 
       
   623     @no_spatialite # SpatiaLite can only count vertices in LineStrings
       
   624     def test24_numpoints(self):
       
   625         "Testing the `num_points` GeoQuerySet method."
       
   626         if DISABLE: return
       
   627         for c in Country.objects.num_points():
       
   628             self.assertEqual(c.mpoly.num_points, c.num_points)
       
   629         if not SpatialBackend.oracle:
       
   630             # Oracle cannot count vertices in Point geometries.
       
   631             for c in City.objects.num_points(): self.assertEqual(1, c.num_points)
       
   632 
       
   633     def test25_geoset(self):
       
   634         "Testing the `difference`, `intersection`, `sym_difference`, and `union` GeoQuerySet methods."
       
   635         if DISABLE: return
       
   636         geom = Point(5, 23)
       
   637         tol = 1
       
   638         qs = Country.objects.all().difference(geom).sym_difference(geom).union(geom)
       
   639 
       
   640         # XXX For some reason SpatiaLite does something screwey with the Texas geometry here.  Also,
       
   641         # XXX it doesn't like the null intersection.
       
   642         if SpatialBackend.spatialite:
       
   643             qs = qs.exclude(name='Texas')
       
   644         else:
       
   645             qs = qs.intersection(geom)
       
   646         
       
   647         for c in qs:
       
   648             if SpatialBackend.oracle:
       
   649                 # Should be able to execute the queries; however, they won't be the same
       
   650                 # as GEOS (because Oracle doesn't use GEOS internally like PostGIS or
       
   651                 # SpatiaLite).
       
   652                 pass
       
   653             else:
       
   654                 self.assertEqual(c.mpoly.difference(geom), c.difference)
       
   655                 if not SpatialBackend.spatialite:
       
   656                     self.assertEqual(c.mpoly.intersection(geom), c.intersection)
       
   657                 self.assertEqual(c.mpoly.sym_difference(geom), c.sym_difference)
       
   658                 self.assertEqual(c.mpoly.union(geom), c.union)
       
   659 
       
   660     def test26_inherited_geofields(self):
       
   661         "Test GeoQuerySet methods on inherited Geometry fields."
       
   662         if DISABLE: return
       
   663         # Creating a Pennsylvanian city.
       
   664         mansfield = PennsylvaniaCity.objects.create(name='Mansfield', county='Tioga', point='POINT(-77.071445 41.823881)')
       
   665 
       
   666         # All transformation SQL will need to be performed on the
       
   667         # _parent_ table.
       
   668         qs = PennsylvaniaCity.objects.transform(32128)
       
   669 
       
   670         self.assertEqual(1, qs.count())
       
   671         for pc in qs: self.assertEqual(32128, pc.point.srid)
       
   672         
       
   673     @no_spatialite
       
   674     @no_oracle
       
   675     def test27_snap_to_grid(self):
       
   676         "Testing GeoQuerySet.snap_to_grid()."
       
   677         if DISABLE: return
       
   678 
       
   679         # Let's try and break snap_to_grid() with bad combinations of arguments.
       
   680         for bad_args in ((), range(3), range(5)):
       
   681             self.assertRaises(ValueError, Country.objects.snap_to_grid, *bad_args)
       
   682         for bad_args in (('1.0',), (1.0, None), tuple(map(unicode, range(4)))):
       
   683             self.assertRaises(TypeError, Country.objects.snap_to_grid, *bad_args)
       
   684 
       
   685         # Boundary for San Marino, courtesy of Bjorn Sandvik of thematicmapping.org
       
   686         # from the world borders dataset he provides.
       
   687         wkt = ('MULTIPOLYGON(((12.41580 43.95795,12.45055 43.97972,12.45389 43.98167,'
       
   688                '12.46250 43.98472,12.47167 43.98694,12.49278 43.98917,'
       
   689                '12.50555 43.98861,12.51000 43.98694,12.51028 43.98277,'
       
   690                '12.51167 43.94333,12.51056 43.93916,12.49639 43.92333,'
       
   691                '12.49500 43.91472,12.48778 43.90583,12.47444 43.89722,'
       
   692                '12.46472 43.89555,12.45917 43.89611,12.41639 43.90472,'
       
   693                '12.41222 43.90610,12.40782 43.91366,12.40389 43.92667,'
       
   694                '12.40500 43.94833,12.40889 43.95499,12.41580 43.95795)))')
       
   695         sm = Country.objects.create(name='San Marino', mpoly=fromstr(wkt))
       
   696 
       
   697         # Because floating-point arithmitic isn't exact, we set a tolerance
       
   698         # to pass into GEOS `equals_exact`.
       
   699         tol = 0.000000001
       
   700 
       
   701         # SELECT AsText(ST_SnapToGrid("geoapp_country"."mpoly", 0.1)) FROM "geoapp_country" WHERE "geoapp_country"."name" = 'San Marino';
       
   702         ref = fromstr('MULTIPOLYGON(((12.4 44,12.5 44,12.5 43.9,12.4 43.9,12.4 44)))')
       
   703         self.failUnless(ref.equals_exact(Country.objects.snap_to_grid(0.1).get(name='San Marino').snap_to_grid, tol))
       
   704 
       
   705         # SELECT AsText(ST_SnapToGrid("geoapp_country"."mpoly", 0.05, 0.23)) FROM "geoapp_country" WHERE "geoapp_country"."name" = 'San Marino';
       
   706         ref = fromstr('MULTIPOLYGON(((12.4 43.93,12.45 43.93,12.5 43.93,12.45 43.93,12.4 43.93)))')
       
   707         self.failUnless(ref.equals_exact(Country.objects.snap_to_grid(0.05, 0.23).get(name='San Marino').snap_to_grid, tol))
       
   708 
       
   709         # SELECT AsText(ST_SnapToGrid("geoapp_country"."mpoly", 0.5, 0.17, 0.05, 0.23)) FROM "geoapp_country" WHERE "geoapp_country"."name" = 'San Marino';
       
   710         ref = fromstr('MULTIPOLYGON(((12.4 43.87,12.45 43.87,12.45 44.1,12.5 44.1,12.5 43.87,12.45 43.87,12.4 43.87)))')
       
   711         self.failUnless(ref.equals_exact(Country.objects.snap_to_grid(0.05, 0.23, 0.5, 0.17).get(name='San Marino').snap_to_grid, tol))
       
   712 
       
   713 from test_feeds import GeoFeedTest
       
   714 from test_regress import GeoRegressionTests
       
   715 from test_sitemaps import GeoSitemapTest
       
   716 
       
   717 def suite():
       
   718     s = unittest.TestSuite()
       
   719     s.addTest(unittest.makeSuite(GeoModelTest))
       
   720     s.addTest(unittest.makeSuite(GeoFeedTest))
       
   721     s.addTest(unittest.makeSuite(GeoSitemapTest))
       
   722     s.addTest(unittest.makeSuite(GeoRegressionTests))
       
   723     return s