web/lib/django/contrib/gis/db/models/sql/query.py
changeset 38 77b6da96e6f1
equal deleted inserted replaced
37:8d941af65caf 38:77b6da96e6f1
       
     1 from django.db import connections
       
     2 from django.db.models.query import sql
       
     3 
       
     4 from django.contrib.gis.db.models.fields import GeometryField
       
     5 from django.contrib.gis.db.models.sql import aggregates as gis_aggregates
       
     6 from django.contrib.gis.db.models.sql.conversion import AreaField, DistanceField, GeomField
       
     7 from django.contrib.gis.db.models.sql.where import GeoWhereNode
       
     8 from django.contrib.gis.geometry.backend import Geometry
       
     9 from django.contrib.gis.measure import Area, Distance
       
    10 
       
    11 
       
    12 ALL_TERMS = dict([(x, None) for x in (
       
    13             'bbcontains', 'bboverlaps', 'contained', 'contains',
       
    14             'contains_properly', 'coveredby', 'covers', 'crosses', 'disjoint',
       
    15             'distance_gt', 'distance_gte', 'distance_lt', 'distance_lte',
       
    16             'dwithin', 'equals', 'exact',
       
    17             'intersects', 'overlaps', 'relate', 'same_as', 'touches', 'within',
       
    18             'left', 'right', 'overlaps_left', 'overlaps_right',
       
    19             'overlaps_above', 'overlaps_below',
       
    20             'strictly_above', 'strictly_below'
       
    21             )])
       
    22 ALL_TERMS.update(sql.constants.QUERY_TERMS)
       
    23 
       
    24 class GeoQuery(sql.Query):
       
    25     """
       
    26     A single spatial SQL query.
       
    27     """
       
    28     # Overridding the valid query terms.
       
    29     query_terms = ALL_TERMS
       
    30     aggregates_module = gis_aggregates
       
    31 
       
    32     compiler = 'GeoSQLCompiler'
       
    33 
       
    34     #### Methods overridden from the base Query class ####
       
    35     def __init__(self, model, where=GeoWhereNode):
       
    36         super(GeoQuery, self).__init__(model, where)
       
    37         # The following attributes are customized for the GeoQuerySet.
       
    38         # The GeoWhereNode and SpatialBackend classes contain backend-specific
       
    39         # routines and functions.
       
    40         self.custom_select = {}
       
    41         self.transformed_srid = None
       
    42         self.extra_select_fields = {}
       
    43 
       
    44     def clone(self, *args, **kwargs):
       
    45         obj = super(GeoQuery, self).clone(*args, **kwargs)
       
    46         # Customized selection dictionary and transformed srid flag have
       
    47         # to also be added to obj.
       
    48         obj.custom_select = self.custom_select.copy()
       
    49         obj.transformed_srid = self.transformed_srid
       
    50         obj.extra_select_fields = self.extra_select_fields.copy()
       
    51         return obj
       
    52 
       
    53     def convert_values(self, value, field, connection):
       
    54         """
       
    55         Using the same routines that Oracle does we can convert our
       
    56         extra selection objects into Geometry and Distance objects.
       
    57         TODO: Make converted objects 'lazy' for less overhead.
       
    58         """
       
    59         if connection.ops.oracle:
       
    60             # Running through Oracle's first.
       
    61             value = super(GeoQuery, self).convert_values(value, field or GeomField(), connection)
       
    62 
       
    63         if value is None:
       
    64             # Output from spatial function is NULL (e.g., called
       
    65             # function on a geometry field with NULL value).
       
    66             pass
       
    67         elif isinstance(field, DistanceField):
       
    68             # Using the field's distance attribute, can instantiate
       
    69             # `Distance` with the right context.
       
    70             value = Distance(**{field.distance_att : value})
       
    71         elif isinstance(field, AreaField):
       
    72             value = Area(**{field.area_att : value})
       
    73         elif isinstance(field, (GeomField, GeometryField)) and value:
       
    74             value = Geometry(value)
       
    75         return value
       
    76 
       
    77     def get_aggregation(self, using):
       
    78         # Remove any aggregates marked for reduction from the subquery
       
    79         # and move them to the outer AggregateQuery.
       
    80         connection = connections[using]
       
    81         for alias, aggregate in self.aggregate_select.items():
       
    82             if isinstance(aggregate, gis_aggregates.GeoAggregate):
       
    83                 if not getattr(aggregate, 'is_extent', False) or connection.ops.oracle:
       
    84                     self.extra_select_fields[alias] = GeomField()
       
    85         return super(GeoQuery, self).get_aggregation(using)
       
    86 
       
    87     def resolve_aggregate(self, value, aggregate, connection):
       
    88         """
       
    89         Overridden from GeoQuery's normalize to handle the conversion of
       
    90         GeoAggregate objects.
       
    91         """
       
    92         if isinstance(aggregate, self.aggregates_module.GeoAggregate):
       
    93             if aggregate.is_extent:
       
    94                 if aggregate.is_extent == '3D':
       
    95                     return connection.ops.convert_extent3d(value)
       
    96                 else:
       
    97                     return connection.ops.convert_extent(value)
       
    98             else:
       
    99                 return connection.ops.convert_geom(value, aggregate.source)
       
   100         else:
       
   101             return super(GeoQuery, self).resolve_aggregate(value, aggregate, connection)
       
   102 
       
   103     # Private API utilities, subject to change.
       
   104     def _geo_field(self, field_name=None):
       
   105         """
       
   106         Returns the first Geometry field encountered; or specified via the
       
   107         `field_name` keyword.  The `field_name` may be a string specifying
       
   108         the geometry field on this GeoQuery's model, or a lookup string
       
   109         to a geometry field via a ForeignKey relation.
       
   110         """
       
   111         if field_name is None:
       
   112             # Incrementing until the first geographic field is found.
       
   113             for fld in self.model._meta.fields:
       
   114                 if isinstance(fld, GeometryField): return fld
       
   115             return False
       
   116         else:
       
   117             # Otherwise, check by the given field name -- which may be
       
   118             # a lookup to a _related_ geographic field.
       
   119             return GeoWhereNode._check_geo_field(self.model._meta, field_name)