1
0
mirror of https://github.com/django/django.git synced 2025-10-24 06:06:09 +00:00

Fixed #26455 -- Allowed filtering and repairing invalid geometries.

Added the IsValid and MakeValid database functions, and the isvalid lookup,
all for PostGIS.

Thanks Tim Graham for the review.
This commit is contained in:
Daniel Wiesmann
2016-04-06 12:50:25 +01:00
committed by Tim Graham
parent f6ca63a9f8
commit c12a00e554
14 changed files with 121 additions and 17 deletions

View File

@@ -64,6 +64,10 @@ class BaseSpatialFeatures(object):
def supports_relate_lookup(self):
return 'relate' in self.connection.ops.gis_operators
@property
def supports_isvalid_lookup(self):
return 'isvalid' in self.connection.ops.gis_operators
# For each of those methods, the class will have a property named
# `has_<name>_method` (defined in __init__) which accesses connection.ops
# to determine GIS method availability.

View File

@@ -58,10 +58,10 @@ class BaseSpatialOperations(object):
unsupported_functions = {
'Area', 'AsGeoJSON', 'AsGML', 'AsKML', 'AsSVG',
'BoundingCircle', 'Centroid', 'Difference', 'Distance', 'Envelope',
'ForceRHR', 'GeoHash', 'Intersection', 'Length', 'MemSize', 'NumGeometries',
'NumPoints', 'Perimeter', 'PointOnSurface', 'Reverse', 'Scale',
'SnapToGrid', 'SymDifference', 'Transform', 'Translate',
'Union',
'ForceRHR', 'GeoHash', 'Intersection', 'IsValid', 'Length', 'MakeValid',
'MemSize', 'NumGeometries', 'NumPoints', 'Perimeter', 'PointOnSurface',
'Reverse', 'Scale', 'SnapToGrid', 'SymDifference', 'Transform',
'Translate', 'Union',
}
# Serialization

View File

@@ -67,7 +67,7 @@ class MySQLOperations(BaseSpatialOperations, DatabaseOperations):
def unsupported_functions(self):
unsupported = {
'AsGeoJSON', 'AsGML', 'AsKML', 'AsSVG', 'BoundingCircle',
'ForceRHR', 'GeoHash', 'MemSize',
'ForceRHR', 'GeoHash', 'IsValid', 'MakeValid', 'MemSize',
'Perimeter', 'PointOnSurface', 'Reverse', 'Scale', 'SnapToGrid',
'Transform', 'Translate',
}

View File

@@ -127,7 +127,7 @@ class OracleOperations(BaseSpatialOperations, DatabaseOperations):
unsupported_functions = {
'AsGeoJSON', 'AsGML', 'AsKML', 'AsSVG',
'BoundingCircle', 'Envelope',
'ForceRHR', 'GeoHash', 'MemSize', 'Scale',
'ForceRHR', 'GeoHash', 'IsValid', 'MakeValid', 'MemSize', 'Scale',
'SnapToGrid', 'Translate',
}

View File

@@ -79,6 +79,7 @@ class PostGISOperations(BaseSpatialOperations, DatabaseOperations):
'disjoint': PostGISOperator(func='ST_Disjoint'),
'equals': PostGISOperator(func='ST_Equals'),
'intersects': PostGISOperator(func='ST_Intersects', geography=True),
'isvalid': PostGISOperator(func='ST_IsValid'),
'overlaps': PostGISOperator(func='ST_Overlaps'),
'relate': PostGISOperator(func='ST_Relate'),
'touches': PostGISOperator(func='ST_Touches'),
@@ -118,11 +119,13 @@ class PostGISOperations(BaseSpatialOperations, DatabaseOperations):
self.geojson = prefix + 'AsGeoJson'
self.gml = prefix + 'AsGML'
self.intersection = prefix + 'Intersection'
self.isvalid = prefix + 'IsValid'
self.kml = prefix + 'AsKML'
self.length = prefix + 'Length'
self.length3d = prefix + '3DLength'
self.length_spheroid = prefix + 'length_spheroid'
self.makeline = prefix + 'MakeLine'
self.makevalid = prefix + 'MakeValid'
self.mem_size = prefix + 'mem_size'
self.num_geom = prefix + 'NumGeometries'
self.num_points = prefix + 'npoints'

View File

@@ -96,7 +96,7 @@ class SpatiaLiteOperations(BaseSpatialOperations, DatabaseOperations):
@cached_property
def unsupported_functions(self):
unsupported = {'BoundingCircle', 'ForceRHR', 'MemSize'}
unsupported = {'BoundingCircle', 'ForceRHR', 'IsValid', 'MakeValid', 'MemSize'}
if self.spatial_version < (3, 1, 0):
unsupported.add('SnapToGrid')
if self.spatial_version < (4, 0, 0):

View File

@@ -225,7 +225,7 @@ class GeometryField(GeoSelectFormatMixin, BaseSpatialField):
returning to the caller.
"""
value = super(GeometryField, self).get_prep_value(value)
if isinstance(value, Expression):
if isinstance(value, (Expression, bool)):
return value
elif isinstance(value, (tuple, list)):
geom = value[0]

View File

@@ -6,7 +6,7 @@ from django.contrib.gis.measure import (
Area as AreaMeasure, Distance as DistanceMeasure,
)
from django.core.exceptions import FieldError
from django.db.models import FloatField, IntegerField, TextField
from django.db.models import BooleanField, FloatField, IntegerField, TextField
from django.db.models.expressions import Func, Value
from django.utils import six
@@ -282,6 +282,10 @@ class Intersection(OracleToleranceMixin, GeoFuncWithGeoParam):
arity = 2
class IsValid(GeoFunc):
output_field_class = BooleanField
class Length(DistanceResultMixin, OracleToleranceMixin, GeoFunc):
output_field_class = FloatField
@@ -319,6 +323,10 @@ class Length(DistanceResultMixin, OracleToleranceMixin, GeoFunc):
return super(Length, self).as_sql(compiler, connection)
class MakeValid(GeoFunc):
pass
class MemSize(GeoFunc):
output_field_class = IntegerField
arity = 1

View File

@@ -5,7 +5,7 @@ import re
from django.core.exceptions import FieldDoesNotExist
from django.db.models.constants import LOOKUP_SEP
from django.db.models.expressions import Col, Expression
from django.db.models.lookups import Lookup
from django.db.models.lookups import BuiltinLookup, Lookup
from django.utils import six
gis_lookups = {}
@@ -270,6 +270,19 @@ class IntersectsLookup(GISLookup):
gis_lookups['intersects'] = IntersectsLookup
class IsValidLookup(BuiltinLookup):
lookup_name = 'isvalid'
def as_sql(self, compiler, connection):
gis_op = connection.ops.gis_operators[self.lookup_name]
sql, params = self.process_lhs(compiler, connection)
sql = '%(func)s(%(lhs)s)' % {'func': gis_op.func, 'lhs': sql}
if not self.rhs:
sql = 'NOT ' + sql
return sql, params
gis_lookups['isvalid'] = IsValidLookup
class OverlapsLookup(GISLookup):
lookup_name = 'overlaps'
gis_lookups['overlaps'] = OverlapsLookup