Skip to content

Commit 7bdb9a9

Browse files
committed
PostGIS 1.5 allows distance queries on non-point geographic geometry columns with ST_Distance_Sphere, enabled this functionality.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@12890 bcc190cf-cafb-0310-a4f2-bffc1f526a37
1 parent dafc077 commit 7bdb9a9

File tree

4 files changed

+54
-36
lines changed

4 files changed

+54
-36
lines changed

django/contrib/gis/db/backends/postgis/operations.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -536,12 +536,14 @@ def spatial_lookup_sql(self, lvalue, lookup_type, value, field, qn):
536536
op = op(self.geom_func_prefix, value[1])
537537
elif lookup_type in self.distance_functions and lookup_type != 'dwithin':
538538
if not field.geography and field.geodetic(self.connection):
539-
# Geodetic distances are only availble from Points to PointFields.
540-
if field.geom_type != 'POINT':
541-
raise ValueError('PostGIS spherical operations are only valid on PointFields.')
542-
543-
if str(geom.geom_type) != 'Point':
544-
raise ValueError('PostGIS geometry distance parameter is required to be of type Point.')
539+
# Geodetic distances are only availble from Points to
540+
# PointFields on PostGIS 1.4 and below.
541+
if not self.connection.ops.geography:
542+
if field.geom_type != 'POINT':
543+
raise ValueError('PostGIS spherical operations are only valid on PointFields.')
544+
545+
if str(geom.geom_type) != 'Point':
546+
raise ValueError('PostGIS geometry distance parameter is required to be of type Point.')
545547

546548
# Setting up the geodetic operation appropriately.
547549
if nparams == 3 and value[2] == 'spheroid':

django/contrib/gis/tests/distapp/tests.py

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -265,21 +265,31 @@ def test04_distance_lookups(self):
265265

266266
def test05_geodetic_distance_lookups(self):
267267
"Testing distance lookups on geodetic coordinate systems."
268-
if not oracle:
269-
# Oracle doesn't have this limitation -- PostGIS only allows geodetic
270-
# distance queries from Points to PointFields on geometry columns (geography
271-
# columns don't have that limitation).
272-
mp = GEOSGeometry('MULTIPOINT(0 0, 5 23)')
273-
self.assertRaises(ValueError, len,
274-
AustraliaCity.objects.filter(point__distance_lte=(mp, D(km=100))))
268+
# Line is from Canberra to Sydney. Query is for all other cities within
269+
# a 100km of that line (which should exclude only Hobart & Adelaide).
270+
line = GEOSGeometry('LINESTRING(144.9630 -37.8143,151.2607 -33.8870)', 4326)
271+
dist_qs = AustraliaCity.objects.filter(point__distance_lte=(line, D(km=100)))
272+
273+
if oracle or connection.ops.geography:
274+
# Oracle and PostGIS 1.5 can do distance lookups on arbitrary geometries.
275+
self.assertEqual(9, dist_qs.count())
276+
self.assertEqual(['Batemans Bay', 'Canberra', 'Hillsdale',
277+
'Melbourne', 'Mittagong', 'Shellharbour',
278+
'Sydney', 'Thirroul', 'Wollongong'],
279+
self.get_names(dist_qs))
280+
else:
281+
# PostGIS 1.4 and below only allows geodetic distance queries (utilizing
282+
# ST_Distance_Sphere/ST_Distance_Spheroid) from Points to PointFields
283+
# on geometry columns.
284+
self.assertRaises(ValueError, dist_qs.count)
275285

276286
# Ensured that a ValueError was raised, none of the rest of the test is
277287
# support on this backend, so bail now.
278288
if spatialite: return
279289

280-
# Too many params (4 in this case) should raise a ValueError.
281-
self.assertRaises(ValueError, len,
282-
AustraliaCity.objects.filter(point__distance_lte=('POINT(5 23)', D(km=100), 'spheroid', '4')))
290+
# Too many params (4 in this case) should raise a ValueError.
291+
self.assertRaises(ValueError, len,
292+
AustraliaCity.objects.filter(point__distance_lte=('POINT(5 23)', D(km=100), 'spheroid', '4')))
283293

284294
# Not enough params should raise a ValueError.
285295
self.assertRaises(ValueError, len,

docs/ref/contrib/gis/db-api.txt

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -160,21 +160,26 @@ to be in the units of the field.
160160

161161
.. note::
162162

163-
For PostGIS users, the routine ``ST_distance_sphere``
163+
For users of PostGIS 1.4 and below, the routine ``ST_Distance_Sphere``
164164
is used by default for calculating distances on geographic coordinate systems
165-
-- which may only be called with point geometries [#fndistsphere]_. Thus,
166-
geographic distance lookups on traditional PostGIS geometry columns are
165+
(e.g., WGS84) -- which may only be called with point geometries [#fndistsphere14]_.
166+
Thus, geographic distance lookups on traditional PostGIS geometry columns are
167167
only allowed on :class:`PointField` model fields using a point for the
168168
geometry parameter.
169169

170170
.. note::
171171

172-
PostGIS 1.5 introduced :ref:`geography columns <geography-type>`, which
173-
is limited on what geometry types distance queries are performed with. In
174-
other words, if you have ``geography=True`` in your geometry field
175-
definition you'll be allowed to peform arbitrary distance queries with your
176-
data in geodetic units of WGS84.
172+
In PostGIS 1.5, ``ST_Distance_Sphere`` does *not* limit the geometry types
173+
geographic distance queries are performed with. [#fndistsphere15]_ However,
174+
these queries may take a long time, as great-circle distances must be
175+
calculated on the fly for *every* row in the query. This is because the
176+
spatial index on traditional geometry fields cannot be used.
177177

178+
For much better performance on WGS84 distance queries, consider using
179+
:ref:`geography columns <geography-type>` in your database instead because
180+
they are able to use their spatial index in distance queries.
181+
You can tell GeoDjango to use a geography column by setting ``geography=True``
182+
in your field definition.
178183

179184
For example, let's say we have a ``SouthTexasCity`` model (from the
180185
`GeoDjango distance tests`__ ) on a *projected* coordinate system valid for cities
@@ -300,5 +305,6 @@ Method PostGIS Oracle SpatiaLite
300305
.. [#fnwkt] *See* Open Geospatial Consortium, Inc., `OpenGIS Simple Feature Specification For SQL <http://www.opengis.org/docs/99-049.pdf>`_, Document 99-049 (May 5, 1999), at Ch. 3.2.5, p. 3-11 (SQL Textual Representation of Geometry).
301306
.. [#fnewkb] *See* `PostGIS EWKB, EWKT and Canonical Forms <http://postgis.refractions.net/documentation/manual-1.5/ch04.html#EWKB_EWKT>`_, PostGIS documentation at Ch. 4.1.2.
302307
.. [#fngeojson] *See* Howard Butler, Martin Daly, Allan Doyle, Tim Schaub, & Christopher Schmidt, `The GeoJSON Format Specification <http://geojson.org/geojson-spec.html>`_, Revision 1.0 (June 16, 2008).
303-
.. [#fndistsphere] *See* PostGIS 1.5 ``ST_distance_sphere`` `documentation <http://postgis.refractions.net/documentation/manual-1.5/ST_Distance_Sphere.html>`_.
308+
.. [#fndistsphere14] *See* `PostGIS 1.4 documentation <http://postgis.refractions.net/documentation/manual-1.4/ST_Distance_Sphere.html>`_ on ``ST_distance_sphere``.
309+
.. [#fndistsphere15] *See* `PostGIS 1.5 documentation <http://postgis.refractions.net/documentation/manual-1.5/ST_Distance_Sphere.html>`_ on ``ST_distance_sphere``.
304310
.. [#] MySQL only supports bounding box operations (known as minimum bounding rectangles, or MBR, in MySQL). Thus, spatial lookups such as :lookup:`contains <gis-contains>` are really equivalent to :lookup:`bbcontains`.

docs/ref/contrib/gis/model-api.txt

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,11 @@ a flat surface is a straight line, the shortest path between two points on a cur
107107
surface (such as the earth) is an *arc* of a `great circle`__. [#fnthematic]_ Thus,
108108
additional computation is required to obtain distances in planar units (e.g.,
109109
kilometers and miles). Using a geographic coordinate system may introduce
110-
complications for the developer later on. For example, PostGIS does not
111-
have the capability to perform distance calculations between non-point
112-
geometries using geographic coordinate systems, e.g., constructing a query to
113-
find all points within 5 miles of a county boundary stored as WGS84. [#fndist]_
110+
complications for the developer later on. For example, PostGIS versions 1.4
111+
and below do not have the capability to perform distance calculations between
112+
non-point geometries using geographic coordinate systems, e.g., constructing a
113+
query to find all points within 5 miles of a county boundary stored as WGS84.
114+
[#fndist]_
114115

115116
Portions of the earth's surface may projected onto a two-dimensional, or
116117
Cartesian, plane. Projected coordinate systems are especially convenient
@@ -123,9 +124,10 @@ calculations.
123124
.. note::
124125

125126
If you wish to peform arbitrary distance queries using non-point
126-
geometries, consider using PostGIS 1.5 and enabling the
127-
:attr:`GeometryField.geography` keyword to use the
128-
:ref:`geography database type <geography-type>` instead.
127+
geometries in WGS84, consider upgrading to PostGIS 1.5. For
128+
better performance, enable the :attr:`GeometryField.geography`
129+
keyword so that :ref:`geography database type <geography-type>`
130+
is used instead.
129131

130132
Additional Resources:
131133

@@ -182,7 +184,7 @@ three-dimensonal support.
182184

183185
.. attribute:: GeometryField.geography
184186

185-
If set to ``True``, this option will use create a database column of
187+
If set to ``True``, this option will create a database column of
186188
type geography, rather than geometry. Please refer to the
187189
:ref:`geography type <geography-type>` section below for more
188190
details.
@@ -223,8 +225,6 @@ For more information, the PostGIS documentation contains a helpful section on
223225
determining `when to use geography data type over geometry data type
224226
<http://postgis.refractions.net/documentation/manual-1.5/ch04.html#PostGIS_GeographyVSGeometry>`_.
225227

226-
227-
228228
``GeoManager``
229229
==============
230230

@@ -262,5 +262,5 @@ for example::
262262
.. [#fnsrid] Typically, SRID integer corresponds to an EPSG (`European Petroleum Survey Group <http://www.epsg.org>`_) identifier. However, it may also be associated with custom projections defined in spatial database's spatial reference systems table.
263263
.. [#fnharvard] Harvard Graduate School of Design, `An Overview of Geodesy and Geographic Referencing Systems <http://www.gsd.harvard.edu/gis/manual/projections/fundamentals/>`_. This is an excellent resource for an overview of principles relating to geographic and Cartesian coordinate systems.
264264
.. [#fnthematic] Terry A. Slocum, Robert B. McMaster, Fritz C. Kessler, & Hugh H. Howard, *Thematic Cartography and Geographic Visualization* (Prentice Hall, 2nd edition), at Ch. 7.1.3.
265-
.. [#fndist] This isn't impossible using GeoDjango; you could for example, take a known point in a projected coordinate system, buffer it to the appropriate radius, and then perform an intersection operation with the buffer transformed to the geographic coordinate system.
265+
.. [#fndist] This limitation does not apply to PostGIS 1.5. It should be noted that even in previous versions of PostGIS, this isn't impossible using GeoDjango; you could for example, take a known point in a projected coordinate system, buffer it to the appropriate radius, and then perform an intersection operation with the buffer transformed to the geographic coordinate system.
266266
.. [#fngeography] Please refer to the `PostGIS Geography Type <http://postgis.refractions.net/documentation/manual-1.5/ch04.html#PostGIS_Geography>`_ documentation for more details.

0 commit comments

Comments
 (0)