diff options
Diffstat (limited to 'django/contrib/gis/db/backends/postgis/operations.py')
-rw-r--r-- | django/contrib/gis/db/backends/postgis/operations.py | 108 |
1 files changed, 81 insertions, 27 deletions
diff --git a/django/contrib/gis/db/backends/postgis/operations.py b/django/contrib/gis/db/backends/postgis/operations.py index 89ddc11127..8216668ca5 100644 --- a/django/contrib/gis/db/backends/postgis/operations.py +++ b/django/contrib/gis/db/backends/postgis/operations.py @@ -4,30 +4,83 @@ from django.conf import settings from django.contrib.gis.db.backends.base.operations import \ BaseSpatialOperations from django.contrib.gis.db.backends.utils import SpatialOperator +from django.contrib.gis.gdal import GDALRaster from django.contrib.gis.geometry.backend import Geometry from django.contrib.gis.measure import Distance from django.core.exceptions import ImproperlyConfigured from django.db.backends.postgresql.operations import DatabaseOperations from django.db.utils import ProgrammingError +from django.utils import six from django.utils.functional import cached_property from .adapter import PostGISAdapter from .models import PostGISGeometryColumns, PostGISSpatialRefSys from .pgraster import from_pgraster, get_pgraster_srid, to_pgraster +# Identifier to mark raster lookups as bilateral. +BILATERAL = 'bilateral' + class PostGISOperator(SpatialOperator): - def __init__(self, geography=False, **kwargs): - # Only a subset of the operators and functions are available - # for the geography type. + def __init__(self, geography=False, raster=False, **kwargs): + # Only a subset of the operators and functions are available for the + # geography type. self.geography = geography + # Only a subset of the operators and functions are available for the + # raster type. Lookups that don't suport raster will be converted to + # polygons. If the raster argument is set to BILATERAL, then the + # operator cannot handle mixed geom-raster lookups. + self.raster = raster super(PostGISOperator, self).__init__(**kwargs) - def as_sql(self, connection, lookup, *args): + def as_sql(self, connection, lookup, template_params, *args): if lookup.lhs.output_field.geography and not self.geography: raise ValueError('PostGIS geography does not support the "%s" ' 'function/operator.' % (self.func or self.op,)) - return super(PostGISOperator, self).as_sql(connection, lookup, *args) + + template_params = self.check_raster(lookup, template_params) + return super(PostGISOperator, self).as_sql(connection, lookup, template_params, *args) + + def check_raster(self, lookup, template_params): + # Get rhs value. + if isinstance(lookup.rhs, (tuple, list)): + rhs_val = lookup.rhs[0] + spheroid = lookup.rhs[-1] == 'spheroid' + else: + rhs_val = lookup.rhs + spheroid = False + + # Check which input is a raster. + lhs_is_raster = lookup.lhs.field.geom_type == 'RASTER' + rhs_is_raster = isinstance(rhs_val, GDALRaster) + + # Look for band indices and inject them if provided. + if lookup.band_lhs is not None and lhs_is_raster: + if not self.func: + raise ValueError('Band indices are not allowed for this operator, it works on bbox only.') + template_params['lhs'] = '%s, %s' % (template_params['lhs'], lookup.band_lhs) + + if lookup.band_rhs is not None and rhs_is_raster: + if not self.func: + raise ValueError('Band indices are not allowed for this operator, it works on bbox only.') + template_params['rhs'] = '%s, %s' % (template_params['rhs'], lookup.band_rhs) + + # Convert rasters to polygons if necessary. + if not self.raster or spheroid: + # Operators without raster support. + if lhs_is_raster: + template_params['lhs'] = 'ST_Polygon(%s)' % template_params['lhs'] + if rhs_is_raster: + template_params['rhs'] = 'ST_Polygon(%s)' % template_params['rhs'] + elif self.raster == BILATERAL: + # Operators with raster support but don't support mixed (rast-geom) + # lookups. + if lhs_is_raster and not rhs_is_raster: + template_params['lhs'] = 'ST_Polygon(%s)' % template_params['lhs'] + elif rhs_is_raster and not lhs_is_raster: + template_params['rhs'] = 'ST_Polygon(%s)' % template_params['rhs'] + + return template_params class PostGISDistanceOperator(PostGISOperator): @@ -35,6 +88,7 @@ class PostGISDistanceOperator(PostGISOperator): def as_sql(self, connection, lookup, template_params, sql_params): if not lookup.lhs.output_field.geography and lookup.lhs.output_field.geodetic(connection): + template_params = self.check_raster(lookup, template_params) sql_template = self.sql_template if len(lookup.rhs) == 3 and lookup.rhs[-1] == 'spheroid': template_params.update({'op': self.op, 'func': 'ST_Distance_Spheroid'}) @@ -58,33 +112,33 @@ class PostGISOperations(BaseSpatialOperations, DatabaseOperations): Adapter = PostGISAdapter gis_operators = { - 'bbcontains': PostGISOperator(op='~'), - 'bboverlaps': PostGISOperator(op='&&', geography=True), - 'contained': PostGISOperator(op='@'), - 'contains': PostGISOperator(func='ST_Contains'), - 'overlaps_left': PostGISOperator(op='&<'), - 'overlaps_right': PostGISOperator(op='&>'), + 'bbcontains': PostGISOperator(op='~', raster=True), + 'bboverlaps': PostGISOperator(op='&&', geography=True, raster=True), + 'contained': PostGISOperator(op='@', raster=True), + 'overlaps_left': PostGISOperator(op='&<', raster=BILATERAL), + 'overlaps_right': PostGISOperator(op='&>', raster=BILATERAL), 'overlaps_below': PostGISOperator(op='&<|'), 'overlaps_above': PostGISOperator(op='|&>'), 'left': PostGISOperator(op='<<'), 'right': PostGISOperator(op='>>'), 'strictly_below': PostGISOperator(op='<<|'), 'strictly_above': PostGISOperator(op='|>>'), - 'same_as': PostGISOperator(op='~='), - 'exact': PostGISOperator(op='~='), # alias of same_as - 'contains_properly': PostGISOperator(func='ST_ContainsProperly'), - 'coveredby': PostGISOperator(func='ST_CoveredBy', geography=True), - 'covers': PostGISOperator(func='ST_Covers', geography=True), + 'same_as': PostGISOperator(op='~=', raster=BILATERAL), + 'exact': PostGISOperator(op='~=', raster=BILATERAL), # alias of same_as + 'contains': PostGISOperator(func='ST_Contains', raster=BILATERAL), + 'contains_properly': PostGISOperator(func='ST_ContainsProperly', raster=BILATERAL), + 'coveredby': PostGISOperator(func='ST_CoveredBy', geography=True, raster=BILATERAL), + 'covers': PostGISOperator(func='ST_Covers', geography=True, raster=BILATERAL), 'crosses': PostGISOperator(func='ST_Crosses'), - 'disjoint': PostGISOperator(func='ST_Disjoint'), + 'disjoint': PostGISOperator(func='ST_Disjoint', raster=BILATERAL), 'equals': PostGISOperator(func='ST_Equals'), - 'intersects': PostGISOperator(func='ST_Intersects', geography=True), + 'intersects': PostGISOperator(func='ST_Intersects', geography=True, raster=BILATERAL), 'isvalid': PostGISOperator(func='ST_IsValid'), - 'overlaps': PostGISOperator(func='ST_Overlaps'), + 'overlaps': PostGISOperator(func='ST_Overlaps', raster=BILATERAL), 'relate': PostGISOperator(func='ST_Relate'), - 'touches': PostGISOperator(func='ST_Touches'), - 'within': PostGISOperator(func='ST_Within'), - 'dwithin': PostGISOperator(func='ST_DWithin', geography=True), + 'touches': PostGISOperator(func='ST_Touches', raster=BILATERAL), + 'within': PostGISOperator(func='ST_Within', raster=BILATERAL), + 'dwithin': PostGISOperator(func='ST_DWithin', geography=True, raster=BILATERAL), 'distance_gt': PostGISDistanceOperator(func='ST_Distance', op='>', geography=True), 'distance_gte': PostGISDistanceOperator(func='ST_Distance', op='>=', geography=True), 'distance_lt': PostGISDistanceOperator(func='ST_Distance', op='<', geography=True), @@ -272,14 +326,14 @@ class PostGISOperations(BaseSpatialOperations, DatabaseOperations): def get_geom_placeholder(self, f, value, compiler): """ - Provides a proper substitution value for Geometries that are not in the - SRID of the field. Specifically, this routine will substitute in the - ST_Transform() function call. + Provide a proper substitution value for Geometries or rasters that are + not in the SRID of the field. Specifically, this routine will + substitute in the ST_Transform() function call. """ # Get the srid for this object if value is None: value_srid = None - elif f.geom_type == 'RASTER': + elif f.geom_type == 'RASTER' and isinstance(value, six.string_types): value_srid = get_pgraster_srid(value) else: value_srid = value.srid @@ -288,7 +342,7 @@ class PostGISOperations(BaseSpatialOperations, DatabaseOperations): # is not equal to the field srid. if value_srid is None or value_srid == f.srid: placeholder = '%s' - elif f.geom_type == 'RASTER': + elif f.geom_type == 'RASTER' and isinstance(value, six.string_types): placeholder = '%s((%%s)::raster, %s)' % (self.transform, f.srid) else: placeholder = '%s(%%s, %s)' % (self.transform, f.srid) |