From 4f4c0a78dfc9d291ce941f9737a3ffbc15aed107 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 27 Jan 2025 16:06:51 +0100 Subject: [PATCH 1/3] gdal raster mosaic/stack: turn gdalbuildvrt -strict mode --- apps/gdalalg_raster_mosaic.cpp | 5 + apps/gdalalg_raster_stack.cpp | 5 + apps/gdalbuildvrt_lib.cpp | 128 +++++++++++------- .../utilities/test_gdalalg_raster_mosaic.py | 22 ++- 4 files changed, 108 insertions(+), 52 deletions(-) diff --git a/apps/gdalalg_raster_mosaic.cpp b/apps/gdalalg_raster_mosaic.cpp index 53e388fe15d7..8c8eae01f187 100644 --- a/apps/gdalalg_raster_mosaic.cpp +++ b/apps/gdalalg_raster_mosaic.cpp @@ -185,6 +185,11 @@ bool GDALRasterMosaicAlgorithm::RunImpl(GDALProgressFunc pfnProgress, "VRT"); CPLStringList aosOptions; + aosOptions.push_back("-strict"); + + aosOptions.push_back("-program_name"); + aosOptions.push_back("gdal raster mosaic"); + if (!m_resolution.empty()) { const auto aosTokens = diff --git a/apps/gdalalg_raster_stack.cpp b/apps/gdalalg_raster_stack.cpp index 6d8d0552ad6e..41292a2b6775 100644 --- a/apps/gdalalg_raster_stack.cpp +++ b/apps/gdalalg_raster_stack.cpp @@ -181,6 +181,11 @@ bool GDALRasterStackAlgorithm::RunImpl(GDALProgressFunc pfnProgress, CPLStringList aosOptions; + aosOptions.push_back("-strict"); + + aosOptions.push_back("-program_name"); + aosOptions.push_back("gdal raster stack"); + aosOptions.push_back("-separate"); if (!m_resolution.empty()) diff --git a/apps/gdalbuildvrt_lib.cpp b/apps/gdalbuildvrt_lib.cpp index febb54b74096..43441e18c052 100644 --- a/apps/gdalbuildvrt_lib.cpp +++ b/apps/gdalbuildvrt_lib.cpp @@ -290,6 +290,8 @@ class VRTBuilder ~VRTBuilder(); GDALDataset *Build(GDALProgressFunc pfnProgress, void *pProgressData); + + std::string m_osProgramName{}; }; /************************************************************************/ @@ -514,6 +516,10 @@ std::string VRTBuilder::AnalyseRaster(GDALDatasetH hDS, int bGotGeoTransform = poDS->GetGeoTransform(padfGeoTransform) == CE_None; if (bSeparate) { + std::string osProgramName(m_osProgramName); + if (osProgramName == "gdalbuildvrt") + osProgramName += " -separate"; + if (bFirst) { bHasGeoTransform = bGotGeoTransform; @@ -521,35 +527,37 @@ std::string VRTBuilder::AnalyseRaster(GDALDatasetH hDS, { if (bUserExtent) { - CPLError(CE_Warning, CPLE_NotSupported, - "User extent ignored by gdalbuildvrt -separate " - "with ungeoreferenced images."); + CPLError(CE_Warning, CPLE_NotSupported, "%s", + ("User extent ignored by " + osProgramName + + "with ungeoreferenced images.") + .c_str()); } if (resolutionStrategy == USER_RESOLUTION) { - CPLError(CE_Warning, CPLE_NotSupported, - "User resolution ignored by gdalbuildvrt " - "-separate with ungeoreferenced images."); + CPLError(CE_Warning, CPLE_NotSupported, "%s", + ("User resolution ignored by " + osProgramName + + " with ungeoreferenced images.") + .c_str()); } } } else if (bHasGeoTransform != bGotGeoTransform) { - return "gdalbuildvrt -separate cannot stack ungeoreferenced and " - "georeferenced images."; + return osProgramName + " cannot stack ungeoreferenced and " + "georeferenced images."; } else if (!bHasGeoTransform && (nRasterXSize != poDS->GetRasterXSize() || nRasterYSize != poDS->GetRasterYSize())) { - return "gdalbuildvrt -separate cannot stack ungeoreferenced images " - "that have not the same dimensions."; + return osProgramName + " cannot stack ungeoreferenced images " + "that have not the same dimensions."; } } else { if (!bGotGeoTransform) { - return "gdalbuildvrt does not support ungeoreferenced image."; + return m_osProgramName + " does not support ungeoreferenced image."; } bHasGeoTransform = TRUE; } @@ -559,11 +567,13 @@ std::string VRTBuilder::AnalyseRaster(GDALDatasetH hDS, if (padfGeoTransform[GEOTRSFRM_ROTATION_PARAM1] != 0 || padfGeoTransform[GEOTRSFRM_ROTATION_PARAM2] != 0) { - return "gdalbuildvrt does not support rotated geo transforms."; + return m_osProgramName + + " does not support rotated geo transforms."; } if (padfGeoTransform[GEOTRSFRM_NS_RES] >= 0) { - return "gdalbuildvrt does not support positive NS resolution."; + return m_osProgramName + + " does not support positive NS resolution."; } } @@ -800,7 +810,8 @@ std::string VRTBuilder::AnalyseRaster(GDALDatasetH hDS, { CPLString osExpected = GetProjectionName(pszProjectionRef); CPLString osGot = GetProjectionName(proj); - return CPLSPrintf("gdalbuildvrt does not support heterogeneous " + return m_osProgramName + + CPLSPrintf(" does not support heterogeneous " "projection: expected %s, got %s.", osExpected.c_str(), osGot.c_str()); } @@ -816,18 +827,18 @@ std::string VRTBuilder::AnalyseRaster(GDALDatasetH hDS, } else { - return CPLSPrintf( - "gdalbuildvrt does not support heterogeneous band " - "numbers: expected %d, got %d.", - nTotalBands, _nBands); + return m_osProgramName + + CPLSPrintf(" does not support heterogeneous band " + "numbers: expected %d, got %d.", + nTotalBands, _nBands); } } else if (bExplicitBandList && _nBands < nMaxSelectedBandNo) { - return CPLSPrintf( - "gdalbuildvrt does not support heterogeneous band " - "numbers: expected at least %d, got %d.", - nMaxSelectedBandNo, _nBands); + return m_osProgramName + + CPLSPrintf(" does not support heterogeneous band " + "numbers: expected at least %d, got %d.", + nMaxSelectedBandNo, _nBands); } for (int j = 0; j < nSelectedBands; j++) @@ -838,21 +849,25 @@ std::string VRTBuilder::AnalyseRaster(GDALDatasetH hDS, if (asBandProperties[j].colorInterpretation != poBand->GetColorInterpretation()) { - return CPLSPrintf( - "gdalbuildvrt does not support heterogeneous " - "band color interpretation: expected %s, got %s.", - GDALGetColorInterpretationName( - asBandProperties[j].colorInterpretation), - GDALGetColorInterpretationName( - poBand->GetColorInterpretation())); + return m_osProgramName + + CPLSPrintf( + " does not support heterogeneous " + "band color interpretation: expected %s, got " + "%s.", + GDALGetColorInterpretationName( + asBandProperties[j].colorInterpretation), + GDALGetColorInterpretationName( + poBand->GetColorInterpretation())); } if (asBandProperties[j].dataType != poBand->GetRasterDataType()) { - return CPLSPrintf( - "gdalbuildvrt does not support heterogeneous " - "band data type: expected %s, got %s.", - GDALGetDataTypeName(asBandProperties[j].dataType), - GDALGetDataTypeName(poBand->GetRasterDataType())); + return m_osProgramName + + CPLSPrintf(" does not support heterogeneous " + "band data type: expected %s, got %s.", + GDALGetDataTypeName( + asBandProperties[j].dataType), + GDALGetDataTypeName( + poBand->GetRasterDataType())); } if (asBandProperties[j].colorTable) { @@ -862,7 +877,8 @@ std::string VRTBuilder::AnalyseRaster(GDALDatasetH hDS, if (colorTable == nullptr || colorTable->GetColorEntryCount() != nRefColorEntryCount) { - return "gdalbuildvrt does not support rasters with " + return m_osProgramName + + " does not support rasters with " "different color tables (different number of " "color table entries)"; } @@ -894,9 +910,9 @@ std::string VRTBuilder::AnalyseRaster(GDALDatasetH hDS, "You're advised to pre-process your " "rasters with other tools, such as " "pct2rgb.py or gdal_translate -expand RGB\n" - "to operate gdalbuildvrt on RGB rasters " + "to operate %s on RGB rasters " "instead", - dsFileName); + dsFileName, m_osProgramName.c_str()); else CPLError(CE_Warning, CPLE_NotSupported, "%s has different values than the " @@ -915,13 +931,15 @@ std::string VRTBuilder::AnalyseRaster(GDALDatasetH hDS, psDatasetProperties->adfOffset[j] != asBandProperties[j].dfOffset)) { - return CPLSPrintf( - "gdalbuildvrt does not support heterogeneous " - "band offset: expected (%d,%f), got (%d,%f).", - static_cast(asBandProperties[j].bHasOffset), - asBandProperties[j].dfOffset, - static_cast(psDatasetProperties->abHasOffset[j]), - psDatasetProperties->adfOffset[j]); + return m_osProgramName + + CPLSPrintf( + " does not support heterogeneous " + "band offset: expected (%d,%f), got (%d,%f).", + static_cast(asBandProperties[j].bHasOffset), + asBandProperties[j].dfOffset, + static_cast( + psDatasetProperties->abHasOffset[j]), + psDatasetProperties->adfOffset[j]); } if (psDatasetProperties->abHasScale[j] != @@ -930,13 +948,15 @@ std::string VRTBuilder::AnalyseRaster(GDALDatasetH hDS, psDatasetProperties->adfScale[j] != asBandProperties[j].dfScale)) { - return CPLSPrintf( - "gdalbuildvrt does not support heterogeneous " - "band scale: expected (%d,%f), got (%d,%f).", - static_cast(asBandProperties[j].bHasScale), - asBandProperties[j].dfScale, - static_cast(psDatasetProperties->abHasScale[j]), - psDatasetProperties->adfScale[j]); + return m_osProgramName + + CPLSPrintf( + " does not support heterogeneous " + "band scale: expected (%d,%f), got (%d,%f).", + static_cast(asBandProperties[j].bHasScale), + asBandProperties[j].dfScale, + static_cast( + psDatasetProperties->abHasScale[j]), + psDatasetProperties->adfScale[j]); } } } @@ -1752,6 +1772,7 @@ static bool add_file_to_list(const char *filename, const char *tile_index, */ struct GDALBuildVRTOptions { + std::string osProgramName = "gdalbuildvrt"; std::string osTileIndex = "location"; bool bStrict = false; std::string osResolution{}; @@ -1925,6 +1946,7 @@ GDALDatasetH GDALBuildVRT(const char *pszDest, int nSrcCount, sOptions.osOutputSRS.empty() ? nullptr : sOptions.osOutputSRS.c_str(), sOptions.osResampling.empty() ? nullptr : sOptions.osResampling.c_str(), sOptions.aosOpenOptions.List(), sOptions.aosCreateOptions); + oBuilder.m_osProgramName = sOptions.osProgramName; return GDALDataset::ToHandle( oBuilder.Build(sOptions.pfnProgress, sOptions.pProgressData)); @@ -2195,6 +2217,10 @@ GDALBuildVRTOptionsGetParser(GDALBuildVRTOptions *psOptions, "when the value of the mask band of the source is less or " "equal to the threshold.")); + argParser->add_argument("-program_name") + .store_into(psOptions->osProgramName) + .hidden(); + if (psOptionsForBinary) { if (psOptionsForBinary->osDstFilename.empty()) diff --git a/autotest/utilities/test_gdalalg_raster_mosaic.py b/autotest/utilities/test_gdalalg_raster_mosaic.py index bf526b9b5176..95b36eca9c90 100755 --- a/autotest/utilities/test_gdalalg_raster_mosaic.py +++ b/autotest/utilities/test_gdalalg_raster_mosaic.py @@ -13,7 +13,7 @@ import pytest -from osgeo import gdal +from osgeo import gdal, osr def get_mosaic_alg(): @@ -373,3 +373,23 @@ def test_gdalalg_raster_mosaic_tif_creation_options(tmp_vsimem): with gdal.Open(out_filename) as ds: assert ds.GetRasterBand(1).Checksum() == 50054 assert ds.GetRasterBand(1).GetBlockSize() == [256, 256] + + +def test_gdalalg_raster_mosaic_inconsistent_characteristics(): + + src1_ds = gdal.GetDriverByName("MEM").Create("", 1, 1) + src1_ds.SetGeoTransform([2, 1, 0, 49, 0, -1]) + src2_ds = gdal.GetDriverByName("MEM").Create("", 2, 2) + src2_ds.SetGeoTransform([3, 0.5, 0, 49, 0, -0.5]) + srs = osr.SpatialReference() + srs.SetAxisMappingStrategy(osr.OAMS_TRADITIONAL_GIS_ORDER) + srs.SetFromUserInput("WGS84") + src2_ds.SetSpatialRef(srs) + + alg = get_mosaic_alg() + alg.GetArg("input").Set([src1_ds, src2_ds]) + alg.GetArg("output").Set("") + with pytest.raises( + Exception, match="gdal raster mosaic does not support heterogeneous projection" + ): + assert alg.Run() From cd728bdaead5954d51bde0bab849c8f10ab89d04 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 27 Jan 2025 16:28:48 +0100 Subject: [PATCH 2/3] gdalbuildvrt: add '-resolution same' mode to check all source rasters have the same resolution --- apps/gdalbuildvrt_lib.cpp | 25 +++++++++++++++++++++++-- doc/source/programs/gdalbuildvrt.rst | 8 +++++--- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/apps/gdalbuildvrt_lib.cpp b/apps/gdalbuildvrt_lib.cpp index 43441e18c052..2491fcbab006 100644 --- a/apps/gdalbuildvrt_lib.cpp +++ b/apps/gdalbuildvrt_lib.cpp @@ -63,6 +63,7 @@ typedef enum LOWEST_RESOLUTION, HIGHEST_RESOLUTION, AVERAGE_RESOLUTION, + SAME_RESOLUTION, USER_RESOLUTION } ResolutionStrategy; @@ -985,6 +986,23 @@ std::string VRTBuilder::AnalyseRaster(GDALDatasetH hDS, ns_res += dfDelta / nCountValid; } } + else if (resolutionStrategy == SAME_RESOLUTION) + { + if (bFirst) + { + we_res = padfGeoTransform[GEOTRSFRM_WE_RES]; + ns_res = padfGeoTransform[GEOTRSFRM_NS_RES]; + } + else if (we_res != padfGeoTransform[GEOTRSFRM_WE_RES] || + ns_res != padfGeoTransform[GEOTRSFRM_NS_RES]) + { + return CPLSPrintf("Dataset %s has resolution %f x %f, whereas " + "previous sources have resolution %f x %f", + dsFileName, padfGeoTransform[GEOTRSFRM_WE_RES], + padfGeoTransform[GEOTRSFRM_NS_RES], we_res, + ns_res); + } + } else if (resolutionStrategy != USER_RESOLUTION) { if (bFirst) @@ -1922,6 +1940,8 @@ GDALDatasetH GDALBuildVRT(const char *pszDest, int nSrcCount, eStrategy = HIGHEST_RESOLUTION; else if (EQUAL(sOptions.osResolution.c_str(), "lowest")) eStrategy = LOWEST_RESOLUTION; + else if (EQUAL(sOptions.osResolution.c_str(), "same")) + eStrategy = SAME_RESOLUTION; /* If -srcnodata is specified, use it as the -vrtnodata if the latter is not */ @@ -2055,7 +2075,7 @@ GDALBuildVRTOptionsGetParser(GDALBuildVRTOptions *psOptions, "the default value which is 'location'.")); argParser->add_argument("-resolution") - .metavar("user|average|highest|lowest") + .metavar("user|average|highest|lowest|same") .action( [psOptions](const std::string &s) { @@ -2063,7 +2083,8 @@ GDALBuildVRTOptionsGetParser(GDALBuildVRTOptions *psOptions, if (!EQUAL(psOptions->osResolution.c_str(), "user") && !EQUAL(psOptions->osResolution.c_str(), "average") && !EQUAL(psOptions->osResolution.c_str(), "highest") && - !EQUAL(psOptions->osResolution.c_str(), "lowest")) + !EQUAL(psOptions->osResolution.c_str(), "lowest") && + !EQUAL(psOptions->osResolution.c_str(), "same")) { throw std::invalid_argument( CPLSPrintf("Illegal resolution value (%s).", diff --git a/doc/source/programs/gdalbuildvrt.rst b/doc/source/programs/gdalbuildvrt.rst index 26fcd5122e54..e8c66f6006f8 100644 --- a/doc/source/programs/gdalbuildvrt.rst +++ b/doc/source/programs/gdalbuildvrt.rst @@ -19,8 +19,8 @@ Synopsis [--quiet] [[-strict]|[-non_strict]] [-tile_index ] - [-resolution user|average|highest|lowest] [-tr ] - [-input_file_list ] [-separate] + [-resolution user|average|highest|lowest|same] + [-tr ] [-input_file_list ] [-separate] [-allow_projection_difference] [-sd ] [-tap] [-te ] [-addalpha] [-b ]... [-hidenodata] [-overwrite] @@ -79,7 +79,7 @@ changed in later versions. Use the specified value as the tile index field, instead of the default value which is 'location'. -.. option:: -resolution {highest|lowest|average|user} +.. option:: -resolution {highest|lowest|average|user|same} In case the resolution of all input files is not the same, the :option:`-resolution` flag enables the user to control the way the output resolution is computed. @@ -92,6 +92,8 @@ changed in later versions. `user` must be used in combination with the :option:`-tr` option to specify the target resolution. + `same` (added in GDAL 3.11) checks that all source rasters have the same resolution and errors out when this is not the case. + .. option:: -tr Set target resolution. The values must be expressed in georeferenced units. From 8e44a031a54e0d77f5a3959e45f86b81deed1342 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 27 Jan 2025 16:29:07 +0100 Subject: [PATCH 3/3] gdal raster mosaic/stack: add a --resolution=same mode, and default to it --- apps/gdalalg_raster_mosaic.cpp | 43 +++++++++--------- apps/gdalalg_raster_stack.cpp | 43 +++++++++--------- .../utilities/test_gdalalg_raster_mosaic.py | 24 ++++++++-- doc/source/programs/gdal_raster_mosaic.rst | 45 ++++++++++--------- doc/source/programs/gdal_raster_stack.rst | 43 +++++++++--------- 5 files changed, 110 insertions(+), 88 deletions(-) diff --git a/apps/gdalalg_raster_mosaic.cpp b/apps/gdalalg_raster_mosaic.cpp index 8c8eae01f187..bc4daf11b9bc 100644 --- a/apps/gdalalg_raster_mosaic.cpp +++ b/apps/gdalalg_raster_mosaic.cpp @@ -46,15 +46,18 @@ GDALRasterMosaicAlgorithm::GDALRasterMosaicAlgorithm() AddArg("band", 'b', _("Specify input band(s) number."), &m_bands); AddOverwriteArg(&m_overwrite); { - auto &arg = AddArg("resolution", 0, - _("Target resolution (in destination CRS units)"), - &m_resolution) - .SetMetaVar(",|average|highest|lowest"); + auto &arg = + AddArg("resolution", 0, + _("Target resolution (in destination CRS units)"), + &m_resolution) + .SetDefault("same") + .SetMetaVar(",|same|average|highest|lowest"); arg.AddValidationAction( [this, &arg]() { const std::string val = arg.Get(); - if (val != "average" && val != "highest" && val != "lowest") + if (val != "average" && val != "highest" && val != "lowest" && + val != "same") { const auto aosTokens = CPLStringList(CSLTokenizeString2(val.c_str(), ",", 0)); @@ -66,8 +69,8 @@ GDALRasterMosaicAlgorithm::GDALRasterMosaicAlgorithm() { ReportError(CE_Failure, CPLE_AppDefined, "resolution: two comma separated positive " - "values should be provided, or 'average', " - "'highest' or 'lowest'"); + "values should be provided, or 'same', " + "'average', 'highest' or 'lowest'"); return false; } } @@ -190,22 +193,20 @@ bool GDALRasterMosaicAlgorithm::RunImpl(GDALProgressFunc pfnProgress, aosOptions.push_back("-program_name"); aosOptions.push_back("gdal raster mosaic"); - if (!m_resolution.empty()) + const auto aosTokens = + CPLStringList(CSLTokenizeString2(m_resolution.c_str(), ",", 0)); + if (aosTokens.size() == 2) { - const auto aosTokens = - CPLStringList(CSLTokenizeString2(m_resolution.c_str(), ",", 0)); - if (aosTokens.size() == 2) - { - aosOptions.push_back("-tr"); - aosOptions.push_back(aosTokens[0]); - aosOptions.push_back(aosTokens[1]); - } - else - { - aosOptions.push_back("-resolution"); - aosOptions.push_back(m_resolution); - } + aosOptions.push_back("-tr"); + aosOptions.push_back(aosTokens[0]); + aosOptions.push_back(aosTokens[1]); + } + else + { + aosOptions.push_back("-resolution"); + aosOptions.push_back(m_resolution); } + if (!m_bbox.empty()) { aosOptions.push_back("-te"); diff --git a/apps/gdalalg_raster_stack.cpp b/apps/gdalalg_raster_stack.cpp index 41292a2b6775..0a56cc7d4bbd 100644 --- a/apps/gdalalg_raster_stack.cpp +++ b/apps/gdalalg_raster_stack.cpp @@ -46,15 +46,18 @@ GDALRasterStackAlgorithm::GDALRasterStackAlgorithm() AddArg("band", 'b', _("Specify input band(s) number."), &m_bands); AddOverwriteArg(&m_overwrite); { - auto &arg = AddArg("resolution", 0, - _("Target resolution (in destination CRS units)"), - &m_resolution) - .SetMetaVar(",|average|highest|lowest"); + auto &arg = + AddArg("resolution", 0, + _("Target resolution (in destination CRS units)"), + &m_resolution) + .SetDefault("same") + .SetMetaVar(",|same|average|highest|lowest"); arg.AddValidationAction( [this, &arg]() { const std::string val = arg.Get(); - if (val != "average" && val != "highest" && val != "lowest") + if (val != "average" && val != "highest" && val != "lowest" && + val != "same") { const auto aosTokens = CPLStringList(CSLTokenizeString2(val.c_str(), ",", 0)); @@ -66,8 +69,8 @@ GDALRasterStackAlgorithm::GDALRasterStackAlgorithm() { ReportError(CE_Failure, CPLE_AppDefined, "resolution: two comma separated positive " - "values should be provided, or 'average', " - "'highest' or 'lowest'"); + "values should be provided, or 'same', " + "'average', 'highest' or 'lowest'"); return false; } } @@ -188,22 +191,20 @@ bool GDALRasterStackAlgorithm::RunImpl(GDALProgressFunc pfnProgress, aosOptions.push_back("-separate"); - if (!m_resolution.empty()) + const auto aosTokens = + CPLStringList(CSLTokenizeString2(m_resolution.c_str(), ",", 0)); + if (aosTokens.size() == 2) { - const auto aosTokens = - CPLStringList(CSLTokenizeString2(m_resolution.c_str(), ",", 0)); - if (aosTokens.size() == 2) - { - aosOptions.push_back("-tr"); - aosOptions.push_back(aosTokens[0]); - aosOptions.push_back(aosTokens[1]); - } - else - { - aosOptions.push_back("-resolution"); - aosOptions.push_back(m_resolution); - } + aosOptions.push_back("-tr"); + aosOptions.push_back(aosTokens[0]); + aosOptions.push_back(aosTokens[1]); + } + else + { + aosOptions.push_back("-resolution"); + aosOptions.push_back(m_resolution); } + if (!m_bbox.empty()) { aosOptions.push_back("-te"); diff --git a/autotest/utilities/test_gdalalg_raster_mosaic.py b/autotest/utilities/test_gdalalg_raster_mosaic.py index 95b36eca9c90..414d871a9f07 100755 --- a/autotest/utilities/test_gdalalg_raster_mosaic.py +++ b/autotest/utilities/test_gdalalg_raster_mosaic.py @@ -115,6 +115,7 @@ def test_gdalalg_raster_mosaic_resolution_average(): alg = get_mosaic_alg() alg.GetArg("input").Set([src1_ds, src2_ds]) + alg.GetArg("resolution").Set("average") alg.GetArg("output").Set("") assert alg.Run() ds = alg.GetArg("output").Get().GetDataset() @@ -196,24 +197,41 @@ def test_gdalalg_raster_mosaic_target_aligned_pixels(): assert ds.GetGeoTransform() == pytest.approx((1.8, 0.3, 0.0, 49.2, 0.0, -0.6)) +def test_gdalalg_raster_mosaic_resolution_same_default(): + + src1_ds = gdal.GetDriverByName("MEM").Create("", 1, 1) + src1_ds.SetGeoTransform([2, 1, 0, 49, 0, -1]) + src2_ds = gdal.GetDriverByName("MEM").Create("", 2, 2) + src2_ds.SetGeoTransform([3, 0.5, 0, 49, 0, -0.5]) + + alg = get_mosaic_alg() + alg.GetArg("input").Set([src1_ds, src2_ds]) + alg.GetArg("output").Set("") + with pytest.raises( + Exception, + match="whereas previous sources have resolution", + ): + assert alg.Run() + + def test_gdalalg_raster_mosaic_resolution_invalid(): alg = get_mosaic_alg() with pytest.raises( Exception, - match="resolution: two comma separated positive values should be provided, or 'average', 'highest' or 'lowest'", + match="resolution: two comma separated positive values should be provided, or 'same', 'average', 'highest' or 'lowest'", ): alg.GetArg("resolution").Set("invalid") with pytest.raises( Exception, - match="resolution: two comma separated positive values should be provided, or 'average', 'highest' or 'lowest'", + match="resolution: two comma separated positive values should be provided, or 'same', 'average', 'highest' or 'lowest'", ): alg.GetArg("resolution").Set("0.5") with pytest.raises( Exception, - match="resolution: two comma separated positive values should be provided, or 'average', 'highest' or 'lowest'", + match="resolution: two comma separated positive values should be provided, or 'same', 'average', 'highest' or 'lowest'", ): alg.GetArg("resolution").Set("-0.5,-0.5") diff --git a/doc/source/programs/gdal_raster_mosaic.rst b/doc/source/programs/gdal_raster_mosaic.rst index 4cb66f440c20..abdf14d848a2 100644 --- a/doc/source/programs/gdal_raster_mosaic.rst +++ b/doc/source/programs/gdal_raster_mosaic.rst @@ -22,30 +22,29 @@ Synopsis Build a mosaic, either virtual (VRT) or materialized Positional arguments: - -i, --input Input raster datasets (or specify a @ to point to a file containing filenames) [1.. values] - -o, --output Output raster dataset (created by algorithm) [required] + -i, --input Input raster datasets (or specify a @ to point to a file containing filenames) [1.. values] + -o, --output Output raster dataset (created by algorithm) [required] Common Options: - -h, --help Display help message and exit - --version Display GDAL version and exit - --json-usage Display usage as JSON document and exit - --drivers Display driver list as JSON document and exit - --config = Configuration option [may be repeated] - --progress Display progress bar + -h, --help Display help message and exit + --version Display GDAL version and exit + --json-usage Display usage as JSON document and exit + --drivers Display driver list as JSON document and exit + --config = Configuration option [may be repeated] + --progress Display progress bar Options: - -f, --of, --format, --output-format Output format - --co, --creation-option = Creation option [may be repeated] - -b, --band Specify input band(s) number. [may be repeated] - --overwrite Whether overwriting existing output is allowed - --resolution ,|average|highest|lowest> Target resolution (in destination CRS units) - --bbox Target bounding box as xmin,ymin,xmax,ymax (in destination CRS units) - --target-aligned-pixels Round target extent to target resolution - --srcnodata Set nodata values for input bands. [1.. values] - --dstnodata Set nodata values at the destination band level. [1.. values] - --hidenodata Makes the destination band not report the NoData. - --addalpha Adds an alpha mask band to the destination when the source raster have none. - + -f, --of, --format, --output-format Output format + --co, --creation-option = Creation option [may be repeated] + -b, --band Specify input band(s) number. [may be repeated] + --overwrite Whether overwriting existing output is allowed + --resolution ,|same|average|highest|lowest> Target resolution (in destination CRS units) (default: same) + --bbox Target bounding box as xmin,ymin,xmax,ymax (in destination CRS units) + --target-aligned-pixels Round target extent to target resolution + --srcnodata Set nodata values for input bands. [1.. values] + --dstnodata Set nodata values at the destination band level. [1.. values] + --hidenodata Makes the destination band not report the NoData. + --addalpha Adds an alpha mask band to the destination when the source raster have none. Description @@ -86,16 +85,18 @@ The following options are available: If input bands not set all bands will be added to the output. Multiple :option:`-b` switches may be used to select a set of input bands. -.. option:: --resolution {|highest|lowest|average} +.. option:: --resolution {|same|highest|lowest|average} In case the resolution of all input files is not the same, the :option:`--resolution` flag enables the user to control the way the output resolution is computed. + `same`, the default, checks that all source rasters have the same resolution and errors out when this is not the case. + `highest` will pick the smallest values of pixel dimensions within the set of source rasters. `lowest` will pick the largest values of pixel dimensions within the set of source rasters. - `average` is the default and will compute an average of pixel dimensions within the set of source rasters. + `average` will compute an average of pixel dimensions within the set of source rasters. ,. The values must be expressed in georeferenced units. Both must be positive values. diff --git a/doc/source/programs/gdal_raster_stack.rst b/doc/source/programs/gdal_raster_stack.rst index a43a702ed6a7..835a9945203c 100644 --- a/doc/source/programs/gdal_raster_stack.rst +++ b/doc/source/programs/gdal_raster_stack.rst @@ -23,29 +23,28 @@ Synopsis Combine together input bands into a multi-band output, either virtual (VRT) or materialized. Positional arguments: - -i, --input Input raster datasets (or specify a @ to point to a file containing filenames) [1.. values] - -o, --output Output raster dataset (created by algorithm) [required] + -i, --input Input raster datasets (or specify a @ to point to a file containing filenames) [1.. values] + -o, --output Output raster dataset (created by algorithm) [required] Common Options: - -h, --help Display help message and exit - --version Display GDAL version and exit - --json-usage Display usage as JSON document and exit - --drivers Display driver list as JSON document and exit - --config = Configuration option [may be repeated] - --progress Display progress bar + -h, --help Display help message and exit + --version Display GDAL version and exit + --json-usage Display usage as JSON document and exit + --drivers Display driver list as JSON document and exit + --config = Configuration option [may be repeated] + --progress Display progress bar Options: - -f, --of, --format, --output-format Output format - --co, --creation-option = Creation option [may be repeated] - -b, --band Specify input band(s) number. [may be repeated] - --overwrite Whether overwriting existing output is allowed - --resolution ,|average|highest|lowest> Target resolution (in destination CRS units) - --bbox Target bounding box as xmin,ymin,xmax,ymax (in destination CRS units) - --target-aligned-pixels Round target extent to target resolution - --srcnodata Set nodata values for input bands. [1.. values] - --dstnodata Set nodata values at the destination band level. [1.. values] - --hidenodata Makes the destination band not report the NoData. - + -f, --of, --format, --output-format Output format + --co, --creation-option = Creation option [may be repeated] + -b, --band Specify input band(s) number. [may be repeated] + --overwrite Whether overwriting existing output is allowed + --resolution ,|same|average|highest|lowest> Target resolution (in destination CRS units) (default: same) + --bbox Target bounding box as xmin,ymin,xmax,ymax (in destination CRS units) + --target-aligned-pixels Round target extent to target resolution + --srcnodata Set nodata values for input bands. [1.. values] + --dstnodata Set nodata values at the destination band level. [1.. values] + --hidenodata Makes the destination band not report the NoData. Description @@ -77,16 +76,18 @@ The following options are available: If input bands not set all bands will be added to the output. Multiple :option:`-b` switches may be used to select a set of input bands. -.. option:: --resolution {|highest|lowest|average} +.. option:: --resolution {|same|highest|lowest|average} In case the resolution of all input files is not the same, the :option:`--resolution` flag enables the user to control the way the output resolution is computed. + `same`, the default, checks that all source rasters have the same resolution and errors out when this is not the case. + `highest` will pick the smallest values of pixel dimensions within the set of source rasters. `lowest` will pick the largest values of pixel dimensions within the set of source rasters. - `average` is the default and will compute an average of pixel dimensions within the set of source rasters. + `average` will compute an average of pixel dimensions within the set of source rasters. ,. The values must be expressed in georeferenced units. Both must be positive values.