mirror of https://github.com/django/django.git
Fixed #15165 -- Prevented wrong results with perimeter on geodetic fields.
This commit is contained in:
parent
e051930123
commit
00cb9e13b4
|
@ -26,9 +26,10 @@ class BaseSpatialFeatures(object):
|
||||||
supports_real_shape_operations = True
|
supports_real_shape_operations = True
|
||||||
# Can geometry fields be null?
|
# Can geometry fields be null?
|
||||||
supports_null_geometries = True
|
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_distance_geodetic = True
|
||||||
supports_length_geodetic = True
|
supports_length_geodetic = True
|
||||||
|
supports_perimeter_geodetic = False
|
||||||
# Is the database able to count vertices on polygons (with `num_points`)?
|
# Is the database able to count vertices on polygons (with `num_points`)?
|
||||||
supports_num_points_poly = True
|
supports_num_points_poly = True
|
||||||
|
|
||||||
|
|
|
@ -7,3 +7,4 @@ class DatabaseFeatures(BaseSpatialFeatures, OracleDatabaseFeatures):
|
||||||
supports_add_srs_entry = False
|
supports_add_srs_entry = False
|
||||||
supports_geometry_field_introspection = False
|
supports_geometry_field_introspection = False
|
||||||
supports_geometry_field_unique_index = False
|
supports_geometry_field_unique_index = False
|
||||||
|
supports_perimeter_geodetic = True
|
||||||
|
|
|
@ -206,6 +206,9 @@ class Difference(OracleToleranceMixin, GeoFuncWithGeoParam):
|
||||||
|
|
||||||
|
|
||||||
class DistanceResultMixin(object):
|
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):
|
def convert_value(self, value, expression, connection, context):
|
||||||
if value is None:
|
if value is None:
|
||||||
return None
|
return None
|
||||||
|
@ -236,9 +239,7 @@ class Distance(DistanceResultMixin, OracleToleranceMixin, GeoFuncWithGeoParam):
|
||||||
|
|
||||||
def as_postgresql(self, compiler, connection):
|
def as_postgresql(self, compiler, connection):
|
||||||
geo_field = GeometryField(srid=self.srid) # Fake field to get SRID info
|
geo_field = GeometryField(srid=self.srid) # Fake field to get SRID info
|
||||||
src_field = self.get_source_fields()[0]
|
if self.source_is_geography():
|
||||||
geography = src_field.geography and self.srid == 4326
|
|
||||||
if geography:
|
|
||||||
# Set parameters as geography if base field is geography
|
# Set parameters as geography if base field is geography
|
||||||
for pos, expr in enumerate(
|
for pos, expr in enumerate(
|
||||||
self.source_expressions[self.geom_param_pos + 1:], start=self.geom_param_pos + 1):
|
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):
|
def as_postgresql(self, compiler, connection):
|
||||||
geo_field = GeometryField(srid=self.srid) # Fake field to get SRID info
|
geo_field = GeometryField(srid=self.srid) # Fake field to get SRID info
|
||||||
src_field = self.get_source_fields()[0]
|
if self.source_is_geography():
|
||||||
geography = src_field.geography and self.srid == 4326
|
|
||||||
if geography:
|
|
||||||
self.source_expressions.append(Value(self.spheroid))
|
self.source_expressions.append(Value(self.spheroid))
|
||||||
elif geo_field.geodetic(connection):
|
elif geo_field.geodetic(connection):
|
||||||
# Geometry fields with geodetic (lon/lat) coordinates need length_spheroid
|
# Geometry fields with geodetic (lon/lat) coordinates need length_spheroid
|
||||||
|
@ -346,11 +345,20 @@ class Perimeter(DistanceResultMixin, OracleToleranceMixin, GeoFunc):
|
||||||
arity = 1
|
arity = 1
|
||||||
|
|
||||||
def as_postgresql(self, compiler, connection):
|
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())
|
dim = min(f.dim for f in self.get_source_fields())
|
||||||
if dim > 2:
|
if dim > 2:
|
||||||
self.function = connection.ops.perimeter3d
|
self.function = connection.ops.perimeter3d
|
||||||
return super(Perimeter, self).as_sql(compiler, connection)
|
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):
|
class PointOnSurface(OracleToleranceMixin, GeoFunc):
|
||||||
arity = 1
|
arity = 1
|
||||||
|
|
|
@ -444,6 +444,8 @@ Distance_Sphere(geom1, geom2) | N/A | OK (meter
|
||||||
|
|
||||||
Distance_Spheroid(geom1, geom2, spheroid) | N/A | OK (meters) | N/A
|
Distance_Spheroid(geom1, geom2, spheroid) | N/A | OK (meters) | N/A
|
||||||
|
|
||||||
|
ST_Perimeter(geom1) | OK | :-( (degrees) | OK
|
||||||
|
|
||||||
|
|
||||||
================================
|
================================
|
||||||
Distance functions on Spatialite
|
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
|
ST_Distance(geom1, geom2, use_ellipsoid=False) | N/A | OK (meters), less accurate, quick
|
||||||
|
|
||||||
|
Perimeter(geom1) | OK | :-( (degrees)
|
||||||
|
|
||||||
''' # NOQA
|
''' # NOQA
|
||||||
|
|
||||||
|
|
||||||
|
@ -688,6 +692,20 @@ class DistanceFunctionsTests(TestCase):
|
||||||
for city in qs:
|
for city in qs:
|
||||||
self.assertEqual(0, city.perim.m)
|
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")
|
@skipUnlessDBFeature("supports_null_geometries", "has_Area_function", "has_Distance_function")
|
||||||
def test_measurement_null_fields(self):
|
def test_measurement_null_fields(self):
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Reference in New Issue