Table of Contents
The PostGIS raster extension stores gridded coverage data (elevation, temperature, satellite imagery, …) in the raster type. Each raster has one or more bands; a band holds a 2D array of pixel values, a spatial extent, a pixel size, and an optional nodata sentinel value.
MobilityDB's raster sampling operator evaluates a raster band at the instants of a moving-point trajectory and returns a tfloat. Instants whose position falls outside the raster extent or on a nodata pixel are silently dropped. The sampling operators are double-valued whatever the pixel type of the band, so a pixel type belongs to the sampling surface when every value it can hold is exactly representable in a double precision number. That is what lets a band of any type be sampled into one tfloat, and a column holding tiles of several pixel types be read by a single query. A pixel type is also accepted under the name PostGIS raster gives it, so the band type an ST_BandPixelType call reports (8BUI, 16BSI, 32BF, …) passes into the tile constructors as it stands. PostGIS raster carries no 16-bit float and no 64-bit integer, so float16, int64 and uint64 take the spelling its grammar gives them, 16BF, 64BSI and 64BUI. Its 1BB, 2BUI and 4BUI types bound a pixel to less than a byte while PostGIS stores each of them a byte a pixel, so such a band is already the bytes of an unsigned 8-bit band and the constructors read it as uint8; the bound is the only thing the name adds, and a tile does not carry it. The name a tile reports is the one the RaQuet specification gives the type.
The types uint64 and int64 hold values beyond that domain: a tile of either is stored and read back exactly, while sampling a pixel whose value no double precision number names raises an error rather than answering with a neighbouring value. This enables queries such as what elevation profile did each vehicle follow? or which trips entered a temperature threshold band?
To use the operators described here, MobilityDB must be built with -DRASTER=ON. On such a build the generated mobilitydb.control declares requires = 'postgis, postgis_raster', so a single CASCADE creates the full stack:
CREATE EXTENSION mobilitydb CASCADE; -- NOTICE: installing required extension "postgis" -- NOTICE: installing required extension "postgis_raster"
Sample a raster band at each instant of a trajectory
rasterValue(tgeompoint,rast raster,band integer=1) → tfloat
Evaluates pixel band at every instant of traj using bilinear-nearest sampling (ST_Value with resample=false). Returns an instant-set tfloat whose values are rounded to four decimal places. Returns NULL when no instant intersects the raster.
-- Build a 3x3 synthetic elevation raster (SRID 4326, 1 degree pixels)
WITH rast AS (
SELECT ST_SetValues(ST_AddBand( ST_MakeEmptyRaster(3, 3, 0.0, 3.0, 1.0, -1.0, 0.0, 0.0,
4326), '32BF'::text, 0.0::float8, NULL::float8), 1, 1, 1,
ARRAY[[10.0::float4, 20.0::float4, 30.0::float4],
[40.0::float4, 50.0::float4, 60.0::float4],
[70.0::float4, 80.0::float4, 90.0::float4]]) AS r )
SELECT rasterValue(tgeompoint 'SRID=4326;[POINT(0.5 2.5)@2001-01-01,
POINT(2.5 2.5)@2001-01-02, POINT(0.5 0.5)@2001-01-03]', r)::text FROM rast;
-- {10@2001-01-01, 30@2001-01-02, 70@2001-01-03}
Applied to a trajectory dataset with a terrain elevation raster:
-- Elevation profile per trip (requires a loaded elevation raster) SELECT tripid, rasterValue(trip, elev) AS elevation FROM trips, elevation_raster; -- Average elevation per trip SELECT tripid, avgValue(rasterValue(trip, elev)) AS mean_elev FROM trips, elevation_raster; -- Trips that crossed terrain above 200 m SELECT tripid FROM trips, elevation_raster WHERE atSpan(rasterValue(trip, elev), floatspan '[200, 9999]') IS NOT NULL;