From a79e6b67175f532049967268c10609af6d31d140 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Wed, 14 Jan 2015 20:48:55 +0100 Subject: [PATCH] Fixed #24152 -- Deprecated GeoQuerySet aggregate methods Thanks Josh Smeaton and Tim Graham for the reviews. --- django/contrib/gis/db/models/aggregates.py | 8 +- django/contrib/gis/db/models/query.py | 28 ++++ django/contrib/gis/tests/geo3d/tests.py | 5 +- .../contrib/gis/tests/geoapp/test_regress.py | 3 +- django/contrib/gis/tests/geoapp/tests.py | 47 +++++-- django/contrib/gis/tests/relatedapp/tests.py | 30 +++- docs/internals/deprecation.txt | 3 + docs/ref/contrib/gis/db-api.txt | 22 ++- docs/ref/contrib/gis/geoquerysets.txt | 129 +++++++++++------- docs/releases/1.8.txt | 8 ++ 10 files changed, 208 insertions(+), 75 deletions(-) diff --git a/django/contrib/gis/db/models/aggregates.py b/django/contrib/gis/db/models/aggregates.py index d7db0a8dd8..0ec842de0f 100644 --- a/django/contrib/gis/db/models/aggregates.py +++ b/django/contrib/gis/db/models/aggregates.py @@ -1,5 +1,5 @@ from django.db.models.aggregates import Aggregate -from django.contrib.gis.db.models.fields import GeometryField, ExtentField +from django.contrib.gis.db.models.fields import ExtentField __all__ = ['Collect', 'Extent', 'Extent3D', 'MakeLine', 'Union'] @@ -20,9 +20,9 @@ class GeoAggregate(Aggregate): self.template = '%(function)s(SDOAGGRTYPE(%(expressions)s,%(tolerance)s))' return self.as_sql(compiler, connection) - def prepare(self, query=None, allow_joins=True, reuse=None, summarize=False): - c = super(GeoAggregate, self).prepare(query, allow_joins, reuse, summarize) - if not isinstance(self.expressions[0].output_field, GeometryField): + def resolve_expression(self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False): + c = super(GeoAggregate, self).resolve_expression(query, allow_joins, reuse, summarize, for_save) + if not hasattr(c.input_field.field, 'geom_type'): raise ValueError('Geospatial aggregates only allowed on geometry fields.') return c diff --git a/django/contrib/gis/db/models/query.py b/django/contrib/gis/db/models/query.py index 55f286ff85..c36381ecaa 100644 --- a/django/contrib/gis/db/models/query.py +++ b/django/contrib/gis/db/models/query.py @@ -1,3 +1,5 @@ +import warnings + from django.db import connections from django.db.models.expressions import RawSQL from django.db.models.fields import Field @@ -15,6 +17,7 @@ from django.contrib.gis.geometry.backend import Geometry from django.contrib.gis.measure import Area, Distance from django.utils import six +from django.utils.deprecation import RemovedInDjango20Warning class GeoQuerySet(QuerySet): @@ -65,6 +68,11 @@ class GeoQuerySet(QuerySet): This is analogous to a union operation, but much faster because boundaries are not dissolved. """ + warnings.warn( + "The collect GeoQuerySet method is deprecated. Use the Collect() " + "aggregate in an aggregate() or annotate() method.", + RemovedInDjango20Warning, stacklevel=2 + ) return self._spatial_aggregate(aggregates.Collect, **kwargs) def difference(self, geom, **kwargs): @@ -105,6 +113,11 @@ class GeoQuerySet(QuerySet): Returns the extent (aggregate) of the features in the GeoQuerySet. The extent will be returned as a 4-tuple, consisting of (xmin, ymin, xmax, ymax). """ + warnings.warn( + "The extent GeoQuerySet method is deprecated. Use the Extent() " + "aggregate in an aggregate() or annotate() method.", + RemovedInDjango20Warning, stacklevel=2 + ) return self._spatial_aggregate(aggregates.Extent, **kwargs) def extent3d(self, **kwargs): @@ -113,6 +126,11 @@ class GeoQuerySet(QuerySet): GeoQuerySet. It is returned as a 6-tuple, comprising: (xmin, ymin, zmin, xmax, ymax, zmax). """ + warnings.warn( + "The extent3d GeoQuerySet method is deprecated. Use the Extent3D() " + "aggregate in an aggregate() or annotate() method.", + RemovedInDjango20Warning, stacklevel=2 + ) return self._spatial_aggregate(aggregates.Extent3D, **kwargs) def force_rhr(self, **kwargs): @@ -215,6 +233,11 @@ class GeoQuerySet(QuerySet): this GeoQuerySet and returns it. This is a spatial aggregate method, and thus returns a geometry rather than a GeoQuerySet. """ + warnings.warn( + "The make_line GeoQuerySet method is deprecated. Use the MakeLine() " + "aggregate in an aggregate() or annotate() method.", + RemovedInDjango20Warning, stacklevel=2 + ) return self._spatial_aggregate(aggregates.MakeLine, geo_field_type=PointField, **kwargs) def mem_size(self, **kwargs): @@ -398,6 +421,11 @@ class GeoQuerySet(QuerySet): None if the GeoQuerySet is empty. The `tolerance` keyword is for Oracle backends only. """ + warnings.warn( + "The unionagg GeoQuerySet method is deprecated. Use the Union() " + "aggregate in an aggregate() or annotate() method.", + RemovedInDjango20Warning, stacklevel=2 + ) return self._spatial_aggregate(aggregates.Union, **kwargs) ### Private API -- Abstracted DRY routines. ### diff --git a/django/contrib/gis/tests/geo3d/tests.py b/django/contrib/gis/tests/geo3d/tests.py index 62e989962e..9530cd9b55 100644 --- a/django/contrib/gis/tests/geo3d/tests.py +++ b/django/contrib/gis/tests/geo3d/tests.py @@ -6,7 +6,8 @@ from unittest import skipUnless from django.contrib.gis.gdal import HAS_GDAL from django.contrib.gis.geos import HAS_GEOS -from django.test import TestCase, skipUnlessDBFeature +from django.test import TestCase, ignore_warnings, skipUnlessDBFeature +from django.utils.deprecation import RemovedInDjango20Warning from django.utils._os import upath if HAS_GEOS: @@ -206,6 +207,7 @@ class Geo3DTest(TestCase): # Ordering of points in the resulting geometry may vary between implementations self.assertSetEqual({p.ewkt for p in ref_union}, {p.ewkt for p in union}) + @ignore_warnings(category=RemovedInDjango20Warning) def test_extent(self): """ Testing the Extent3D aggregate for 3D models. @@ -223,6 +225,7 @@ class Geo3DTest(TestCase): for e3d in [extent1, extent2]: check_extent3d(e3d) self.assertIsNone(City3D.objects.none().extent3d()) + self.assertIsNone(City3D.objects.none().aggregate(Extent3D('point'))['point__extent3d']) def test_perimeter(self): """ diff --git a/django/contrib/gis/tests/geoapp/test_regress.py b/django/contrib/gis/tests/geoapp/test_regress.py index e0a7dd9d73..e5cefb0752 100644 --- a/django/contrib/gis/tests/geoapp/test_regress.py +++ b/django/contrib/gis/tests/geoapp/test_regress.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals from datetime import datetime +from django.contrib.gis.db.models import Extent from django.contrib.gis.geos import HAS_GEOS from django.contrib.gis.shortcuts import render_to_kmz from django.contrib.gis.tests.utils import no_oracle @@ -44,7 +45,7 @@ class GeoRegressionTests(TestCase): "Testing `extent` on a table with a single point. See #11827." pnt = City.objects.get(name='Pueblo').point ref_ext = (pnt.x, pnt.y, pnt.x, pnt.y) - extent = City.objects.filter(name='Pueblo').extent() + extent = City.objects.filter(name='Pueblo').aggregate(Extent('point'))['point__extent'] for ref_val, val in zip(ref_ext, extent): self.assertAlmostEqual(ref_val, val, 4) diff --git a/django/contrib/gis/tests/geoapp/tests.py b/django/contrib/gis/tests/geoapp/tests.py index 0bafe0080b..c98d244235 100644 --- a/django/contrib/gis/tests/geoapp/tests.py +++ b/django/contrib/gis/tests/geoapp/tests.py @@ -5,11 +5,13 @@ from tempfile import NamedTemporaryFile from django.db import connection from django.contrib.gis import gdal +from django.contrib.gis.db.models import Extent, MakeLine, Union from django.contrib.gis.geos import HAS_GEOS from django.contrib.gis.tests.utils import no_oracle, oracle, postgis, spatialite from django.core.management import call_command -from django.test import TestCase, skipUnlessDBFeature +from django.test import TestCase, ignore_warnings, skipUnlessDBFeature from django.utils import six +from django.utils.deprecation import RemovedInDjango20Warning if HAS_GEOS: from django.contrib.gis.geos import (fromstr, GEOSGeometry, @@ -470,19 +472,26 @@ class GeoQuerySetTest(TestCase): self.assertIsInstance(country.envelope, Polygon) @skipUnlessDBFeature("supports_extent_aggr") + @ignore_warnings(category=RemovedInDjango20Warning) def test_extent(self): - "Testing the `extent` GeoQuerySet method." + """ + Testing the (deprecated) `extent` GeoQuerySet method and the Extent + aggregate. + """ # Reference query: # `SELECT ST_extent(point) FROM geoapp_city WHERE (name='Houston' or name='Dallas');` # => BOX(-96.8016128540039 29.7633724212646,-95.3631439208984 32.7820587158203) expected = (-96.8016128540039, 29.7633724212646, -95.3631439208984, 32.782058715820) qs = City.objects.filter(name__in=('Houston', 'Dallas')) - extent = qs.extent() + extent1 = qs.extent() + extent2 = qs.aggregate(Extent('point'))['point__extent'] - for val, exp in zip(extent, expected): - self.assertAlmostEqual(exp, val, 4) + for extent in (extent1, extent2): + for val, exp in zip(extent, expected): + self.assertAlmostEqual(exp, val, 4) self.assertIsNone(City.objects.filter(name=('Smalltown')).extent()) + self.assertIsNone(City.objects.filter(name=('Smalltown')).aggregate(Extent('point'))['point__extent']) @skipUnlessDBFeature("has_force_rhr_method") def test_force_rhr(self): @@ -614,11 +623,17 @@ class GeoQuerySetTest(TestCase): # Only PostGIS has support for the MakeLine aggregate. @skipUnlessDBFeature("supports_make_line_aggr") + @ignore_warnings(category=RemovedInDjango20Warning) def test_make_line(self): - "Testing the `make_line` GeoQuerySet method." + """ + Testing the (deprecated) `make_line` GeoQuerySet method and the MakeLine + aggregate. + """ # Ensuring that a `TypeError` is raised on models without PointFields. self.assertRaises(TypeError, State.objects.make_line) self.assertRaises(TypeError, Country.objects.make_line) + # MakeLine on an inappropriate field returns simply None + self.assertIsNone(State.objects.aggregate(MakeLine('poly'))['poly__makeline']) # Reference query: # SELECT AsText(ST_MakeLine(geoapp_city.point)) FROM geoapp_city; ref_line = GEOSGeometry( @@ -629,9 +644,11 @@ class GeoQuerySetTest(TestCase): ) # We check for equality with a tolerance of 10e-5 which is a lower bound # of the precisions of ref_line coordinates - line = City.objects.make_line() - self.assertTrue(ref_line.equals_exact(line, tolerance=10e-5), - "%s != %s" % (ref_line, line)) + line1 = City.objects.make_line() + line2 = City.objects.aggregate(MakeLine('point'))['point__makeline'] + for line in (line1, line2): + self.assertTrue(ref_line.equals_exact(line, tolerance=10e-5), + "%s != %s" % (ref_line, line)) @skipUnlessDBFeature("has_num_geom_method") def test_num_geom(self): @@ -813,24 +830,34 @@ class GeoQuerySetTest(TestCase): # but this seems unexpected and should be investigated to determine the cause. @skipUnlessDBFeature("has_unionagg_method") @no_oracle + @ignore_warnings(category=RemovedInDjango20Warning) def test_unionagg(self): - "Testing the `unionagg` (aggregate union) GeoQuerySet method." + """ + Testing the (deprecated) `unionagg` (aggregate union) GeoQuerySet method + and the Union aggregate. + """ tx = Country.objects.get(name='Texas').mpoly # Houston, Dallas -- Ordering may differ depending on backend or GEOS version. union1 = fromstr('MULTIPOINT(-96.801611 32.782057,-95.363151 29.763374)') union2 = fromstr('MULTIPOINT(-95.363151 29.763374,-96.801611 32.782057)') qs = City.objects.filter(point__within=tx) self.assertRaises(TypeError, qs.unionagg, 'name') + self.assertRaises(ValueError, qs.aggregate, Union('name')) # Using `field_name` keyword argument in one query and specifying an # order in the other (which should not be used because this is # an aggregate method on a spatial column) u1 = qs.unionagg(field_name='point') u2 = qs.order_by('name').unionagg() + u3 = qs.aggregate(Union('point'))['point__union'] + u4 = qs.order_by('name').aggregate(Union('point'))['point__union'] tol = 0.00001 self.assertTrue(union1.equals_exact(u1, tol) or union2.equals_exact(u1, tol)) self.assertTrue(union1.equals_exact(u2, tol) or union2.equals_exact(u2, tol)) + self.assertTrue(union1.equals_exact(u3, tol) or union2.equals_exact(u3, tol)) + self.assertTrue(union1.equals_exact(u4, tol) or union2.equals_exact(u4, tol)) qs = City.objects.filter(name='NotACity') self.assertIsNone(qs.unionagg(field_name='point')) + self.assertIsNone(qs.aggregate(Union('point'))['point__union']) def test_non_concrete_field(self): NonConcreteModel.objects.create(point=Point(0, 0), name='name') diff --git a/django/contrib/gis/tests/relatedapp/tests.py b/django/contrib/gis/tests/relatedapp/tests.py index 5beab038fd..bcd88c0681 100644 --- a/django/contrib/gis/tests/relatedapp/tests.py +++ b/django/contrib/gis/tests/relatedapp/tests.py @@ -3,9 +3,10 @@ from __future__ import unicode_literals from django.contrib.gis.geos import HAS_GEOS from django.contrib.gis.tests.utils import no_oracle from django.db import connection -from django.test import TestCase, skipUnlessDBFeature +from django.test import TestCase, ignore_warnings, skipUnlessDBFeature from django.test.utils import override_settings from django.utils import timezone +from django.utils.deprecation import RemovedInDjango20Warning if HAS_GEOS: from django.contrib.gis.db.models import Collect, Count, Extent, F, Union @@ -64,7 +65,8 @@ class RelatedGeoModelTest(TestCase): check_pnt(GEOSGeometry(wkt, srid), qs[0].location.point) @skipUnlessDBFeature("supports_extent_aggr") - def test04a_related_extent_aggregate(self): + @ignore_warnings(category=RemovedInDjango20Warning) + def test_related_extent_aggregate(self): "Testing the `extent` GeoQuerySet aggregates on related geographic models." # This combines the Extent and Union aggregates into one query aggs = City.objects.aggregate(Extent('location__point')) @@ -83,8 +85,22 @@ class RelatedGeoModelTest(TestCase): for ref_val, e_val in zip(ref, e): self.assertAlmostEqual(ref_val, e_val, tol) + @skipUnlessDBFeature("supports_extent_aggr") + def test_related_extent_annotate(self): + """ + Test annotation with Extent GeoAggregate. + """ + cities = City.objects.annotate(points_extent=Extent('location__point')).order_by('name') + tol = 4 + self.assertAlmostEqual( + cities[0].points_extent, + (-97.516111, 33.058333, -97.516111, 33.058333), + tol + ) + @skipUnlessDBFeature("has_unionagg_method") - def test04b_related_union_aggregate(self): + @ignore_warnings(category=RemovedInDjango20Warning) + def test_related_union_aggregate(self): "Testing the `unionagg` GeoQuerySet aggregates on related geographic models." # This combines the Extent and Union aggregates into one query aggs = City.objects.aggregate(Union('location__point')) @@ -277,8 +293,12 @@ class RelatedGeoModelTest(TestCase): self.assertEqual(None, b.author) @skipUnlessDBFeature("supports_collect_aggr") - def test14_collect(self): - "Testing the `collect` GeoQuerySet method and `Collect` aggregate." + @ignore_warnings(category=RemovedInDjango20Warning) + def test_collect(self): + """ + Testing the (deprecated) `collect` GeoQuerySet method and `Collect` + aggregate. + """ # Reference query: # SELECT AsText(ST_Collect("relatedapp_location"."point")) FROM "relatedapp_city" LEFT OUTER JOIN # "relatedapp_location" ON ("relatedapp_city"."location_id" = "relatedapp_location"."id") diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index 0a460b7739..c0bbd58938 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -161,6 +161,9 @@ details on these changes. * Support for the legacy ``%()s`` syntax in ``ModelFormMixin.success_url`` will be removed. +* ``GeoQuerySet`` aggregate methods ``collect()``, ``extent()``, ``extent3d()``, + ``makeline()``, and ``union()`` will be removed. + .. _deprecation-removed-in-1.9: 1.9 diff --git a/docs/ref/contrib/gis/db-api.txt b/docs/ref/contrib/gis/db-api.txt index 089783c044..20efb97a0c 100644 --- a/docs/ref/contrib/gis/db-api.txt +++ b/docs/ref/contrib/gis/db-api.txt @@ -268,12 +268,9 @@ Method PostGIS Oracle SpatiaLite ==================================== ======= ====== ========== :meth:`GeoQuerySet.area` X X X :meth:`GeoQuerySet.centroid` X X X -:meth:`GeoQuerySet.collect` X (from v3.0) :meth:`GeoQuerySet.difference` X X X :meth:`GeoQuerySet.distance` X X X :meth:`GeoQuerySet.envelope` X X -:meth:`GeoQuerySet.extent` X X (from v3.0) -:meth:`GeoQuerySet.extent3d` X :meth:`GeoQuerySet.force_rhr` X :meth:`GeoQuerySet.geohash` X :meth:`GeoQuerySet.geojson` X X @@ -281,7 +278,6 @@ Method PostGIS Oracle SpatiaLite :meth:`GeoQuerySet.intersection` X X X :meth:`GeoQuerySet.kml` X X :meth:`GeoQuerySet.length` X X X -:meth:`GeoQuerySet.make_line` X :meth:`GeoQuerySet.mem_size` X :meth:`GeoQuerySet.num_geom` X X X :meth:`GeoQuerySet.num_points` X X X @@ -295,7 +291,23 @@ Method PostGIS Oracle SpatiaLite :meth:`GeoQuerySet.transform` X X X :meth:`GeoQuerySet.translate` X X :meth:`GeoQuerySet.union` X X X -:meth:`GeoQuerySet.unionagg` X X X +==================================== ======= ====== ========== + +Aggregate Functions +------------------- + +The following table provides a summary of what GIS-specific aggregate functions +are available on each spatial backend. Please note that MySQL does not +support any of these aggregates, and is thus excluded from the table. + +==================================== ======= ====== ========== +Aggregate PostGIS Oracle SpatiaLite +==================================== ======= ====== ========== +:class:`Collect` X (from v3.0) +:class:`Extent` X X (from v3.0) +:class:`Extent3D` X +:class:`MakeLine` X +:class:`Union` X X X ==================================== ======= ====== ========== .. rubric:: Footnotes diff --git a/docs/ref/contrib/gis/geoquerysets.txt b/docs/ref/contrib/gis/geoquerysets.txt index ab37a4a2d5..12e21d8cf1 100644 --- a/docs/ref/contrib/gis/geoquerysets.txt +++ b/docs/ref/contrib/gis/geoquerysets.txt @@ -1090,87 +1090,72 @@ Spatial Aggregates Aggregate Methods ----------------- +.. deprecated:: 1.8 + + Aggregate methods are now deprecated. Prefer using their function-based + equivalents. + ``collect`` ~~~~~~~~~~~ .. method:: GeoQuerySet.collect(**kwargs) -*Availability*: PostGIS, Spatialite (>=3.0) +.. deprecated:: 1.8 -Returns a ``GEOMETRYCOLLECTION`` or a ``MULTI`` geometry object from the geometry -column. This is analogous to a simplified version of the :meth:`GeoQuerySet.unionagg` method, -except it can be several orders of magnitude faster than performing a union because -it simply rolls up geometries into a collection or multi object, not caring about -dissolving boundaries. + Use the :class:`Collect` aggregate instead. + +Shortcut for ``aggregate(Collect())``. ``extent`` ~~~~~~~~~~ .. method:: GeoQuerySet.extent(**kwargs) -*Availability*: PostGIS, Oracle, Spatialite (>=3.0) +.. deprecated:: 1.8 -Returns the extent of the ``GeoQuerySet`` as a four-tuple, comprising the -lower left coordinate and the upper right coordinate. + Use the :class:`Extent` aggregate instead. -Example:: - - >>> qs = City.objects.filter(name__in=('Houston', 'Dallas')) - >>> print(qs.extent()) - (-96.8016128540039, 29.7633724212646, -95.3631439208984, 32.782058715820) +Shortcut for ``aggregate(Extent())``. ``extent3d`` ~~~~~~~~~~~~ .. method:: GeoQuerySet.extent3d(**kwargs) -*Availability*: PostGIS +.. deprecated:: 1.8 -Returns the 3D extent of the ``GeoQuerySet`` as a six-tuple, comprising -the lower left coordinate and upper right coordinate. + Use the :class:`Extent` aggregate instead. -Example:: - - >>> qs = City.objects.filter(name__in=('Houston', 'Dallas')) - >>> print(qs.extent3d()) - (-96.8016128540039, 29.7633724212646, 0, -95.3631439208984, 32.782058715820, 0) +Shortcut for ``aggregate(Extent3D())``. ``make_line`` ~~~~~~~~~~~~~ .. method:: GeoQuerySet.make_line(**kwargs) -*Availability*: PostGIS +.. deprecated:: 1.8 -Returns a ``LineString`` constructed from the point field geometries in the -``GeoQuerySet``. Currently, ordering the queryset has no effect. + Use the :class:`MakeLine` aggregate instead. -Example:: - - >>> print(City.objects.filter(name__in=('Houston', 'Dallas')).make_line()) - LINESTRING (-95.3631510000000020 29.7633739999999989, -96.8016109999999941 32.7820570000000018) +Shortcut for ``aggregate(MakeLine())``. ``unionagg`` ~~~~~~~~~~~~ .. method:: GeoQuerySet.unionagg(**kwargs) -*Availability*: PostGIS, Oracle, SpatiaLite +.. deprecated:: 1.8 -This method returns a :class:`~django.contrib.gis.geos.GEOSGeometry` object -comprising the union of every geometry in the queryset. Please note that -use of ``unionagg`` is processor intensive and may take a significant amount -of time on large querysets. + Use the :class:`Union` aggregate instead. -.. note:: +Shortcut for ``aggregate(Union())``. - If the computation time for using this method is too expensive, - consider using :meth:`GeoQuerySet.collect` instead. +Aggregate Functions +------------------- -Example:: - - >>> u = Zipcode.objects.unionagg() # This may take a long time. - >>> u = Zipcode.objects.filter(poly__within=bbox).unionagg() # A more sensible approach. +Django provides some GIS-specific aggregate functions. For details on how to +use these aggregate functions, see :doc:`the topic guide on aggregation +`. ===================== ===================================================== Keyword Argument Description @@ -1183,9 +1168,6 @@ Keyword Argument Description __ http://docs.oracle.com/html/B14255_01/sdo_intro.htm#sthref150 -Aggregate Functions -------------------- - Example:: >>> from django.contrib.gis.db.models import Extent, Union @@ -1196,35 +1178,84 @@ Example:: .. class:: Collect(geo_field) -Returns the same as the :meth:`GeoQuerySet.collect` aggregate method. +*Availability*: PostGIS, Spatialite (≥3.0) + +Returns a ``GEOMETRYCOLLECTION`` or a ``MULTI`` geometry object from the geometry +column. This is analogous to a simplified version of the :class:`Union` +aggregate, except it can be several orders of magnitude faster than performing +a union because it simply rolls up geometries into a collection or multi object, +not caring about dissolving boundaries. ``Extent`` ~~~~~~~~~~ + .. class:: Extent(geo_field) +*Availability*: PostGIS, Oracle, Spatialite (≥3.0) -Returns the same as the :meth:`GeoQuerySet.extent` aggregate method. +Returns the extent of all ``geo_field`` in the ``QuerySet`` as a four-tuple, +comprising the lower left coordinate and the upper right coordinate. + +Example:: + + >>> qs = City.objects.filter(name__in=('Houston', 'Dallas')).aggregate(Extent('poly')) + >>> print(qs[poly__extent]) + (-96.8016128540039, 29.7633724212646, -95.3631439208984, 32.782058715820) ``Extent3D`` ~~~~~~~~~~~~ .. class:: Extent3D(geo_field) -Returns the same as the :meth:`GeoQuerySet.extent3d` aggregate method. +*Availability*: PostGIS + +Returns the 3D extent of all ``geo_field`` in the ``QuerySet`` as a six-tuple, +comprising the lower left coordinate and upper right coordinate (each with x, y, +and z coordinates). + +Example:: + + >>> qs = City.objects.filter(name__in=('Houston', 'Dallas')).aggregate(Extent3D('poly')) + >>> print(qs[poly__extent3d]) + (-96.8016128540039, 29.7633724212646, 0, -95.3631439208984, 32.782058715820, 0) ``MakeLine`` ~~~~~~~~~~~~ .. class:: MakeLine(geo_field) -Returns the same as the :meth:`GeoQuerySet.make_line` aggregate method. +*Availability*: PostGIS + +Returns a ``LineString`` constructed from the point field geometries in the +``QuerySet``. Currently, ordering the queryset has no effect. + +Example:: + + >>> print(City.objects.filter(name__in=('Houston', 'Dallas') + ... ).aggregate(MakeLine('poly'))[poly__makeline] + LINESTRING (-95.3631510000000020 29.7633739999999989, -96.8016109999999941 32.7820570000000018) ``Union`` ~~~~~~~~~ .. class:: Union(geo_field) -Returns the same as the :meth:`GeoQuerySet.union` aggregate method. +*Availability*: PostGIS, Oracle, SpatiaLite + +This method returns a :class:`~django.contrib.gis.geos.GEOSGeometry` object +comprising the union of every geometry in the queryset. Please note that use of +``Union`` is processor intensive and may take a significant amount of time on +large querysets. + +.. note:: + + If the computation time for using this method is too expensive, consider + using :class:`Collect` instead. + +Example:: + + >>> u = Zipcode.objects.aggregate(Union(poly)) # This may take a long time. + >>> u = Zipcode.objects.filter(poly__within=bbox).aggregate(Union(poly)) # A more sensible approach. .. rubric:: Footnotes .. [#fnde9im] *See* `OpenGIS Simple Feature Specification For SQL `_, at Ch. 2.1.13.2, p. 2-13 (The Dimensionally Extended Nine-Intersection Model). diff --git a/docs/releases/1.8.txt b/docs/releases/1.8.txt index cb2816723f..0b52860c13 100644 --- a/docs/releases/1.8.txt +++ b/docs/releases/1.8.txt @@ -1580,6 +1580,14 @@ The legacy ``%()s`` syntax in :attr:`ModelFormMixin.success_url ` is deprecated and will be removed in Django 2.0. +``GeoQuerySet`` aggregate methods +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``collect()``, ``extent()``, ``extent3d()``, ``makeline()``, and ``union()`` +aggregate methods are deprecated and should be replaced by their function-based +aggregate equivalents (``Collect``, ``Extent``, ``Extent3D``, ``MakeLine``, and +``Union``). + .. removed-features-1.8: Features removed in 1.8