query.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. from django.db import connections
  2. from django.db.models.query import sql
  3. from django.contrib.gis.db.models.constants import ALL_TERMS
  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. class GeoQuery(sql.Query):
  11. """
  12. A single spatial SQL query.
  13. """
  14. # Overridding the valid query terms.
  15. query_terms = ALL_TERMS
  16. aggregates_module = gis_aggregates
  17. compiler = 'GeoSQLCompiler'
  18. #### Methods overridden from the base Query class ####
  19. def __init__(self, model, where=GeoWhereNode):
  20. super(GeoQuery, self).__init__(model, where)
  21. # The following attributes are customized for the GeoQuerySet.
  22. # The GeoWhereNode and SpatialBackend classes contain backend-specific
  23. # routines and functions.
  24. self.custom_select = {}
  25. self.transformed_srid = None
  26. self.extra_select_fields = {}
  27. def clone(self, *args, **kwargs):
  28. obj = super(GeoQuery, self).clone(*args, **kwargs)
  29. # Customized selection dictionary and transformed srid flag have
  30. # to also be added to obj.
  31. obj.custom_select = self.custom_select.copy()
  32. obj.transformed_srid = self.transformed_srid
  33. obj.extra_select_fields = self.extra_select_fields.copy()
  34. return obj
  35. def convert_values(self, value, field, connection):
  36. """
  37. Using the same routines that Oracle does we can convert our
  38. extra selection objects into Geometry and Distance objects.
  39. TODO: Make converted objects 'lazy' for less overhead.
  40. """
  41. if connection.ops.oracle:
  42. # Running through Oracle's first.
  43. value = super(GeoQuery, self).convert_values(value, field or GeomField(), connection)
  44. if value is None:
  45. # Output from spatial function is NULL (e.g., called
  46. # function on a geometry field with NULL value).
  47. pass
  48. elif isinstance(field, DistanceField):
  49. # Using the field's distance attribute, can instantiate
  50. # `Distance` with the right context.
  51. value = Distance(**{field.distance_att: value})
  52. elif isinstance(field, AreaField):
  53. value = Area(**{field.area_att: value})
  54. elif isinstance(field, (GeomField, GeometryField)) and value:
  55. value = Geometry(value)
  56. elif field is not None:
  57. return super(GeoQuery, self).convert_values(value, field, connection)
  58. return value
  59. def get_aggregation(self, using, force_subq=False):
  60. # Remove any aggregates marked for reduction from the subquery
  61. # and move them to the outer AggregateQuery.
  62. connection = connections[using]
  63. for alias, aggregate in self.aggregate_select.items():
  64. if isinstance(aggregate, gis_aggregates.GeoAggregate):
  65. if not getattr(aggregate, 'is_extent', False) or connection.ops.oracle:
  66. self.extra_select_fields[alias] = GeomField()
  67. return super(GeoQuery, self).get_aggregation(using, force_subq)
  68. def resolve_aggregate(self, value, aggregate, connection):
  69. """
  70. Overridden from GeoQuery's normalize to handle the conversion of
  71. GeoAggregate objects.
  72. """
  73. if isinstance(aggregate, self.aggregates_module.GeoAggregate):
  74. if aggregate.is_extent:
  75. if aggregate.is_extent == '3D':
  76. return connection.ops.convert_extent3d(value)
  77. else:
  78. return connection.ops.convert_extent(value)
  79. else:
  80. return connection.ops.convert_geom(value, aggregate.source)
  81. else:
  82. return super(GeoQuery, self).resolve_aggregate(value, aggregate, connection)
  83. # Private API utilities, subject to change.
  84. def _geo_field(self, field_name=None):
  85. """
  86. Returns the first Geometry field encountered; or specified via the
  87. `field_name` keyword. The `field_name` may be a string specifying
  88. the geometry field on this GeoQuery's model, or a lookup string
  89. to a geometry field via a ForeignKey relation.
  90. """
  91. if field_name is None:
  92. # Incrementing until the first geographic field is found.
  93. for fld in self.model._meta.fields:
  94. if isinstance(fld, GeometryField):
  95. return fld
  96. return False
  97. else:
  98. # Otherwise, check by the given field name -- which may be
  99. # a lookup to a _related_ geographic field.
  100. return GeoWhereNode._check_geo_field(self.model._meta, field_name)