Skip to content

Commit

Permalink
Fixed #28288 -- Allowed passing papsz options to GDALRaster initiation.
Browse files Browse the repository at this point in the history
  • Loading branch information
yellowcap committed Jun 8, 2017
1 parent c16aa69 commit 66ac711
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 17 deletions.
15 changes: 13 additions & 2 deletions 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
Expand Down Expand Up @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
59 changes: 44 additions & 15 deletions docs/ref/contrib/gis/gdal.txt
Expand Up @@ -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

Expand Down Expand Up @@ -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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down
3 changes: 3 additions & 0 deletions docs/releases/2.0.txt
Expand Up @@ -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`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down
42 changes: 42 additions & 0 deletions tests/gis_tests/gdal_tests/test_raster.py
Expand Up @@ -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({
Expand Down

0 comments on commit 66ac711

Please sign in to comment.