Coverage Report

Created: 2025-06-13 06:18

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