diff --git a/django/contrib/gis/gdal/raster/source.py b/django/contrib/gis/gdal/raster/source.py index 609c9d6b46690..c3b1056ad5805 100644 --- a/django/contrib/gis/gdal/raster/source.py +++ b/django/contrib/gis/gdal/raster/source.py @@ -1,6 +1,6 @@ import json import os -from ctypes import addressof, byref, c_double, c_void_p +from ctypes import addressof, byref, c_char_p, c_double, c_void_p from django.contrib.gis.gdal.driver import Driver from django.contrib.gis.gdal.error import GDALException @@ -92,6 +92,16 @@ def __init__(self, ds_input, write=False): if 'srid' not in ds_input: raise GDALException('Specify srid for JSON or dict input.') + # Create null terminated gdal options array. + papsz_options = [] + for key, val in ds_input.get('papsz_options', {}).items(): + option = '{0}={1}'.format(key, val) + papsz_options.append(option.upper().encode()) + papsz_options.append(None) + + # Convert papszlist to ctypes array. + papsz_options = (c_char_p * len(papsz_options))(*papsz_options) + # Create GDAL Raster self._ptr = capi.create_ds( driver._ptr, @@ -100,7 +110,7 @@ def __init__(self, ds_input, write=False): ds_input['height'], ds_input.get('nr_of_bands', len(ds_input.get('bands', []))), ds_input.get('datatype', 6), - None + byref(papsz_options), ) # Set band data if provided @@ -135,6 +145,7 @@ def __init__(self, ds_input, write=False): 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 diff --git a/docs/ref/contrib/gis/gdal.txt b/docs/ref/contrib/gis/gdal.txt index d6501cbf487b3..864ab0fbb8696 100644 --- a/docs/ref/contrib/gis/gdal.txt +++ b/docs/ref/contrib/gis/gdal.txt @@ -1619,21 +1619,22 @@ the others are described below. The following table describes all keys that can be set in the ``ds_input`` dictionary. -=============== ======== ================================================== -Key Default Usage -=============== ======== ================================================== -``srid`` required Mapped to the :attr:`~GDALRaster.srid` attribute -``width`` required Mapped to the :attr:`~GDALRaster.width` attribute -``height`` required Mapped to the :attr:`~GDALRaster.height` attribute -``driver`` ``MEM`` Mapped to the :attr:`~GDALRaster.driver` attribute -``name`` ``''`` See below -``origin`` ``0`` Mapped to the :attr:`~GDALRaster.origin` attribute -``scale`` ``0`` Mapped to the :attr:`~GDALRaster.scale` attribute -``skew`` ``0`` Mapped to the :attr:`~GDALRaster.width` attribute -``bands`` ``[]`` See below -``nr_of_bands`` ``0`` See below -``datatype`` ``6`` See below -=============== ======== ================================================== +================= ======== ================================================== +Key Default Usage +================= ======== ================================================== +``srid`` required Mapped to the :attr:`~GDALRaster.srid` attribute +``width`` required Mapped to the :attr:`~GDALRaster.width` attribute +``height`` required Mapped to the :attr:`~GDALRaster.height` attribute +``driver`` ``MEM`` Mapped to the :attr:`~GDALRaster.driver` attribute +``name`` ``''`` See below +``origin`` ``0`` Mapped to the :attr:`~GDALRaster.origin` attribute +``scale`` ``0`` Mapped to the :attr:`~GDALRaster.scale` attribute +``skew`` ``0`` Mapped to the :attr:`~GDALRaster.width` attribute +``bands`` ``[]`` See below +``nr_of_bands`` ``0`` See below +``datatype`` ``6`` See below +``papsz_options`` ``{}`` See below +================= ======== ================================================== .. object:: name @@ -1673,6 +1674,34 @@ Key Default Usage raster bands values are instantiated as an array of zeros and the "no data" value is set to ``None``. +.. object:: papsz_options + + .. versionadded:: 2.0 + + A dictionary with raster creation options. The key-value pairs of the + dictionary are passed as options to the driver on creation of the raster. + The values in the options dictionary are not case sensitive, they are + automatically converted into the right string format upon creation. + + GDAL allows each driver to have its own specific creation options. These + options are known as "papsz options" and are passed to GDAL as an array + of ``name=value`` strings. The documentation of each driver specifies what + options it accepts. + + The following example is an options set for the `GTiff driver`__. With + these options, a compressed raster with a signed byte pixel type and a + tile block size of 23 by 23 would be created:: + + papsz_options = { + 'compress': 'packbits', + 'pixeltype': 'signedbyte', + 'blockxsize': 23, + 'blockysize': 23, + } + + +__ http://www.gdal.org/frmt_gtiff.html + The ``band_input`` dictionary ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/releases/2.0.txt b/docs/releases/2.0.txt index 3b7a5a74f1501..8082c1c7924bb 100644 --- a/docs/releases/2.0.txt +++ b/docs/releases/2.0.txt @@ -83,6 +83,9 @@ Minor features :attr:`~django.contrib.gis.gdal.GDALRaster.info`, and :attr:`~django.contrib.gis.gdal.GDALBand.metadata` attributes. +* Allowed passing driver specific creation options to + :class:`~django.contrib.gis.gdal.GDALRaster` objects. + :mod:`django.contrib.messages` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/gis_tests/gdal_tests/test_raster.py b/tests/gis_tests/gdal_tests/test_raster.py index f82b37a6a96dc..4132e9a41cbd5 100644 --- a/tests/gis_tests/gdal_tests/test_raster.py +++ b/tests/gis_tests/gdal_tests/test_raster.py @@ -310,6 +310,48 @@ def test_raster_info_accessor(self): info_ref = [line.strip() for line in gdalinfo.split('\n') if line.strip() != ''] self.assertEqual(info_dyn, info_ref) + def test_compressed_file_based_raster_creation(self): + # Prepare tempfile + rstfile = tempfile.NamedTemporaryFile(suffix='.tif') + + # Make a compressed copy of an existing raster. + compressed = self.rs.warp({'papsz_options': {'compress': 'packbits'}, 'name': rstfile.name}) + + # Check pysically if compression worked. + self.assertTrue( + os.path.getsize(compressed.name) < os.path.getsize(self.rs.name) + ) + + # Create file-based raster with options from scratch. + compressed = GDALRaster({ + 'datatype': 1, + 'driver': 'tif', + 'name': rstfile.name, + 'width': 40, + 'height': 40, + 'srid': 3086, + 'origin': (500000, 400000), + 'scale': (100, -100), + 'skew': (0, 0), + 'bands': [{ + 'data': range(40 ^ 2), + 'nodata_value': 255, + }], + 'papsz_options': { + 'compress': 'packbits', + 'pixeltype': 'signedbyte', + 'blockxsize': 23, + 'blockysize': 23, + } + }) + + # Check if options used on creation and stored in metadata. Re-opening + # the raster ensures that all metadata has been written to file. + compressed = GDALRaster(compressed.name) + self.assertEqual(compressed.metadata['IMAGE_STRUCTURE']['COMPRESSION'], 'PACKBITS') + self.assertEqual(compressed.bands[0].metadata['IMAGE_STRUCTURE']['PIXELTYPE'], 'SIGNEDBYTE') + self.assertIn('Block=40x23', compressed.info) + def test_raster_warp(self): # Create in memory raster source = GDALRaster({