From 30e123ed351317b7527f632b3b7dc4e81e850449 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 3 Apr 2021 16:23:19 +0200 Subject: [PATCH] Fixed #32575 -- Added support for SpatiaLite 5. --- .../contrib/gis/db/backends/spatialite/base.py | 5 ++++- .../gis/db/backends/spatialite/features.py | 2 +- .../gis/db/backends/spatialite/operations.py | 16 +++++++++++++++- docs/ref/contrib/gis/db-api.txt | 12 ++++++------ docs/ref/contrib/gis/functions.txt | 9 +++++---- docs/ref/contrib/gis/install/geolibs.txt | 3 ++- docs/releases/4.0.txt | 2 +- tests/gis_tests/distapp/tests.py | 8 ++------ tests/gis_tests/geoapp/test_functions.py | 6 +++++- tests/gis_tests/geogapp/tests.py | 9 ++++++--- 10 files changed, 47 insertions(+), 25 deletions(-) diff --git a/django/contrib/gis/db/backends/spatialite/base.py b/django/contrib/gis/db/backends/spatialite/base.py index 1afba58706..fef7de62af 100644 --- a/django/contrib/gis/db/backends/spatialite/base.py +++ b/django/contrib/gis/db/backends/spatialite/base.py @@ -71,4 +71,7 @@ class DatabaseWrapper(SQLiteDatabaseWrapper): with self.cursor() as cursor: cursor.execute("PRAGMA table_info(geometry_columns);") 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)') diff --git a/django/contrib/gis/db/backends/spatialite/features.py b/django/contrib/gis/db/backends/spatialite/features.py index 947d874b30..b6c1746c07 100644 --- a/django/contrib/gis/db/backends/spatialite/features.py +++ b/django/contrib/gis/db/backends/spatialite/features.py @@ -11,7 +11,7 @@ class DatabaseFeatures(BaseSpatialFeatures, SQLiteDatabaseFeatures): @cached_property def supports_area_geodetic(self): - return bool(self.connection.ops.lwgeom_version()) + return bool(self.connection.ops.geom_lib_version()) @cached_property def django_test_skips(self): diff --git a/django/contrib/gis/db/backends/spatialite/operations.py b/django/contrib/gis/db/backends/spatialite/operations.py index 8fd95d414e..8dcd62de9b 100644 --- a/django/contrib/gis/db/backends/spatialite/operations.py +++ b/django/contrib/gis/db/backends/spatialite/operations.py @@ -81,7 +81,7 @@ class SpatiaLiteOperations(BaseSpatialOperations, DatabaseOperations): @cached_property def unsupported_functions(self): unsupported = {'BoundingCircle', 'GeometryDistance', 'MemSize'} - if not self.lwgeom_version(): + if not self.geom_lib_version(): unsupported |= {'Azimuth', 'GeoHash', 'MakeValid'} return unsupported @@ -167,6 +167,20 @@ class SpatiaLiteOperations(BaseSpatialOperations, DatabaseOperations): """Return the version of LWGEOM library used by SpatiaLite.""" 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): "Return the SpatiaLite library version as a string." return self._get_spatialite_func('spatialite_version()') diff --git a/docs/ref/contrib/gis/db-api.txt b/docs/ref/contrib/gis/db-api.txt index 7921d53a8a..af2c1c04a0 100644 --- a/docs/ref/contrib/gis/db-api.txt +++ b/docs/ref/contrib/gis/db-api.txt @@ -344,9 +344,9 @@ functions are available on each spatial backend. .. currentmodule:: django.contrib.gis.db.models.functions -==================================== ======= ============== ============ =========== ========== +==================================== ======= ============== ============ =========== ================= Function PostGIS Oracle MariaDB MySQL SpatiaLite -==================================== ======= ============== ============ =========== ========== +==================================== ======= ============== ============ =========== ================= :class:`Area` X X X X X :class:`AsGeoJSON` X X X (≥ 10.2.4) X (≥ 5.7.5) X :class:`AsGML` X X X @@ -354,19 +354,19 @@ Function PostGIS Oracle MariaDB MySQL :class:`AsSVG` X X :class:`AsWKB` 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:`Centroid` X X X X X :class:`Difference` X X X X X :class:`Distance` X X X X X :class:`Envelope` X X X 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:`IsValid` X X X (≥ 5.7.5) X :class:`Length` X X X X X :class:`LineLocatePoint` X X -:class:`MakeValid` X X (LWGEOM) +:class:`MakeValid` X X (LWGEOM/RTTOPO) :class:`MemSize` X :class:`NumGeometries` 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:`Translate` X X :class:`Union` X X X X X -==================================== ======= ============== ============ =========== ========== +==================================== ======= ============== ============ =========== ================= Aggregate Functions ------------------- diff --git a/docs/ref/contrib/gis/functions.txt b/docs/ref/contrib/gis/functions.txt index cf2c3217e4..82aaf1196d 100644 --- a/docs/ref/contrib/gis/functions.txt +++ b/docs/ref/contrib/gis/functions.txt @@ -44,7 +44,7 @@ Oracle, `PostGIS `__, SpatiaLite Accepts a single geographic field or expression and returns the area of the 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. ``AsGeoJSON`` @@ -208,7 +208,7 @@ __ https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry .. class:: Azimuth(point_a, point_b, **extra) *Availability*: `PostGIS `__, -SpatiaLite (LWGEOM) +SpatiaLite (LWGEOM/RTTOPO) 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 @@ -334,7 +334,8 @@ are returned unchanged. *Availability*: `MySQL `__ (≥ 5.7.5), -`PostGIS `__, SpatiaLite (LWGEOM) +`PostGIS `__, SpatiaLite +(LWGEOM/RTTOPO) Accepts a single geographic field or expression and returns a `GeoHash`__ 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) *Availability*: `PostGIS `__, -SpatiaLite (LWGEOM) +SpatiaLite (LWGEOM/RTTOPO) 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 diff --git a/docs/ref/contrib/gis/install/geolibs.txt b/docs/ref/contrib/gis/install/geolibs.txt index 058653c7d2..d24caec57f 100644 --- a/docs/ref/contrib/gis/install/geolibs.txt +++ b/docs/ref/contrib/gis/install/geolibs.txt @@ -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:`GeoIP <../geoip2>` IP-based geolocation library No 2 `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 @@ -36,6 +36,7 @@ totally fine with GeoDjango. Your mileage may vary. PostGIS 2.5.0 2018-09-23 PostGIS 3.0.0 2019-10-20 SpatiaLite 4.3.0 2015-09-07 + SpatiaLite 5.0.0 2020-08-23 .. note:: diff --git a/docs/releases/4.0.txt b/docs/releases/4.0.txt index ab06cbb40f..3792c4b716 100644 --- a/docs/releases/4.0.txt +++ b/docs/releases/4.0.txt @@ -100,7 +100,7 @@ Minor features :mod:`django.contrib.gis` ~~~~~~~~~~~~~~~~~~~~~~~~~ -* ... +* Added support for SpatiaLite 5. :mod:`django.contrib.messages` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/gis_tests/distapp/tests.py b/tests/gis_tests/distapp/tests.py index bb03fc20c3..244c99a1d7 100644 --- a/tests/gis_tests/distapp/tests.py +++ b/tests/gis_tests/distapp/tests.py @@ -367,16 +367,12 @@ class DistanceFunctionsTests(FuncTestMixin, TestCase): dist2 = SouthTexasCityFt.objects.annotate(distance=Distance('point', lagrange)).order_by('id') 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. for qs in dist_qs: for i, c in enumerate(qs): with self.subTest(c=c): - self.assertAlmostEqual(m_distances[i], c.distance.m, tol) - self.assertAlmostEqual(ft_distances[i], c.distance.survey_ft, tol) + self.assertAlmostEqual(m_distances[i], c.distance.m, -1) + self.assertAlmostEqual(ft_distances[i], c.distance.survey_ft, -1) @skipUnlessDBFeature("has_Distance_function", "supports_distance_geodetic") def test_distance_geodetic(self): diff --git a/tests/gis_tests/geoapp/test_functions.py b/tests/gis_tests/geoapp/test_functions.py index 22d40a4400..a1b1f48e02 100644 --- a/tests/gis_tests/geoapp/test_functions.py +++ b/tests/gis_tests/geoapp/test_functions.py @@ -183,7 +183,11 @@ class GISFunctionsTests(FuncTestMixin, TestCase): def test_azimuth(self): # Returns the azimuth in radians. 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. azimuth_expr = functions.Azimuth(Point(0, 0, srid=4326), Point(0, 0, srid=4326)) self.assertIsNone(City.objects.annotate(azimuth=azimuth_expr).first().azimuth) diff --git a/tests/gis_tests/geogapp/tests.py b/tests/gis_tests/geogapp/tests.py index 53852517e8..7f465f5753 100644 --- a/tests/gis_tests/geogapp/tests.py +++ b/tests/gis_tests/geogapp/tests.py @@ -111,9 +111,12 @@ class GeographyFunctionTests(FuncTestMixin, TestCase): if connection.ops.oracle: ref_dists = [0, 4899.68, 8081.30, 9115.15] elif connection.ops.spatialite: - # SpatiaLite returns non-zero distance for polygons and points - # covered by that polygon. - ref_dists = [326.61, 4899.68, 8081.30, 9115.15] + if connection.ops.spatial_version < (5,): + # SpatiaLite < 5 returns non-zero distance for polygons and points + # covered by that polygon. + ref_dists = [326.61, 4899.68, 8081.30, 9115.15] + else: + ref_dists = [0, 4899.68, 8081.30, 9115.15] else: ref_dists = [0, 4891.20, 8071.64, 9123.95] htown = City.objects.get(name='Houston')