Commit c0780215 authored by Daniel Wiesmann's avatar Daniel Wiesmann Committed by Claude Paroz
Browse files

Refs #24840 -- Added GDALRaster Warp and transform methods

Thanks to Tim Graham for the review.
parent c0fff644
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -69,3 +69,6 @@ get_band_maximum = double_output(std_call('GDALGetRasterMaximum'), [c_void_p, PO
reproject_image = void_output(std_call('GDALReprojectImage'),
    [c_void_p, c_char_p, c_void_p, c_char_p, c_int, c_double, c_double, c_void_p, c_void_p, c_void_p]
)
auto_create_warped_vrt = voidptr_output(std_call('GDALAutoCreateWarpedVRT'),
    [c_void_p, c_char_p, c_char_p, c_int, c_double, c_void_p]
)
+11 −0
Original line number Diff line number Diff line
@@ -32,3 +32,14 @@ GDAL_TO_CTYPES = [
    None, c_byte, c_uint16, c_int16, c_uint32, c_int32,
    c_float, c_double, None, None, None, None
]

# List of resampling algorithms that can be used to warp a GDALRaster.
GDAL_RESAMPLE_ALGORITHMS = {
    'NearestNeighbour': 0,
    'Bilinear': 1,
    'Cubic': 2,
    'CubicSpline': 3,
    'Lanczos': 4,
    'Average': 5,
    'Mode': 6,
}
+115 −1
Original line number Diff line number Diff line
import json
import os
from ctypes import addressof, byref, c_double
from ctypes import addressof, byref, c_double, c_void_p

from django.contrib.gis.gdal.base import GDALBase
from django.contrib.gis.gdal.driver import Driver
from django.contrib.gis.gdal.error import GDALException
from django.contrib.gis.gdal.prototypes import raster as capi
from django.contrib.gis.gdal.raster.band import GDALBand
from django.contrib.gis.gdal.raster.const import GDAL_RESAMPLE_ALGORITHMS
from django.contrib.gis.gdal.srs import SpatialReference, SRSException
from django.contrib.gis.geometry.regex import json_regex
from django.utils import six
@@ -123,6 +124,9 @@ class GDALRaster(GDALBase):

            if 'skew' in ds_input:
                self.skew.x, self.skew.y = ds_input['skew']
        elif isinstance(ds_input, c_void_p):
            # Instantiate the object using an existing pointer to a gdal raster.
            self._ptr = ds_input
        else:
            raise GDALException('Invalid data source input type: "{}".'.format(type(ds_input)))

@@ -278,3 +282,113 @@ class GDALRaster(GDALBase):
        for idx in range(1, capi.get_ds_raster_count(self._ptr) + 1):
            bands.append(GDALBand(self, idx))
        return bands

    def warp(self, ds_input, resampling='NearestNeighbour', max_error=0.0):
        """
        Returns a warped GDALRaster with the given input characteristics.

        The input is expected to be a dictionary containing the parameters
        of the target raster. Allowed values are width, height, SRID, origin,
        scale, skew, datatype, driver, and name (filename).

        By default, the warp functions keeps all parameters equal to the values
        of the original source raster. For the name of the target raster, the
        name of the source raster will be used and appended with
        _copy. + source_driver_name.

        In addition, the resampling algorithm can be specified with the "resampling"
        input parameter. The default is NearestNeighbor. For a list of all options
        consult the GDAL_RESAMPLE_ALGORITHMS constant.
        """
        # Get the parameters defining the geotransform, srid, and size of the raster
        if 'width' not in ds_input:
            ds_input['width'] = self.width

        if 'height' not in ds_input:
            ds_input['height'] = self.height

        if 'srid' not in ds_input:
            ds_input['srid'] = self.srs.srid

        if 'origin' not in ds_input:
            ds_input['origin'] = self.origin

        if 'scale' not in ds_input:
            ds_input['scale'] = self.scale

        if 'skew' not in ds_input:
            ds_input['skew'] = self.skew

        # Get the driver, name, and datatype of the target raster
        if 'driver' not in ds_input:
            ds_input['driver'] = self.driver.name

        if 'name' not in ds_input:
            ds_input['name'] = self.name + '_copy.' + self.driver.name

        if 'datatype' not in ds_input:
            ds_input['datatype'] = self.bands[0].datatype()

        # Set the number of bands
        ds_input['nr_of_bands'] = len(self.bands)

        # Create target raster
        target = GDALRaster(ds_input, write=True)

        # Copy nodata values to warped raster
        for index, band in enumerate(self.bands):
            target.bands[index].nodata_value = band.nodata_value

        # Select resampling algorithm
        algorithm = GDAL_RESAMPLE_ALGORITHMS[resampling]

        # Reproject image
        capi.reproject_image(
            self._ptr, self.srs.wkt.encode(),
            target._ptr, target.srs.wkt.encode(),
            algorithm, 0.0, max_error,
            c_void_p(), c_void_p(), c_void_p()
        )

        # Make sure all data is written to file
        target._flush()

        return target

    def transform(self, srid, driver=None, name=None, resampling='NearestNeighbour',
                  max_error=0.0):
        """
        Returns a copy of this raster reprojected into the given SRID.
        """
        # Convert the resampling algorithm name into an algorithm id
        algorithm = GDAL_RESAMPLE_ALGORITHMS[resampling]

        # Instantiate target spatial reference system
        target_srs = SpatialReference(srid)

        # Create warped virtual dataset in the target reference system
        target = capi.auto_create_warped_vrt(
            self._ptr, self.srs.wkt.encode(), target_srs.wkt.encode(),
            algorithm, max_error, c_void_p()
        )
        target = GDALRaster(target)

        # Construct the target warp dictionary from the virtual raster
        data = {
            'srid': srid,
            'width': target.width,
            'height': target.height,
            'origin': [target.origin.x, target.origin.y],
            'scale': [target.scale.x, target.scale.y],
            'skew': [target.skew.x, target.skew.y],
        }

        # Set the driver and filepath if provided
        if driver:
            data['driver'] = driver

        if name:
            data['name'] = name

        # Warp the raster into new srid
        return self.warp(data, resampling=resampling, max_error=max_error)
+84 −2
Original line number Diff line number Diff line
@@ -1119,7 +1119,7 @@ blue.
    values defining the properties of a new raster (such as size and name). If the
    input is a file path, the second parameter specifies if the raster should
    be opened with write access. If the input is raw data, the parameters ``width``,
    ``heigth``, and ``srid`` are required. The following example shows how rasters
    ``height``, and ``srid`` are required. The following example shows how rasters
    can be created from different input sources (using the sample data from the
    GeoDjango tests, see also the :ref:`gdal_sample_data` section)::

@@ -1288,6 +1288,89 @@ blue.
            >>> rst.bands[1].data()
            array([[ 2.,  3.]], dtype=float32)

    .. method:: warp(ds_input, resampling='NearestNeighbour', max_error=0.0)

        .. versionadded:: 1.9

        Returns a warped version of this raster.

        The warping parameters can be specified through the ``ds_input``
        argument. The use of ``ds_input`` is analogous to the corresponding
        argument of the class constructor. It is a dictionary with the
        characteristics of the target raster. Allowed dictionary key values are
        width, height, SRID, origin, scale, skew, datatype, driver, and name
        (filename).

        By default, the warp functions keeps most parameters equal to the
        values of the original source raster, so only parameters that should be
        changed need to be specified. Note that this includes the driver, so
        for file-based rasters the warp function will create a new raster on
        disk.

        The only parameter that is set differently from the source raster is the
        name. The default value of the the raster name is the name of the source
        raster appended with ``'_copy' + source_driver_name``. For file-based
        rasters it is recommended to provide the file path of the target raster.

        The resampling algorithm used for warping can be specified with the
        ``resampling`` argument. The default is ``NearestNeighbor``, and the
        other allowed values are ``Bilinear``, ``Cubic``, ``CubicSpline``,
        ``Lanczos``, ``Average``, and ``Mode``.

        The ``max_error`` argument can be used to specify the maximum error
        measured in input pixels that is allowed in approximating the
        transformation. The default is 0.0 for exact calculations.

        For users familiar with ``GDAL``, this function has a similar
        functionality to the ``gdalwarp`` command-line utility.

        For example, the warp function can be used for aggregating a raster to
        the double of its original pixel scale:

            >>> rst = GDALRaster({
            ...     "width": 6, "height": 6, "srid": 3086,
            ...     "origin": [500000, 400000],
            ...     "scale": [100, -100],
            ...     "bands": [{"data": range(36), "nodata_value": 99}]
            ... })
            >>> target = rst.warp({"scale": [200, -200], "width": 3, "height": 3})
            >>> target.bands[0].data()
            array([[  7.,   9.,  11.],
                   [ 19.,  21.,  23.],
                   [ 31.,  33.,  35.]], dtype=float32)

    .. method:: transform(srid, driver=None, name=None, resampling='NearestNeighbour', max_error=0.0)

        .. versionadded:: 1.9

        Returns a transformed version of this raster with the specified SRID.

        This function transforms the current raster into a new spatial reference
        system that can be specified with an ``srid``. It calculates the bounds
        and scale of the current raster in the new spatial reference system and
        warps the raster using the :attr:`~GDALRaster.warp` function.

        By default, the driver of the source raster is used and the name of the
        raster is the original name appended with
        ``'_copy' + source_driver_name``. A different driver or name can be
        specified with the ``driver`` and ``name`` arguments.

        The default resampling algorithm is ``NearestNeighbour`` but can be
        changed using the ``resampling`` argument. The default maximum allowed
        error for resampling is 0.0 and can be changed using the ``max_error``
        argument. Consult the :attr:`~GDALRaster.warp` documentation for detail
        on those arguments.

            >>> rst = GDALRaster({
            ...     "width": 6, "height": 6, "srid": 3086,
            ...     "origin": [500000, 400000],
            ...     "scale": [100, -100],
            ...     "bands": [{"data": range(36), "nodata_value": 99}]
            ... })
            >>> target = rst.transform(4326)
            >>> target.origin
            [-82.98492744885776, 27.601924753080144]

``GDALBand``
------------

@@ -1385,7 +1468,6 @@ blue.
                   [  8, -77, -66,  11],
                   [ 12,  13,  14,  15]], dtype=int8)


Settings
========

+9 −0
Original line number Diff line number Diff line
@@ -177,6 +177,15 @@ Minor features
  It supports automatic spatial index creation and reprojection when saving a
  model. It does not yet support spatial querying.

* The new :meth:`GDALRaster.warp() <django.contrib.gis.gdal.GDALRaster.warp>`
  method allows warping a raster by specifying target raster properties such as
  origin, width, height, or pixel size (amongst others).

* The new :meth:`GDALRaster.transform()
  <django.contrib.gis.gdal.GDALRaster.transform>` method allows transforming a
  raster into a different spatial reference system by specifying a target
  ``srid``.

:mod:`django.contrib.messages`
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Loading