Fixed #28436 -- Added support for distance lookups on MySQL.
This commit is contained in:
parent
38af496b98
commit
f3bada9889
|
@ -37,7 +37,6 @@ class BaseSpatialFeatures:
|
|||
|
||||
# The following properties indicate if the database backend support
|
||||
# certain lookups (dwithin, left and right, relate, ...)
|
||||
supports_distances_lookups = True
|
||||
supports_left_right_lookups = False
|
||||
|
||||
# Does the database have raster support?
|
||||
|
@ -58,6 +57,10 @@ class BaseSpatialFeatures:
|
|||
def supports_crosses_lookup(self):
|
||||
return 'crosses' in self.connection.ops.gis_operators
|
||||
|
||||
@property
|
||||
def supports_distances_lookups(self):
|
||||
return self.has_Distance_function
|
||||
|
||||
@property
|
||||
def supports_dwithin_lookup(self):
|
||||
return 'dwithin' in self.connection.ops.gis_operators
|
||||
|
|
|
@ -10,7 +10,6 @@ class DatabaseFeatures(BaseSpatialFeatures, MySQLDatabaseFeatures):
|
|||
supports_distance_geodetic = False
|
||||
supports_length_geodetic = False
|
||||
supports_area_geodetic = False
|
||||
supports_distances_lookups = False
|
||||
supports_transform = False
|
||||
supports_real_shape_operations = False
|
||||
supports_null_geometries = False
|
||||
|
|
|
@ -4,6 +4,7 @@ from django.contrib.gis.db.backends.base.operations import (
|
|||
)
|
||||
from django.contrib.gis.db.backends.utils import SpatialOperator
|
||||
from django.contrib.gis.db.models import GeometryField, aggregates
|
||||
from django.contrib.gis.measure import Distance
|
||||
from django.db.backends.mysql.operations import DatabaseOperations
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
|
@ -87,6 +88,19 @@ class MySQLOperations(BaseSpatialOperations, DatabaseOperations):
|
|||
def geo_db_type(self, f):
|
||||
return f.geom_type
|
||||
|
||||
def get_distance(self, f, value, lookup_type):
|
||||
value = value[0]
|
||||
if isinstance(value, Distance):
|
||||
if f.geodetic(self.connection):
|
||||
raise ValueError(
|
||||
'Only numeric values of degree units are allowed on '
|
||||
'geodetic distance queries.'
|
||||
)
|
||||
dist_param = getattr(value, Distance.unit_attname(f.units_name(self.connection)))
|
||||
else:
|
||||
dist_param = value
|
||||
return [dist_param]
|
||||
|
||||
def get_db_converters(self, expression):
|
||||
converters = super().get_db_converters(expression)
|
||||
if isinstance(expression.output_field, GeometryField) and self.uses_invalid_empty_geometry_collection:
|
||||
|
|
|
@ -606,7 +606,7 @@ PostGIS equivalent::
|
|||
Distance Lookups
|
||||
================
|
||||
|
||||
*Availability*: PostGIS, Oracle, SpatiaLite, PGRaster (Native)
|
||||
*Availability*: PostGIS, Oracle, MySQL, SpatiaLite, PGRaster (Native)
|
||||
|
||||
For an overview on performing distance queries, please refer to
|
||||
the :ref:`distance queries introduction <distance-queries>`.
|
||||
|
@ -639,6 +639,10 @@ spheroid based lookups.
|
|||
|
||||
Support for the ``'spheroid'`` option on SQLite was added.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
MySQL support was added.
|
||||
|
||||
.. fieldlookup:: distance_gt
|
||||
|
||||
``distance_gt``
|
||||
|
|
|
@ -65,8 +65,8 @@ Minor features
|
|||
* 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.
|
||||
:class:`~django.contrib.gis.db.models.functions.IsValid` function,
|
||||
:lookup:`isvalid` lookup, and :ref:`distance lookups <distance-lookups>`.
|
||||
|
||||
* Added the :class:`~django.contrib.gis.db.models.functions.Azimuth` and
|
||||
:class:`~django.contrib.gis.db.models.functions.LineLocatePoint` functions,
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import unittest
|
||||
|
||||
from django.contrib.gis.db.models.functions import (
|
||||
Area, Distance, Intersection, Length, Perimeter, Transform,
|
||||
Area, Distance, Length, Perimeter, Transform, Union,
|
||||
)
|
||||
from django.contrib.gis.geos import GEOSGeometry, LineString, Point
|
||||
from django.contrib.gis.measure import D # alias for Distance
|
||||
|
@ -7,7 +9,7 @@ from django.db import connection
|
|||
from django.db.models import F, Q
|
||||
from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature
|
||||
|
||||
from ..utils import no_oracle, oracle, postgis, spatialite
|
||||
from ..utils import mysql, no_oracle, oracle, postgis, spatialite
|
||||
from .models import (
|
||||
AustraliaCity, CensusZipcode, Interstate, SouthTexasCity, SouthTexasCityFt,
|
||||
SouthTexasInterstate, SouthTexasZipcode,
|
||||
|
@ -107,8 +109,9 @@ class DistanceTest(TestCase):
|
|||
# (thus, Houston and Southside place will be excluded as tested in
|
||||
# the `test02_dwithin` above).
|
||||
for model in [SouthTexasCity, SouthTexasCityFt]:
|
||||
qs = model.objects.filter(point__distance_gte=(self.stx_pnt, D(km=7))).filter(
|
||||
point__distance_lte=(self.stx_pnt, D(km=20)),
|
||||
stx_pnt = self.stx_pnt.transform(model._meta.get_field('point').srid, clone=True)
|
||||
qs = model.objects.filter(point__distance_gte=(stx_pnt, D(km=7))).filter(
|
||||
point__distance_lte=(stx_pnt, D(km=20)),
|
||||
)
|
||||
cities = self.get_names(qs)
|
||||
self.assertEqual(cities, ['Bellaire', 'Pearland', 'West University Place'])
|
||||
|
@ -183,8 +186,9 @@ class DistanceTest(TestCase):
|
|||
|
||||
@skipUnlessDBFeature("supports_distances_lookups")
|
||||
def test_distance_lookups_with_expression_rhs(self):
|
||||
stx_pnt = self.stx_pnt.transform(SouthTexasCity._meta.get_field('point').srid, clone=True)
|
||||
qs = SouthTexasCity.objects.filter(
|
||||
point__distance_lte=(self.stx_pnt, F('radius')),
|
||||
point__distance_lte=(stx_pnt, F('radius')),
|
||||
).order_by('name')
|
||||
self.assertEqual(
|
||||
self.get_names(qs),
|
||||
|
@ -193,7 +197,7 @@ class DistanceTest(TestCase):
|
|||
|
||||
# With a combined expression
|
||||
qs = SouthTexasCity.objects.filter(
|
||||
point__distance_lte=(self.stx_pnt, F('radius') * 2),
|
||||
point__distance_lte=(stx_pnt, F('radius') * 2),
|
||||
).order_by('name')
|
||||
self.assertEqual(len(qs), 5)
|
||||
self.assertIn('Pearland', self.get_names(qs))
|
||||
|
@ -207,12 +211,18 @@ class DistanceTest(TestCase):
|
|||
self.assertEqual(self.get_names(qs), ['Canberra', 'Hobart', 'Melbourne'])
|
||||
|
||||
# With a complex geometry expression
|
||||
self.assertFalse(SouthTexasCity.objects.filter(point__distance_gt=(Intersection('point', 'point'), 0)))
|
||||
self.assertFalse(SouthTexasCity.objects.filter(point__distance_gt=(Union('point', 'point'), 0)))
|
||||
self.assertEqual(
|
||||
SouthTexasCity.objects.filter(point__distance_lte=(Intersection('point', 'point'), 0)).count(),
|
||||
SouthTexasCity.objects.filter(point__distance_lte=(Union('point', 'point'), 0)).count(),
|
||||
SouthTexasCity.objects.count(),
|
||||
)
|
||||
|
||||
@unittest.skipUnless(mysql, 'This is a MySQL-specific test')
|
||||
def test_mysql_geodetic_distance_error(self):
|
||||
msg = 'Only numeric values of degree units are allowed on geodetic distance queries.'
|
||||
with self.assertRaisesMessage(ValueError, msg):
|
||||
AustraliaCity.objects.filter(point__distance_lte=(Point(0, 0), D(m=100))).exists()
|
||||
|
||||
|
||||
'''
|
||||
=============================
|
||||
|
|
Loading…
Reference in New Issue