diff --git a/django/contrib/gis/db/backends/postgis/const.py b/django/contrib/gis/db/backends/postgis/const.py index 6091acc7b4..193aa3fc36 100644 --- a/django/contrib/gis/db/backends/postgis/const.py +++ b/django/contrib/gis/db/backends/postgis/const.py @@ -42,5 +42,11 @@ STRUCT_SIZE = { 'd': 8, # Double } +# Pixel type specifies type of pixel values in a band. Storage flag specifies +# whether the band data is stored as part of the datum or is to be found on the +# server's filesystem. There are currently 11 supported pixel value types, so 4 +# bits are enough to account for all. Reserve the upper 4 bits for generic +# flags. # See https://trac.osgeo.org/postgis/wiki/WKTRaster/RFC/RFC1_V0SerialFormat#Pixeltypeandstorageflag +BANDTYPE_PIXTYPE_MASK = 0x0F BANDTYPE_FLAG_HASNODATA = 1 << 6 diff --git a/django/contrib/gis/db/backends/postgis/pgraster.py b/django/contrib/gis/db/backends/postgis/pgraster.py index 1a4dc9aa23..085c130183 100644 --- a/django/contrib/gis/db/backends/postgis/pgraster.py +++ b/django/contrib/gis/db/backends/postgis/pgraster.py @@ -3,8 +3,8 @@ import struct from django.forms import ValidationError from .const import ( - BANDTYPE_FLAG_HASNODATA, GDAL_TO_POSTGIS, GDAL_TO_STRUCT, - POSTGIS_HEADER_STRUCTURE, POSTGIS_TO_GDAL, STRUCT_SIZE, + BANDTYPE_FLAG_HASNODATA, BANDTYPE_PIXTYPE_MASK, GDAL_TO_POSTGIS, + GDAL_TO_STRUCT, POSTGIS_HEADER_STRUCTURE, POSTGIS_TO_GDAL, STRUCT_SIZE, ) @@ -45,13 +45,9 @@ def from_pgraster(data): pixeltypes = [] while data: # Get pixel type for this band - pixeltype, data = chunk(data, 2) - pixeltype = unpack('B', pixeltype)[0] - - # Remove nodata byte from band nodata value if it exists. - has_nodata = pixeltype & BANDTYPE_FLAG_HASNODATA - if has_nodata: - pixeltype &= ~BANDTYPE_FLAG_HASNODATA + pixeltype_with_flags, data = chunk(data, 2) + pixeltype_with_flags = unpack('B', pixeltype_with_flags)[0] + pixeltype = pixeltype_with_flags & BANDTYPE_PIXTYPE_MASK # Convert datatype from PostGIS to GDAL & get pack type and size pixeltype = POSTGIS_TO_GDAL[pixeltype] @@ -68,8 +64,8 @@ def from_pgraster(data): band, data = chunk(data, pack_size * header[10] * header[11]) band_result = {'data': bytes.fromhex(band)} - # If the nodata flag is True, set the nodata value. - if has_nodata: + # Set the nodata value if the nodata flag is set. + if pixeltype_with_flags & BANDTYPE_FLAG_HASNODATA: band_result['nodata_value'] = nodata # Append band data to band list diff --git a/tests/gis_tests/rasterapp/test_rasterfield.py b/tests/gis_tests/rasterapp/test_rasterfield.py index 63a6325135..2a6fe09f6a 100644 --- a/tests/gis_tests/rasterapp/test_rasterfield.py +++ b/tests/gis_tests/rasterapp/test_rasterfield.py @@ -8,7 +8,7 @@ from django.contrib.gis.geos import GEOSGeometry from django.contrib.gis.measure import D from django.contrib.gis.shortcuts import numpy from django.db import connection -from django.db.models import Q +from django.db.models import F, Func, Q from django.test import TransactionTestCase, skipUnlessDBFeature from django.test.utils import CaptureQueriesContext @@ -51,6 +51,26 @@ class RasterFieldTest(TransactionTestCase): qs = RasterModel.objects.all() qs[0].rast.bands[0].data() + def test_deserialize_with_pixeltype_flags(self): + no_data = 3 + rast = GDALRaster({ + 'srid': 4326, + 'origin': [0, 0], + 'scale': [-1, 1], + 'skew': [0, 0], + 'width': 1, + 'height': 1, + 'nr_of_bands': 1, + 'bands': [{'data': [no_data], 'nodata_value': no_data}], + }) + r = RasterModel.objects.create(rast=rast) + RasterModel.objects.filter(pk=r.pk).update( + rast=Func(F('rast'), function='ST_SetBandIsNoData'), + ) + r.refresh_from_db() + self.assertEquals(r.rast.bands[0].data(), [[no_data]]) + self.assertEquals(r.rast.bands[0].nodata_value, no_data) + def test_model_creation(self): """ Test RasterField through a test model.