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