The sections above state what a raster shares with the other tessellations. The operations below are the raster's own: a raster cell carries a value where an H3, QUADBIN or S2 cell carries only identity, so a trajectory read against a raster answers a tfloat rather than a temporal cell.
MobilityDB's raster sampling operator reads a raster band along a moving-point trajectory and returns a tfloat. A position outside the raster extent or on a nodata pixel carries no value. 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?
A Raquet table keeps the QUADBIN cell of a tile in its block column as a 64-bit integer, beside the metadata row at block = 0, which holds no QUADBIN index. A query casts the cells of a trajectory to bigint to read that column, and a cell read from it to quadbin, the cast checking that it is a QUADBIN index. The typical query pattern joins a trajectory against a Raquet table in two steps:
-- Step 1: identify which tiles the trajectory overlaps SELECT getValues(tquadbin(traj, 8)); -- Step 2: sample each matching tile, a 256 x 256 float32 band without nodata SELECT rasterTileValueQuadbin(traj, band_1, 256, 256, block::quadbin, 'float32', 0, false) FROM elevation_raquet WHERE block IN (SELECT unnest(getValues(tquadbin(traj, 8)))::bigint);
The restriction and predicate operators that close the section compose rasterValue with the standard MobilityDB temporal restriction and predicate functions. They accept an optional band argument (default 1) and call rasterValue exactly once per invocation.
A raster function takes the name of its PostGIS raster counterpart with the ST_ prefix removed, and the names and defaults of its arguments. One signature answers each operation, so a PostGIS form that only fixes an argument of another is written through that other form. A name the SQL standard reserves takes the type as a prefix, so ST_Value is rasterValue, and the Well-Known Binary functions take the names every MobilityDB type takes.
| PostGIS raster | MobilityDB |
|---|---|
ST_Value | rasterValue |
ST_Clip | clip |
ST_Transform | transform |
ST_Rescale | rescale |
ST_Reclass | reclass |
ST_SummaryStats | summaryStats |
ST_DumpAsPolygons | dumpAsPolygons |
ST_NumBands, ST_Width, ST_Height, ST_SRID | numBands, width, height, SRID |
ST_UpperLeftX, ST_UpperLeftY, ST_ScaleX, ST_ScaleY, ST_SkewX, ST_SkewY | upperLeftX, upperLeftY, scaleX, scaleY, skewX, skewY |
ST_BandPixelType, ST_BandNoDataValue | bandPixelType, bandNoDataValue |
ST_MakeEmptyRaster, ST_AddBand, ST_SetValues | makeEmptyRaster, addBand, setValues |
ST_AsBinary, ST_AsHexWKB | asBinary, asHexWKB |
ST_RastFromWKB, ST_RastFromHexWKB | rasterFromBinary, rasterFromHexWKB |
asBinary and asHexWKB take a byte order where PostGIS takes outasin, and without one answer the bytes of ST_AsBinary and ST_AsHexWKB.
Read a raster band along a trajectory
rasterValue(tgeompoint,rast raster,band integer=1,exclude_nodata_value boolean=true, resample text='nearest') → tfloat rasterValue(tgeompoint,path text,band integer=1) → tfloat
Reads band band along traj, taking the value of the pixel each position falls in. A trajectory that states nothing between its instants is read at its instants and answers an instant-set tfloat. A trajectory that moves between them passes over the pixels between them, so each segment is traversed pixel by pixel, and it answers a step tfloat holding each value from the instant the trip reaches the pixel holding it, a pixel whose corner the trip only clips included. Under an exclusive upper bound, a pixel the trajectory reaches only at its end, holding it for no time, carries no value into the result, so the reads of a trajectory cut at successive half-open periods merge into the read of the whole trajectory. A position outside the raster or over a nodata pixel carries no value and ends the run, so a trip that leaves and re-enters the raster answers one sequence per visit, and NULL when it never meets a pixel carrying data. A band whose pixels cannot be read, such as a band stored outside the database in a file that cannot be opened, raises an error rather than reading as a band without values. The form taking path reads a raster file on the server, in any format GDAL supports, through GDAL, where PostGIS allows a raster band stored outside the database to be read: postgis.enable_outdb_rasters must be on and postgis.gdal_enabled_drivers must enable the driver of the file. It answers what the form taking a raster answers over the same raster.
exclude_nodata_value and resample are those of the PostGIS ST_Value: with the first false a position over a nodata pixel keeps the nodata value where it is otherwise dropped, and the second reads the pixel under the position with nearest or bilinear, named without regard to case. A bilinear value varies within a pixel. Along a segment of a trajectory that moves between its instants it is, over each half-pixel square the segment crosses, a polynomial of degree two in time, so a trajectory read with bilinear answers a linear tfloat holding the value at the instants of the trajectory, where it crosses the pixel edges and the lines through the pixel centres, and at the extremum each of those pieces reaches, linear between them, as the product of two temporal floats holds its extremum. Its minimum and maximum are those of the value, and it is the value itself where the trajectory moves along a grid axis, over which the value is linear; elsewhere, between the instants it holds, it is the chord of the value. The value jumps across a pixel edge where a pixel read there holds nodata, which takes the value of the pixel the position falls in, and a new sequence then starts at the instant the trajectory crosses the edge.
-- 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;
/* Interp=Step;[10@2001-01-01, 20@2001-01-01 06:00:00, 30@2001-01-01 18:00:00,
50@2001-01-02 06:00:00, 70@2001-01-02 18:00:00, 70@2001-01-03] */
-- A bilinear read reaching its maximum between two pixel centres
WITH rast AS (
SELECT ST_SetValues(ST_AddBand( ST_MakeEmptyRaster(2, 2, 0.0, 2.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], [20.0::float4, 10.0::float4]]) AS r )
SELECT round(rasterValue(tgeompoint 'SRID=4326;[POINT(0.5 1.5)@2001-01-01,
POINT(1.5 1.1)@2001-01-03]', r, 1, true, 'bilinear'), 6)::text FROM rast;
/* [10@2001-01-01, 15@2001-01-02, 16.125@2001-01-02 18:00:00, 16@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;
A raster loaded as a table of tiles, as raster2pgsql -t loads it, is read along a trajectory by merging the reads of its tiles with merge. A position lies in a pixel of one tile, so the reads of the tiles hold disjoint times, and their merge answers the read of the raster the tiles cover, a trip crossing from one tile to the next included.
-- A 4x4 raster read along a trip whole, and as its four tiles of 2x2 pixels
WITH r AS (
SELECT ST_SetValues(ST_AddBand(ST_MakeEmptyRaster(4, 4, 0.0, 4.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, 100.0::float4, 110.0::float4, 120.0::float4],
[130.0::float4, 140.0::float4, 150.0::float4, 160.0::float4]]) AS rast ),
t AS (
SELECT tgeompoint 'SRID=4326;[POINT(0.5 3.5)@2001-01-01, POINT(3.5 0.5)@2001-01-02]'
AS trip )
SELECT merge(rasterValue(trip, tile)) = (SELECT rasterValue(trip, rast) FROM t, r)
FROM t, r, ST_Tile(rast, 2, 2) AS tile;
-- true
Return the number of pixels of a raster band holding each value
valueCount(rast raster,nband integer=1,exclude_nodata_value boolean=true, searchvalues float[]=NULL,roundto float=0) → setof (value float,count integer) valueCount(rast raster,nband integer,searchvalues float[],roundto float=0) → setof (value float,count integer) valueCount(rast raster,searchvalues float[],roundto float=0) → setof (value float,count integer) valueCount(rast raster,nband integer,exclude_nodata_value boolean,searchvalue float, roundto float=0) → integer valueCount(rast raster,nband integer,searchvalue float,roundto float=0) → integer valueCount(rast raster,searchvalue float,roundto float=0) → integer
The histogram of PostGIS's ST_ValueCount, with its arguments and defaults: one row for each distinct value of the band, or one for each search value, which counts zero where no pixel holds it, and the number of pixels holding one value in the forms taking a single search value. A value is rounded to the nearest multiple of roundto before it is counted where roundto is positive. A band whose every pixel is nodata answers no row where nodata is excluded, and a band the raster does not have raises an error where PostGIS answers NULL. The forms of PostGIS reading a raster column of a table query the database, so they stay PostGIS's.
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, -9999.0::float8), 1, 1, 1,
ARRAY[[1.24, 1.26, -9999], [2, 2, 2], [3, 1.24, 2]]::double precision[][]) AS r )
SELECT round(value::numeric, 2), count
FROM rast, valueCount(r, 1, true, NULL::float[], 0.1) ORDER BY 1;
-- 1.20 | 2
-- 1.30 | 1
-- 2.00 | 4
-- 3.00 | 1
Return the share of the pixels of a raster band holding each value
valuePercent(rast raster,nband integer=1,exclude_nodata_value boolean=true, searchvalues float[]=NULL,roundto float=0) → setof (value float,percent float) valuePercent(rast raster,nband integer,searchvalues float[],roundto float=0) → setof (value float,percent float) valuePercent(rast raster,searchvalues float[],roundto float=0) → setof (value float,percent float) valuePercent(rast raster,nband integer,exclude_nodata_value boolean, searchvalue float,roundto float=0) → float valuePercent(rast raster,nband integer,searchvalue float,roundto float=0) → float valuePercent(rast raster,searchvalue float,roundto float=0) → float
The share of PostGIS's ST_ValuePercent, between 0 and 1, of the pixels valueCount reads, with the same arguments and forms. Where no pixel is read, the share of a search value is zero divided by zero, NaN, as for PostGIS.
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, -9999.0::float8), 1, 1, 1,
ARRAY[[1.24, 1.26, -9999], [2, 2, 2], [3, 1.24, 2]]::double precision[][]) AS r )
SELECT valuePercent(r, 2::float), valuePercent(r, 3::float) FROM rast;
-- 0.5 | 0.125
Return true if rasters lie on the same grid
sameAlignment(rast1 raster,rast2 raster) → boolean sameAlignment(raster) → boolean sameAlignment(raster) → boolean
The test of PostGIS's ST_SameAlignment: two rasters are aligned when they have the same scale and skew and a pixel corner of one falls on a pixel corner of the other, so that their pixels can be combined one to one. The aggregate answers whether every raster of a column is aligned with the first, skipping a NULL raster as setUnion skips a NULL set, where PostGIS answers NULL; over no raster it answers NULL. Rasters of different SRIDs raise an error, where PostGIS answers false with a notice.
WITH r AS (SELECT ST_MakeEmptyRaster(3, 3, 0.0, 3.0, 1.0, -1.0, 0.0, 0.0, 4326) AS base, ST_MakeEmptyRaster(3, 3, 1.0, 3.0, 1.0, -1.0, 0.0, 0.0, 4326) AS east1, ST_MakeEmptyRaster(3, 3, 0.5, 3.0, 1.0, -1.0, 0.0, 0.0, 4326) AS easthalf) SELECT sameAlignment(base, east1), sameAlignment(base, easthalf), (SELECT sameAlignment(x) FROM (VALUES (base), (east1), (easthalf)) t(x)) FROM r; -- t | f | f
Return why two rasters do not lie on the same grid
notSameAlignmentReason(rast1 raster,rast2 raster) → text
The reason of PostGIS's ST_NotSameAlignmentReason, in its words: the first property in which the grids differ, or that the rasters are aligned.
WITH r AS (SELECT ST_MakeEmptyRaster(3, 3, 0.0, 3.0, 1.0, -1.0, 0.0, 0.0, 4326) AS base, ST_MakeEmptyRaster(3, 3, 0.5, 3.0, 1.0, -1.0, 0.0, 0.0, 4326) AS easthalf, ST_MakeEmptyRaster(3, 3, 0.0, 3.0, 2.0, -1.0, 0.0, 0.0, 4326) AS scalex2) SELECT notSameAlignmentReason(base, easthalf), notSameAlignmentReason(base, scalex2) FROM r; -- The rasters (pixel corner coordinates) are not aligned | -- The rasters have different scales on the X axis
Return the union of the rasters of a column
rasterUnion(raster) → raster rasterUnion(raster,nband integer) → raster rasterUnion(raster,uniontype text) → raster rasterUnion(raster,nband integer,uniontype text) → raster
The aggregate of PostGIS's ST_Union over rasters, named as geoUnion names the union of geometries, since union is a keyword of SQL. The result covers the union of the extents of the rasters, each pixel the pixel operation of the pixels holding a value there: LAST, the default, FIRST, MIN, MAX, COUNT, SUM, MEAN or RANGE, regardless of case. Without nband every band is united, a band a raster lacks read as nodata; with nband only that band, which every raster must hold. The pixel type and the nodata value are those of the band read first, and a count is an unsigned 32-bit band without a nodata value. A NULL raster is skipped, and over no raster the aggregate answers NULL. The aggregate runs in parallel, the unions of the workers combined, where PostGIS's has no combine function; LAST and FIRST depend on the order of the rasters, which an ORDER BY in the aggregate fixes. Rasters of different SRIDs or on different grids raise an error, and so does a pixel operation outside these, which PostGIS reads as LAST.
WITH tiles AS (
SELECT 1 AS id, ST_AddBand(ST_MakeEmptyRaster(2, 2, 0.0, 2.0, 1.0, -1.0, 0.0, 0.0, 3857),
'32BF'::text, 1.0::float8, -9999.0::float8) AS r
UNION ALL
SELECT 2, ST_AddBand(ST_MakeEmptyRaster(2, 2, 1.0, 1.0, 1.0, -1.0, 0.0, 0.0, 3857),
'32BF'::text, 3.0::float8, -9999.0::float8) )
SELECT ST_DumpValues(rasterUnion(r, 'MEAN' ORDER BY id), 1) FROM tiles;
-- {{1,1,NULL},{1,2,3},{NULL,3,3}}
Return a raster whose band holds the classes an expression maps its values to
reclass(raster,nband integer,reclassexpr text,pixeltype text,nodataval float=NULL) → raster
The expression follows the grammar of the PostGIS ST_Reclass: a comma-separated list of mappings, each a source range and a destination around a colon, a range being one value or two around a dash. A range written plainly includes its low end and excludes its high end, while ( excludes the low end and ] includes the high end, so 0-50 leaves 50 unmapped where [50-100] takes it. A pixel no range takes holds the nodata value of the resulting band, or zero when the band states none. That band has the pixel type pixeltype, named as PostGIS raster names it, such as 8BUI, and states no nodata value when nodataval is left out. The raster keeps its grid and its other bands, so the classes can be read along a trajectory with rasterValue.
-- 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 ST_DumpValues(reclass(r, 1, '0-50:1, 51-100:2', '8BUI', 0), 1) AS plain,
ST_DumpValues(reclass(r, 1, '0-50:1, [50-100]:2', '8BUI', 0), 1) AS bracket
FROM rast;
-- {{1,1,1},{1,NULL,2},{2,2,2}} | {{1,1,1},{1,2,2},{2,2,2}}
-- Read the classes along a trajectory
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(1.5 1.5)@2001-01-02, POINT(2.5 0.5)@2001-01-03}',
reclass(r, 1, '0-50:1, [50-100]:2', '8BUI', 0))::text FROM rast;
-- {1@2001-01-01, 2@2001-01-02, 2@2001-01-03}
Addition, subtraction, multiplication, and division of rasters, pixel by pixel
{float,raster} {+, -, *, /} {float,raster} → raster
A raster takes the arithmetic operators of a temporal number, so a derived raster is written as the expression it is, such as (nir - red) / (nir + red) or depth - 8.5, where PostGIS writes it as the expression of ST_MapAlgebra. Every band of the result has the pixel type 64BF, whatever the pixel types of the operands, so a pixel holds the arithmetic as computed: a negative difference of two unsigned bands, a product beyond their range, and a ratio between zero and one are kept rather than clamped or truncated, and reclass narrows the result where a smaller pixel type is needed. Two rasters are operated band by band over the pixels they share, as two temporal numbers are over the time they share: they are to have the same reference system, the same grid, and the same number of bands, and two rasters sharing no pixel have no result. A pixel that is nodata in either operand is nodata in the result, which takes the nodata value of the first operand stating one and states none when neither does, so a result of zero is a value. A division by zero raises an error, whether the divisor is a number or a pixel. The functions behind the operators are rasterAdd, rasterSub, rasterMul, and rasterDiv.
-- The normalized difference of two unsigned 16-bit bands
WITH bands AS (
SELECT
ST_SetValues(ST_AddBand(ST_MakeEmptyRaster(3, 3, 0.0, 3.0, 1.0, -1.0, 0.0, 0.0,
4326), '16BUI'::text, 0.0::float8, NULL::float8), 1, 1, 1,
ARRAY[[3000, 500, 2000], [4000, 1000, 6000], [100, 0, 65535]]
::double precision[][]) AS nir,
ST_SetValues(ST_AddBand(ST_MakeEmptyRaster(3, 3, 0.0, 3.0, 1.0, -1.0, 0.0, 0.0,
4326), '16BUI'::text, 0.0::float8, NULL::float8), 1, 1, 1,
ARRAY[[1000, 1500, 2000], [1000, 3000, 2000], [300, 1, 0]]
::double precision[][]) AS red )
SELECT ST_BandPixelType((nir - red) / (nir + red), 1),
ST_DumpValues((nir - red) / (nir + red), 1)
FROM bands;
-- 64BF | {{0.5,-0.5,0},{0.6,-0.5,0.5},{-0.5,-1,1}}
-- Read a derived raster along a trajectory: the depth under a vessel of draught 8.5
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 depth )
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]', depth - 8.5)::text
FROM rast;
-- Interp=Step;[1.5@2001-01-01 00:00:00+00, 11.5@2001-01-01 06:00:00+00,
-- 21.5@2001-01-01 18:00:00+00, 41.5@2001-01-02 06:00:00+00,
-- 61.5@2001-01-02 18:00:00+00, 61.5@2001-01-03 00:00:00+00]
Return the slope of a raster band, pixel by pixel
slope(rast raster,nband integer=1,pixeltype text='32BF',units text='DEGREES', scale float=1.0,interpolate_nodata boolean=false) → raster slope(rast raster,nband integer,customextent raster,pixeltype text='32BF', units text='DEGREES',scale float=1.0,interpolate_nodata boolean=false) → raster
The slope of PostGIS's ST_Slope, with its arguments and defaults: the gradients of each pixel are read from its eight neighbours, a neighbour that is nodata or lies outside the raster taking the value of the pixel, and a nodata pixel stays nodata. The units are DEGREES, RADIANS or PERCENT, read by their first three letters regardless of case, and units outside these raise an error; PostGIS answers radians in degrees. The scale is the ratio of the vertical units to the horizontal ones, such as 111120 for elevations in meters over a grid in degrees. Where interpolate_nodata is true, a nodata pixel first takes the mean of its neighbours weighted by the inverse of their distances, as PostGIS's ST_InvDistWeight4ma computes it. The result takes the grid of customextent where it is given.
-- A plane rising by 1 for each pixel to the east
WITH plane 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[[0, 1, 2], [0, 1, 2], [0, 1, 2]]::double precision[][]) AS r )
SELECT ST_Value(slope(r), 2, 2), ST_Value(slope(r, units => 'PERCENT'), 2, 2)
FROM plane;
-- 45 | 100
Return the terrain ruggedness index of a raster band, pixel by pixel
tri(rast raster,nband integer=1,pixeltype text='32BF',interpolate_nodata boolean=false) → raster tri(rast raster,nband integer,customextent raster,pixeltype text='32BF', interpolate_nodata boolean=false) → raster
The index of PostGIS's ST_TRI, with its arguments and defaults: the mean of the absolute differences between a pixel and its eight neighbours, read as slope reads them.
WITH dem AS (
SELECT ST_SetValues(ST_AddBand(ST_MakeEmptyRaster(4, 4, 0.0, 4.0, 1.0, -1.0, 0.0, 0.0,
3857), '32BF'::text, 0.0::float8, -9999.0::float8), 1, 1, 1,
ARRAY[[10, 20, 30, 40], [15, 25, -9999, 45], [20, 30, 40, 50],
[25, 35, 45, 55]]::double precision[][]) AS r )
SELECT ST_DumpValues(tri(r), 1) FROM dem;
-- {{3.75,3.75,5,1.875},{5,7.5,NULL,3.75},{5,8.125,8.125,3.125},{2.5,5.625,5.625,3.75}}
Return the topographic position index of a raster band, pixel by pixel
tpi(rast raster,nband integer=1,pixeltype text='32BF',interpolate_nodata boolean=false) → raster tpi(rast raster,nband integer,customextent raster,pixeltype text='32BF', interpolate_nodata boolean=false) → raster
The index of PostGIS's ST_TPI, with its arguments and defaults: the value of a pixel minus the mean of its eight neighbours, read as slope reads them.
WITH dem AS (
SELECT ST_SetValues(ST_AddBand(ST_MakeEmptyRaster(4, 4, 0.0, 4.0, 1.0, -1.0, 0.0, 0.0,
3857), '32BF'::text, 0.0::float8, -9999.0::float8), 1, 1, 1,
ARRAY[[10, 20, 30, 40], [15, 25, -9999, 45], [20, 30, 40, 50],
[25, 35, 45, 55]]::double precision[][]) AS r )
SELECT ST_DumpValues(tpi(r, interpolate_nodata => true), 1) FROM dem;
-- {{-3.75,-1.875,-1.875,1.25},{-3.75,0,0,3.75},{-3.75,0,0,3.75},{-1.25,1.875,1.875,3.75}}
Return a statistic of the neighbourhood of each pixel of a raster band
mapAlgebra(rast raster,nband integer,stat text,pixeltype text=NULL, extenttype text='FIRST',customextent raster=NULL,distancex integer=0, distancey integer=0) → raster
The focal statistics of PostGIS's ST_MapAlgebra with the callbacks ST_Min4ma, ST_Max4ma, ST_Sum4ma, ST_Mean4ma, ST_Range4ma and ST_StdDev4ma, with its arguments and defaults, the callback named by its statistic: MIN, MAX, SUM, MEAN, RANGE or STDDEV, regardless of case, as ST_Union names its operations. The neighbourhood of a pixel extends distancex pixels to the west and to the east and distancey pixels to the north and to the south; its nodata pixels, and those outside the raster, are skipped. The sum of no pixel is 0, every other statistic of no pixel is nodata, and the standard deviation is the sample one. As for PostGIS, a neighbourhood whose distance is zero along one axis only is the pixel alone. A pixel type left NULL is the one of the band, and the result keeps the nodata value of the band, or its minimum value where it states none. Every extent type but CUSTOM is the extent of the raster, and CUSTOM takes the grid of customextent. A statistic or an extent type outside these raises an error, where PostGIS reads an unknown extent type as INTERSECTION.
WITH dem AS (
SELECT ST_SetValues(ST_AddBand(ST_MakeEmptyRaster(4, 4, 0.0, 4.0, 1.0, -1.0, 0.0, 0.0,
3857), '32BF'::text, 0.0::float8, -9999.0::float8), 1, 1, 1,
ARRAY[[10, 20, 30, 40], [15, 25, -9999, 45], [20, 30, 40, 50],
[25, 35, 45, 55]]::double precision[][]) AS r )
SELECT ST_DumpValues(mapAlgebra(r, 1, 'MEAN', '64BF', distancex => 1, distancey => 1), 1)
FROM dem;
-- {{17.5,20,32,38.333333333333336},{20,23.75,35,41},{25,29.375,40.625,47},
-- {27.5,32.5,42.5,47.5}}
Return a raster keeping the pixels of another that a geometry covers
clip(raster,geom geometry,crop boolean=true,touched boolean=false) → raster clip(raster,nband integer[],geom geometry,nodataval float[]=NULL,crop boolean=true, touched boolean=false) → raster
Every band is read against a mask the geometry is burnt into, as the PostGIS ST_Clip reads it: a pixel the geometry does not cover answers the nodata value of its band, and the smallest value its pixel type holds when the band declares none. With crop the result carries the extent the raster and the geometry share, and otherwise the extent of the raster. The geometry is in the reference system of the raster; one in another system raises an error. With nband the result keeps the bands named, every band when it is NULL, nodataval states the nodata value of the pixels the geometry does not cover, one for every band or one per band, and with touched a pixel the geometry touches is kept where otherwise only one whose centre it covers is.
-- 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, -9999::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 ST_DumpValues(clip(r, geometry 'SRID=4326;POLYGON((0 1,2 1,2 3,0 3,0 1))'), 1)
AS cropped,
ST_DumpValues(clip(r, geometry 'SRID=4326;POLYGON((0 1,2 1,2 3,0 3,0 1))', false), 1)
AS kept_extent
FROM rast;
-- {{10,20},{40,50}} | {{10,20,NULL},{40,50,NULL},{NULL,NULL,NULL}}
Return a raster stated in another spatial reference system
transform(raster,srid integer,algorithm text='NearestNeighbour',maxerr float=0.125, scalex float=0,scaley float=0) → raster transform(raster,alignto raster,algorithm text='NearestNeighbour', maxerr float=0.125) → raster transformPipeline(raster,pipeline text,srid integer=0,is_forward boolean=true, algorithm text='NearestNeighbour',maxerr float=0.125,scalex float=0, scaley float=0) → raster
Every band is carried into the target system and resampled onto the grid the reprojection implies, through the same GDAL warp as the PostGIS ST_Transform, so the result states the same coverage read through another system. The algorithm is one of NearestNeighbour, Bilinear, Cubic, CubicSpline, Lanczos, Max and Min, named without regard to case, and maxerr is the error in input pixels the warp may commit, 0 for an exact calculation. scalex and scaley state the pixel size of the result, 0 letting the warp derive it, and alignto takes the reference system, the pixel size, the grid origin and the skew of another raster, so the result lies on its grid.
The transformPipeline function carries the raster through a coordinate operation, stated as a PROJ string, a WKT or an authority code such as urn:ogc:def:coordinateOperation:EPSG::16031, instead of the one GDAL derives from the two systems, and reads it as the geometry transformPipeline does: longitude first and in degrees. The result states the reference system given by srid, 0 by default. By default the operation is applied forward; setting is_forward to false applies its inverse. The remaining arguments are those of transform, so an operation GDAL would derive answers as transform to the same system.
-- 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, -9999::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 SRID(transform(r, 3857)) AS srid, width(transform(r, 3857)) AS width,
height(transform(r, 3857)) AS height
FROM rast;
-- 3857 | 3 | 3
-- 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, -9999::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 SRID(transformPipeline(r, '+proj=utm +zone=31 +ellps=WGS84', 32631)) AS srid,
width(transformPipeline(r, '+proj=utm +zone=31 +ellps=WGS84', 32631)) AS width,
height(transformPipeline(r, '+proj=utm +zone=31 +ellps=WGS84', 32631)) AS height
FROM rast;
-- 32631 | 3 | 3
Return a raster resampled to another pixel size
rescale(raster,scalex float,scaley float,algorithm text='NearestNeighbour', maxerr float=0.125) → raster
The result keeps the reference system and the upper left corner of the raster and states its coverage on a grid of the pixel size asked for, through the same GDAL warp as the PostGIS ST_Rescale, so a coarser scale reads fewer pixels over the same ground. The scales are in the units of the raster's reference system, and the algorithm and the error are those of transform.
-- 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, -9999::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 width(rescale(r, 0.5, -0.5)) AS width,
height(rescale(r, 0.5, -0.5)) AS height,
scaleX(rescale(r, 0.5, -0.5)) AS scalex
FROM rast;
-- 6 | 6 | 0.5
Return the statistics of the pixels of a raster band
summaryStats(raster,nband integer=1,exclude_nodata_value boolean=true) → summarystats
The band is read once and answers the PostGIS summarystats record that ST_SummaryStats answers: the number of pixels counted, and the sum, mean, standard deviation, minimum and maximum of their values. Every pixel is read, so the answer is exact rather than sampled; the pixels the band states as nodata are left out unless exclude_nodata_value is false, and a band with no pixel to count answers a count of zero and no statistic.
-- 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, -9999::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 (summaryStats(r)).* FROM rast;
-- 9 | 450 | 50 | 25.81988897471611 | 10 | 90
Return the statistics of the pixels of a band over the rasters of a column
summaryStatsAgg(raster,nband integer,exclude_nodata_value boolean) → summarystats summaryStatsAgg(raster,exclude_nodata_value boolean) → summarystats
The aggregate of PostGIS's ST_SummaryStatsAgg, answering the summarystats record that summaryStats answers for one raster over every pixel of the band of the rasters, the band 1 where nband is left out. Every pixel is read, as for summaryStats, so the forms take no sample percentage and the standard deviation is the one of every pixel rather than of a sample. A NULL raster is skipped, over no raster the aggregate answers NULL, and rasters with no pixel to count answer a count of zero and no statistic. The aggregate runs in parallel, the statistics of the workers combined, where PostGIS's has no combine function. A band a raster does not have raises an error, where PostGIS skips the raster.
WITH tiles AS (
SELECT ST_AddBand(ST_MakeEmptyRaster(2, 2, 0.0, 2.0, 1.0, -1.0, 0.0, 0.0, 3857),
'32BF'::text, 1.0::float8, -9999.0::float8) AS r
UNION ALL
SELECT ST_AddBand(ST_MakeEmptyRaster(2, 2, 1.0, 1.0, 1.0, -1.0, 0.0, 0.0, 3857),
'32BF'::text, 3.0::float8, -9999.0::float8) )
SELECT (summaryStatsAgg(r, 1, true)).* FROM tiles;
-- 8 | 16 | 2 | 1 | 1 | 3
Return the number of pixels, the sum, the mean, the standard deviation, the minimum or the maximum of the statistics of a raster band
summarystatsCount(summarystats) → bigint summarystatsSum(summarystats) → float summarystatsMean(summarystats) → float summarystatsStddev(summarystats) → float summarystatsMin(summarystats) → float summarystatsMax(summarystats) → float
Each function reads one field of the summarystats record that summaryStats and summaryStatsAgg return, the form in which every engine reads it (Chapter 18, Writing Portable Queries), beside the reading of a field by its name, (summaryStats(r)).mean, that PostgreSQL keeps. Over statistics that count no pixel the count is 0 and the other functions return NULL.
-- 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, -9999::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 summarystatsCount(summaryStats(r)), summarystatsMean(summaryStats(r)),
summarystatsStddev(summaryStats(r)) FROM rast;
-- 9 | 50 | 25.81988897471611
Return the polygons of a raster band, one for each group of pixels sharing a value
dumpAsPolygons(raster,band integer=1,exclude_nodata_value boolean=true) → {geomval}
A band states a value per pixel, and this states the same band as the regions those values cover, answering the PostGIS geomval records that ST_DumpAsPolygons answers. Each polygon carries the reference system of the raster, where ST_DumpAsPolygons states none, so it can be compared with the trajectories the coverage is read along. The pixels the band states as nodata are left out unless exclude_nodata_value is false, a band whose every pixel is nodata covers nothing and answers no row, and a band the raster does not have raises an error.
-- Classify a 3x3 synthetic elevation raster, then read each class as a region
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, -9999::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 val, ST_AsText(geom)
FROM rast, dumpAsPolygons(reclass(r, 1, '0-50:1, [50-100]:2', '8BUI', 0))
ORDER BY val;
-- 1 | POLYGON((0 3,0 1,1 1,1 2,3 2,3 3,0 3))
-- 2 | POLYGON((1 2,1 1,0 1,0 0,3 0,3 2,1 2))
Return the pixels of a raster band as points
pixelAsPoints(rast raster,band integer=1,exclude_nodata_value boolean=true) → setof (geom geometry,val float,x integer,y integer)
The points of PostGIS's ST_PixelAsPoints: for each pixel, row by row, its upper-left corner in the reference system of the raster, its value, and its column and row, starting at 1. The pixels the band states as nodata are left out unless exclude_nodata_value is false, in which case they keep the value the band holds for them, and a band stating no nodata value leaves none out. A band the raster does not have raises an error, where PostGIS answers no row.
WITH rast AS (
SELECT ST_SetValues(ST_AddBand(ST_MakeEmptyRaster(3, 2, 10.0, 20.0, 2.0, -1.0, 0.0, 0.0,
3857), '32BF'::text, 0.0::float8, -9999.0::float8), 1, 1, 1,
ARRAY[[1.5, -9999, 3], [4, 5, 6]]::double precision[][]) AS r )
SELECT ST_AsText(geom), val, x, y FROM rast, pixelAsPoints(r) LIMIT 2;
-- POINT(10 20) | 1.5 | 1 | 1
-- POINT(14 20) | 3 | 3 | 1
Return the values of the bands of a raster as arrays
dumpValues(rast raster,nband integer[]=NULL,exclude_nodata_value boolean=true) → setof (nband integer,valarray float[]) dumpValues(rast raster,nband integer,exclude_nodata_value boolean=true) → float[]
The values of PostGIS's ST_DumpValues: an array per band, a row of the array per row of the raster, in the order of the bands given in nband, every band when it is NULL; the form with one band answers its array alone. A pixel the band states as nodata is NULL unless exclude_nodata_value is false, in which case it is the value the band holds for it. A band the raster does not have, or a NULL band number, raises an error, where PostGIS answers no row or skips it.
WITH rast AS (
SELECT ST_SetValues(ST_AddBand(ST_MakeEmptyRaster(3, 2, 10.0, 20.0, 2.0, -1.0, 0.0, 0.0,
3857), '32BF'::text, 0.0::float8, -9999.0::float8), 1, 1, 1,
ARRAY[[1.5, -9999, 3], [4, 5, 6]]::double precision[][]) AS r )
SELECT dumpValues(r, 1), dumpValues(r, 1, false) FROM rast;
-- {{1.5,NULL,3},{4,5,6}} | {{1.5,-9999,3},{4,5,6}}
Sample a raquet raster tile along a trajectory
rasterTileValue(tgeompoint,rast raquet) → tfloat
Reads the tile along traj (SRID 4326) as rasterValue reads a raster, returning the pixel values as a tfloat. This is the typed equivalent of rasterTileValueQuadbin: the dimensions, QUADBIN cell, pixel type and nodata handling are taken from the raquet value instead of from separate arguments. Returns NULL when the trip never meets a pixel of the tile carrying data.
-- Sample pre-packaged raquet tiles along ship trajectories SELECT t.tripid, rasterTileValue(t.trip, r.tile) FROM trips t JOIN elevation_tiles r ON r.quadbin IN (SELECT unnest(getValues(tquadbin(t.trip, 8))));
Sample an array of raquet raster tiles along a trajectory
rasterTileValue(tgeompoint,rast raquet[]) → tfloat
Reads every tile of rast along traj (SRID 4326) and returns the pixel values as a single tfloat. A trajectory that leaves one tile is covered by several of them, each answering the part of the trip over it. Tiles of one zoom level partition the plane, so they answer disjoint times. Tiles of different zoom levels overlap, and where two of them answer the same time the value of the tile of higher zoom is kept, that being the one carrying the finer resolution. Returns NULL when the trip never meets a pixel of a tile carrying data.
-- Sample a trajectory across every tile it crosses, in one value SELECT t.tripid, rasterTileValue(t.trip, array_agg(r.tile)) FROM trips t JOIN elevation_tiles r ON r.quadbin IN (SELECT unnest(getValues(tquadbin(t.trip, 8)))) GROUP BY t.tripid, t.trip;
Sample a Raquet raster chip along a trajectory using a QUADBIN cell for georeferencing
rasterTileValueQuadbin(tgeompoint,pixels bytea,width integer,height integer, quadbin,pixtype text,nodata float,has_nodata boolean) → tfloat
Reads a row-major single-band pixel array along traj (SRID 4326), as rasterValue reads a raster. The tile georeferencing (bounding box, pixel-to-coordinate mapping) is derived from quadbin alone via the Web-Mercator slippy-tile transform. The pixtype argument must be one of uint8, int8, uint16, int16, uint32, int32, uint64, int64, float16, float32, or float64. When has_nodata is true, a pixel equal to nodata carries no value. Returns NULL when the trip never meets a pixel carrying data.
-- Sample the 256 x 256 float32 elevation tiles of a Raquet table, stating no -- nodata value, along every ship trajectory SELECT t.tripid, rasterTileValueQuadbin(t.trip, r.band_1, 256, 256, r.block::quadbin, 'float32', 0, false) FROM trips t JOIN elevation_raquet r ON r.block IN (SELECT unnest(getValues(tquadbin(t.trip, 8)))::bigint); -- Average sea-surface temperature per trajectory SELECT t.tripid, avgValue(rasterTileValueQuadbin(t.trip, r.band_1, 256, 256, r.block::quadbin, 'int16', -9999, true)) AS mean_sst FROM trips t JOIN sst_raquet r ON r.block IN (SELECT unnest(getValues(tquadbin(t.trip, 6)))::bigint);
Return the part of a trajectory where the raster value it reads falls inside a float range
atRasterValue(tgeompoint,rast raster,vspan floatspan,band integer=1) → tgeompoint atRasterValue(tgeompoint,path text,vspan floatspan,band integer=1) → tgeompoint
Reads rasterValue along traj and returns the sub-trajectory restricted to the times when the pixel value lies within vspan. Returns NULL when no time satisfies the condition. The form taking path reads a raster file on the server, as the corresponding form of rasterValue does.
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 asText(atRasterValue(tgeompoint 'SRID=4326;
[POINT(0.5 2.5)@2001-01-01, POINT(1.5 1.5)@2001-01-02, POINT(0.5 0.5)@2001-01-03]', r,
floatspan '[40, 90]')) FROM rast;
/* {[POINT(1 2)@2001-01-01 12:00:00, POINT(1.5 1.5)@2001-01-02,
POINT(0.5 0.5)@2001-01-03]} */
Return the part of a trajectory where the raster value it reads falls outside a float range
minusRasterValue(tgeompoint,rast raster,vspan floatspan,band integer=1) → tgeompoint minusRasterValue(tgeompoint,path text,vspan floatspan,band integer=1) → tgeompoint
The complement of atRasterValue: returns the sub-trajectory restricted to the times when the pixel value lies outside vspan. Returns NULL when the pixel value lies within vspan at every time. The form taking path reads a raster file on the server, as the corresponding form of rasterValue does.
-- Keep only the instant whose raster value (10) is below 40 SELECT asText(minusRasterValue(traj, elev_raster, floatspan '[40, 9999]')) FROM trips, elevation_raster;
Return true if the trajectory ever reads a raster pixel value inside a float range
eRasterValue(tgeompoint,rast raster,vspan floatspan,band integer=1) → boolean eRasterValue(tgeompoint,path text,vspan floatspan,band integer=1) → boolean
Returns true when traj ever reads a pixel value within vspan, false otherwise. Returns NULL only when the input is NULL. The form taking path reads a raster file on the server, as the corresponding form of rasterValue does.
-- Trips that ever crossed terrain above 200 m SELECT tripid FROM trips, elevation_raster WHERE eRasterValue(trip, elev_raster, floatspan '[200, 9999]');
Return true if every pixel value the trajectory reads over the raster lies inside a float range
aRasterValue(tgeompoint,rast raster,vspan floatspan,band integer=1) → boolean aRasterValue(tgeompoint,path text,vspan floatspan,band integer=1) → boolean
Returns true when every pixel value traj reads over the raster lies within vspan, false when at least one does not. Returns NULL only when the input is NULL. The form taking path reads a raster file on the server, as the corresponding form of rasterValue does.
-- Trips that stayed entirely below 100 m elevation SELECT tripid FROM trips, elevation_raster WHERE aRasterValue(trip, elev_raster, floatspan '[0, 100]');
To use the operators described here, MobilityDB must be built with -DQUADBIN=ON and -DRASTER=ON, since a Raquet tile is identified by a quadbin cell. 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"
PostGIS raster is part of the standard postgresql- package on Debian/Ubuntu; no extra package is required. Pass N-postgis-3-DQUADBIN=ON and -DRASTER=ON to CMake:
cmake -DQUADBIN=ON -DRASTER=ON .. make -j$(nproc) sudo make install
The CI builds, .github/workflows/pgversion.yml among them, pass -DALL=ON, which includes raster alongside the other optional families.