web/lib/django/contrib/gis/db/models/sql/aggregates.py
changeset 29 cc9b7e14412b
parent 0 0d40e90630ef
equal deleted inserted replaced
28:b758351d191f 29:cc9b7e14412b
     1 from django.db.models.sql.aggregates import *
     1 from django.db.models.sql.aggregates import *
     2 from django.contrib.gis.db.models.fields import GeometryField
     2 from django.contrib.gis.db.models.fields import GeometryField
     3 from django.contrib.gis.db.models.sql.conversion import GeomField
     3 from django.contrib.gis.db.models.sql.conversion import GeomField
     4 from django.contrib.gis.db.backend import SpatialBackend
       
     5 
       
     6 # Default SQL template for spatial aggregates.
       
     7 geo_template = '%(function)s(%(field)s)'
       
     8 
       
     9 # Default conversion functions for aggregates; will be overridden if implemented
       
    10 # for the spatial backend.
       
    11 def convert_extent(box):
       
    12     raise NotImplementedError('Aggregate extent not implemented for this spatial backend.')
       
    13 
       
    14 def convert_geom(wkt, geo_field):
       
    15     raise NotImplementedError('Aggregate method not implemented for this spatial backend.')
       
    16 
       
    17 if SpatialBackend.postgis:
       
    18     def convert_extent(box):
       
    19         # Box text will be something like "BOX(-90.0 30.0, -85.0 40.0)";
       
    20         # parsing out and returning as a 4-tuple.
       
    21         ll, ur = box[4:-1].split(',')
       
    22         xmin, ymin = map(float, ll.split())
       
    23         xmax, ymax = map(float, ur.split())
       
    24         return (xmin, ymin, xmax, ymax)
       
    25 
       
    26     def convert_geom(hex, geo_field):
       
    27         if hex: return SpatialBackend.Geometry(hex)
       
    28         else: return None
       
    29 elif SpatialBackend.oracle:
       
    30     # Oracle spatial aggregates need a tolerance.
       
    31     geo_template = '%(function)s(SDOAGGRTYPE(%(field)s,%(tolerance)s))'
       
    32 
       
    33     def convert_extent(clob):
       
    34         if clob:
       
    35             # Generally, Oracle returns a polygon for the extent -- however,
       
    36             # it can return a single point if there's only one Point in the
       
    37             # table.
       
    38             ext_geom = SpatialBackend.Geometry(clob.read())
       
    39             gtype = str(ext_geom.geom_type)
       
    40             if gtype == 'Polygon':
       
    41                 # Construct the 4-tuple from the coordinates in the polygon.
       
    42                 shell = ext_geom.shell
       
    43                 ll, ur = shell[0][:2], shell[2][:2]
       
    44             elif gtype == 'Point':
       
    45                 ll = ext_geom.coords[:2]
       
    46                 ur = ll
       
    47             else:
       
    48                 raise Exception('Unexpected geometry type returned for extent: %s' % gtype)
       
    49             xmin, ymin = ll
       
    50             xmax, ymax = ur
       
    51             return (xmin, ymin, xmax, ymax)
       
    52         else:
       
    53             return None
       
    54 
       
    55     def convert_geom(clob, geo_field):
       
    56         if clob:
       
    57             return SpatialBackend.Geometry(clob.read(), geo_field.srid)
       
    58         else:
       
    59             return None
       
    60 elif SpatialBackend.spatialite:
       
    61     # SpatiaLite returns WKT.
       
    62     def convert_geom(wkt, geo_field):
       
    63         if wkt:
       
    64             return SpatialBackend.Geometry(wkt, geo_field.srid)
       
    65         else:
       
    66             return None
       
    67 
     4 
    68 class GeoAggregate(Aggregate):
     5 class GeoAggregate(Aggregate):
    69     # Overriding the SQL template with the geographic one.
     6     # Default SQL template for spatial aggregates.
    70     sql_template = geo_template
     7     sql_template = '%(function)s(%(field)s)'
    71 
     8 
    72     # Conversion class, if necessary.
     9     # Conversion class, if necessary.
    73     conversion_class = None
    10     conversion_class = None
    74 
    11 
    75     # Flags for indicating the type of the aggregate.
    12     # Flags for indicating the type of the aggregate.
    76     is_extent = False
    13     is_extent = False
    77 
    14 
    78     def __init__(self, col, source=None, is_summary=False, **extra):
    15     def __init__(self, col, source=None, is_summary=False, tolerance=0.05, **extra):
    79         super(GeoAggregate, self).__init__(col, source, is_summary, **extra)
    16         super(GeoAggregate, self).__init__(col, source, is_summary, **extra)
    80 
    17 
    81         if not self.is_extent and SpatialBackend.oracle:
    18         # Required by some Oracle aggregates.
    82             self.extra.setdefault('tolerance', 0.05)
    19         self.tolerance = tolerance
    83 
    20 
    84         # Can't use geographic aggregates on non-geometry fields.
    21         # Can't use geographic aggregates on non-geometry fields.
    85         if not isinstance(self.source, GeometryField):
    22         if not isinstance(self.source, GeometryField):
    86             raise ValueError('Geospatial aggregates only allowed on geometry fields.')
    23             raise ValueError('Geospatial aggregates only allowed on geometry fields.')
    87 
    24 
    88         # Making sure the SQL function is available for this spatial backend.
    25     def as_sql(self, qn, connection):
    89         if not self.sql_function:
    26         "Return the aggregate, rendered as SQL."
    90             raise NotImplementedError('This aggregate functionality not implemented for your spatial backend.')
    27 
       
    28         if connection.ops.oracle:
       
    29             self.extra['tolerance'] = self.tolerance
       
    30 
       
    31         if hasattr(self.col, 'as_sql'):
       
    32             field_name = self.col.as_sql(qn, connection)
       
    33         elif isinstance(self.col, (list, tuple)):
       
    34             field_name = '.'.join([qn(c) for c in self.col])
       
    35         else:
       
    36             field_name = self.col
       
    37 
       
    38         sql_template, sql_function = connection.ops.spatial_aggregate_sql(self)
       
    39 
       
    40         params = {
       
    41             'function': sql_function,
       
    42             'field': field_name
       
    43         }
       
    44         params.update(self.extra)
       
    45 
       
    46         return sql_template % params
    91 
    47 
    92 class Collect(GeoAggregate):
    48 class Collect(GeoAggregate):
    93     conversion_class = GeomField
    49     pass
    94     sql_function = SpatialBackend.collect
       
    95 
    50 
    96 class Extent(GeoAggregate):
    51 class Extent(GeoAggregate):
    97     is_extent = True
    52     is_extent = '2D'
    98     sql_function = SpatialBackend.extent
       
    99 
    53 
   100 if SpatialBackend.oracle:
    54 class Extent3D(GeoAggregate):
   101     # Have to change Extent's attributes here for Oracle.
    55     is_extent = '3D'
   102     Extent.conversion_class = GeomField
       
   103     Extent.sql_template = '%(function)s(%(field)s)'
       
   104 
    56 
   105 class MakeLine(GeoAggregate):
    57 class MakeLine(GeoAggregate):
   106     conversion_class = GeomField
    58     pass
   107     sql_function = SpatialBackend.make_line
       
   108 
    59 
   109 class Union(GeoAggregate):
    60 class Union(GeoAggregate):
   110     conversion_class = GeomField
    61     pass
   111     sql_function = SpatialBackend.unionagg