wrap a gdal raster band needs to be obtained from a gdalraster object call the flush method on the band s parent raster and force a refresh of the statistics attribute when requested the next time return the description string of the band width x axis in pixels of the band height y axis in pixels of the band return the total number of pixels in this band compute statistics on the pixel values of this band the return value is a tuple with the following structure minimum maximum mean standard deviation if approximatetrue the statistics may be computed based on overviews or a subset of image tiles if refreshtrue the statistics will be computed from the data directly and the cache will be updated where applicable for empty bands where all pixel values are nodata all statistics values are returned as none for raster formats using persistent auxiliary metadata pam services the statistics might be cached in an auxiliary file prepare array with arguments for capi function add additional argument to force computation if there is no existing pam file to take the values from computation of statistics fails for empty bands return the minimum pixel value for this band return the maximum pixel value for this band return the mean of all pixel values of this band return the standard deviation of all pixel values of this band return the nodata value for this band or none if it isn t set get value and nodata exists flag if the pixeltype is an integer convert to int set the nodata value for this band return the gdal pixel datatype for this band return the gdal color interpretation for this band color capi getbandcolorinterpself ptr if asstring color gdalcolortypescolor return color def dataself datanone offsetnone sizenone shapenone asmemoryviewfalse offset offset or 0 0 size size or self width offset0 self height offset1 shape shape or size if anyx 0 for x in size raise valueerroroffset too big for this raster if size0 self width or size1 self height raise valueerrorsize is larger than raster create ctypes type array generator ctypesarray gdaltoctypesself datatype shape0 shape1 if data is none set read mode accessflag 0 prepare empty ctypes array dataarray ctypesarray else set write mode accessflag 1 instantiate ctypes array holding the input data if isinstancedata bytes memoryview or numpy and isinstancedata numpy ndarray dataarray ctypesarray frombuffercopydata else dataarray ctypesarraydata access band capi bandio self ptr accessflag offset0 offset1 size0 size1 byrefdataarray shape0 shape1 self datatype 0 0 return data as numpy array if possible otherwise as list if data is none if asmemoryview return memoryviewdataarray elif numpy reshape needs a reshape parameter with the height first return numpy frombuffer dataarray dtypenumpy dtypedataarray reshapetuplereversedsize else return listdataarray else self flush class bandlistlist def initself source self source source super init def iterself for idx in range1 lenself 1 yield gdalbandself source idx def lenself return capi getdsrastercountself source ptr def getitemself index try return gdalbandself source index 1 except gdalexception raise gdalexceptionunable to get band index d index wrap a gdal raster band needs to be obtained from a gdalraster object call the flush method on the band s parent raster and force a refresh of the statistics attribute when requested the next time return the description string of the band width x axis in pixels of the band height y axis in pixels of the band return the total number of pixels in this band compute statistics on the pixel values of this band the return value is a tuple with the following structure minimum maximum mean standard deviation if approximate true the statistics may be computed based on overviews or a subset of image tiles if refresh true the statistics will be computed from the data directly and the cache will be updated where applicable for empty bands where all pixel values are nodata all statistics values are returned as none for raster formats using persistent auxiliary metadata pam services the statistics might be cached in an auxiliary file prepare array with arguments for capi function add additional argument to force computation if there is no existing pam file to take the values from computation of statistics fails for empty bands return the minimum pixel value for this band return the maximum pixel value for this band return the mean of all pixel values of this band return the standard deviation of all pixel values of this band return the nodata value for this band or none if it isn t set get value and nodata exists flag if the pixeltype is an integer convert to int set the nodata value for this band return the gdal pixel datatype for this band return the gdal color interpretation for this band read or writes pixel values for this band blocks of data can be accessed by specifying the width height and offset of the desired block the same specification can be used to update parts of a raster by providing an array of values allowed input data types are bytes memoryview list tuple and array create ctypes type array generator set read mode prepare empty ctypes array set write mode instantiate ctypes array holding the input data access band return data as numpy array if possible otherwise as list reshape needs a reshape parameter with the height first
from ctypes import byref, c_double, c_int, c_void_p

from django.contrib.gis.gdal.error import GDALException
from django.contrib.gis.gdal.prototypes import raster as capi
from django.contrib.gis.gdal.raster.base import GDALRasterBase
from django.contrib.gis.shortcuts import numpy
from django.utils.encoding import force_str

from .const import (
    GDAL_COLOR_TYPES,
    GDAL_INTEGER_TYPES,
    GDAL_PIXEL_TYPES,
    GDAL_TO_CTYPES,
)


class GDALBand(GDALRasterBase):
    

    def __init__(self, source, index):
        self.source = source
        self._ptr = capi.get_ds_raster_band(source._ptr, index)

    def _flush(self):
        
        self.source._flush()
        self._stats_refresh = True

    @property
    def description(self):
        
        return force_str(capi.get_band_description(self._ptr))

    @property
    def width(self):
        
        return capi.get_band_xsize(self._ptr)

    @property
    def height(self):
        
        return capi.get_band_ysize(self._ptr)

    @property
    def pixel_count(self):
        
        return self.width * self.height

    _stats_refresh = False

    def statistics(self, refresh=False, approximate=False):
        
        
        smin, smax, smean, sstd = c_double(), c_double(), c_double(), c_double()
        stats_args = [
            self._ptr,
            c_int(approximate),
            byref(smin),
            byref(smax),
            byref(smean),
            byref(sstd),
            c_void_p(),
            c_void_p(),
        ]

        if refresh or self._stats_refresh:
            func = capi.compute_band_statistics
        else:
            
            
            force = True
            stats_args.insert(2, c_int(force))
            func = capi.get_band_statistics

        
        try:
            func(*stats_args)
            result = smin.value, smax.value, smean.value, sstd.value
        except GDALException:
            result = (None, None, None, None)

        self._stats_refresh = False

        return result

    @property
    def min(self):
        
        return self.statistics()[0]

    @property
    def max(self):
        
        return self.statistics()[1]

    @property
    def mean(self):
        
        return self.statistics()[2]

    @property
    def std(self):
        
        return self.statistics()[3]

    @property
    def nodata_value(self):
        
        
        nodata_exists = c_int()
        value = capi.get_band_nodata_value(self._ptr, nodata_exists)
        if not nodata_exists:
            value = None
        
        elif self.datatype() in GDAL_INTEGER_TYPES:
            value = int(value)
        return value

    @nodata_value.setter
    def nodata_value(self, value):
        
        if value is None:
            capi.delete_band_nodata_value(self._ptr)
        elif not isinstance(value, (int, float)):
            raise ValueError("Nodata value must be numeric or None.")
        else:
            capi.set_band_nodata_value(self._ptr, value)
        self._flush()

    def datatype(self, as_string=False):
        
        dtype = capi.get_band_datatype(self._ptr)
        if as_string:
            dtype = GDAL_PIXEL_TYPES[dtype]
        return dtype

    def color_interp(self, as_string=False):
        
        color = capi.get_band_color_interp(self._ptr)
        if as_string:
            color = GDAL_COLOR_TYPES[color]
        return color

    def data(self, data=None, offset=None, size=None, shape=None, as_memoryview=False):
        
        offset = offset or (0, 0)
        size = size or (self.width - offset[0], self.height - offset[1])
        shape = shape or size
        if any(x <= 0 for x in size):
            raise ValueError("Offset too big for this raster.")

        if size[0] > self.width or size[1] > self.height:
            raise ValueError("Size is larger than raster.")

        
        ctypes_array = GDAL_TO_CTYPES[self.datatype()] * (shape[0] * shape[1])

        if data is None:
            
            access_flag = 0
            
            data_array = ctypes_array()
        else:
            
            access_flag = 1

            
            if isinstance(data, (bytes, memoryview)) or (
                numpy and isinstance(data, numpy.ndarray)
            ):
                data_array = ctypes_array.from_buffer_copy(data)
            else:
                data_array = ctypes_array(*data)

        
        capi.band_io(
            self._ptr,
            access_flag,
            offset[0],
            offset[1],
            size[0],
            size[1],
            byref(data_array),
            shape[0],
            shape[1],
            self.datatype(),
            0,
            0,
        )

        
        if data is None:
            if as_memoryview:
                return memoryview(data_array)
            elif numpy:
                
                return numpy.frombuffer(
                    data_array, dtype=numpy.dtype(data_array)
                ).reshape(tuple(reversed(size)))
            else:
                return list(data_array)
        else:
            self._flush()


class BandList(list):
    def __init__(self, source):
        self.source = source
        super().__init__()

    def __iter__(self):
        for idx in range(1, len(self) + 1):
            yield GDALBand(self.source, idx)

    def __len__(self):
        return capi.get_ds_raster_count(self.source._ptr)

    def __getitem__(self, index):
        try:
            return GDALBand(self.source, index + 1)
        except GDALException:
            raise GDALException("Unable to get band index %d" % index)