mirror of
https://github.com/django/django.git
synced 2025-07-04 09:49:12 +00:00
gis: Fixed bug in GeoQuerySet.distance
caused by SQL substitution that shouldn't be done there (thanks robotika); an exception is no longer raised when trying to import the spatial metadata models on backends that do not support them (e.g., MySQL).
git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@7138 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
bdab7abe2c
commit
6fef560d97
@ -12,7 +12,7 @@ from django.contrib.gis.oldforms import WKTField
|
|||||||
# Attempting to get the spatial reference system.
|
# Attempting to get the spatial reference system.
|
||||||
try:
|
try:
|
||||||
from django.contrib.gis.models import SpatialRefSys
|
from django.contrib.gis.models import SpatialRefSys
|
||||||
except NotImplementedError:
|
except ImportError:
|
||||||
SpatialRefSys = None
|
SpatialRefSys = None
|
||||||
|
|
||||||
#TODO: Flesh out widgets; consider adding support for OGR Geometry proxies.
|
#TODO: Flesh out widgets; consider adding support for OGR Geometry proxies.
|
||||||
@ -22,6 +22,9 @@ class GeometryField(SpatialBackend.Field):
|
|||||||
# The OpenGIS Geometry name.
|
# The OpenGIS Geometry name.
|
||||||
_geom = 'GEOMETRY'
|
_geom = 'GEOMETRY'
|
||||||
|
|
||||||
|
# Geodetic units.
|
||||||
|
geodetic_units = ('Decimal Degree', 'degree')
|
||||||
|
|
||||||
def __init__(self, srid=4326, spatial_index=True, dim=2, **kwargs):
|
def __init__(self, srid=4326, spatial_index=True, dim=2, **kwargs):
|
||||||
"""
|
"""
|
||||||
The initialization function for geometry fields. Takes the following
|
The initialization function for geometry fields. Takes the following
|
||||||
@ -82,8 +85,9 @@ class GeometryField(SpatialBackend.Field):
|
|||||||
`D(km=1)` was passed in and the units of the field were in meters,
|
`D(km=1)` was passed in and the units of the field were in meters,
|
||||||
then 1000 would be returned.
|
then 1000 would be returned.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if isinstance(dist, Distance):
|
if isinstance(dist, Distance):
|
||||||
if self._unit_name in ('Decimal Degree', 'degree'):
|
if self._unit_name in self.geodetic_units:
|
||||||
# Spherical distance calculation parameter should be in meters.
|
# Spherical distance calculation parameter should be in meters.
|
||||||
dist_param = dist.m
|
dist_param = dist.m
|
||||||
else:
|
else:
|
||||||
@ -93,7 +97,7 @@ class GeometryField(SpatialBackend.Field):
|
|||||||
dist_param = dist
|
dist_param = dist
|
||||||
|
|
||||||
# Sphereical distance query; returning meters.
|
# Sphereical distance query; returning meters.
|
||||||
if SpatialBackend.name == 'postgis' and self._unit_name == 'degree':
|
if SpatialBackend.name == 'postgis' and self._unit_name in self.geodetic_units:
|
||||||
return [gqn(self._spheroid), dist_param]
|
return [gqn(self._spheroid), dist_param]
|
||||||
else:
|
else:
|
||||||
return [dist_param]
|
return [dist_param]
|
||||||
|
@ -4,10 +4,10 @@ from django.db import connection
|
|||||||
from django.db.models.query import EmptyResultSet, Q, QuerySet, handle_legacy_orderlist, quote_only_if_word, orderfield2column, fill_table_cache
|
from django.db.models.query import EmptyResultSet, Q, QuerySet, handle_legacy_orderlist, quote_only_if_word, orderfield2column, fill_table_cache
|
||||||
from django.db.models.fields import FieldDoesNotExist
|
from django.db.models.fields import FieldDoesNotExist
|
||||||
from django.utils.datastructures import SortedDict
|
from django.utils.datastructures import SortedDict
|
||||||
from django.contrib.gis.db.models.fields import GeometryField
|
from django.contrib.gis.db.models.fields import GeometryField, PointField
|
||||||
# parse_lookup depends on the spatial database backend.
|
# parse_lookup depends on the spatial database backend.
|
||||||
from django.contrib.gis.db.backend import gqn, parse_lookup, SpatialBackend
|
from django.contrib.gis.db.backend import gqn, parse_lookup, SpatialBackend
|
||||||
from django.contrib.gis.geos import GEOSGeometry
|
from django.contrib.gis.geos import GEOSGeometry, Point
|
||||||
|
|
||||||
# Shortcut booleans for determining the backend.
|
# Shortcut booleans for determining the backend.
|
||||||
oracle = SpatialBackend.name == 'oracle'
|
oracle = SpatialBackend.name == 'oracle'
|
||||||
@ -279,24 +279,27 @@ class GeoQuerySet(QuerySet):
|
|||||||
# `distance_lte` lookup type.
|
# `distance_lte` lookup type.
|
||||||
where, params = geo_field.get_db_prep_lookup('distance_lte', (geom, 0))
|
where, params = geo_field.get_db_prep_lookup('distance_lte', (geom, 0))
|
||||||
if oracle:
|
if oracle:
|
||||||
# The `tolerance` keyword may be used for Oracle.
|
# The `tolerance` keyword may be used for Oracle; the tolerance is
|
||||||
|
# in meters -- a default of 5 centimeters is used.
|
||||||
tolerance = kwargs.get('tolerance', 0.05)
|
tolerance = kwargs.get('tolerance', 0.05)
|
||||||
|
dist_select = {'distance' : '%s(%s, %s, %s)' % (DISTANCE, geo_col, where[0], tolerance)}
|
||||||
# More legwork here because the OracleSpatialAdaptor doesn't do
|
|
||||||
# quoting of the WKT.
|
|
||||||
tmp_params = [gqn(str(params[0]))]
|
|
||||||
tmp_params.extend(params[1:])
|
|
||||||
dsql = where[0] % tuple(tmp_params)
|
|
||||||
dist_select = {'distance' : '%s(%s, %s, %s)' % (DISTANCE, geo_col, dsql, tolerance)}
|
|
||||||
else:
|
else:
|
||||||
dsql = where[0] % tuple(params)
|
|
||||||
if len(where) == 3:
|
if len(where) == 3:
|
||||||
|
# Spherical distance calculation was requested (b/c spheroid
|
||||||
|
# parameter was attached) However, the PostGIS ST_distance_spheroid()
|
||||||
|
# procedure may only do queries from point columns to point geometries
|
||||||
|
# some error checking is required.
|
||||||
|
if not isinstance(geo_field, PointField):
|
||||||
|
raise TypeError('Spherical distance calculation only supported on PointFields.')
|
||||||
|
if not isinstance(GEOSGeometry(params[0].wkb), Point):
|
||||||
|
raise TypeError('Spherical distance calculation only supported with Point Geometry parameters')
|
||||||
|
|
||||||
# Call to distance_spheroid() requires the spheroid as well.
|
# Call to distance_spheroid() requires the spheroid as well.
|
||||||
dist_sql = '%s(%s, %s, %s)' % (SpatialBackend.distance_spheroid, geo_col, dsql, where[1])
|
dist_sql = '%s(%s, %s, %s)' % (SpatialBackend.distance_spheroid, geo_col, where[0], where[1])
|
||||||
else:
|
else:
|
||||||
dist_sql = '%s(%s, %s)' % (DISTANCE, geo_col, dsql)
|
dist_sql = '%s(%s, %s)' % (DISTANCE, geo_col, where[0])
|
||||||
dist_select = {'distance' : dist_sql}
|
dist_select = {'distance' : dist_sql}
|
||||||
return self.extra(select=dist_select)
|
return self.extra(select=dist_select, params=params)
|
||||||
|
|
||||||
def extent(self, field_name=None):
|
def extent(self, field_name=None):
|
||||||
"""
|
"""
|
||||||
|
@ -214,4 +214,4 @@ if settings.DATABASE_ENGINE == 'postgresql_psycopg2':
|
|||||||
elif settings.DATABASE_ENGINE == 'oracle':
|
elif settings.DATABASE_ENGINE == 'oracle':
|
||||||
from django.contrib.gis.db.backend.oracle.models import GeometryColumns, SpatialRefSys
|
from django.contrib.gis.db.backend.oracle.models import GeometryColumns, SpatialRefSys
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError('No SpatialRefSys or GeometryColumns models for backend: %s' % settings.DATABASE_ENGINE)
|
pass
|
||||||
|
@ -8,6 +8,7 @@ au_cities = (('Wollongong', 150.902, -34.4245),
|
|||||||
('Sydney', 151.26071, -33.887034),
|
('Sydney', 151.26071, -33.887034),
|
||||||
('Hobart', 147.33, -42.8827),
|
('Hobart', 147.33, -42.8827),
|
||||||
('Adelaide', 138.6, -34.9258),
|
('Adelaide', 138.6, -34.9258),
|
||||||
|
('Hillsdale', 151.231341, -33.952685),
|
||||||
)
|
)
|
||||||
|
|
||||||
stx_cities = (('Downtown Houston', 951640.547328, 4219369.26172),
|
stx_cities = (('Downtown Houston', 951640.547328, 4219369.26172),
|
||||||
|
@ -2,7 +2,7 @@ import os, unittest
|
|||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
from django.contrib.gis.gdal import DataSource
|
from django.contrib.gis.gdal import DataSource
|
||||||
from django.contrib.gis.geos import GEOSGeometry, Point
|
from django.contrib.gis.geos import GEOSGeometry, Point, LineString
|
||||||
from django.contrib.gis.measure import D # alias for Distance
|
from django.contrib.gis.measure import D # alias for Distance
|
||||||
from django.contrib.gis.db.models import GeoQ
|
from django.contrib.gis.db.models import GeoQ
|
||||||
from django.contrib.gis.tests.utils import oracle
|
from django.contrib.gis.tests.utils import oracle
|
||||||
@ -35,7 +35,7 @@ class DistanceTest(unittest.TestCase):
|
|||||||
load_cities(AustraliaCity, 4326, au_cities)
|
load_cities(AustraliaCity, 4326, au_cities)
|
||||||
|
|
||||||
self.assertEqual(10, SouthTexasCity.objects.count())
|
self.assertEqual(10, SouthTexasCity.objects.count())
|
||||||
self.assertEqual(10, AustraliaCity.objects.count())
|
self.assertEqual(11, AustraliaCity.objects.count())
|
||||||
|
|
||||||
def test02_dwithin(self):
|
def test02_dwithin(self):
|
||||||
"Testing the `dwithin` lookup type."
|
"Testing the `dwithin` lookup type."
|
||||||
@ -56,18 +56,39 @@ class DistanceTest(unittest.TestCase):
|
|||||||
138809.684197, 158309.246259, 212183.594374,
|
138809.684197, 158309.246259, 212183.594374,
|
||||||
70870.188967, 165337.758878, 102128.654360,
|
70870.188967, 165337.758878, 102128.654360,
|
||||||
139196.085105]
|
139196.085105]
|
||||||
|
|
||||||
|
# Testing when the field name is explicitly set.
|
||||||
dist1 = SouthTexasCity.objects.distance('point', lagrange)
|
dist1 = SouthTexasCity.objects.distance('point', lagrange)
|
||||||
dist2 = SouthTexasCity.objects.distance(lagrange)
|
dist2 = SouthTexasCity.objects.distance(lagrange) # Using GEOSGeometry parameter
|
||||||
|
dist3 = SouthTexasCity.objects.distance(lagrange.ewkt) # Using EWKT string parameter.
|
||||||
|
|
||||||
# Original query done on PostGIS, have to adjust AlmostEqual tolerance
|
# Original query done on PostGIS, have to adjust AlmostEqual tolerance
|
||||||
# for Oracle.
|
# for Oracle.
|
||||||
if oracle: tol = 3
|
if oracle: tol = 2
|
||||||
else: tol = 5
|
else: tol = 5
|
||||||
|
|
||||||
for qs in [dist1, dist2]:
|
# Ensuring expected distances are returned for each distance queryset.
|
||||||
|
for qs in [dist1, dist2, dist3]:
|
||||||
for i, c in enumerate(qs):
|
for i, c in enumerate(qs):
|
||||||
self.assertAlmostEqual(distances[i], c.distance, tol)
|
self.assertAlmostEqual(distances[i], c.distance, tol)
|
||||||
|
|
||||||
|
# Now testing geodetic distance aggregation.
|
||||||
|
hillsdale = AustraliaCity.objects.get(name='Hillsdale')
|
||||||
|
if not oracle:
|
||||||
|
# PostGIS is limited to disance queries only to/from point geometries,
|
||||||
|
# ensuring a TypeError is raised if something else is put in.
|
||||||
|
self.assertRaises(TypeError, AustraliaCity.objects.distance, 'LINESTRING(0 0, 1 1)')
|
||||||
|
self.assertRaises(TypeError, AustraliaCity.objects.distance, LineString((0, 0), (1, 1)))
|
||||||
|
|
||||||
|
# Got these distances using the raw SQL statement:
|
||||||
|
# SELECT ST_distance_spheroid(point, ST_GeomFromText('POINT(151.231341 -33.952685)', 4326), 'SPHEROID["WGS 84",6378137.0,298.257223563]') FROM distapp_australiacity WHERE (NOT (id = 11));
|
||||||
|
geodetic_distances = [60504.0628825298, 77023.948962654, 49154.8867507115, 90847.435881812, 217402.811862568, 709599.234619957, 640011.483583758, 7772.00667666425, 1047861.7859506, 1165126.55237647]
|
||||||
|
|
||||||
|
# Ensuring the expected distances are returned.
|
||||||
|
qs = AustraliaCity.objects.exclude(id=hillsdale.id).distance(hillsdale.point)
|
||||||
|
for i, c in enumerate(qs):
|
||||||
|
self.assertAlmostEqual(geodetic_distances[i], c.distance, tol)
|
||||||
|
|
||||||
def test04_distance_lookups(self):
|
def test04_distance_lookups(self):
|
||||||
"Testing the `distance_lt`, `distance_gt`, `distance_lte`, and `distance_gte` lookup types."
|
"Testing the `distance_lt`, `distance_gt`, `distance_lte`, and `distance_gte` lookup types."
|
||||||
# Only two cities (Houston and Southside Place) should be
|
# Only two cities (Houston and Southside Place) should be
|
||||||
|
Loading…
x
Reference in New Issue
Block a user