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