From 7bdb9a90d06a955d0ad099f9b72e6c2c30cb89dc Mon Sep 17 00:00:00 2001 From: Justin Bronn Date: Tue, 30 Mar 2010 23:15:43 +0000 Subject: [PATCH] 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 --- .../gis/db/backends/postgis/operations.py | 12 ++++---- django/contrib/gis/tests/distapp/tests.py | 30 ++++++++++++------- docs/ref/contrib/gis/db-api.txt | 24 +++++++++------ docs/ref/contrib/gis/model-api.txt | 22 +++++++------- 4 files changed, 53 insertions(+), 35 deletions(-) diff --git a/django/contrib/gis/db/backends/postgis/operations.py b/django/contrib/gis/db/backends/postgis/operations.py index c4576a35748..eeb91050029 100644 --- a/django/contrib/gis/db/backends/postgis/operations.py +++ b/django/contrib/gis/db/backends/postgis/operations.py @@ -536,12 +536,14 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations): op = op(self.geom_func_prefix, value[1]) elif lookup_type in self.distance_functions and lookup_type != 'dwithin': if not field.geography and field.geodetic(self.connection): - # Geodetic distances are only availble from Points to PointFields. - if field.geom_type != 'POINT': - raise ValueError('PostGIS spherical operations are only valid on PointFields.') + # Geodetic distances are only availble from Points to + # PointFields on PostGIS 1.4 and below. + if not self.connection.ops.geography: + if field.geom_type != 'POINT': + raise ValueError('PostGIS spherical operations are only valid on PointFields.') - if str(geom.geom_type) != 'Point': - raise ValueError('PostGIS geometry distance parameter is required to be of type Point.') + if str(geom.geom_type) != 'Point': + raise ValueError('PostGIS geometry distance parameter is required to be of type Point.') # Setting up the geodetic operation appropriately. if nparams == 3 and value[2] == 'spheroid': diff --git a/django/contrib/gis/tests/distapp/tests.py b/django/contrib/gis/tests/distapp/tests.py index 28e50a35712..aacb610dcc4 100644 --- a/django/contrib/gis/tests/distapp/tests.py +++ b/django/contrib/gis/tests/distapp/tests.py @@ -265,21 +265,31 @@ class DistanceTest(unittest.TestCase): def test05_geodetic_distance_lookups(self): "Testing distance lookups on geodetic coordinate systems." - if not oracle: - # Oracle doesn't have this limitation -- PostGIS only allows geodetic - # distance queries from Points to PointFields on geometry columns (geography - # columns don't have that limitation). - mp = GEOSGeometry('MULTIPOINT(0 0, 5 23)') - self.assertRaises(ValueError, len, - AustraliaCity.objects.filter(point__distance_lte=(mp, D(km=100)))) + # Line is from Canberra to Sydney. Query is for all other cities within + # a 100km of that line (which should exclude only Hobart & Adelaide). + line = GEOSGeometry('LINESTRING(144.9630 -37.8143,151.2607 -33.8870)', 4326) + dist_qs = AustraliaCity.objects.filter(point__distance_lte=(line, D(km=100))) + + if oracle or connection.ops.geography: + # Oracle and PostGIS 1.5 can do distance lookups on arbitrary geometries. + self.assertEqual(9, dist_qs.count()) + self.assertEqual(['Batemans Bay', 'Canberra', 'Hillsdale', + 'Melbourne', 'Mittagong', 'Shellharbour', + 'Sydney', 'Thirroul', 'Wollongong'], + self.get_names(dist_qs)) + else: + # PostGIS 1.4 and below only allows geodetic distance queries (utilizing + # ST_Distance_Sphere/ST_Distance_Spheroid) from Points to PointFields + # on geometry columns. + self.assertRaises(ValueError, dist_qs.count) # Ensured that a ValueError was raised, none of the rest of the test is # support on this backend, so bail now. if spatialite: return - # Too many params (4 in this case) should raise a ValueError. - self.assertRaises(ValueError, len, - AustraliaCity.objects.filter(point__distance_lte=('POINT(5 23)', D(km=100), 'spheroid', '4'))) + # Too many params (4 in this case) should raise a ValueError. + self.assertRaises(ValueError, len, + AustraliaCity.objects.filter(point__distance_lte=('POINT(5 23)', D(km=100), 'spheroid', '4'))) # Not enough params should raise a ValueError. self.assertRaises(ValueError, len, diff --git a/docs/ref/contrib/gis/db-api.txt b/docs/ref/contrib/gis/db-api.txt index 017c9ec1427..0959156c430 100644 --- a/docs/ref/contrib/gis/db-api.txt +++ b/docs/ref/contrib/gis/db-api.txt @@ -160,21 +160,26 @@ to be in the units of the field. .. note:: - For PostGIS users, the routine ``ST_distance_sphere`` + For users of PostGIS 1.4 and below, the routine ``ST_Distance_Sphere`` is used by default for calculating distances on geographic coordinate systems - -- which may only be called with point geometries [#fndistsphere]_. Thus, - geographic distance lookups on traditional PostGIS geometry columns are + (e.g., WGS84) -- which may only be called with point geometries [#fndistsphere14]_. + Thus, geographic distance lookups on traditional PostGIS geometry columns are only allowed on :class:`PointField` model fields using a point for the geometry parameter. .. note:: - PostGIS 1.5 introduced :ref:`geography columns `, which - is limited on what geometry types distance queries are performed with. In - other words, if you have ``geography=True`` in your geometry field - definition you'll be allowed to peform arbitrary distance queries with your - data in geodetic units of WGS84. + In PostGIS 1.5, ``ST_Distance_Sphere`` does *not* limit the geometry types + geographic distance queries are performed with. [#fndistsphere15]_ However, + these queries may take a long time, as great-circle distances must be + calculated on the fly for *every* row in the query. This is because the + spatial index on traditional geometry fields cannot be used. + For much better performance on WGS84 distance queries, consider using + :ref:`geography columns ` in your database instead because + they are able to use their spatial index in distance queries. + You can tell GeoDjango to use a geography column by setting ``geography=True`` + in your field definition. For example, let's say we have a ``SouthTexasCity`` model (from the `GeoDjango distance tests`__ ) on a *projected* coordinate system valid for cities @@ -300,5 +305,6 @@ Method PostGIS Oracle SpatiaLite .. [#fnwkt] *See* Open Geospatial Consortium, Inc., `OpenGIS Simple Feature Specification For SQL `_, Document 99-049 (May 5, 1999), at Ch. 3.2.5, p. 3-11 (SQL Textual Representation of Geometry). .. [#fnewkb] *See* `PostGIS EWKB, EWKT and Canonical Forms `_, PostGIS documentation at Ch. 4.1.2. .. [#fngeojson] *See* Howard Butler, Martin Daly, Allan Doyle, Tim Schaub, & Christopher Schmidt, `The GeoJSON Format Specification `_, Revision 1.0 (June 16, 2008). -.. [#fndistsphere] *See* PostGIS 1.5 ``ST_distance_sphere`` `documentation `_. +.. [#fndistsphere14] *See* `PostGIS 1.4 documentation `_ on ``ST_distance_sphere``. +.. [#fndistsphere15] *See* `PostGIS 1.5 documentation `_ on ``ST_distance_sphere``. .. [#] MySQL only supports bounding box operations (known as minimum bounding rectangles, or MBR, in MySQL). Thus, spatial lookups such as :lookup:`contains ` are really equivalent to :lookup:`bbcontains`. diff --git a/docs/ref/contrib/gis/model-api.txt b/docs/ref/contrib/gis/model-api.txt index 6b7b20d4789..7c83a7e267d 100644 --- a/docs/ref/contrib/gis/model-api.txt +++ b/docs/ref/contrib/gis/model-api.txt @@ -107,10 +107,11 @@ a flat surface is a straight line, the shortest path between two points on a cur surface (such as the earth) is an *arc* of a `great circle`__. [#fnthematic]_ Thus, additional computation is required to obtain distances in planar units (e.g., kilometers and miles). Using a geographic coordinate system may introduce -complications for the developer later on. For example, PostGIS does not -have the capability to perform distance calculations between non-point -geometries using geographic coordinate systems, e.g., constructing a query to -find all points within 5 miles of a county boundary stored as WGS84. [#fndist]_ +complications for the developer later on. For example, PostGIS versions 1.4 +and below do not have the capability to perform distance calculations between +non-point geometries using geographic coordinate systems, e.g., constructing a +query to find all points within 5 miles of a county boundary stored as WGS84. +[#fndist]_ Portions of the earth's surface may projected onto a two-dimensional, or Cartesian, plane. Projected coordinate systems are especially convenient @@ -123,9 +124,10 @@ calculations. .. note:: If you wish to peform arbitrary distance queries using non-point - geometries, consider using PostGIS 1.5 and enabling the - :attr:`GeometryField.geography` keyword to use the - :ref:`geography database type ` instead. + geometries in WGS84, consider upgrading to PostGIS 1.5. For + better performance, enable the :attr:`GeometryField.geography` + keyword so that :ref:`geography database type ` + is used instead. Additional Resources: @@ -182,7 +184,7 @@ three-dimensonal support. .. attribute:: GeometryField.geography -If set to ``True``, this option will use create a database column of +If set to ``True``, this option will create a database column of type geography, rather than geometry. Please refer to the :ref:`geography type ` section below for more details. @@ -223,8 +225,6 @@ For more information, the PostGIS documentation contains a helpful section on determining `when to use geography data type over geometry data type `_. - - ``GeoManager`` ============== @@ -262,5 +262,5 @@ for example:: .. [#fnsrid] Typically, SRID integer corresponds to an EPSG (`European Petroleum Survey Group `_) identifier. However, it may also be associated with custom projections defined in spatial database's spatial reference systems table. .. [#fnharvard] Harvard Graduate School of Design, `An Overview of Geodesy and Geographic Referencing Systems `_. This is an excellent resource for an overview of principles relating to geographic and Cartesian coordinate systems. .. [#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. -.. [#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. +.. [#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. .. [#fngeography] Please refer to the `PostGIS Geography Type `_ documentation for more details.