Kaydet (Commit) a79e6b67 authored tarafından Claude Paroz's avatar Claude Paroz

Fixed #24152 -- Deprecated GeoQuerySet aggregate methods

Thanks Josh Smeaton and Tim Graham for the reviews.
üst 5338ff48
from django.db.models.aggregates import Aggregate 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'] __all__ = ['Collect', 'Extent', 'Extent3D', 'MakeLine', 'Union']
...@@ -20,9 +20,9 @@ class GeoAggregate(Aggregate): ...@@ -20,9 +20,9 @@ class GeoAggregate(Aggregate):
self.template = '%(function)s(SDOAGGRTYPE(%(expressions)s,%(tolerance)s))' self.template = '%(function)s(SDOAGGRTYPE(%(expressions)s,%(tolerance)s))'
return self.as_sql(compiler, connection) return self.as_sql(compiler, connection)
def prepare(self, query=None, allow_joins=True, reuse=None, summarize=False): def resolve_expression(self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False):
c = super(GeoAggregate, self).prepare(query, allow_joins, reuse, summarize) c = super(GeoAggregate, self).resolve_expression(query, allow_joins, reuse, summarize, for_save)
if not isinstance(self.expressions[0].output_field, GeometryField): if not hasattr(c.input_field.field, 'geom_type'):
raise ValueError('Geospatial aggregates only allowed on geometry fields.') raise ValueError('Geospatial aggregates only allowed on geometry fields.')
return c return c
......
import warnings
from django.db import connections from django.db import connections
from django.db.models.expressions import RawSQL from django.db.models.expressions import RawSQL
from django.db.models.fields import Field from django.db.models.fields import Field
...@@ -15,6 +17,7 @@ from django.contrib.gis.geometry.backend import Geometry ...@@ -15,6 +17,7 @@ from django.contrib.gis.geometry.backend import Geometry
from django.contrib.gis.measure import Area, Distance from django.contrib.gis.measure import Area, Distance
from django.utils import six from django.utils import six
from django.utils.deprecation import RemovedInDjango20Warning
class GeoQuerySet(QuerySet): class GeoQuerySet(QuerySet):
...@@ -65,6 +68,11 @@ class GeoQuerySet(QuerySet): ...@@ -65,6 +68,11 @@ class GeoQuerySet(QuerySet):
This is analogous to a union operation, but much faster because This is analogous to a union operation, but much faster because
boundaries are not dissolved. 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) return self._spatial_aggregate(aggregates.Collect, **kwargs)
def difference(self, geom, **kwargs): def difference(self, geom, **kwargs):
...@@ -105,6 +113,11 @@ class GeoQuerySet(QuerySet): ...@@ -105,6 +113,11 @@ class GeoQuerySet(QuerySet):
Returns the extent (aggregate) of the features in the GeoQuerySet. The 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). 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) return self._spatial_aggregate(aggregates.Extent, **kwargs)
def extent3d(self, **kwargs): def extent3d(self, **kwargs):
...@@ -113,6 +126,11 @@ class GeoQuerySet(QuerySet): ...@@ -113,6 +126,11 @@ class GeoQuerySet(QuerySet):
GeoQuerySet. It is returned as a 6-tuple, comprising: GeoQuerySet. It is returned as a 6-tuple, comprising:
(xmin, ymin, zmin, xmax, ymax, zmax). (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) return self._spatial_aggregate(aggregates.Extent3D, **kwargs)
def force_rhr(self, **kwargs): def force_rhr(self, **kwargs):
...@@ -215,6 +233,11 @@ class GeoQuerySet(QuerySet): ...@@ -215,6 +233,11 @@ class GeoQuerySet(QuerySet):
this GeoQuerySet and returns it. This is a spatial aggregate this GeoQuerySet and returns it. This is a spatial aggregate
method, and thus returns a geometry rather than a GeoQuerySet. 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) return self._spatial_aggregate(aggregates.MakeLine, geo_field_type=PointField, **kwargs)
def mem_size(self, **kwargs): def mem_size(self, **kwargs):
...@@ -398,6 +421,11 @@ class GeoQuerySet(QuerySet): ...@@ -398,6 +421,11 @@ class GeoQuerySet(QuerySet):
None if the GeoQuerySet is empty. The `tolerance` keyword is for None if the GeoQuerySet is empty. The `tolerance` keyword is for
Oracle backends only. 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) return self._spatial_aggregate(aggregates.Union, **kwargs)
### Private API -- Abstracted DRY routines. ### ### Private API -- Abstracted DRY routines. ###
......
...@@ -6,7 +6,8 @@ from unittest import skipUnless ...@@ -6,7 +6,8 @@ from unittest import skipUnless
from django.contrib.gis.gdal import HAS_GDAL from django.contrib.gis.gdal import HAS_GDAL
from django.contrib.gis.geos import HAS_GEOS 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 from django.utils._os import upath
if HAS_GEOS: if HAS_GEOS:
...@@ -206,6 +207,7 @@ class Geo3DTest(TestCase): ...@@ -206,6 +207,7 @@ class Geo3DTest(TestCase):
# Ordering of points in the resulting geometry may vary between implementations # 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}) self.assertSetEqual({p.ewkt for p in ref_union}, {p.ewkt for p in union})
@ignore_warnings(category=RemovedInDjango20Warning)
def test_extent(self): def test_extent(self):
""" """
Testing the Extent3D aggregate for 3D models. Testing the Extent3D aggregate for 3D models.
...@@ -223,6 +225,7 @@ class Geo3DTest(TestCase): ...@@ -223,6 +225,7 @@ class Geo3DTest(TestCase):
for e3d in [extent1, extent2]: for e3d in [extent1, extent2]:
check_extent3d(e3d) check_extent3d(e3d)
self.assertIsNone(City3D.objects.none().extent3d()) self.assertIsNone(City3D.objects.none().extent3d())
self.assertIsNone(City3D.objects.none().aggregate(Extent3D('point'))['point__extent3d'])
def test_perimeter(self): def test_perimeter(self):
""" """
......
...@@ -3,6 +3,7 @@ from __future__ import unicode_literals ...@@ -3,6 +3,7 @@ from __future__ import unicode_literals
from datetime import datetime from datetime import datetime
from django.contrib.gis.db.models import Extent
from django.contrib.gis.geos import HAS_GEOS from django.contrib.gis.geos import HAS_GEOS
from django.contrib.gis.shortcuts import render_to_kmz from django.contrib.gis.shortcuts import render_to_kmz
from django.contrib.gis.tests.utils import no_oracle from django.contrib.gis.tests.utils import no_oracle
...@@ -44,7 +45,7 @@ class GeoRegressionTests(TestCase): ...@@ -44,7 +45,7 @@ class GeoRegressionTests(TestCase):
"Testing `extent` on a table with a single point. See #11827." "Testing `extent` on a table with a single point. See #11827."
pnt = City.objects.get(name='Pueblo').point pnt = City.objects.get(name='Pueblo').point
ref_ext = (pnt.x, pnt.y, pnt.x, pnt.y) 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): for ref_val, val in zip(ref_ext, extent):
self.assertAlmostEqual(ref_val, val, 4) self.assertAlmostEqual(ref_val, val, 4)
......
...@@ -5,11 +5,13 @@ from tempfile import NamedTemporaryFile ...@@ -5,11 +5,13 @@ from tempfile import NamedTemporaryFile
from django.db import connection from django.db import connection
from django.contrib.gis import gdal 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.geos import HAS_GEOS
from django.contrib.gis.tests.utils import no_oracle, oracle, postgis, spatialite from django.contrib.gis.tests.utils import no_oracle, oracle, postgis, spatialite
from django.core.management import call_command 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 import six
from django.utils.deprecation import RemovedInDjango20Warning
if HAS_GEOS: if HAS_GEOS:
from django.contrib.gis.geos import (fromstr, GEOSGeometry, from django.contrib.gis.geos import (fromstr, GEOSGeometry,
...@@ -470,19 +472,26 @@ class GeoQuerySetTest(TestCase): ...@@ -470,19 +472,26 @@ class GeoQuerySetTest(TestCase):
self.assertIsInstance(country.envelope, Polygon) self.assertIsInstance(country.envelope, Polygon)
@skipUnlessDBFeature("supports_extent_aggr") @skipUnlessDBFeature("supports_extent_aggr")
@ignore_warnings(category=RemovedInDjango20Warning)
def test_extent(self): def test_extent(self):
"Testing the `extent` GeoQuerySet method." """
Testing the (deprecated) `extent` GeoQuerySet method and the Extent
aggregate.
"""
# Reference query: # Reference query:
# `SELECT ST_extent(point) FROM geoapp_city WHERE (name='Houston' or name='Dallas');` # `SELECT ST_extent(point) FROM geoapp_city WHERE (name='Houston' or name='Dallas');`
# => BOX(-96.8016128540039 29.7633724212646,-95.3631439208984 32.7820587158203) # => BOX(-96.8016128540039 29.7633724212646,-95.3631439208984 32.7820587158203)
expected = (-96.8016128540039, 29.7633724212646, -95.3631439208984, 32.782058715820) expected = (-96.8016128540039, 29.7633724212646, -95.3631439208984, 32.782058715820)
qs = City.objects.filter(name__in=('Houston', 'Dallas')) 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): for extent in (extent1, extent2):
self.assertAlmostEqual(exp, val, 4) 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')).extent())
self.assertIsNone(City.objects.filter(name=('Smalltown')).aggregate(Extent('point'))['point__extent'])
@skipUnlessDBFeature("has_force_rhr_method") @skipUnlessDBFeature("has_force_rhr_method")
def test_force_rhr(self): def test_force_rhr(self):
...@@ -614,11 +623,17 @@ class GeoQuerySetTest(TestCase): ...@@ -614,11 +623,17 @@ class GeoQuerySetTest(TestCase):
# Only PostGIS has support for the MakeLine aggregate. # Only PostGIS has support for the MakeLine aggregate.
@skipUnlessDBFeature("supports_make_line_aggr") @skipUnlessDBFeature("supports_make_line_aggr")
@ignore_warnings(category=RemovedInDjango20Warning)
def test_make_line(self): 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. # Ensuring that a `TypeError` is raised on models without PointFields.
self.assertRaises(TypeError, State.objects.make_line) self.assertRaises(TypeError, State.objects.make_line)
self.assertRaises(TypeError, Country.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: # Reference query:
# SELECT AsText(ST_MakeLine(geoapp_city.point)) FROM geoapp_city; # SELECT AsText(ST_MakeLine(geoapp_city.point)) FROM geoapp_city;
ref_line = GEOSGeometry( ref_line = GEOSGeometry(
...@@ -629,9 +644,11 @@ class GeoQuerySetTest(TestCase): ...@@ -629,9 +644,11 @@ class GeoQuerySetTest(TestCase):
) )
# We check for equality with a tolerance of 10e-5 which is a lower bound # We check for equality with a tolerance of 10e-5 which is a lower bound
# of the precisions of ref_line coordinates # of the precisions of ref_line coordinates
line = City.objects.make_line() line1 = City.objects.make_line()
self.assertTrue(ref_line.equals_exact(line, tolerance=10e-5), line2 = City.objects.aggregate(MakeLine('point'))['point__makeline']
"%s != %s" % (ref_line, line)) 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") @skipUnlessDBFeature("has_num_geom_method")
def test_num_geom(self): def test_num_geom(self):
...@@ -813,24 +830,34 @@ class GeoQuerySetTest(TestCase): ...@@ -813,24 +830,34 @@ class GeoQuerySetTest(TestCase):
# but this seems unexpected and should be investigated to determine the cause. # but this seems unexpected and should be investigated to determine the cause.
@skipUnlessDBFeature("has_unionagg_method") @skipUnlessDBFeature("has_unionagg_method")
@no_oracle @no_oracle
@ignore_warnings(category=RemovedInDjango20Warning)
def test_unionagg(self): 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 tx = Country.objects.get(name='Texas').mpoly
# Houston, Dallas -- Ordering may differ depending on backend or GEOS version. # Houston, Dallas -- Ordering may differ depending on backend or GEOS version.
union1 = fromstr('MULTIPOINT(-96.801611 32.782057,-95.363151 29.763374)') union1 = fromstr('MULTIPOINT(-96.801611 32.782057,-95.363151 29.763374)')
union2 = fromstr('MULTIPOINT(-95.363151 29.763374,-96.801611 32.782057)') union2 = fromstr('MULTIPOINT(-95.363151 29.763374,-96.801611 32.782057)')
qs = City.objects.filter(point__within=tx) qs = City.objects.filter(point__within=tx)
self.assertRaises(TypeError, qs.unionagg, 'name') self.assertRaises(TypeError, qs.unionagg, 'name')
self.assertRaises(ValueError, qs.aggregate, Union('name'))
# Using `field_name` keyword argument in one query and specifying an # Using `field_name` keyword argument in one query and specifying an
# order in the other (which should not be used because this is # order in the other (which should not be used because this is
# an aggregate method on a spatial column) # an aggregate method on a spatial column)
u1 = qs.unionagg(field_name='point') u1 = qs.unionagg(field_name='point')
u2 = qs.order_by('name').unionagg() 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 tol = 0.00001
self.assertTrue(union1.equals_exact(u1, tol) or union2.equals_exact(u1, tol)) 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(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') qs = City.objects.filter(name='NotACity')
self.assertIsNone(qs.unionagg(field_name='point')) self.assertIsNone(qs.unionagg(field_name='point'))
self.assertIsNone(qs.aggregate(Union('point'))['point__union'])
def test_non_concrete_field(self): def test_non_concrete_field(self):
NonConcreteModel.objects.create(point=Point(0, 0), name='name') NonConcreteModel.objects.create(point=Point(0, 0), name='name')
......
...@@ -3,9 +3,10 @@ from __future__ import unicode_literals ...@@ -3,9 +3,10 @@ from __future__ import unicode_literals
from django.contrib.gis.geos import HAS_GEOS from django.contrib.gis.geos import HAS_GEOS
from django.contrib.gis.tests.utils import no_oracle from django.contrib.gis.tests.utils import no_oracle
from django.db import connection 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.test.utils import override_settings
from django.utils import timezone from django.utils import timezone
from django.utils.deprecation import RemovedInDjango20Warning
if HAS_GEOS: if HAS_GEOS:
from django.contrib.gis.db.models import Collect, Count, Extent, F, Union from django.contrib.gis.db.models import Collect, Count, Extent, F, Union
...@@ -64,7 +65,8 @@ class RelatedGeoModelTest(TestCase): ...@@ -64,7 +65,8 @@ class RelatedGeoModelTest(TestCase):
check_pnt(GEOSGeometry(wkt, srid), qs[0].location.point) check_pnt(GEOSGeometry(wkt, srid), qs[0].location.point)
@skipUnlessDBFeature("supports_extent_aggr") @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." "Testing the `extent` GeoQuerySet aggregates on related geographic models."
# This combines the Extent and Union aggregates into one query # This combines the Extent and Union aggregates into one query
aggs = City.objects.aggregate(Extent('location__point')) aggs = City.objects.aggregate(Extent('location__point'))
...@@ -83,8 +85,22 @@ class RelatedGeoModelTest(TestCase): ...@@ -83,8 +85,22 @@ class RelatedGeoModelTest(TestCase):
for ref_val, e_val in zip(ref, e): for ref_val, e_val in zip(ref, e):
self.assertAlmostEqual(ref_val, e_val, tol) 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") @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." "Testing the `unionagg` GeoQuerySet aggregates on related geographic models."
# This combines the Extent and Union aggregates into one query # This combines the Extent and Union aggregates into one query
aggs = City.objects.aggregate(Union('location__point')) aggs = City.objects.aggregate(Union('location__point'))
...@@ -277,8 +293,12 @@ class RelatedGeoModelTest(TestCase): ...@@ -277,8 +293,12 @@ class RelatedGeoModelTest(TestCase):
self.assertEqual(None, b.author) self.assertEqual(None, b.author)
@skipUnlessDBFeature("supports_collect_aggr") @skipUnlessDBFeature("supports_collect_aggr")
def test14_collect(self): @ignore_warnings(category=RemovedInDjango20Warning)
"Testing the `collect` GeoQuerySet method and `Collect` aggregate." def test_collect(self):
"""
Testing the (deprecated) `collect` GeoQuerySet method and `Collect`
aggregate.
"""
# Reference query: # Reference query:
# SELECT AsText(ST_Collect("relatedapp_location"."point")) FROM "relatedapp_city" LEFT OUTER JOIN # SELECT AsText(ST_Collect("relatedapp_location"."point")) FROM "relatedapp_city" LEFT OUTER JOIN
# "relatedapp_location" ON ("relatedapp_city"."location_id" = "relatedapp_location"."id") # "relatedapp_location" ON ("relatedapp_city"."location_id" = "relatedapp_location"."id")
......
...@@ -161,6 +161,9 @@ details on these changes. ...@@ -161,6 +161,9 @@ details on these changes.
* Support for the legacy ``%(<foo>)s`` syntax in ``ModelFormMixin.success_url`` * Support for the legacy ``%(<foo>)s`` syntax in ``ModelFormMixin.success_url``
will be removed. will be removed.
* ``GeoQuerySet`` aggregate methods ``collect()``, ``extent()``, ``extent3d()``,
``makeline()``, and ``union()`` will be removed.
.. _deprecation-removed-in-1.9: .. _deprecation-removed-in-1.9:
1.9 1.9
......
...@@ -268,12 +268,9 @@ Method PostGIS Oracle SpatiaLite ...@@ -268,12 +268,9 @@ Method PostGIS Oracle SpatiaLite
==================================== ======= ====== ========== ==================================== ======= ====== ==========
:meth:`GeoQuerySet.area` X X X :meth:`GeoQuerySet.area` X X X
:meth:`GeoQuerySet.centroid` X X X :meth:`GeoQuerySet.centroid` X X X
:meth:`GeoQuerySet.collect` X (from v3.0)
:meth:`GeoQuerySet.difference` X X X :meth:`GeoQuerySet.difference` X X X
:meth:`GeoQuerySet.distance` X X X :meth:`GeoQuerySet.distance` X X X
:meth:`GeoQuerySet.envelope` 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.force_rhr` X
:meth:`GeoQuerySet.geohash` X :meth:`GeoQuerySet.geohash` X
:meth:`GeoQuerySet.geojson` X X :meth:`GeoQuerySet.geojson` X X
...@@ -281,7 +278,6 @@ Method PostGIS Oracle SpatiaLite ...@@ -281,7 +278,6 @@ Method PostGIS Oracle SpatiaLite
:meth:`GeoQuerySet.intersection` X X X :meth:`GeoQuerySet.intersection` X X X
:meth:`GeoQuerySet.kml` X X :meth:`GeoQuerySet.kml` X X
:meth:`GeoQuerySet.length` X X X :meth:`GeoQuerySet.length` X X X
:meth:`GeoQuerySet.make_line` X
:meth:`GeoQuerySet.mem_size` X :meth:`GeoQuerySet.mem_size` X
:meth:`GeoQuerySet.num_geom` X X X :meth:`GeoQuerySet.num_geom` X X X
:meth:`GeoQuerySet.num_points` X X X :meth:`GeoQuerySet.num_points` X X X
...@@ -295,7 +291,23 @@ Method PostGIS Oracle SpatiaLite ...@@ -295,7 +291,23 @@ Method PostGIS Oracle SpatiaLite
:meth:`GeoQuerySet.transform` X X X :meth:`GeoQuerySet.transform` X X X
:meth:`GeoQuerySet.translate` X X :meth:`GeoQuerySet.translate` X X
:meth:`GeoQuerySet.union` X 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 .. rubric:: Footnotes
......
...@@ -1090,87 +1090,72 @@ Spatial Aggregates ...@@ -1090,87 +1090,72 @@ Spatial Aggregates
Aggregate Methods Aggregate Methods
----------------- -----------------
.. deprecated:: 1.8
Aggregate methods are now deprecated. Prefer using their function-based
equivalents.
``collect`` ``collect``
~~~~~~~~~~~ ~~~~~~~~~~~
.. method:: GeoQuerySet.collect(**kwargs) .. method:: GeoQuerySet.collect(**kwargs)
*Availability*: PostGIS, Spatialite (>=3.0) .. deprecated:: 1.8
Returns a ``GEOMETRYCOLLECTION`` or a ``MULTI`` geometry object from the geometry Use the :class:`Collect` aggregate instead.
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 Shortcut for ``aggregate(Collect(<field>))``.
it simply rolls up geometries into a collection or multi object, not caring about
dissolving boundaries.
``extent`` ``extent``
~~~~~~~~~~ ~~~~~~~~~~
.. method:: GeoQuerySet.extent(**kwargs) .. 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 Use the :class:`Extent` aggregate instead.
lower left coordinate and the upper right coordinate.
Example:: Shortcut for ``aggregate(Extent(<field>))``.
>>> qs = City.objects.filter(name__in=('Houston', 'Dallas'))
>>> print(qs.extent())
(-96.8016128540039, 29.7633724212646, -95.3631439208984, 32.782058715820)
``extent3d`` ``extent3d``
~~~~~~~~~~~~ ~~~~~~~~~~~~
.. method:: GeoQuerySet.extent3d(**kwargs) .. 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.
Example:: Use the :class:`Extent` aggregate instead.
>>> qs = City.objects.filter(name__in=('Houston', 'Dallas')) Shortcut for ``aggregate(Extent3D(<field>))``.
>>> print(qs.extent3d())
(-96.8016128540039, 29.7633724212646, 0, -95.3631439208984, 32.782058715820, 0)
``make_line`` ``make_line``
~~~~~~~~~~~~~ ~~~~~~~~~~~~~
.. method:: GeoQuerySet.make_line(**kwargs) .. 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.
Example:: Use the :class:`MakeLine` aggregate instead.
>>> print(City.objects.filter(name__in=('Houston', 'Dallas')).make_line()) Shortcut for ``aggregate(MakeLine(<field>))``.
LINESTRING (-95.3631510000000020 29.7633739999999989, -96.8016109999999941 32.7820570000000018)
``unionagg`` ``unionagg``
~~~~~~~~~~~~ ~~~~~~~~~~~~
.. method:: GeoQuerySet.unionagg(**kwargs) .. method:: GeoQuerySet.unionagg(**kwargs)
*Availability*: PostGIS, Oracle, SpatiaLite .. deprecated:: 1.8
This method returns a :class:`~django.contrib.gis.geos.GEOSGeometry` object Use the :class:`Union` aggregate instead.
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.
.. note:: Shortcut for ``aggregate(Union(<field>))``.
If the computation time for using this method is too expensive,
consider using :meth:`GeoQuerySet.collect` instead.
Example:: Aggregate Functions
-------------------
>>> u = Zipcode.objects.unionagg() # This may take a long time. Django provides some GIS-specific aggregate functions. For details on how to
>>> u = Zipcode.objects.filter(poly__within=bbox).unionagg() # A more sensible approach. use these aggregate functions, see :doc:`the topic guide on aggregation
</topics/db/aggregation>`.
===================== ===================================================== ===================== =====================================================
Keyword Argument Description Keyword Argument Description
...@@ -1183,9 +1168,6 @@ Keyword Argument Description ...@@ -1183,9 +1168,6 @@ Keyword Argument Description
__ http://docs.oracle.com/html/B14255_01/sdo_intro.htm#sthref150 __ http://docs.oracle.com/html/B14255_01/sdo_intro.htm#sthref150
Aggregate Functions
-------------------
Example:: Example::
>>> from django.contrib.gis.db.models import Extent, Union >>> from django.contrib.gis.db.models import Extent, Union
...@@ -1196,35 +1178,84 @@ Example:: ...@@ -1196,35 +1178,84 @@ Example::
.. class:: Collect(geo_field) .. 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`` ``Extent``
~~~~~~~~~~ ~~~~~~~~~~
.. class:: Extent(geo_field) .. class:: Extent(geo_field)
*Availability*: PostGIS, Oracle, Spatialite (≥3.0)
Returns the extent of all ``geo_field`` in the ``QuerySet`` as a four-tuple,
comprising the lower left coordinate and the upper right coordinate.
Returns the same as the :meth:`GeoQuerySet.extent` aggregate method. 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`` ``Extent3D``
~~~~~~~~~~~~ ~~~~~~~~~~~~
.. class:: Extent3D(geo_field) .. 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`` ``MakeLine``
~~~~~~~~~~~~ ~~~~~~~~~~~~
.. class:: MakeLine(geo_field) .. 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`` ``Union``
~~~~~~~~~ ~~~~~~~~~
.. class:: Union(geo_field) .. 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 .. rubric:: Footnotes
.. [#fnde9im] *See* `OpenGIS Simple Feature Specification For SQL <http://www.opengis.org/docs/99-049.pdf>`_, at Ch. 2.1.13.2, p. 2-13 (The Dimensionally Extended Nine-Intersection Model). .. [#fnde9im] *See* `OpenGIS Simple Feature Specification For SQL <http://www.opengis.org/docs/99-049.pdf>`_, at Ch. 2.1.13.2, p. 2-13 (The Dimensionally Extended Nine-Intersection Model).
......
...@@ -1580,6 +1580,14 @@ The legacy ``%(<foo>)s`` syntax in :attr:`ModelFormMixin.success_url ...@@ -1580,6 +1580,14 @@ The legacy ``%(<foo>)s`` syntax in :attr:`ModelFormMixin.success_url
<django.views.generic.edit.ModelFormMixin.success_url>` is deprecated and <django.views.generic.edit.ModelFormMixin.success_url>` is deprecated and
will be removed in Django 2.0. 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: .. removed-features-1.8:
Features removed in 1.8 Features removed in 1.8
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment