-
-
Notifications
You must be signed in to change notification settings - Fork 31k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
376 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
""" | ||
PostGIS to GDAL conversion constant definitions | ||
""" | ||
# Lookup to convert pixel type values from GDAL to PostGIS | ||
GDAL_TO_POSTGIS = [None, 4, 6, 5, 8, 7, 10, 11, None, None, None, None] | ||
|
||
# Lookup to convert pixel type values from PostGIS to GDAL | ||
POSTGIS_TO_GDAL = [1, 1, 1, 3, 1, 3, 2, 5, 4, None, 6, 7, None, None] | ||
|
||
# Struct pack structure for raster header, the raster header has the | ||
# following structure: | ||
# | ||
# Endianness, PostGIS raster version, number of bands, scale, origin | ||
# skew, srid, width, and height. | ||
# | ||
# Scale, origin and skew have x and y values. PostGIS currently uses | ||
# a fixed endiannes (1) and there is only one version (0). | ||
POSTGIS_HEADER_STRUCTURE = 'B H H d d d d d d i H H' | ||
|
||
# Lookup values to convert GDAL pixel types to struct characters. This is | ||
# used to pack and unpack the pixel values of PostGIS raster bands. | ||
GDAL_TO_STRUCT = [ | ||
None, 'B', 'H', 'h', 'L', 'l', 'f', 'd', | ||
None, None, None, None | ||
] | ||
|
||
# Size of the packed value in bytes for different numerical types. | ||
# This is needed to cut chunks of band data out of PostGIS raster strings | ||
# When decomposing them into GDALRasters. | ||
# See https://docs.python.org/3/library/struct.html#format-characters | ||
STRUCT_SIZE = { | ||
'b': 1, # Signed char | ||
'B': 1, # Unsigned char | ||
'?': 1, # _Bool | ||
'h': 2, # Short | ||
'H': 2, # Unsigned short | ||
'i': 4, # Integer | ||
'I': 4, # Unsigned Integer | ||
'l': 4, # Long | ||
'L': 4, # Unsigned Long | ||
'f': 4, # Float | ||
'd': 8 # Double | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
import binascii | ||
import struct | ||
|
||
from django.forms import ValidationError | ||
|
||
from .const import ( | ||
GDAL_TO_POSTGIS, GDAL_TO_STRUCT, POSTGIS_HEADER_STRUCTURE, POSTGIS_TO_GDAL, | ||
STRUCT_SIZE, | ||
) | ||
|
||
|
||
def pack(structure, data): | ||
""" | ||
Packs data into hex string with little endian format. | ||
""" | ||
return binascii.hexlify(struct.pack('<' + structure, *data)).upper() | ||
|
||
|
||
def unpack(structure, data): | ||
""" | ||
Unpacks little endian hexlified binary string into python list. | ||
""" | ||
return struct.unpack('<' + structure, binascii.unhexlify(data)) | ||
|
||
|
||
def chunk(data, index): | ||
""" | ||
Splits a string into two parts at the input index. | ||
""" | ||
return data[:index], data[index:] | ||
|
||
|
||
def band_to_hex(band): | ||
""" | ||
Returns a GDALBand's pixel values as PGRaster Band hex string. | ||
""" | ||
return binascii.hexlify(band.data(as_memoryview=True)).upper() | ||
|
||
|
||
def from_pgraster(data): | ||
""" | ||
Converts a PostGIS HEX String into a python dictionary. | ||
""" | ||
# Split raster header from data | ||
header, data = chunk(data, 122) | ||
header = unpack(POSTGIS_HEADER_STRUCTURE, header) | ||
|
||
# Parse band data | ||
bands = [] | ||
pixeltypes = [] | ||
while data: | ||
# Get pixel type for this band | ||
pixeltype, data = chunk(data, 2) | ||
pixeltype = unpack('B', pixeltype)[0] | ||
|
||
# Subtract nodata byte from band nodata value if exists | ||
has_nodata = pixeltype >= 64 | ||
if has_nodata: | ||
pixeltype -= 64 | ||
|
||
# Convert datatype from PostGIS to GDAL & get pack type and size | ||
pixeltype = POSTGIS_TO_GDAL[pixeltype] | ||
pack_type = GDAL_TO_STRUCT[pixeltype] | ||
pack_size = 2 * STRUCT_SIZE[pack_type] | ||
|
||
# Parse band nodata value, even if it is ignored by the nodata flag | ||
nodata, data = chunk(data, pack_size) | ||
nodata = unpack(pack_type, nodata)[0] | ||
|
||
# Chunk and unpack band data (pack size times nr of pixels) | ||
band, data = chunk(data, pack_size * header[10] * header[11]) | ||
band_result = {'data': binascii.unhexlify(band)} | ||
|
||
if has_nodata: | ||
band_result['nodata_value'] = nodata | ||
|
||
bands.append(band_result) | ||
pixeltypes.append(pixeltype) | ||
|
||
# Check that all bands have the same pixeltype, this is required by GDAL | ||
if len(set(pixeltypes)) != 1: | ||
raise ValidationError("Band pixeltypes are not all equal.") | ||
|
||
# Process raster header | ||
return { | ||
'srid': int(header[8]), | ||
'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): | ||
""" | ||
Converts a GDALRaster into PostGIS Raster format. | ||
""" | ||
# Prepare the raster header data as tuple. The first two numbers are | ||
# the endianness and the PostGIS Raster Version, both are fixed by | ||
# PostGIS at the moment. | ||
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) | ||
|
||
# Hexlify raster header | ||
result = pack(POSTGIS_HEADER_STRUCTURE, rasterheader) | ||
|
||
for band in rast.bands: | ||
# The PostGIS WKB band header has 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 integer plus 64 as a flag for existing | ||
# nodata values. As a formula one could write: | ||
# 8BUI_VALUE = PG_PIXEL_TYPE (0-11) + FLAG (0 or 64) | ||
# | ||
# For example, if the byte value is 71, then the datatype is | ||
# 71-64 = 7 (32BSI) and the nodata value is True. | ||
structure = 'B' + GDAL_TO_STRUCT[band.datatype()] | ||
|
||
# Get band pixel type in PostGIS notation | ||
pixeltype = GDAL_TO_POSTGIS[band.datatype()] | ||
|
||
# Set the nodata flag | ||
if band.nodata_value is not None: | ||
pixeltype += 64 | ||
|
||
# Pack band header | ||
bandheader = pack(structure, (pixeltype, band.nodata_value or 0)) | ||
|
||
# Add packed header and band data to result string | ||
result += bandheader + band_to_hex(band) | ||
|
||
# Cast raster to string before passing it to the DB | ||
result = result.decode() | ||
|
||
return result |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.