From c425510e079fd91c99a64f604535376fd2d587cf Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 13 Mar 2024 17:10:29 +0100 Subject: [PATCH] gdaladdo: use GDALArgumentParser --- apps/gdaladdo.cpp | 382 ++++++++++++++++++++-------------------------- 1 file changed, 164 insertions(+), 218 deletions(-) diff --git a/apps/gdaladdo.cpp b/apps/gdaladdo.cpp index c29a35df8c3d..a87a054f31bd 100644 --- a/apps/gdaladdo.cpp +++ b/apps/gdaladdo.cpp @@ -33,68 +33,10 @@ #include "commonutils.h" #include "vrtdataset.h" #include "vrt_priv.h" +#include "gdalargumentparser.h" #include -/************************************************************************/ -/* Usage() */ -/************************************************************************/ - -static void Usage(bool bIsError, const char *pszErrorMsg = nullptr) - -{ - fprintf( - bIsError ? stderr : stdout, - "Usage: gdaladdo [--help] [--help-general]\n" - " [-r " - "{nearest|average|rms|gauss|cubic|cubicspline|lanczos|average_mp|" - "average_magphase|mode}]\n" - " [-ro] [-clean] [-q] [-oo =]... [-minsize " - "]\n" - " [--partial-refresh-from-source-timestamp]\n" - " [--partial-refresh-from-projwin " - "]\n" - " [--partial-refresh-from-source-extent " - "[,]...]\n" - " []...\n" - "\n" - " -r : choice of resampling method (default: nearest)\n" - " -ro : open the dataset in read-only mode, in order to generate\n" - " external overview (for GeoTIFF datasets especially)\n" - " -clean : remove all overviews\n" - " -q : turn off progress display\n" - " -b : band to create overview (if not set overviews will be created " - "for all bands)\n" - " filename: The file to build overviews for (or whose overviews must " - "be removed).\n" - " levels: A list of integral overview levels to build. Ignored with " - "-clean option.\n" - "\n" - "Useful configuration variables :\n" - " --config USE_RRD YES : Use Erdas Imagine format (.aux) as overview " - "format.\n" - "Below, only for external overviews in GeoTIFF format:\n" - " --config COMPRESS_OVERVIEW {JPEG,LZW,PACKBITS,DEFLATE} : TIFF " - "compression\n" - " --config PHOTOMETRIC_OVERVIEW {RGB,YCBCR,...} : TIFF photometric " - "interp.\n" - " --config INTERLEAVE_OVERVIEW {PIXEL|BAND} : TIFF interleaving " - "method\n" - " --config BIGTIFF_OVERVIEW {IF_NEEDED|IF_SAFER|YES|NO} : is BigTIFF " - "used\n" - "\n" - "Examples:\n" - " %% gdaladdo -r average abc.tif\n" - " %% gdaladdo --config COMPRESS_OVERVIEW JPEG\n" - " --config PHOTOMETRIC_OVERVIEW YCBCR\n" - " --config INTERLEAVE_OVERVIEW PIXEL -ro abc.tif\n"); - - if (pszErrorMsg != nullptr) - fprintf(stderr, "\nFAILURE: %s\n", pszErrorMsg); - - exit(bIsError ? 1 : 0); -} - /************************************************************************/ /* GDALAddoErrorHandler() */ /************************************************************************/ @@ -612,153 +554,148 @@ static bool PartialRefreshFromProjWin( /* main() */ /************************************************************************/ -#define CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(nExtraArg) \ - do \ - { \ - if (iArg + nExtraArg >= nArgc) \ - Usage(true, CPLSPrintf("%s option requires %d argument(s)", \ - papszArgv[iArg], nExtraArg)); \ - } while (false) - MAIN_START(nArgc, papszArgv) { - // Check that we are running against at least GDAL 1.7. - // Note to developers: if we use newer API, please change the requirement. - if (atoi(GDALVersionInfo("VERSION_NUM")) < 1700) - { - fprintf(stderr, - "At least, GDAL >= 1.7.0 is required for this version of %s, " - "which was compiled against GDAL %s\n", - papszArgv[0], GDAL_RELEASE_NAME); - exit(1); - } - + EarlySetConfigOptions(nArgc, papszArgv); GDALAllRegister(); nArgc = GDALGeneralCmdLineProcessor(nArgc, &papszArgv, 0); if (nArgc < 1) exit(-nArgc); + CPLStringList aosArgv; + aosArgv.Assign(papszArgv, /* bAssign = */ true); + + GDALArgumentParser argParser(aosArgv[0], /* bForBinary=*/true); + + argParser.add_description(_("Builds or rebuilds overview images.")); + + const char *pszEpilog = _( + "Useful configuration variables :\n" + " --config USE_RRD YES : Use Erdas Imagine format (.aux) as overview " + "format.\n" + "Below, only for external overviews in GeoTIFF format:\n" + " --config COMPRESS_OVERVIEW {JPEG,LZW,PACKBITS,DEFLATE} : TIFF " + "compression\n" + " --config PHOTOMETRIC_OVERVIEW {RGB,YCBCR,...} : TIFF photometric " + "interp.\n" + " --config INTERLEAVE_OVERVIEW {PIXEL|BAND} : TIFF interleaving " + "method\n" + " --config BIGTIFF_OVERVIEW {IF_NEEDED|IF_SAFER|YES|NO} : is BigTIFF " + "used\n" + "\n" + "Examples:\n" + " %% gdaladdo -r average abc.tif\n" + " %% gdaladdo --config COMPRESS_OVERVIEW JPEG\n" + " --config PHOTOMETRIC_OVERVIEW YCBCR\n" + " --config INTERLEAVE_OVERVIEW PIXEL -ro abc.tif\n" + "\n" + "For more details, consult https://gdal.org/programs/gdaladdo.html"); + argParser.add_epilog(pszEpilog); std::string osResampling; - const char *pszFilename = nullptr; - std::vector anLevels; - int nResultStatus = 0; + argParser.add_argument("-r") + .store_into(osResampling) + .metavar("nearest|average|rms|gauss|cubic|cubicspline|lanczos|average_" + "magphase|mode") + .help(_("Select a resampling algorithm.")); + bool bReadOnly = false; - bool bClean = false; - GDALProgressFunc pfnProgress = GDALTermProgress; - void *pProgressArg = nullptr; - int *panBandList = nullptr; - int nBandCount = 0; - char **papszOpenOptions = nullptr; - bool bMinSizeSpecified = false; + argParser.add_argument("-ro").store_into(bReadOnly).help( + _("Open the dataset in read-only mode, in order to generate external " + "overview.")); + + bool bQuiet = false; + argParser.add_quiet_argument(&bQuiet); + + std::vector anBandList; + argParser.add_argument("-b") + .append() + .metavar("") + .action( + [&anBandList](const std::string &s) + { + const int nBand = atoi(s.c_str()); + if (nBand < 1) + { + throw std::invalid_argument(CPLSPrintf( + "Unrecognizable band number (%s).\n", s.c_str())); + } + anBandList.push_back(nBand); + }) + .help(_("Select input band(s) for overview generation")); + + CPLStringList aosOpenOptions; + argParser.add_argument("-oo") + .append() + .metavar("") + .action([&aosOpenOptions](const std::string &s) + { aosOpenOptions.AddString(s.c_str()); }) + .help(_("Dataset open option (format-specific)")); + int nMinSize = 256; + argParser.add_argument("-minsize") + .default_value(nMinSize) + .metavar("") + .store_into(nMinSize) + .help(_("Maximum width or height of the smallest overview level")); + + bool bClean = false; bool bPartialRefreshFromSourceTimestamp = false; - bool bPartialRefreshFromProjWin = false; - double dfULX = 0; - double dfULY = 0; - double dfLRX = 0; - double dfLRY = 0; - bool bPartialRefreshFromSourceExtent = false; - CPLStringList aosSources; + std::string osPartialRefeshFromSourceExtent; - /* -------------------------------------------------------------------- */ - /* Parse command line. */ - /* -------------------------------------------------------------------- */ - for (int iArg = 1; iArg < nArgc; iArg++) { - if (EQUAL(papszArgv[iArg], "--utility_version")) - { - printf("%s was compiled against GDAL %s and " - "is running against GDAL %s\n", - papszArgv[0], GDAL_RELEASE_NAME, - GDALVersionInfo("RELEASE_NAME")); - CSLDestroy(papszArgv); - return 0; - } - else if (EQUAL(papszArgv[iArg], "--help")) - { - Usage(false); - } - else if (EQUAL(papszArgv[iArg], "-r")) - { - CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1); - osResampling = papszArgv[++iArg]; - } - else if (EQUAL(papszArgv[iArg], "-ro")) - { - bReadOnly = true; - } - else if (EQUAL(papszArgv[iArg], "-clean")) - { - bClean = true; - } - else if (EQUAL(papszArgv[iArg], "-q") || - EQUAL(papszArgv[iArg], "-quiet")) - { - pfnProgress = GDALDummyProgress; - } - else if (EQUAL(papszArgv[iArg], "-b")) - { - CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1); - const char *pszBand = papszArgv[iArg + 1]; - const int nBand = atoi(pszBand); - if (nBand < 1) - { - Usage(true, CPLSPrintf("Unrecognizable band number (%s).\n", - papszArgv[iArg + 1])); - } - iArg++; + auto &group = argParser.add_mutually_exclusive_group(); + group.add_argument("-clean").store_into(bClean).help( + _("Remove all overviews.")); - nBandCount++; - panBandList = static_cast( - CPLRealloc(panBandList, sizeof(int) * nBandCount)); - panBandList[nBandCount - 1] = nBand; - } - else if (EQUAL(papszArgv[iArg], "-oo")) - { - CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1); - papszOpenOptions = - CSLAddString(papszOpenOptions, papszArgv[++iArg]); - } - else if (EQUAL(papszArgv[iArg], "-minsize")) - { - CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1); - nMinSize = atoi(papszArgv[++iArg]); - bMinSizeSpecified = true; - } - else if (EQUAL(papszArgv[iArg], - "--partial-refresh-from-source-timestamp")) - { - bPartialRefreshFromSourceTimestamp = true; - } - else if (EQUAL(papszArgv[iArg], "--partial-refresh-from-projwin")) - { - CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(4); - bPartialRefreshFromProjWin = true; - dfULX = CPLAtof(papszArgv[++iArg]); - dfULY = CPLAtof(papszArgv[++iArg]); - dfLRX = CPLAtof(papszArgv[++iArg]); - dfLRY = CPLAtof(papszArgv[++iArg]); - } - else if (EQUAL(papszArgv[iArg], "--partial-refresh-from-source-extent")) - { - CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1); - bPartialRefreshFromSourceExtent = true; - aosSources = CSLTokenizeString2(papszArgv[++iArg], ",", 0); - } - else if (papszArgv[iArg][0] == '-') - { - Usage(true, - CPLSPrintf("Unknown option name '%s'", papszArgv[iArg])); - } - else if (pszFilename == nullptr) - { - pszFilename = papszArgv[iArg]; - } - else if (atoi(papszArgv[iArg]) > 0) + group.add_argument("--partial-refresh-from-source-timestamp") + .store_into(bPartialRefreshFromSourceTimestamp) + .help(_("Performs a partial refresh of existing overviews, when " + " is a VRT file with an external overview")); + + group.add_argument("--partial-refresh-from-projwin") + .metavar(" ") + .nargs(4) + .scan<'g', double>() + .help( + _("Performs a partial refresh of existing overviews, in the " + "region of interest specified by georeference coordinates.")); + + group.add_argument("--partial-refresh-from-source-extent") + .metavar("[,]...") + .store_into(osPartialRefeshFromSourceExtent) + .help( + _("Performs a partial refresh of existing overviews, in the " + "region of interest specified by one or several filename.")); + } + + std::string osFilename; + argParser.add_argument("filename") + .store_into(osFilename) + .help(_("The file to build overviews for (or whose overviews must be " + "removed).")); + + argParser.add_argument("level").remaining().metavar("").help( + _("A list of integral overview levels to build.")); + + try + { + argParser.parse_args(aosArgv); + } + catch (const std::exception &err) + { + argParser.display_error_and_usage(err); + std::exit(1); + } + + std::vector anLevels; + auto levels = argParser.present>("level"); + if (levels) + { + for (const auto &level : *levels) { - anLevels.push_back(atoi(papszArgv[iArg])); + anLevels.push_back(atoi(level.c_str())); if (anLevels.back() == 1) { printf( @@ -767,21 +704,32 @@ MAIN_START(nArgc, papszArgv) "overview!\n"); } } - else - { - Usage(true, "Too many command options."); - } } - if (pszFilename == nullptr) - Usage(true, "No datasource specified."); + GDALProgressFunc pfnProgress = + bQuiet ? GDALDummyProgress : GDALTermProgress; + const bool bMinSizeSpecified = argParser.is_used("-minsize"); - if (((bClean) ? 1 : 0) + ((bPartialRefreshFromSourceTimestamp) ? 1 : 0) + - ((bPartialRefreshFromProjWin) ? 1 : 0) + - ((bPartialRefreshFromSourceExtent) ? 1 : 0) > - 1) + CPLStringList aosSources; + if (!osPartialRefeshFromSourceExtent.empty()) { - Usage(true, "Mutually exclusive options used"); + aosSources = + CSLTokenizeString2(osPartialRefeshFromSourceExtent.c_str(), ",", 0); + } + + bool bPartialRefreshFromProjWin = false; + double dfULX = 0; + double dfULY = 0; + double dfLRX = 0; + double dfLRY = 0; + if (auto oProjWin = argParser.present>( + "--partial-refresh-from-projwin")) + { + bPartialRefreshFromProjWin = true; + dfULX = (*oProjWin)[0]; + dfULY = (*oProjWin)[1]; + dfLRX = (*oProjWin)[2]; + dfLRY = (*oProjWin)[3]; } /* -------------------------------------------------------------------- */ @@ -792,8 +740,9 @@ MAIN_START(nArgc, papszArgv) { CPLPushErrorHandler(GDALAddoErrorHandler); CPLSetCurrentErrorHandlerCatchDebug(FALSE); - hDataset = GDALOpenEx(pszFilename, GDAL_OF_RASTER | GDAL_OF_UPDATE, - nullptr, papszOpenOptions, nullptr); + hDataset = + GDALOpenEx(osFilename.c_str(), GDAL_OF_RASTER | GDAL_OF_UPDATE, + nullptr, aosOpenOptions.List(), nullptr); CPLPopErrorHandler(); if (hDataset != nullptr) { @@ -806,13 +755,9 @@ MAIN_START(nArgc, papszArgv) } if (hDataset == nullptr) - hDataset = - GDALOpenEx(pszFilename, GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR, - nullptr, papszOpenOptions, nullptr); - - CSLDestroy(papszOpenOptions); - papszOpenOptions = nullptr; - + hDataset = GDALOpenEx(osFilename.c_str(), + GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR, nullptr, + aosOpenOptions.List(), nullptr); if (hDataset == nullptr) exit(2); @@ -848,6 +793,9 @@ MAIN_START(nArgc, papszArgv) /* -------------------------------------------------------------------- */ /* Clean overviews. */ /* -------------------------------------------------------------------- */ + int nResultStatus = 0; + void *pProgressArg = nullptr; + const int nBandCount = static_cast(anBandList.size()); if (bClean) { if (GDALBuildOverviews(hDataset, "NONE", 0, nullptr, 0, nullptr, @@ -862,7 +810,7 @@ MAIN_START(nArgc, papszArgv) if (!PartialRefreshFromSourceTimestamp( GDALDataset::FromHandle(hDataset), osResampling.c_str(), static_cast(anLevels.size()), anLevels.data(), nBandCount, - panBandList, bMinSizeSpecified, nMinSize, pfnProgress, + anBandList.data(), bMinSizeSpecified, nMinSize, pfnProgress, pProgressArg)) { nResultStatus = 1; @@ -873,19 +821,19 @@ MAIN_START(nArgc, papszArgv) if (!PartialRefreshFromProjWin( GDALDataset::FromHandle(hDataset), dfULX, dfULY, dfLRX, dfLRY, osResampling.c_str(), static_cast(anLevels.size()), - anLevels.data(), nBandCount, panBandList, bMinSizeSpecified, - nMinSize, pfnProgress, pProgressArg)) + anLevels.data(), nBandCount, anBandList.data(), + bMinSizeSpecified, nMinSize, pfnProgress, pProgressArg)) { nResultStatus = 1; } } - else if (bPartialRefreshFromSourceExtent) + else if (!aosSources.empty()) { if (!PartialRefreshFromSourceExtent( GDALDataset::FromHandle(hDataset), aosSources, osResampling.c_str(), static_cast(anLevels.size()), - anLevels.data(), nBandCount, panBandList, bMinSizeSpecified, - nMinSize, pfnProgress, pProgressArg)) + anLevels.data(), nBandCount, anBandList.data(), + bMinSizeSpecified, nMinSize, pfnProgress, pProgressArg)) { nResultStatus = 1; } @@ -943,7 +891,7 @@ MAIN_START(nArgc, papszArgv) if (!anLevels.empty() && GDALBuildOverviews(hDataset, osResampling.c_str(), static_cast(anLevels.size()), - anLevels.data(), nBandCount, panBandList, + anLevels.data(), nBandCount, anBandList.data(), pfnProgress, pProgressArg) != CE_None) { fprintf(stderr, "Overview building failed.\n"); @@ -960,8 +908,6 @@ MAIN_START(nArgc, papszArgv) nResultStatus = 1; } - CSLDestroy(papszArgv); - CPLFree(panBandList); GDALDestroyDriverManager(); return nResultStatus;