Fixed #28288 -- Allowed passing papsz options to GDALRaster initialization.

This commit is contained in:
Daniel Wiesmann 2017-06-08 15:51:19 +01:00 committed by Tim Graham
parent 0c3c37a376
commit fe5e34a295
4 changed files with 104 additions and 17 deletions

View File

@ -1,6 +1,6 @@
import json import json
import os 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.driver import Driver
from django.contrib.gis.gdal.error import GDALException from django.contrib.gis.gdal.error import GDALException
@ -92,6 +92,16 @@ class GDALRaster(GDALRasterBase):
if 'srid' not in ds_input: if 'srid' not in ds_input:
raise GDALException('Specify srid for JSON or dict 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 = '{}={}'.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 # Create GDAL Raster
self._ptr = capi.create_ds( self._ptr = capi.create_ds(
driver._ptr, driver._ptr,
@ -100,7 +110,7 @@ class GDALRaster(GDALRasterBase):
ds_input['height'], ds_input['height'],
ds_input.get('nr_of_bands', len(ds_input.get('bands', []))), ds_input.get('nr_of_bands', len(ds_input.get('bands', []))),
ds_input.get('datatype', 6), ds_input.get('datatype', 6),
None byref(papsz_options),
) )
# Set band data if provided # Set band data if provided

View File

@ -1619,9 +1619,9 @@ the others are described below.
The following table describes all keys that can be set in the ``ds_input`` The following table describes all keys that can be set in the ``ds_input``
dictionary. dictionary.
=============== ======== ================================================== ================= ======== ==================================================
Key Default Usage Key Default Usage
=============== ======== ================================================== ================= ======== ==================================================
``srid`` required Mapped to the :attr:`~GDALRaster.srid` attribute ``srid`` required Mapped to the :attr:`~GDALRaster.srid` attribute
``width`` required Mapped to the :attr:`~GDALRaster.width` attribute ``width`` required Mapped to the :attr:`~GDALRaster.width` attribute
``height`` required Mapped to the :attr:`~GDALRaster.height` attribute ``height`` required Mapped to the :attr:`~GDALRaster.height` attribute
@ -1633,7 +1633,8 @@ Key Default Usage
``bands`` ``[]`` See below ``bands`` ``[]`` See below
``nr_of_bands`` ``0`` See below ``nr_of_bands`` ``0`` See below
``datatype`` ``6`` See below ``datatype`` ``6`` See below
=============== ======== ================================================== ``papsz_options`` ``{}`` See below
================= ======== ==================================================
.. object:: name .. object:: name
@ -1673,6 +1674,41 @@ Key Default Usage
raster bands values are instantiated as an array of zeros and the "no raster bands values are instantiated as an array of zeros and the "no
data" value is set to ``None``. data" value is set to ``None``.
.. object:: papsz_options
.. versionadded:: 2.0
A dictionary with raster creation options. The key-value pairs of the
input dictionary are passed to the driver on creation of the raster.
The available options are driver-specific and are described in the
documentation of each driver.
The values in the dictionary are not case-sensitive and are automatically
converted to the correct string format upon creation.
The following example uses some of the options available for the
`GTiff driver`__. The result is a compressed signed byte raster with an
internal tiling scheme. The internal tiles have a block size of 23 by 23::
>>> GDALRaster({
... 'driver': 'GTiff',
... 'name': '/path/to/new/file.tif',
... 'srid': 4326,
... 'width': 255,
... 'height': 255,
... 'nr_of_bands': 1,
... 'papsz_options': {
... 'compress': 'packbits',
... 'pixeltype': 'signedbyte',
... 'tiled': 'yes',
... 'blockxsize': 23,
... 'blockysize': 23,
... }
... })
__ http://www.gdal.org/frmt_gtiff.html
The ``band_input`` dictionary The ``band_input`` dictionary
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -83,6 +83,9 @@ Minor features
:attr:`~django.contrib.gis.gdal.GDALRaster.info`, and :attr:`~django.contrib.gis.gdal.GDALRaster.info`, and
:attr:`~django.contrib.gis.gdal.GDALBand.metadata` attributes. :attr:`~django.contrib.gis.gdal.GDALBand.metadata` attributes.
* Allowed passing driver-specific creation options to
:class:`~django.contrib.gis.gdal.GDALRaster` objects using ``papsz_options``.
:mod:`django.contrib.messages` :mod:`django.contrib.messages`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -310,6 +310,44 @@ class GDALRasterTests(SimpleTestCase):
info_ref = [line.strip() for line in gdalinfo.split('\n') if line.strip() != ''] info_ref = [line.strip() for line in gdalinfo.split('\n') if line.strip() != '']
self.assertEqual(info_dyn, info_ref) self.assertEqual(info_dyn, info_ref)
def test_compressed_file_based_raster_creation(self):
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 physically if compression worked.
self.assertLess(os.path.getsize(compressed.name), os.path.getsize(self.rs.name))
if GDAL_VERSION > (1, 11):
# 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 are stored in metadata.
# Reopening the raster ensures that all metadata has been written
# to the file.
compressed = GDALRaster(compressed.name)
self.assertEqual(compressed.metadata['IMAGE_STRUCTURE']['COMPRESSION'], 'PACKBITS',)
self.assertEqual(compressed.bands[0].metadata['IMAGE_STRUCTURE']['PIXELTYPE'], 'SIGNEDBYTE')
if GDAL_VERSION >= (2, 1):
self.assertIn('Block=40x23', compressed.info)
def test_raster_warp(self): def test_raster_warp(self):
# Create in memory raster # Create in memory raster
source = GDALRaster({ source = GDALRaster({