|
0
|
1 |
from django.db.models.sql.aggregates import * |
|
|
2 |
from django.contrib.gis.db.models.fields import GeometryField |
|
|
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 |
|
|
|
68 |
class GeoAggregate(Aggregate): |
|
|
69 |
# Overriding the SQL template with the geographic one. |
|
|
70 |
sql_template = geo_template |
|
|
71 |
|
|
|
72 |
# Conversion class, if necessary. |
|
|
73 |
conversion_class = None |
|
|
74 |
|
|
|
75 |
# Flags for indicating the type of the aggregate. |
|
|
76 |
is_extent = False |
|
|
77 |
|
|
|
78 |
def __init__(self, col, source=None, is_summary=False, **extra): |
|
|
79 |
super(GeoAggregate, self).__init__(col, source, is_summary, **extra) |
|
|
80 |
|
|
|
81 |
if not self.is_extent and SpatialBackend.oracle: |
|
|
82 |
self.extra.setdefault('tolerance', 0.05) |
|
|
83 |
|
|
|
84 |
# Can't use geographic aggregates on non-geometry fields. |
|
|
85 |
if not isinstance(self.source, GeometryField): |
|
|
86 |
raise ValueError('Geospatial aggregates only allowed on geometry fields.') |
|
|
87 |
|
|
|
88 |
# Making sure the SQL function is available for this spatial backend. |
|
|
89 |
if not self.sql_function: |
|
|
90 |
raise NotImplementedError('This aggregate functionality not implemented for your spatial backend.') |
|
|
91 |
|
|
|
92 |
class Collect(GeoAggregate): |
|
|
93 |
conversion_class = GeomField |
|
|
94 |
sql_function = SpatialBackend.collect |
|
|
95 |
|
|
|
96 |
class Extent(GeoAggregate): |
|
|
97 |
is_extent = True |
|
|
98 |
sql_function = SpatialBackend.extent |
|
|
99 |
|
|
|
100 |
if SpatialBackend.oracle: |
|
|
101 |
# Have to change Extent's attributes here for Oracle. |
|
|
102 |
Extent.conversion_class = GeomField |
|
|
103 |
Extent.sql_template = '%(function)s(%(field)s)' |
|
|
104 |
|
|
|
105 |
class MakeLine(GeoAggregate): |
|
|
106 |
conversion_class = GeomField |
|
|
107 |
sql_function = SpatialBackend.make_line |
|
|
108 |
|
|
|
109 |
class Union(GeoAggregate): |
|
|
110 |
conversion_class = GeomField |
|
|
111 |
sql_function = SpatialBackend.unionagg |