Coverage Report

Created: 2026-09-14 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/alg/contour.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  Contour Generation
4
 * Purpose:  Core algorithm implementation for contour line generation.
5
 * Author:   Frank Warmerdam, warmerdam@pobox.com
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2003, Frank Warmerdam <warmerdam@pobox.com>
9
 * Copyright (c) 2003, Applied Coherent Technology Corporation, www.actgate.com
10
 * Copyright (c) 2007-2013, Even Rouault <even dot rouault at spatialys.com>
11
 * Copyright (c) 2018, Oslandia <infos at oslandia dot com>
12
 *
13
 * SPDX-License-Identifier: MIT
14
 ****************************************************************************/
15
16
#include "level_generator.h"
17
#include "polygon_ring_appender.h"
18
#include "utility.h"
19
#include "contour_generator.h"
20
#include "segment_merger.h"
21
#include <algorithm>
22
23
#include "gdal.h"
24
#include "gdal_alg.h"
25
#include "cpl_conv.h"
26
#include "cpl_error_internal.h"
27
#include "cpl_string.h"
28
#include "ogr_api.h"
29
#include "ogr_srs_api.h"
30
#include "ogr_geometry.h"
31
32
#include <climits>
33
#include <limits>
34
35
static CPLErr OGRPolygonContourWriter(double dfLevelMin, double dfLevelMax,
36
                                      const OGRMultiPolygon &multipoly,
37
                                      void *pInfo)
38
39
0
{
40
0
    OGRContourWriterInfo *poInfo = static_cast<OGRContourWriterInfo *>(pInfo);
41
42
0
    OGRFeatureDefnH hFDefn =
43
0
        OGR_L_GetLayerDefn(static_cast<OGRLayerH>(poInfo->hLayer));
44
45
0
    OGRFeatureH hFeat = OGR_F_Create(hFDefn);
46
47
0
    if (poInfo->nIDField != -1)
48
0
        OGR_F_SetFieldInteger(hFeat, poInfo->nIDField, poInfo->nNextID++);
49
50
0
    if (poInfo->nElevFieldMin != -1)
51
0
        OGR_F_SetFieldDouble(hFeat, poInfo->nElevFieldMin, dfLevelMin);
52
53
0
    if (poInfo->nElevFieldMax != -1)
54
0
        OGR_F_SetFieldDouble(hFeat, poInfo->nElevFieldMax, dfLevelMax);
55
56
0
    const bool bHasZ = wkbHasZ(OGR_FD_GetGeomType(hFDefn));
57
0
    OGRGeometryH hGeom =
58
0
        OGR_G_CreateGeometry(bHasZ ? wkbMultiPolygon25D : wkbMultiPolygon);
59
60
0
    for (int iPart = 0; iPart < multipoly.getNumGeometries(); iPart++)
61
0
    {
62
0
        OGRPolygon *poNewPoly = new OGRPolygon();
63
0
        const OGRPolygon *poPolygon =
64
0
            static_cast<const OGRPolygon *>(multipoly.getGeometryRef(iPart));
65
66
0
        for (int iRing = 0; iRing < poPolygon->getNumInteriorRings() + 1;
67
0
             iRing++)
68
0
        {
69
0
            const OGRLinearRing *poRing =
70
0
                iRing == 0 ? poPolygon->getExteriorRing()
71
0
                           : poPolygon->getInteriorRing(iRing - 1);
72
73
0
            OGRLinearRing *poNewRing = new OGRLinearRing();
74
0
            for (int iPoint = 0; iPoint < poRing->getNumPoints(); iPoint++)
75
0
            {
76
0
                const double dfX =
77
0
                    poInfo->adfGeoTransform[0] +
78
0
                    poInfo->adfGeoTransform[1] * poRing->getX(iPoint) +
79
0
                    poInfo->adfGeoTransform[2] * poRing->getY(iPoint);
80
0
                const double dfY =
81
0
                    poInfo->adfGeoTransform[3] +
82
0
                    poInfo->adfGeoTransform[4] * poRing->getX(iPoint) +
83
0
                    poInfo->adfGeoTransform[5] * poRing->getY(iPoint);
84
0
                if (bHasZ)
85
0
                    OGR_G_SetPoint(OGRGeometry::ToHandle(poNewRing), iPoint,
86
0
                                   dfX, dfY, dfLevelMax);
87
0
                else
88
0
                    OGR_G_SetPoint_2D(OGRGeometry::ToHandle(poNewRing), iPoint,
89
0
                                      dfX, dfY);
90
0
            }
91
0
            poNewPoly->addRingDirectly(poNewRing);
92
0
        }
93
0
        OGR_G_AddGeometryDirectly(hGeom, OGRGeometry::ToHandle(poNewPoly));
94
0
    }
95
96
0
    OGR_F_SetGeometryDirectly(hFeat, hGeom);
97
98
0
    OGRErr eErr =
99
0
        OGR_L_CreateFeature(static_cast<OGRLayerH>(poInfo->hLayer), hFeat);
100
0
    OGR_F_Destroy(hFeat);
101
102
0
    if (eErr == OGRERR_NONE && poInfo->nTransactionCommitInterval > 0)
103
0
    {
104
0
        if (++poInfo->nWrittenFeatureCountSinceLastCommit ==
105
0
            poInfo->nTransactionCommitInterval)
106
0
        {
107
0
            poInfo->nWrittenFeatureCountSinceLastCommit = 0;
108
            // CPLDebug("CONTOUR", "Flush transaction");
109
0
            eErr =
110
0
                OGR_L_CommitTransaction(static_cast<OGRLayerH>(poInfo->hLayer));
111
0
            if (eErr == OGRERR_NONE)
112
0
            {
113
0
                eErr = OGR_L_StartTransaction(
114
0
                    static_cast<OGRLayerH>(poInfo->hLayer));
115
0
            }
116
0
        }
117
0
    }
118
119
0
    return eErr == OGRERR_NONE ? CE_None : CE_Failure;
120
0
}
121
122
struct PolygonContourWriter
123
{
124
    CPL_DISALLOW_COPY_ASSIGN(PolygonContourWriter)
125
126
    explicit PolygonContourWriter(OGRContourWriterInfo *poInfo, double minLevel)
127
0
        : poInfo_(poInfo), currentLevel_(minLevel)
128
0
    {
129
0
    }
130
131
    void startPolygon(double level)
132
0
    {
133
0
        previousLevel_ = currentLevel_;
134
0
        currentGeometry_.reset(new OGRMultiPolygon());
135
0
        currentLevel_ = level;
136
0
    }
137
138
    void endPolygon()
139
0
    {
140
0
        if (currentPart_)
141
0
        {
142
0
            currentGeometry_->addGeometryDirectly(currentPart_);
143
0
        }
144
145
0
        if (currentGeometry_->getNumGeometries() > 0)
146
0
        {
147
0
            OGRPolygonContourWriter(previousLevel_, currentLevel_,
148
0
                                    *currentGeometry_, poInfo_);
149
0
        }
150
151
0
        currentGeometry_.reset(nullptr);
152
0
        currentPart_ = nullptr;
153
0
    }
154
155
    void addPart(const marching_squares::LineString &ring)
156
0
    {
157
0
        if (currentPart_)
158
0
        {
159
0
            currentGeometry_->addGeometryDirectly(currentPart_);
160
0
        }
161
162
0
        OGRLinearRing *poNewRing = new OGRLinearRing();
163
0
        poNewRing->setNumPoints(int(ring.size()));
164
0
        int iPoint = 0;
165
0
        for (const auto &p : ring)
166
0
        {
167
0
            poNewRing->setPoint(iPoint, p.x, p.y);
168
0
            iPoint++;
169
0
        }
170
0
        currentPart_ = new OGRPolygon();
171
0
        currentPart_->addRingDirectly(poNewRing);
172
0
    }
173
174
    void addInteriorRing(const marching_squares::LineString &ring)
175
0
    {
176
0
        OGRLinearRing *poNewRing = new OGRLinearRing();
177
0
        for (const auto &p : ring)
178
0
        {
179
0
            poNewRing->addPoint(p.x, p.y);
180
0
        }
181
0
        currentPart_->addRingDirectly(poNewRing);
182
0
    }
183
184
    std::unique_ptr<OGRMultiPolygon> currentGeometry_ = {};
185
    OGRPolygon *currentPart_ = nullptr;
186
    OGRContourWriterInfo *poInfo_ = nullptr;
187
    double currentLevel_ = 0;
188
    double previousLevel_ = 0;
189
};
190
191
struct GDALRingAppender
192
{
193
    CPL_DISALLOW_COPY_ASSIGN(GDALRingAppender)
194
195
    GDALRingAppender(GDALContourWriter write, void *data)
196
0
        : write_(write), data_(data)
197
0
    {
198
0
    }
199
200
    void addLine(double level, marching_squares::LineString &ls,
201
                 bool /*closed*/)
202
0
    {
203
0
        const size_t sz = ls.size();
204
0
        std::vector<double> xs(sz), ys(sz);
205
0
        size_t i = 0;
206
0
        for (const auto &pt : ls)
207
0
        {
208
0
            xs[i] = pt.x;
209
0
            ys[i] = pt.y;
210
0
            i++;
211
0
        }
212
213
0
        if (write_(level, int(sz), &xs[0], &ys[0], data_) != CE_None)
214
0
            CPLError(CE_Failure, CPLE_AppDefined, "cannot write linestring");
215
0
    }
216
217
  private:
218
    GDALContourWriter write_;
219
    void *data_;
220
};
221
222
/************************************************************************/
223
/* ==================================================================== */
224
/*                   Additional C Callable Functions                    */
225
/* ==================================================================== */
226
/************************************************************************/
227
228
/************************************************************************/
229
/*                          OGRContourWriter()                          */
230
/************************************************************************/
231
232
CPLErr OGRContourWriter(double dfLevel, int nPoints, double *padfX,
233
                        double *padfY, void *pInfo)
234
235
0
{
236
0
    OGRContourWriterInfo *poInfo = static_cast<OGRContourWriterInfo *>(pInfo);
237
238
0
    OGRFeatureDefnH hFDefn =
239
0
        OGR_L_GetLayerDefn(static_cast<OGRLayerH>(poInfo->hLayer));
240
241
0
    OGRFeatureH hFeat = OGR_F_Create(hFDefn);
242
243
0
    if (poInfo->nIDField != -1)
244
0
        OGR_F_SetFieldInteger(hFeat, poInfo->nIDField, poInfo->nNextID++);
245
246
0
    if (poInfo->nElevField != -1)
247
0
        OGR_F_SetFieldDouble(hFeat, poInfo->nElevField, dfLevel);
248
249
0
    const bool bHasZ = wkbHasZ(OGR_FD_GetGeomType(hFDefn));
250
0
    OGRGeometryH hGeom =
251
0
        OGR_G_CreateGeometry(bHasZ ? wkbLineString25D : wkbLineString);
252
253
0
    for (int iPoint = nPoints - 1; iPoint >= 0; iPoint--)
254
0
    {
255
0
        const double dfX = poInfo->adfGeoTransform[0] +
256
0
                           poInfo->adfGeoTransform[1] * padfX[iPoint] +
257
0
                           poInfo->adfGeoTransform[2] * padfY[iPoint];
258
0
        const double dfY = poInfo->adfGeoTransform[3] +
259
0
                           poInfo->adfGeoTransform[4] * padfX[iPoint] +
260
0
                           poInfo->adfGeoTransform[5] * padfY[iPoint];
261
0
        if (bHasZ)
262
0
            OGR_G_SetPoint(hGeom, iPoint, dfX, dfY, dfLevel);
263
0
        else
264
0
            OGR_G_SetPoint_2D(hGeom, iPoint, dfX, dfY);
265
0
    }
266
267
0
    OGR_F_SetGeometryDirectly(hFeat, hGeom);
268
269
0
    const OGRErr eErr =
270
0
        OGR_L_CreateFeature(static_cast<OGRLayerH>(poInfo->hLayer), hFeat);
271
0
    OGR_F_Destroy(hFeat);
272
273
0
    return eErr == OGRERR_NONE ? CE_None : CE_Failure;
274
0
}
275
276
/************************************************************************/
277
/*                        GDALContourGenerate()                         */
278
/************************************************************************/
279
280
/**
281
 * Create vector contours from raster DEM.
282
 *
283
 * This function is kept for compatibility reason and will call the new
284
 * variant GDALContourGenerateEx that is more extensible and provide more
285
 * options.
286
 *
287
 * Details about the algorithm are also given in the documentation of the
288
 * new GDALContourenerateEx function.
289
 *
290
 * @param hBand The band to read raster data from.  The whole band will be
291
 * processed.
292
 *
293
 * @param dfContourInterval The elevation interval between contours generated.
294
 *
295
 * @param dfContourBase The "base" relative to which contour intervals are
296
 * applied.  This is normally zero, but could be different.  To generate 10m
297
 * contours at 5, 15, 25, ... the ContourBase would be 5.
298
 *
299
 * @param  nFixedLevelCount The number of fixed levels. If this is greater than
300
 * zero, then fixed levels will be used, and ContourInterval and ContourBase
301
 * are ignored.
302
 *
303
 * @param padfFixedLevels The list of fixed contour levels at which contours
304
 * should be generated.  It will contain FixedLevelCount entries, and may be
305
 * NULL if fixed levels are disabled (FixedLevelCount = 0).
306
 *
307
 * @param bUseNoData If TRUE the dfNoDataValue will be used.
308
 *
309
 * @param dfNoDataValue The value to use as a "nodata" value. That is, a
310
 * pixel value which should be ignored in generating contours as if the value
311
 * of the pixel were not known.
312
 *
313
 * @param hLayer The layer to which new contour vectors will be written.
314
 * Each contour will have a LINESTRING geometry attached to it.   This
315
 * is really of type OGRLayerH, but void * is used to avoid pulling the
316
 * ogr_api.h file in here.
317
 *
318
 * @param iIDField If not -1 this will be used as a field index to indicate
319
 * where a unique id should be written for each feature (contour) written.
320
 *
321
 * @param iElevField If not -1 this will be used as a field index to indicate
322
 * where the elevation value of the contour should be written.
323
 *
324
 * @param pfnProgress A GDALProgressFunc that may be used to report progress
325
 * to the user, or to interrupt the algorithm.  May be NULL if not required.
326
 *
327
 * @param pProgressArg The callback data for the pfnProgress function.
328
 *
329
 * @return CE_None on success or CE_Failure if an error occurs.
330
 */
331
332
CPLErr GDALContourGenerate(GDALRasterBandH hBand, double dfContourInterval,
333
                           double dfContourBase, int nFixedLevelCount,
334
                           double *padfFixedLevels, int bUseNoData,
335
                           double dfNoDataValue, void *hLayer, int iIDField,
336
                           int iElevField, GDALProgressFunc pfnProgress,
337
                           void *pProgressArg)
338
0
{
339
0
    char **options = nullptr;
340
0
    if (nFixedLevelCount > 0)
341
0
    {
342
0
        std::string values = "FIXED_LEVELS=";
343
0
        for (int i = 0; i < nFixedLevelCount; i++)
344
0
        {
345
0
            const int sz = 32;
346
0
            char *newValue = new char[sz + 1];
347
0
            if (i == nFixedLevelCount - 1)
348
0
            {
349
0
                CPLsnprintf(newValue, sz + 1, "%f", padfFixedLevels[i]);
350
0
            }
351
0
            else
352
0
            {
353
0
                CPLsnprintf(newValue, sz + 1, "%f,", padfFixedLevels[i]);
354
0
            }
355
0
            values = values + std::string(newValue);
356
0
            delete[] newValue;
357
0
        }
358
0
        options = CSLAddString(options, values.c_str());
359
0
    }
360
0
    else if (dfContourInterval != 0.0)
361
0
    {
362
0
        options =
363
0
            CSLAppendPrintf(options, "LEVEL_INTERVAL=%f", dfContourInterval);
364
0
    }
365
366
0
    if (dfContourBase != 0.0)
367
0
    {
368
0
        options = CSLAppendPrintf(options, "LEVEL_BASE=%f", dfContourBase);
369
0
    }
370
371
0
    if (bUseNoData)
372
0
    {
373
0
        options = CSLAppendPrintf(options, "NODATA=%.19g", dfNoDataValue);
374
0
    }
375
0
    if (iIDField != -1)
376
0
    {
377
0
        options = CSLAppendPrintf(options, "ID_FIELD=%d", iIDField);
378
0
    }
379
0
    if (iElevField != -1)
380
0
    {
381
0
        options = CSLAppendPrintf(options, "ELEV_FIELD=%d", iElevField);
382
0
    }
383
384
0
    CPLErr err = GDALContourGenerateEx(hBand, hLayer, options, pfnProgress,
385
0
                                       pProgressArg);
386
0
    CSLDestroy(options);
387
388
0
    return err;
389
0
}
390
391
/**
392
 * Create vector contours from raster DEM.
393
 *
394
 * This algorithm is an implementation of "Marching squares" [1] that will
395
 * generate contour vectors for the input raster band on the requested set
396
 * of contour levels.  The vector contours are written to the passed in OGR
397
 * vector layer. Also, a NODATA value may be specified to identify pixels
398
 * that should not be considered in contour line generation.
399
 *
400
 * The gdal/apps/gdal_contour_bin.cpp mainline can be used as an example of
401
 * how to use this function.
402
 *
403
 * [1] see https://en.wikipedia.org/wiki/Marching_squares
404
 *
405
 * ALGORITHM RULES
406
407
For contouring purposes raster pixel values are assumed to represent a point
408
value at the center of the corresponding pixel region.  For the purpose of
409
contour generation we virtually connect each pixel center to the values to
410
the left, right, top and bottom.  We assume that the pixel value is linearly
411
interpolated between the pixel centers along each line, and determine where
412
(if any) contour lines will appear along these line segments.  Then the
413
contour crossings are connected.
414
415
This means that contour lines' nodes will not actually be on pixel edges, but
416
rather along vertical and horizontal lines connecting the pixel centers.
417
418
\verbatim
419
General Case:
420
421
      5 |                  | 3
422
     -- + ---------------- + --
423
        |                  |
424
        |                  |
425
        |                  |
426
        |                  |
427
     10 +                  |
428
        |\                 |
429
        | \                |
430
     -- + -+-------------- + --
431
     12 |  10              | 1
432
433
Saddle Point:
434
435
      5 |                  | 12
436
     -- + -------------+-- + --
437
        |               \  |
438
        |                 \|
439
        |                  +
440
        |                  |
441
        +                  |
442
        |\                 |
443
        | \                |
444
     -- + -+-------------- + --
445
     12 |                  | 1
446
447
or:
448
449
      5 |                  | 12
450
     -- + -------------+-- + --
451
        |          __/     |
452
        |      ___/        |
453
        |  ___/          __+
454
        | /           __/  |
455
        +'         __/     |
456
        |       __/        |
457
        |   ,__/           |
458
     -- + -+-------------- + --
459
     12 |                  | 1
460
\endverbatim
461
462
Nodata:
463
464
In the "nodata" case we treat the whole nodata pixel as a no-mans land.
465
We extend the corner pixels near the nodata out to half way and then
466
construct extra lines from those points to the center which is assigned
467
an averaged value from the two nearby points (in this case (12+3+5)/3).
468
469
\verbatim
470
      5 |                  | 3
471
     -- + ---------------- + --
472
        |                  |
473
        |                  |
474
        |      6.7         |
475
        |        +---------+ 3
476
     10 +___     |
477
        |   \____+ 10
478
        |        |
479
     -- + -------+        +
480
     12 |       12           (nodata)
481
482
\endverbatim
483
484
 *
485
 * @param hBand The band to read raster data from.  The whole band will be
486
 * processed.
487
 *
488
 * @param hLayer The layer to which new contour vectors will be written.
489
 * Each contour will have a LINESTRING geometry attached to it
490
 * (or POLYGON if POLYGONIZE=YES). This is really of type OGRLayerH, but
491
 * void * is used to avoid pulling the ogr_api.h file in here.
492
 *
493
 * @param pfnProgress A GDALProgressFunc that may be used to report progress
494
 * to the user, or to interrupt the algorithm.  May be NULL if not required.
495
 *
496
 * @param pProgressArg The callback data for the pfnProgress function.
497
 *
498
 * @param options List of options
499
 *
500
 * Options:
501
 *
502
 *   LEVEL_INTERVAL=f
503
 *
504
 * The elevation interval between contours generated.
505
 *
506
 *   LEVEL_BASE=f
507
 *
508
 * The "base" relative to which contour intervals are
509
 * applied.  This is normally zero, but could be different.  To generate 10m
510
 * contours at 5, 15, 25, ... the LEVEL_BASE would be 5.
511
 *
512
 *   LEVEL_EXP_BASE=f
513
 *
514
 * If greater than 0, contour levels are generated on an
515
 * exponential scale. Levels will then be generated by LEVEL_EXP_BASE^k
516
 * where k is a positive integer.
517
 *
518
 *   FIXED_LEVELS=f[,f]*
519
 *
520
 * The list of fixed contour levels at which contours should be generated.
521
 * This option has precedence on LEVEL_INTERVAL. MIN and MAX can be used
522
 * as special values to represent the minimum and maximum values of the
523
 * raster.
524
 *
525
 *   NODATA=f
526
 *
527
 * The value to use as a "nodata" value. That is, a pixel value which
528
 * should be ignored in generating contours as if the value of the pixel
529
 * were not known.
530
 *
531
 *   ID_FIELD=d
532
 *
533
 * This will be used as a field index to indicate where a unique id should
534
 * be written for each feature (contour) written.
535
 *
536
 *   ELEV_FIELD=d
537
 *
538
 * This will be used as a field index to indicate where the elevation value
539
 * of the contour should be written. Only used in line contouring mode.
540
 *
541
 *   ELEV_FIELD_MIN=d
542
 *
543
 * This will be used as a field index to indicate where the minimum elevation
544
value
545
 * of the polygon contour should be written. Only used in polygonal contouring
546
mode.
547
 *
548
 *   ELEV_FIELD_MAX=d
549
 *
550
 * This will be used as a field index to indicate where the maximum elevation
551
value
552
 * of the polygon contour should be written. Only used in polygonal contouring
553
mode.
554
 *
555
 *   POLYGONIZE=YES|NO
556
 *
557
 * If YES, contour polygons will be created, rather than polygon lines.
558
 *
559
 *
560
 *   COMMIT_INTERVAL=num
561
 *
562
 * (GDAL >= 3.10) Interval in number of features at which transactions must be
563
 * flushed. The default value of 0 means that no transactions are opened.
564
 * A negative value means a single transaction. The function takes care of
565
 * issuing the starting transaction and committing the final one.
566
 *
567
 * @return CE_None on success or CE_Failure if an error occurs.
568
 */
569
CPLErr GDALContourGenerateEx(GDALRasterBandH hBand, void *hLayer,
570
                             CSLConstList options, GDALProgressFunc pfnProgress,
571
                             void *pProgressArg)
572
0
{
573
0
    VALIDATE_POINTER1(hBand, "GDALContourGenerateEx", CE_Failure);
574
575
0
    if (pfnProgress == nullptr)
576
0
        pfnProgress = GDALDummyProgress;
577
578
0
    double contourInterval = 0.0;
579
0
    const char *opt = CSLFetchNameValue(options, "LEVEL_INTERVAL");
580
0
    if (opt)
581
0
    {
582
0
        contourInterval = CPLAtof(opt);
583
        // Written this way to catch NaN as well.
584
0
        if (!(contourInterval > 0))
585
0
        {
586
0
            CPLError(CE_Failure, CPLE_AppDefined,
587
0
                     "Invalid value for LEVEL_INTERVAL. Should be strictly "
588
0
                     "positive.");
589
0
            return CE_Failure;
590
0
        }
591
0
    }
592
593
0
    double contourBase = 0.0;
594
0
    opt = CSLFetchNameValue(options, "LEVEL_BASE");
595
0
    if (opt)
596
0
    {
597
0
        contourBase = CPLAtof(opt);
598
0
    }
599
600
0
    double expBase = 0.0;
601
0
    opt = CSLFetchNameValue(options, "LEVEL_EXP_BASE");
602
0
    if (opt)
603
0
    {
604
0
        expBase = CPLAtof(opt);
605
0
    }
606
607
0
    std::vector<double> fixedLevels;
608
0
    opt = CSLFetchNameValue(options, "FIXED_LEVELS");
609
0
    if (opt)
610
0
    {
611
0
        const CPLStringList aosLevels(
612
0
            CSLTokenizeStringComplex(opt, ",", FALSE, FALSE));
613
0
        fixedLevels.resize(aosLevels.size());
614
0
        for (size_t i = 0; i < fixedLevels.size(); i++)
615
0
        {
616
            // Handle min/max values
617
0
            if (EQUAL(aosLevels[i], "MIN"))
618
0
            {
619
0
                fixedLevels[i] = std::numeric_limits<double>::lowest();
620
0
            }
621
0
            else if (EQUAL(aosLevels[i], "MAX"))
622
0
            {
623
0
                fixedLevels[i] = std::numeric_limits<double>::max();
624
0
            }
625
0
            else
626
0
            {
627
0
                fixedLevels[i] = CPLAtof(aosLevels[i]);
628
0
            }
629
0
            if (i > 0 && !(fixedLevels[i] >= fixedLevels[i - 1]))
630
0
            {
631
0
                CPLError(CE_Failure, CPLE_AppDefined,
632
0
                         "FIXED_LEVELS should be strictly increasing");
633
0
                return CE_Failure;
634
0
            }
635
0
        }
636
0
    }
637
638
0
    bool useNoData = false;
639
0
    double noDataValue = 0.0;
640
0
    opt = CSLFetchNameValue(options, "NODATA");
641
0
    if (opt)
642
0
    {
643
0
        useNoData = true;
644
0
        noDataValue = CPLAtof(opt);
645
0
        if (GDALGetRasterDataType(hBand) == GDT_Float32)
646
0
        {
647
0
            int bClamped = FALSE;
648
0
            int bRounded = FALSE;
649
0
            noDataValue = GDALAdjustValueToDataType(GDT_Float32, noDataValue,
650
0
                                                    &bClamped, &bRounded);
651
0
        }
652
0
    }
653
654
0
    int idField = -1;
655
0
    opt = CSLFetchNameValue(options, "ID_FIELD");
656
0
    if (opt)
657
0
    {
658
0
        idField = atoi(opt);
659
0
    }
660
661
0
    int elevField = -1;
662
0
    opt = CSLFetchNameValue(options, "ELEV_FIELD");
663
0
    if (opt)
664
0
    {
665
0
        elevField = atoi(opt);
666
0
    }
667
668
0
    int elevFieldMin = -1;
669
0
    opt = CSLFetchNameValue(options, "ELEV_FIELD_MIN");
670
0
    if (opt)
671
0
    {
672
0
        elevFieldMin = atoi(opt);
673
0
    }
674
675
0
    int elevFieldMax = -1;
676
0
    opt = CSLFetchNameValue(options, "ELEV_FIELD_MAX");
677
0
    if (opt)
678
0
    {
679
0
        elevFieldMax = atoi(opt);
680
0
    }
681
682
0
    bool polygonize = CPLFetchBool(options, "POLYGONIZE", false);
683
684
0
    int bSuccessMin = FALSE;
685
0
    double dfMinimum = GDALGetRasterMinimum(hBand, &bSuccessMin);
686
0
    int bSuccessMax = FALSE;
687
0
    double dfMaximum = GDALGetRasterMaximum(hBand, &bSuccessMax);
688
0
    if ((!bSuccessMin || !bSuccessMax))
689
0
    {
690
0
        double adfMinMax[2];
691
0
        CPLErrorAccumulator oAccumulator;
692
0
        bool bGotMinMax;
693
0
        {
694
0
            CPLErrorStateBackuper oBackuper;
695
0
            auto oAccumulatorContext = oAccumulator.InstallForCurrentScope();
696
0
            CPL_IGNORE_RET_VAL(oAccumulatorContext);
697
0
            bGotMinMax =
698
0
                (GDALComputeRasterMinMax(hBand, false, adfMinMax) == CE_None);
699
0
        }
700
0
        if (bGotMinMax)
701
0
        {
702
0
            dfMinimum = adfMinMax[0];
703
0
            dfMaximum = adfMinMax[1];
704
0
        }
705
0
        else
706
0
        {
707
0
            bool bNoValidPixels = false;
708
0
            for (const auto &sError : oAccumulator.GetErrors())
709
0
            {
710
0
                if (sError.msg.find("no valid pixels found in sampling") !=
711
0
                    std::string::npos)
712
0
                {
713
0
                    bNoValidPixels = true;
714
0
                }
715
0
                else if (sError.type == CE_Debug)
716
0
                {
717
0
                    CPLDebug("GDAL", "%s", sError.msg.c_str());
718
0
                }
719
0
                else
720
0
                {
721
0
                    CPLError(sError.type, sError.no, "%s", sError.msg.c_str());
722
0
                }
723
0
            }
724
0
            if (bNoValidPixels && oAccumulator.GetErrors().size() == 1)
725
0
            {
726
0
                CPLDebug("GDAL",
727
0
                         "contour: no valid pixels found in input band");
728
0
                return CE_None;
729
0
            }
730
0
            CPLError(CE_Failure, CPLE_AppDefined,
731
0
                     "contour: cannot compute min/max values for input band");
732
0
            return CE_Failure;
733
0
        }
734
0
    }
735
736
0
    using namespace marching_squares;
737
738
0
    OGRContourWriterInfo oCWI;
739
0
    oCWI.hLayer = static_cast<OGRLayerH>(hLayer);
740
0
    oCWI.nElevField = elevField;
741
0
    oCWI.nElevFieldMin = elevFieldMin;
742
0
    oCWI.nElevFieldMax = elevFieldMax;
743
0
    oCWI.nIDField = idField;
744
0
    oCWI.adfGeoTransform[0] = 0.0;
745
0
    oCWI.adfGeoTransform[1] = 1.0;
746
0
    oCWI.adfGeoTransform[2] = 0.0;
747
0
    oCWI.adfGeoTransform[3] = 0.0;
748
0
    oCWI.adfGeoTransform[4] = 0.0;
749
0
    oCWI.adfGeoTransform[5] = 1.0;
750
0
    GDALDatasetH hSrcDS = GDALGetBandDataset(hBand);
751
0
    if (hSrcDS != nullptr)
752
0
        GDALGetGeoTransform(hSrcDS, oCWI.adfGeoTransform);
753
0
    oCWI.nNextID = 0;
754
0
    oCWI.nWrittenFeatureCountSinceLastCommit = 0;
755
0
    oCWI.nTransactionCommitInterval =
756
0
        CPLAtoGIntBig(CSLFetchNameValueDef(options, "COMMIT_INTERVAL", "0"));
757
758
0
    if (oCWI.nTransactionCommitInterval < 0)
759
0
        oCWI.nTransactionCommitInterval = std::numeric_limits<GIntBig>::max();
760
0
    if (oCWI.nTransactionCommitInterval > 0)
761
0
    {
762
0
        if (OGR_L_StartTransaction(static_cast<OGRLayerH>(hLayer)) !=
763
0
            OGRERR_NONE)
764
0
        {
765
0
            oCWI.nTransactionCommitInterval = 0;
766
0
        }
767
0
    }
768
769
0
    bool ok = true;
770
771
    // Replace fixed levels min/max values with raster min/max values
772
0
    if (!fixedLevels.empty())
773
0
    {
774
0
        if (fixedLevels[0] == std::numeric_limits<double>::lowest())
775
0
        {
776
0
            fixedLevels[0] = dfMinimum;
777
0
        }
778
0
        if (fixedLevels.back() == std::numeric_limits<double>::max())
779
0
        {
780
0
            fixedLevels.back() = dfMaximum;
781
0
        }
782
0
    }
783
784
0
    try
785
0
    {
786
0
        if (polygonize)
787
0
        {
788
789
0
            if (!fixedLevels.empty())
790
0
            {
791
792
                // If the minimum raster value is larger than the first requested
793
                // level, select the requested level that is just below the
794
                // minimum raster value
795
0
                if (fixedLevels[0] < dfMinimum)
796
0
                {
797
0
                    for (size_t i = 1; i < fixedLevels.size(); ++i)
798
0
                    {
799
0
                        if (fixedLevels[i] >= dfMinimum)
800
0
                        {
801
0
                            dfMinimum = fixedLevels[i - 1];
802
0
                            break;
803
0
                        }
804
0
                    }
805
0
                }
806
807
                // Largest requested level (levels are sorted)
808
0
                const double maxLevel{fixedLevels.back()};
809
810
                // If the maximum raster value is smaller than the last requested
811
                // level, select the requested level that is just above the
812
                // maximum raster value
813
0
                if (maxLevel > dfMaximum)
814
0
                {
815
0
                    for (size_t i = fixedLevels.size() - 1; i > 0; --i)
816
0
                    {
817
0
                        if (fixedLevels[i] <= dfMaximum)
818
0
                        {
819
0
                            dfMaximum = fixedLevels[i + 1];
820
0
                            break;
821
0
                        }
822
0
                    }
823
0
                }
824
825
                // If the maximum raster value is equal to the last requested
826
                // level, add a small value to the maximum to avoid skipping the
827
                // last level polygons
828
0
                if (maxLevel == dfMaximum)
829
0
                {
830
0
                    dfMaximum = std::nextafter(
831
0
                        dfMaximum, std::numeric_limits<double>::infinity());
832
0
                }
833
0
            }
834
835
0
            PolygonContourWriter w(&oCWI, dfMinimum);
836
0
            typedef PolygonRingAppender<PolygonContourWriter> RingAppender;
837
            // Ring coordinates reach the appender in raster space (the
838
            // writer applies the geotransform), so the spatial index's
839
            // domain is the raster extent plus the border cells.
840
0
            RingAppender appender(w, -1.0, -1.0,
841
0
                                  GDALGetRasterBandXSize(hBand) + 1.0,
842
0
                                  GDALGetRasterBandYSize(hBand) + 1.0);
843
844
0
            if (expBase > 0.0)
845
0
            {
846
                // Do not provide the actual minimum value to level iterator
847
                // in polygonal case, otherwise it can result in a polygon
848
                // with a degenerate min=max range.
849
0
                ExponentialLevelRangeIterator generator(
850
0
                    expBase, -std::numeric_limits<double>::infinity());
851
0
                auto levelIt{generator.range(dfMinimum, dfMaximum)};
852
0
                for (auto i = levelIt.begin(); i != levelIt.end(); ++i)
853
0
                {
854
0
                    const double level = (*i).second;
855
0
                    fixedLevels.push_back(level);
856
0
                }
857
                // Append minimum value to fixed levels
858
0
                fixedLevels.push_back(dfMinimum);
859
                // Append maximum value to fixed levels
860
0
                fixedLevels.push_back(dfMaximum);
861
0
            }
862
0
            else if (contourInterval != 0)
863
0
            {
864
                // Do not provide the actual minimum value to level iterator
865
                // in polygonal case, otherwise it can result in a polygon
866
                // with a degenerate min=max range.
867
0
                IntervalLevelRangeIterator generator(
868
0
                    contourBase, contourInterval,
869
0
                    -std::numeric_limits<double>::infinity());
870
0
                auto levelIt{generator.range(dfMinimum, dfMaximum)};
871
0
                for (auto i = levelIt.begin(); i != levelIt.end(); ++i)
872
0
                {
873
0
                    const double level = (*i).second;
874
0
                    fixedLevels.push_back(level);
875
0
                }
876
                // Append minimum value to fixed levels
877
0
                fixedLevels.push_back(dfMinimum);
878
                // Append maximum value to fixed levels
879
0
                fixedLevels.push_back(dfMaximum);
880
0
            }
881
882
0
            if (!fixedLevels.empty())
883
0
            {
884
0
                std::sort(fixedLevels.begin(), fixedLevels.end());
885
0
                auto uniqueIt =
886
0
                    std::unique(fixedLevels.begin(), fixedLevels.end());
887
0
                fixedLevels.erase(uniqueIt, fixedLevels.end());
888
                // Do not provide the actual minimum value to level iterator
889
                // in polygonal case, otherwise it can result in a polygon
890
                // with a degenerate min=max range.
891
0
                FixedLevelRangeIterator levels(
892
0
                    &fixedLevels[0], fixedLevels.size(),
893
0
                    -std::numeric_limits<double>::infinity(), dfMaximum);
894
0
                SegmentMerger<RingAppender, FixedLevelRangeIterator> writer(
895
0
                    appender, levels, /* polygonize */ true);
896
0
                std::vector<int> aoiSkipLevels;
897
                // Skip first and last levels (min/max) in polygonal case
898
0
                aoiSkipLevels.push_back(0);
899
0
                aoiSkipLevels.push_back(static_cast<int>(levels.levelsCount()));
900
0
                writer.setSkipLevels(aoiSkipLevels);
901
0
                ContourGeneratorFromRaster<decltype(writer),
902
0
                                           FixedLevelRangeIterator>
903
0
                    cg(hBand, useNoData, noDataValue, writer, levels);
904
0
                ok = cg.process(pfnProgress, pProgressArg);
905
0
            }
906
0
        }
907
0
        else
908
0
        {
909
0
            GDALRingAppender appender(OGRContourWriter, &oCWI);
910
911
            // Append all exp levels to fixed levels
912
0
            if (expBase > 0.0)
913
0
            {
914
0
                ExponentialLevelRangeIterator generator(expBase, dfMinimum);
915
0
                auto levelIt{generator.range(dfMinimum, dfMaximum)};
916
0
                for (auto i = levelIt.begin(); i != levelIt.end(); ++i)
917
0
                {
918
0
                    const double level = (*i).second;
919
0
                    fixedLevels.push_back(level);
920
0
                }
921
0
            }
922
0
            else if (contourInterval != 0)
923
0
            {
924
0
                IntervalLevelRangeIterator levels(contourBase, contourInterval,
925
0
                                                  dfMinimum);
926
0
                auto levelIt{levels.range(dfMinimum, dfMaximum)};
927
0
                for (auto i = levelIt.begin(); i != levelIt.end(); ++i)
928
0
                {
929
0
                    const double level = (*i).second;
930
0
                    fixedLevels.push_back(level);
931
0
                }
932
0
            }
933
934
0
            if (!fixedLevels.empty())
935
0
            {
936
0
                std::sort(fixedLevels.begin(), fixedLevels.end());
937
0
                auto uniqueIt =
938
0
                    std::unique(fixedLevels.begin(), fixedLevels.end());
939
0
                fixedLevels.erase(uniqueIt, fixedLevels.end());
940
0
                FixedLevelRangeIterator levels(
941
0
                    &fixedLevels[0], fixedLevels.size(), dfMinimum, dfMaximum);
942
0
                SegmentMerger<GDALRingAppender, FixedLevelRangeIterator> writer(
943
0
                    appender, levels, /* polygonize */ false);
944
0
                ContourGeneratorFromRaster<decltype(writer),
945
0
                                           FixedLevelRangeIterator>
946
0
                    cg(hBand, useNoData, noDataValue, writer, levels);
947
0
                ok = cg.process(pfnProgress, pProgressArg);
948
0
            }
949
0
        }
950
0
    }
951
0
    catch (const std::exception &e)
952
0
    {
953
0
        CPLError(CE_Failure, CPLE_AppDefined, "%s", e.what());
954
0
        return CE_Failure;
955
0
    }
956
957
0
    if (oCWI.nTransactionCommitInterval > 0)
958
0
    {
959
        // CPLDebug("CONTOUR", "Flush transaction");
960
0
        if (OGR_L_CommitTransaction(static_cast<OGRLayerH>(hLayer)) !=
961
0
            OGRERR_NONE)
962
0
        {
963
0
            ok = false;
964
0
        }
965
0
    }
966
967
0
    if (ok)
968
0
    {
969
0
        pfnProgress(1.0, "", pProgressArg);
970
0
    }
971
972
0
    return ok ? CE_None : CE_Failure;
973
0
}
974
975
/************************************************************************/
976
/*                           GDAL_CG_Create()                           */
977
/************************************************************************/
978
979
namespace marching_squares
980
{
981
982
// Opaque type used by the C API
983
struct ContourGeneratorOpaque
984
{
985
    typedef SegmentMerger<GDALRingAppender, IntervalLevelRangeIterator>
986
        SegmentMergerT;
987
    typedef ContourGenerator<SegmentMergerT, IntervalLevelRangeIterator>
988
        ContourGeneratorT;
989
990
    ContourGeneratorOpaque(int nWidth, int nHeight, int bNoDataSet,
991
                           double dfNoDataValue, double dfContourInterval,
992
                           double dfContourBase, GDALContourWriter pfnWriter,
993
                           void *pCBData)
994
0
        : levels(dfContourBase, dfContourInterval,
995
0
                 -std::numeric_limits<double>::infinity()),
996
0
          writer(pfnWriter, pCBData),
997
0
          merger(writer, levels, /* polygonize */ false),
998
0
          contourGenerator(nWidth, nHeight, bNoDataSet != 0, dfNoDataValue,
999
0
                           merger, levels)
1000
0
    {
1001
0
    }
1002
1003
    IntervalLevelRangeIterator levels;
1004
    GDALRingAppender writer;
1005
    SegmentMergerT merger;
1006
    ContourGeneratorT contourGenerator;
1007
};
1008
1009
}  // namespace marching_squares
1010
1011
/** Create contour generator */
1012
GDALContourGeneratorH GDAL_CG_Create(int nWidth, int nHeight, int bNoDataSet,
1013
                                     double dfNoDataValue,
1014
                                     double dfContourInterval,
1015
                                     double dfContourBase,
1016
                                     GDALContourWriter pfnWriter, void *pCBData)
1017
1018
0
{
1019
0
    auto cg = new marching_squares::ContourGeneratorOpaque(
1020
0
        nWidth, nHeight, bNoDataSet, dfNoDataValue, dfContourInterval,
1021
0
        dfContourBase, pfnWriter, pCBData);
1022
1023
0
    return reinterpret_cast<GDALContourGeneratorH>(cg);
1024
0
}
1025
1026
/************************************************************************/
1027
/*                          GDAL_CG_FeedLine()                          */
1028
/************************************************************************/
1029
1030
/** Feed a line to the contour generator */
1031
CPLErr GDAL_CG_FeedLine(GDALContourGeneratorH hCG, double *padfScanline)
1032
1033
0
{
1034
0
    VALIDATE_POINTER1(hCG, "GDAL_CG_FeedLine", CE_Failure);
1035
0
    return reinterpret_cast<marching_squares::ContourGeneratorOpaque *>(hCG)
1036
0
        ->contourGenerator.feedLine(padfScanline);
1037
0
}
1038
1039
/************************************************************************/
1040
/*                          GDAL_CG_Destroy()                           */
1041
/************************************************************************/
1042
1043
/** Destroy contour generator */
1044
void GDAL_CG_Destroy(GDALContourGeneratorH hCG)
1045
1046
0
{
1047
0
    delete reinterpret_cast<marching_squares::ContourGeneratorOpaque *>(hCG);
1048
0
}