Removed some more hardcoded backends in GIS tests

Refs #22632. Thanks Tim Graham for the review.
This commit is contained in:
Claude Paroz 2014-08-21 18:47:57 +02:00
parent 5675eb371f
commit 60428ed5db
15 changed files with 117 additions and 112 deletions

View File

@ -16,6 +16,9 @@ class BaseSpatialFeatures(object):
# Does the database contain a SpatialRefSys model to store SRID information? # Does the database contain a SpatialRefSys model to store SRID information?
has_spatialrefsys_table = True has_spatialrefsys_table = True
# Reference implementation of 3D functions is:
# http://postgis.net/docs/PostGIS_Special_Functions_Index.html#PostGIS_3D_Functions
supports_3d_functions = False
# Does the database support SRID transform operations? # Does the database support SRID transform operations?
supports_transform = True supports_transform = True
# Do geometric relationship operations operate on real shapes (or only on bounding boxes)? # Do geometric relationship operations operate on real shapes (or only on bounding boxes)?
@ -29,24 +32,34 @@ class BaseSpatialFeatures(object):
# The following properties indicate if the database backend support # The following properties indicate if the database backend support
# certain lookups (dwithin, left and right, relate, ...) # certain lookups (dwithin, left and right, relate, ...)
supports_distances_lookups = True
supports_left_right_lookups = False supports_left_right_lookups = False
@property @property
def supports_relate_lookup(self): def supports_bbcontains_lookup(self):
return 'relate' in self.connection.ops.geometry_functions return 'bbcontains' in self.connection.ops.gis_terms
@property @property
def has_dwithin_lookup(self): def supports_contained_lookup(self):
return 'contained' in self.connection.ops.gis_terms
@property
def supports_dwithin_lookup(self):
return 'dwithin' in self.connection.ops.distance_functions return 'dwithin' in self.connection.ops.distance_functions
@property
def supports_relate_lookup(self):
return 'relate' in self.connection.ops.gis_terms
# For each of those methods, the class will have a property named # For each of those methods, the class will have a property named
# `has_<name>_method` (defined in __init__) which accesses connection.ops # `has_<name>_method` (defined in __init__) which accesses connection.ops
# to determine GIS method availability. # to determine GIS method availability.
geoqueryset_methods = ( geoqueryset_methods = (
'centroid', 'difference', 'envelope', 'force_rhr', 'geohash', 'gml', 'area', 'centroid', 'difference', 'distance', 'distance_spheroid',
'intersection', 'kml', 'num_geom', 'perimeter', 'point_on_surface', 'envelope', 'force_rhr', 'geohash', 'gml', 'intersection', 'kml',
'reverse', 'scale', 'snap_to_grid', 'svg', 'sym_difference', 'length', 'num_geom', 'perimeter', 'point_on_surface', 'reverse',
'transform', 'translate', 'union', 'unionagg', 'scale', 'snap_to_grid', 'svg', 'sym_difference', 'transform',
'translate', 'union', 'unionagg',
) )
# Specifies whether the Collect and Extent aggregates are supported by the database # Specifies whether the Collect and Extent aggregates are supported by the database

View File

@ -10,6 +10,7 @@ from django.contrib.gis.db.backends.mysql.operations import MySQLOperations
class DatabaseFeatures(BaseSpatialFeatures, MySQLDatabaseFeatures): class DatabaseFeatures(BaseSpatialFeatures, MySQLDatabaseFeatures):
has_spatialrefsys_table = False has_spatialrefsys_table = False
supports_distances_lookups = False
supports_transform = False supports_transform = False
supports_real_shape_operations = False supports_real_shape_operations = False
supports_null_geometries = False supports_null_geometries = False

View File

@ -11,6 +11,7 @@ from django.contrib.gis.db.backends.postgis.schema import PostGISSchemaEditor
class DatabaseFeatures(BaseSpatialFeatures, Psycopg2DatabaseFeatures): class DatabaseFeatures(BaseSpatialFeatures, Psycopg2DatabaseFeatures):
supports_3d_functions = True
supports_left_right_lookups = True supports_left_right_lookups = True

View File

@ -631,8 +631,8 @@ class GeoQuerySet(QuerySet):
u, unit_name, s = get_srid_info(self.query.transformed_srid, connection) u, unit_name, s = get_srid_info(self.query.transformed_srid, connection)
geodetic = unit_name.lower() in geo_field.geodetic_units geodetic = unit_name.lower() in geo_field.geodetic_units
if backend.spatialite and geodetic: if geodetic and not connection.features.supports_distance_geodetic:
raise ValueError('SQLite does not support linear distance calculations on geodetic coordinate systems.') raise ValueError('This database does not support linear distance calculations on geodetic coordinate systems.')
if distance: if distance:
if self.query.transformed_srid: if self.query.transformed_srid:
@ -690,8 +690,8 @@ class GeoQuerySet(QuerySet):
# works on 3D geometries. # works on 3D geometries.
procedure_fmt += ",'%(spheroid)s'" procedure_fmt += ",'%(spheroid)s'"
procedure_args.update({'function': backend.length_spheroid, 'spheroid': params[1]}) procedure_args.update({'function': backend.length_spheroid, 'spheroid': params[1]})
elif geom_3d and backend.postgis: elif geom_3d and connection.features.supports_3d_functions:
# Use 3D variants of perimeter and length routines on PostGIS. # Use 3D variants of perimeter and length routines on supported backends.
if perimeter: if perimeter:
procedure_args.update({'function': backend.perimeter3d}) procedure_args.update({'function': backend.perimeter3d})
elif length: elif length:

View File

@ -1,4 +1,5 @@
from django.contrib.gis.db import models from django.contrib.gis.db import models
from django.contrib.gis.tests.utils import gisfield_may_be_null
from django.utils.encoding import python_2_unicode_compatible from django.utils.encoding import python_2_unicode_compatible
@ -38,7 +39,7 @@ class CensusZipcode(NamedModel):
class SouthTexasZipcode(NamedModel): class SouthTexasZipcode(NamedModel):
"Model for a few South Texas ZIP codes." "Model for a few South Texas ZIP codes."
poly = models.PolygonField(srid=32140, null=True) poly = models.PolygonField(srid=32140, null=gisfield_may_be_null)
class Interstate(NamedModel): class Interstate(NamedModel):

View File

@ -6,9 +6,7 @@ from django.db import connection
from django.db.models import Q from django.db.models import Q
from django.contrib.gis.geos import HAS_GEOS from django.contrib.gis.geos import HAS_GEOS
from django.contrib.gis.measure import D # alias for Distance from django.contrib.gis.measure import D # alias for Distance
from django.contrib.gis.tests.utils import ( from django.contrib.gis.tests.utils import oracle, postgis, spatialite, no_oracle
mysql, oracle, postgis, spatialite, no_oracle
)
from django.test import TestCase, skipUnlessDBFeature from django.test import TestCase, skipUnlessDBFeature
if HAS_GEOS: if HAS_GEOS:
@ -18,8 +16,6 @@ if HAS_GEOS:
SouthTexasCity, SouthTexasCityFt, CensusZipcode, SouthTexasZipcode) SouthTexasCity, SouthTexasCityFt, CensusZipcode, SouthTexasZipcode)
@skipUnless(HAS_GEOS and not mysql,
"GEOS and spatial db (not mysql) are required.")
@skipUnlessDBFeature("gis_enabled") @skipUnlessDBFeature("gis_enabled")
class DistanceTest(TestCase): class DistanceTest(TestCase):
fixtures = ['initial'] fixtures = ['initial']
@ -50,7 +46,7 @@ class DistanceTest(TestCase):
self.assertEqual(1, Interstate.objects.count()) self.assertEqual(1, Interstate.objects.count())
self.assertEqual(1, SouthTexasInterstate.objects.count()) self.assertEqual(1, SouthTexasInterstate.objects.count())
@skipUnlessDBFeature("has_dwithin_lookup") @skipUnlessDBFeature("supports_dwithin_lookup")
def test_dwithin(self): def test_dwithin(self):
""" """
Test the `dwithin` lookup type. Test the `dwithin` lookup type.
@ -99,6 +95,7 @@ class DistanceTest(TestCase):
else: else:
self.assertListEqual(au_cities, self.get_names(qs.filter(point__dwithin=(self.au_pnt, dist)))) self.assertListEqual(au_cities, self.get_names(qs.filter(point__dwithin=(self.au_pnt, dist))))
@skipUnlessDBFeature("has_distance_method")
def test_distance_projected(self): def test_distance_projected(self):
""" """
Test the `distance` GeoQuerySet method on projected coordinate systems. Test the `distance` GeoQuerySet method on projected coordinate systems.
@ -139,7 +136,7 @@ class DistanceTest(TestCase):
self.assertAlmostEqual(m_distances[i], c.distance.m, tol) self.assertAlmostEqual(m_distances[i], c.distance.m, tol)
self.assertAlmostEqual(ft_distances[i], c.distance.survey_ft, tol) self.assertAlmostEqual(ft_distances[i], c.distance.survey_ft, tol)
@skipUnlessDBFeature("supports_distance_geodetic") @skipUnlessDBFeature("has_distance_method", "supports_distance_geodetic")
def test_distance_geodetic(self): def test_distance_geodetic(self):
""" """
Test the `distance` GeoQuerySet method on geodetic coordinate systems. Test the `distance` GeoQuerySet method on geodetic coordinate systems.
@ -149,16 +146,16 @@ class DistanceTest(TestCase):
# Testing geodetic distance calculation with a non-point geometry # Testing geodetic distance calculation with a non-point geometry
# (a LineString of Wollongong and Shellharbour coords). # (a LineString of Wollongong and Shellharbour coords).
ls = LineString(((150.902, -34.4245), (150.87, -34.5789))) ls = LineString(((150.902, -34.4245), (150.87, -34.5789)))
if oracle or postgis:
# Reference query: # Reference query:
# SELECT ST_distance_sphere(point, ST_GeomFromText('LINESTRING(150.9020 -34.4245,150.8700 -34.5789)', 4326)) FROM distapp_australiacity ORDER BY name; # SELECT ST_distance_sphere(point, ST_GeomFromText('LINESTRING(150.9020 -34.4245,150.8700 -34.5789)', 4326)) FROM distapp_australiacity ORDER BY name;
distances = [1120954.92533513, 140575.720018241, 640396.662906304, distances = [1120954.92533513, 140575.720018241, 640396.662906304,
60580.9693849269, 972807.955955075, 568451.8357838, 60580.9693849269, 972807.955955075, 568451.8357838,
40435.4335201384, 0, 68272.3896586844, 12375.0643697706, 0] 40435.4335201384, 0, 68272.3896586844, 12375.0643697706, 0]
qs = AustraliaCity.objects.distance(ls).order_by('name') qs = AustraliaCity.objects.distance(ls).order_by('name')
for city, distance in zip(qs, distances): for city, distance in zip(qs, distances):
# Testing equivalence to within a meter. # Testing equivalence to within a meter.
self.assertAlmostEqual(distance, city.distance.m, 0) self.assertAlmostEqual(distance, city.distance.m, 0)
# Got the reference distances using the raw SQL statements: # Got the reference distances using the raw SQL statements:
# SELECT ST_distance_spheroid(point, ST_GeomFromText('POINT(151.231341 -33.952685)', 4326), 'SPHEROID["WGS 84",6378137.0,298.257223563]') FROM distapp_australiacity WHERE (NOT (id = 11)); # SELECT ST_distance_spheroid(point, ST_GeomFromText('POINT(151.231341 -33.952685)', 4326), 'SPHEROID["WGS 84",6378137.0,298.257223563]') FROM distapp_australiacity WHERE (NOT (id = 11));
@ -197,6 +194,7 @@ class DistanceTest(TestCase):
self.assertAlmostEqual(sphere_distances[i], c.distance.m, tol) self.assertAlmostEqual(sphere_distances[i], c.distance.m, tol)
@no_oracle # Oracle already handles geographic distance calculation. @no_oracle # Oracle already handles geographic distance calculation.
@skipUnlessDBFeature("has_distance_method")
def test_distance_transform(self): def test_distance_transform(self):
""" """
Test the `distance` GeoQuerySet method used with `transform` on a geographic field. Test the `distance` GeoQuerySet method used with `transform` on a geographic field.
@ -226,6 +224,7 @@ class DistanceTest(TestCase):
for i, z in enumerate(qs): for i, z in enumerate(qs):
self.assertAlmostEqual(z.distance.m, dists_m[i], 5) self.assertAlmostEqual(z.distance.m, dists_m[i], 5)
@skipUnlessDBFeature("supports_distances_lookups")
def test_distance_lookups(self): def test_distance_lookups(self):
""" """
Test the `distance_lt`, `distance_gt`, `distance_lte`, and `distance_gte` lookup types. Test the `distance_lt`, `distance_gt`, `distance_lte`, and `distance_gte` lookup types.
@ -255,6 +254,7 @@ class DistanceTest(TestCase):
qs = SouthTexasZipcode.objects.exclude(name='77005').filter(poly__distance_lte=(z.poly, D(m=300))) qs = SouthTexasZipcode.objects.exclude(name='77005').filter(poly__distance_lte=(z.poly, D(m=300)))
self.assertEqual(['77002', '77025', '77401'], self.get_names(qs)) self.assertEqual(['77002', '77025', '77401'], self.get_names(qs))
@skipUnlessDBFeature("supports_distances_lookups", "supports_distance_geodetic")
def test_geodetic_distance_lookups(self): def test_geodetic_distance_lookups(self):
""" """
Test distance lookups on geodetic coordinate systems. Test distance lookups on geodetic coordinate systems.
@ -264,23 +264,11 @@ class DistanceTest(TestCase):
line = GEOSGeometry('LINESTRING(144.9630 -37.8143,151.2607 -33.8870)', 4326) line = GEOSGeometry('LINESTRING(144.9630 -37.8143,151.2607 -33.8870)', 4326)
dist_qs = AustraliaCity.objects.filter(point__distance_lte=(line, D(km=100))) dist_qs = AustraliaCity.objects.filter(point__distance_lte=(line, D(km=100)))
if oracle or postgis: self.assertEqual(9, dist_qs.count())
# Oracle and PostGIS can do distance lookups on arbitrary geometries. self.assertEqual(['Batemans Bay', 'Canberra', 'Hillsdale',
self.assertEqual(9, dist_qs.count()) 'Melbourne', 'Mittagong', 'Shellharbour',
self.assertEqual(['Batemans Bay', 'Canberra', 'Hillsdale', 'Sydney', 'Thirroul', 'Wollongong'],
'Melbourne', 'Mittagong', 'Shellharbour', self.get_names(dist_qs))
'Sydney', 'Thirroul', 'Wollongong'],
self.get_names(dist_qs))
else:
# spatialite only allow geodetic distance queries (utilizing
# ST_Distance_Sphere/ST_Distance_Spheroid) from Points to PointFields
# on geometry columns.
self.assertRaises(ValueError, dist_qs.count)
# Ensured that a ValueError was raised, none of the rest of the test is
# support on this backend, so bail now.
if spatialite:
return
# Too many params (4 in this case) should raise a ValueError. # Too many params (4 in this case) should raise a ValueError.
self.assertRaises(ValueError, len, self.assertRaises(ValueError, len,
@ -309,18 +297,18 @@ class DistanceTest(TestCase):
# Geodetic distance lookup but telling GeoDjango to use `distance_spheroid` # Geodetic distance lookup but telling GeoDjango to use `distance_spheroid`
# instead (we should get the same results b/c accuracy variance won't matter # instead (we should get the same results b/c accuracy variance won't matter
# in this test case). # in this test case).
if postgis: querysets = [qs1]
if connection.features.has_distance_spheroid_method:
gq3 = Q(point__distance_lte=(wollongong.point, d1, 'spheroid')) gq3 = Q(point__distance_lte=(wollongong.point, d1, 'spheroid'))
gq4 = Q(point__distance_gte=(wollongong.point, d2, 'spheroid')) gq4 = Q(point__distance_gte=(wollongong.point, d2, 'spheroid'))
qs2 = AustraliaCity.objects.exclude(name='Wollongong').filter(gq3 | gq4) qs2 = AustraliaCity.objects.exclude(name='Wollongong').filter(gq3 | gq4)
querysets = [qs1, qs2] querysets.append(qs2)
else:
querysets = [qs1]
for qs in querysets: for qs in querysets:
cities = self.get_names(qs) cities = self.get_names(qs)
self.assertEqual(cities, ['Adelaide', 'Hobart', 'Shellharbour', 'Thirroul']) self.assertEqual(cities, ['Adelaide', 'Hobart', 'Shellharbour', 'Thirroul'])
@skipUnlessDBFeature("has_area_method")
def test_area(self): def test_area(self):
""" """
Test the `area` GeoQuerySet method. Test the `area` GeoQuerySet method.
@ -333,6 +321,7 @@ class DistanceTest(TestCase):
for i, z in enumerate(SouthTexasZipcode.objects.order_by('name').area()): for i, z in enumerate(SouthTexasZipcode.objects.order_by('name').area()):
self.assertAlmostEqual(area_sq_m[i], z.area.sq_m, tol) self.assertAlmostEqual(area_sq_m[i], z.area.sq_m, tol)
@skipUnlessDBFeature("has_length_method")
def test_length(self): def test_length(self):
""" """
Test the `length` GeoQuerySet method. Test the `length` GeoQuerySet method.
@ -342,13 +331,13 @@ class DistanceTest(TestCase):
len_m1 = 473504.769553813 len_m1 = 473504.769553813
len_m2 = 4617.668 len_m2 = 4617.668
if spatialite: if connection.features.supports_distance_geodetic:
# Does not support geodetic coordinate systems.
self.assertRaises(ValueError, Interstate.objects.length)
else:
qs = Interstate.objects.length() qs = Interstate.objects.length()
tol = 2 if oracle else 3 tol = 2 if oracle else 3
self.assertAlmostEqual(len_m1, qs[0].length.m, tol) self.assertAlmostEqual(len_m1, qs[0].length.m, tol)
else:
# Does not support geodetic coordinate systems.
self.assertRaises(ValueError, Interstate.objects.length)
# Now doing length on a projected coordinate system. # Now doing length on a projected coordinate system.
i10 = SouthTexasInterstate.objects.length().get(name='I-10') i10 = SouthTexasInterstate.objects.length().get(name='I-10')
@ -370,6 +359,7 @@ class DistanceTest(TestCase):
for i, c in enumerate(SouthTexasCity.objects.perimeter(model_att='perim')): for i, c in enumerate(SouthTexasCity.objects.perimeter(model_att='perim')):
self.assertEqual(0, c.perim.m) self.assertEqual(0, c.perim.m)
@skipUnlessDBFeature("has_area_method", "has_distance_method")
def test_measurement_null_fields(self): def test_measurement_null_fields(self):
""" """
Test the measurement GeoQuerySet methods on fields with NULL values. Test the measurement GeoQuerySet methods on fields with NULL values.

View File

@ -6,8 +6,7 @@ from unittest import skipUnless
from django.contrib.gis.gdal import HAS_GDAL from django.contrib.gis.gdal import HAS_GDAL
from django.contrib.gis.geos import HAS_GEOS from django.contrib.gis.geos import HAS_GEOS
from django.contrib.gis.tests.utils import postgis from django.test import TestCase, skipUnlessDBFeature
from django.test import TestCase
from django.utils._os import upath from django.utils._os import upath
if HAS_GEOS: if HAS_GEOS:
@ -62,7 +61,8 @@ bbox_data = (
) )
@skipUnless(HAS_GEOS and HAS_GDAL and postgis, "Geos, GDAL and postgis are required.") @skipUnless(HAS_GDAL, "GDAL is required for Geo3DTest.")
@skipUnlessDBFeature("gis_enabled", "supports_3d_functions")
class Geo3DTest(TestCase): class Geo3DTest(TestCase):
""" """
Only a subset of the PostGIS routines are 3D-enabled, and this TestCase Only a subset of the PostGIS routines are 3D-enabled, and this TestCase
@ -70,7 +70,7 @@ class Geo3DTest(TestCase):
available within GeoDjango. For more information, see the PostGIS docs available within GeoDjango. For more information, see the PostGIS docs
on the routines that support 3D: on the routines that support 3D:
http://postgis.refractions.net/documentation/manual-1.5/ch08.html#PostGIS_3D_Functions http://postgis.net/docs/PostGIS_Special_Functions_Index.html#PostGIS_3D_Functions
""" """
def _load_interstate_data(self): def _load_interstate_data(self):

View File

@ -1,10 +1,7 @@
from django.contrib.gis.db import models from django.contrib.gis.db import models
from django.contrib.gis.tests.utils import mysql from django.contrib.gis.tests.utils import gisfield_may_be_null
from django.utils.encoding import python_2_unicode_compatible from django.utils.encoding import python_2_unicode_compatible
# MySQL spatial indices can't handle NULL geometries.
null_flag = not mysql
@python_2_unicode_compatible @python_2_unicode_compatible
class NamedModel(models.Model): class NamedModel(models.Model):
@ -42,7 +39,7 @@ class PennsylvaniaCity(City):
class State(NamedModel): class State(NamedModel):
poly = models.PolygonField(null=null_flag) # Allowing NULL geometries here. poly = models.PolygonField(null=gisfield_may_be_null) # Allowing NULL geometries here.
class Track(NamedModel): class Track(NamedModel):

View File

@ -2,12 +2,11 @@ from __future__ import unicode_literals
import re import re
import unittest import unittest
from unittest import skipUnless
from django.db import connection from django.db import connection
from django.contrib.gis import gdal from django.contrib.gis import gdal
from django.contrib.gis.geos import HAS_GEOS from django.contrib.gis.geos import HAS_GEOS
from django.contrib.gis.tests.utils import mysql, oracle, postgis, spatialite from django.contrib.gis.tests.utils import oracle, postgis, spatialite
from django.test import TestCase, skipUnlessDBFeature from django.test import TestCase, skipUnlessDBFeature
from django.utils import six from django.utils import six
@ -142,11 +141,12 @@ class GeoModelTest(TestCase):
# If the GeometryField SRID is -1, then we shouldn't perform any # If the GeometryField SRID is -1, then we shouldn't perform any
# transformation if the SRID of the input geometry is different. # transformation if the SRID of the input geometry is different.
# SpatiaLite does not support missing SRID values. if spatialite and connection.ops.spatial_version < 3:
if not spatialite: # SpatiaLite < 3 does not support missing SRID values.
m1 = MinusOneSRID(geom=Point(17, 23, srid=4326)) return
m1.save() m1 = MinusOneSRID(geom=Point(17, 23, srid=4326))
self.assertEqual(-1, m1.geom.srid) m1.save()
self.assertEqual(-1, m1.geom.srid)
def test_createnull(self): def test_createnull(self):
"Testing creating a model instance and the geometry being None" "Testing creating a model instance and the geometry being None"
@ -223,7 +223,7 @@ class GeoLookupTest(TestCase):
# Seeing what cities are in Texas, should get Houston and Dallas, # Seeing what cities are in Texas, should get Houston and Dallas,
# and Oklahoma City because 'contained' only checks on the # and Oklahoma City because 'contained' only checks on the
# _bounding box_ of the Geometries. # _bounding box_ of the Geometries.
if not oracle: if connection.features.supports_contained_lookup:
qs = City.objects.filter(point__contained=texas.mpoly) qs = City.objects.filter(point__contained=texas.mpoly)
self.assertEqual(3, qs.count()) self.assertEqual(3, qs.count())
cities = ['Houston', 'Dallas', 'Oklahoma City'] cities = ['Houston', 'Dallas', 'Oklahoma City']
@ -245,23 +245,22 @@ class GeoLookupTest(TestCase):
self.assertEqual('New Zealand', nz.name) self.assertEqual('New Zealand', nz.name)
# Spatialite 2.3 thinks that Lawrence is in Puerto Rico (a NULL geometry). # Spatialite 2.3 thinks that Lawrence is in Puerto Rico (a NULL geometry).
if not spatialite: if not (spatialite and connection.ops.spatial_version < 3):
ks = State.objects.get(poly__contains=lawrence.point) ks = State.objects.get(poly__contains=lawrence.point)
self.assertEqual('Kansas', ks.name) self.assertEqual('Kansas', ks.name)
# Pueblo and Oklahoma City (even though OK City is within the bounding box of Texas) # Pueblo and Oklahoma City (even though OK City is within the bounding box of Texas)
# are not contained in Texas or New Zealand. # are not contained in Texas or New Zealand.
self.assertEqual(0, len(Country.objects.filter(mpoly__contains=pueblo.point))) # Query w/GEOSGeometry object self.assertEqual(len(Country.objects.filter(mpoly__contains=pueblo.point)), 0) # Query w/GEOSGeometry object
self.assertEqual((mysql and 1) or 0, self.assertEqual(len(Country.objects.filter(mpoly__contains=okcity.point.wkt)),
len(Country.objects.filter(mpoly__contains=okcity.point.wkt))) # Qeury w/WKT 0 if connection.features.supports_real_shape_operations else 1) # Query w/WKT
# OK City is contained w/in bounding box of Texas. # OK City is contained w/in bounding box of Texas.
if not oracle: if connection.features.supports_bbcontains_lookup:
qs = Country.objects.filter(mpoly__bbcontains=okcity.point) qs = Country.objects.filter(mpoly__bbcontains=okcity.point)
self.assertEqual(1, len(qs)) self.assertEqual(1, len(qs))
self.assertEqual('Texas', qs[0].name) self.assertEqual('Texas', qs[0].name)
# Only PostGIS has `left` and `right` lookup types.
@skipUnlessDBFeature("supports_left_right_lookups") @skipUnlessDBFeature("supports_left_right_lookups")
def test_left_right_lookups(self): def test_left_right_lookups(self):
"Testing the 'left' and 'right' lookup types." "Testing the 'left' and 'right' lookup types."
@ -409,10 +408,9 @@ class GeoQuerySetTest(TestCase):
for s in qs: for s in qs:
self.assertEqual(True, s.poly.centroid.equals_exact(s.centroid, tol)) self.assertEqual(True, s.poly.centroid.equals_exact(s.centroid, tol))
@skipUnlessDBFeature("has_difference_method") @skipUnlessDBFeature(
@skipUnlessDBFeature("has_intersection_method") "has_difference_method", "has_intersection_method",
@skipUnlessDBFeature("has_sym_difference_method") "has_sym_difference_method", "has_union_method")
@skipUnlessDBFeature("has_union_method")
def test_diff_intersection_union(self): def test_diff_intersection_union(self):
"Testing the `difference`, `intersection`, `sym_difference`, and `union` GeoQuerySet methods." "Testing the `difference`, `intersection`, `sym_difference`, and `union` GeoQuerySet methods."
geom = Point(5, 23) geom = Point(5, 23)
@ -610,7 +608,7 @@ class GeoQuerySetTest(TestCase):
'Texas': fromstr('POINT (-103.002434 36.500397)', srid=4326), 'Texas': fromstr('POINT (-103.002434 36.500397)', srid=4326),
} }
elif postgis or spatialite: else:
# Using GEOSGeometry to compute the reference point on surface values # Using GEOSGeometry to compute the reference point on surface values
# -- since PostGIS also uses GEOS these should be the same. # -- since PostGIS also uses GEOS these should be the same.
ref = {'New Zealand': Country.objects.get(name='New Zealand').mpoly.point_on_surface, ref = {'New Zealand': Country.objects.get(name='New Zealand').mpoly.point_on_surface,

View File

@ -10,14 +10,14 @@ from django.contrib.gis.gdal import HAS_GDAL
from django.contrib.gis.geos import HAS_GEOS from django.contrib.gis.geos import HAS_GEOS
from django.contrib.gis.measure import D from django.contrib.gis.measure import D
from django.contrib.gis.tests.utils import postgis from django.contrib.gis.tests.utils import postgis
from django.test import TestCase from django.test import TestCase, skipUnlessDBFeature
from django.utils._os import upath from django.utils._os import upath
if HAS_GEOS: if HAS_GEOS:
from .models import City, County, Zipcode from .models import City, County, Zipcode
@skipUnless(HAS_GEOS and postgis, "Geos and postgis are required.") @skipUnlessDBFeature("gis_enabled")
class GeographyTest(TestCase): class GeographyTest(TestCase):
fixtures = ['initial'] fixtures = ['initial']
@ -25,6 +25,7 @@ class GeographyTest(TestCase):
"Ensure geography features loaded properly." "Ensure geography features loaded properly."
self.assertEqual(8, City.objects.count()) self.assertEqual(8, City.objects.count())
@skipUnlessDBFeature("supports_distances_lookups", "supports_distance_geodetic")
def test02_distance_lookup(self): def test02_distance_lookup(self):
"Testing GeoQuerySet distance lookup support on non-point geography fields." "Testing GeoQuerySet distance lookup support on non-point geography fields."
z = Zipcode.objects.get(code='77002') z = Zipcode.objects.get(code='77002')
@ -39,12 +40,14 @@ class GeographyTest(TestCase):
for cities in [cities1, cities2]: for cities in [cities1, cities2]:
self.assertEqual(['Dallas', 'Houston', 'Oklahoma City'], cities) self.assertEqual(['Dallas', 'Houston', 'Oklahoma City'], cities)
@skipUnlessDBFeature("has_distance_method", "supports_distance_geodetic")
def test03_distance_method(self): def test03_distance_method(self):
"Testing GeoQuerySet.distance() support on non-point geography fields." "Testing GeoQuerySet.distance() support on non-point geography fields."
# `GeoQuerySet.distance` is not allowed geometry fields. # `GeoQuerySet.distance` is not allowed geometry fields.
htown = City.objects.get(name='Houston') htown = City.objects.get(name='Houston')
Zipcode.objects.distance(htown.point) Zipcode.objects.distance(htown.point)
@skipUnless(postgis, "This is a PostGIS-specific test")
def test04_invalid_operators_functions(self): def test04_invalid_operators_functions(self):
"Ensuring exceptions are raised for operators & functions invalid on geography fields." "Ensuring exceptions are raised for operators & functions invalid on geography fields."
# Only a subset of the geometry functions & operator are available # Only a subset of the geometry functions & operator are available
@ -89,6 +92,7 @@ class GeographyTest(TestCase):
self.assertEqual(name, c.name) self.assertEqual(name, c.name)
self.assertEqual(state, c.state) self.assertEqual(state, c.state)
@skipUnlessDBFeature("has_area_method", "supports_distance_geodetic")
def test06_geography_area(self): def test06_geography_area(self):
"Testing that Area calculations work on geography columns." "Testing that Area calculations work on geography columns."
# SELECT ST_Area(poly) FROM geogapp_zipcode WHERE code='77002'; # SELECT ST_Area(poly) FROM geogapp_zipcode WHERE code='77002';

View File

@ -1,6 +1,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import os import os
import re
from unittest import skipUnless from unittest import skipUnless
from django.core.management import call_command from django.core.management import call_command
@ -11,7 +12,7 @@ from django.contrib.gis.geometry.test_data import TEST_DATA
from django.utils.six import StringIO from django.utils.six import StringIO
if HAS_GDAL: if HAS_GDAL:
from django.contrib.gis.gdal import Driver from django.contrib.gis.gdal import Driver, OGRException
from django.contrib.gis.utils.ogrinspect import ogrinspect from django.contrib.gis.utils.ogrinspect import ogrinspect
from .models import AllOGRFields from .models import AllOGRFields
@ -77,23 +78,20 @@ class OGRInspectTest(TestCase):
self.assertEqual(model_def, '\n'.join(expected)) self.assertEqual(model_def, '\n'.join(expected))
def test_time_field(self): def test_time_field(self):
# Only possible to test this on PostGIS at the moment. MySQL
# complains about permissions, and SpatiaLite/Oracle are
# insanely difficult to get support compiled in for in GDAL.
if not connections['default'].ops.postgis:
self.skipTest("This database does not support 'ogrinspect'ion")
# Getting the database identifier used by OGR, if None returned # Getting the database identifier used by OGR, if None returned
# GDAL does not have the support compiled in. # GDAL does not have the support compiled in.
ogr_db = get_ogr_db_string() ogr_db = get_ogr_db_string()
if not ogr_db: if not ogr_db:
self.skipTest("Your GDAL installation does not support PostGIS databases") self.skipTest("Unable to setup an OGR connection to your database")
# Writing shapefiles via GDAL currently does not support writing OGRTime try:
# fields, so we need to actually use a database # Writing shapefiles via GDAL currently does not support writing OGRTime
model_def = ogrinspect(ogr_db, 'Measurement', # fields, so we need to actually use a database
layer_key=AllOGRFields._meta.db_table, model_def = ogrinspect(ogr_db, 'Measurement',
decimal=['f_decimal']) layer_key=AllOGRFields._meta.db_table,
decimal=['f_decimal'])
except OGRException:
self.skipTest("Unable to setup an OGR connection to your database")
self.assertTrue(model_def.startswith( self.assertTrue(model_def.startswith(
'# This is an auto-generated Django model module created by ogrinspect.\n' '# This is an auto-generated Django model module created by ogrinspect.\n'
@ -111,10 +109,9 @@ class OGRInspectTest(TestCase):
self.assertIn(' f_char = models.CharField(max_length=10)', model_def) self.assertIn(' f_char = models.CharField(max_length=10)', model_def)
self.assertIn(' f_date = models.DateField()', model_def) self.assertIn(' f_date = models.DateField()', model_def)
self.assertTrue(model_def.endswith( self.assertIsNotNone(re.search(
' geom = models.PolygonField()\n' r' geom = models.PolygonField\(([^\)])*\)\n' # Some backends may have srid=-1
' objects = models.GeoManager()' r' objects = models.GeoManager\(\)', model_def))
))
def test_management_command(self): def test_management_command(self):
shp_file = os.path.join(TEST_DATA, 'cities', 'cities.shp') shp_file = os.path.join(TEST_DATA, 'cities', 'cities.shp')
@ -142,7 +139,7 @@ def get_ogr_db_string():
'django.contrib.gis.db.backends.spatialite': ('SQLite', '%(db_name)s', '') 'django.contrib.gis.db.backends.spatialite': ('SQLite', '%(db_name)s', '')
} }
drv_name, db_str, param_sep = drivers[db['ENGINE']] drv_name, db_str, param_sep = drivers.get(db['ENGINE'])
# Ensure that GDAL library has driver support for the database. # Ensure that GDAL library has driver support for the database.
try: try:

View File

@ -8,8 +8,7 @@ import unittest
from unittest import skipUnless from unittest import skipUnless
from django.contrib.gis.gdal import HAS_GDAL from django.contrib.gis.gdal import HAS_GDAL
from django.contrib.gis.tests.utils import mysql from django.db import connection, router
from django.db import router
from django.conf import settings from django.conf import settings
from django.test import TestCase, skipUnlessDBFeature from django.test import TestCase, skipUnlessDBFeature
from django.utils._os import upath from django.utils._os import upath
@ -151,7 +150,7 @@ class LayerMapTest(TestCase):
# Unique may take tuple or string parameters. # Unique may take tuple or string parameters.
for arg in ('name', ('name', 'mpoly')): for arg in ('name', ('name', 'mpoly')):
lm = LayerMapping(County, co_shp, co_mapping, transform=False, unique=arg) lm = LayerMapping(County, co_shp, co_mapping, transform=False, unique=arg)
except: except Exception:
self.fail('No exception should be raised for proper use of keywords.') self.fail('No exception should be raised for proper use of keywords.')
# Testing invalid params for the `unique` keyword. # Testing invalid params for the `unique` keyword.
@ -159,7 +158,7 @@ class LayerMapTest(TestCase):
self.assertRaises(e, LayerMapping, County, co_shp, co_mapping, transform=False, unique=arg) self.assertRaises(e, LayerMapping, County, co_shp, co_mapping, transform=False, unique=arg)
# No source reference system defined in the shapefile, should raise an error. # No source reference system defined in the shapefile, should raise an error.
if not mysql: if connection.features.supports_transform:
self.assertRaises(LayerMapError, LayerMapping, County, co_shp, co_mapping) self.assertRaises(LayerMapError, LayerMapping, County, co_shp, co_mapping)
# Passing in invalid ForeignKey mapping parameters -- must be a dictionary # Passing in invalid ForeignKey mapping parameters -- must be a dictionary

View File

@ -1,7 +1,8 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.contrib.gis.geos import HAS_GEOS from django.contrib.gis.geos import HAS_GEOS
from django.contrib.gis.tests.utils import mysql, no_oracle from django.contrib.gis.tests.utils import no_oracle
from django.db import connection
from django.test import TestCase, skipUnlessDBFeature from django.test import TestCase, skipUnlessDBFeature
if HAS_GEOS: if HAS_GEOS:
@ -146,7 +147,7 @@ class RelatedGeoModelTest(TestCase):
self.assertEqual(1, len(qs)) self.assertEqual(1, len(qs))
self.assertEqual('P2', qs[0].name) self.assertEqual('P2', qs[0].name)
if not mysql: if connection.features.supports_transform:
# This time center2 is in a different coordinate system and needs # This time center2 is in a different coordinate system and needs
# to be wrapped in transformation SQL. # to be wrapped in transformation SQL.
qs = Parcel.objects.filter(center2__within=F('border1')) qs = Parcel.objects.filter(center2__within=F('border1'))
@ -159,7 +160,7 @@ class RelatedGeoModelTest(TestCase):
self.assertEqual(1, len(qs)) self.assertEqual(1, len(qs))
self.assertEqual('P1', qs[0].name) self.assertEqual('P1', qs[0].name)
if not mysql: if connection.features.supports_transform:
# This time the city column should be wrapped in transformation SQL. # This time the city column should be wrapped in transformation SQL.
qs = Parcel.objects.filter(border2__contains=F('city__location__point')) qs = Parcel.objects.filter(border2__contains=F('city__location__point'))
self.assertEqual(1, len(qs)) self.assertEqual(1, len(qs))

View File

@ -28,6 +28,9 @@ postgis = _default_db == 'postgis'
mysql = _default_db == 'mysql' mysql = _default_db == 'mysql'
spatialite = _default_db == 'spatialite' spatialite = _default_db == 'spatialite'
# MySQL spatial indices can't handle NULL geometries.
gisfield_may_be_null = not mysql
if oracle and 'gis' in settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE']: if oracle and 'gis' in settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE']:
from django.contrib.gis.db.backends.oracle.models import OracleSpatialRefSys as SpatialRefSys from django.contrib.gis.db.backends.oracle.models import OracleSpatialRefSys as SpatialRefSys
elif postgis: elif postgis:

View File

@ -103,10 +103,10 @@ class LayerMapping(object):
# Getting the geometry column associated with the model (an # Getting the geometry column associated with the model (an
# exception will be raised if there is no geometry column). # exception will be raised if there is no geometry column).
if self.spatial_backend.mysql: if connections[self.using].features.supports_transform:
transform = False
else:
self.geo_field = self.geometry_field() self.geo_field = self.geometry_field()
else:
transform = False
# Checking the source spatial reference system, and getting # Checking the source spatial reference system, and getting
# the coordinate transformation object (unless the `transform` # the coordinate transformation object (unless the `transform`