Commit b769bbd4 authored by Daniel Wiesmann's avatar Daniel Wiesmann Committed by Tim Graham
Browse files

Fixed #23804 -- Added RasterField for PostGIS.

Thanks to Tim Graham and Claude Paroz for the reviews and patches.
parent d3d66d47
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -37,6 +37,9 @@ class BaseSpatialFeatures(object):
    supports_distances_lookups = True
    supports_left_right_lookups = False

    # Does the database have raster support?
    supports_raster = False

    @property
    def supports_bbcontains_lookup(self):
        return 'bbcontains' in self.connection.ops.gis_operators
+43 −0
Original line number Diff line number Diff line
"""
PostGIS to GDAL conversion constant definitions
"""
# Lookup to convert pixel type values from GDAL to PostGIS
GDAL_TO_POSTGIS = [None, 4, 6, 5, 8, 7, 10, 11, None, None, None, None]

# Lookup to convert pixel type values from PostGIS to GDAL
POSTGIS_TO_GDAL = [1, 1, 1, 3, 1, 3, 2, 5, 4, None, 6, 7, None, None]

# Struct pack structure for raster header, the raster header has the
# following structure:
#
# Endianness, PostGIS raster version, number of bands, scale, origin,
# skew, srid, width, and height.
#
# Scale, origin, and skew have x and y values. PostGIS currently uses
# a fixed endianness (1) and there is only one version (0).
POSTGIS_HEADER_STRUCTURE = 'B H H d d d d d d i H H'

# Lookup values to convert GDAL pixel types to struct characters. This is
# used to pack and unpack the pixel values of PostGIS raster bands.
GDAL_TO_STRUCT = [
    None, 'B', 'H', 'h', 'L', 'l', 'f', 'd',
    None, None, None, None,
]

# Size of the packed value in bytes for different numerical types.
# This is needed to cut chunks of band data out of PostGIS raster strings
# when decomposing them into GDALRasters.
# See https://docs.python.org/3/library/struct.html#format-characters
STRUCT_SIZE = {
    'b': 1,  # Signed char
    'B': 1,  # Unsigned char
    '?': 1,  # _Bool
    'h': 2,  # Short
    'H': 2,  # Unsigned short
    'i': 4,  # Integer
    'I': 4,  # Unsigned Integer
    'l': 4,  # Long
    'L': 4,  # Unsigned Long
    'f': 4,  # Float
    'd': 8,  # Double
}
+1 −0
Original line number Diff line number Diff line
@@ -7,3 +7,4 @@ class DatabaseFeatures(BaseSpatialFeatures, Psycopg2DatabaseFeatures):
    supports_3d_storage = True
    supports_3d_functions = True
    supports_left_right_lookups = True
    supports_raster = True
+20 −0
Original line number Diff line number Diff line
@@ -20,6 +20,26 @@ class PostGISIntrospection(DatabaseIntrospection):
        'raster_overviews',
    ]

    # Overridden from parent to include raster indices in retrieval.
    # Raster indices have pg_index.indkey value 0 because they are an
    # expression over the raster column through the ST_ConvexHull function.
    # So the default query has to be adapted to include raster indices.
    _get_indexes_query = """
        SELECT DISTINCT attr.attname, idx.indkey, idx.indisunique, idx.indisprimary
        FROM pg_catalog.pg_class c, pg_catalog.pg_class c2,
            pg_catalog.pg_index idx, pg_catalog.pg_attribute attr
        LEFT JOIN pg_catalog.pg_type t ON t.oid = attr.atttypid
        WHERE
            c.oid = idx.indrelid
            AND idx.indexrelid = c2.oid
            AND attr.attrelid = c.oid
            AND (
                attr.attnum = idx.indkey[0] OR
                (t.typname LIKE 'raster' AND idx.indkey = '0')
            )
            AND attr.attnum > 0
            AND c.relname = %s"""

    def get_postgis_types(self):
        """
        Returns a dictionary with keys that are the PostgreSQL object
+29 −7
Original line number Diff line number Diff line
@@ -4,6 +4,9 @@ from django.conf import settings
from django.contrib.gis.db.backends.base.operations import \
    BaseSpatialOperations
from django.contrib.gis.db.backends.postgis.adapter import PostGISAdapter
from django.contrib.gis.db.backends.postgis.pgraster import (
    from_pgraster, to_pgraster,
)
from django.contrib.gis.db.backends.utils import SpatialOperator
from django.contrib.gis.geometry.backend import Geometry
from django.contrib.gis.measure import Distance
@@ -14,6 +17,7 @@ from django.db.utils import ProgrammingError
from django.utils.functional import cached_property

from .models import PostGISGeometryColumns, PostGISSpatialRefSys
from .pgraster import get_pgraster_srid


class PostGISOperator(SpatialOperator):
@@ -205,12 +209,11 @@ class PostGISOperations(BaseSpatialOperations, DatabaseOperations):

    def geo_db_type(self, f):
        """
        Return the database field type for the given geometry field.
        Typically this is `None` because geometry columns are added via
        the `AddGeometryColumn` stored procedure, unless the field
        has been specified to be of geography type instead.
        Return the database field type for the given spatial field.
        """
        if f.geography:
        if f.geom_type == 'RASTER':
            return 'raster'
        elif f.geography:
            if f.srid != 4326:
                raise NotImplementedError('PostGIS only supports geography columns with an SRID of 4326.')

@@ -272,10 +275,21 @@ class PostGISOperations(BaseSpatialOperations, DatabaseOperations):
        SRID of the field.  Specifically, this routine will substitute in the
        ST_Transform() function call.
        """
        if value is None or value.srid == f.srid:
        # Get the srid for this object
        if value is None:
            value_srid = None
        elif f.geom_type == 'RASTER':
            value_srid = get_pgraster_srid(value)
        else:
            value_srid = value.srid

        # Adding Transform() to the SQL placeholder if the value srid
        # is not equal to the field srid.
        if value_srid is None or value_srid == f.srid:
            placeholder = '%s'
        elif f.geom_type == 'RASTER':
            placeholder = '%s((%%s)::raster, %s)' % (self.transform, f.srid)
        else:
            # Adding Transform() to the SQL placeholder.
            placeholder = '%s(%%s, %s)' % (self.transform, f.srid)

        if hasattr(value, 'as_sql'):
@@ -359,3 +373,11 @@ class PostGISOperations(BaseSpatialOperations, DatabaseOperations):

    def spatial_ref_sys(self):
        return PostGISSpatialRefSys

    # Methods to convert between PostGIS rasters and dicts that are
    # readable by GDALRaster.
    def parse_raster(self, value):
        return from_pgraster(value)

    def deconstruct_raster(self, value):
        return to_pgraster(value)
Loading