A raster of MobilityDB is the same type, with the same functions, in every engine (the section called “Raster-Specific Operations”). Two engines also have a raster of their own: DuckDB through its raster extension, and Apache Spark through Apache Sedona; Apache Flink has none. Each host names everything it adds with a prefix, RT_ in DuckDB and RS_ in Sedona, and MobilityDB names nothing with either, so the name of a function tells whose raster it works on: RT_Read and RS_FromGeoTiff are the host's, summaryStats and rasterValue MobilityDB's. A query computes with MobilityDB's raster and converts a value at the boundary only where it needs the host: to read a raster file with the host's reader, or to hand a raster to one of the host's functions. The conversion keeps every pixel value and the georeferencing, so a raster converted to the host's raster and back is the raster it was.
| What the query does | DuckDB with its raster extension | Spark with Apache Sedona |
|---|---|---|
| Make the host's raster available | LOAD raster | SedonaContext.create(spark), beside the registration of MobilitySpark |
| Read a raster file | RT_Read(file), then raster(databand_1, tile_x, tile_y, metadata) for each tile | RS_FromGeoTiff(content), then the conversion of the section called “Spark and Apache Sedona” |
| Give a raster to the host's functions | CAST(rast AS RT_DATACUBE) for its pixels, rasterMetadata(rast) for its georeferencing | RS_AddBandFromArray over RS_MakeEmptyRaster, from its accessors |
| Give the statistics of a band to the host | CAST(stats AS RT_STATS), and back CAST(stats AS summarystats) | none |
The two hosts hold different pixel types. Each pixel type of MobilityDB converts to the type of each host on its row, and a raster of a type a host has none for has no raster in that host.
| Pixel type of the raster | Datacube of DuckDB | Pixel type of Sedona |
|---|---|---|
| 1BB, 2BUI, 4BUI | UINT8 | none |
| 8BUI | UINT8 | B |
| 8BSI | INT8 | none |
| 16BUI | UINT16 | US |
| 16BSI | INT16 | S |
| 32BUI | UINT32 | none |
| 32BSI | INT32 | I |
| 32BF | FLOAT | F |
| 64BF | DOUBLE | D |
DuckDB's raster extension, a community extension, reads a raster file with RT_Read as a table with one row per tile: the pixels of each band in a databand_N column of type RT_DATACUBE, the georeferencing of the whole file in a metadata JSON column, and the position of the tile in tile_x and tile_y. It has no raster value of its own, only these columns. A MobilityDB raster is built from the four columns of a tile, and gives back a datacube and a metadata column, the two columns the extension's own functions read a tile from. The metadata rasterMetadata writes is that of a file holding the raster as its one tile, so the raster is built back from it at the tile (0, 0).
The type RT_DATACUBE exists only once the extension is loaded, so a query naming it runs after LOAD raster; before it, DuckDB answers that no type RT_DATACUBE exists. The conversions are explicit casts and functions: a raster is never converted to a datacube by itself when it is passed to an RT_ function, since a datacube leaves the georeferencing behind.
A datacube holds one pixel type for all its bands, and each pixel type of MobilityDB becomes the datacube type that GDAL gives it, as a PostGIS raster written through GDAL does. The 1-, 2- and 4-bit types have no smaller type in a datacube: their values are kept exactly in 8 bits, and a raster built back from the datacube has the type 8BUI.
A conversion either keeps every value or raises an error; it never answers an approximation. A datacube holds one pixel type and one nodata value, so a raster whose bands differ in either cannot be cast to RT_DATACUBE. A datacube of type UINT64 or INT64 has no pixel type of MobilityDB to become. A datacube or a metadata column that is not well formed, truncated, carrying an unknown type or format, or declaring more pixels than it holds, is refused rather than read. The coordinate reference system of the metadata is read as EPSG:n and gives the SRID of the raster; a raster combined with a value of another SRID raises the error any mix of SRIDs raises. A raster cast to a datacube writes its pixels uncompressed; a datacube is read whichever compression the extension wrote it with, RAW, SNAPPY, GZIP, ZSTD or LZ4.
LOAD raster;
-- The tiles of 2 x 2 pixels of a 4 x 4 GeoTIFF of elevations, as MobilityDB rasters
CREATE TABLE elevation AS
SELECT tile_x, tile_y, raster(databand_1, tile_x, tile_y, metadata) AS rast
FROM RT_Read('elevation.tif', blocksize_x := 2, blocksize_y := 2);
-- MobilityDB's raster functions over them
SELECT count(*), summarystatsMean(summaryStatsAgg(rast, true)) FROM elevation;
-- 4 | 57.06666666666667
-- A MobilityDB raster handed to a function of the extension
SELECT tile_x, tile_y, RT_CubeStats(CAST(rast AS RT_DATACUBE), 0).mean
FROM elevation ORDER BY tile_y, tile_x;
-- 0 | 0 | 53.5
-- 1 | 0 | 68.5
-- 0 | 1 | 46.0
-- 1 | 1 | 61.333333333333336
-- A raster converted to the extension's columns and back is the raster it was
SELECT bool_and(raster(CAST(rast AS RT_DATACUBE), 0, 0, rasterMetadata(rast)) = rast)
FROM elevation;
-- true
Apache Sedona registers its raster type and its RS_ functions in a Spark session through SedonaContext.create(spark), beside the functions of MobilitySpark; none of the names of either is a name of the other. A Sedona raster is a value, read from a file with RS_FromGeoTiff and written with RS_AsGeoTiff, and it converts to and from a MobilityDB raster through the functions both already have: the georeferencing passes through the accessors of each, and the values of a band as an array.
A Sedona raster is built from a MobilityDB raster r by RS_MakeEmptyRaster with the georeferencing of r and RS_AddBandFromArray with the values of a band, its pixel type and its nodata value. A MobilityDB raster is built from a Sedona raster s by raster(pixels, width, height, upperleftx, upperlefty, scalex, scaley, skewx, skewy, srid, pixeltype[, nodataval]) over the RS_ accessors of s, its pixels being the bytes of the values of RS_BandAsArray(s, 1) in their order; each value becomes its bytes as the pixel of a one-pixel band, rasterPixels(addBand(makeEmptyRaster(1, 1, 0.0, 0.0, 1.0, -1.0, 0.0, 0.0, 0), 1, pixeltype, v), 1). Both directions read one band; a raster of several bands converts band by band.
The six pixel types Sedona holds convert both ways, with or without a nodata value, without changing a value, and a raster converted to Sedona and back is the raster it was, byte for byte; the others have no Sedona raster.
-- A Sedona raster from the georeferencing and the band of a MobilityDB raster r
SELECT RS_AddBandFromArray(
RS_MakeEmptyRaster(1, CASE rasterBandPixelType(r, 1) WHEN 'uint8' THEN 'B'
WHEN 'int16' THEN 'S' WHEN 'uint16' THEN 'US' WHEN 'int32' THEN 'I'
WHEN 'float32' THEN 'F' WHEN 'float64' THEN 'D' END,
rasterWidth(r), rasterHeight(r), upperLeftX(r), upperLeftY(r),
scaleX(r), scaleY(r), skewX(r), skewY(r), SRID(r)),
flatten(dumpValues(r, 1, false)), 1, rasterBandNoDataValue(r, 1)) AS s
FROM t;