浏览代码

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

Daniel Wiesmann 7 年之前
父节点
当前提交
fe5e34a295

+ 12 - 2
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 @@ class GDALRaster(GDALRasterBase):
             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 = '{}={}'.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 @@ class GDALRaster(GDALRasterBase):
                 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

+ 51 - 15
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,41 @@ 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
+    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
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

+ 3 - 0
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 using ``papsz_options``.
+
 :mod:`django.contrib.messages`
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

+ 38 - 0
tests/gis_tests/gdal_tests/test_raster.py

@@ -310,6 +310,44 @@ class GDALRasterTests(SimpleTestCase):
         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):
+        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):
         # Create in memory raster
         source = GDALRaster({