Skip to content

Commit d178b03

Browse files
committed
gdaladdo: reuse previous resampling method (from GTiff RESAMPLING metadata item) if not specifying -r and overview levels if not specifying them
1 parent 1c73ebe commit d178b03

File tree

3 files changed

+147
-28
lines changed

3 files changed

+147
-28
lines changed

apps/gdaladdo.cpp

Lines changed: 81 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -635,10 +635,9 @@ MAIN_START(nArgc, papszArgv)
635635
if (nArgc < 1)
636636
exit(-nArgc);
637637

638-
const char *pszResampling = "nearest";
638+
std::string osResampling;
639639
const char *pszFilename = nullptr;
640-
int anLevels[1024] = {};
641-
int nLevelCount = 0;
640+
std::vector<int> anLevels;
642641
int nResultStatus = 0;
643642
bool bReadOnly = false;
644643
bool bClean = false;
@@ -679,7 +678,7 @@ MAIN_START(nArgc, papszArgv)
679678
else if (EQUAL(papszArgv[iArg], "-r"))
680679
{
681680
CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1);
682-
pszResampling = papszArgv[++iArg];
681+
osResampling = papszArgv[++iArg];
683682
}
684683
else if (EQUAL(papszArgv[iArg], "-ro"))
685684
{
@@ -752,11 +751,10 @@ MAIN_START(nArgc, papszArgv)
752751
{
753752
pszFilename = papszArgv[iArg];
754753
}
755-
else if (atoi(papszArgv[iArg]) > 0 &&
756-
static_cast<size_t>(nLevelCount) < CPL_ARRAYSIZE(anLevels))
754+
else if (atoi(papszArgv[iArg]) > 0)
757755
{
758-
anLevels[nLevelCount++] = atoi(papszArgv[iArg]);
759-
if (anLevels[nLevelCount - 1] == 1)
756+
anLevels.push_back(atoi(papszArgv[iArg]));
757+
if (anLevels.back() == 1)
760758
{
761759
printf(
762760
"Warning: Overview with subsampling factor of 1 requested. "
@@ -813,24 +811,54 @@ MAIN_START(nArgc, papszArgv)
813811
if (hDataset == nullptr)
814812
exit(2);
815813

814+
if (!bClean && osResampling.empty())
815+
{
816+
auto poDS = GDALDataset::FromHandle(hDataset);
817+
if (poDS->GetRasterCount() > 0)
818+
{
819+
auto poBand = poDS->GetRasterBand(1);
820+
if (poBand->GetOverviewCount() > 0)
821+
{
822+
const char *pszResampling =
823+
poBand->GetOverview(0)->GetMetadataItem("RESAMPLING");
824+
if (pszResampling)
825+
{
826+
osResampling = pszResampling;
827+
if (pfnProgress == GDALDummyProgress)
828+
CPLDebug("GDAL",
829+
"Reusing resampling method %s from existing "
830+
"overview",
831+
pszResampling);
832+
else
833+
printf("Info: reusing resampling method %s from "
834+
"existing overview.\n",
835+
pszResampling);
836+
}
837+
}
838+
}
839+
if (osResampling.empty())
840+
osResampling = "nearest";
841+
}
842+
816843
/* -------------------------------------------------------------------- */
817844
/* Clean overviews. */
818845
/* -------------------------------------------------------------------- */
819846
if (bClean)
820847
{
821-
if (GDALBuildOverviews(hDataset, pszResampling, 0, nullptr, 0, nullptr,
848+
if (GDALBuildOverviews(hDataset, "NONE", 0, nullptr, 0, nullptr,
822849
pfnProgress, pProgressArg) != CE_None)
823850
{
824-
printf("Cleaning overviews failed.\n");
851+
fprintf(stderr, "Cleaning overviews failed.\n");
825852
nResultStatus = 200;
826853
}
827854
}
828855
else if (bPartialRefreshFromSourceTimestamp)
829856
{
830857
if (!PartialRefreshFromSourceTimestamp(
831-
GDALDataset::FromHandle(hDataset), pszResampling, nLevelCount,
832-
anLevels, nBandCount, panBandList, bMinSizeSpecified, nMinSize,
833-
pfnProgress, pProgressArg))
858+
GDALDataset::FromHandle(hDataset), osResampling.c_str(),
859+
static_cast<int>(anLevels.size()), anLevels.data(), nBandCount,
860+
panBandList, bMinSizeSpecified, nMinSize, pfnProgress,
861+
pProgressArg))
834862
{
835863
nResultStatus = 1;
836864
}
@@ -839,18 +867,20 @@ MAIN_START(nArgc, papszArgv)
839867
{
840868
if (!PartialRefreshFromProjWin(
841869
GDALDataset::FromHandle(hDataset), dfULX, dfULY, dfLRX, dfLRY,
842-
pszResampling, nLevelCount, anLevels, nBandCount, panBandList,
843-
bMinSizeSpecified, nMinSize, pfnProgress, pProgressArg))
870+
osResampling.c_str(), static_cast<int>(anLevels.size()),
871+
anLevels.data(), nBandCount, panBandList, bMinSizeSpecified,
872+
nMinSize, pfnProgress, pProgressArg))
844873
{
845874
nResultStatus = 1;
846875
}
847876
}
848877
else if (bPartialRefreshFromSourceExtent)
849878
{
850879
if (!PartialRefreshFromSourceExtent(
851-
GDALDataset::FromHandle(hDataset), aosSources, pszResampling,
852-
nLevelCount, anLevels, nBandCount, panBandList,
853-
bMinSizeSpecified, nMinSize, pfnProgress, pProgressArg))
880+
GDALDataset::FromHandle(hDataset), aosSources,
881+
osResampling.c_str(), static_cast<int>(anLevels.size()),
882+
anLevels.data(), nBandCount, panBandList, bMinSizeSpecified,
883+
nMinSize, pfnProgress, pProgressArg))
854884
{
855885
nResultStatus = 1;
856886
}
@@ -863,7 +893,32 @@ MAIN_START(nArgc, papszArgv)
863893
/* --------------------------------------------------------------------
864894
*/
865895

866-
if (nLevelCount == 0)
896+
// If no levels are specified, reuse the potentially existing ones.
897+
if (anLevels.empty())
898+
{
899+
auto poDS = GDALDataset::FromHandle(hDataset);
900+
if (poDS->GetRasterCount() > 0)
901+
{
902+
auto poBand = poDS->GetRasterBand(1);
903+
const int nExistingCount = poBand->GetOverviewCount();
904+
if (nExistingCount > 0)
905+
{
906+
for (int iOvr = 0; iOvr < nExistingCount; ++iOvr)
907+
{
908+
auto poOverview = poBand->GetOverview(iOvr);
909+
if (poOverview)
910+
{
911+
const int nOvFactor = GDALComputeOvFactor(
912+
poOverview->GetXSize(), poBand->GetXSize(),
913+
poOverview->GetYSize(), poBand->GetYSize());
914+
anLevels.push_back(nOvFactor);
915+
}
916+
}
917+
}
918+
}
919+
}
920+
921+
if (anLevels.empty())
867922
{
868923
const int nXSize = GDALGetRasterXSize(hDataset);
869924
const int nYSize = GDALGetRasterYSize(hDataset);
@@ -872,20 +927,21 @@ MAIN_START(nArgc, papszArgv)
872927
DIV_ROUND_UP(nYSize, nOvrFactor) > nMinSize)
873928
{
874929
nOvrFactor *= 2;
875-
anLevels[nLevelCount++] = nOvrFactor;
930+
anLevels.push_back(nOvrFactor);
876931
}
877932
}
878933

879934
// Only HFA supports selected layers
880935
if (nBandCount > 0)
881936
CPLSetConfigOption("USE_RRD", "YES");
882937

883-
if (nLevelCount > 0 &&
884-
GDALBuildOverviews(hDataset, pszResampling, nLevelCount, anLevels,
885-
nBandCount, panBandList, pfnProgress,
886-
pProgressArg) != CE_None)
938+
if (!anLevels.empty() &&
939+
GDALBuildOverviews(hDataset, osResampling.c_str(),
940+
static_cast<int>(anLevels.size()),
941+
anLevels.data(), nBandCount, panBandList,
942+
pfnProgress, pProgressArg) != CE_None)
887943
{
888-
printf("Overview building failed.\n");
944+
fprintf(stderr, "Overview building failed.\n");
889945
nResultStatus = 100;
890946
}
891947
}

autotest/utilities/test_gdaladdo.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,3 +324,54 @@ def test_gdaladdo_partial_refresh_from_source_extent(gdaladdo_path, tmp_path):
324324
ovr_data_refreshed[idx] = ovr_data_ori[idx]
325325
assert ovr_data_refreshed == ovr_data_ori
326326
ds = None
327+
328+
329+
###############################################################################
330+
# Test reuse of previous resampling method and overview levels
331+
332+
333+
@pytest.mark.parametrize("read_only", [True, False])
334+
def test_gdaladdo_reuse_previous_resampling_and_levels(
335+
gdaladdo_path, tmp_path, read_only
336+
):
337+
338+
tmpfilename = str(tmp_path / "test.tif")
339+
340+
gdal.Translate(tmpfilename, "../gcore/data/byte.tif")
341+
342+
out, err = gdaltest.runexternal_out_and_err(
343+
f"{gdaladdo_path} -r average {tmpfilename}"
344+
+ (" -ro" if read_only else "")
345+
+ " 2 4"
346+
)
347+
assert "ERROR" not in err, (out, err)
348+
349+
ds = gdal.Open(tmpfilename)
350+
assert ds.GetRasterBand(1).GetOverview(0).GetMetadataItem("RESAMPLING") == "AVERAGE"
351+
assert ds.GetRasterBand(1).GetOverview(0).Checksum() == 1152
352+
ds = None
353+
354+
# Change resampling method to CUBIC
355+
out, err = gdaltest.runexternal_out_and_err(
356+
f"{gdaladdo_path} -r cubic {tmpfilename}" + (" -ro" if read_only else "")
357+
)
358+
assert "ERROR" not in err, (out, err)
359+
360+
ds = gdal.Open(tmpfilename, gdal.GA_Update)
361+
assert ds.GetRasterBand(1).GetOverview(0).GetMetadataItem("RESAMPLING") == "CUBIC"
362+
assert ds.GetRasterBand(1).GetOverview(0).Checksum() == 1059
363+
# Zeroize overview
364+
ds.GetRasterBand(1).GetOverview(0).Fill(0)
365+
ds = None
366+
367+
# Invoke gdaladdo without arguments and check overviews are regenerated
368+
# using CUBIC
369+
out, err = gdaltest.runexternal_out_and_err(
370+
f"{gdaladdo_path} {tmpfilename}" + (" -ro" if read_only else "")
371+
)
372+
assert "ERROR" not in err, (out, err)
373+
374+
ds = gdal.Open(tmpfilename)
375+
assert ds.GetRasterBand(1).GetOverview(0).GetMetadataItem("RESAMPLING") == "CUBIC"
376+
assert ds.GetRasterBand(1).GetOverview(0).Checksum() == 1059
377+
ds = None

doc/source/programs/gdaladdo.rst

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,16 @@ most supported file formats with one of several downsampling algorithms.
3535

3636
.. option:: -r {nearest|average|rms|gauss|cubic|cubicspline|lanczos|average_magphase|mode}
3737

38-
Select a resampling algorithm.
38+
Select a resampling algorithm. The default is ``nearest``, which is generally not
39+
appropriate if sub-pixel accuracy is desired.
3940

40-
``nearest`` applies a nearest neighbour (simple sampling) resampler (default)
41+
Starting with GDAL 3.9, when refreshing existing TIFF overviews, the previously
42+
used method, as noted in the RESAMPLING metadata item of the overview, will
43+
be used if :option:`-r` is not specified.
44+
45+
The available methods are:
46+
47+
``nearest`` applies a nearest neighbour (simple sampling) resampler.
4148

4249
``average`` computes the average of all non-NODATA contributing pixels. Starting with GDAL 3.1, this is a weighted average taking into account properly the weight of source pixels not contributing fully to the target pixel.
4350

@@ -131,10 +138,15 @@ most supported file formats with one of several downsampling algorithms.
131138

132139
.. versionadded:: 2.3
133140

134-
levels are no longer required to build overviews.
141+
Levels are no longer required to build overviews.
135142
In which case, appropriate overview power-of-two factors will be selected
136143
until the smallest overview is smaller than the value of the -minsize switch.
137144

145+
Starting with GDAL 3.9, if there are already existing overviews, the
146+
corresponding levels will be used to refresh them if no explicit levels
147+
are specified.
148+
149+
138150
gdaladdo will honour properly NODATA_VALUES tuples (special dataset metadata) so
139151
that only a given RGB triplet (in case of a RGB image) will be considered as the
140152
nodata value and not each value of the triplet independently per band.

0 commit comments

Comments
 (0)