You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

operations.py 6.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. from django.contrib.gis.db.models import GeometryField
  2. from django.contrib.gis.db.models.functions import Distance
  3. from django.contrib.gis.measure import (
  4. Area as AreaMeasure, Distance as DistanceMeasure,
  5. )
  6. from django.db.utils import NotSupportedError
  7. from django.utils.functional import cached_property
  8. class BaseSpatialOperations:
  9. # Quick booleans for the type of this spatial backend, and
  10. # an attribute for the spatial database version tuple (if applicable)
  11. postgis = False
  12. spatialite = False
  13. mysql = False
  14. oracle = False
  15. spatial_version = None
  16. # How the geometry column should be selected.
  17. select = '%s'
  18. @cached_property
  19. def select_extent(self):
  20. return self.select
  21. # Does the spatial database have a geometry or geography type?
  22. geography = False
  23. geometry = False
  24. # Aggregates
  25. disallowed_aggregates = ()
  26. geom_func_prefix = ''
  27. # Mapping between Django function names and backend names, when names do not
  28. # match; used in spatial_function_name().
  29. function_names = {}
  30. # Blacklist/set of known unsupported functions of the backend
  31. unsupported_functions = {
  32. 'Area', 'AsGeoJSON', 'AsGML', 'AsKML', 'AsSVG', 'Azimuth',
  33. 'BoundingCircle', 'Centroid', 'Difference', 'Distance', 'Envelope',
  34. 'ForceRHR', 'GeoHash', 'Intersection', 'IsValid', 'Length',
  35. 'LineLocatePoint', 'MakeValid', 'MemSize', 'NumGeometries',
  36. 'NumPoints', 'Perimeter', 'PointOnSurface', 'Reverse', 'Scale',
  37. 'SnapToGrid', 'SymDifference', 'Transform', 'Translate', 'Union',
  38. }
  39. # Constructors
  40. from_text = False
  41. # Default conversion functions for aggregates; will be overridden if implemented
  42. # for the spatial backend.
  43. def convert_extent(self, box, srid):
  44. raise NotImplementedError('Aggregate extent not implemented for this spatial backend.')
  45. def convert_extent3d(self, box, srid):
  46. raise NotImplementedError('Aggregate 3D extent not implemented for this spatial backend.')
  47. # For quoting column values, rather than columns.
  48. def geo_quote_name(self, name):
  49. return "'%s'" % name
  50. # GeometryField operations
  51. def geo_db_type(self, f):
  52. """
  53. Return the database column type for the geometry field on
  54. the spatial backend.
  55. """
  56. raise NotImplementedError('subclasses of BaseSpatialOperations must provide a geo_db_type() method')
  57. def get_distance(self, f, value, lookup_type):
  58. """
  59. Return the distance parameters for the given geometry field,
  60. lookup value, and lookup type.
  61. """
  62. raise NotImplementedError('Distance operations not available on this spatial backend.')
  63. def get_geom_placeholder(self, f, value, compiler):
  64. """
  65. Return the placeholder for the given geometry field with the given
  66. value. Depending on the spatial backend, the placeholder may contain a
  67. stored procedure call to the transformation function of the spatial
  68. backend.
  69. """
  70. def transform_value(value, field):
  71. return value is not None and value.srid != field.srid
  72. if hasattr(value, 'as_sql'):
  73. return (
  74. '%s(%%s, %s)' % (self.spatial_function_name('Transform'), f.srid)
  75. if transform_value(value.output_field, f)
  76. else '%s'
  77. )
  78. if transform_value(value, f):
  79. # Add Transform() to the SQL placeholder.
  80. return '%s(%s(%%s,%s), %s)' % (
  81. self.spatial_function_name('Transform'),
  82. self.from_text, value.srid, f.srid,
  83. )
  84. elif self.connection.features.has_spatialrefsys_table:
  85. return '%s(%%s,%s)' % (self.from_text, f.srid)
  86. else:
  87. # For backwards compatibility on MySQL (#27464).
  88. return '%s(%%s)' % self.from_text
  89. def check_expression_support(self, expression):
  90. if isinstance(expression, self.disallowed_aggregates):
  91. raise NotSupportedError(
  92. "%s spatial aggregation is not supported by this database backend." % expression.name
  93. )
  94. super().check_expression_support(expression)
  95. def spatial_aggregate_name(self, agg_name):
  96. raise NotImplementedError('Aggregate support not implemented for this spatial backend.')
  97. def spatial_function_name(self, func_name):
  98. if func_name in self.unsupported_functions:
  99. raise NotSupportedError("This backend doesn't support the %s function." % func_name)
  100. return self.function_names.get(func_name, self.geom_func_prefix + func_name)
  101. # Routines for getting the OGC-compliant models.
  102. def geometry_columns(self):
  103. raise NotImplementedError('Subclasses of BaseSpatialOperations must provide a geometry_columns() method.')
  104. def spatial_ref_sys(self):
  105. raise NotImplementedError('subclasses of BaseSpatialOperations must a provide spatial_ref_sys() method')
  106. distance_expr_for_lookup = staticmethod(Distance)
  107. def get_db_converters(self, expression):
  108. converters = super().get_db_converters(expression)
  109. if isinstance(expression.output_field, GeometryField):
  110. converters.append(self.get_geometry_converter(expression))
  111. return converters
  112. def get_geometry_converter(self, expression):
  113. raise NotImplementedError(
  114. 'Subclasses of BaseSpatialOperations must provide a '
  115. 'get_geometry_converter() method.'
  116. )
  117. def get_area_att_for_field(self, field):
  118. if field.geodetic(self.connection):
  119. if self.connection.features.supports_area_geodetic:
  120. return 'sq_m'
  121. raise NotImplementedError('Area on geodetic coordinate systems not supported.')
  122. else:
  123. units_name = field.units_name(self.connection)
  124. if units_name:
  125. return AreaMeasure.unit_attname(units_name)
  126. def get_distance_att_for_field(self, field):
  127. dist_att = None
  128. if field.geodetic(self.connection):
  129. if self.connection.features.supports_distance_geodetic:
  130. dist_att = 'm'
  131. else:
  132. units = field.units_name(self.connection)
  133. if units:
  134. dist_att = DistanceMeasure.unit_attname(units)
  135. return dist_att