From 870dd1d38b8f9238ddc917bbb55cee54ce37a0b2 Mon Sep 17 00:00:00 2001 From: Daniel Wiesmann Date: Tue, 29 Mar 2016 14:42:35 +0100 Subject: [PATCH] Fixed #26417 -- Allowed setting GDALBand data with partial values. --- django/contrib/gis/gdal/raster/band.py | 11 +++++--- docs/ref/contrib/gis/gdal.txt | 32 ++++++++++++++++++++--- docs/releases/1.10.txt | 4 +++ tests/gis_tests/gdal_tests/test_raster.py | 21 +++++++++++++++ 4 files changed, 60 insertions(+), 8 deletions(-) diff --git a/django/contrib/gis/gdal/raster/band.py b/django/contrib/gis/gdal/raster/band.py index 6ffa011282..716aff0a53 100644 --- a/django/contrib/gis/gdal/raster/band.py +++ b/django/contrib/gis/gdal/raster/band.py @@ -170,7 +170,7 @@ class GDALBand(GDALBase): dtype = GDAL_PIXEL_TYPES[dtype] return dtype - def data(self, data=None, offset=None, size=None, as_memoryview=False): + def data(self, data=None, offset=None, size=None, shape=None, as_memoryview=False): """ Reads or writes pixel values for this band. Blocks of data can be accessed by specifying the width, height and offset of the @@ -185,6 +185,9 @@ class GDALBand(GDALBase): if not size: size = (self.width - offset[0], self.height - offset[1]) + if not shape: + shape = size + if any(x <= 0 for x in size): raise ValueError('Offset too big for this raster.') @@ -192,7 +195,7 @@ class GDALBand(GDALBase): raise ValueError('Size is larger than raster.') # Create ctypes type array generator - ctypes_array = GDAL_TO_CTYPES[self.datatype()] * (size[0] * size[1]) + ctypes_array = GDAL_TO_CTYPES[self.datatype()] * (shape[0] * shape[1]) if data is None: # Set read mode @@ -211,8 +214,8 @@ class GDALBand(GDALBase): # Access band capi.band_io(self._ptr, access_flag, offset[0], offset[1], - size[0], size[1], byref(data_array), size[0], - size[1], self.datatype(), 0, 0) + size[0], size[1], byref(data_array), shape[0], + shape[1], self.datatype(), 0, 0) # Return data as numpy array if possible, otherwise as list if data is None: diff --git a/docs/ref/contrib/gis/gdal.txt b/docs/ref/contrib/gis/gdal.txt index ed036de12a..82256539a0 100644 --- a/docs/ref/contrib/gis/gdal.txt +++ b/docs/ref/contrib/gis/gdal.txt @@ -1477,7 +1477,7 @@ blue. ``GDT_UInt32``, ``GDT_Int32``, ``GDT_Float32``, ``GDT_Float64``, ``GDT_CInt16``, ``GDT_CInt32``, ``GDT_CFloat32``, and ``GDT_CFloat64``. - .. method:: data(data=None, offset=None, size=None) + .. method:: data(data=None, offset=None, size=None, shape=None) .. versionadded:: 1.9 @@ -1490,9 +1490,17 @@ blue. Data is written to the ``GDALBand`` if the ``data`` parameter is provided. The input can be of one of the following types - packed string, buffer, list, - array, and NumPy array. The number of items in the input must correspond to the - total number of pixels in the band, or to the number of pixels for a specific - block of pixel values if the ``offset`` and ``size`` parameters are provided. + array, and NumPy array. The number of items in the input should normally + correspond to the total number of pixels in the band, or to the number + of pixels for a specific block of pixel values if the ``offset`` and + ``size`` parameters are provided. + + If the number of items in the input is different from the target pixel + block, the ``shape`` parameter must be specified. The shape is a tuple + that specifies the width and height of the input data in pixels. The + data is then replicated to update the pixel values of the selected + block. This is useful to fill an entire band with a single value, for + instance. For example: @@ -1519,6 +1527,22 @@ blue. [ 4, -99, -88, 7], [ 8, -77, -66, 11], [ 12, 13, 14, 15]], dtype=int8) + >>> bnd.data([1], shape=(1, 1)) + >>> bnd.data() + array([[1, 1, 1, 1], + [1, 1, 1, 1], + [1, 1, 1, 1], + [1, 1, 1, 1]], dtype=uint8) + >>> bnd.data(range(4), shape=(1, 4)) + array([[0, 0, 0, 0], + [1, 1, 1, 1], + [2, 2, 2, 2], + [3, 3, 3, 3]], dtype=uint8) + + .. versionchanged:: 1.10 + + The ``shape`` parameter and the ability to replicate data input when + setting ``GDALBand`` data was added. Settings ======== diff --git a/docs/releases/1.10.txt b/docs/releases/1.10.txt index 1d253bfe62..29c9327f99 100644 --- a/docs/releases/1.10.txt +++ b/docs/releases/1.10.txt @@ -143,6 +143,10 @@ Minor features primary key of objects in the ``properties`` dictionary if specific fields aren't specified. +* The ability to replicate input data on the :meth:`GDALBand.data() + ` method was added. Band data can + now be updated with repeated values efficiently. + :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 d08071342c..18453e6eb4 100644 --- a/tests/gis_tests/gdal_tests/test_raster.py +++ b/tests/gis_tests/gdal_tests/test_raster.py @@ -483,3 +483,24 @@ class GDALBandTests(SimpleTestCase): else: rsmem.bands[0].nodata_value = None self.assertIsNone(rsmem.bands[0].nodata_value) + + def test_band_data_replication(self): + band = GDALRaster({ + 'srid': 4326, + 'width': 3, + 'height': 3, + 'bands': [{'data': range(10, 19), 'nodata_value': 0}], + }).bands[0] + + # Variations for input (data, shape, expected result). + combos = ( + ([1], (1, 1), [1] * 9), + (range(3), (1, 3), [0, 0, 0, 1, 1, 1, 2, 2, 2]), + (range(3), (3, 1), [0, 1, 2, 0, 1, 2, 0, 1, 2]), + ) + for combo in combos: + band.data(combo[0], shape=combo[1]) + if numpy: + numpy.testing.assert_equal(band.data(), numpy.array(combo[2]).reshape(3, 3)) + else: + self.assertEqual(band.data(), list(combo[2]))