Coverage Report

Created: 2026-02-14 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/apps/gdal_contour_lib.cpp
Line
Count
Source
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
#include "gdal_utils_priv.h"
25
26
/************************************************************************/
27
/*                          GDALContourOptions                          */
28
/************************************************************************/
29
30
/** Options for use with GDALContour(). GDALContourOptions must be allocated
31
 *  with GDALContourOptionsNew() and deallocated with GDALContourOptionsFree().
32
 */
33
struct GDALContourOptions
34
{
35
    int nBand = 1;
36
    double dfInterval = 0.0;
37
    double dfNoData = 0.0;
38
    double dfOffset = 0.0;
39
    double dfExpBase = 0.0;
40
    bool b3D = false;
41
    bool bPolygonize = false;
42
    bool bNoDataSet = false;
43
    bool bIgnoreNoData = false;
44
    std::string osNewLayerName = "contour";
45
    std::string osFormat{};
46
    std::string osElevAttrib{};
47
    std::string osElevAttribMin{};
48
    std::string osElevAttribMax{};
49
    std::vector<std::string> aosFixedLevels{};
50
    CPLStringList aosOpenOptions{};
51
    CPLStringList aosCreationOptions{};
52
    CPLStringList aosLayerCreationOptions{};
53
    bool bQuiet = false;
54
    std::string osDestDataSource{};
55
    std::string osSrcDataSource{};
56
    GIntBig nGroupTransactions = 100 * 1000;
57
    GDALProgressFunc pfnProgress = GDALDummyProgress;
58
    void *pProgressData = nullptr;
59
};
60
61
/************************************************************************/
62
/*                 GDALContourOptionsSetDestDataSource                  */
63
/************************************************************************/
64
void GDALContourOptionsSetDestDataSource(GDALContourOptions *psOptions,
65
                                         const char *pszDestDatasource)
66
0
{
67
0
    psOptions->osDestDataSource = pszDestDatasource;
68
0
}
69
70
/************************************************************************/
71
/*                   GDALContourOptionsSetProgress()                    */
72
/************************************************************************/
73
74
/**
75
 * Set a progress function.
76
 *
77
 * @param psOptions the options struct for GDALContour().
78
 * @param pfnProgress the progress callback.
79
 * @param pProgressData the user data for the progress callback.
80
 *
81
 */
82
83
void GDALContourOptionsSetProgress(GDALContourOptions *psOptions,
84
                                   GDALProgressFunc pfnProgress,
85
                                   void *pProgressData)
86
0
{
87
0
    psOptions->pfnProgress = pfnProgress ? pfnProgress : GDALDummyProgress;
88
0
    psOptions->pProgressData = pProgressData;
89
0
}
90
91
///@cond Doxygen_Suppress
92
93
/************************************************************************/
94
/*                          CreateElevAttrib()                          */
95
/************************************************************************/
96
97
static bool CreateElevAttrib(const char *pszElevAttrib, OGRLayerH hLayer)
98
0
{
99
0
    OGRFieldDefnH hFld = OGR_Fld_Create(pszElevAttrib, OFTReal);
100
0
    OGRErr eErr = OGR_L_CreateField(hLayer, hFld, FALSE);
101
0
    OGR_Fld_Destroy(hFld);
102
0
    return eErr == OGRERR_NONE;
103
0
}
104
105
/************************************************************************/
106
/*                     GDALContourProcessOptions()                      */
107
/************************************************************************/
108
109
CPLErr GDALContourProcessOptions(GDALContourOptions *psOptions,
110
                                 char ***ppapszStringOptions,
111
                                 GDALDatasetH *hSrcDS, GDALRasterBandH *hBand,
112
                                 GDALDatasetH *hDstDS, OGRLayerH *hLayer)
113
0
{
114
115
    /* -------------------------------------------------------------------- */
116
    /*      Open source raster file.                                        */
117
    /* -------------------------------------------------------------------- */
118
0
    if (!*hSrcDS)
119
0
    {
120
0
        *hSrcDS = GDALOpen(psOptions->osSrcDataSource.c_str(), GA_ReadOnly);
121
0
    }
122
123
0
    if (*hSrcDS == nullptr)
124
0
    {
125
0
        CPLError(CE_Failure, CPLE_AppDefined,
126
0
                 "Unable to open source raster file '%s'.",
127
0
                 psOptions->osSrcDataSource.c_str());
128
0
        return CE_Failure;
129
0
    }
130
131
0
    if (!*hBand)
132
0
    {
133
0
        *hBand = GDALGetRasterBand(*hSrcDS, psOptions->nBand);
134
0
    }
135
136
0
    if (*hBand == nullptr)
137
0
    {
138
0
        CPLError(CE_Failure, CPLE_AppDefined,
139
0
                 "Band %d does not exist on dataset.", psOptions->nBand);
140
0
        return CE_Failure;
141
0
    }
142
143
0
    if (!psOptions->bNoDataSet && !psOptions->bIgnoreNoData)
144
0
    {
145
0
        int bNoDataSet;
146
0
        psOptions->dfNoData = GDALGetRasterNoDataValue(*hBand, &bNoDataSet);
147
0
        psOptions->bNoDataSet = bNoDataSet;
148
0
    }
149
150
    /* -------------------------------------------------------------------- */
151
    /*      Try to get a coordinate system from the raster.                 */
152
    /* -------------------------------------------------------------------- */
153
0
    OGRSpatialReferenceH hSRS = GDALGetSpatialRef(*hSrcDS);
154
155
    // Dedup lambda to create the layer
156
0
    auto CreateLayer = [&]() -> OGRLayerH
157
0
    {
158
0
        return GDALDatasetCreateLayer(
159
0
            *hDstDS, psOptions->osNewLayerName.c_str(), hSRS,
160
0
            psOptions->bPolygonize
161
0
                ? (psOptions->b3D ? wkbMultiPolygon25D : wkbMultiPolygon)
162
0
                : (psOptions->b3D ? wkbLineString25D : wkbLineString),
163
0
            psOptions->aosLayerCreationOptions);
164
0
    };
165
166
    /* -------------------------------------------------------------------- */
167
    /*      Create the output file.                                         */
168
    /* -------------------------------------------------------------------- */
169
0
    if (!*hDstDS && !*hLayer)
170
0
    {
171
0
        CPLString osFormat;
172
0
        if (psOptions->osFormat.empty())
173
0
        {
174
0
            const auto aoDrivers = GetOutputDriversFor(
175
0
                psOptions->osDestDataSource.c_str(), GDAL_OF_VECTOR);
176
0
            if (aoDrivers.empty())
177
0
            {
178
0
                CPLError(CE_Failure, CPLE_AppDefined,
179
0
                         "Cannot guess driver for %s",
180
0
                         psOptions->osDestDataSource.c_str());
181
0
                return CE_Failure;
182
0
            }
183
0
            else
184
0
            {
185
0
                if (aoDrivers.size() > 1)
186
0
                {
187
0
                    CPLError(
188
0
                        CE_Warning, CPLE_AppDefined,
189
0
                        "Several drivers matching %s extension. Using %s",
190
0
                        CPLGetExtensionSafe(psOptions->osDestDataSource.c_str())
191
0
                            .c_str(),
192
0
                        aoDrivers[0].c_str());
193
0
                }
194
0
                osFormat = aoDrivers[0];
195
0
            }
196
0
        }
197
0
        else
198
0
        {
199
0
            osFormat = psOptions->osFormat;
200
0
        }
201
202
0
        OGRSFDriverH hDriver = OGRGetDriverByName(osFormat.c_str());
203
204
0
        if (hDriver == nullptr)
205
0
        {
206
0
            fprintf(stderr, "Unable to find format driver named %s.\n",
207
0
                    osFormat.c_str());
208
0
            return CE_Failure;
209
0
        }
210
211
0
        if (!*hDstDS)
212
0
        {
213
0
            *hDstDS =
214
0
                GDALCreate(hDriver, psOptions->osDestDataSource.c_str(), 0, 0,
215
0
                           0, GDT_Unknown, psOptions->aosCreationOptions);
216
0
        }
217
218
0
        if (*hDstDS == nullptr)
219
0
        {
220
0
            return CE_Failure;
221
0
        }
222
223
        // Create the layer
224
0
        *hLayer = CreateLayer();
225
0
    }
226
227
0
    if (!*hLayer)
228
0
    {
229
0
        auto hDriver = GDALGetDatasetDriver(*hDstDS);
230
        // Try to load the layer if it already exists
231
0
        if (GDALGetMetadataItem(hDriver, GDAL_DCAP_MULTIPLE_VECTOR_LAYERS,
232
0
                                nullptr))
233
0
        {
234
0
            *hLayer = GDALDatasetGetLayerByName(
235
0
                *hDstDS, psOptions->osNewLayerName.c_str());
236
0
            if (!*hLayer &&
237
0
                GDALGetMetadataItem(hDriver, GDAL_DCAP_CREATE_LAYER, nullptr) &&
238
0
                !GDALDatasetTestCapability(*hDstDS, ODsCCreateLayer))
239
0
            {
240
0
                *hLayer = CreateLayer();
241
0
            }
242
0
        }
243
0
        else
244
0
        {
245
0
            *hLayer = GDALDatasetGetLayer(*hDstDS, 0);
246
0
        }
247
0
    }
248
249
0
    if (*hLayer == nullptr)
250
0
    {
251
0
        return CE_Failure;
252
0
    }
253
254
0
    if (!OGR_L_TestCapability(*hLayer, OLCTransactions))
255
0
    {
256
0
        psOptions->nGroupTransactions = 0;
257
0
    }
258
259
0
    OGRFieldDefnH hFld = OGR_Fld_Create("ID", OFTInteger);
260
0
    OGR_Fld_SetWidth(hFld, 8);
261
0
    OGR_L_CreateField(*hLayer, hFld, FALSE);
262
0
    OGR_Fld_Destroy(hFld);
263
264
0
    if (psOptions->bPolygonize)
265
0
    {
266
0
        if (!psOptions->osElevAttrib.empty())
267
0
        {
268
0
            psOptions->osElevAttrib.clear();
269
0
            CPLError(CE_Warning, CPLE_NotSupported,
270
0
                     "-a is ignored in polygonal contouring mode. "
271
0
                     "Use -amin and/or -amax instead");
272
0
        }
273
0
    }
274
0
    else
275
0
    {
276
0
        if (!psOptions->osElevAttribMin.empty() ||
277
0
            !psOptions->osElevAttribMax.empty())
278
0
        {
279
0
            psOptions->osElevAttribMin.clear();
280
0
            psOptions->osElevAttribMax.clear();
281
0
            CPLError(CE_Warning, CPLE_NotSupported,
282
0
                     "-amin and/or -amax are ignored in line contouring mode. "
283
0
                     "Use -a instead");
284
0
        }
285
0
    }
286
287
0
    OGRFeatureDefnH hFeatureDefn = OGR_L_GetLayerDefn(*hLayer);
288
289
0
    if (!psOptions->osElevAttrib.empty())
290
0
    {
291
        // Skip if field already exists
292
0
        if (OGR_FD_GetFieldIndex(hFeatureDefn,
293
0
                                 psOptions->osElevAttrib.c_str()) == -1)
294
0
        {
295
0
            if (!CreateElevAttrib(psOptions->osElevAttrib.c_str(), *hLayer))
296
0
            {
297
0
                CPLError(CE_Failure, CPLE_AppDefined,
298
0
                         "Failed to create elevation field '%s'",
299
0
                         psOptions->osElevAttrib.c_str());
300
0
                return CE_Failure;
301
0
            }
302
0
        }
303
0
    }
304
305
0
    if (!psOptions->osElevAttribMin.empty())
306
0
    {
307
        // Skip if field already exists
308
0
        if (OGR_FD_GetFieldIndex(hFeatureDefn,
309
0
                                 psOptions->osElevAttribMin.c_str()) == -1)
310
0
        {
311
0
            if (!CreateElevAttrib(psOptions->osElevAttribMin.c_str(), *hLayer))
312
0
            {
313
0
                CPLError(CE_Failure, CPLE_AppDefined,
314
0
                         "Failed to create elevation min field '%s'",
315
0
                         psOptions->osElevAttribMin.c_str());
316
0
                return CE_Failure;
317
0
            }
318
0
        }
319
0
    }
320
321
0
    if (!psOptions->osElevAttribMax.empty())
322
0
    {
323
        // Skip if field already exists
324
0
        if (OGR_FD_GetFieldIndex(hFeatureDefn,
325
0
                                 psOptions->osElevAttribMax.c_str()) == -1)
326
0
        {
327
0
            if (!CreateElevAttrib(psOptions->osElevAttribMax.c_str(), *hLayer))
328
0
            {
329
0
                CPLError(CE_Failure, CPLE_AppDefined,
330
0
                         "Failed to create elevation max field '%s'",
331
0
                         psOptions->osElevAttribMax.c_str());
332
0
                return CE_Failure;
333
0
            }
334
0
        }
335
0
    }
336
337
    /* -------------------------------------------------------------------- */
338
    /*      Invoke.                                                         */
339
    /* -------------------------------------------------------------------- */
340
0
    int iIDField = OGR_FD_GetFieldIndex(hFeatureDefn, "ID");
341
0
    int iElevField = (psOptions->osElevAttrib.empty())
342
0
                         ? -1
343
0
                         : OGR_FD_GetFieldIndex(
344
0
                               hFeatureDefn, psOptions->osElevAttrib.c_str());
345
346
0
    int iElevFieldMin =
347
0
        (psOptions->osElevAttribMin.empty())
348
0
            ? -1
349
0
            : OGR_FD_GetFieldIndex(hFeatureDefn,
350
0
                                   psOptions->osElevAttribMin.c_str());
351
352
0
    int iElevFieldMax =
353
0
        (psOptions->osElevAttribMax.empty())
354
0
            ? -1
355
0
            : OGR_FD_GetFieldIndex(hFeatureDefn,
356
0
                                   psOptions->osElevAttribMax.c_str());
357
358
0
    if (!psOptions->aosFixedLevels.empty())
359
0
    {
360
0
        std::string values = "FIXED_LEVELS=";
361
0
        for (size_t i = 0; i < psOptions->aosFixedLevels.size(); i++)
362
0
        {
363
0
            if (i == psOptions->aosFixedLevels.size() - 1)
364
0
            {
365
0
                values = values + psOptions->aosFixedLevels[i];
366
0
            }
367
0
            else
368
0
            {
369
0
                values = values + psOptions->aosFixedLevels[i] + ",";
370
0
            }
371
0
        }
372
0
        *ppapszStringOptions =
373
0
            CSLAddString(*ppapszStringOptions, values.c_str());
374
0
    }
375
376
0
    if (psOptions->dfExpBase != 0.0)
377
0
    {
378
0
        *ppapszStringOptions = CSLAppendPrintf(
379
0
            *ppapszStringOptions, "LEVEL_EXP_BASE=%f", psOptions->dfExpBase);
380
0
    }
381
0
    else if (psOptions->dfInterval != 0.0)
382
0
    {
383
0
        *ppapszStringOptions = CSLAppendPrintf(
384
0
            *ppapszStringOptions, "LEVEL_INTERVAL=%f", psOptions->dfInterval);
385
0
    }
386
387
0
    if (psOptions->dfOffset != 0.0)
388
0
    {
389
0
        *ppapszStringOptions = CSLAppendPrintf(
390
0
            *ppapszStringOptions, "LEVEL_BASE=%f", psOptions->dfOffset);
391
0
    }
392
393
0
    if (psOptions->bNoDataSet)
394
0
    {
395
0
        *ppapszStringOptions = CSLAppendPrintf(
396
0
            *ppapszStringOptions, "NODATA=%.19g", psOptions->dfNoData);
397
0
    }
398
0
    if (iIDField != -1)
399
0
    {
400
0
        *ppapszStringOptions =
401
0
            CSLAppendPrintf(*ppapszStringOptions, "ID_FIELD=%d", iIDField);
402
0
    }
403
0
    if (iElevField != -1)
404
0
    {
405
0
        *ppapszStringOptions =
406
0
            CSLAppendPrintf(*ppapszStringOptions, "ELEV_FIELD=%d", iElevField);
407
0
    }
408
0
    if (iElevFieldMin != -1)
409
0
    {
410
0
        *ppapszStringOptions = CSLAppendPrintf(
411
0
            *ppapszStringOptions, "ELEV_FIELD_MIN=%d", iElevFieldMin);
412
0
    }
413
0
    if (iElevFieldMax != -1)
414
0
    {
415
0
        *ppapszStringOptions = CSLAppendPrintf(
416
0
            *ppapszStringOptions, "ELEV_FIELD_MAX=%d", iElevFieldMax);
417
0
    }
418
0
    if (psOptions->bPolygonize)
419
0
    {
420
0
        *ppapszStringOptions =
421
0
            CSLAppendPrintf(*ppapszStringOptions, "POLYGONIZE=YES");
422
0
    }
423
0
    if (psOptions->nGroupTransactions)
424
0
    {
425
0
        *ppapszStringOptions = CSLAppendPrintf(*ppapszStringOptions,
426
0
                                               "COMMIT_INTERVAL=" CPL_FRMT_GIB,
427
0
                                               psOptions->nGroupTransactions);
428
0
    }
429
430
0
    return CE_None;
431
0
}
432
433
///@endcond
434
435
/************************************************************************/
436
/*                   GDALContourAppOptionsGetParser()                   */
437
/************************************************************************/
438
439
static std::unique_ptr<GDALArgumentParser>
440
GDALContourAppOptionsGetParser(GDALContourOptions *psOptions,
441
                               GDALContourOptionsForBinary *psOptionsForBinary)
442
0
{
443
444
0
    auto argParser = std::make_unique<GDALArgumentParser>(
445
0
        "gdal_contour", /* bForBinary=*/psOptionsForBinary != nullptr);
446
447
0
    argParser->add_description(_("Creates contour lines from a raster file."));
448
0
    argParser->add_epilog(_(
449
0
        "For more details, consult the full documentation for the gdal_contour "
450
0
        "utility: http://gdal.org/gdal_contour.html"));
451
452
0
    argParser->add_extra_usage_hint(
453
0
        _("One of -i, -fl or -e must be specified."));
454
455
0
    argParser->add_argument("-b")
456
0
        .metavar("<name>")
457
0
        .default_value(1)
458
0
        .nargs(1)
459
0
        .scan<'i', int>()
460
0
        .store_into(psOptions->nBand)
461
0
        .help(_("Select an input band band containing the DEM data."));
462
463
0
    argParser->add_argument("-a")
464
0
        .metavar("<name>")
465
0
        .store_into(psOptions->osElevAttrib)
466
0
        .help(_("Provides a name for the attribute in which to put the "
467
0
                "elevation."));
468
469
0
    argParser->add_argument("-amin")
470
0
        .metavar("<name>")
471
0
        .store_into(psOptions->osElevAttribMin)
472
0
        .help(_("Provides a name for the attribute in which to put the minimum "
473
0
                "elevation."));
474
475
0
    argParser->add_argument("-amax")
476
0
        .metavar("<name>")
477
0
        .store_into(psOptions->osElevAttribMax)
478
0
        .help(_("Provides a name for the attribute in which to put the maximum "
479
0
                "elevation."));
480
481
0
    argParser->add_argument("-3d")
482
0
        .flag()
483
0
        .store_into(psOptions->b3D)
484
0
        .help(_("Force production of 3D vectors instead of 2D."));
485
486
0
    argParser->add_argument("-inodata")
487
0
        .flag()
488
0
        .store_into(psOptions->bIgnoreNoData)
489
0
        .help(_("Ignore any nodata value implied in the dataset - treat all "
490
0
                "values as valid."));
491
492
0
    argParser->add_argument("-snodata")
493
0
        .metavar("<value>")
494
0
        .scan<'g', double>()
495
0
        .action(
496
0
            [psOptions](const auto &d)
497
0
            {
498
0
                psOptions->bNoDataSet = true;
499
0
                psOptions->dfNoData = CPLAtofM(d.c_str());
500
0
            })
501
0
        .help(_("Input pixel value to treat as \"nodata\"."));
502
503
0
    auto &group = argParser->add_mutually_exclusive_group();
504
505
0
    group.add_argument("-i")
506
0
        .metavar("<interval>")
507
0
        .scan<'g', double>()
508
0
        .store_into(psOptions->dfInterval)
509
0
        .help(_("Elevation interval between contours."));
510
511
0
    group.add_argument("-e")
512
0
        .metavar("<base>")
513
0
        .scan<'g', double>()
514
0
        .store_into(psOptions->dfExpBase)
515
0
        .help(_("Generate levels on an exponential scale: base ^ k, for k an "
516
0
                "integer."));
517
518
    // Dealt manually as argparse::nargs_pattern::at_least_one is problematic
519
0
    argParser->add_argument("-fl").metavar("<level>").help(
520
0
        _("Name one or more \"fixed levels\" to extract."));
521
522
0
    argParser->add_argument("-off")
523
0
        .metavar("<offset>")
524
0
        .scan<'g', double>()
525
0
        .store_into(psOptions->dfOffset)
526
0
        .help(_("Offset from zero relative to which to interpret intervals."));
527
528
0
    argParser->add_argument("-nln")
529
0
        .metavar("<name>")
530
0
        .store_into(psOptions->osNewLayerName)
531
0
        .help(_("Provide a name for the output vector layer. Defaults to "
532
0
                "\"contour\"."));
533
534
0
    argParser->add_argument("-p")
535
0
        .flag()
536
0
        .store_into(psOptions->bPolygonize)
537
0
        .help(_("Generate contour polygons instead of lines."));
538
539
0
    argParser->add_argument("-gt")
540
0
        .metavar("<n>|unlimited")
541
0
        .action(
542
0
            [psOptions](const std::string &s)
543
0
            {
544
0
                if (EQUAL(s.c_str(), "unlimited"))
545
0
                    psOptions->nGroupTransactions = -1;
546
0
                else
547
0
                    psOptions->nGroupTransactions = atoi(s.c_str());
548
0
            })
549
0
        .help(_("Group <n> features per transaction."));
550
551
    // Written that way so that in library mode, users can still use the -q
552
    // switch, even if it has no effect
553
0
    argParser->add_quiet_argument(
554
0
        psOptionsForBinary ? &(psOptionsForBinary->bQuiet) : nullptr);
555
556
0
    if (psOptionsForBinary)
557
0
    {
558
0
        argParser->add_open_options_argument(
559
0
            psOptionsForBinary->aosOpenOptions);
560
561
0
        argParser->add_argument("src_filename")
562
0
            .store_into(psOptions->osSrcDataSource)
563
0
            .help("The source raster file.");
564
565
0
        argParser->add_dataset_creation_options_argument(
566
0
            psOptions->aosOpenOptions);
567
568
0
        argParser->add_argument("dst_filename")
569
0
            .store_into(psOptions->osDestDataSource)
570
0
            .help("The destination vector file.");
571
572
0
        argParser->add_output_format_argument(psOptions->osFormat);
573
574
0
        argParser->add_creation_options_argument(psOptions->aosCreationOptions);
575
576
0
        argParser->add_layer_creation_options_argument(
577
0
            psOptions->aosLayerCreationOptions);
578
0
    }
579
580
0
    return argParser;
581
0
}
582
583
/************************************************************************/
584
/*                     GDALContourGetParserUsage()                      */
585
/************************************************************************/
586
587
std::string GDALContourGetParserUsage()
588
0
{
589
0
    try
590
0
    {
591
0
        GDALContourOptions sOptions;
592
0
        auto argParser = GDALContourAppOptionsGetParser(&sOptions, nullptr);
593
0
        return argParser->usage();
594
0
    }
595
0
    catch (const std::exception &err)
596
0
    {
597
0
        CPLError(CE_Failure, CPLE_AppDefined, "Unexpected exception: %s",
598
0
                 err.what());
599
0
        return std::string();
600
0
    }
601
0
}
602
603
/************************************************************************/
604
/*                       GDALContourOptionsNew()                        */
605
/************************************************************************/
606
607
/**
608
 * Create a new GDALContourOptions object.
609
 *
610
 * @param papszArgv the command line arguments.
611
 * @param psOptionsForBinary the options for binary.
612
 *
613
 * @return the new GDALContourOptions object.
614
 */
615
GDALContourOptions *
616
GDALContourOptionsNew(char **papszArgv,
617
                      GDALContourOptionsForBinary *psOptionsForBinary)
618
0
{
619
620
0
    auto psOptions = std::make_unique<GDALContourOptions>();
621
622
    /*-------------------------------------------------------------------- */
623
    /*      Parse arguments.                                               */
624
    /*-------------------------------------------------------------------- */
625
626
0
    CPLStringList aosArgv;
627
628
    /* -------------------------------------------------------------------- */
629
    /*      Pre-processing for custom syntax that ArgumentParser does not   */
630
    /*      support.                                                        */
631
    /* -------------------------------------------------------------------- */
632
0
    const int argc = CSLCount(papszArgv);
633
634
    /* -------------------------------------------------------------------- */
635
    /*      Pre-processing for custom syntax that ArgumentParser does not   */
636
    /*      support.                                                        */
637
    /* -------------------------------------------------------------------- */
638
0
    for (int i = 0; i < argc && papszArgv != nullptr && papszArgv[i] != nullptr;
639
0
         i++)
640
0
    {
641
        // argparser is confused by arguments that have at_least_one
642
        // cardinality, if they immediately precede positional arguments.
643
0
        if (EQUAL(papszArgv[i], "-fl") && papszArgv[i + 1])
644
0
        {
645
0
            if (strchr(papszArgv[i + 1], ' '))
646
0
            {
647
0
                const CPLStringList aosTokens(
648
0
                    CSLTokenizeString(papszArgv[i + 1]));
649
0
                for (const char *pszToken : aosTokens)
650
0
                {
651
                    // Handle min/max special values
652
0
                    if (EQUAL(pszToken, "MIN"))
653
0
                    {
654
0
                        psOptions->aosFixedLevels.push_back("MIN");
655
0
                    }
656
0
                    else if (EQUAL(pszToken, "MAX"))
657
0
                    {
658
0
                        psOptions->aosFixedLevels.push_back("MAX");
659
0
                    }
660
0
                    else
661
0
                    {
662
0
                        psOptions->aosFixedLevels.push_back(
663
0
                            std::to_string(CPLAtof(pszToken)));
664
0
                    }
665
0
                }
666
0
                i += 1;
667
0
            }
668
0
            else
669
0
            {
670
0
                auto isNumericOrMinMax = [](const char *pszArg) -> bool
671
0
                {
672
0
                    if (EQUAL(pszArg, "MIN") || EQUAL(pszArg, "MAX"))
673
0
                        return true;
674
0
                    char *pszEnd = nullptr;
675
0
                    CPLStrtod(pszArg, &pszEnd);
676
0
                    return pszEnd != nullptr && pszEnd[0] == '\0';
677
0
                };
678
679
0
                while (i < argc - 1 && isNumericOrMinMax(papszArgv[i + 1]))
680
0
                {
681
0
                    if (EQUAL(papszArgv[i + 1], "MIN"))
682
0
                    {
683
0
                        psOptions->aosFixedLevels.push_back("MIN");
684
0
                    }
685
0
                    else if (EQUAL(papszArgv[i + 1], "MAX"))
686
0
                    {
687
0
                        psOptions->aosFixedLevels.push_back("MAX");
688
0
                    }
689
0
                    else
690
0
                    {
691
0
                        psOptions->aosFixedLevels.push_back(
692
0
                            std::to_string(CPLAtof(papszArgv[i + 1])));
693
0
                    }
694
0
                    i += 1;
695
0
                }
696
0
            }
697
0
        }
698
0
        else
699
0
        {
700
0
            aosArgv.AddString(papszArgv[i]);
701
0
        }
702
0
    }
703
704
0
    try
705
0
    {
706
707
0
        auto argParser =
708
0
            GDALContourAppOptionsGetParser(psOptions.get(), psOptionsForBinary);
709
0
        argParser->parse_args_without_binary_name(aosArgv.List());
710
711
0
        if (psOptions->dfInterval == 0.0 && psOptions->aosFixedLevels.empty() &&
712
0
            psOptions->dfExpBase == 0.0)
713
0
        {
714
0
            fprintf(stderr, "%s\n", argParser->usage().c_str());
715
0
            return nullptr;
716
0
        }
717
718
0
        if (psOptions->osSrcDataSource.find("/vsistdout/") !=
719
0
                std::string::npos ||
720
0
            psOptions->osDestDataSource.find("/vsistdout/") !=
721
0
                std::string::npos)
722
0
        {
723
0
            psOptions->bQuiet = true;
724
0
        }
725
726
0
        if (psOptionsForBinary)
727
0
        {
728
0
            psOptionsForBinary->bQuiet = psOptions->bQuiet;
729
0
            psOptionsForBinary->osDestDataSource = psOptions->osDestDataSource;
730
0
            psOptionsForBinary->osSrcDataSource = psOptions->osSrcDataSource;
731
0
            psOptionsForBinary->aosOpenOptions = psOptions->aosOpenOptions;
732
0
        }
733
734
0
        return psOptions.release();
735
0
    }
736
0
    catch (const std::exception &e)
737
0
    {
738
0
        CPLError(CE_Failure, CPLE_AppDefined, "%s", e.what());
739
0
        return nullptr;
740
0
    }
741
0
}
742
743
/************************************************************************/
744
/*                       GDALContourOptionsFree()                       */
745
/************************************************************************/
746
747
/**
748
 * Free a GDALContourOptions object.
749
 *
750
 * @param psOptions the GDALContourOptions object to free.
751
 */
752
void GDALContourOptionsFree(GDALContourOptions *psOptions)
753
0
{
754
0
    delete psOptions;
755
0
}