Commit f671a5c9 authored by Justin Bronn's avatar Justin Bronn
Browse files

Fixed incomplete merge of geographic aggregates; added support for `Extent`...

Fixed incomplete merge of geographic aggregates; added support for `Extent` aggregate to Oracle spatial backend.  Refs #3566. 


git-svn-id: http://code.djangoproject.com/svn/django/trunk@9748 bcc190cf-cafb-0310-a4f2-bffc1f526a37
parent 91e25f9e
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
# Want to get everything from the 'normal' models package.
from django.db.models import *

# Geographic aggregate functions
from django.contrib.gis.db.models.aggregates import *

# The GeoManager
from django.contrib.gis.db.models.manager import GeoManager

+21 −3
Original line number Diff line number Diff line
from django.db.models import Aggregate
from django.contrib.gis.db.backend import SpatialBackend
from django.contrib.gis.db.models.sql import GeomField

class Extent(Aggregate):
class GeoAggregate(Aggregate):

    def add_to_query(self, query, alias, col, source, is_summary):
        if hasattr(source, '_geom'):
            # Doing additional setup on the Query object for spatial aggregates.
            aggregate = getattr(query.aggregates_module, self.name)
            
            # Adding a conversion class instance and any selection wrapping
            # SQL (e.g., needed by Oracle).
            if aggregate.conversion_class is GeomField:
                query.extra_select_fields[alias] = GeomField()
                if SpatialBackend.select:
                    query.custom_select[alias] = SpatialBackend.select
     
        super(GeoAggregate, self).add_to_query(query, alias, col, source, is_summary)

class Extent(GeoAggregate):
    name = 'Extent'

class MakeLine(Aggregate):
class MakeLine(GeoAggregate):
    name = 'MakeLine'

class Union(Aggregate):
class Union(GeoAggregate):
    name = 'Union'
+1 −2
Original line number Diff line number Diff line
@@ -8,7 +8,6 @@ from django.contrib.gis.db.models.fields import GeometryField, PointField
from django.contrib.gis.db.models.sql import AreaField, DistanceField, GeomField, GeoQuery, GeoWhereNode
from django.contrib.gis.measure import Area, Distance
from django.contrib.gis.models import get_srid_info
qn = connection.ops.quote_name

# For backwards-compatibility; Q object should work just fine
# after queryset-refactor.
@@ -331,7 +330,7 @@ class GeoQuerySet(QuerySet):
        if SpatialBackend.oracle: agg_kwargs['tolerance'] = tolerance

        # Calling the QuerySet.aggregate, and returning only the value of the aggregate.
        return self.aggregate(_geoagg=aggregate(agg_col, **agg_kwargs))['_geoagg']
        return self.aggregate(geoagg=aggregate(agg_col, **agg_kwargs))['geoagg']

    def _spatial_attribute(self, att, settings, field_name=None, model_att=None):
        """
+2 −1
Original line number Diff line number Diff line
from django.contrib.gis.db.models.sql.query import AreaField, DistanceField, GeomField, GeoQuery
from django.contrib.gis.db.models.sql.conversion import AreaField, DistanceField, GeomField
from django.contrib.gis.db.models.sql.query import GeoQuery
from django.contrib.gis.db.models.sql.where import GeoWhereNode
+58 −5
Original line number Diff line number Diff line
from django.db.models.sql.aggregates import *

from django.contrib.gis.db.models.fields import GeometryField
from django.contrib.gis.db.models.sql.conversion import GeomField
from django.contrib.gis.db.backend import SpatialBackend

if SpatialBackend.oracle:
# Default SQL template for spatial aggregates.
geo_template = '%(function)s(%(field)s)'

# Default conversion functions for aggregates; will be overridden if implemented
# for the spatial backend.
def convert_extent(box):
    raise NotImplementedError('Aggregate extent not implemented for this spatial backend.')

def convert_geom(wkt, geo_field):
    raise NotImplementedError('Aggregate method not implemented for this spatial backend.')

if SpatialBackend.postgis:
    def convert_extent(box):
        # Box text will be something like "BOX(-90.0 30.0, -85.0 40.0)"; 
        # parsing out and returning as a 4-tuple.
        ll, ur = box[4:-1].split(',')
        xmin, ymin = map(float, ll.split())
        xmax, ymax = map(float, ur.split())
        return (xmin, ymin, xmax, ymax)

    def convert_geom(hex, geo_field):
        if hex: return SpatialBackend.Geometry(hex)
        else: return None
elif SpatialBackend.oracle:
    # Oracle spatial aggregates need a tolerance.
    geo_template = '%(function)s(SDOAGGRTYPE(%(field)s,%(tolerance)s))'

    def convert_extent(clob):
        if clob:
            # Oracle returns a polygon for the extent, we construct
            # the 4-tuple from the coordinates in the polygon.
            poly = SpatialBackend.Geometry(clob.read())
            shell = poly.shell
            ll, ur = shell[0], shell[2]
            xmin, ymin = ll
            xmax, ymax = ur
            return (xmin, ymin, xmax, ymax)
        else:
    geo_template = '%(function)s(%(field)s)'
            return None
    
    def convert_geom(clob, geo_field):
        if clob: return SpatialBackend.Geometry(clob.read(), geo_field._srid)
        else: return None

class GeoAggregate(Aggregate):
    # Overriding the SQL template with the geographic one.
    sql_template = geo_template

    # Conversion class, if necessary.
    conversion_class = None

    # Flags for indicating the type of the aggregate.
    is_extent = False

    def __init__(self, col, source=None, is_summary=False, **extra):
        super(GeoAggregate, self).__init__(col, source, is_summary, **extra)

        if not self.is_extent and SpatialBackend.oracle:
            self.extra.setdefault('tolerance', 0.05)

        # Can't use geographic aggregates on non-geometry fields.
        if not isinstance(self.source, GeometryField): 
            raise ValueError('Geospatial aggregates only allowed on geometry fields.')
@@ -29,8 +75,15 @@ class Extent(GeoAggregate):
    is_extent = True
    sql_function = SpatialBackend.extent
        
if SpatialBackend.oracle:
    # Have to change Extent's attributes here for Oracle.
    Extent.conversion_class = GeomField
    Extent.sql_template = '%(function)s(%(field)s)'

class MakeLine(GeoAggregate):
    conversion_class = GeomField
    sql_function = SpatialBackend.make_line

class Union(GeoAggregate):
    conversion_class = GeomField
    sql_function = SpatialBackend.unionagg
Loading