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

Added Spatialite support to GIS functions

This commit is contained in:
Claude Paroz
2015-01-23 22:45:57 +01:00
parent d9ff5ef36d
commit 44bdbbc316
5 changed files with 91 additions and 12 deletions

View File

@@ -26,8 +26,9 @@ class BaseSpatialFeatures(object):
supports_real_shape_operations = True
# Can geometry fields be null?
supports_null_geometries = True
# Can the `distance` GeoQuerySet method be applied on geodetic coordinate systems?
# Can the `distance`/`length` functions be applied on geodetic coordinate systems?
supports_distance_geodetic = True
supports_length_geodetic = True
# Is the database able to count vertices on polygons (with `num_points`)?
supports_num_points_poly = True

View File

@@ -1,3 +1,9 @@
"""
SQL functions reference lists:
http://www.gaia-gis.it/spatialite-2.4.0/spatialite-sql-2.4.html
http://www.gaia-gis.it/spatialite-3.0.0-BETA/spatialite-sql-3.0.0.html
http://www.gaia-gis.it/gaia-sins/spatialite-sql-4.2.1.html
"""
import re
import sys
@@ -74,6 +80,21 @@ class SpatiaLiteOperations(BaseSpatialOperations, DatabaseOperations):
'distance_lte': SpatialOperator(func='Distance', op='<='),
}
function_names = {
'Length': 'ST_Length',
'Reverse': 'ST_Reverse',
'Scale': 'ScaleCoords',
'Translate': 'ST_Translate',
'Union': 'ST_Union',
}
@cached_property
def unsupported_functions(self):
unsupported = {'BoundingCircle', 'ForceRHR', 'GeoHash', 'MemSize'}
if self.spatial_version < (4, 0, 0):
unsupported.add('Reverse')
return unsupported
@cached_property
def spatial_version(self):
"""Determine the version of the SpatiaLite library."""

View File

@@ -79,6 +79,9 @@ class GeomValue(Value):
self.value = connection.ops.Adapter(self.value)
return super(GeomValue, self).as_sql(compiler, connection)
def as_sqlite(self, compiler, connection):
return 'GeomFromText(%%s, %s)' % self.srid, [connection.ops.Adapter(self.value)]
class GeoFuncWithGeoParam(GeoFunc):
def __init__(self, expression, geom, *expressions, **extra):
@@ -94,6 +97,18 @@ class GeoFuncWithGeoParam(GeoFunc):
super(GeoFuncWithGeoParam, self).__init__(expression, geom, *expressions, **extra)
class SQLiteDecimalToFloatMixin(object):
"""
By default, Decimal values are converted to str by the SQLite backend, which
is not acceptable by the GIS functions expecting numeric values.
"""
def as_sqlite(self, compiler, connection):
for expr in self.get_source_expressions():
if hasattr(expr, 'value') and isinstance(expr.value, Decimal):
expr.value = float(expr.value)
return super(SQLiteDecimalToFloatMixin, self).as_sql(compiler, connection)
class Area(GeoFunc):
def as_sql(self, compiler, connection):
if connection.ops.oracle:
@@ -143,7 +158,10 @@ class AsGML(GeoFunc):
class AsKML(AsGML):
pass
def as_sqlite(self, compiler, connection):
# No version parameter
self.source_expressions.pop(0)
return super(AsKML, self).as_sql(compiler, connection)
class AsSVG(GeoFunc):
@@ -261,6 +279,15 @@ class Length(DistanceResultMixin, GeoFunc):
self.function = connection.ops.length3d
return super(Length, self).as_sql(compiler, connection)
def as_sqlite(self, compiler, connection):
geo_field = GeometryField(srid=self.srid)
if geo_field.geodetic(connection):
if self.spheroid:
self.function = 'GeodesicLength'
else:
self.function = 'GreatCircleLength'
return super(Length, self).as_sql(compiler, connection)
class MemSize(GeoFunc):
output_field_class = IntegerField
@@ -273,6 +300,11 @@ class NumGeometries(GeoFunc):
class NumPoints(GeoFunc):
output_field_class = IntegerField
def as_sqlite(self, compiler, connection):
if self.source_expressions[self.geom_param_pos].output_field.geom_type != 'LINESTRING':
raise TypeError("Spatialite NumPoints can only operate on LineString content")
return super(NumPoints, self).as_sql(compiler, connection)
class Perimeter(DistanceResultMixin, GeoFunc):
output_field_class = FloatField
@@ -292,7 +324,7 @@ class Reverse(GeoFunc):
pass
class Scale(GeoFunc):
class Scale(SQLiteDecimalToFloatMixin, GeoFunc):
def __init__(self, expression, x, y, z=0.0, **extra):
expressions = [
expression,
@@ -304,7 +336,7 @@ class Scale(GeoFunc):
super(Scale, self).__init__(*expressions, **extra)
class SnapToGrid(GeoFunc):
class SnapToGrid(SQLiteDecimalToFloatMixin, GeoFunc):
def __init__(self, expression, *args, **extra):
nargs = len(args)
expressions = [expression]
@@ -342,9 +374,20 @@ class Transform(GeoFunc):
# Make srid the resulting srid of the transformation
return self.source_expressions[self.geom_param_pos + 1].value
def convert_value(self, value, expression, connection, context):
value = super(Transform, self).convert_value(value, expression, connection, context)
if not connection.ops.postgis and not value.srid:
# Some backends do not set the srid on the returning geometry
value.srid = self.srid
return value
class Translate(Scale):
pass
def as_sqlite(self, compiler, connection):
# Always provide the z parameter
if len(self.source_expressions) < 4:
self.source_expressions.append(Value(0))
return super(Translate, self).as_sqlite(compiler, connection)
class Union(GeoFuncWithGeoParam):