web/lib/django/contrib/gis/db/models/sql/query.py
changeset 38 77b6da96e6f1
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django/contrib/gis/db/models/sql/query.py	Wed Jun 02 18:57:35 2010 +0200
@@ -0,0 +1,119 @@
+from django.db import connections
+from django.db.models.query import sql
+
+from django.contrib.gis.db.models.fields import GeometryField
+from django.contrib.gis.db.models.sql import aggregates as gis_aggregates
+from django.contrib.gis.db.models.sql.conversion import AreaField, DistanceField, GeomField
+from django.contrib.gis.db.models.sql.where import GeoWhereNode
+from django.contrib.gis.geometry.backend import Geometry
+from django.contrib.gis.measure import Area, Distance
+
+
+ALL_TERMS = dict([(x, None) for x in (
+            'bbcontains', 'bboverlaps', 'contained', 'contains',
+            'contains_properly', 'coveredby', 'covers', 'crosses', 'disjoint',
+            'distance_gt', 'distance_gte', 'distance_lt', 'distance_lte',
+            'dwithin', 'equals', 'exact',
+            'intersects', 'overlaps', 'relate', 'same_as', 'touches', 'within',
+            'left', 'right', 'overlaps_left', 'overlaps_right',
+            'overlaps_above', 'overlaps_below',
+            'strictly_above', 'strictly_below'
+            )])
+ALL_TERMS.update(sql.constants.QUERY_TERMS)
+
+class GeoQuery(sql.Query):
+    """
+    A single spatial SQL query.
+    """
+    # Overridding the valid query terms.
+    query_terms = ALL_TERMS
+    aggregates_module = gis_aggregates
+
+    compiler = 'GeoSQLCompiler'
+
+    #### Methods overridden from the base Query class ####
+    def __init__(self, model, where=GeoWhereNode):
+        super(GeoQuery, self).__init__(model, where)
+        # The following attributes are customized for the GeoQuerySet.
+        # The GeoWhereNode and SpatialBackend classes contain backend-specific
+        # routines and functions.
+        self.custom_select = {}
+        self.transformed_srid = None
+        self.extra_select_fields = {}
+
+    def clone(self, *args, **kwargs):
+        obj = super(GeoQuery, self).clone(*args, **kwargs)
+        # Customized selection dictionary and transformed srid flag have
+        # to also be added to obj.
+        obj.custom_select = self.custom_select.copy()
+        obj.transformed_srid = self.transformed_srid
+        obj.extra_select_fields = self.extra_select_fields.copy()
+        return obj
+
+    def convert_values(self, value, field, connection):
+        """
+        Using the same routines that Oracle does we can convert our
+        extra selection objects into Geometry and Distance objects.
+        TODO: Make converted objects 'lazy' for less overhead.
+        """
+        if connection.ops.oracle:
+            # Running through Oracle's first.
+            value = super(GeoQuery, self).convert_values(value, field or GeomField(), connection)
+
+        if value is None:
+            # Output from spatial function is NULL (e.g., called
+            # function on a geometry field with NULL value).
+            pass
+        elif isinstance(field, DistanceField):
+            # Using the field's distance attribute, can instantiate
+            # `Distance` with the right context.
+            value = Distance(**{field.distance_att : value})
+        elif isinstance(field, AreaField):
+            value = Area(**{field.area_att : value})
+        elif isinstance(field, (GeomField, GeometryField)) and value:
+            value = Geometry(value)
+        return value
+
+    def get_aggregation(self, using):
+        # Remove any aggregates marked for reduction from the subquery
+        # and move them to the outer AggregateQuery.
+        connection = connections[using]
+        for alias, aggregate in self.aggregate_select.items():
+            if isinstance(aggregate, gis_aggregates.GeoAggregate):
+                if not getattr(aggregate, 'is_extent', False) or connection.ops.oracle:
+                    self.extra_select_fields[alias] = GeomField()
+        return super(GeoQuery, self).get_aggregation(using)
+
+    def resolve_aggregate(self, value, aggregate, connection):
+        """
+        Overridden from GeoQuery's normalize to handle the conversion of
+        GeoAggregate objects.
+        """
+        if isinstance(aggregate, self.aggregates_module.GeoAggregate):
+            if aggregate.is_extent:
+                if aggregate.is_extent == '3D':
+                    return connection.ops.convert_extent3d(value)
+                else:
+                    return connection.ops.convert_extent(value)
+            else:
+                return connection.ops.convert_geom(value, aggregate.source)
+        else:
+            return super(GeoQuery, self).resolve_aggregate(value, aggregate, connection)
+
+    # Private API utilities, subject to change.
+    def _geo_field(self, field_name=None):
+        """
+        Returns the first Geometry field encountered; or specified via the
+        `field_name` keyword.  The `field_name` may be a string specifying
+        the geometry field on this GeoQuery's model, or a lookup string
+        to a geometry field via a ForeignKey relation.
+        """
+        if field_name is None:
+            # Incrementing until the first geographic field is found.
+            for fld in self.model._meta.fields:
+                if isinstance(fld, GeometryField): return fld
+            return False
+        else:
+            # Otherwise, check by the given field name -- which may be
+            # a lookup to a _related_ geographic field.
+            return GeoWhereNode._check_geo_field(self.model._meta, field_name)