pack data into hex string with little endian format unpack little endian hexlified binary string into a list split a string into two parts at the input index convert a postgis hex string into a dictionary split raster header from data parse band data get pixel type for this band convert datatype from postgis to gdal get pack type and size parse band nodata value the nodata value is part of the pgraster string even if the nodata flag is true so it always has to be chunked off the data string chunk and unpack band data pack size times nr of pixels set the nodata value if the nodata flag is set append band data to band list store pixeltype of this band in pixeltypes array check that all bands have the same pixeltype this is required by gdal postgis rasters could have different pixeltypes for bands of the same raster convert a gdalraster into postgis raster format prepare the raster header data as a tuple the first two numbers are the endianness and the postgis raster version both are fixed by postgis at the moment pack raster header the postgis raster band header has exactly two elements a 8bui byte and the nodata value the 8bui stores both the postgis pixel data type and a nodata flag it is composed as the datatype with bandtypeflaghasnodata 1 6 for existing nodata values 8buivalue pgpixeltype 011 bandtypeflaghasnodata for example if the byte value is 71 then the datatype is 71 bandtypeflaghasnodata 7 32bsi and the nodata value is true get band pixel type in postgis notation set the nodata flag pack band header add packed header and band data to result pack data into hex string with little endian format unpack little endian hexlified binary string into a list split a string into two parts at the input index convert a postgis hex string into a dictionary split raster header from data parse band data get pixel type for this band convert datatype from postgis to gdal get pack type and size parse band nodata value the nodata value is part of the pgraster string even if the nodata flag is true so it always has to be chunked off the data string chunk and unpack band data pack size times nr of pixels set the nodata value if the nodata flag is set append band data to band list store pixeltype of this band in pixeltypes array check that all bands have the same pixeltype this is required by gdal postgis rasters could have different pixeltypes for bands of the same raster convert a gdalraster into postgis raster format prepare the raster header data as a tuple the first two numbers are the endianness and the postgis raster version both are fixed by postgis at the moment pack raster header the postgis raster band header has exactly two elements a 8bui byte and the nodata value the 8bui stores both the postgis pixel data type and a nodata flag it is composed as the datatype with bandtype_flag_hasnodata 1 6 for existing nodata values 8bui_value pg_pixel_type 0 11 bandtype_flag_hasnodata for example if the byte value is 71 then the datatype is 71 bandtype_flag_hasnodata 7 32bsi and the nodata value is true get band pixel type in postgis notation set the nodata flag pack band header add packed header and band data to result
import struct

from django.core.exceptions import ValidationError

from .const import (
    BANDTYPE_FLAG_HASNODATA,
    BANDTYPE_PIXTYPE_MASK,
    GDAL_TO_POSTGIS,
    GDAL_TO_STRUCT,
    POSTGIS_HEADER_STRUCTURE,
    POSTGIS_TO_GDAL,
    STRUCT_SIZE,
)


def pack(structure, data):
    
    return struct.pack("<" + structure, *data)


def unpack(structure, data):
    
    return struct.unpack("<" + structure, bytes.fromhex(data))


def chunk(data, index):
    
    return data[:index], data[index:]


def from_pgraster(data):
    
    if data is None:
        return

    
    header, data = chunk(data, 122)
    header = unpack(POSTGIS_HEADER_STRUCTURE, header)

    
    bands = []
    pixeltypes = []
    while data:
        
        pixeltype_with_flags, data = chunk(data, 2)
        pixeltype_with_flags = unpack("B", pixeltype_with_flags)[0]
        pixeltype = pixeltype_with_flags & BANDTYPE_PIXTYPE_MASK

        
        pixeltype = POSTGIS_TO_GDAL[pixeltype]
        pack_type = GDAL_TO_STRUCT[pixeltype]
        pack_size = 2 * STRUCT_SIZE[pack_type]

        
        
        
        nodata, data = chunk(data, pack_size)
        nodata = unpack(pack_type, nodata)[0]

        
        band, data = chunk(data, pack_size * header[10] * header[11])
        band_result = {"data": bytes.fromhex(band)}

        
        if pixeltype_with_flags & BANDTYPE_FLAG_HASNODATA:
            band_result["nodata_value"] = nodata

        
        bands.append(band_result)

        
        pixeltypes.append(pixeltype)

    
    
    
    if len(set(pixeltypes)) != 1:
        raise ValidationError("Band pixeltypes are not all equal.")

    return {
        "srid": int(header[9]),
        "width": header[10],
        "height": header[11],
        "datatype": pixeltypes[0],
        "origin": (header[5], header[6]),
        "scale": (header[3], header[4]),
        "skew": (header[7], header[8]),
        "bands": bands,
    }


def to_pgraster(rast):
    
    
    
    
    rasterheader = (
        1,
        0,
        len(rast.bands),
        rast.scale.x,
        rast.scale.y,
        rast.origin.x,
        rast.origin.y,
        rast.skew.x,
        rast.skew.y,
        rast.srs.srid,
        rast.width,
        rast.height,
    )

    
    result = pack(POSTGIS_HEADER_STRUCTURE, rasterheader)

    for band in rast.bands:
        
        
        
        
        
        
        
        
        
        
        
        structure = "B" + GDAL_TO_STRUCT[band.datatype()]

        
        pixeltype = GDAL_TO_POSTGIS[band.datatype()]

        
        if band.nodata_value is not None:
            pixeltype |= BANDTYPE_FLAG_HASNODATA

        
        bandheader = pack(structure, (pixeltype, band.nodata_value or 0))

        
        result += bandheader + band.data(as_memoryview=True)

    return result