[SCM] PostGIS branch master updated. 3.6.0rc2-634-g47224001d

git at osgeo.org git at osgeo.org
Sat Jun 20 12:33:56 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  47224001d6fdd5e6443a6db1114419fd5b7d5d81 (commit)
      from  6ce331f4e2bfd2054f56ba09c43907621259bffc (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 47224001d6fdd5e6443a6db1114419fd5b7d5d81
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Sat Jun 20 22:45:48 2026 +0400

    Add ST_Value boundary resample modes
    
    Add nearest-neighbor boundary resample modes for ST_Value so callers can choose upper-left, upper-right, lower-left, or lower-right pixels when a point lies on a pixel edge or corner.
    
    Snap near-integer raster coordinates before applying boundary rounding, and reject non-exact resample names instead of accepting prefixes.
    
    Closes #2116
    
    Closes https://github.com/postgis/postgis/pull/1039

diff --git a/NEWS b/NEWS
index e668c7a62..395493674 100644
--- a/NEWS
+++ b/NEWS
@@ -54,6 +54,8 @@ To take advantage of all postgis_sfcgal extension features SFCGAL 2.3+ is needed
 
 * Enhancements *
 
+ - #2116, [raster] Add ST_Value nearest-neighbor boundary options
+          (Darafei Praliaskouski)
  - #2804, #4315, [raster] Support two-argument ST_MapAlgebra callbacks
           and pass callback call data as actual arguments (Darafei Praliaskouski)
  - #2898, Document SQL function cost tiers for contributors
diff --git a/doc/reference_raster.xml b/doc/reference_raster.xml
index 04d059ffc..00206e352 100644
--- a/doc/reference_raster.xml
+++ b/doc/reference_raster.xml
@@ -4815,8 +4815,9 @@ SELECT x, y, val, ST_AsText(geom)
 
                 <para>Returns the value of a given band in a given columnx, rowy pixel or at a given geometry point. Band numbers start at 1 and band is assumed to be 1 if not specified.</para>
                 <para>If <varname>exclude_nodata_value</varname> is set to true, then only non <varname>nodata</varname> pixels are considered.  If <varname>exclude_nodata_value</varname> is set to false, then all pixels are considered.</para>
-                <para>The allowed values of the <varname>resample</varname> parameter are "nearest" which performs the default nearest-neighbor resampling, and "bilinear" which performs a <link xlink:href="https://en.wikipedia.org/wiki/Bilinear_interpolation">bilinear interpolation</link> to estimate the value between pixel centers.</para>
+                <para>The allowed values of the <varname>resample</varname> parameter are "nearest" which performs the default nearest-neighbor resampling, "bilinear" which performs a <link xlink:href="https://en.wikipedia.org/wiki/Bilinear_interpolation">bilinear interpolation</link> to estimate the value between pixel centers, and the nearest-neighbor boundary options "nearest-UL", "nearest-UR", "nearest-LL", and "nearest-LR". Boundary options choose which pixel is returned when the point lies on a horizontal or vertical pixel boundary, including corner intersections.</para>
 
+                <para role="enhanced" conformance="3.7.0">Enhanced: 3.7.0 resample accepts nearest-neighbor boundary options "nearest-UL", "nearest-UR", "nearest-LL", and "nearest-LR".</para>
                 <para role="enhanced" conformance="3.2.0">Enhanced: 3.2.0 resample optional argument was added.</para>
                 <para role="enhanced" conformance="2.0.0">Enhanced: 2.0.0 exclude_nodata_value optional argument was added.</para>
                 </refsection>
diff --git a/raster/rt_core/librtcore.h b/raster/rt_core/librtcore.h
index 618d1292d..97cc83bff 100644
--- a/raster/rt_core/librtcore.h
+++ b/raster/rt_core/librtcore.h
@@ -1475,8 +1475,13 @@ rt_errorstate rt_band_get_pixel_bilinear(
 	double *r_value, int *r_nodata
 );
 
-typedef enum {
+typedef enum
+{
 	RT_NEAREST,
+	RT_NEAREST_UL,
+	RT_NEAREST_UR,
+	RT_NEAREST_LL,
+	RT_NEAREST_LR,
 	RT_BILINEAR
 } rt_resample_type;
 
@@ -1487,7 +1492,7 @@ typedef enum {
  * @param band : the band to read for values
  * @param xr : x unrounded raster coordinate
  * @param yr : y unrounded raster coordinate
- * @param resample : algorithm for reading raster (nearest or bilinear)
+ * @param resample : algorithm for reading raster
  * @param r_value : return pointer for point value
  * @param r_nodata : return pointer for if this is a nodata
  *
diff --git a/raster/rt_core/rt_band.c b/raster/rt_core/rt_band.c
index 0fb95d1f8..cace34bfc 100644
--- a/raster/rt_core/rt_band.c
+++ b/raster/rt_core/rt_band.c
@@ -1386,6 +1386,18 @@ rt_errorstate rt_band_get_pixel_line(
 	return ES_NONE;
 }
 
+static double
+rt_band_snap_pixel_coordinate(double coordinate)
+{
+	double nearest = round(coordinate);
+	double tolerance = DBL_EPSILON * fmax(fabs(coordinate), 1.0) * 16.0;
+
+	if (fabs(coordinate - nearest) <= tolerance)
+		return nearest;
+
+	return coordinate;
+}
+
 /**
  * Retrieve a point value from the raster using a world coordinate
  * and selected interpolation.
@@ -1411,11 +1423,25 @@ rt_band_get_pixel_resample(
 			band, xr, yr, r_value, r_nodata
 			);
 	}
-	else if (resample == RT_NEAREST) {
-		return rt_band_get_pixel(
-			band, floor(xr), floor(yr),
-			r_value, r_nodata
-			);
+	else if (resample == RT_NEAREST || resample == RT_NEAREST_UL || resample == RT_NEAREST_UR ||
+		 resample == RT_NEAREST_LL || resample == RT_NEAREST_LR)
+	{
+		int x, y;
+
+		xr = rt_band_snap_pixel_coordinate(xr);
+		yr = rt_band_snap_pixel_coordinate(yr);
+
+		if (resample == RT_NEAREST_UL || resample == RT_NEAREST_LL)
+			x = ceil(xr) - 1;
+		else
+			x = floor(xr);
+
+		if (resample == RT_NEAREST_UL || resample == RT_NEAREST_UR)
+			y = ceil(yr) - 1;
+		else
+			y = floor(yr);
+
+		return rt_band_get_pixel(band, x, y, r_value, r_nodata);
 	}
 	else {
 		rtwarn("Invalid resample type requested %d", resample);
diff --git a/raster/rt_pg/rtpg_pixel.c b/raster/rt_pg/rtpg_pixel.c
index 9aa65ebea..3791353f3 100644
--- a/raster/rt_pg/rtpg_pixel.c
+++ b/raster/rt_pg/rtpg_pixel.c
@@ -141,16 +141,27 @@ Datum RASTER_getPixelValue(PG_FUNCTION_ARGS)
 
 static rt_resample_type resample_text_to_type(text *txt)
 {
+	rt_resample_type resample_type = RT_NEAREST;
 	char *resample = asc_tolower(VARDATA(txt), VARSIZE_ANY_EXHDR(txt));
-	if (strncmp(resample, "bilinear", 8) == 0)
-		return RT_BILINEAR;
-	else if (strncmp(resample, "nearest", 7) == 0)
-		return RT_NEAREST;
+
+	if (strcmp(resample, "bilinear") == 0)
+		resample_type = RT_BILINEAR;
+	else if (strcmp(resample, "nearest-ul") == 0)
+		resample_type = RT_NEAREST_UL;
+	else if (strcmp(resample, "nearest-ur") == 0)
+		resample_type = RT_NEAREST_UR;
+	else if (strcmp(resample, "nearest-ll") == 0)
+		resample_type = RT_NEAREST_LL;
+	else if (strcmp(resample, "nearest-lr") == 0)
+		resample_type = RT_NEAREST_LR;
+	else if (strcmp(resample, "nearest") == 0)
+		resample_type = RT_NEAREST;
 	else {
 		elog(ERROR, "Unknown resample type '%s' requested", resample);
 	}
+
 	pfree(resample);
-	return RT_NEAREST;
+	return resample_type;
 }
 
 /*
@@ -2506,4 +2517,3 @@ Datum RASTER_neighborhood(PG_FUNCTION_ARGS)
 
 	PG_RETURN_ARRAYTYPE_P(mdArray);
 }
-
diff --git a/raster/test/regress/rt_pixelvalue.sql b/raster/test/regress/rt_pixelvalue.sql
index 2842e47d4..00071db9a 100644
--- a/raster/test/regress/rt_pixelvalue.sql
+++ b/raster/test/regress/rt_pixelvalue.sql
@@ -367,6 +367,57 @@ round(ST_Value(rast, 1, 'SRID=4326;POINT(0.5 0.5)'::geometry, resample => 'neare
 round(ST_Value(rast, 1, 'SRID=4326;POINT(1.0 1.0)'::geometry, resample => 'bilinear')) as nearest_10_10,
 round(ST_Value(rast, 1, 'SRID=4326;POINT(1.0 0.1)'::geometry, resample => 'bilinear')) as nearest_10_00,
 round(ST_Value(rast, 1, 'SRID=4326;POINT(1.0 1.9)'::geometry, resample => 'bilinear')) as nearest_10_20
-FROM r
+FROM r;
 
+WITH r AS (
+SELECT
+ST_SetValues(
+  ST_AddBand(
+    ST_MakeEmptyRaster(width => 2, height => 2,
+      upperleftx => 0, upperlefty => 2,
+      scalex => 1.0, scaley => -1.0,
+      skewx => 0, skewy => 0, srid => 4326),
+    index => 1, pixeltype => '16BSI',
+    initialvalue => 0,
+    nodataval => -999),
+  1,1,1,
+  newvalueset =>ARRAY[ARRAY[10.0::float8, 50.0::float8], ARRAY[40.0::float8, 20.0::float8]]) AS rast
+)
+SELECT
+'#2116',
+round(ST_Value(rast, 1, 'SRID=4326;POINT(1.0 1.0)'::geometry, resample => 'nearest-ul')) as nearest_ul,
+round(ST_Value(rast, 1, 'SRID=4326;POINT(1.0 1.0)'::geometry, resample => 'nearest-ur')) as nearest_ur,
+round(ST_Value(rast, 1, 'SRID=4326;POINT(1.0 1.0)'::geometry, resample => 'nearest-ll')) as nearest_ll,
+round(ST_Value(rast, 1, 'SRID=4326;POINT(1.0 1.0)'::geometry, resample => 'nearest-lr')) as nearest_lr,
+round(ST_Value(rast, 1, 'SRID=4326;POINT(1.0 1.0)'::geometry, resample => 'nearest')) as nearest_default
+FROM r;
 
+WITH r AS (
+SELECT
+ST_SetValues(
+  ST_AddBand(
+    ST_MakeEmptyRaster(width => 2, height => 2,
+      upperleftx => 0, upperlefty => 2,
+      scalex => 1.0, scaley => -1.0,
+      skewx => 0, skewy => 0, srid => 4326),
+    index => 1, pixeltype => '16BSI',
+    initialvalue => 0,
+    nodataval => -999),
+  1,1,1,
+  newvalueset =>ARRAY[ARRAY[10.0::float8, 50.0::float8], ARRAY[40.0::float8, 20.0::float8]]) AS rast
+)
+SELECT
+'#2116.snap',
+round(ST_Value(rast, 1, 'SRID=4326;POINT(1.0000000000000004 0.9999999999999996)'::geometry, resample => 'nearest-ul')) as nearest_ul,
+round(ST_Value(rast, 1, 'SRID=4326;POINT(0.9999999999999997 1.0000000000000002)'::geometry, resample => 'nearest-lr')) as nearest_lr
+FROM r;
+
+WITH r AS (
+SELECT ST_AddBand(ST_MakeEmptyRaster(1, 1, 0, 1, 1, -1, 0, 0, 4326), '16BSI'::text, 1, -999) AS rast
+)
+SELECT ST_Value(rast, 1, 'SRID=4326;POINT(0.5 0.5)'::geometry, resample => 'nearest-foo') FROM r;
+
+WITH r AS (
+SELECT ST_AddBand(ST_MakeEmptyRaster(1, 1, 0, 1, 1, -1, 0, 0, 4326), '16BSI'::text, 1, -999) AS rast
+)
+SELECT ST_Value(rast, 1, 'SRID=4326;POINT(0.5 0.5)'::geometry, resample => 'bilinearXYZ') FROM r;
diff --git a/raster/test/regress/rt_pixelvalue_expected b/raster/test/regress/rt_pixelvalue_expected
index 7576caaff..989a306b4 100644
--- a/raster/test/regress/rt_pixelvalue_expected
+++ b/raster/test/regress/rt_pixelvalue_expected
@@ -11,3 +11,7 @@ NOTICE:  Raster do not have a nodata value defined. Set band nodata value first.
 NOTICE:  Raster do not have a nodata value defined. Set band nodata value first. Nodata value not set. Returning original raster
 NOTICE:  Raster do not have a nodata value defined. Set band nodata value first. Nodata value not set. Returning original raster
 Test 5|50|40|30|26|38
+#2116|10|50|40|20|20
+#2116.snap|10|20
+ERROR:  Unknown resample type 'nearest-foo' requested
+ERROR:  Unknown resample type 'bilinearxyz' requested

-----------------------------------------------------------------------

Summary of changes:
 NEWS                                       |  2 ++
 doc/reference_raster.xml                   |  3 +-
 raster/rt_core/librtcore.h                 |  9 +++--
 raster/rt_core/rt_band.c                   | 36 +++++++++++++++++---
 raster/rt_pg/rtpg_pixel.c                  | 22 +++++++++----
 raster/test/regress/rt_pixelvalue.sql      | 53 +++++++++++++++++++++++++++++-
 raster/test/regress/rt_pixelvalue_expected |  4 +++
 7 files changed, 114 insertions(+), 15 deletions(-)


hooks/post-receive
-- 
PostGIS


More information about the postgis-tickets mailing list