Coverage Report

Created: 2026-02-14 06:52

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
            return CE_Failure;
731
0
        }
732
0
    }
733
734
0
    using namespace marching_squares;
735
736
0
    OGRContourWriterInfo oCWI;
737
0
    oCWI.hLayer = static_cast<OGRLayerH>(hLayer);
738
0
    oCWI.nElevField = elevField;
739
0
    oCWI.nElevFieldMin = elevFieldMin;
740
0
    oCWI.nElevFieldMax = elevFieldMax;
741
0
    oCWI.nIDField = idField;
742
0
    oCWI.adfGeoTransform[0] = 0.0;
743
0
    oCWI.adfGeoTransform[1] = 1.0;
744
0
    oCWI.adfGeoTransform[2] = 0.0;
745
0
    oCWI.adfGeoTransform[3] = 0.0;
746
0
    oCWI.adfGeoTransform[4] = 0.0;
747
0
    oCWI.adfGeoTransform[5] = 1.0;
748
0
    GDALDatasetH hSrcDS = GDALGetBandDataset(hBand);
749
0
    if (hSrcDS != nullptr)
750
0
        GDALGetGeoTransform(hSrcDS, oCWI.adfGeoTransform);
751
0
    oCWI.nNextID = 0;
752
0
    oCWI.nWrittenFeatureCountSinceLastCommit = 0;
753
0
    oCWI.nTransactionCommitInterval =
754
0
        CPLAtoGIntBig(CSLFetchNameValueDef(options, "COMMIT_INTERVAL", "0"));
755
756
0
    if (oCWI.nTransactionCommitInterval < 0)
757
0
        oCWI.nTransactionCommitInterval = std::numeric_limits<GIntBig>::max();
758
0
    if (oCWI.nTransactionCommitInterval > 0)
759
0
    {
760
0
        if (OGR_L_StartTransaction(static_cast<OGRLayerH>(hLayer)) !=
761
0
            OGRERR_NONE)
762
0
        {
763
0
            oCWI.nTransactionCommitInterval = 0;
764
0
        }
765
0
    }
766
767
0
    bool ok = true;
768
769
    // Replace fixed levels min/max values with raster min/max values
770
0
    if (!fixedLevels.empty())
771
0
    {
772
0
        if (fixedLevels[0] == std::numeric_limits<double>::lowest())
773
0
        {
774
0
            fixedLevels[0] = dfMinimum;
775
0
        }
776
0
        if (fixedLevels.back() == std::numeric_limits<double>::max())
777
0
        {
778
0
            fixedLevels.back() = dfMaximum;
779
0
        }
780
0
    }
781
782
0
    try
783
0
    {
784
0
        if (polygonize)
785
0
        {
786
787
0
            if (!fixedLevels.empty())
788
0
            {
789
790
                // If the minimum raster value is larger than the first requested
791
                // level, select the requested level that is just below the
792
                // minimum raster value
793
0
                if (fixedLevels[0] < dfMinimum)
794
0
                {
795
0
                    for (size_t i = 1; i < fixedLevels.size(); ++i)
796
0
                    {
797
0
                        if (fixedLevels[i] >= dfMinimum)
798
0
                        {
799
0
                            dfMinimum = fixedLevels[i - 1];
800
0
                            break;
801
0
                        }
802
0
                    }
803
0
                }
804
805
                // Largest requested level (levels are sorted)
806
0
                const double maxLevel{fixedLevels.back()};
807
808
                // If the maximum raster value is smaller than the last requested
809
                // level, select the requested level that is just above the
810
                // maximum raster value
811
0
                if (maxLevel > dfMaximum)
812
0
                {
813
0
                    for (size_t i = fixedLevels.size() - 1; i > 0; --i)
814
0
                    {
815
0
                        if (fixedLevels[i] <= dfMaximum)
816
0
                        {
817
0
                            dfMaximum = fixedLevels[i + 1];
818
0
                            break;
819
0
                        }
820
0
                    }
821
0
                }
822
823
                // If the maximum raster value is equal to the last requested
824
                // level, add a small value to the maximum to avoid skipping the
825
                // last level polygons
826
0
                if (maxLevel == dfMaximum)
827
0
                {
828
0
                    dfMaximum = std::nextafter(
829
0
                        dfMaximum, std::numeric_limits<double>::infinity());
830
0
                }
831
0
            }
832
833
0
            PolygonContourWriter w(&oCWI, dfMinimum);
834
0
            typedef PolygonRingAppender<PolygonContourWriter> RingAppender;
835
0
            RingAppender appender(w);
836
837
0
            if (expBase > 0.0)
838
0
            {
839
                // Do not provide the actual minimum value to level iterator
840
                // in polygonal case, otherwise it can result in a polygon
841
                // with a degenerate min=max range.
842
0
                ExponentialLevelRangeIterator generator(
843
0
                    expBase, -std::numeric_limits<double>::infinity());
844
0
                auto levelIt{generator.range(dfMinimum, dfMaximum)};
845
0
                for (auto i = levelIt.begin(); i != levelIt.end(); ++i)
846
0
                {
847
0
                    const double level = (*i).second;
848
0
                    fixedLevels.push_back(level);
849
0
                }
850
                // Append minimum value to fixed levels
851
0
                fixedLevels.push_back(dfMinimum);
852
                // Append maximum value to fixed levels
853
0
                fixedLevels.push_back(dfMaximum);
854
0
            }
855
0
            else if (contourInterval != 0)
856
0
            {
857
                // Do not provide the actual minimum value to level iterator
858
                // in polygonal case, otherwise it can result in a polygon
859
                // with a degenerate min=max range.
860
0
                IntervalLevelRangeIterator generator(
861
0
                    contourBase, contourInterval,
862
0
                    -std::numeric_limits<double>::infinity());
863
0
                auto levelIt{generator.range(dfMinimum, dfMaximum)};
864
0
                for (auto i = levelIt.begin(); i != levelIt.end(); ++i)
865
0
                {
866
0
                    const double level = (*i).second;
867
0
                    fixedLevels.push_back(level);
868
0
                }
869
                // Append minimum value to fixed levels
870
0
                fixedLevels.push_back(dfMinimum);
871
                // Append maximum value to fixed levels
872
0
                fixedLevels.push_back(dfMaximum);
873
0
            }
874
875
0
            if (!fixedLevels.empty())
876
0
            {
877
0
                std::sort(fixedLevels.begin(), fixedLevels.end());
878
0
                auto uniqueIt =
879
0
                    std::unique(fixedLevels.begin(), fixedLevels.end());
880
0
                fixedLevels.erase(uniqueIt, fixedLevels.end());
881
                // Do not provide the actual minimum value to level iterator
882
                // in polygonal case, otherwise it can result in a polygon
883
                // with a degenerate min=max range.
884
0
                FixedLevelRangeIterator levels(
885
0
                    &fixedLevels[0], fixedLevels.size(),
886
0
                    -std::numeric_limits<double>::infinity(), dfMaximum);
887
0
                SegmentMerger<RingAppender, FixedLevelRangeIterator> writer(
888
0
                    appender, levels, /* polygonize */ true);
889
0
                std::vector<int> aoiSkipLevels;
890
                // Skip first and last levels (min/max) in polygonal case
891
0
                aoiSkipLevels.push_back(0);
892
0
                aoiSkipLevels.push_back(static_cast<int>(levels.levelsCount()));
893
0
                writer.setSkipLevels(aoiSkipLevels);
894
0
                ContourGeneratorFromRaster<decltype(writer),
895
0
                                           FixedLevelRangeIterator>
896
0
                    cg(hBand, useNoData, noDataValue, writer, levels);
897
0
                ok = cg.process(pfnProgress, pProgressArg);
898
0
            }
899
0
        }
900
0
        else
901
0
        {
902
0
            GDALRingAppender appender(OGRContourWriter, &oCWI);
903
904
            // Append all exp levels to fixed levels
905
0
            if (expBase > 0.0)
906
0
            {
907
0
                ExponentialLevelRangeIterator generator(expBase, dfMinimum);
908
0
                auto levelIt{generator.range(dfMinimum, dfMaximum)};
909
0
                for (auto i = levelIt.begin(); i != levelIt.end(); ++i)
910
0
                {
911
0
                    const double level = (*i).second;
912
0
                    fixedLevels.push_back(level);
913
0
                }
914
0
            }
915
0
            else if (contourInterval != 0)
916
0
            {
917
0
                IntervalLevelRangeIterator levels(contourBase, contourInterval,
918
0
                                                  dfMinimum);
919
0
                auto levelIt{levels.range(dfMinimum, dfMaximum)};
920
0
                for (auto i = levelIt.begin(); i != levelIt.end(); ++i)
921
0
                {
922
0
                    const double level = (*i).second;
923
0
                    fixedLevels.push_back(level);
924
0
                }
925
0
            }
926
927
0
            if (!fixedLevels.empty())
928
0
            {
929
0
                std::sort(fixedLevels.begin(), fixedLevels.end());
930
0
                auto uniqueIt =
931
0
                    std::unique(fixedLevels.begin(), fixedLevels.end());
932
0
                fixedLevels.erase(uniqueIt, fixedLevels.end());
933
0
                FixedLevelRangeIterator levels(
934
0
                    &fixedLevels[0], fixedLevels.size(), dfMinimum, dfMaximum);
935
0
                SegmentMerger<GDALRingAppender, FixedLevelRangeIterator> writer(
936
0
                    appender, levels, /* polygonize */ false);
937
0
                ContourGeneratorFromRaster<decltype(writer),
938
0
                                           FixedLevelRangeIterator>
939
0
                    cg(hBand, useNoData, noDataValue, writer, levels);
940
0
                ok = cg.process(pfnProgress, pProgressArg);
941
0
            }
942
0
        }
943
0
    }
944
0
    catch (const std::exception &e)
945
0
    {
946
0
        CPLError(CE_Failure, CPLE_AppDefined, "%s", e.what());
947
0
        return CE_Failure;
948
0
    }
949
950
0
    if (oCWI.nTransactionCommitInterval > 0)
951
0
    {
952
        // CPLDebug("CONTOUR", "Flush transaction");
953
0
        if (OGR_L_CommitTransaction(static_cast<OGRLayerH>(hLayer)) !=
954
0
            OGRERR_NONE)
955
0
        {
956
0
            ok = false;
957
0
        }
958
0
    }
959
960
0
    if (ok)
961
0
    {
962
0
        pfnProgress(1.0, "", pProgressArg);
963
0
    }
964
965
0
    return ok ? CE_None : CE_Failure;
966
0
}
967
968
/************************************************************************/
969
/*                           GDAL_CG_Create()                           */
970
/************************************************************************/
971
972
namespace marching_squares
973
{
974
975
// Opaque type used by the C API
976
struct ContourGeneratorOpaque
977
{
978
    typedef SegmentMerger<GDALRingAppender, IntervalLevelRangeIterator>
979
        SegmentMergerT;
980
    typedef ContourGenerator<SegmentMergerT, IntervalLevelRangeIterator>
981
        ContourGeneratorT;
982
983
    ContourGeneratorOpaque(int nWidth, int nHeight, int bNoDataSet,
984
                           double dfNoDataValue, double dfContourInterval,
985
                           double dfContourBase, GDALContourWriter pfnWriter,
986
                           void *pCBData)
987
0
        : levels(dfContourBase, dfContourInterval,
988
0
                 -std::numeric_limits<double>::infinity()),
989
0
          writer(pfnWriter, pCBData),
990
0
          merger(writer, levels, /* polygonize */ false),
991
0
          contourGenerator(nWidth, nHeight, bNoDataSet != 0, dfNoDataValue,
992
0
                           merger, levels)
993
0
    {
994
0
    }
995
996
    IntervalLevelRangeIterator levels;
997
    GDALRingAppender writer;
998
    SegmentMergerT merger;
999
    ContourGeneratorT contourGenerator;
1000
};
1001
1002
}  // namespace marching_squares
1003
1004
/** Create contour generator */
1005
GDALContourGeneratorH GDAL_CG_Create(int nWidth, int nHeight, int bNoDataSet,
1006
                                     double dfNoDataValue,
1007
                                     double dfContourInterval,
1008
                                     double dfContourBase,
1009
                                     GDALContourWriter pfnWriter, void *pCBData)
1010
1011
0
{
1012
0
    auto cg = new marching_squares::ContourGeneratorOpaque(
1013
0
        nWidth, nHeight, bNoDataSet, dfNoDataValue, dfContourInterval,
1014
0
        dfContourBase, pfnWriter, pCBData);
1015
1016
0
    return reinterpret_cast<GDALContourGeneratorH>(cg);
1017
0
}
1018
1019
/************************************************************************/
1020
/*                          GDAL_CG_FeedLine()                          */
1021
/************************************************************************/
1022
1023
/** Feed a line to the contour generator */
1024
CPLErr GDAL_CG_FeedLine(GDALContourGeneratorH hCG, double *padfScanline)
1025
1026
0
{
1027
0
    VALIDATE_POINTER1(hCG, "GDAL_CG_FeedLine", CE_Failure);
1028
0
    return reinterpret_cast<marching_squares::ContourGeneratorOpaque *>(hCG)
1029
0
        ->contourGenerator.feedLine(padfScanline);
1030
0
}
1031
1032
/************************************************************************/
1033
/*                          GDAL_CG_Destroy()                           */
1034
/************************************************************************/
1035
1036
/** Destroy contour generator */
1037
void GDAL_CG_Destroy(GDALContourGeneratorH hCG)
1038
1039
0
{
1040
0
    delete reinterpret_cast<marching_squares::ContourGeneratorOpaque *>(hCG);
1041
0
}