[SCM] PostGIS branch master updated. 3.6.0rc2-635-gea6edb5d5
git at osgeo.org
git at osgeo.org
Sat Jun 20 12:53:39 PDT 2026
This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "PostGIS".
The branch, master has been updated
via ea6edb5d5a44804bd0e3d2d05d76b220134fae1f (commit)
from 47224001d6fdd5e6443a6db1114419fd5b7d5d81 (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit ea6edb5d5a44804bd0e3d2d05d76b220134fae1f
Author: Darafei Praliaskouski <me at komzpa.net>
Date: Sat Jun 20 23:50:49 2026 +0400
raster: preserve missing NODATA in map algebra
ST_MapAlgebra no longer injects an output NODATA value when the selected input band has none. Expression and callback variants now raise an error if they produce NULL without an output NODATA value available.
Keep ST_Grayscale explicit about its own NODATA contract so later RGB-band NODATA values still become output NODATA pixels even when the first band has no NODATA value.
Closes #2807
Closes https://github.com/postgis/postgis/pull/1056
diff --git a/NEWS b/NEWS
index 395493674..e84f23d2a 100644
--- a/NEWS
+++ b/NEWS
@@ -74,6 +74,8 @@ To take advantage of all postgis_sfcgal extension features SFCGAL 2.3+ is needed
* Bug Fixes *
+ - #2807, [raster] Preserve missing NODATA values in ST_MapAlgebra outputs
+ (Darafei Praliaskouski)
- #2832, [raster] Avoid padding single-tile raster2pgsql overviews
when padding is not requested (Darafei Praliaskouski)
- Fix WKB and TWKB parser resource exhaustion on malformed input
diff --git a/raster/rt_pg/rtpg_mapalgebra.c b/raster/rt_pg/rtpg_mapalgebra.c
index f2109a30d..36aad8f02 100644
--- a/raster/rt_pg/rtpg_mapalgebra.c
+++ b/raster/rt_pg/rtpg_mapalgebra.c
@@ -88,6 +88,7 @@ typedef struct {
Oid ufc_noid;
Oid ufc_rettype;
FmgrInfo ufl_info;
+ int hasnodata;
/* copied from LOCAL_FCINFO in fmgr.h */
union {
FunctionCallInfoBaseData fcinfo;
@@ -159,6 +160,7 @@ static rtpg_nmapalgebra_arg rtpg_nmapalgebra_arg_init(void) {
arg->callback.ufc_noid = InvalidOid;
arg->callback.ufc_rettype = InvalidOid;
arg->callback.empty_userargs = NULL;
+ arg->callback.hasnodata = 1;
return arg;
}
@@ -518,8 +520,13 @@ static int rtpg_nmapalgebra_callback(
break;
}
}
- else
+ else if (callback->hasnodata)
*nodata = 1;
+ else
+ {
+ elog(ERROR, "RASTER_nMapAlgebra: Callback returned NULL but output raster has no NODATA value");
+ return 0;
+ }
return 1;
}
@@ -929,8 +936,9 @@ Datum RASTER_nMapAlgebra(PG_FUNCTION_ARGS)
arg->pixtype = rt_band_get_pixtype(band);
/* set hasnodata and nodataval */
- arg->hasnodata = 1;
- if (rt_band_get_hasnodata_flag(band))
+ arg->hasnodata = rt_band_get_hasnodata_flag(band);
+ arg->callback.hasnodata = arg->hasnodata;
+ if (arg->hasnodata)
rt_band_get_nodata(band, &(arg->nodataval));
else
arg->nodataval = rt_band_get_min_value(band);
@@ -1009,6 +1017,8 @@ typedef struct {
double val;
} nodatanodata;
+ int hasnodata;
+
struct {
int count;
char **val;
@@ -1058,6 +1068,7 @@ static rtpg_nmapalgebraexpr_arg rtpg_nmapalgebraexpr_arg_init(int cnt, char **kw
arg->callback.nodatanodata.hasval = 0;
arg->callback.nodatanodata.val = 0;
+ arg->callback.hasnodata = 1;
return arg;
}
@@ -1122,13 +1133,15 @@ static int rtpg_nmapalgebraexpr_callback(
*nodata = 1;
}
/* expression */
- else {
+ else
+ {
id = 0;
if (callback->expr[id].hasval)
*value = callback->expr[id].val;
else if (callback->expr[id].spi_plan)
plan = callback->expr[id].spi_plan;
- else {
+ else
+ {
if (callback->nodatanodata.hasval)
*value = callback->nodatanodata.val;
else
@@ -1301,6 +1314,12 @@ static int rtpg_nmapalgebraexpr_callback(
if (SPI_tuptable) SPI_freetuptable(tuptable);
}
+ if (*nodata && !callback->hasnodata)
+ {
+ elog(ERROR, "RASTER_nMapAlgebraExpr: Expression returned NULL but output raster has no NODATA value");
+ return 0;
+ }
+
POSTGIS_RT_DEBUGF(4, "(value, nodata) = (%f, %d)", *value, *nodata);
return 1;
}
@@ -1622,8 +1641,9 @@ Datum RASTER_nMapAlgebraExpr(PG_FUNCTION_ARGS)
arg->bandarg->pixtype = rt_band_get_pixtype(band);
/* set hasnodata and nodataval */
- arg->bandarg->hasnodata = 1;
- if (rt_band_get_hasnodata_flag(band))
+ arg->bandarg->hasnodata = rt_band_get_hasnodata_flag(band);
+ arg->callback.hasnodata = arg->bandarg->hasnodata;
+ if (arg->bandarg->hasnodata)
rt_band_get_nodata(band, &(arg->bandarg->nodataval));
else
arg->bandarg->nodataval = rt_band_get_min_value(band);
diff --git a/raster/rt_pg/rtpostgis.sql.in b/raster/rt_pg/rtpostgis.sql.in
index 5a1bfc085..96bb55ed8 100644
--- a/raster/rt_pg/rtpostgis.sql.in
+++ b/raster/rt_pg/rtpostgis.sql.in
@@ -4429,6 +4429,13 @@ CREATE OR REPLACE FUNCTION _st_grayscale4ma(value double precision[][][], pos in
green := _value[2][1][1];
blue := _value[3][1][1];
+ -- ST_Grayscale uses 255 as its output NODATA sentinel. Returning the
+ -- sentinel here lets the wrapper mark it as NODATA only when one of
+ -- the source RGB bands has a NODATA value.
+ IF red IS NULL OR green IS NULL OR blue IS NULL THEN
+ RETURN 255;
+ END IF;
+
gray = round(0.2989 * red + 0.5870 * green + 0.1140 * blue);
RETURN gray;
@@ -4459,6 +4466,8 @@ CREATE OR REPLACE FUNCTION st_grayscale(
nodata double precision;
nodataval integer;
reclassexpr text;
+ hasnodata boolean DEFAULT FALSE;
+ grayscale @extschema at .raster;
BEGIN
@@ -4483,11 +4492,16 @@ CREATE OR REPLACE FUNCTION st_grayscale(
RAISE EXCEPTION 'Band at index ''%'' not found for raster ''%''', nband, idx;
- -- check that each band is 8BUI. if not, reclassify to 8BUI
- ELSIF @extschema at .ST_BandPixelType(rast, nband) != _PIXTYPE THEN
+ END IF;
+ nodata := @extschema at .ST_BandNoDataValue(rast, nband);
+ IF nodata IS NOT NULL THEN
+ hasnodata := TRUE;
+ END IF;
+
+ -- check that each band is 8BUI. if not, reclassify to 8BUI
+ IF @extschema at .ST_BandPixelType(rast, nband) != _PIXTYPE THEN
stats := @extschema at .ST_SummaryStats(rast, nband);
- nodata := @extschema at .ST_BandNoDataValue(rast, nband);
IF nodata IS NOT NULL THEN
nodataval := _NODATA;
@@ -4513,13 +4527,19 @@ CREATE OR REPLACE FUNCTION st_grayscale(
END LOOP;
-- call map algebra with _st_grayscale4ma
- RETURN @extschema at .ST_MapAlgebra(
+ grayscale := @extschema at .ST_MapAlgebra(
_set,
'@extschema at ._ST_Grayscale4MA(double precision[][][], integer[][], text[])'::regprocedure,
'8BUI',
extenttype
);
+ IF hasnodata THEN
+ grayscale := @extschema at .ST_SetBandNoDataValue(grayscale, 1, _NODATA);
+ END IF;
+
+ RETURN grayscale;
+
END;
$$ LANGUAGE 'plpgsql' IMMUTABLE PARALLEL SAFE;
diff --git a/raster/test/regress/rt_grayscale.sql b/raster/test/regress/rt_grayscale.sql
index b85b95c67..d65e7547e 100644
--- a/raster/test/regress/rt_grayscale.sql
+++ b/raster/test/regress/rt_grayscale.sql
@@ -125,6 +125,41 @@ SELECT
FROM raster_grayscale_out
ORDER BY 1, 2, nband;
+-- #2807: ST_Grayscale is implemented through ST_MapAlgebra. Source rasters 1,
+-- 2, and 5 have no NODATA value, so zero-valued pixels in the dump above are
+-- real pixel values. Source rasters 3 and 4 have explicit NODATA values, so
+-- their matching output pixels remain NULL.
+SELECT
+ '#2807.grayscale',
+ testid,
+ rid,
+ (ST_BandMetadata(rast, 1)).nodatavalue
+FROM raster_grayscale_out
+WHERE rid IN (1, 3, 5)
+ORDER BY testid, rid;
+
+-- The first RGB band controls generic n-raster MapAlgebra output metadata.
+-- ST_Grayscale still needs to preserve NODATA from later RGB bands when the
+-- first band has no NODATA value.
+WITH mixed AS (
+ SELECT ST_Grayscale(ARRAY[
+ ROW(red.rast, 1)::rastbandarg,
+ ROW(green.rast, 1)::rastbandarg,
+ ROW(blue.rast, 1)::rastbandarg
+ ]::rastbandarg[]) AS rast
+ FROM raster_grayscale_in red
+ CROSS JOIN raster_grayscale_in green
+ CROSS JOIN raster_grayscale_in blue
+ WHERE red.rid = 1
+ AND green.rid = 3
+ AND blue.rid = 3
+)
+SELECT
+ '#2807.grayscale.mixed',
+ (ST_BandMetadata(rast, 1)).nodatavalue,
+ (ST_DumpValues(rast)).valarray
+FROM mixed;
+
-- error because of insufficient bands
BEGIN;
SELECT
diff --git a/raster/test/regress/rt_grayscale_expected b/raster/test/regress/rt_grayscale_expected
index 22e121d27..6ab88ba52 100644
--- a/raster/test/regress/rt_grayscale_expected
+++ b/raster/test/regress/rt_grayscale_expected
@@ -5,21 +5,31 @@ WARNING: Only the first three elements of 'rastbandargset' will be used
WARNING: Only the first three elements of 'rastbandargset' will be used
WARNING: Only the first three elements of 'rastbandargset' will be used
WARNING: Only the first three elements of 'rastbandargset' will be used
-1|1|1|{{NULL,128},{254,255}}
-1|2|1|{{NULL,128},{254,255}}
+1|1|1|{{0,128},{254,255}}
+1|2|1|{{0,128},{254,255}}
1|3|1|{{NULL,0},{254,254}}
1|4|1|{{NULL,0},{254,254}}
-1|5|1|{{NULL,NULL},{128,128},{255,255}}
-2|1|1|{{NULL,128},{254,255}}
-2|2|1|{{NULL,128},{254,255}}
+1|5|1|{{0,0},{128,128},{255,255}}
+2|1|1|{{0,128},{254,255}}
+2|2|1|{{0,128},{254,255}}
2|3|1|{{NULL,0},{254,254}}
2|4|1|{{NULL,0},{254,254}}
-2|5|1|{{NULL,NULL},{128,128},{255,255}}
-3|1|1|{{NULL,128},{254,255}}
-3|2|1|{{NULL,128},{254,255}}
+2|5|1|{{0,0},{128,128},{255,255}}
+3|1|1|{{0,128},{254,255}}
+3|2|1|{{0,128},{254,255}}
3|3|1|{{NULL,0},{254,254}}
3|4|1|{{NULL,0},{254,254}}
-3|5|1|{{NULL,NULL},{128,128},{255,255}}
+3|5|1|{{0,0},{128,128},{255,255}}
+#2807.grayscale|1|1|
+#2807.grayscale|1|3|255
+#2807.grayscale|1|5|
+#2807.grayscale|2|1|
+#2807.grayscale|2|3|255
+#2807.grayscale|2|5|
+#2807.grayscale|3|1|
+#2807.grayscale|3|3|255
+#2807.grayscale|3|5|
+#2807.grayscale.mixed|255|{{NULL,38},{254,254}}
ERROR: 'rastbandargset' must have three bands for red, green and blue
ERROR: Band at index '2' not found for raster '2'
ERROR: Band at index '2' not found for raster '2'
diff --git a/raster/test/regress/rt_mapalgebra.sql b/raster/test/regress/rt_mapalgebra.sql
index a3ad1e527..879db364a 100644
--- a/raster/test/regress/rt_mapalgebra.sql
+++ b/raster/test/regress/rt_mapalgebra.sql
@@ -588,6 +588,85 @@ SELECT
FROM raster_nmapalgebra_in
WHERE rid IN (2);
+-- Ticket #2807
+-- https://trac.osgeo.org/postgis/ticket/2807
+CREATE OR REPLACE FUNCTION raster_nmapalgebra_one(
+ value double precision[][][],
+ pos int[][],
+ VARIADIC userargs text[]
+)
+ RETURNS double precision
+ AS $$ SELECT 1::double precision $$
+ LANGUAGE 'sql' IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION raster_nmapalgebra_null(
+ value double precision[][][],
+ pos int[][],
+ VARIADIC userargs text[]
+)
+ RETURNS double precision
+ AS $$ SELECT NULL::double precision $$
+ LANGUAGE 'sql' IMMUTABLE;
+
+SET client_min_messages TO warning;
+
+WITH src AS (
+ SELECT ST_AddBand(
+ ST_MakeEmptyRaster(2, 2, 0, 0, 1),
+ '8BUI', 255.0
+ ) AS rast
+)
+SELECT
+ '#2807.expr',
+ (ST_BandMetadata(ST_MapAlgebra(rast, 1, '8BUI', '0'), 1)),
+ ST_Value(ST_MapAlgebra(rast, 1, '8BUI', '0'), 1, 1)
+FROM src;
+
+WITH src AS (
+ SELECT ST_AddBand(
+ ST_MakeEmptyRaster(2, 2, 0, 0, 1),
+ '8BUI', 255.0
+ ) AS rast
+)
+SELECT
+ '#2807.callback',
+ (ST_BandMetadata(ST_MapAlgebra(
+ rast, 1,
+ 'raster_nmapalgebra_one(double precision[], int[], text[])'::regprocedure
+ ), 1)),
+ ST_Value(ST_MapAlgebra(
+ rast, 1,
+ 'raster_nmapalgebra_one(double precision[], int[], text[])'::regprocedure
+ ), 1, 1)
+FROM src;
+
+WITH src AS (
+ SELECT ST_AddBand(
+ ST_MakeEmptyRaster(2, 2, 0, 0, 1),
+ '8BUI', 255.0
+ ) AS rast
+)
+SELECT
+ '#2807.expr.null',
+ (ST_BandMetadata(ST_MapAlgebra(rast, 1, '8BUI', 'NULL'), 1))
+FROM src;
+
+WITH src AS (
+ SELECT ST_AddBand(
+ ST_MakeEmptyRaster(2, 2, 0, 0, 1),
+ '8BUI', 255.0
+ ) AS rast
+)
+SELECT
+ '#2807.callback.null',
+ (ST_BandMetadata(ST_MapAlgebra(
+ rast, 1,
+ 'raster_nmapalgebra_null(double precision[], int[], text[])'::regprocedure
+ ), 1))
+FROM src;
+
+SET client_min_messages TO notice;
+
-- Ticket #2802
-- http://trac.osgeo.org/postgis/ticket/2802
CREATE OR REPLACE FUNCTION raster_nmapalgebra_test_bad_return(
@@ -614,6 +693,8 @@ SELECT
FROM raster_nmapalgebra_in
WHERE rid IN (2);
+DROP FUNCTION IF EXISTS raster_nmapalgebra_one(double precision[], int[], text[]);
+DROP FUNCTION IF EXISTS raster_nmapalgebra_null(double precision[], int[], text[]);
DROP FUNCTION IF EXISTS raster_nmapalgebra_test(double precision[], int[], text[]);
DROP FUNCTION IF EXISTS raster_nmapalgebra_test_no_userargs(double precision[], int[]);
DROP FUNCTION IF EXISTS raster_nmapalgebra_test_bad_return(double precision[], int[], text[]);
diff --git a/raster/test/regress/rt_mapalgebra_expected b/raster/test/regress/rt_mapalgebra_expected
index 407d368be..ed0092046 100644
--- a/raster/test/regress/rt_mapalgebra_expected
+++ b/raster/test/regress/rt_mapalgebra_expected
@@ -353,4 +353,8 @@ NOTICE: value = {{{1}}}
NOTICE: pos = [0:1][1:2]={{2,2},{2,2}}
NOTICE: userargs = {}
2|
+#2807.expr|(8BUI,,f,,,,)|0
+#2807.callback|(8BUI,,f,,,,)|1
+ERROR: RASTER_nMapAlgebraExpr: Expression returned NULL but output raster has no NODATA value
+ERROR: RASTER_nMapAlgebra: Callback returned NULL but output raster has no NODATA value
ERROR: RASTER_nMapAlgebra: Function provided must return a double precision, float, int or smallint
-----------------------------------------------------------------------
Summary of changes:
NEWS | 2 +
raster/rt_pg/rtpg_mapalgebra.c | 34 ++++++++++---
raster/rt_pg/rtpostgis.sql.in | 28 +++++++++--
raster/test/regress/rt_grayscale.sql | 35 +++++++++++++
raster/test/regress/rt_grayscale_expected | 28 +++++++----
raster/test/regress/rt_mapalgebra.sql | 81 ++++++++++++++++++++++++++++++
raster/test/regress/rt_mapalgebra_expected | 4 ++
7 files changed, 192 insertions(+), 20 deletions(-)
hooks/post-receive
--
PostGIS
More information about the postgis-tickets
mailing list