summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergey Fedoseev <fedoseev.sergey@gmail.com>2017-04-01 22:43:53 +0500
committerTim Graham <timograham@gmail.com>2017-04-01 13:43:53 -0400
commit0a13b249e2e0e901d795f4198a3a5dfd1fc49554 (patch)
treed1c68e2adfb3f920c133e7124c368264d3360ba1
parent9a9e22832166594e3561fa1911dd255a2fb09b2b (diff)
Fixed #26967 -- Added MySQL support for AsGeoJSON, GeoHash, IsValid functions, and isvalid lookup.
-rw-r--r--django/contrib/gis/db/backends/mysql/operations.py9
-rw-r--r--django/contrib/gis/db/models/functions.py7
-rw-r--r--docs/ref/contrib/gis/db-api.txt8
-rw-r--r--docs/ref/contrib/gis/functions.txt26
-rw-r--r--docs/ref/contrib/gis/geoquerysets.txt20
-rw-r--r--docs/releases/2.0.txt6
-rw-r--r--tests/gis_tests/geoapp/test_functions.py15
-rw-r--r--tests/gis_tests/geoapp/tests.py7
8 files changed, 68 insertions, 30 deletions
diff --git a/django/contrib/gis/db/backends/mysql/operations.py b/django/contrib/gis/db/backends/mysql/operations.py
index 6408d76fa4..58aeda4deb 100644
--- a/django/contrib/gis/db/backends/mysql/operations.py
+++ b/django/contrib/gis/db/backends/mysql/operations.py
@@ -72,11 +72,12 @@ class MySQLOperations(BaseSpatialOperations, DatabaseOperations):
@cached_property
def unsupported_functions(self):
unsupported = {
- 'AsGeoJSON', 'AsGML', 'AsKML', 'AsSVG', 'BoundingCircle',
- 'ForceRHR', 'GeoHash', 'IsValid', 'MakeValid', 'MemSize',
- 'Perimeter', 'PointOnSurface', 'Reverse', 'Scale', 'SnapToGrid',
- 'Transform', 'Translate',
+ 'AsGML', 'AsKML', 'AsSVG', 'BoundingCircle', 'ForceRHR',
+ 'MakeValid', 'MemSize', 'Perimeter', 'PointOnSurface', 'Reverse',
+ 'Scale', 'SnapToGrid', 'Transform', 'Translate',
}
+ if self.connection.mysql_version < (5, 7, 5):
+ unsupported.update({'AsGeoJSON', 'GeoHash', 'IsValid'})
if self.is_mysql_5_5:
unsupported.update({'Difference', 'Distance', 'Intersection', 'SymDifference', 'Union'})
return unsupported
diff --git a/django/contrib/gis/db/models/functions.py b/django/contrib/gis/db/models/functions.py
index dcd09472e3..c595a143d0 100644
--- a/django/contrib/gis/db/models/functions.py
+++ b/django/contrib/gis/db/models/functions.py
@@ -330,6 +330,13 @@ class GeoHash(GeoFunc):
expressions.append(self._handle_param(precision, 'precision', int))
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):
arity = 2
diff --git a/docs/ref/contrib/gis/db-api.txt b/docs/ref/contrib/gis/db-api.txt
index 09a49c8418..d0555ec145 100644
--- a/docs/ref/contrib/gis/db-api.txt
+++ b/docs/ref/contrib/gis/db-api.txt
@@ -341,7 +341,7 @@ Lookup Type PostGIS Oracle MySQL [#]_ SpatiaLite
:lookup:`equals` X X X X C
:lookup:`exact` 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:`relate` X X X C
:lookup:`same_as` X X X X B
@@ -372,7 +372,7 @@ functions are available on each spatial backend.
Function PostGIS Oracle MySQL SpatiaLite
==================================== ======= ============== =========== ==========
:class:`Area` X X X X
-:class:`AsGeoJSON` X X
+:class:`AsGeoJSON` X X (≥ 5.7.5) X
:class:`AsGML` X X X
:class:`AsKML` 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:`Envelope` X X 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:`IsValid` X X X (LWGEOM)
+:class:`IsValid` X X X (≥ 5.7.5) X (LWGEOM)
:class:`Length` X X X X
:class:`MakeValid` X X (LWGEOM)
:class:`MemSize` X
diff --git a/docs/ref/contrib/gis/functions.txt b/docs/ref/contrib/gis/functions.txt
index fc22d43c30..b54feb03a1 100644
--- a/docs/ref/contrib/gis/functions.txt
+++ b/docs/ref/contrib/gis/functions.txt
@@ -56,8 +56,8 @@ geographic SRSes.
.. class:: AsGeoJSON(expression, bbox=False, crs=False, precision=8, **extra)
-*Availability*: `PostGIS <https://postgis.net/docs/ST_AsGeoJSON.html>`__,
-SpatiaLite
+*Availability*: MySQL (≥ 5.7.5), `PostGIS
+<https://postgis.net/docs/ST_AsGeoJSON.html>`__, SpatiaLite
Accepts a single geographic field or expression and returns a `GeoJSON
<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
reference system to be included in the returned
- GeoJSON.
+ GeoJSON. Ignored on MySQL.
``precision`` It may be used to specify the number of significant
digits for the coordinates in the GeoJSON
representation -- the default value is 8.
===================== =====================================================
+.. versionchanged:: 2.0
+
+ MySQL support was added.
+
``AsGML``
=========
@@ -286,8 +290,8 @@ right-hand rule.
.. class:: GeoHash(expression, precision=None, **extra)
-*Availability*: `PostGIS <https://postgis.net/docs/ST_GeoHash.html>`__,
-SpatiaLite (LWGEOM)
+*Availability*: MySQL (≥ 5.7.5), `PostGIS
+<https://postgis.net/docs/ST_GeoHash.html>`__, SpatiaLite (LWGEOM)
Accepts a single geographic field or expression and returns a `GeoHash`__
representation of the geometry.
@@ -295,6 +299,10 @@ representation of the geometry.
The ``precision`` keyword argument controls the number of characters in the
result.
+.. versionchanged:: 2.0
+
+ MySQL support was added.
+
__ https://en.wikipedia.org/wiki/Geohash
``Intersection``
@@ -313,8 +321,8 @@ intersection between them.
.. class:: IsValid(expr)
-*Availability*: `PostGIS <https://postgis.net/docs/ST_IsValid.html>`__,
-Oracle, SpatiaLite (LWGEOM)
+*Availability*: MySQL (≥ 5.7.5), `PostGIS
+<https://postgis.net/docs/ST_IsValid.html>`__, Oracle, SpatiaLite (LWGEOM)
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.
@@ -323,6 +331,10 @@ Returns ``True`` if its value is a valid geometry and ``False`` otherwise.
SpatiaLite and Oracle support was added.
+.. versionchanged:: 2.0
+
+ MySQL support was added.
+
``Length``
==========
diff --git a/docs/ref/contrib/gis/geoquerysets.txt b/docs/ref/contrib/gis/geoquerysets.txt
index d7135a7c39..d106191485 100644
--- a/docs/ref/contrib/gis/geoquerysets.txt
+++ b/docs/ref/contrib/gis/geoquerysets.txt
@@ -306,8 +306,8 @@ SpatiaLite ``Intersects(poly, geom)``
``isvalid``
-----------
-*Availability*: `PostGIS <https://postgis.net/docs/ST_IsValid.html>`__,
-Oracle, SpatiaLite
+*Availability*: MySQL (≥ 5.7.5), `PostGIS
+<https://postgis.net/docs/ST_IsValid.html>`__, Oracle, SpatiaLite
Tests if the geometry is valid.
@@ -315,17 +315,21 @@ Example::
Zipcode.objects.filter(poly__isvalid=True)
-=================== ================================================================
-Backend SQL Equivalent
-=================== ================================================================
-PostGIS, SpatiaLite ``ST_IsValid(poly)``
-Oracle ``SDO_GEOM.VALIDATE_GEOMETRY_WITH_CONTEXT(poly, 0.05) = 'TRUE'``
-=================== ================================================================
+========================== ================================================================
+Backend SQL Equivalent
+========================== ================================================================
+MySQL, PostGIS, SpatiaLite ``ST_IsValid(poly)``
+Oracle ``SDO_GEOM.VALIDATE_GEOMETRY_WITH_CONTEXT(poly, 0.05) = 'TRUE'``
+========================== ================================================================
.. versionchanged:: 1.11
Oracle and SpatiaLite support was added.
+.. versionchanged:: 2.0
+
+ MySQL support was added.
+
.. fieldlookup:: overlaps
``overlaps``
diff --git a/docs/releases/2.0.txt b/docs/releases/2.0.txt
index ee2117546b..0006ef4c45 100644
--- a/docs/releases/2.0.txt
+++ b/docs/releases/2.0.txt
@@ -62,7 +62,11 @@ Minor features
: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`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/tests/gis_tests/geoapp/test_functions.py b/tests/gis_tests/geoapp/test_functions.py
index 34deb88f4a..818ec56e93 100644
--- a/tests/gis_tests/geoapp/test_functions.py
+++ b/tests/gis_tests/geoapp/test_functions.py
@@ -1,3 +1,4 @@
+import json
import re
from decimal import Decimal
@@ -44,6 +45,12 @@ class GISFunctionsTests(TestCase):
'{"type":"Point","crs":{"type":"name","properties":{"name":"EPSG:4326"}},'
'"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
with self.assertRaises(TypeError):
@@ -61,8 +68,8 @@ class GISFunctionsTests(TestCase):
# WHERE "geoapp_city"."name" = 'Houston';
# This time we want to include the CRS by using the `crs` keyword.
self.assertJSONEqual(
+ City.objects.annotate(json=functions.AsGeoJSON('point', crs=True)).get(name='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"
@@ -79,10 +86,10 @@ class GISFunctionsTests(TestCase):
# WHERE "geoapp_city"."name" = 'Chicago';
# Finally, we set every available keyword.
self.assertJSONEqual(
- chicago_json,
City.objects.annotate(
geojson=functions.AsGeoJSON('point', bbox=True, crs=True, precision=5)
- ).get(name='Chicago').geojson
+ ).get(name='Chicago').geojson,
+ chicago_json,
)
@skipUnlessDBFeature("has_AsGML_function")
@@ -224,7 +231,7 @@ class GISFunctionsTests(TestCase):
ref_hash = '9vk1mfq8jx0c8e0386z6'
h1 = City.objects.annotate(geohash=functions.GeoHash('point')).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)
@skipUnlessDBFeature("has_Intersection_function")
diff --git a/tests/gis_tests/geoapp/tests.py b/tests/gis_tests/geoapp/tests.py
index e0ee1ac238..068b9f3de2 100644
--- a/tests/gis_tests/geoapp/tests.py
+++ b/tests/gis_tests/geoapp/tests.py
@@ -11,7 +11,9 @@ from django.core.management import call_command
from django.db import connection
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 (
City, Country, Feature, MinusOneSRID, NonConcreteModel, PennsylvaniaCity,
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))')
State.objects.create(name='invalid', poly=invalid_geom)
qs = State.objects.all()
- if oracle:
+ if oracle or mysql:
# Kansas has adjacent vertices with distance 6.99244813842e-12
# which is smaller than the default Oracle tolerance.
+ # It's invalid on MySQL too.
qs = qs.exclude(name='Kansas')
self.assertEqual(State.objects.filter(name='Kansas', poly__isvalid=False).count(), 1)
self.assertEqual(qs.filter(poly__isvalid=False).count(), 1)