LCOV - code coverage report
Current view: top level - apps - gdal_contour.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 217 261 83.1 %
Date: 2025-01-18 12:42:00 Functions: 5 6 83.3 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  Contour Generator
       4             :  * Purpose:  Contour Generator mainline.
       5             :  * Author:   Frank Warmerdam <warmerdam@pobox.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2003, Applied Coherent Technology (www.actgate.com).
       9             :  * Copyright (c) 2008-2013, Even Rouault <even dot rouault at spatialys.com>
      10             :  * Copyright (c) 2018, Oslandia <infos at oslandia dot com>
      11             :  *
      12             :  * SPDX-License-Identifier: MIT
      13             :  ****************************************************************************/
      14             : 
      15             : #include "cpl_conv.h"
      16             : #include "cpl_string.h"
      17             : #include "gdal_version.h"
      18             : #include "gdal.h"
      19             : #include "gdal_alg.h"
      20             : #include "gdalargumentparser.h"
      21             : #include "ogr_api.h"
      22             : #include "ogr_srs_api.h"
      23             : #include "commonutils.h"
      24             : 
      25             : /************************************************************************/
      26             : /*                     GDALContourOptions                               */
      27             : /************************************************************************/
      28             : 
      29             : struct GDALContourOptions
      30             : {
      31             :     int nBand = 1;
      32             :     double dfInterval = 0.0;
      33             :     double dfNoData = 0.0;
      34             :     double dfOffset = 0.0;
      35             :     double dfExpBase = 0.0;
      36             :     bool b3D = false;
      37             :     bool bPolygonize = false;
      38             :     bool bNoDataSet = false;
      39             :     bool bIgnoreNoData = false;
      40             :     std::string osNewLayerName = "contour";
      41             :     std::string osFormat;
      42             :     std::string osElevAttrib;
      43             :     std::string osElevAttribMin;
      44             :     std::string osElevAttribMax;
      45             :     std::vector<std::string> aosFixedLevels;
      46             :     CPLStringList aosOpenOptions;
      47             :     CPLStringList aosCreationOptions;
      48             :     bool bQuiet = false;
      49             :     std::string aosDestFilename;
      50             :     std::string aosSrcFilename;
      51             :     GIntBig nGroupTransactions = 100 * 1000;
      52             : };
      53             : 
      54             : /************************************************************************/
      55             : /*                     GDALContourAppOptionsGetParser()                 */
      56             : /************************************************************************/
      57             : 
      58             : static std::unique_ptr<GDALArgumentParser>
      59          20 : GDALContourAppOptionsGetParser(GDALContourOptions *psOptions)
      60             : {
      61             :     auto argParser = std::make_unique<GDALArgumentParser>(
      62          20 :         "gdal_contour", /* bForBinary */ true);
      63             : 
      64          20 :     argParser->add_description(_("Creates contour lines from a raster file."));
      65          20 :     argParser->add_epilog(_(
      66             :         "For more details, consult the full documentation for the gdal_contour "
      67          20 :         "utility: http://gdal.org/gdal_contour.html"));
      68             : 
      69          20 :     argParser->add_extra_usage_hint(
      70             :         _("One of -i, -fl or -e must be specified."));
      71             : 
      72          20 :     argParser->add_argument("-b")
      73          40 :         .metavar("<name>")
      74          20 :         .default_value(1)
      75          20 :         .nargs(1)
      76          20 :         .scan<'i', int>()
      77          20 :         .store_into(psOptions->nBand)
      78          20 :         .help(_("Select an input band band containing the DEM data."));
      79             : 
      80          20 :     argParser->add_argument("-a")
      81          40 :         .metavar("<name>")
      82          20 :         .store_into(psOptions->osElevAttrib)
      83             :         .help(_("Provides a name for the attribute in which to put the "
      84          20 :                 "elevation."));
      85             : 
      86          20 :     argParser->add_argument("-amin")
      87          40 :         .metavar("<name>")
      88          20 :         .store_into(psOptions->osElevAttribMin)
      89             :         .help(_("Provides a name for the attribute in which to put the minimum "
      90          20 :                 "elevation."));
      91             : 
      92          20 :     argParser->add_argument("-amax")
      93          40 :         .metavar("<name>")
      94          20 :         .store_into(psOptions->osElevAttribMax)
      95             :         .help(_("Provides a name for the attribute in which to put the maximum "
      96          20 :                 "elevation."));
      97             : 
      98          20 :     argParser->add_argument("-3d")
      99          20 :         .flag()
     100          20 :         .store_into(psOptions->b3D)
     101          20 :         .help(_("Force production of 3D vectors instead of 2D."));
     102             : 
     103          20 :     argParser->add_argument("-inodata")
     104          20 :         .flag()
     105          20 :         .store_into(psOptions->bIgnoreNoData)
     106             :         .help(_("Ignore any nodata value implied in the dataset - treat all "
     107          20 :                 "values as valid."));
     108             : 
     109          20 :     argParser->add_argument("-snodata")
     110          40 :         .metavar("<value>")
     111          20 :         .scan<'g', double>()
     112             :         .action(
     113           0 :             [psOptions](const auto &d)
     114             :             {
     115           0 :                 psOptions->bNoDataSet = true;
     116           0 :                 psOptions->dfNoData = CPLAtofM(d.c_str());
     117          20 :             })
     118          20 :         .help(_("Input pixel value to treat as \"nodata\"."));
     119             : 
     120          20 :     argParser->add_output_format_argument(psOptions->osFormat);
     121             : 
     122          20 :     argParser->add_dataset_creation_options_argument(psOptions->aosOpenOptions);
     123             : 
     124             :     argParser->add_layer_creation_options_argument(
     125          20 :         psOptions->aosCreationOptions);
     126             : 
     127          20 :     auto &group = argParser->add_mutually_exclusive_group();
     128             : 
     129          20 :     group.add_argument("-i")
     130          40 :         .metavar("<interval>")
     131          20 :         .scan<'g', double>()
     132          20 :         .store_into(psOptions->dfInterval)
     133          20 :         .help(_("Elevation interval between contours."));
     134             : 
     135          20 :     group.add_argument("-e")
     136          40 :         .metavar("<base>")
     137          20 :         .scan<'g', double>()
     138          20 :         .store_into(psOptions->dfExpBase)
     139             :         .help(_("Generate levels on an exponential scale: base ^ k, for k an "
     140          20 :                 "integer."));
     141             : 
     142             :     // Dealt manually as argparse::nargs_pattern::at_least_one is problematic
     143          40 :     argParser->add_argument("-fl").metavar("<level>").help(
     144          20 :         _("Name one or more \"fixed levels\" to extract."));
     145             : 
     146          20 :     argParser->add_argument("-off")
     147          40 :         .metavar("<offset>")
     148          20 :         .scan<'g', double>()
     149          20 :         .store_into(psOptions->dfOffset)
     150          20 :         .help(_("Offset from zero relative to which to interpret intervals."));
     151             : 
     152          20 :     argParser->add_argument("-nln")
     153          40 :         .metavar("<name>")
     154          20 :         .store_into(psOptions->osNewLayerName)
     155             :         .help(_("Provide a name for the output vector layer. Defaults to "
     156          20 :                 "\"contour\"."));
     157             : 
     158          20 :     argParser->add_argument("-p")
     159          20 :         .flag()
     160          20 :         .store_into(psOptions->bPolygonize)
     161          20 :         .help(_("Generate contour polygons instead of lines."));
     162             : 
     163          20 :     argParser->add_argument("-gt")
     164          40 :         .metavar("<n>|unlimited")
     165             :         .action(
     166           6 :             [psOptions](const std::string &s)
     167             :             {
     168           3 :                 if (EQUAL(s.c_str(), "unlimited"))
     169           1 :                     psOptions->nGroupTransactions = -1;
     170             :                 else
     171           2 :                     psOptions->nGroupTransactions = atoi(s.c_str());
     172          20 :             })
     173          20 :         .help(_("Group <n> features per transaction."));
     174             : 
     175          20 :     argParser->add_quiet_argument(&psOptions->bQuiet);
     176             : 
     177          20 :     argParser->add_argument("src_filename")
     178          20 :         .store_into(psOptions->aosSrcFilename)
     179          20 :         .help("The source raster file.");
     180             : 
     181          20 :     argParser->add_argument("dst_filename")
     182          20 :         .store_into(psOptions->aosDestFilename)
     183          20 :         .help("The destination vector file.");
     184             : 
     185          20 :     return argParser;
     186             : }
     187             : 
     188          23 : static void CreateElevAttrib(const char *pszElevAttrib, OGRLayerH hLayer)
     189             : {
     190          23 :     OGRFieldDefnH hFld = OGR_Fld_Create(pszElevAttrib, OFTReal);
     191          23 :     OGRErr eErr = OGR_L_CreateField(hLayer, hFld, FALSE);
     192          23 :     OGR_Fld_Destroy(hFld);
     193          23 :     if (eErr == OGRERR_FAILURE)
     194             :     {
     195           0 :         exit(1);
     196             :     }
     197          23 : }
     198             : 
     199             : /************************************************************************/
     200             : /*                                main()                                */
     201             : /************************************************************************/
     202             : 
     203          21 : MAIN_START(argc, argv)
     204             : 
     205             : {
     206             : 
     207          21 :     GDALProgressFunc pfnProgress = nullptr;
     208             : 
     209          21 :     EarlySetConfigOptions(argc, argv);
     210             : 
     211             :     /* -------------------------------------------------------------------- */
     212             :     /*      Register standard GDAL drivers, and process generic GDAL        */
     213             :     /*      command options.                                                */
     214             :     /* -------------------------------------------------------------------- */
     215             : 
     216          21 :     GDALAllRegister();
     217          21 :     OGRRegisterAll();
     218             : 
     219          21 :     argc = GDALGeneralCmdLineProcessor(argc, &argv, 0);
     220          21 :     if (argc < 1)
     221           1 :         exit(-argc);
     222             : 
     223             :     /* -------------------------------------------------------------------- */
     224             :     /*      Parse arguments.                                                */
     225             :     /* -------------------------------------------------------------------- */
     226             : 
     227          20 :     if (argc < 2)
     228             :     {
     229             : 
     230             :         try
     231             :         {
     232           0 :             GDALContourOptions sOptions;
     233           0 :             auto argParser = GDALContourAppOptionsGetParser(&sOptions);
     234           0 :             fprintf(stderr, "%s\n", argParser->usage().c_str());
     235             :         }
     236           0 :         catch (const std::exception &err)
     237             :         {
     238           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Unexpected exception: %s",
     239           0 :                      err.what());
     240             :         }
     241           0 :         exit(1);
     242             :     }
     243             : 
     244          39 :     GDALContourOptions sOptions;
     245          39 :     CPLStringList aosArgv;
     246             : 
     247             :     try
     248             :     {
     249             :         /* -------------------------------------------------------------------- */
     250             :         /*      Pre-processing for custom syntax that ArgumentParser does not   */
     251             :         /*      support.                                                        */
     252             :         /* -------------------------------------------------------------------- */
     253         170 :         for (int i = 1; i < argc && argv != nullptr && argv[i] != nullptr; i++)
     254             :         {
     255             :             // argparser is confused by arguments that have at_least_one
     256             :             // cardinality, if they immediately precede positional arguments.
     257         150 :             if (EQUAL(argv[i], "-fl") && argv[i + 1])
     258             :             {
     259          11 :                 if (strchr(argv[i + 1], ' '))
     260             :                 {
     261             :                     const CPLStringList aosTokens(
     262           0 :                         CSLTokenizeString(argv[i + 1]));
     263           0 :                     for (const char *pszToken : aosTokens)
     264             :                     {
     265             :                         // Handle min/max special values
     266           0 :                         if (EQUAL(pszToken, "MIN"))
     267             :                         {
     268           0 :                             sOptions.aosFixedLevels.push_back("MIN");
     269             :                         }
     270           0 :                         else if (EQUAL(pszToken, "MAX"))
     271             :                         {
     272           0 :                             sOptions.aosFixedLevels.push_back("MAX");
     273             :                         }
     274             :                         else
     275             :                         {
     276           0 :                             sOptions.aosFixedLevels.push_back(
     277           0 :                                 std::to_string(CPLAtof(pszToken)));
     278             :                         }
     279             :                     }
     280           0 :                     i += 1;
     281             :                 }
     282             :                 else
     283             :                 {
     284          44 :                     auto isNumericOrMinMax = [](const char *pszArg) -> bool
     285             :                     {
     286          44 :                         if (EQUAL(pszArg, "MIN") || EQUAL(pszArg, "MAX"))
     287           2 :                             return true;
     288          42 :                         char *pszEnd = nullptr;
     289          42 :                         CPLStrtod(pszArg, &pszEnd);
     290          42 :                         return pszEnd != nullptr && pszEnd[0] == '\0';
     291             :                     };
     292             : 
     293          44 :                     while (i < argc - 1 && isNumericOrMinMax(argv[i + 1]))
     294             :                     {
     295          33 :                         if (EQUAL(argv[i + 1], "MIN"))
     296             :                         {
     297           1 :                             sOptions.aosFixedLevels.push_back("MIN");
     298             :                         }
     299          32 :                         else if (EQUAL(argv[i + 1], "MAX"))
     300             :                         {
     301           1 :                             sOptions.aosFixedLevels.push_back("MAX");
     302             :                         }
     303             :                         else
     304             :                         {
     305          31 :                             sOptions.aosFixedLevels.push_back(
     306          62 :                                 std::to_string(CPLAtof(argv[i + 1])));
     307             :                         }
     308          33 :                         i += 1;
     309             :                     }
     310          11 :                 }
     311             :             }
     312             :             else
     313             :             {
     314         139 :                 aosArgv.AddString(argv[i]);
     315             :             }
     316             :         }
     317             : 
     318          39 :         auto argParser = GDALContourAppOptionsGetParser(&sOptions);
     319          20 :         argParser->parse_args_without_binary_name(aosArgv.List());
     320             : 
     321          21 :         if (sOptions.dfInterval == 0.0 && sOptions.aosFixedLevels.empty() &&
     322           1 :             sOptions.dfExpBase == 0.0)
     323             :         {
     324           1 :             fprintf(stderr, "%s\n", argParser->usage().c_str());
     325           1 :             exit(1);
     326             :         }
     327             :     }
     328           0 :     catch (const std::exception &error)
     329             :     {
     330           0 :         CPLError(CE_Failure, CPLE_AppDefined, "%s", error.what());
     331           0 :         exit(1);
     332             :     }
     333             : 
     334          38 :     if (sOptions.aosSrcFilename.find("/vsistdout/") != std::string::npos ||
     335          19 :         sOptions.aosDestFilename.find("/vsistdout/") != std::string::npos)
     336             :     {
     337           0 :         sOptions.bQuiet = true;
     338             :     }
     339             : 
     340          19 :     if (!sOptions.bQuiet)
     341          19 :         pfnProgress = GDALTermProgress;
     342             : 
     343             :     /* -------------------------------------------------------------------- */
     344             :     /*      Open source raster file.                                        */
     345             :     /* -------------------------------------------------------------------- */
     346             :     GDALDatasetH hSrcDS =
     347          19 :         GDALOpen(sOptions.aosSrcFilename.c_str(), GA_ReadOnly);
     348          19 :     if (hSrcDS == nullptr)
     349           0 :         exit(2);
     350             : 
     351          19 :     GDALRasterBandH hBand = GDALGetRasterBand(hSrcDS, sOptions.nBand);
     352          19 :     if (hBand == nullptr)
     353             :     {
     354           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     355             :                  "Band %d does not exist on dataset.", sOptions.nBand);
     356           0 :         exit(2);
     357             :     }
     358             : 
     359          19 :     if (!sOptions.bNoDataSet && !sOptions.bIgnoreNoData)
     360             :     {
     361             :         int bNoDataSet;
     362          19 :         sOptions.dfNoData = GDALGetRasterNoDataValue(hBand, &bNoDataSet);
     363          19 :         sOptions.bNoDataSet = bNoDataSet;
     364             :     }
     365             : 
     366             :     /* -------------------------------------------------------------------- */
     367             :     /*      Try to get a coordinate system from the raster.                 */
     368             :     /* -------------------------------------------------------------------- */
     369          19 :     OGRSpatialReferenceH hSRS = GDALGetSpatialRef(hSrcDS);
     370             : 
     371             :     /* -------------------------------------------------------------------- */
     372             :     /*      Create the output file.                                         */
     373             :     /* -------------------------------------------------------------------- */
     374          19 :     CPLString osFormat;
     375          19 :     if (sOptions.osFormat.empty())
     376             :     {
     377             :         const auto aoDrivers = GetOutputDriversFor(
     378          19 :             sOptions.aosDestFilename.c_str(), GDAL_OF_VECTOR);
     379          19 :         if (aoDrivers.empty())
     380             :         {
     381           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Cannot guess driver for %s",
     382             :                      sOptions.aosDestFilename.c_str());
     383           0 :             exit(10);
     384             :         }
     385             :         else
     386             :         {
     387          19 :             if (aoDrivers.size() > 1)
     388             :             {
     389           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
     390             :                          "Several drivers matching %s extension. Using %s",
     391           0 :                          CPLGetExtensionSafe(sOptions.aosDestFilename.c_str())
     392             :                              .c_str(),
     393           0 :                          aoDrivers[0].c_str());
     394             :             }
     395          19 :             osFormat = aoDrivers[0];
     396             :         }
     397             :     }
     398             :     else
     399             :     {
     400           0 :         osFormat = sOptions.osFormat;
     401             :     }
     402             : 
     403          19 :     OGRSFDriverH hDriver = OGRGetDriverByName(osFormat.c_str());
     404             : 
     405          19 :     if (hDriver == nullptr)
     406             :     {
     407           0 :         fprintf(stderr, "Unable to find format driver named %s.\n",
     408             :                 osFormat.c_str());
     409           0 :         exit(10);
     410             :     }
     411             : 
     412          19 :     OGRDataSourceH hDS = OGR_Dr_CreateDataSource(
     413             :         hDriver, sOptions.aosDestFilename.c_str(), sOptions.aosCreationOptions);
     414          19 :     if (hDS == nullptr)
     415           0 :         exit(1);
     416             : 
     417          38 :     OGRLayerH hLayer = OGR_DS_CreateLayer(
     418             :         hDS, sOptions.osNewLayerName.c_str(), hSRS,
     419          19 :         sOptions.bPolygonize
     420           7 :             ? (sOptions.b3D ? wkbMultiPolygon25D : wkbMultiPolygon)
     421          12 :             : (sOptions.b3D ? wkbLineString25D : wkbLineString),
     422             :         sOptions.aosCreationOptions);
     423          19 :     if (hLayer == nullptr)
     424           0 :         exit(1);
     425             : 
     426          19 :     if (!OGR_L_TestCapability(hLayer, OLCTransactions))
     427             :     {
     428          16 :         sOptions.nGroupTransactions = 0;
     429             :     }
     430             : 
     431          19 :     OGRFieldDefnH hFld = OGR_Fld_Create("ID", OFTInteger);
     432          19 :     OGR_Fld_SetWidth(hFld, 8);
     433          19 :     OGR_L_CreateField(hLayer, hFld, FALSE);
     434          19 :     OGR_Fld_Destroy(hFld);
     435             : 
     436          19 :     if (sOptions.bPolygonize)
     437             :     {
     438           7 :         if (!sOptions.osElevAttrib.empty())
     439             :         {
     440           0 :             sOptions.osElevAttrib.clear();
     441           0 :             CPLError(CE_Warning, CPLE_NotSupported,
     442             :                      "-a is ignored in polygonal contouring mode. "
     443             :                      "Use -amin and/or -amax instead");
     444             :         }
     445             :     }
     446             :     else
     447             :     {
     448          24 :         if (!sOptions.osElevAttribMin.empty() ||
     449          12 :             !sOptions.osElevAttribMax.empty())
     450             :         {
     451           0 :             sOptions.osElevAttribMin.clear();
     452           0 :             sOptions.osElevAttribMax.clear();
     453           0 :             CPLError(CE_Warning, CPLE_NotSupported,
     454             :                      "-amin and/or -amax are ignored in line contouring mode. "
     455             :                      "Use -a instead");
     456             :         }
     457             :     }
     458             : 
     459          19 :     if (!sOptions.osElevAttrib.empty())
     460             :     {
     461           9 :         CreateElevAttrib(sOptions.osElevAttrib.c_str(), hLayer);
     462             :     }
     463             : 
     464          19 :     if (!sOptions.osElevAttribMin.empty())
     465             :     {
     466           7 :         CreateElevAttrib(sOptions.osElevAttribMin.c_str(), hLayer);
     467             :     }
     468             : 
     469          19 :     if (!sOptions.osElevAttribMax.empty())
     470             :     {
     471           7 :         CreateElevAttrib(sOptions.osElevAttribMax.c_str(), hLayer);
     472             :     }
     473             : 
     474             :     /* -------------------------------------------------------------------- */
     475             :     /*      Invoke.                                                         */
     476             :     /* -------------------------------------------------------------------- */
     477          19 :     int iIDField = OGR_FD_GetFieldIndex(OGR_L_GetLayerDefn(hLayer), "ID");
     478          19 :     int iElevField = (sOptions.osElevAttrib.empty())
     479          19 :                          ? -1
     480           9 :                          : OGR_FD_GetFieldIndex(OGR_L_GetLayerDefn(hLayer),
     481          19 :                                                 sOptions.osElevAttrib.c_str());
     482             : 
     483             :     int iElevFieldMin =
     484          19 :         (sOptions.osElevAttribMin.empty())
     485          19 :             ? -1
     486           7 :             : OGR_FD_GetFieldIndex(OGR_L_GetLayerDefn(hLayer),
     487          19 :                                    sOptions.osElevAttribMin.c_str());
     488             : 
     489             :     int iElevFieldMax =
     490          19 :         (sOptions.osElevAttribMax.empty())
     491          19 :             ? -1
     492           7 :             : OGR_FD_GetFieldIndex(OGR_L_GetLayerDefn(hLayer),
     493          19 :                                    sOptions.osElevAttribMax.c_str());
     494             : 
     495          19 :     char **options = nullptr;
     496          19 :     if (!sOptions.aosFixedLevels.empty())
     497             :     {
     498          11 :         std::string values = "FIXED_LEVELS=";
     499          44 :         for (size_t i = 0; i < sOptions.aosFixedLevels.size(); i++)
     500             :         {
     501          33 :             if (i == sOptions.aosFixedLevels.size() - 1)
     502             :             {
     503          11 :                 values = values + sOptions.aosFixedLevels[i];
     504             :             }
     505             :             else
     506             :             {
     507          22 :                 values = values + sOptions.aosFixedLevels[i] + ",";
     508             :             }
     509             :         }
     510          11 :         options = CSLAddString(options, values.c_str());
     511             :     }
     512             : 
     513          19 :     if (sOptions.dfExpBase != 0.0)
     514             :     {
     515             :         options =
     516           5 :             CSLAppendPrintf(options, "LEVEL_EXP_BASE=%f", sOptions.dfExpBase);
     517             :     }
     518          14 :     else if (sOptions.dfInterval != 0.0)
     519             :     {
     520             :         options =
     521          12 :             CSLAppendPrintf(options, "LEVEL_INTERVAL=%f", sOptions.dfInterval);
     522             :     }
     523             : 
     524          19 :     if (sOptions.dfOffset != 0.0)
     525             :     {
     526           1 :         options = CSLAppendPrintf(options, "LEVEL_BASE=%f", sOptions.dfOffset);
     527             :     }
     528             : 
     529          19 :     if (sOptions.bNoDataSet)
     530             :     {
     531          10 :         options = CSLAppendPrintf(options, "NODATA=%.19g", sOptions.dfNoData);
     532             :     }
     533          19 :     if (iIDField != -1)
     534             :     {
     535          19 :         options = CSLAppendPrintf(options, "ID_FIELD=%d", iIDField);
     536             :     }
     537          19 :     if (iElevField != -1)
     538             :     {
     539           9 :         options = CSLAppendPrintf(options, "ELEV_FIELD=%d", iElevField);
     540             :     }
     541          19 :     if (iElevFieldMin != -1)
     542             :     {
     543           7 :         options = CSLAppendPrintf(options, "ELEV_FIELD_MIN=%d", iElevFieldMin);
     544             :     }
     545          19 :     if (iElevFieldMax != -1)
     546             :     {
     547           7 :         options = CSLAppendPrintf(options, "ELEV_FIELD_MAX=%d", iElevFieldMax);
     548             :     }
     549          19 :     if (sOptions.bPolygonize)
     550             :     {
     551           7 :         options = CSLAppendPrintf(options, "POLYGONIZE=YES");
     552             :     }
     553          19 :     if (sOptions.nGroupTransactions)
     554             :     {
     555           2 :         options = CSLAppendPrintf(options, "COMMIT_INTERVAL=" CPL_FRMT_GIB,
     556             :                                   sOptions.nGroupTransactions);
     557             :     }
     558             : 
     559             :     CPLErr eErr =
     560          19 :         GDALContourGenerateEx(hBand, hLayer, options, pfnProgress, nullptr);
     561             : 
     562          19 :     CSLDestroy(options);
     563          19 :     if (GDALClose(hDS) != CE_None)
     564           0 :         eErr = CE_Failure;
     565          19 :     GDALClose(hSrcDS);
     566             : 
     567          19 :     CSLDestroy(argv);
     568          19 :     GDALDestroyDriverManager();
     569          19 :     OGRCleanupAll();
     570             : 
     571          19 :     return (eErr == CE_None) ? 0 : 1;
     572             : }
     573             : 
     574           0 : MAIN_END

Generated by: LCOV version 1.14