diff --git a/apps/gdal_create.cpp b/apps/gdal_create.cpp index 461a47ca456d..d7c43b77f323 100644 --- a/apps/gdal_create.cpp +++ b/apps/gdal_create.cpp @@ -31,45 +31,185 @@ #include "gdal_priv.h" #include "gdal.h" #include "commonutils.h" +#include "gdalargumentparser.h" #include "ogr_spatialref.h" #include #include #include -static void Usage(bool bIsError) - +/** + * @brief Makes sure the GDAL library is properly cleaned up before exiting. + * @param nCode exit code + * @todo Move to API + */ +static void GDALExit(int nCode) { - fprintf( - bIsError ? stderr : stdout, - "Usage: gdal_create [--help] [--help-general]\n" - " [-of ]\n" - " [-outsize ]\n" - " [-bands ]\n" - " [-burn ]...\n" - " [-ot " - "{Byte/Int8/Int16/UInt16/UInt32/Int32/UInt64/Int64/Float32/Float64/\n" - " CInt16/CInt32/CFloat32/CFloat64}] [-strict]\n" - " [-a_srs ] [-a_ullr ] " - "[-a_nodata ]\n" - " [-mo =]... [-q]\n" - " [-co =]...\n" - " [-if ]\n" - " \n"); - - exit(bIsError ? 1 : 0); + GDALDestroy(); + exit(nCode); } /************************************************************************/ -/* ArgIsNumericCreate() */ +/* GDALCreateOptions */ /************************************************************************/ -static bool ArgIsNumericCreate(const char *pszArg) +struct GDALCreateOptions +{ + int nBandCount = -1; + int nPixels = 0; + bool bPixelsSet{false}; + int nLines = 0; + GDALDataType eDT = GDT_Unknown; + double dfULX = 0; + double dfULY = 0; + double dfLRX = 0; + double dfLRY = 0; + int nULCounter{0}; + bool bGeoTransform = false; + std::string osOutputSRS; + CPLStringList aosMetadata; + std::vector adfBurnValues; + bool bQuiet = false; + bool bSetNoData = false; + std::string osNoData; + std::string osOutputFilename; + std::string osInputFilename; + std::string osFormat; + CPLStringList aosCreateOptions; +}; +/************************************************************************/ +/* GDALCreateAppOptionsGetParser() */ +/************************************************************************/ + +static std::unique_ptr +GDALCreateAppOptionsGetParser(GDALCreateOptions *psOptions) { - char *pszEnd = nullptr; - CPLStrtod(pszArg, &pszEnd); - return pszEnd != nullptr && pszEnd[0] == '\0'; + auto argParser = std::make_unique( + "gdal_create", /* bForBinary */ true); + + argParser->add_description( + _("Create a raster file (without source dataset).")); + + argParser->add_epilog(_( + "For more details, consult the full documentation for the gdal_create " + "utility: http://gdal.org/gdal_create.html")); + + argParser->add_output_type_argument(psOptions->eDT); + + argParser->add_output_format_argument(psOptions->osFormat); + + argParser->add_argument("-outsize") + .metavar(" ") + .nargs(2) + .scan<'i', int>() + .action( + [psOptions](const std::string &s) + { + if (!psOptions->bPixelsSet) + { + psOptions->nPixels = atoi(s.c_str()); + psOptions->bPixelsSet = true; + } + else + { + psOptions->nLines = atoi(s.c_str()); + } + }) + .help(_("Set the size of the output file.")); + + argParser->add_argument("-bands") + .metavar("") + .store_into(psOptions->nBandCount) + .help(_("Set the number of bands in the output file.")); + + argParser->add_argument("-burn") + .metavar("") + .nargs(nargs_pattern::at_least_one) + .append() + .scan<'g', double>() + .action( + [psOptions](const std::string &s) + { + if (s.find(' ') != std::string::npos) + { + const CPLStringList aosTokens(CSLTokenizeString(s.c_str())); + for (int i = 0; i < aosTokens.size(); i++) + { + psOptions->adfBurnValues.push_back( + CPLAtof(aosTokens[i])); + } + } + else + { + psOptions->adfBurnValues.push_back(CPLAtof(s.c_str())); + } + }) + .help( + _("A fixed value to burn into a band for all objects. A list of " + "-burn options can be supplied, one per band being written to.")); + + argParser->add_argument("-a_srs") + .metavar("") + .store_into(psOptions->osOutputSRS) + .help(_("Override the projection for the output file. ")); + + argParser->add_argument("-a_ullr") + .metavar(" ") + .scan<'g', double>() + .nargs(4) + .action( + [psOptions](const std::string &s) + { + switch (psOptions->nULCounter++) + { + case 0: + psOptions->bGeoTransform = true; + psOptions->dfULX = CPLAtofM(s.c_str()); + break; + case 1: + psOptions->dfULY = CPLAtofM(s.c_str()); + break; + case 2: + psOptions->dfLRX = CPLAtofM(s.c_str()); + break; + case 3: + psOptions->dfLRY = CPLAtof(s.c_str()); + break; + } + }) + .help(_("Assign the georeferenced bounds of the output file. ")); + + argParser->add_argument("-a_nodata") + .metavar("") + .scan<'g', double>() + .action( + [psOptions](const std::string &s) + { + psOptions->bSetNoData = true; + psOptions->osNoData = s; + }) + .help(_("Assign a specified nodata value to output bands.")); + + argParser->add_metadata_item_options_argument(psOptions->aosMetadata); + + argParser->add_creation_options_argument(psOptions->aosCreateOptions); + + argParser->add_quiet_argument(&psOptions->bQuiet); + + argParser->add_argument("-if") + .metavar("") + .store_into(psOptions->osInputFilename) + .help(_("Name of GDAL input dataset that serves as a template for " + "default values of options -outsize, -bands, -ot, -a_srs, " + "-a_ullr and -a_nodata.")); + + argParser->add_argument("out_dataset") + .metavar("") + .store_into(psOptions->osOutputFilename) + .help(_("Name of the output dataset to create.")); + + return argParser; } /************************************************************************/ @@ -90,258 +230,137 @@ MAIN_START(argc, argv) /* command options. */ /* -------------------------------------------------------------------- */ GDALAllRegister(); + argc = GDALGeneralCmdLineProcessor(argc, &argv, 0); if (argc < 1) - exit(-argc); + GDALExit(-argc); - const char *pszFormat = nullptr; - const char *pszFilename = nullptr; - CPLStringList aosCreateOptions; - int nPixels = 0; - int nLines = 0; - int nBandCount = -1; - GDALDataType eDT = GDT_Unknown; - double dfULX = 0; - double dfULY = 0; - double dfLRX = 0; - double dfLRY = 0; - bool bGeoTransform = false; - const char *pszOutputSRS = nullptr; - CPLStringList aosMetadata; - std::vector adfBurnValues; - bool bQuiet = false; - int bSetNoData = false; - std::string osNoData; - const char *pszInputFile = nullptr; - for (int i = 1; argv != nullptr && argv[i] != nullptr; i++) + if (argc < 2) { - if (EQUAL(argv[i], "--utility_version")) - { - printf("%s was compiled against GDAL %s and is running against " - "GDAL %s\n", - argv[0], GDAL_RELEASE_NAME, GDALVersionInfo("RELEASE_NAME")); - CSLDestroy(argv); - return 0; - } - else if (EQUAL(argv[i], "--help")) - { - Usage(false); - } - else if (i < argc - 1 && - (EQUAL(argv[i], "-of") || EQUAL(argv[i], "-f"))) - { - ++i; - pszFormat = argv[i]; - } - else if (i < argc - 1 && EQUAL(argv[i], "-co")) + try { - ++i; - aosCreateOptions.AddString(argv[i]); + GDALCreateOptions sOptions; + auto argParser = GDALCreateAppOptionsGetParser(&sOptions); + fprintf(stderr, "%s\n", argParser->usage().c_str()); } - else if (i < argc - 1 && EQUAL(argv[i], "-mo")) + catch (const std::exception &err) { - ++i; - aosMetadata.AddString(argv[i]); + CPLError(CE_Failure, CPLE_AppDefined, "Unexpected exception: %s", + err.what()); } - else if (i < argc - 1 && EQUAL(argv[i], "-bands")) - { - ++i; - nBandCount = atoi(argv[i]); - } - else if (i + 2 < argc && EQUAL(argv[i], "-outsize")) - { - ++i; - nPixels = atoi(argv[i]); - ++i; - nLines = atoi(argv[i]); - } - else if (i < argc - 1 && EQUAL(argv[i], "-ot")) - { - ++i; - for (int iType = 1; iType < GDT_TypeCount; iType++) - { - if (GDALGetDataTypeName(static_cast(iType)) != - nullptr && - EQUAL(GDALGetDataTypeName(static_cast(iType)), - argv[i])) - { - eDT = static_cast(iType); - } - } + CSLDestroy(argv); + GDALExit(1); + } - if (eDT == GDT_Unknown) - { - CPLError(CE_Failure, CPLE_NotSupported, - "Unknown output pixel type: %s.", argv[i]); - CSLDestroy(argv); - exit(1); - } - } - else if (i + 4 < argc && EQUAL(argv[i], "-a_ullr")) - { - bGeoTransform = true; - // coverity[tainted_data] - dfULX = CPLAtofM(argv[++i]); - // coverity[tainted_data] - dfULY = CPLAtofM(argv[++i]); - // coverity[tainted_data] - dfLRX = CPLAtofM(argv[++i]); - // coverity[tainted_data] - dfLRY = CPLAtofM(argv[++i]); - } - else if (i < argc - 1 && EQUAL(argv[i], "-a_srs")) - { - ++i; - pszOutputSRS = argv[i]; - } - else if (i < argc - 1 && EQUAL(argv[i], "-a_nodata")) - { - bSetNoData = true; - ++i; - // coverity[tainted_data] - osNoData = argv[i]; - } + GDALCreateOptions sOptions; - else if (i < argc - 1 && EQUAL(argv[i], "-burn")) - { - if (strchr(argv[i + 1], ' ')) - { - ++i; - CPLStringList aosTokens(CSLTokenizeString(argv[i])); - for (int j = 0; j < aosTokens.size(); j++) - { - adfBurnValues.push_back(CPLAtof(aosTokens[j])); - } - } - else - { - // coverity[tainted_data] - while (i < argc - 1 && ArgIsNumericCreate(argv[i + 1])) - { - ++i; - // coverity[tainted_data] - adfBurnValues.push_back(CPLAtof(argv[i])); - } - } - } - else if (i < argc - 1 && EQUAL(argv[i], "-if")) - { - ++i; - pszInputFile = argv[i]; - } - else if (EQUAL(argv[i], "-q")) - { - bQuiet = true; - } - else if (argv[i][0] == '-') - { - CPLError(CE_Failure, CPLE_NotSupported, "Unknown option name '%s'", - argv[i]); - CSLDestroy(argv); - Usage(true); - } - else if (pszFilename == nullptr) - { - pszFilename = argv[i]; - } - else - { - CPLError(CE_Failure, CPLE_NotSupported, - "Too many command options '%s'", argv[i]); - CSLDestroy(argv); - Usage(true); - } + try + { + + auto argParser = GDALCreateAppOptionsGetParser(&sOptions); + argParser->parse_args_without_binary_name(argv + 1); + CSLDestroy(argv); } - if (pszFilename == nullptr) + catch (const std::exception &error) { CSLDestroy(argv); - Usage(true); + CPLError(CE_Failure, CPLE_AppDefined, "%s", error.what()); + GDALExit(1); } double adfGeoTransform[6] = {0, 1, 0, 0, 0, 1}; - if (bGeoTransform && nPixels > 0 && nLines > 0) + if (sOptions.bGeoTransform && sOptions.nPixels > 0 && sOptions.nLines > 0) { - adfGeoTransform[0] = dfULX; - adfGeoTransform[1] = (dfLRX - dfULX) / nPixels; + adfGeoTransform[0] = sOptions.dfULX; + adfGeoTransform[1] = + (sOptions.dfLRX - sOptions.dfULX) / sOptions.nPixels; adfGeoTransform[2] = 0; - adfGeoTransform[3] = dfULY; + adfGeoTransform[3] = sOptions.dfULY; adfGeoTransform[4] = 0; - adfGeoTransform[5] = (dfLRY - dfULY) / nLines; + adfGeoTransform[5] = + (sOptions.dfLRY - sOptions.dfULY) / sOptions.nLines; } std::unique_ptr poInputDS; - if (pszInputFile) + if (!sOptions.osInputFilename.empty()) { - poInputDS.reset(GDALDataset::Open( - pszInputFile, GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR)); + poInputDS.reset( + GDALDataset::Open(sOptions.osInputFilename.c_str(), + GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR)); if (poInputDS == nullptr) { - CSLDestroy(argv); - GDALDestroyDriverManager(); - exit(1); + GDALExit(1); } - if (nPixels == 0) + if (sOptions.nPixels == 0) { - nPixels = poInputDS->GetRasterXSize(); - nLines = poInputDS->GetRasterYSize(); + sOptions.nPixels = poInputDS->GetRasterXSize(); + sOptions.nLines = poInputDS->GetRasterYSize(); } - if (nBandCount < 0) + if (sOptions.nBandCount < 0) { - nBandCount = poInputDS->GetRasterCount(); + sOptions.nBandCount = poInputDS->GetRasterCount(); } - if (eDT == GDT_Unknown && poInputDS->GetRasterCount() > 0) + if (sOptions.eDT == GDT_Unknown && poInputDS->GetRasterCount() > 0) { - eDT = poInputDS->GetRasterBand(1)->GetRasterDataType(); + sOptions.eDT = poInputDS->GetRasterBand(1)->GetRasterDataType(); } - if (pszOutputSRS == nullptr) + if (sOptions.osOutputSRS.empty()) { - pszOutputSRS = poInputDS->GetProjectionRef(); + sOptions.osOutputSRS = poInputDS->GetProjectionRef(); } - if (!(bGeoTransform && nPixels > 0 && nLines > 0)) + if (!(sOptions.bGeoTransform && sOptions.nPixels > 0 && + sOptions.nLines > 0)) { if (poInputDS->GetGeoTransform(adfGeoTransform) == CE_None) { - bGeoTransform = true; + sOptions.bGeoTransform = true; } } - if (!bSetNoData && poInputDS->GetRasterCount() > 0) + if (!sOptions.bSetNoData && poInputDS->GetRasterCount() > 0) { - if (eDT == GDT_Int64) + if (sOptions.eDT == GDT_Int64) { + int noData; const auto nNoDataValue = - poInputDS->GetRasterBand(1)->GetNoDataValueAsInt64( - &bSetNoData); - if (bSetNoData) - osNoData = CPLSPrintf(CPL_FRMT_GIB, - static_cast(nNoDataValue)); + poInputDS->GetRasterBand(1)->GetNoDataValueAsInt64(&noData); + sOptions.bSetNoData = noData; + if (sOptions.bSetNoData) + sOptions.osNoData = CPLSPrintf( + CPL_FRMT_GIB, static_cast(nNoDataValue)); } - else if (eDT == GDT_UInt64) + else if (sOptions.eDT == GDT_UInt64) { + int noData; const auto nNoDataValue = poInputDS->GetRasterBand(1)->GetNoDataValueAsUInt64( - &bSetNoData); - if (bSetNoData) - osNoData = CPLSPrintf(CPL_FRMT_GUIB, - static_cast(nNoDataValue)); + &noData); + sOptions.bSetNoData = noData; + if (sOptions.bSetNoData) + sOptions.osNoData = CPLSPrintf( + CPL_FRMT_GUIB, static_cast(nNoDataValue)); } else { + int noData; const double dfNoDataValue = - poInputDS->GetRasterBand(1)->GetNoDataValue(&bSetNoData); - if (bSetNoData) - osNoData = CPLSPrintf("%.18g", dfNoDataValue); + poInputDS->GetRasterBand(1)->GetNoDataValue(&noData); + sOptions.bSetNoData = noData; + if (sOptions.bSetNoData) + sOptions.osNoData = CPLSPrintf("%.18g", dfNoDataValue); } } } GDALDriverH hDriver = GDALGetDriverByName( - pszFormat ? pszFormat : GetOutputDriverForRaster(pszFilename).c_str()); + sOptions.osFormat.empty() + ? GetOutputDriverForRaster(sOptions.osOutputFilename.c_str()) + .c_str() + : sOptions.osFormat.c_str()); + if (hDriver == nullptr) { fprintf(stderr, "Output driver not found.\n"); - CSLDestroy(argv); - GDALDestroyDriverManager(); - exit(1); + GDALExit(1); } const bool bHasCreate = GDALGetMetadataItem(hDriver, GDAL_DCAP_CREATE, nullptr) != nullptr; @@ -349,50 +368,45 @@ MAIN_START(argc, argv) GDALGetMetadataItem(hDriver, GDAL_DCAP_CREATECOPY, nullptr) == nullptr) { fprintf(stderr, "This driver has no creation capabilities.\n"); - CSLDestroy(argv); - GDALDestroyDriverManager(); - exit(1); + GDALExit(1); } GDALDriverH hTmpDriver = GDALGetDriverByName("MEM"); if (!bHasCreate && hTmpDriver == nullptr) { fprintf(stderr, "MEM driver not available.\n"); - CSLDestroy(argv); - GDALDestroyDriverManager(); - exit(1); + GDALExit(1); } - if (nPixels != 0 && eDT == GDT_Unknown) + if (sOptions.nPixels != 0 && sOptions.eDT == GDT_Unknown) { - eDT = GDT_Byte; + sOptions.eDT = GDT_Byte; } - if (nBandCount < 0) + if (sOptions.nBandCount < 0) { - nBandCount = eDT == GDT_Unknown ? 0 : 1; + sOptions.nBandCount = sOptions.eDT == GDT_Unknown ? 0 : 1; } GDALDatasetH hDS = GDALCreate( - bHasCreate ? hDriver : hTmpDriver, pszFilename, nPixels, nLines, - nBandCount, eDT, bHasCreate ? aosCreateOptions.List() : nullptr); + bHasCreate ? hDriver : hTmpDriver, sOptions.osOutputFilename.c_str(), + sOptions.nPixels, sOptions.nLines, sOptions.nBandCount, sOptions.eDT, + bHasCreate ? sOptions.aosCreateOptions.List() : nullptr); if (hDS == nullptr) { - GDALDestroyDriverManager(); - CSLDestroy(argv); - exit(1); + GDALExit(1); } - if (pszOutputSRS && pszOutputSRS[0] != '\0' && !EQUAL(pszOutputSRS, "NONE")) + if (!sOptions.osOutputSRS.empty() && + !EQUAL(sOptions.osOutputSRS.c_str(), "NONE")) { OGRSpatialReference oSRS; oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); - if (oSRS.SetFromUserInput(pszOutputSRS) != OGRERR_NONE) + if (oSRS.SetFromUserInput(sOptions.osOutputSRS.c_str()) != OGRERR_NONE) { CPLError(CE_Failure, CPLE_AppDefined, - "Failed to process SRS definition: %s", pszOutputSRS); - CSLDestroy(argv); - GDALDestroyDriverManager(); - exit(1); + "Failed to process SRS definition: %s", + sOptions.osOutputSRS.c_str()); + GDALExit(1); } char *pszSRS = nullptr; @@ -402,28 +416,23 @@ MAIN_START(argc, argv) { CPLFree(pszSRS); GDALClose(hDS); - CSLDestroy(argv); - GDALDestroyDriverManager(); - exit(1); + GDALExit(1); } CPLFree(pszSRS); } - if (bGeoTransform) + if (sOptions.bGeoTransform) { - if (nPixels == 0) + if (sOptions.nPixels == 0) { fprintf(stderr, "-outsize must be specified when -a_ullr is used.\n"); GDALClose(hDS); - GDALDestroyDriverManager(); - exit(1); + GDALExit(1); } if (GDALSetGeoTransform(hDS, adfGeoTransform) != CE_None) { GDALClose(hDS); - CSLDestroy(argv); - GDALDestroyDriverManager(); - exit(1); + GDALExit(1); } } else if (poInputDS && poInputDS->GetGCPCount() > 0) @@ -433,42 +442,43 @@ MAIN_START(argc, argv) poInputDS->GetGCPSpatialRef()); } - if (!aosMetadata.empty()) + if (!sOptions.aosMetadata.empty()) { - GDALSetMetadata(hDS, aosMetadata.List(), nullptr); + GDALSetMetadata(hDS, sOptions.aosMetadata.List(), nullptr); } const int nBands = GDALGetRasterCount(hDS); - if (bSetNoData) + if (sOptions.bSetNoData) { for (int i = 0; i < nBands; i++) { auto hBand = GDALGetRasterBand(hDS, i + 1); - if (eDT == GDT_Int64) + if (sOptions.eDT == GDT_Int64) { GDALSetRasterNoDataValueAsInt64( - hBand, static_cast( - std::strtoll(osNoData.c_str(), nullptr, 10))); + hBand, static_cast(std::strtoll( + sOptions.osNoData.c_str(), nullptr, 10))); } - else if (eDT == GDT_UInt64) + else if (sOptions.eDT == GDT_UInt64) { GDALSetRasterNoDataValueAsUInt64( - hBand, static_cast( - std::strtoull(osNoData.c_str(), nullptr, 10))); + hBand, static_cast(std::strtoull( + sOptions.osNoData.c_str(), nullptr, 10))); } else { - GDALSetRasterNoDataValue(hBand, CPLAtofM(osNoData.c_str())); + GDALSetRasterNoDataValue(hBand, + CPLAtofM(sOptions.osNoData.c_str())); } } } - if (!adfBurnValues.empty()) + if (!sOptions.adfBurnValues.empty()) { for (int i = 0; i < nBands; i++) { GDALFillRaster(GDALGetRasterBand(hDS, i + 1), - i < static_cast(adfBurnValues.size()) - ? adfBurnValues[i] - : adfBurnValues.back(), + i < static_cast(sOptions.adfBurnValues.size()) + ? sOptions.adfBurnValues[i] + : sOptions.adfBurnValues.back(), 0); } } @@ -477,14 +487,13 @@ MAIN_START(argc, argv) if (!bHasCreate) { GDALDatasetH hOutDS = GDALCreateCopy( - hDriver, pszFilename, hDS, false, aosCreateOptions.List(), - bQuiet ? GDALDummyProgress : GDALTermProgress, nullptr); + hDriver, sOptions.osOutputFilename.c_str(), hDS, false, + sOptions.aosCreateOptions.List(), + sOptions.bQuiet ? GDALDummyProgress : GDALTermProgress, nullptr); if (hOutDS == nullptr) { GDALClose(hDS); - CSLDestroy(argv); - GDALDestroyDriverManager(); - exit(1); + GDALExit(1); } if (GDALClose(hOutDS) != CE_None) { @@ -500,7 +509,6 @@ MAIN_START(argc, argv) bHasGotErr = true; } - CSLDestroy(argv); return bHasGotErr ? 1 : 0; } diff --git a/autotest/utilities/test_gdal_create.py b/autotest/utilities/test_gdal_create.py index 38bddddc0715..dc85453edc8e 100755 --- a/autotest/utilities/test_gdal_create.py +++ b/autotest/utilities/test_gdal_create.py @@ -55,13 +55,14 @@ def gdal_create_path(): ############################################################################### -def test_gdal_create_pdf_tif(gdal_create_path, tmp_path): +@pytest.mark.parametrize("burn", ("-burn 1 2", '-burn "1 2"', "-burn 1 -burn 2")) +def test_gdal_create_pdf_tif(gdal_create_path, tmp_path, burn): output_tif = str(tmp_path / "tmp.tif") (_, err) = gdaltest.runexternal_out_and_err( gdal_create_path - + f" {output_tif} -bands 3 -outsize 1 2 -a_srs EPSG:4326 -a_ullr 2 50 3 49 -a_nodata 5 -burn 1 2 -ot UInt16 -co COMPRESS=DEFLATE -mo FOO=BAR" + + f" {output_tif} -bands 3 -outsize 1 2 -a_srs EPSG:4326 -a_ullr 2 50 3 49 -a_nodata 5 {burn} -ot UInt16 -co COMPRESS=DEFLATE -mo FOO=BAR" ) assert err is None or err == "", "got error/warning"