summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClaude Paroz <claude@2xlibre.net>2015-10-10 19:22:42 +0200
committerTim Graham <timograham@gmail.com>2015-12-30 18:07:02 -0500
commit00cb9e13b4cf06ed2be27ee9e7fc18969ae69f7d (patch)
tree4a737ac215203f39fd5c3d6cd6d63999531eb25e
parente0519301238b43633bab64054772f9a62634a05f (diff)
Fixed #15165 -- Prevented wrong results with perimeter on geodetic fields.
-rw-r--r--django/contrib/gis/db/backends/base/features.py3
-rw-r--r--django/contrib/gis/db/backends/oracle/features.py1
-rw-r--r--django/contrib/gis/db/models/functions.py20
-rw-r--r--tests/gis_tests/distapp/tests.py18
4 files changed, 35 insertions, 7 deletions
diff --git a/django/contrib/gis/db/backends/base/features.py b/django/contrib/gis/db/backends/base/features.py
index 14eea12721..027baa5575 100644
--- a/django/contrib/gis/db/backends/base/features.py
+++ b/django/contrib/gis/db/backends/base/features.py
@@ -26,9 +26,10 @@ class BaseSpatialFeatures(object):
supports_real_shape_operations = True
# Can geometry fields be null?
supports_null_geometries = True
- # Can the `distance`/`length` functions be applied on geodetic coordinate systems?
+ # Can the the function be applied on geodetic coordinate systems?
supports_distance_geodetic = True
supports_length_geodetic = True
+ supports_perimeter_geodetic = False
# Is the database able to count vertices on polygons (with `num_points`)?
supports_num_points_poly = True
diff --git a/django/contrib/gis/db/backends/oracle/features.py b/django/contrib/gis/db/backends/oracle/features.py
index b0b7e27df8..996fe8e54b 100644
--- a/django/contrib/gis/db/backends/oracle/features.py
+++ b/django/contrib/gis/db/backends/oracle/features.py
@@ -7,3 +7,4 @@ class DatabaseFeatures(BaseSpatialFeatures, OracleDatabaseFeatures):
supports_add_srs_entry = False
supports_geometry_field_introspection = False
supports_geometry_field_unique_index = False
+ supports_perimeter_geodetic = True
diff --git a/django/contrib/gis/db/models/functions.py b/django/contrib/gis/db/models/functions.py
index 5b59e02f0b..17847bdc5c 100644
--- a/django/contrib/gis/db/models/functions.py
+++ b/django/contrib/gis/db/models/functions.py
@@ -206,6 +206,9 @@ class Difference(OracleToleranceMixin, GeoFuncWithGeoParam):
class DistanceResultMixin(object):
+ def source_is_geography(self):
+ return self.get_source_fields()[0].geography and self.srid == 4326
+
def convert_value(self, value, expression, connection, context):
if value is None:
return None
@@ -236,9 +239,7 @@ class Distance(DistanceResultMixin, OracleToleranceMixin, GeoFuncWithGeoParam):
def as_postgresql(self, compiler, connection):
geo_field = GeometryField(srid=self.srid) # Fake field to get SRID info
- src_field = self.get_source_fields()[0]
- geography = src_field.geography and self.srid == 4326
- if geography:
+ if self.source_is_geography():
# Set parameters as geography if base field is geography
for pos, expr in enumerate(
self.source_expressions[self.geom_param_pos + 1:], start=self.geom_param_pos + 1):
@@ -297,9 +298,7 @@ class Length(DistanceResultMixin, OracleToleranceMixin, GeoFunc):
def as_postgresql(self, compiler, connection):
geo_field = GeometryField(srid=self.srid) # Fake field to get SRID info
- src_field = self.get_source_fields()[0]
- geography = src_field.geography and self.srid == 4326
- if geography:
+ if self.source_is_geography():
self.source_expressions.append(Value(self.spheroid))
elif geo_field.geodetic(connection):
# Geometry fields with geodetic (lon/lat) coordinates need length_spheroid
@@ -346,11 +345,20 @@ class Perimeter(DistanceResultMixin, OracleToleranceMixin, GeoFunc):
arity = 1
def as_postgresql(self, compiler, connection):
+ geo_field = GeometryField(srid=self.srid) # Fake field to get SRID info
+ if geo_field.geodetic(connection) and not self.source_is_geography():
+ raise NotImplementedError("ST_Perimeter cannot use a non-projected non-geography field.")
dim = min(f.dim for f in self.get_source_fields())
if dim > 2:
self.function = connection.ops.perimeter3d
return super(Perimeter, self).as_sql(compiler, connection)
+ def as_sqlite(self, compiler, connection):
+ geo_field = GeometryField(srid=self.srid) # Fake field to get SRID info
+ if geo_field.geodetic(connection):
+ raise NotImplementedError("Perimeter cannot use a non-projected field.")
+ return super(Perimeter, self).as_sql(compiler, connection)
+
class PointOnSurface(OracleToleranceMixin, GeoFunc):
arity = 1
diff --git a/tests/gis_tests/distapp/tests.py b/tests/gis_tests/distapp/tests.py
index f9d3e7ecd8..9dbc4d3c11 100644
--- a/tests/gis_tests/distapp/tests.py
+++ b/tests/gis_tests/distapp/tests.py
@@ -444,6 +444,8 @@ Distance_Sphere(geom1, geom2) | N/A | OK (meter
Distance_Spheroid(geom1, geom2, spheroid) | N/A | OK (meters) | N/A
+ST_Perimeter(geom1) | OK | :-( (degrees) | OK
+
================================
Distance functions on Spatialite
@@ -457,6 +459,8 @@ ST_Distance(geom1, geom2, use_ellipsoid=True) | N/A | OK (
ST_Distance(geom1, geom2, use_ellipsoid=False) | N/A | OK (meters), less accurate, quick
+Perimeter(geom1) | OK | :-( (degrees)
+
''' # NOQA
@@ -688,6 +692,20 @@ class DistanceFunctionsTests(TestCase):
for city in qs:
self.assertEqual(0, city.perim.m)
+ @skipUnlessDBFeature("has_Perimeter_function")
+ def test_perimeter_geodetic(self):
+ # Currently only Oracle supports calculating the perimeter on geodetic
+ # geometries (without being transformed).
+ qs1 = CensusZipcode.objects.annotate(perim=Perimeter('poly'))
+ if connection.features.supports_perimeter_geodetic:
+ self.assertAlmostEqual(qs1[0].perim.m, 18406.3818954314, 3)
+ else:
+ with self.assertRaises(NotImplementedError):
+ list(qs1)
+ # But should work fine when transformed to projected coordinates
+ qs2 = CensusZipcode.objects.annotate(perim=Perimeter(Transform('poly', 32140))).filter(name='77002')
+ self.assertAlmostEqual(qs2[0].perim.m, 18404.355, 3)
+
@skipUnlessDBFeature("supports_null_geometries", "has_Area_function", "has_Distance_function")
def test_measurement_null_fields(self):
"""