Fixed #22423 -- Added support for MySQL operators on real geometries.
Thanks Viswanathan Mahalingam for the report and initial patch, and Nicke Pope and Tim Graham for the review.
This commit is contained in:
parent
8cf9dbee6a
commit
15715bf2a2
|
@ -21,8 +21,6 @@ class BaseSpatialFeatures:
|
||||||
supports_3d_functions = False
|
supports_3d_functions = False
|
||||||
# Does the database support SRID transform operations?
|
# Does the database support SRID transform operations?
|
||||||
supports_transform = True
|
supports_transform = True
|
||||||
# Do geometric relationship operations operate on real shapes (or only on bounding boxes)?
|
|
||||||
supports_real_shape_operations = True
|
|
||||||
# Can geometry fields be null?
|
# Can geometry fields be null?
|
||||||
supports_null_geometries = True
|
supports_null_geometries = True
|
||||||
# Are empty geometries supported?
|
# Are empty geometries supported?
|
||||||
|
|
|
@ -12,7 +12,6 @@ class DatabaseFeatures(BaseSpatialFeatures, MySQLDatabaseFeatures):
|
||||||
supports_length_geodetic = False
|
supports_length_geodetic = False
|
||||||
supports_area_geodetic = False
|
supports_area_geodetic = False
|
||||||
supports_transform = False
|
supports_transform = False
|
||||||
supports_real_shape_operations = False
|
|
||||||
supports_null_geometries = False
|
supports_null_geometries = False
|
||||||
supports_num_points_poly = False
|
supports_num_points_poly = False
|
||||||
|
|
||||||
|
|
|
@ -29,22 +29,20 @@ class MySQLOperations(BaseSpatialOperations, DatabaseOperations):
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def gis_operators(self):
|
def gis_operators(self):
|
||||||
MBREquals = 'MBREqual' if (
|
|
||||||
self.connection.mysql_is_mariadb or self.connection.mysql_version < (5, 7, 6)
|
|
||||||
) else 'MBREquals'
|
|
||||||
return {
|
return {
|
||||||
'bbcontains': SpatialOperator(func='MBRContains'), # For consistency w/PostGIS API
|
'bbcontains': SpatialOperator(func='MBRContains'), # For consistency w/PostGIS API
|
||||||
'bboverlaps': SpatialOperator(func='MBROverlaps'), # ...
|
'bboverlaps': SpatialOperator(func='MBROverlaps'), # ...
|
||||||
'contained': SpatialOperator(func='MBRWithin'), # ...
|
'contained': SpatialOperator(func='MBRWithin'), # ...
|
||||||
'contains': SpatialOperator(func='MBRContains'),
|
'contains': SpatialOperator(func='ST_Contains'),
|
||||||
'disjoint': SpatialOperator(func='MBRDisjoint'),
|
'crosses': SpatialOperator(func='ST_Crosses'),
|
||||||
'equals': SpatialOperator(func=MBREquals),
|
'disjoint': SpatialOperator(func='ST_Disjoint'),
|
||||||
'exact': SpatialOperator(func=MBREquals),
|
'equals': SpatialOperator(func='ST_Equals'),
|
||||||
'intersects': SpatialOperator(func='MBRIntersects'),
|
'exact': SpatialOperator(func='ST_Equals'),
|
||||||
'overlaps': SpatialOperator(func='MBROverlaps'),
|
'intersects': SpatialOperator(func='ST_Intersects'),
|
||||||
'same_as': SpatialOperator(func=MBREquals),
|
'overlaps': SpatialOperator(func='ST_Overlaps'),
|
||||||
'touches': SpatialOperator(func='MBRTouches'),
|
'same_as': SpatialOperator(func='ST_Equals'),
|
||||||
'within': SpatialOperator(func='MBRWithin'),
|
'touches': SpatialOperator(func='ST_Touches'),
|
||||||
|
'within': SpatialOperator(func='ST_Within'),
|
||||||
}
|
}
|
||||||
|
|
||||||
disallowed_aggregates = (
|
disallowed_aggregates = (
|
||||||
|
|
|
@ -25,21 +25,15 @@ GeoDjango currently provides the following spatial database backends:
|
||||||
MySQL Spatial Limitations
|
MySQL Spatial Limitations
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
MySQL's spatial extensions only support bounding box operations
|
Before MySQL 5.6.1, spatial extensions only support bounding box operations
|
||||||
(what MySQL calls minimum bounding rectangles, or MBR). Specifically,
|
(what MySQL calls minimum bounding rectangles, or MBR). Specifically, MySQL did
|
||||||
`MySQL does not conform to the OGC standard
|
not conform to the OGC standard. Django supports spatial functions operating on
|
||||||
<https://dev.mysql.com/doc/refman/en/spatial-relation-functions.html>`_:
|
real geometries available in modern MySQL versions. However, the spatial
|
||||||
|
functions are not as rich as other backends like PostGIS.
|
||||||
|
|
||||||
Currently, MySQL does not implement these functions
|
.. versionchanged:: 3.0
|
||||||
[``Contains``, ``Crosses``, ``Disjoint``, ``Intersects``, ``Overlaps``,
|
|
||||||
``Touches``, ``Within``]
|
|
||||||
according to the specification. Those that are implemented return
|
|
||||||
the same result as the corresponding MBR-based functions.
|
|
||||||
|
|
||||||
In other words, while spatial lookups such as :lookup:`contains <gis-contains>`
|
Support for spatial functions operating on real geometries was added.
|
||||||
are available in GeoDjango when using MySQL, the results returned are really
|
|
||||||
equivalent to what would be returned when using :lookup:`bbcontains`
|
|
||||||
on a different spatial backend.
|
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
|
|
|
@ -148,10 +148,15 @@ Backend SQL Equivalent
|
||||||
========== ============================
|
========== ============================
|
||||||
PostGIS ``ST_Contains(poly, geom)``
|
PostGIS ``ST_Contains(poly, geom)``
|
||||||
Oracle ``SDO_CONTAINS(poly, geom)``
|
Oracle ``SDO_CONTAINS(poly, geom)``
|
||||||
MySQL ``MBRContains(poly, geom)``
|
MySQL ``ST_Contains(poly, geom)``
|
||||||
SpatiaLite ``Contains(poly, geom)``
|
SpatiaLite ``Contains(poly, geom)``
|
||||||
========== ============================
|
========== ============================
|
||||||
|
|
||||||
|
.. versionchanged:: 3.0
|
||||||
|
|
||||||
|
In older versions, MySQL uses ``MBRContains`` and operates only on bounding
|
||||||
|
boxes.
|
||||||
|
|
||||||
.. fieldlookup:: contains_properly
|
.. fieldlookup:: contains_properly
|
||||||
|
|
||||||
``contains_properly``
|
``contains_properly``
|
||||||
|
@ -233,7 +238,7 @@ SpatiaLite ``Covers(poly, geom)``
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
*Availability*: `PostGIS <https://postgis.net/docs/ST_Crosses.html>`__,
|
*Availability*: `PostGIS <https://postgis.net/docs/ST_Crosses.html>`__,
|
||||||
SpatiaLite, PGRaster (Conversion)
|
MySQL, SpatiaLite, PGRaster (Conversion)
|
||||||
|
|
||||||
Tests if the geometry field spatially crosses the lookup geometry.
|
Tests if the geometry field spatially crosses the lookup geometry.
|
||||||
|
|
||||||
|
@ -245,9 +250,14 @@ Example::
|
||||||
Backend SQL Equivalent
|
Backend SQL Equivalent
|
||||||
========== ==========================
|
========== ==========================
|
||||||
PostGIS ``ST_Crosses(poly, geom)``
|
PostGIS ``ST_Crosses(poly, geom)``
|
||||||
|
MySQL ``ST_Crosses(poly, geom)``
|
||||||
SpatiaLite ``Crosses(poly, geom)``
|
SpatiaLite ``Crosses(poly, geom)``
|
||||||
========== ==========================
|
========== ==========================
|
||||||
|
|
||||||
|
.. versionchanged:: 3.0
|
||||||
|
|
||||||
|
MySQL support was added.
|
||||||
|
|
||||||
.. fieldlookup:: disjoint
|
.. fieldlookup:: disjoint
|
||||||
|
|
||||||
``disjoint``
|
``disjoint``
|
||||||
|
@ -267,10 +277,15 @@ Backend SQL Equivalent
|
||||||
========== =================================================
|
========== =================================================
|
||||||
PostGIS ``ST_Disjoint(poly, geom)``
|
PostGIS ``ST_Disjoint(poly, geom)``
|
||||||
Oracle ``SDO_GEOM.RELATE(poly, 'DISJOINT', geom, 0.05)``
|
Oracle ``SDO_GEOM.RELATE(poly, 'DISJOINT', geom, 0.05)``
|
||||||
MySQL ``MBRDisjoint(poly, geom)``
|
MySQL ``ST_Disjoint(poly, geom)``
|
||||||
SpatiaLite ``Disjoint(poly, geom)``
|
SpatiaLite ``Disjoint(poly, geom)``
|
||||||
========== =================================================
|
========== =================================================
|
||||||
|
|
||||||
|
.. versionchanged:: 3.0
|
||||||
|
|
||||||
|
In older versions, MySQL uses ``MBRDisjoint`` and operates only on bounding
|
||||||
|
boxes.
|
||||||
|
|
||||||
.. fieldlookup:: equals
|
.. fieldlookup:: equals
|
||||||
|
|
||||||
``equals``
|
``equals``
|
||||||
|
@ -290,10 +305,15 @@ Backend SQL Equivalent
|
||||||
========== =================================================
|
========== =================================================
|
||||||
PostGIS ``ST_Equals(poly, geom)``
|
PostGIS ``ST_Equals(poly, geom)``
|
||||||
Oracle ``SDO_EQUAL(poly, geom)``
|
Oracle ``SDO_EQUAL(poly, geom)``
|
||||||
MySQL ``MBREquals(poly, geom)``
|
MySQL ``ST_Equals(poly, geom)``
|
||||||
SpatiaLite ``Equals(poly, geom)``
|
SpatiaLite ``Equals(poly, geom)``
|
||||||
========== =================================================
|
========== =================================================
|
||||||
|
|
||||||
|
.. versionchanged:: 3.0
|
||||||
|
|
||||||
|
In older versions, MySQL uses ``MBREquals`` and operates only on bounding
|
||||||
|
boxes.
|
||||||
|
|
||||||
.. fieldlookup:: exact
|
.. fieldlookup:: exact
|
||||||
.. fieldlookup:: same_as
|
.. fieldlookup:: same_as
|
||||||
|
|
||||||
|
@ -303,8 +323,8 @@ SpatiaLite ``Equals(poly, geom)``
|
||||||
*Availability*: `PostGIS <https://postgis.net/docs/ST_Geometry_Same.html>`__,
|
*Availability*: `PostGIS <https://postgis.net/docs/ST_Geometry_Same.html>`__,
|
||||||
Oracle, MySQL, SpatiaLite, PGRaster (Bilateral)
|
Oracle, MySQL, SpatiaLite, PGRaster (Bilateral)
|
||||||
|
|
||||||
Tests if the geometry field is "equal" to the lookup geometry. On Oracle and
|
Tests if the geometry field is "equal" to the lookup geometry. On Oracle,
|
||||||
SpatiaLite it tests spatial equality, while on MySQL and PostGIS it tests
|
MySQL, and SpatiaLite, it tests spatial equality, while on PostGIS it tests
|
||||||
equality of bounding boxes.
|
equality of bounding boxes.
|
||||||
|
|
||||||
Example::
|
Example::
|
||||||
|
@ -316,10 +336,15 @@ Backend SQL Equivalent
|
||||||
========== =================================================
|
========== =================================================
|
||||||
PostGIS ``poly ~= geom``
|
PostGIS ``poly ~= geom``
|
||||||
Oracle ``SDO_EQUAL(poly, geom)``
|
Oracle ``SDO_EQUAL(poly, geom)``
|
||||||
MySQL ``MBREquals(poly, geom)``
|
MySQL ``ST_Equals(poly, geom)``
|
||||||
SpatiaLite ``Equals(poly, geom)``
|
SpatiaLite ``Equals(poly, geom)``
|
||||||
========== =================================================
|
========== =================================================
|
||||||
|
|
||||||
|
.. versionchanged:: 3.0
|
||||||
|
|
||||||
|
In older versions, MySQL uses ``MBREquals`` and operates only on bounding
|
||||||
|
boxes.
|
||||||
|
|
||||||
.. fieldlookup:: intersects
|
.. fieldlookup:: intersects
|
||||||
|
|
||||||
``intersects``
|
``intersects``
|
||||||
|
@ -339,10 +364,15 @@ Backend SQL Equivalent
|
||||||
========== =================================================
|
========== =================================================
|
||||||
PostGIS ``ST_Intersects(poly, geom)``
|
PostGIS ``ST_Intersects(poly, geom)``
|
||||||
Oracle ``SDO_OVERLAPBDYINTERSECT(poly, geom)``
|
Oracle ``SDO_OVERLAPBDYINTERSECT(poly, geom)``
|
||||||
MySQL ``MBRIntersects(poly, geom)``
|
MySQL ``ST_Intersects(poly, geom)``
|
||||||
SpatiaLite ``Intersects(poly, geom)``
|
SpatiaLite ``Intersects(poly, geom)``
|
||||||
========== =================================================
|
========== =================================================
|
||||||
|
|
||||||
|
.. versionchanged:: 3.0
|
||||||
|
|
||||||
|
In older versions, MySQL uses ``MBRIntersects`` and operates only on
|
||||||
|
bounding boxes.
|
||||||
|
|
||||||
.. fieldlookup:: isvalid
|
.. fieldlookup:: isvalid
|
||||||
|
|
||||||
``isvalid``
|
``isvalid``
|
||||||
|
@ -379,10 +409,15 @@ Backend SQL Equivalent
|
||||||
========== ============================
|
========== ============================
|
||||||
PostGIS ``ST_Overlaps(poly, geom)``
|
PostGIS ``ST_Overlaps(poly, geom)``
|
||||||
Oracle ``SDO_OVERLAPS(poly, geom)``
|
Oracle ``SDO_OVERLAPS(poly, geom)``
|
||||||
MySQL ``MBROverlaps(poly, geom)``
|
MySQL ``ST_Overlaps(poly, geom)``
|
||||||
SpatiaLite ``Overlaps(poly, geom)``
|
SpatiaLite ``Overlaps(poly, geom)``
|
||||||
========== ============================
|
========== ============================
|
||||||
|
|
||||||
|
.. versionchanged:: 3.0
|
||||||
|
|
||||||
|
In older versions, MySQL uses ``MBROverlaps`` and operates only on bounding
|
||||||
|
boxes.
|
||||||
|
|
||||||
.. fieldlookup:: relate
|
.. fieldlookup:: relate
|
||||||
|
|
||||||
``relate``
|
``relate``
|
||||||
|
@ -464,11 +499,16 @@ Example::
|
||||||
Backend SQL Equivalent
|
Backend SQL Equivalent
|
||||||
========== ==========================
|
========== ==========================
|
||||||
PostGIS ``ST_Touches(poly, geom)``
|
PostGIS ``ST_Touches(poly, geom)``
|
||||||
MySQL ``MBRTouches(poly, geom)``
|
MySQL ``ST_Touches(poly, geom)``
|
||||||
Oracle ``SDO_TOUCH(poly, geom)``
|
Oracle ``SDO_TOUCH(poly, geom)``
|
||||||
SpatiaLite ``Touches(poly, geom)``
|
SpatiaLite ``Touches(poly, geom)``
|
||||||
========== ==========================
|
========== ==========================
|
||||||
|
|
||||||
|
.. versionchanged:: 3.0
|
||||||
|
|
||||||
|
In older versions, MySQL uses ``MBRTouches`` and operates only on bounding
|
||||||
|
boxes.
|
||||||
|
|
||||||
.. fieldlookup:: within
|
.. fieldlookup:: within
|
||||||
|
|
||||||
``within``
|
``within``
|
||||||
|
@ -487,11 +527,16 @@ Example::
|
||||||
Backend SQL Equivalent
|
Backend SQL Equivalent
|
||||||
========== ==========================
|
========== ==========================
|
||||||
PostGIS ``ST_Within(poly, geom)``
|
PostGIS ``ST_Within(poly, geom)``
|
||||||
MySQL ``MBRWithin(poly, geom)``
|
MySQL ``ST_Within(poly, geom)``
|
||||||
Oracle ``SDO_INSIDE(poly, geom)``
|
Oracle ``SDO_INSIDE(poly, geom)``
|
||||||
SpatiaLite ``Within(poly, geom)``
|
SpatiaLite ``Within(poly, geom)``
|
||||||
========== ==========================
|
========== ==========================
|
||||||
|
|
||||||
|
.. versionchanged:: 3.0
|
||||||
|
|
||||||
|
In older versions, MySQL uses ``MBRWithin`` and operates only on bounding
|
||||||
|
boxes.
|
||||||
|
|
||||||
.. fieldlookup:: left
|
.. fieldlookup:: left
|
||||||
|
|
||||||
``left``
|
``left``
|
||||||
|
|
|
@ -59,7 +59,7 @@ supported versions, and any notes for each of the supported database backends:
|
||||||
Database Library Requirements Supported Versions Notes
|
Database Library Requirements Supported Versions Notes
|
||||||
================== ============================== ================== =========================================
|
================== ============================== ================== =========================================
|
||||||
PostgreSQL GEOS, GDAL, PROJ.4, PostGIS 9.5+ Requires PostGIS.
|
PostgreSQL GEOS, GDAL, PROJ.4, PostGIS 9.5+ Requires PostGIS.
|
||||||
MySQL GEOS, GDAL 5.6+ Not OGC-compliant; :ref:`limited functionality <mysql-spatial-limitations>`.
|
MySQL GEOS, GDAL 5.6.1+ :ref:`Limited functionality <mysql-spatial-limitations>`.
|
||||||
Oracle GEOS, GDAL 12.2+ XE not supported.
|
Oracle GEOS, GDAL 12.2+ XE not supported.
|
||||||
SQLite GEOS, GDAL, PROJ.4, SpatiaLite 3.8.3+ Requires SpatiaLite 4.3+
|
SQLite GEOS, GDAL, PROJ.4, SpatiaLite 3.8.3+ Requires SpatiaLite 4.3+
|
||||||
================== ============================== ================== =========================================
|
================== ============================== ================== =========================================
|
||||||
|
|
|
@ -64,7 +64,8 @@ Minor features
|
||||||
:mod:`django.contrib.gis`
|
:mod:`django.contrib.gis`
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
* ...
|
* Allowed MySQL spatial lookup functions to operate on real geometries.
|
||||||
|
Previous support was limited to bounding boxes.
|
||||||
|
|
||||||
:mod:`django.contrib.messages`
|
:mod:`django.contrib.messages`
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import tempfile
|
import tempfile
|
||||||
|
import unittest
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
|
|
||||||
from django.contrib.gis import gdal
|
from django.contrib.gis import gdal
|
||||||
|
@ -226,14 +227,15 @@ class GeoLookupTest(TestCase):
|
||||||
|
|
||||||
def test_disjoint_lookup(self):
|
def test_disjoint_lookup(self):
|
||||||
"Testing the `disjoint` lookup type."
|
"Testing the `disjoint` lookup type."
|
||||||
|
if (connection.vendor == 'mysql' and not connection.mysql_is_mariadb and
|
||||||
|
connection.mysql_version < (8, 0, 0)):
|
||||||
|
raise unittest.SkipTest('MySQL < 8 gives different results.')
|
||||||
ptown = City.objects.get(name='Pueblo')
|
ptown = City.objects.get(name='Pueblo')
|
||||||
qs1 = City.objects.filter(point__disjoint=ptown.point)
|
qs1 = City.objects.filter(point__disjoint=ptown.point)
|
||||||
self.assertEqual(7, qs1.count())
|
self.assertEqual(7, qs1.count())
|
||||||
|
qs2 = State.objects.filter(poly__disjoint=ptown.point)
|
||||||
if connection.features.supports_real_shape_operations:
|
self.assertEqual(1, qs2.count())
|
||||||
qs2 = State.objects.filter(poly__disjoint=ptown.point)
|
self.assertEqual('Kansas', qs2[0].name)
|
||||||
self.assertEqual(1, qs2.count())
|
|
||||||
self.assertEqual('Kansas', qs2[0].name)
|
|
||||||
|
|
||||||
def test_contains_contained_lookups(self):
|
def test_contains_contained_lookups(self):
|
||||||
"Testing the 'contained', 'contains', and 'bbcontains' lookup types."
|
"Testing the 'contained', 'contains', and 'bbcontains' lookup types."
|
||||||
|
@ -271,8 +273,7 @@ class GeoLookupTest(TestCase):
|
||||||
# Pueblo and Oklahoma City (even though OK City is within the bounding box of Texas)
|
# Pueblo and Oklahoma City (even though OK City is within the bounding box of Texas)
|
||||||
# are not contained in Texas or New Zealand.
|
# are not contained in Texas or New Zealand.
|
||||||
self.assertEqual(len(Country.objects.filter(mpoly__contains=pueblo.point)), 0) # Query w/GEOSGeometry object
|
self.assertEqual(len(Country.objects.filter(mpoly__contains=pueblo.point)), 0) # Query w/GEOSGeometry object
|
||||||
self.assertEqual(len(Country.objects.filter(mpoly__contains=okcity.point.wkt)),
|
self.assertEqual(len(Country.objects.filter(mpoly__contains=okcity.point.wkt)), 0) # Query w/WKT
|
||||||
0 if connection.features.supports_real_shape_operations else 1) # Query w/WKT
|
|
||||||
|
|
||||||
# OK City is contained w/in bounding box of Texas.
|
# OK City is contained w/in bounding box of Texas.
|
||||||
if connection.features.supports_bbcontains_lookup:
|
if connection.features.supports_bbcontains_lookup:
|
||||||
|
@ -570,13 +571,7 @@ class GeoQuerySetTest(TestCase):
|
||||||
"""
|
"""
|
||||||
tex_cities = City.objects.filter(
|
tex_cities = City.objects.filter(
|
||||||
point__within=Country.objects.filter(name='Texas').values('mpoly')).order_by('name')
|
point__within=Country.objects.filter(name='Texas').values('mpoly')).order_by('name')
|
||||||
expected = ['Dallas', 'Houston']
|
self.assertEqual(list(tex_cities.values_list('name', flat=True)), ['Dallas', 'Houston'])
|
||||||
if not connection.features.supports_real_shape_operations:
|
|
||||||
expected.append('Oklahoma City')
|
|
||||||
self.assertEqual(
|
|
||||||
list(tex_cities.values_list('name', flat=True)),
|
|
||||||
expected
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_non_concrete_field(self):
|
def test_non_concrete_field(self):
|
||||||
NonConcreteModel.objects.create(point=Point(0, 0), name='name')
|
NonConcreteModel.objects.create(point=Point(0, 0), name='name')
|
||||||
|
|
Loading…
Reference in New Issue