Fixed #32575 -- Added support for SpatiaLite 5.

This commit is contained in:
Claude Paroz 2021-04-03 16:23:19 +02:00 committed by Mariusz Felisiak
parent b9d156761f
commit 30e123ed35
10 changed files with 47 additions and 25 deletions

View File

@ -71,4 +71,7 @@ class DatabaseWrapper(SQLiteDatabaseWrapper):
with self.cursor() as cursor: with self.cursor() as cursor:
cursor.execute("PRAGMA table_info(geometry_columns);") cursor.execute("PRAGMA table_info(geometry_columns);")
if cursor.fetchall() == []: if cursor.fetchall() == []:
cursor.execute("SELECT InitSpatialMetaData(1)") if self.ops.spatial_version < (5,):
cursor.execute('SELECT InitSpatialMetaData(1)')
else:
cursor.execute('SELECT InitSpatialMetaDataFull(1)')

View File

@ -11,7 +11,7 @@ class DatabaseFeatures(BaseSpatialFeatures, SQLiteDatabaseFeatures):
@cached_property @cached_property
def supports_area_geodetic(self): def supports_area_geodetic(self):
return bool(self.connection.ops.lwgeom_version()) return bool(self.connection.ops.geom_lib_version())
@cached_property @cached_property
def django_test_skips(self): def django_test_skips(self):

View File

@ -81,7 +81,7 @@ class SpatiaLiteOperations(BaseSpatialOperations, DatabaseOperations):
@cached_property @cached_property
def unsupported_functions(self): def unsupported_functions(self):
unsupported = {'BoundingCircle', 'GeometryDistance', 'MemSize'} unsupported = {'BoundingCircle', 'GeometryDistance', 'MemSize'}
if not self.lwgeom_version(): if not self.geom_lib_version():
unsupported |= {'Azimuth', 'GeoHash', 'MakeValid'} unsupported |= {'Azimuth', 'GeoHash', 'MakeValid'}
return unsupported return unsupported
@ -167,6 +167,20 @@ class SpatiaLiteOperations(BaseSpatialOperations, DatabaseOperations):
"""Return the version of LWGEOM library used by SpatiaLite.""" """Return the version of LWGEOM library used by SpatiaLite."""
return self._get_spatialite_func('lwgeom_version()') return self._get_spatialite_func('lwgeom_version()')
def rttopo_version(self):
"""Return the version of RTTOPO library used by SpatiaLite."""
return self._get_spatialite_func('rttopo_version()')
def geom_lib_version(self):
"""
Return the version of the version-dependant geom library used by
SpatiaLite.
"""
if self.spatial_version >= (5,):
return self.rttopo_version()
else:
return self.lwgeom_version()
def spatialite_version(self): def spatialite_version(self):
"Return the SpatiaLite library version as a string." "Return the SpatiaLite library version as a string."
return self._get_spatialite_func('spatialite_version()') return self._get_spatialite_func('spatialite_version()')

View File

@ -344,9 +344,9 @@ functions are available on each spatial backend.
.. currentmodule:: django.contrib.gis.db.models.functions .. currentmodule:: django.contrib.gis.db.models.functions
==================================== ======= ============== ============ =========== ========== ==================================== ======= ============== ============ =========== =================
Function PostGIS Oracle MariaDB MySQL SpatiaLite Function PostGIS Oracle MariaDB MySQL SpatiaLite
==================================== ======= ============== ============ =========== ========== ==================================== ======= ============== ============ =========== =================
:class:`Area` X X X X X :class:`Area` X X X X X
:class:`AsGeoJSON` X X X (≥ 10.2.4) X (≥ 5.7.5) X :class:`AsGeoJSON` X X X (≥ 10.2.4) X (≥ 5.7.5) X
:class:`AsGML` X X X :class:`AsGML` X X X
@ -354,19 +354,19 @@ Function PostGIS Oracle MariaDB MySQL
:class:`AsSVG` X X :class:`AsSVG` X X
:class:`AsWKB` X X X X X :class:`AsWKB` X X X X X
:class:`AsWKT` X X X X X :class:`AsWKT` X X X X X
:class:`Azimuth` X X (LWGEOM) :class:`Azimuth` X X (LWGEOM/RTTOPO)
:class:`BoundingCircle` X X :class:`BoundingCircle` X X
:class:`Centroid` X X X X X :class:`Centroid` X X X X X
:class:`Difference` X X X X X :class:`Difference` X X X X X
:class:`Distance` X X X X X :class:`Distance` X X X X X
:class:`Envelope` X X X X X :class:`Envelope` X X X X X
:class:`ForcePolygonCW` X X :class:`ForcePolygonCW` X X
:class:`GeoHash` X X (≥ 5.7.5) X (LWGEOM) :class:`GeoHash` X X (≥ 5.7.5) X (LWGEOM/RTTOPO)
:class:`Intersection` X X X X X :class:`Intersection` X X X X X
:class:`IsValid` X X X (≥ 5.7.5) X :class:`IsValid` X X X (≥ 5.7.5) X
:class:`Length` X X X X X :class:`Length` X X X X X
:class:`LineLocatePoint` X X :class:`LineLocatePoint` X X
:class:`MakeValid` X X (LWGEOM) :class:`MakeValid` X X (LWGEOM/RTTOPO)
:class:`MemSize` X :class:`MemSize` X
:class:`NumGeometries` X X X X X :class:`NumGeometries` X X X X X
:class:`NumPoints` X X X X X :class:`NumPoints` X X X X X
@ -379,7 +379,7 @@ Function PostGIS Oracle MariaDB MySQL
:class:`Transform` X X X :class:`Transform` X X X
:class:`Translate` X X :class:`Translate` X X
:class:`Union` X X X X X :class:`Union` X X X X X
==================================== ======= ============== ============ =========== ========== ==================================== ======= ============== ============ =========== =================
Aggregate Functions Aggregate Functions
------------------- -------------------

View File

@ -44,7 +44,7 @@ Oracle, `PostGIS <https://postgis.net/docs/ST_Area.html>`__, SpatiaLite
Accepts a single geographic field or expression and returns the area of the Accepts a single geographic field or expression and returns the area of the
field as an :class:`~django.contrib.gis.measure.Area` measure. field as an :class:`~django.contrib.gis.measure.Area` measure.
MySQL and SpatiaLite without LWGEOM don't support area calculations on MySQL and SpatiaLite without LWGEOM/RTTOPO don't support area calculations on
geographic SRSes. geographic SRSes.
``AsGeoJSON`` ``AsGeoJSON``
@ -208,7 +208,7 @@ __ https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry
.. class:: Azimuth(point_a, point_b, **extra) .. class:: Azimuth(point_a, point_b, **extra)
*Availability*: `PostGIS <https://postgis.net/docs/ST_Azimuth.html>`__, *Availability*: `PostGIS <https://postgis.net/docs/ST_Azimuth.html>`__,
SpatiaLite (LWGEOM) SpatiaLite (LWGEOM/RTTOPO)
Returns the azimuth in radians of the segment defined by the given point Returns the azimuth in radians of the segment defined by the given point
geometries, or ``None`` if the two points are coincident. The azimuth is angle geometries, or ``None`` if the two points are coincident. The azimuth is angle
@ -334,7 +334,8 @@ are returned unchanged.
*Availability*: `MySQL *Availability*: `MySQL
<https://dev.mysql.com/doc/refman/en/spatial-geohash-functions.html#function_st-geohash>`__ (≥ 5.7.5), <https://dev.mysql.com/doc/refman/en/spatial-geohash-functions.html#function_st-geohash>`__ (≥ 5.7.5),
`PostGIS <https://postgis.net/docs/ST_GeoHash.html>`__, SpatiaLite (LWGEOM) `PostGIS <https://postgis.net/docs/ST_GeoHash.html>`__, SpatiaLite
(LWGEOM/RTTOPO)
Accepts a single geographic field or expression and returns a `GeoHash`__ Accepts a single geographic field or expression and returns a `GeoHash`__
representation of the geometry. representation of the geometry.
@ -416,7 +417,7 @@ Returns a float between 0 and 1 representing the location of the closest point o
.. class:: MakeValid(expr) .. class:: MakeValid(expr)
*Availability*: `PostGIS <https://postgis.net/docs/ST_MakeValid.html>`__, *Availability*: `PostGIS <https://postgis.net/docs/ST_MakeValid.html>`__,
SpatiaLite (LWGEOM) SpatiaLite (LWGEOM/RTTOPO)
Accepts a geographic field or expression and attempts to convert the value into Accepts a geographic field or expression and attempts to convert the value into
a valid geometry without losing any of the input vertices. Geometries that are a valid geometry without losing any of the input vertices. Geometries that are

View File

@ -13,7 +13,7 @@ Program Description Required
:doc:`GDAL <../gdal>` Geospatial Data Abstraction Library Yes 3.2, 3.1, 3.0, 2.4, 2.3, 2.2, 2.1 :doc:`GDAL <../gdal>` Geospatial Data Abstraction Library Yes 3.2, 3.1, 3.0, 2.4, 2.3, 2.2, 2.1
:doc:`GeoIP <../geoip2>` IP-based geolocation library No 2 :doc:`GeoIP <../geoip2>` IP-based geolocation library No 2
`PostGIS`__ Spatial extensions for PostgreSQL Yes (PostgreSQL only) 3.0, 2.5, 2.4 `PostGIS`__ Spatial extensions for PostgreSQL Yes (PostgreSQL only) 3.0, 2.5, 2.4
`SpatiaLite`__ Spatial extensions for SQLite Yes (SQLite only) 4.3 `SpatiaLite`__ Spatial extensions for SQLite Yes (SQLite only) 5.0, 4.3
======================== ==================================== ================================ ================================= ======================== ==================================== ================================ =================================
Note that older or more recent versions of these libraries *may* also work Note that older or more recent versions of these libraries *may* also work
@ -36,6 +36,7 @@ totally fine with GeoDjango. Your mileage may vary.
PostGIS 2.5.0 2018-09-23 PostGIS 2.5.0 2018-09-23
PostGIS 3.0.0 2019-10-20 PostGIS 3.0.0 2019-10-20
SpatiaLite 4.3.0 2015-09-07 SpatiaLite 4.3.0 2015-09-07
SpatiaLite 5.0.0 2020-08-23
.. note:: .. note::

View File

@ -100,7 +100,7 @@ Minor features
:mod:`django.contrib.gis` :mod:`django.contrib.gis`
~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~
* ... * Added support for SpatiaLite 5.
:mod:`django.contrib.messages` :mod:`django.contrib.messages`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -367,16 +367,12 @@ class DistanceFunctionsTests(FuncTestMixin, TestCase):
dist2 = SouthTexasCityFt.objects.annotate(distance=Distance('point', lagrange)).order_by('id') dist2 = SouthTexasCityFt.objects.annotate(distance=Distance('point', lagrange)).order_by('id')
dist_qs = [dist1, dist2] dist_qs = [dist1, dist2]
# Original query done on PostGIS, have to adjust AlmostEqual tolerance
# for Oracle.
tol = 2 if connection.ops.oracle else 5
# Ensuring expected distances are returned for each distance queryset. # Ensuring expected distances are returned for each distance queryset.
for qs in dist_qs: for qs in dist_qs:
for i, c in enumerate(qs): for i, c in enumerate(qs):
with self.subTest(c=c): with self.subTest(c=c):
self.assertAlmostEqual(m_distances[i], c.distance.m, tol) self.assertAlmostEqual(m_distances[i], c.distance.m, -1)
self.assertAlmostEqual(ft_distances[i], c.distance.survey_ft, tol) self.assertAlmostEqual(ft_distances[i], c.distance.survey_ft, -1)
@skipUnlessDBFeature("has_Distance_function", "supports_distance_geodetic") @skipUnlessDBFeature("has_Distance_function", "supports_distance_geodetic")
def test_distance_geodetic(self): def test_distance_geodetic(self):

View File

@ -183,7 +183,11 @@ class GISFunctionsTests(FuncTestMixin, TestCase):
def test_azimuth(self): def test_azimuth(self):
# Returns the azimuth in radians. # Returns the azimuth in radians.
azimuth_expr = functions.Azimuth(Point(0, 0, srid=4326), Point(1, 1, srid=4326)) azimuth_expr = functions.Azimuth(Point(0, 0, srid=4326), Point(1, 1, srid=4326))
self.assertAlmostEqual(City.objects.annotate(azimuth=azimuth_expr).first().azimuth, math.pi / 4) self.assertAlmostEqual(
City.objects.annotate(azimuth=azimuth_expr).first().azimuth,
math.pi / 4,
places=2,
)
# Returns None if the two points are coincident. # Returns None if the two points are coincident.
azimuth_expr = functions.Azimuth(Point(0, 0, srid=4326), Point(0, 0, srid=4326)) azimuth_expr = functions.Azimuth(Point(0, 0, srid=4326), Point(0, 0, srid=4326))
self.assertIsNone(City.objects.annotate(azimuth=azimuth_expr).first().azimuth) self.assertIsNone(City.objects.annotate(azimuth=azimuth_expr).first().azimuth)

View File

@ -111,9 +111,12 @@ class GeographyFunctionTests(FuncTestMixin, TestCase):
if connection.ops.oracle: if connection.ops.oracle:
ref_dists = [0, 4899.68, 8081.30, 9115.15] ref_dists = [0, 4899.68, 8081.30, 9115.15]
elif connection.ops.spatialite: elif connection.ops.spatialite:
# SpatiaLite returns non-zero distance for polygons and points if connection.ops.spatial_version < (5,):
# SpatiaLite < 5 returns non-zero distance for polygons and points
# covered by that polygon. # covered by that polygon.
ref_dists = [326.61, 4899.68, 8081.30, 9115.15] ref_dists = [326.61, 4899.68, 8081.30, 9115.15]
else:
ref_dists = [0, 4899.68, 8081.30, 9115.15]
else: else:
ref_dists = [0, 4891.20, 8071.64, 9123.95] ref_dists = [0, 4891.20, 8071.64, 9123.95]
htown = City.objects.get(name='Houston') htown = City.objects.get(name='Houston')