Fixed #26967 -- Added MySQL support for AsGeoJSON, GeoHash, IsValid functions, and isvalid lookup.
This commit is contained in:
parent
9a9e228321
commit
0a13b249e2
|
@ -72,11 +72,12 @@ class MySQLOperations(BaseSpatialOperations, DatabaseOperations):
|
||||||
@cached_property
|
@cached_property
|
||||||
def unsupported_functions(self):
|
def unsupported_functions(self):
|
||||||
unsupported = {
|
unsupported = {
|
||||||
'AsGeoJSON', 'AsGML', 'AsKML', 'AsSVG', 'BoundingCircle',
|
'AsGML', 'AsKML', 'AsSVG', 'BoundingCircle', 'ForceRHR',
|
||||||
'ForceRHR', 'GeoHash', 'IsValid', 'MakeValid', 'MemSize',
|
'MakeValid', 'MemSize', 'Perimeter', 'PointOnSurface', 'Reverse',
|
||||||
'Perimeter', 'PointOnSurface', 'Reverse', 'Scale', 'SnapToGrid',
|
'Scale', 'SnapToGrid', 'Transform', 'Translate',
|
||||||
'Transform', 'Translate',
|
|
||||||
}
|
}
|
||||||
|
if self.connection.mysql_version < (5, 7, 5):
|
||||||
|
unsupported.update({'AsGeoJSON', 'GeoHash', 'IsValid'})
|
||||||
if self.is_mysql_5_5:
|
if self.is_mysql_5_5:
|
||||||
unsupported.update({'Difference', 'Distance', 'Intersection', 'SymDifference', 'Union'})
|
unsupported.update({'Difference', 'Distance', 'Intersection', 'SymDifference', 'Union'})
|
||||||
return unsupported
|
return unsupported
|
||||||
|
|
|
@ -330,6 +330,13 @@ class GeoHash(GeoFunc):
|
||||||
expressions.append(self._handle_param(precision, 'precision', int))
|
expressions.append(self._handle_param(precision, 'precision', int))
|
||||||
super().__init__(*expressions, **extra)
|
super().__init__(*expressions, **extra)
|
||||||
|
|
||||||
|
def as_mysql(self, compiler, connection):
|
||||||
|
clone = self.copy()
|
||||||
|
# If no precision is provided, set it to the maximum.
|
||||||
|
if len(clone.source_expressions) < 2:
|
||||||
|
clone.source_expressions.append(Value(100))
|
||||||
|
return clone.as_sql(compiler, connection)
|
||||||
|
|
||||||
|
|
||||||
class Intersection(OracleToleranceMixin, GeomOutputGeoFunc):
|
class Intersection(OracleToleranceMixin, GeomOutputGeoFunc):
|
||||||
arity = 2
|
arity = 2
|
||||||
|
|
|
@ -341,7 +341,7 @@ Lookup Type PostGIS Oracle MySQL [#]_ SpatiaLite
|
||||||
:lookup:`equals` X X X X C
|
:lookup:`equals` X X X X C
|
||||||
:lookup:`exact` X X X X B
|
:lookup:`exact` X X X X B
|
||||||
:lookup:`intersects` X X X X B
|
:lookup:`intersects` X X X X B
|
||||||
:lookup:`isvalid` X X X (LWGEOM)
|
:lookup:`isvalid` X X X (≥ 5.7.5) X (LWGEOM)
|
||||||
:lookup:`overlaps` X X X X B
|
:lookup:`overlaps` X X X X B
|
||||||
:lookup:`relate` X X X C
|
:lookup:`relate` X X X C
|
||||||
:lookup:`same_as` X X X X B
|
:lookup:`same_as` X X X X B
|
||||||
|
@ -372,7 +372,7 @@ functions are available on each spatial backend.
|
||||||
Function PostGIS Oracle MySQL SpatiaLite
|
Function PostGIS Oracle MySQL SpatiaLite
|
||||||
==================================== ======= ============== =========== ==========
|
==================================== ======= ============== =========== ==========
|
||||||
:class:`Area` X X X X
|
:class:`Area` X X X X
|
||||||
:class:`AsGeoJSON` X X
|
:class:`AsGeoJSON` X X (≥ 5.7.5) X
|
||||||
:class:`AsGML` X X X
|
:class:`AsGML` X X X
|
||||||
:class:`AsKML` X X
|
:class:`AsKML` X X
|
||||||
:class:`AsSVG` X X
|
:class:`AsSVG` X X
|
||||||
|
@ -382,9 +382,9 @@ Function PostGIS Oracle MySQL Spat
|
||||||
:class:`Distance` X X X (≥ 5.6.1) X
|
:class:`Distance` X X X (≥ 5.6.1) X
|
||||||
:class:`Envelope` X X X
|
:class:`Envelope` X X X
|
||||||
:class:`ForceRHR` X
|
:class:`ForceRHR` X
|
||||||
:class:`GeoHash` X X (LWGEOM)
|
:class:`GeoHash` X X (≥ 5.7.5) X (LWGEOM)
|
||||||
:class:`Intersection` X X X (≥ 5.6.1) X
|
:class:`Intersection` X X X (≥ 5.6.1) X
|
||||||
:class:`IsValid` X X X (LWGEOM)
|
:class:`IsValid` X X X (≥ 5.7.5) X (LWGEOM)
|
||||||
:class:`Length` X X X X
|
:class:`Length` X X X X
|
||||||
:class:`MakeValid` X X (LWGEOM)
|
:class:`MakeValid` X X (LWGEOM)
|
||||||
:class:`MemSize` X
|
:class:`MemSize` X
|
||||||
|
|
|
@ -56,8 +56,8 @@ geographic SRSes.
|
||||||
|
|
||||||
.. class:: AsGeoJSON(expression, bbox=False, crs=False, precision=8, **extra)
|
.. class:: AsGeoJSON(expression, bbox=False, crs=False, precision=8, **extra)
|
||||||
|
|
||||||
*Availability*: `PostGIS <https://postgis.net/docs/ST_AsGeoJSON.html>`__,
|
*Availability*: MySQL (≥ 5.7.5), `PostGIS
|
||||||
SpatiaLite
|
<https://postgis.net/docs/ST_AsGeoJSON.html>`__, SpatiaLite
|
||||||
|
|
||||||
Accepts a single geographic field or expression and returns a `GeoJSON
|
Accepts a single geographic field or expression and returns a `GeoJSON
|
||||||
<http://geojson.org/>`_ representation of the geometry. Note that the result is
|
<http://geojson.org/>`_ representation of the geometry. Note that the result is
|
||||||
|
@ -77,13 +77,17 @@ Keyword Argument Description
|
||||||
|
|
||||||
``crs`` Set this to ``True`` if you want the coordinate
|
``crs`` Set this to ``True`` if you want the coordinate
|
||||||
reference system to be included in the returned
|
reference system to be included in the returned
|
||||||
GeoJSON.
|
GeoJSON. Ignored on MySQL.
|
||||||
|
|
||||||
``precision`` It may be used to specify the number of significant
|
``precision`` It may be used to specify the number of significant
|
||||||
digits for the coordinates in the GeoJSON
|
digits for the coordinates in the GeoJSON
|
||||||
representation -- the default value is 8.
|
representation -- the default value is 8.
|
||||||
===================== =====================================================
|
===================== =====================================================
|
||||||
|
|
||||||
|
.. versionchanged:: 2.0
|
||||||
|
|
||||||
|
MySQL support was added.
|
||||||
|
|
||||||
``AsGML``
|
``AsGML``
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
@ -286,8 +290,8 @@ right-hand rule.
|
||||||
|
|
||||||
.. class:: GeoHash(expression, precision=None, **extra)
|
.. class:: GeoHash(expression, precision=None, **extra)
|
||||||
|
|
||||||
*Availability*: `PostGIS <https://postgis.net/docs/ST_GeoHash.html>`__,
|
*Availability*: MySQL (≥ 5.7.5), `PostGIS
|
||||||
SpatiaLite (LWGEOM)
|
<https://postgis.net/docs/ST_GeoHash.html>`__, SpatiaLite (LWGEOM)
|
||||||
|
|
||||||
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.
|
||||||
|
@ -295,6 +299,10 @@ representation of the geometry.
|
||||||
The ``precision`` keyword argument controls the number of characters in the
|
The ``precision`` keyword argument controls the number of characters in the
|
||||||
result.
|
result.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.0
|
||||||
|
|
||||||
|
MySQL support was added.
|
||||||
|
|
||||||
__ https://en.wikipedia.org/wiki/Geohash
|
__ https://en.wikipedia.org/wiki/Geohash
|
||||||
|
|
||||||
``Intersection``
|
``Intersection``
|
||||||
|
@ -313,8 +321,8 @@ intersection between them.
|
||||||
|
|
||||||
.. class:: IsValid(expr)
|
.. class:: IsValid(expr)
|
||||||
|
|
||||||
*Availability*: `PostGIS <https://postgis.net/docs/ST_IsValid.html>`__,
|
*Availability*: MySQL (≥ 5.7.5), `PostGIS
|
||||||
Oracle, SpatiaLite (LWGEOM)
|
<https://postgis.net/docs/ST_IsValid.html>`__, Oracle, SpatiaLite (LWGEOM)
|
||||||
|
|
||||||
Accepts a geographic field or expression and tests if the value is well formed.
|
Accepts a geographic field or expression and tests if the value is well formed.
|
||||||
Returns ``True`` if its value is a valid geometry and ``False`` otherwise.
|
Returns ``True`` if its value is a valid geometry and ``False`` otherwise.
|
||||||
|
@ -323,6 +331,10 @@ Returns ``True`` if its value is a valid geometry and ``False`` otherwise.
|
||||||
|
|
||||||
SpatiaLite and Oracle support was added.
|
SpatiaLite and Oracle support was added.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.0
|
||||||
|
|
||||||
|
MySQL support was added.
|
||||||
|
|
||||||
``Length``
|
``Length``
|
||||||
==========
|
==========
|
||||||
|
|
||||||
|
|
|
@ -306,8 +306,8 @@ SpatiaLite ``Intersects(poly, geom)``
|
||||||
``isvalid``
|
``isvalid``
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
*Availability*: `PostGIS <https://postgis.net/docs/ST_IsValid.html>`__,
|
*Availability*: MySQL (≥ 5.7.5), `PostGIS
|
||||||
Oracle, SpatiaLite
|
<https://postgis.net/docs/ST_IsValid.html>`__, Oracle, SpatiaLite
|
||||||
|
|
||||||
Tests if the geometry is valid.
|
Tests if the geometry is valid.
|
||||||
|
|
||||||
|
@ -315,17 +315,21 @@ Example::
|
||||||
|
|
||||||
Zipcode.objects.filter(poly__isvalid=True)
|
Zipcode.objects.filter(poly__isvalid=True)
|
||||||
|
|
||||||
=================== ================================================================
|
========================== ================================================================
|
||||||
Backend SQL Equivalent
|
Backend SQL Equivalent
|
||||||
=================== ================================================================
|
========================== ================================================================
|
||||||
PostGIS, SpatiaLite ``ST_IsValid(poly)``
|
MySQL, PostGIS, SpatiaLite ``ST_IsValid(poly)``
|
||||||
Oracle ``SDO_GEOM.VALIDATE_GEOMETRY_WITH_CONTEXT(poly, 0.05) = 'TRUE'``
|
Oracle ``SDO_GEOM.VALIDATE_GEOMETRY_WITH_CONTEXT(poly, 0.05) = 'TRUE'``
|
||||||
=================== ================================================================
|
========================== ================================================================
|
||||||
|
|
||||||
.. versionchanged:: 1.11
|
.. versionchanged:: 1.11
|
||||||
|
|
||||||
Oracle and SpatiaLite support was added.
|
Oracle and SpatiaLite support was added.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.0
|
||||||
|
|
||||||
|
MySQL support was added.
|
||||||
|
|
||||||
.. fieldlookup:: overlaps
|
.. fieldlookup:: overlaps
|
||||||
|
|
||||||
``overlaps``
|
``overlaps``
|
||||||
|
|
|
@ -62,7 +62,11 @@ Minor features
|
||||||
:mod:`django.contrib.gis`
|
:mod:`django.contrib.gis`
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
* ...
|
* Added MySQL support for the
|
||||||
|
:class:`~django.contrib.gis.db.models.functions.AsGeoJSON` function,
|
||||||
|
:class:`~django.contrib.gis.db.models.functions.GeoHash` function,
|
||||||
|
:class:`~django.contrib.gis.db.models.functions.IsValid` function, and
|
||||||
|
:lookup:`isvalid` lookup.
|
||||||
|
|
||||||
:mod:`django.contrib.messages`
|
:mod:`django.contrib.messages`
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import json
|
||||||
import re
|
import re
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
|
@ -44,6 +45,12 @@ class GISFunctionsTests(TestCase):
|
||||||
'{"type":"Point","crs":{"type":"name","properties":{"name":"EPSG:4326"}},'
|
'{"type":"Point","crs":{"type":"name","properties":{"name":"EPSG:4326"}},'
|
||||||
'"bbox":[-87.65018,41.85039,-87.65018,41.85039],"coordinates":[-87.65018,41.85039]}'
|
'"bbox":[-87.65018,41.85039,-87.65018,41.85039],"coordinates":[-87.65018,41.85039]}'
|
||||||
)
|
)
|
||||||
|
# MySQL ignores the crs option.
|
||||||
|
if mysql:
|
||||||
|
houston_json = json.loads(houston_json)
|
||||||
|
del houston_json['crs']
|
||||||
|
chicago_json = json.loads(chicago_json)
|
||||||
|
del chicago_json['crs']
|
||||||
|
|
||||||
# Precision argument should only be an integer
|
# Precision argument should only be an integer
|
||||||
with self.assertRaises(TypeError):
|
with self.assertRaises(TypeError):
|
||||||
|
@ -61,8 +68,8 @@ class GISFunctionsTests(TestCase):
|
||||||
# WHERE "geoapp_city"."name" = 'Houston';
|
# WHERE "geoapp_city"."name" = 'Houston';
|
||||||
# This time we want to include the CRS by using the `crs` keyword.
|
# This time we want to include the CRS by using the `crs` keyword.
|
||||||
self.assertJSONEqual(
|
self.assertJSONEqual(
|
||||||
|
City.objects.annotate(json=functions.AsGeoJSON('point', crs=True)).get(name='Houston').json,
|
||||||
houston_json,
|
houston_json,
|
||||||
City.objects.annotate(json=functions.AsGeoJSON('point', crs=True)).get(name='Houston').json
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# SELECT ST_AsGeoJson("geoapp_city"."point", 8, 1) FROM "geoapp_city"
|
# SELECT ST_AsGeoJson("geoapp_city"."point", 8, 1) FROM "geoapp_city"
|
||||||
|
@ -79,10 +86,10 @@ class GISFunctionsTests(TestCase):
|
||||||
# WHERE "geoapp_city"."name" = 'Chicago';
|
# WHERE "geoapp_city"."name" = 'Chicago';
|
||||||
# Finally, we set every available keyword.
|
# Finally, we set every available keyword.
|
||||||
self.assertJSONEqual(
|
self.assertJSONEqual(
|
||||||
chicago_json,
|
|
||||||
City.objects.annotate(
|
City.objects.annotate(
|
||||||
geojson=functions.AsGeoJSON('point', bbox=True, crs=True, precision=5)
|
geojson=functions.AsGeoJSON('point', bbox=True, crs=True, precision=5)
|
||||||
).get(name='Chicago').geojson
|
).get(name='Chicago').geojson,
|
||||||
|
chicago_json,
|
||||||
)
|
)
|
||||||
|
|
||||||
@skipUnlessDBFeature("has_AsGML_function")
|
@skipUnlessDBFeature("has_AsGML_function")
|
||||||
|
@ -224,7 +231,7 @@ class GISFunctionsTests(TestCase):
|
||||||
ref_hash = '9vk1mfq8jx0c8e0386z6'
|
ref_hash = '9vk1mfq8jx0c8e0386z6'
|
||||||
h1 = City.objects.annotate(geohash=functions.GeoHash('point')).get(name='Houston')
|
h1 = City.objects.annotate(geohash=functions.GeoHash('point')).get(name='Houston')
|
||||||
h2 = City.objects.annotate(geohash=functions.GeoHash('point', precision=5)).get(name='Houston')
|
h2 = City.objects.annotate(geohash=functions.GeoHash('point', precision=5)).get(name='Houston')
|
||||||
self.assertEqual(ref_hash, h1.geohash)
|
self.assertEqual(ref_hash, h1.geohash[:len(ref_hash)])
|
||||||
self.assertEqual(ref_hash[:5], h2.geohash)
|
self.assertEqual(ref_hash[:5], h2.geohash)
|
||||||
|
|
||||||
@skipUnlessDBFeature("has_Intersection_function")
|
@skipUnlessDBFeature("has_Intersection_function")
|
||||||
|
|
|
@ -11,7 +11,9 @@ from django.core.management import call_command
|
||||||
from django.db import connection
|
from django.db import connection
|
||||||
from django.test import TestCase, skipUnlessDBFeature
|
from django.test import TestCase, skipUnlessDBFeature
|
||||||
|
|
||||||
from ..utils import no_oracle, oracle, postgis, skipUnlessGISLookup, spatialite
|
from ..utils import (
|
||||||
|
mysql, no_oracle, oracle, postgis, skipUnlessGISLookup, spatialite,
|
||||||
|
)
|
||||||
from .models import (
|
from .models import (
|
||||||
City, Country, Feature, MinusOneSRID, NonConcreteModel, PennsylvaniaCity,
|
City, Country, Feature, MinusOneSRID, NonConcreteModel, PennsylvaniaCity,
|
||||||
State, Track,
|
State, Track,
|
||||||
|
@ -302,9 +304,10 @@ class GeoLookupTest(TestCase):
|
||||||
invalid_geom = fromstr('POLYGON((0 0, 0 1, 1 1, 1 0, 1 1, 1 0, 0 0))')
|
invalid_geom = fromstr('POLYGON((0 0, 0 1, 1 1, 1 0, 1 1, 1 0, 0 0))')
|
||||||
State.objects.create(name='invalid', poly=invalid_geom)
|
State.objects.create(name='invalid', poly=invalid_geom)
|
||||||
qs = State.objects.all()
|
qs = State.objects.all()
|
||||||
if oracle:
|
if oracle or mysql:
|
||||||
# Kansas has adjacent vertices with distance 6.99244813842e-12
|
# Kansas has adjacent vertices with distance 6.99244813842e-12
|
||||||
# which is smaller than the default Oracle tolerance.
|
# which is smaller than the default Oracle tolerance.
|
||||||
|
# It's invalid on MySQL too.
|
||||||
qs = qs.exclude(name='Kansas')
|
qs = qs.exclude(name='Kansas')
|
||||||
self.assertEqual(State.objects.filter(name='Kansas', poly__isvalid=False).count(), 1)
|
self.assertEqual(State.objects.filter(name='Kansas', poly__isvalid=False).count(), 1)
|
||||||
self.assertEqual(qs.filter(poly__isvalid=False).count(), 1)
|
self.assertEqual(qs.filter(poly__isvalid=False).count(), 1)
|
||||||
|
|
Loading…
Reference in New Issue