[SCM] PostGIS branch master updated. 3.6.0rc2-647-ga0b3925cf

git at osgeo.org git at osgeo.org
Sun Jun 21 10:43:45 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  a0b3925cf59cea943ac9bd1d2130692a559ff64f (commit)
      from  2774c1f8dc0960efaf1c23cf2f514f11cff8adef (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 a0b3925cf59cea943ac9bd1d2130692a559ff64f
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Sun Jun 21 21:06:58 2026 +0400

    Reuse source overviews in raster2pgsql
    
    When raster2pgsql builds overview tables with -l, reuse a GDAL source overview whose dimensions match the requested output overview level for every selected band. Fall back to the existing generated overview path when any band has no matching source overview.
    
    Includes a loader regression whose source GeoTIFF carries an averaged 2x overview, so expected output distinguishes source-overview reuse from nearest-neighbor regeneration.
    
    Closes #2137
    
    Closes https://github.com/postgis/postgis/pull/1092

diff --git a/NEWS b/NEWS
index 199261ee1..d0f6e212d 100644
--- a/NEWS
+++ b/NEWS
@@ -74,6 +74,8 @@ To take advantage of all postgis_sfcgal extension features SFCGAL 2.3+ is needed
  - #3743, #4385, [raster loader] Document raster2pgsql -s FROM_SRID:SRID
           reprojection support and clarify index creation timing for append loads
           (Darafei Praliaskouski)
+ - #2137, [raster] Reuse matching source raster overviews in raster2pgsql -l
+          (Darafei Praliaskouski)
  - #4749, Use point-in-polygon predicate fast paths for point-only
           GeometryCollections (Darafei Praliaskouski)
  - #5532, Validate the manual against DocBook XMLSchema in check-xml
diff --git a/doc/man/raster2pgsql.1 b/doc/man/raster2pgsql.1
index 2789b2174..2b84aab44 100644
--- a/doc/man/raster2pgsql.1
+++ b/doc/man/raster2pgsql.1
@@ -85,7 +85,8 @@ Specify the filename column name. This implies \-F.
 \fB\-l\fR, \fB\-\-overview\-factor\fR <\fIoverview_factor\fR>
 Create overview tables for the raster. Separate multiple factors with commas.
 Overview table names follow the pattern o_<overview factor>_<table>. Created
-overviews are stored in the database and are not affected by \-R.
+overviews are stored in the database and are not affected by \-R. If a source
+raster already has an overview with matching dimensions, that overview is reused.
 .TP
 \fB\-q\fR, \fB\-\-quote\fR
 Wrap PostgreSQL identifiers in quotes.
diff --git a/doc/using_raster_dataman.xml b/doc/using_raster_dataman.xml
index 97b60ab87..5ef7dd89b 100644
--- a/doc/using_raster_dataman.xml
+++ b/doc/using_raster_dataman.xml
@@ -266,7 +266,7 @@ Available GDAL raster formats:
                     <term><option>-l, --overview-factor OVERVIEW_FACTOR</option></term>
 										<listitem><para>Create overview of the raster.  For more than
      one factor, separate with comma(,).  Overview table name follows
-		 the pattern o_<varname>overview factor</varname>_<varname>table</varname>, where <varname>overview factor</varname> is a placeholder for numerical overview factor and <varname>table</varname> is replaced with the base table name.  Created overview is
+			 the pattern o_<varname>overview factor</varname>_<varname>table</varname>, where <varname>overview factor</varname> is a placeholder for numerical overview factor and <varname>table</varname> is replaced with the base table name. If a source raster already has an overview with matching dimensions, that overview is reused. Created overview is
      stored in the database and is not affected by -R. Note that your generated sql file will contain both the main table and overview tables.</para>
                     </listitem>
                 </varlistentry>
diff --git a/raster/loader/raster2pgsql.c b/raster/loader/raster2pgsql.c
index 442511ae8..5551c2f5b 100644
--- a/raster/loader/raster2pgsql.c
+++ b/raster/loader/raster2pgsql.c
@@ -1439,6 +1439,47 @@ add_overview_constraints(
 	return 1;
 }
 
+static GDALRasterBandH
+get_matching_overview_band(GDALRasterBandH hbandSrc, const int dimOv[2])
+{
+	int ovcount = GDALGetOverviewCount(hbandSrc);
+	int i = 0;
+
+	for (i = 0; i < ovcount; i++)
+	{
+		GDALRasterBandH hbandOv = GDALGetOverview(hbandSrc, i);
+
+		if (hbandOv != NULL && GDALGetRasterBandXSize(hbandOv) == dimOv[0] &&
+		    GDALGetRasterBandYSize(hbandOv) == dimOv[1])
+		{
+			return hbandOv;
+		}
+	}
+
+	return NULL;
+}
+
+static int
+source_has_matching_overviews(GDALDatasetH hdsSrc, RASTERINFO *info, const int dimOv[2])
+{
+	uint32_t j = 0;
+
+	/*
+	 * Reuse source overviews only when every selected band has an overview
+	 * with the exact dimensions raster2pgsql will load. Mixed source/generated
+	 * bands would make a single output overview depend on two resampling paths.
+	 */
+	for (j = 0; j < info->nband_count; j++)
+	{
+		GDALRasterBandH hbandSrc = GDALGetRasterBand(hdsSrc, info->nband[j]);
+
+		if (hbandSrc == NULL || get_matching_overview_band(hbandSrc, dimOv) == NULL)
+			return LW_FALSE;
+	}
+
+	return LW_TRUE;
+}
+
 static int
 build_overview(int idx, RTLOADERCFG *config, RASTERINFO *info, uint32_t ovx, STRINGBUFFER *tileset, STRINGBUFFER *buffer) {
 	GDALDatasetH hdsSrc;
@@ -1450,6 +1491,7 @@ build_overview(int idx, RTLOADERCFG *config, RASTERINFO *info, uint32_t ovx, STR
 	uint32_t j = 0;
 	int factor;
 	const char *ovtable = NULL;
+	int use_source_overview = LW_FALSE;
 
 	VRTDatasetH hdsDst;
 	VRTSourcedRasterBandH hbandDst;
@@ -1488,6 +1530,12 @@ build_overview(int idx, RTLOADERCFG *config, RASTERINFO *info, uint32_t ovx, STR
 
 	dimOv[0] = (int) (info->dim[0] + (factor / 2)) / factor;
 	dimOv[1] = (int) (info->dim[1] + (factor / 2)) / factor;
+	/*
+	 * Match by dimensions instead of overview index or nominal factor. GDAL
+	 * datasets can carry differently ordered overview levels, while the loader
+	 * needs the level that exactly matches the target overview table size.
+	 */
+	use_source_overview = source_has_matching_overviews(hdsSrc, info, dimOv);
 
 	/* create VRT dataset */
 	hdsOv = VRTCreate(dimOv[0], dimOv[1]);
@@ -1504,20 +1552,39 @@ build_overview(int idx, RTLOADERCFG *config, RASTERINFO *info, uint32_t ovx, STR
 
 	/* add bands as simple sources */
 	for (j = 0; j < info->nband_count; j++) {
+		GDALRasterBandH hbandSrc = GDALGetRasterBand(hdsSrc, info->nband[j]);
+		int sourceDim[2] = {info->dim[0], info->dim[1]};
+
+		if (use_source_overview)
+		{
+			/*
+			 * The selected source is already reduced to dimOv, so VRT should
+			 * copy it 1:1. Otherwise it reads the full-resolution band and
+			 * resamples down into the target overview dimensions.
+			 */
+			hbandSrc = get_matching_overview_band(hbandSrc, dimOv);
+			sourceDim[0] = dimOv[0];
+			sourceDim[1] = dimOv[1];
+		}
+
 		GDALAddBand(hdsOv, info->gdalbandtype[j], NULL);
 		hbandOv = (VRTSourcedRasterBandH) GDALGetRasterBand(hdsOv, j + 1);
 
 		if (info->hasnodata[j])
 			GDALSetRasterNoDataValue(hbandOv, info->nodataval[j]);
 
-		VRTAddSimpleSource(
-			hbandOv, GDALGetRasterBand(hdsSrc, info->nband[j]),
-			0, 0,
-			info->dim[0], info->dim[1],
-			0, 0,
-			dimOv[0], dimOv[1],
-			"near", VRT_NODATA_UNSET
-		);
+		VRTAddSimpleSource(hbandOv,
+				   hbandSrc,
+				   0,
+				   0,
+				   sourceDim[0],
+				   sourceDim[1],
+				   0,
+				   0,
+				   dimOv[0],
+				   dimOv[1],
+				   "near",
+				   VRT_NODATA_UNSET);
 	}
 
 	/* make sure VRT reflects all changes */
diff --git a/raster/test/regress/loader/SourceOverview-post.pl b/raster/test/regress/loader/SourceOverview-post.pl
new file mode 100644
index 000000000..dc1a681da
--- /dev/null
+++ b/raster/test/regress/loader/SourceOverview-post.pl
@@ -0,0 +1,3 @@
+use File::Basename qw(dirname);
+
+unlink dirname($TEST) . '/SourceOverview-generated.tif';
diff --git a/raster/test/regress/loader/SourceOverview-post.sql b/raster/test/regress/loader/SourceOverview-post.sql
new file mode 100644
index 000000000..0ea666a10
--- /dev/null
+++ b/raster/test/regress/loader/SourceOverview-post.sql
@@ -0,0 +1 @@
+DROP TABLE o_2_loadedrast;
diff --git a/raster/test/regress/loader/SourceOverview-pre.pl b/raster/test/regress/loader/SourceOverview-pre.pl
new file mode 100644
index 000000000..872c55207
--- /dev/null
+++ b/raster/test/regress/loader/SourceOverview-pre.pl
@@ -0,0 +1,31 @@
+use File::Basename qw(dirname);
+
+my $dir = dirname($TEST);
+my $asc = "$dir/SourceOverview.asc";
+my $tif = "$dir/SourceOverview-generated.tif";
+
+# The source raster values are deliberately sequential so the averaged GDAL
+# overview has different corner pixels than a nearest-neighbor overview generated
+# by raster2pgsql from the full-resolution band.
+open(my $fh, '>', $asc) or die "Could not create $asc: $!";
+print $fh "ncols 8\n";
+print $fh "nrows 8\n";
+print $fh "xllcorner 0\n";
+print $fh "yllcorner 0\n";
+print $fh "cellsize 1\n";
+print $fh "NODATA_value -9999\n";
+for my $y (0..7) {
+	print $fh join(' ', map { $y * 8 + $_ } 1..8), "\n";
+}
+close($fh);
+
+system(
+	'gdal_translate', '-q', '-of', 'GTiff', '-ot', 'Byte',
+	'-a_ullr', '0', '0', '8', '-8',
+	$asc, $tif
+) == 0 or die "Could not create $tif";
+
+system('gdaladdo', '-q', '-r', 'average', $tif, '2') == 0
+	or die "Could not create overview for $tif";
+
+unlink $asc;
diff --git a/raster/test/regress/loader/SourceOverview.opts b/raster/test/regress/loader/SourceOverview.opts
new file mode 100644
index 000000000..613cec066
--- /dev/null
+++ b/raster/test/regress/loader/SourceOverview.opts
@@ -0,0 +1 @@
+-l 2
diff --git a/raster/test/regress/loader/SourceOverview.select.expected b/raster/test/regress/loader/SourceOverview.select.expected
new file mode 100644
index 000000000..346fe7a64
--- /dev/null
+++ b/raster/test/regress/loader/SourceOverview.select.expected
@@ -0,0 +1,2 @@
+8|8|1|2|9|10
+4|4|6|8|22|24
diff --git a/raster/test/regress/loader/SourceOverview.select.sql b/raster/test/regress/loader/SourceOverview.select.sql
new file mode 100644
index 000000000..3de2a4f88
--- /dev/null
+++ b/raster/test/regress/loader/SourceOverview.select.sql
@@ -0,0 +1,17 @@
+-- Base table keeps the full-resolution source pixels.
+SELECT ST_Width(rast), ST_Height(rast),
+	ST_Value(rast, 1, 1, 1),
+	ST_Value(rast, 1, 2, 1),
+	ST_Value(rast, 1, 1, 2),
+	ST_Value(rast, 1, 2, 2)
+FROM loadedrast;
+
+-- The overview table should reuse the source's averaged 2x overview. These
+-- corner values would be 1, 3, 17, 19 if raster2pgsql regenerated it from the
+-- base band with nearest-neighbor sampling.
+SELECT ST_Width(rast), ST_Height(rast),
+	ST_Value(rast, 1, 1, 1),
+	ST_Value(rast, 1, 2, 1),
+	ST_Value(rast, 1, 1, 2),
+	ST_Value(rast, 1, 2, 2)
+FROM o_2_loadedrast;
diff --git a/raster/test/regress/loader/SourceOverview.tif.ref b/raster/test/regress/loader/SourceOverview.tif.ref
new file mode 100644
index 000000000..c9fe00c65
--- /dev/null
+++ b/raster/test/regress/loader/SourceOverview.tif.ref
@@ -0,0 +1 @@
+SourceOverview-generated.tif
diff --git a/raster/test/regress/tests.mk.in b/raster/test/regress/tests.mk.in
index 7bc6972e6..03ae943ea 100644
--- a/raster/test/regress/tests.mk.in
+++ b/raster/test/regress/tests.mk.in
@@ -131,6 +131,7 @@ RASTER_TEST_LOADER = \
 	$(top_srcdir)/raster/test/regress/loader/Basic \
 	$(top_srcdir)/raster/test/regress/loader/Projected \
 	$(top_srcdir)/raster/test/regress/loader/OverviewNoPadding \
+	$(top_srcdir)/raster/test/regress/loader/SourceOverview \
 	$(top_srcdir)/raster/test/regress/loader/IfNotExists \
 	$(top_srcdir)/raster/test/regress/loader/LongOptions \
 	$(top_srcdir)/raster/test/regress/loader/LongMaintenance \

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

Summary of changes:
 NEWS                                               |  2 +
 doc/man/raster2pgsql.1                             |  3 +-
 doc/using_raster_dataman.xml                       |  2 +-
 raster/loader/raster2pgsql.c                       | 83 +++++++++++++++++++---
 raster/test/regress/loader/SourceOverview-post.pl  |  3 +
 raster/test/regress/loader/SourceOverview-post.sql |  1 +
 raster/test/regress/loader/SourceOverview-pre.pl   | 31 ++++++++
 raster/test/regress/loader/SourceOverview.opts     |  1 +
 .../regress/loader/SourceOverview.select.expected  |  2 +
 .../test/regress/loader/SourceOverview.select.sql  | 17 +++++
 raster/test/regress/loader/SourceOverview.tif.ref  |  1 +
 raster/test/regress/tests.mk.in                    |  1 +
 12 files changed, 137 insertions(+), 10 deletions(-)
 create mode 100644 raster/test/regress/loader/SourceOverview-post.pl
 create mode 100644 raster/test/regress/loader/SourceOverview-post.sql
 create mode 100644 raster/test/regress/loader/SourceOverview-pre.pl
 create mode 100644 raster/test/regress/loader/SourceOverview.opts
 create mode 100644 raster/test/regress/loader/SourceOverview.select.expected
 create mode 100644 raster/test/regress/loader/SourceOverview.select.sql
 create mode 100644 raster/test/regress/loader/SourceOverview.tif.ref


hooks/post-receive
-- 
PostGIS


More information about the postgis-tickets mailing list