Insert RasterΒΆ

The RasterElement objects store the Raster data in WKB form. This WKB format is usually fetched from the database but when the data comes from another source it can be hard to format it as as a WKB. This example shows a method to convert input data into a WKB in order to insert it. This example uses SQLAlchemy ORM queries.

Warning

The PixelType values are not always properly translated by the Rasterio library, so exporting a raster and re-importing it using this method will properly import the values but might not keep the same internal types.

 16 import struct
 17 from sys import byteorder
 18
 19 import numpy as np
 20 import pytest
 21 import rasterio
 22 from sqlalchemy import Column
 23 from sqlalchemy import Integer
 24 from sqlalchemy import MetaData
 25 from sqlalchemy import text
 26 from sqlalchemy.orm import declarative_base
 27
 28 from geoalchemy2 import Raster
 29 from geoalchemy2 import RasterElement
 30
 31 # Tests imports
 32 from tests import test_only_with_dialects
 33
 34 metadata = MetaData()
 35 Base = declarative_base(metadata=metadata)
 36
 37
 38 class Ocean(Base):  # type: ignore
 39     __tablename__ = "ocean"
 40     id = Column(Integer, primary_key=True)
 41     rast = Column(Raster)
 42
 43     def __init__(self, rast):
 44         self.rast = rast
 45
 46
 47 _DTYPE = {
 48     "?": [0, "?", 1],
 49     "u1": [2, "B", 1],
 50     "i1": [3, "b", 1],
 51     "B": [4, "B", 1],
 52     "i2": [5, "h", 2],
 53     "u2": [6, "H", 2],
 54     "i4": [7, "i", 4],
 55     "u4": [8, "I", 4],
 56     "f4": [10, "f", 4],
 57     "f8": [11, "d", 8],
 58 }
 59
 60
 61 def write_wkb_raster(dataset):
 62     """Creates a WKB raster from the given raster file with rasterio.
 63     :dataset: Rasterio dataset
 64     :returns: binary: Binary raster in WKB format
 65
 66     This function was imported from
 67     https://github.com/nathancahill/wkb-raster/blob/master/wkb_raster.py
 68     and slightly adapted.
 69     """
 70
 71     # Define format, see https://docs.python.org/3/library/struct.html
 72     format_string = "bHHddddddIHH"
 73
 74     if byteorder == "big":
 75         endian = ">"
 76         endian_byte = 0
 77     elif byteorder == "little":
 78         endian = "<"
 79         endian_byte = 1
 80
 81     # Write the raster header data.
 82     header = bytes()
 83
 84     transform = dataset.transform.to_gdal()
 85
 86     version = 0
 87     nBands = int(dataset.count)
 88     scaleX = transform[1]
 89     scaleY = transform[5]
 90     ipX = transform[0]
 91     ipY = transform[3]
 92     skewX = 0
 93     skewY = 0
 94     srid = int(dataset.crs.to_string().split("EPSG:")[1])
 95     width = int(dataset.meta.get("width"))
 96     height = int(dataset.meta.get("height"))
 97
 98     fmt = f"{endian}{format_string}"
 99
100     header = struct.pack(
101         fmt,
102         endian_byte,
103         version,
104         nBands,
105         scaleX,
106         scaleY,
107         ipX,
108         ipY,
109         skewX,
110         skewY,
111         srid,
112         width,
113         height,
114     )
115
116     bands = []
117
118     # Create band header data
119
120     # not used - always False
121     isOffline = False
122     hasNodataValue = False
123
124     if "nodata" in dataset.meta:
125         hasNodataValue = True
126
127     # not used - always False
128     isNodataValue = False
129
130     # unset
131     reserved = False
132
133     # # Based on the pixel type, determine the struct format, byte size and
134     # # numpy dtype
135     rasterio_dtype = dataset.meta.get("dtype")
136     dt_short = np.dtype(rasterio_dtype).str[1:]
137     pixtype, nodata_fmt, _ = _DTYPE[dt_short]
138
139     # format binary -> :b
140     binary_str = f"{isOffline:b}{hasNodataValue:b}{isNodataValue:b}{reserved:b}{pixtype:b}"
141     # convert to int
142     binary_decimal = int(binary_str, 2)
143
144     # pack to 1 byte
145     # 4 bits for ifOffline, hasNodataValue, isNodataValue, reserved
146     # 4 bit for pixtype
147     # -> 8 bit = 1 byte
148     band_header = struct.pack("<b", binary_decimal)
149
150     # Write the nodata value
151     nodata = struct.pack(nodata_fmt, int(dataset.meta.get("nodata") or 0))
152
153     for i in range(1, nBands + 1):
154         band_array = dataset.read(i)
155
156         # # Write the pixel values: width * height * size
157
158         # numpy tobytes() method instead of packing with struct.pack()
159         band_binary = band_array.reshape(width * height).tobytes()
160
161         bands.append(band_header + nodata + band_binary)
162
163     # join all bands
164     allbands = bytes()
165     for b in bands:
166         allbands += b
167
168     wkb = header + allbands
169
170     return wkb
171
172
173 @test_only_with_dialects("postgresql")
174 class TestInsertRaster:
175     @pytest.fixture(
176         params=[
177             "1BB",
178             "2BUI",
179             "4BUI",
180             "8BSI",
181             "8BUI",
182             "16BSI",
183             "16BUI",
184             "32BSI",
185             "32BUI",
186             "32BF",
187             "64BF",
188         ]
189     )
190     def input_img(self, conn, tmpdir, request):
191         """Create a TIFF image that will be imported as Raster."""
192         pixel_type = request.param
193         conn.execute(text("SET postgis.gdal_enabled_drivers = 'ENABLE_ALL';"))
194         data = conn.execute(
195             text(
196                 """SELECT
197                     ST_AsTIFF(
198                         ST_AsRaster(
199                             ST_GeomFromText('POLYGON((0 0,1 1,0 1,0 0))'),
200                             5,
201                             6,
202                             '{}'
203                         ),
204                         'GTiff'
205                     );""".format(
206                     pixel_type
207                 )
208             )
209         ).scalar()
210         filename = tmpdir / "image.tiff"
211         with open(filename, "wb") as f:
212             f.write(data.tobytes())
213         return filename, pixel_type
214
215     def test_insert_raster(self, session, conn, input_img):
216         """Insert a TIFF image into a raster column."""
217         filename, pixel_type = input_img
218         metadata.drop_all(conn, checkfirst=True)
219         metadata.create_all(conn)
220
221         # Load the image and transform it into a WKB
222         with rasterio.open(str(filename), "r+") as dataset:
223             dataset.crs = rasterio.crs.CRS.from_epsg(4326)
224             expected_values = dataset.read()[0]
225             raw_wkb = write_wkb_raster(dataset)
226
227         # Insert a new raster element
228         polygon_raster = RasterElement(raw_wkb)
229         o = Ocean(polygon_raster)
230         session.add(o)
231         session.flush()
232
233         # Check inserted values
234         new_values = conn.execute(text("SELECT ST_DumpValues(rast, 1, false) FROM ocean;")).scalar()
235         np.testing.assert_array_equal(
236             np.array(new_values, dtype=expected_values.dtype), expected_values
237         )

Gallery generated by Sphinx-Gallery