2015-01-24 05:45:57 +08:00
|
|
|
"""
|
|
|
|
SQL functions reference lists:
|
2019-02-05 01:03:12 +08:00
|
|
|
https://www.gaia-gis.it/gaia-sins/spatialite-sql-4.3.0.html
|
2015-01-24 05:45:57 +08:00
|
|
|
"""
|
2019-08-20 15:54:41 +08:00
|
|
|
from django.contrib.gis.db import models
|
2017-06-02 01:23:48 +08:00
|
|
|
from django.contrib.gis.db.backends.base.operations import (
|
|
|
|
BaseSpatialOperations,
|
|
|
|
)
|
2009-12-22 23:18:51 +08:00
|
|
|
from django.contrib.gis.db.backends.spatialite.adapter import SpatiaLiteAdapter
|
2015-01-28 20:35:27 +08:00
|
|
|
from django.contrib.gis.db.backends.utils import SpatialOperator
|
2017-09-11 11:00:18 +08:00
|
|
|
from django.contrib.gis.geos.geometry import GEOSGeometry, GEOSGeometryBase
|
2019-02-05 01:03:12 +08:00
|
|
|
from django.contrib.gis.geos.prototypes.io import wkb_r
|
2009-12-22 23:18:51 +08:00
|
|
|
from django.contrib.gis.measure import Distance
|
|
|
|
from django.core.exceptions import ImproperlyConfigured
|
2015-01-13 04:20:40 +08:00
|
|
|
from django.db.backends.sqlite3.operations import DatabaseOperations
|
2013-01-30 02:33:43 +08:00
|
|
|
from django.utils.functional import cached_property
|
2017-02-16 00:14:21 +08:00
|
|
|
from django.utils.version import get_version_tuple
|
2013-01-30 02:33:43 +08:00
|
|
|
|
2009-12-22 23:18:51 +08:00
|
|
|
|
2017-07-10 22:53:29 +08:00
|
|
|
class SpatialiteNullCheckOperator(SpatialOperator):
|
|
|
|
def as_sql(self, connection, lookup, template_params, sql_params):
|
|
|
|
sql, params = super().as_sql(connection, lookup, template_params, sql_params)
|
|
|
|
return '%s > 0' % sql, params
|
|
|
|
|
|
|
|
|
2015-01-14 20:36:32 +08:00
|
|
|
class SpatiaLiteOperations(BaseSpatialOperations, DatabaseOperations):
|
2009-12-22 23:18:51 +08:00
|
|
|
name = 'spatialite'
|
|
|
|
spatialite = True
|
2014-08-20 00:47:23 +08:00
|
|
|
|
2009-12-22 23:18:51 +08:00
|
|
|
Adapter = SpatiaLiteAdapter
|
|
|
|
|
2014-08-20 00:47:23 +08:00
|
|
|
collect = 'Collect'
|
|
|
|
extent = 'Extent'
|
2015-12-01 11:08:41 +08:00
|
|
|
makeline = 'MakeLine'
|
2009-12-22 23:18:51 +08:00
|
|
|
unionagg = 'GUnion'
|
|
|
|
|
|
|
|
from_text = 'GeomFromText'
|
|
|
|
|
2014-05-23 03:51:30 +08:00
|
|
|
gis_operators = {
|
2016-11-13 21:13:34 +08:00
|
|
|
# Binary predicates
|
2017-07-10 22:53:29 +08:00
|
|
|
'equals': SpatialiteNullCheckOperator(func='Equals'),
|
|
|
|
'disjoint': SpatialiteNullCheckOperator(func='Disjoint'),
|
|
|
|
'touches': SpatialiteNullCheckOperator(func='Touches'),
|
|
|
|
'crosses': SpatialiteNullCheckOperator(func='Crosses'),
|
|
|
|
'within': SpatialiteNullCheckOperator(func='Within'),
|
|
|
|
'overlaps': SpatialiteNullCheckOperator(func='Overlaps'),
|
|
|
|
'contains': SpatialiteNullCheckOperator(func='Contains'),
|
|
|
|
'intersects': SpatialiteNullCheckOperator(func='Intersects'),
|
|
|
|
'relate': SpatialiteNullCheckOperator(func='Relate'),
|
2018-06-21 21:25:31 +08:00
|
|
|
'coveredby': SpatialiteNullCheckOperator(func='CoveredBy'),
|
|
|
|
'covers': SpatialiteNullCheckOperator(func='Covers'),
|
2011-08-12 22:14:15 +08:00
|
|
|
# Returns true if B's bounding box completely contains A's bounding box.
|
2014-05-23 03:51:30 +08:00
|
|
|
'contained': SpatialOperator(func='MbrWithin'),
|
2009-12-22 23:18:51 +08:00
|
|
|
# Returns true if A's bounding box completely contains B's bounding box.
|
2014-05-23 03:51:30 +08:00
|
|
|
'bbcontains': SpatialOperator(func='MbrContains'),
|
2009-12-22 23:18:51 +08:00
|
|
|
# Returns true if A's bounding box overlaps B's bounding box.
|
2014-05-23 03:51:30 +08:00
|
|
|
'bboverlaps': SpatialOperator(func='MbrOverlaps'),
|
2009-12-22 23:18:51 +08:00
|
|
|
# These are implemented here as synonyms for Equals
|
2017-07-10 22:53:29 +08:00
|
|
|
'same_as': SpatialiteNullCheckOperator(func='Equals'),
|
|
|
|
'exact': SpatialiteNullCheckOperator(func='Equals'),
|
2016-11-13 21:13:34 +08:00
|
|
|
# Distance predicates
|
|
|
|
'dwithin': SpatialOperator(func='PtDistWithin'),
|
2013-10-18 17:02:43 +08:00
|
|
|
}
|
2012-08-19 17:17:45 +08:00
|
|
|
|
2019-08-20 15:54:41 +08:00
|
|
|
disallowed_aggregates = (models.Extent3D,)
|
2015-12-01 11:08:41 +08:00
|
|
|
|
2019-02-05 01:03:12 +08:00
|
|
|
select = 'CAST (AsEWKB(%s) AS BLOB)'
|
2017-08-23 14:30:24 +08:00
|
|
|
|
2017-09-06 12:29:11 +08:00
|
|
|
function_names = {
|
2019-11-16 02:37:43 +08:00
|
|
|
'AsWKB': 'St_AsBinary',
|
2018-01-07 08:24:44 +08:00
|
|
|
'ForcePolygonCW': 'ST_ForceLHR',
|
2017-09-06 12:29:11 +08:00
|
|
|
'Length': 'ST_Length',
|
|
|
|
'LineLocatePoint': 'ST_Line_Locate_Point',
|
|
|
|
'NumPoints': 'ST_NPoints',
|
|
|
|
'Reverse': 'ST_Reverse',
|
|
|
|
'Scale': 'ScaleCoords',
|
|
|
|
'Translate': 'ST_Translate',
|
|
|
|
'Union': 'ST_Union',
|
|
|
|
}
|
2015-01-24 05:45:57 +08:00
|
|
|
|
|
|
|
@cached_property
|
|
|
|
def unsupported_functions(self):
|
2019-03-21 00:54:42 +08:00
|
|
|
unsupported = {'BoundingCircle', 'GeometryDistance', 'MemSize'}
|
2016-05-31 23:31:51 +08:00
|
|
|
if not self.lwgeom_version():
|
2017-04-03 02:24:06 +08:00
|
|
|
unsupported |= {'Azimuth', 'GeoHash', 'IsValid', 'MakeValid'}
|
2015-01-24 05:45:57 +08:00
|
|
|
return unsupported
|
|
|
|
|
2013-01-30 02:33:43 +08:00
|
|
|
@cached_property
|
|
|
|
def spatial_version(self):
|
|
|
|
"""Determine the version of the SpatiaLite library."""
|
2009-12-22 23:18:51 +08:00
|
|
|
try:
|
2013-01-30 02:33:43 +08:00
|
|
|
version = self.spatialite_version_tuple()[1:]
|
2017-01-08 03:13:29 +08:00
|
|
|
except Exception as exc:
|
|
|
|
raise ImproperlyConfigured(
|
|
|
|
'Cannot determine the SpatiaLite version for the "%s" database. '
|
|
|
|
'Was the SpatiaLite initialization SQL loaded on this database?' % (
|
|
|
|
self.connection.settings_dict['NAME'],
|
|
|
|
)
|
|
|
|
) from exc
|
2019-02-05 01:03:12 +08:00
|
|
|
if version < (4, 3, 0):
|
|
|
|
raise ImproperlyConfigured('GeoDjango supports SpatiaLite 4.3.0 and above.')
|
2013-01-30 02:33:43 +08:00
|
|
|
return version
|
|
|
|
|
2017-03-21 21:13:18 +08:00
|
|
|
def convert_extent(self, box):
|
2014-08-20 00:47:23 +08:00
|
|
|
"""
|
2016-08-10 00:46:14 +08:00
|
|
|
Convert the polygon data received from SpatiaLite to min/max values.
|
2014-08-20 00:47:23 +08:00
|
|
|
"""
|
2015-01-14 00:04:17 +08:00
|
|
|
if box is None:
|
|
|
|
return None
|
2017-09-11 11:00:18 +08:00
|
|
|
shell = GEOSGeometry(box).shell
|
2014-08-20 00:47:23 +08:00
|
|
|
xmin, ymin = shell[0][:2]
|
|
|
|
xmax, ymax = shell[2][:2]
|
|
|
|
return (xmin, ymin, xmax, ymax)
|
|
|
|
|
2009-12-22 23:18:51 +08:00
|
|
|
def geo_db_type(self, f):
|
|
|
|
"""
|
2017-01-25 04:31:57 +08:00
|
|
|
Return None because geometry columns are added via the
|
2009-12-22 23:18:51 +08:00
|
|
|
`AddGeometryColumn` stored procedure on SpatiaLite.
|
|
|
|
"""
|
|
|
|
return None
|
|
|
|
|
2017-02-11 18:47:20 +08:00
|
|
|
def get_distance(self, f, value, lookup_type):
|
2009-12-22 23:18:51 +08:00
|
|
|
"""
|
2017-01-25 04:31:57 +08:00
|
|
|
Return the distance parameters for the given geometry field,
|
2016-11-16 20:07:36 +08:00
|
|
|
lookup value, and lookup type.
|
2009-12-22 23:18:51 +08:00
|
|
|
"""
|
|
|
|
if not value:
|
|
|
|
return []
|
|
|
|
value = value[0]
|
|
|
|
if isinstance(value, Distance):
|
|
|
|
if f.geodetic(self.connection):
|
2016-11-16 20:07:36 +08:00
|
|
|
if lookup_type == 'dwithin':
|
|
|
|
raise ValueError(
|
|
|
|
'Only numeric values of degree units are allowed on '
|
|
|
|
'geographic DWithin queries.'
|
|
|
|
)
|
|
|
|
dist_param = value.m
|
2009-12-22 23:18:51 +08:00
|
|
|
else:
|
|
|
|
dist_param = getattr(value, Distance.unit_attname(f.units_name(self.connection)))
|
|
|
|
else:
|
|
|
|
dist_param = value
|
|
|
|
return [dist_param]
|
|
|
|
|
|
|
|
def _get_spatialite_func(self, func):
|
|
|
|
"""
|
2010-03-30 03:40:59 +08:00
|
|
|
Helper routine for calling SpatiaLite functions and returning
|
|
|
|
their result.
|
2014-04-27 01:18:45 +08:00
|
|
|
Any error occurring in this method should be handled by the caller.
|
2009-12-22 23:18:51 +08:00
|
|
|
"""
|
|
|
|
cursor = self.connection._cursor()
|
|
|
|
try:
|
2013-09-30 23:55:14 +08:00
|
|
|
cursor.execute('SELECT %s' % func)
|
|
|
|
row = cursor.fetchone()
|
2009-12-22 23:18:51 +08:00
|
|
|
finally:
|
|
|
|
cursor.close()
|
|
|
|
return row[0]
|
|
|
|
|
|
|
|
def geos_version(self):
|
2017-01-25 04:31:57 +08:00
|
|
|
"Return the version of GEOS used by SpatiaLite as a string."
|
2010-03-30 03:40:59 +08:00
|
|
|
return self._get_spatialite_func('geos_version()')
|
2009-12-22 23:18:51 +08:00
|
|
|
|
2020-05-11 04:30:03 +08:00
|
|
|
def proj_version(self):
|
|
|
|
"""Return the version of the PROJ library used by SpatiaLite."""
|
2010-03-30 03:40:59 +08:00
|
|
|
return self._get_spatialite_func('proj4_version()')
|
2009-12-22 23:18:51 +08:00
|
|
|
|
2015-12-04 12:52:16 +08:00
|
|
|
def lwgeom_version(self):
|
|
|
|
"""Return the version of LWGEOM library used by SpatiaLite."""
|
|
|
|
return self._get_spatialite_func('lwgeom_version()')
|
|
|
|
|
2009-12-22 23:18:51 +08:00
|
|
|
def spatialite_version(self):
|
2017-01-25 04:31:57 +08:00
|
|
|
"Return the SpatiaLite library version as a string."
|
2010-03-30 03:40:59 +08:00
|
|
|
return self._get_spatialite_func('spatialite_version()')
|
2009-12-22 23:18:51 +08:00
|
|
|
|
|
|
|
def spatialite_version_tuple(self):
|
|
|
|
"""
|
2017-01-25 04:31:57 +08:00
|
|
|
Return the SpatiaLite version as a tuple (version string, major,
|
2009-12-22 23:18:51 +08:00
|
|
|
minor, subminor).
|
|
|
|
"""
|
2014-06-10 04:03:04 +08:00
|
|
|
version = self.spatialite_version()
|
2017-02-16 00:14:21 +08:00
|
|
|
return (version,) + get_version_tuple(version)
|
2009-12-22 23:18:51 +08:00
|
|
|
|
2015-01-15 18:35:35 +08:00
|
|
|
def spatial_aggregate_name(self, agg_name):
|
2009-12-22 23:18:51 +08:00
|
|
|
"""
|
2017-01-25 04:31:57 +08:00
|
|
|
Return the spatial aggregate SQL template and function for the
|
2009-12-22 23:18:51 +08:00
|
|
|
given Aggregate instance.
|
|
|
|
"""
|
2015-01-15 18:35:35 +08:00
|
|
|
agg_name = 'unionagg' if agg_name.lower() == 'union' else agg_name.lower()
|
|
|
|
return getattr(self, agg_name)
|
2009-12-22 23:18:51 +08:00
|
|
|
|
|
|
|
# Routines for getting the OGC-compliant models.
|
|
|
|
def geometry_columns(self):
|
2014-06-08 15:54:35 +08:00
|
|
|
from django.contrib.gis.db.backends.spatialite.models import SpatialiteGeometryColumns
|
|
|
|
return SpatialiteGeometryColumns
|
2009-12-22 23:18:51 +08:00
|
|
|
|
|
|
|
def spatial_ref_sys(self):
|
2014-06-08 15:54:35 +08:00
|
|
|
from django.contrib.gis.db.backends.spatialite.models import SpatialiteSpatialRefSys
|
|
|
|
return SpatialiteSpatialRefSys
|
2017-08-23 14:30:24 +08:00
|
|
|
|
|
|
|
def get_geometry_converter(self, expression):
|
2017-09-05 21:54:57 +08:00
|
|
|
geom_class = expression.output_field.geom_class
|
2019-02-05 01:03:12 +08:00
|
|
|
read = wkb_r().read
|
2017-09-05 21:54:57 +08:00
|
|
|
|
2019-02-05 01:03:12 +08:00
|
|
|
def converter(value, expression, connection):
|
|
|
|
return None if value is None else GEOSGeometryBase(read(value), geom_class)
|
2017-09-05 21:54:57 +08:00
|
|
|
return converter
|