Coverage Report

Created: 2025-06-13 06:18

/src/gdal/alg/marching_squares/square.h
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 *
3
 * Project:  Marching square algorithm
4
 * Purpose:  Core algorithm implementation for contour line generation.
5
 * Author:   Oslandia <infos at oslandia dot com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2018, Oslandia <infos at oslandia dot com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
#ifndef MARCHING_SQUARE_SQUARE_H
13
#define MARCHING_SQUARE_SQUARE_H
14
15
#include <algorithm>
16
#include <cassert>
17
#include <cmath>
18
#include <cstdint>
19
#include <math.h>
20
#include "utility.h"
21
#include "point.h"
22
23
namespace marching_squares
24
{
25
26
struct Square
27
{
28
    // Bit flags to determine borders around pixel
29
    static const uint8_t NO_BORDER = 0;          // 0000 0000
30
    static const uint8_t LEFT_BORDER = 1 << 0;   // 0000 0001
31
    static const uint8_t LOWER_BORDER = 1 << 1;  // 0000 0010
32
    static const uint8_t RIGHT_BORDER = 1 << 2;  // 0000 0100
33
    static const uint8_t UPPER_BORDER = 1 << 3;  // 0000 1000
34
35
    // Bit flags for marching square case
36
    static const uint8_t ALL_LOW = 0;           // 0000 0000
37
    static const uint8_t UPPER_LEFT = 1 << 0;   // 0000 0001
38
    static const uint8_t LOWER_LEFT = 1 << 1;   // 0000 0010
39
    static const uint8_t LOWER_RIGHT = 1 << 2;  // 0000 0100
40
    static const uint8_t UPPER_RIGHT = 1 << 3;  // 0000 1000
41
    static const uint8_t ALL_HIGH =
42
        UPPER_LEFT | LOWER_LEFT | LOWER_RIGHT | UPPER_RIGHT;    // 0000 1111
43
    static const uint8_t SADDLE_NW = UPPER_LEFT | LOWER_RIGHT;  // 0000 0101
44
    static const uint8_t SADDLE_NE = UPPER_RIGHT | LOWER_LEFT;  // 0000 1010
45
46
    typedef std::pair<Point, Point> Segment;
47
    typedef std::pair<ValuedPoint, ValuedPoint> ValuedSegment;
48
49
    //
50
    // An array of segments, at most 3 segments
51
    struct Segments
52
    {
53
0
        Segments() : sz_(0)
54
0
        {
55
0
        }
56
57
0
        Segments(const Segment &first) : sz_(1), segs_()
58
0
        {
59
0
            segs_[0] = first;
60
0
        }
61
62
0
        Segments(const Segment &first, const Segment &second) : sz_(2), segs_()
63
0
        {
64
0
            segs_[0] = first;
65
0
            segs_[1] = second;
66
0
        }
67
68
        Segments(const Segment &first, const Segment &second,
69
                 const Segment &third)
70
            : sz_(3), segs_()
71
0
        {
72
0
            segs_[0] = first;
73
0
            segs_[1] = second;
74
0
            segs_[2] = third;
75
0
        }
76
77
        std::size_t size() const
78
0
        {
79
0
            return sz_;
80
0
        }
81
82
        const Segment &operator[](std::size_t idx) const
83
0
        {
84
0
            assert(idx < sz_);
85
0
            return segs_[idx];
86
0
        }
87
88
      private:
89
        const std::size_t sz_;
90
        /* const */ Segment segs_[3];
91
    };
92
93
    Square(const ValuedPoint &upperLeft_, const ValuedPoint &upperRight_,
94
           const ValuedPoint &lowerLeft_, const ValuedPoint &lowerRight_,
95
           uint8_t borders_ = NO_BORDER, bool split_ = false)
96
0
        : upperLeft(upperLeft_), lowerLeft(lowerLeft_), lowerRight(lowerRight_),
97
0
          upperRight(upperRight_),
98
0
          nanCount((std::isnan(upperLeft.value) ? 1 : 0) +
99
0
                   (std::isnan(upperRight.value) ? 1 : 0) +
100
0
                   (std::isnan(lowerLeft.value) ? 1 : 0) +
101
0
                   (std::isnan(lowerRight.value) ? 1 : 0)),
102
0
          borders(borders_), split(split_)
103
0
    {
104
0
        assert(upperLeft.y == upperRight.y);
105
0
        assert(lowerLeft.y == lowerRight.y);
106
0
        assert(lowerLeft.x == upperLeft.x);
107
0
        assert(lowerRight.x == upperRight.x);
108
0
        assert(!split || nanCount == 0);
109
0
    }
110
111
    Square upperLeftSquare() const
112
0
    {
113
0
        assert(!std::isnan(upperLeft.value));
114
0
        return Square(
115
0
            upperLeft, upperCenter(), leftCenter(), center(),
116
0
            (std::isnan(upperRight.value) ? RIGHT_BORDER : NO_BORDER) |
117
0
                (std::isnan(lowerLeft.value) ? LOWER_BORDER : NO_BORDER),
118
0
            true);
119
0
    }
120
121
    Square lowerLeftSquare() const
122
0
    {
123
0
        assert(!std::isnan(lowerLeft.value));
124
0
        return Square(
125
0
            leftCenter(), center(), lowerLeft, lowerCenter(),
126
0
            (std::isnan(lowerRight.value) ? RIGHT_BORDER : NO_BORDER) |
127
0
                (std::isnan(upperLeft.value) ? UPPER_BORDER : NO_BORDER),
128
0
            true);
129
0
    }
130
131
    Square lowerRightSquare() const
132
0
    {
133
0
        assert(!std::isnan(lowerRight.value));
134
0
        return Square(
135
0
            center(), rightCenter(), lowerCenter(), lowerRight,
136
0
            (std::isnan(lowerLeft.value) ? LEFT_BORDER : NO_BORDER) |
137
0
                (std::isnan(upperRight.value) ? UPPER_BORDER : NO_BORDER),
138
0
            true);
139
0
    }
140
141
    Square upperRightSquare() const
142
0
    {
143
0
        assert(!std::isnan(upperRight.value));
144
0
        return Square(
145
0
            upperCenter(), upperRight, center(), rightCenter(),
146
0
            (std::isnan(lowerRight.value) ? LOWER_BORDER : NO_BORDER) |
147
0
                (std::isnan(upperLeft.value) ? LEFT_BORDER : NO_BORDER),
148
0
            true);
149
0
    }
150
151
    double maxValue() const
152
0
    {
153
0
        assert(nanCount == 0);
154
0
        return std::max(std::max(upperLeft.value, upperRight.value),
155
0
                        std::max(lowerLeft.value, lowerRight.value));
156
0
    }
157
158
    double minValue() const
159
0
    {
160
0
        assert(nanCount == 0);
161
0
        return std::min(std::min(upperLeft.value, upperRight.value),
162
0
                        std::min(lowerLeft.value, lowerRight.value));
163
0
    }
164
165
    ValuedSegment segment(uint8_t border) const
166
0
    {
167
0
        switch (border)
168
0
        {
169
0
            case LEFT_BORDER:
170
0
                return ValuedSegment(upperLeft, lowerLeft);
171
0
            case LOWER_BORDER:
172
0
                return ValuedSegment(lowerLeft, lowerRight);
173
0
            case RIGHT_BORDER:
174
0
                return ValuedSegment(lowerRight, upperRight);
175
0
            case UPPER_BORDER:
176
0
                return ValuedSegment(upperRight, upperLeft);
177
0
        }
178
0
        assert(false);
179
0
        return ValuedSegment(upperLeft, upperLeft);
180
0
    }
181
182
    // returns segments of contour
183
    //
184
    // segments are oriented:
185
    //   - they form a vector from their first point to their second point.
186
    //   - when looking at the vector upward, values greater than the level are
187
    //   on the right
188
    //
189
    //     ^
190
    //  -  |  +
191
    Segments segments(double level, double minLevel) const
192
0
    {
193
0
        switch (marchingCase(level, minLevel))
194
0
        {
195
0
            case (ALL_LOW):
196
                // debug("ALL_LOW");
197
0
                return Segments();
198
0
            case (ALL_HIGH):
199
                // debug("ALL_HIGH");
200
0
                return Segments();
201
0
            case (UPPER_LEFT):
202
                // debug("UPPER_LEFT");
203
0
                return Segments(
204
0
                    Segment(interpolate(UPPER_BORDER, level, minLevel),
205
0
                            interpolate(LEFT_BORDER, level, minLevel)));
206
0
            case (LOWER_LEFT):
207
                // debug("LOWER_LEFT");
208
0
                return Segments(
209
0
                    Segment(interpolate(LEFT_BORDER, level, minLevel),
210
0
                            interpolate(LOWER_BORDER, level, minLevel)));
211
0
            case (LOWER_RIGHT):
212
                // debug("LOWER_RIGHT");
213
0
                return Segments(
214
0
                    Segment(interpolate(LOWER_BORDER, level, minLevel),
215
0
                            interpolate(RIGHT_BORDER, level, minLevel)));
216
0
            case (UPPER_RIGHT):
217
                // debug("UPPER_RIGHT");
218
0
                return Segments(
219
0
                    Segment(interpolate(RIGHT_BORDER, level, minLevel),
220
0
                            interpolate(UPPER_BORDER, level, minLevel)));
221
0
            case (UPPER_LEFT | LOWER_LEFT):
222
                // debug("UPPER_LEFT | LOWER_LEFT");
223
0
                return Segments(
224
0
                    Segment(interpolate(UPPER_BORDER, level, minLevel),
225
0
                            interpolate(LOWER_BORDER, level, minLevel)));
226
0
            case (LOWER_LEFT | LOWER_RIGHT):
227
                // debug("LOWER_LEFT | LOWER_RIGHT");
228
0
                return Segments(
229
0
                    Segment(interpolate(LEFT_BORDER, level, minLevel),
230
0
                            interpolate(RIGHT_BORDER, level, minLevel)));
231
0
            case (LOWER_RIGHT | UPPER_RIGHT):
232
                // debug("LOWER_RIGHT | UPPER_RIGHT");
233
0
                return Segments(
234
0
                    Segment(interpolate(LOWER_BORDER, level, minLevel),
235
0
                            interpolate(UPPER_BORDER, level, minLevel)));
236
0
            case (UPPER_RIGHT | UPPER_LEFT):
237
                // debug("UPPER_RIGHT | UPPER_LEFT");
238
0
                return Segments(
239
0
                    Segment(interpolate(RIGHT_BORDER, level, minLevel),
240
0
                            interpolate(LEFT_BORDER, level, minLevel)));
241
0
            case (ALL_HIGH & ~UPPER_LEFT):
242
                // debug("ALL_HIGH & ~UPPER_LEFT");
243
0
                return Segments(
244
0
                    Segment(interpolate(LEFT_BORDER, level, minLevel),
245
0
                            interpolate(UPPER_BORDER, level, minLevel)));
246
0
            case (ALL_HIGH & ~LOWER_LEFT):
247
                // debug("ALL_HIGH & ~LOWER_LEFT");
248
0
                return Segments(
249
0
                    Segment(interpolate(LOWER_BORDER, level, minLevel),
250
0
                            interpolate(LEFT_BORDER, level, minLevel)));
251
0
            case (ALL_HIGH & ~LOWER_RIGHT):
252
                // debug("ALL_HIGH & ~LOWER_RIGHT");
253
0
                return Segments(
254
0
                    Segment(interpolate(RIGHT_BORDER, level, minLevel),
255
0
                            interpolate(LOWER_BORDER, level, minLevel)));
256
0
            case (ALL_HIGH & ~UPPER_RIGHT):
257
                // debug("ALL_HIGH & ~UPPER_RIGHT");
258
0
                return Segments(
259
0
                    Segment(interpolate(UPPER_BORDER, level, minLevel),
260
0
                            interpolate(RIGHT_BORDER, level, minLevel)));
261
0
            case (SADDLE_NE):
262
0
            case (SADDLE_NW):
263
                // From the two possible saddle configurations, we always return
264
                // the same one.
265
                //
266
                // The classical marching square algorithm says the ambiguity
267
                // should be resolved between the two possible configurations by
268
                // looking at the value of the center point. But in certain
269
                // cases, this may lead to line contours from different levels
270
                // that cross each other and then gives invalid polygons.
271
                //
272
                // Arbitrarily choosing one of the two possible configurations
273
                // is not really that worse than deciding based on the center
274
                // point.
275
0
                return Segments(
276
0
                    Segment(interpolate(LEFT_BORDER, level, minLevel),
277
0
                            interpolate(LOWER_BORDER, level, minLevel)),
278
0
                    Segment(interpolate(RIGHT_BORDER, level, minLevel),
279
0
                            interpolate(UPPER_BORDER, level, minLevel)));
280
0
        }
281
0
        assert(false);
282
0
        return Segments();
283
0
    }
284
285
    template <typename Writer, typename LevelGenerator>
286
    void process(const LevelGenerator &levelGenerator, Writer &writer) const
287
0
    {
288
0
        if (nanCount == 4)  // nothing to do
289
0
            return;
290
291
0
        if (nanCount)  // split in 4
292
0
        {
293
0
            if (!std::isnan(upperLeft.value))
294
0
                upperLeftSquare().process(levelGenerator, writer);
295
0
            if (!std::isnan(upperRight.value))
296
0
                upperRightSquare().process(levelGenerator, writer);
297
0
            if (!std::isnan(lowerLeft.value))
298
0
                lowerLeftSquare().process(levelGenerator, writer);
299
0
            if (!std::isnan(lowerRight.value))
300
0
                lowerRightSquare().process(levelGenerator, writer);
301
0
            return;
302
0
        }
303
304
0
        if (writer.polygonize && borders)
305
0
        {
306
0
            for (uint8_t border :
307
0
                 {UPPER_BORDER, LEFT_BORDER, RIGHT_BORDER, LOWER_BORDER})
308
0
            {
309
                // bitwise AND to test which borders we have on the square
310
0
                if ((border & borders) == 0)
311
0
                    continue;
312
313
                // convention: for a level = L, store borders for the previous
314
                // level up to (and including) L in the border of level "L". For
315
                // fixed sets of level, this means there is an "Inf" slot for
316
                // borders of the highest level
317
0
                const ValuedSegment s(segment(border));
318
319
0
                Point lastPoint(s.first.x, s.first.y);
320
0
                Point endPoint(s.second.x, s.second.y);
321
0
                if (s.first.value > s.second.value)
322
0
                    std::swap(lastPoint, endPoint);
323
0
                bool reverse =
324
0
                    (s.first.value > s.second.value) &&
325
0
                    ((border == UPPER_BORDER) || (border == LEFT_BORDER));
326
327
0
                auto levelIt =
328
0
                    levelGenerator.range(s.first.value, s.second.value);
329
330
0
                auto it = levelIt.begin();  // reused after the for
331
0
                for (; it != levelIt.end(); ++it)
332
0
                {
333
0
                    const int levelIdx = (*it).first;
334
0
                    const double level = (*it).second;
335
336
0
                    const Point nextPoint(
337
0
                        interpolate(border, level, levelGenerator.minLevel()));
338
0
                    if (reverse)
339
0
                        writer.addBorderSegment(levelIdx, nextPoint, lastPoint);
340
0
                    else
341
0
                        writer.addBorderSegment(levelIdx, lastPoint, nextPoint);
342
0
                    lastPoint = nextPoint;
343
0
                }
344
                // last level (past the end)
345
0
                if (reverse)
346
0
                    writer.addBorderSegment((*it).first, endPoint, lastPoint);
347
0
                else
348
0
                    writer.addBorderSegment((*it).first, lastPoint, endPoint);
349
0
            }
350
0
        }
351
352
0
        auto range = levelGenerator.range(minValue(), maxValue());
353
0
        auto it = range.begin();
354
0
        auto itEnd = range.end();
355
0
        auto next = range.begin();
356
0
        ++next;
357
358
0
        for (; it != itEnd; ++it, ++next)
359
0
        {
360
0
            const int levelIdx = (*it).first;
361
0
            const double level = (*it).second;
362
363
0
            const Segments segments_ =
364
0
                segments(level, levelGenerator.minLevel());
365
366
0
            for (std::size_t i = 0; i < segments_.size(); i++)
367
0
            {
368
0
                const Segment &s = segments_[i];
369
0
                writer.addSegment(levelIdx, s.first, s.second);
370
371
0
                if (writer.polygonize)
372
0
                {
373
                    // the contour is used in the polygon of higher level as
374
                    // well
375
                    //
376
                    // TODO: copying the segment to the higher level is easy,
377
                    // but it involves too much memory. We should reuse segment
378
                    // contours when constructing polygon rings.
379
0
                    writer.addSegment((*next).first, s.first, s.second);
380
0
                }
381
0
            }
382
0
        }
383
0
    }
Unexecuted instantiation: void marching_squares::Square::process<marching_squares::SegmentMerger<marching_squares::PolygonRingAppender<PolygonContourWriter>, marching_squares::FixedLevelRangeIterator>, marching_squares::FixedLevelRangeIterator>(marching_squares::FixedLevelRangeIterator const&, marching_squares::SegmentMerger<marching_squares::PolygonRingAppender<PolygonContourWriter>, marching_squares::FixedLevelRangeIterator>&) const
Unexecuted instantiation: void marching_squares::Square::process<marching_squares::SegmentMerger<GDALRingAppender, marching_squares::FixedLevelRangeIterator>, marching_squares::FixedLevelRangeIterator>(marching_squares::FixedLevelRangeIterator const&, marching_squares::SegmentMerger<GDALRingAppender, marching_squares::FixedLevelRangeIterator>&) const
Unexecuted instantiation: void marching_squares::Square::process<marching_squares::SegmentMerger<GDALRingAppender, marching_squares::IntervalLevelRangeIterator>, marching_squares::IntervalLevelRangeIterator>(marching_squares::IntervalLevelRangeIterator const&, marching_squares::SegmentMerger<GDALRingAppender, marching_squares::IntervalLevelRangeIterator>&) const
384
385
    const ValuedPoint upperLeft;
386
    const ValuedPoint lowerLeft;
387
    const ValuedPoint lowerRight;
388
    const ValuedPoint upperRight;
389
    const int nanCount;
390
    const uint8_t borders;
391
    const bool split;
392
393
  private:
394
    ValuedPoint center() const
395
0
    {
396
0
        return ValuedPoint(
397
0
            .5 * (upperLeft.x + lowerRight.x),
398
0
            .5 * (upperLeft.y + lowerRight.y),
399
0
            ((std::isnan(lowerLeft.value) ? 0 : lowerLeft.value) +
400
0
             (std::isnan(upperLeft.value) ? 0 : upperLeft.value) +
401
0
             (std::isnan(lowerRight.value) ? 0 : lowerRight.value) +
402
0
             (std::isnan(upperRight.value) ? 0 : upperRight.value)) /
403
0
                (4 - nanCount));
404
0
    }
405
406
    ValuedPoint leftCenter() const
407
0
    {
408
0
        return ValuedPoint(
409
0
            upperLeft.x, .5 * (upperLeft.y + lowerLeft.y),
410
0
            std::isnan(upperLeft.value)
411
0
                ? lowerLeft.value
412
0
                : (std::isnan(lowerLeft.value)
413
0
                       ? upperLeft.value
414
0
                       : .5 * (upperLeft.value + lowerLeft.value)));
415
0
    }
416
417
    ValuedPoint lowerCenter() const
418
0
    {
419
0
        return ValuedPoint(
420
0
            .5 * (lowerLeft.x + lowerRight.x), lowerLeft.y,
421
0
            std::isnan(lowerRight.value)
422
0
                ? lowerLeft.value
423
0
                : (std::isnan(lowerLeft.value)
424
0
                       ? lowerRight.value
425
0
                       : .5 * (lowerRight.value + lowerLeft.value)));
426
0
    }
427
428
    ValuedPoint rightCenter() const
429
0
    {
430
0
        return ValuedPoint(
431
0
            upperRight.x, .5 * (upperRight.y + lowerRight.y),
432
0
            std::isnan(lowerRight.value)
433
0
                ? upperRight.value
434
0
                : (std::isnan(upperRight.value)
435
0
                       ? lowerRight.value
436
0
                       : .5 * (lowerRight.value + upperRight.value)));
437
0
    }
438
439
    ValuedPoint upperCenter() const
440
0
    {
441
0
        return ValuedPoint(
442
0
            .5 * (upperLeft.x + upperRight.x), upperLeft.y,
443
0
            std::isnan(upperLeft.value)
444
0
                ? upperRight.value
445
0
                : (std::isnan(upperRight.value)
446
0
                       ? upperLeft.value
447
0
                       : .5 * (upperLeft.value + upperRight.value)));
448
0
    }
449
450
    uint8_t marchingCase(double level, double minLevel) const
451
0
    {
452
0
        return (level < fudge(upperLeft.value, minLevel, level) ? UPPER_LEFT
453
0
                                                                : ALL_LOW) |
454
0
               (level < fudge(lowerLeft.value, minLevel, level) ? LOWER_LEFT
455
0
                                                                : ALL_LOW) |
456
0
               (level < fudge(lowerRight.value, minLevel, level) ? LOWER_RIGHT
457
0
                                                                 : ALL_LOW) |
458
0
               (level < fudge(upperRight.value, minLevel, level) ? UPPER_RIGHT
459
0
                                                                 : ALL_LOW);
460
0
    }
461
462
    static double interpolate_(double level, double x1, double x2, double y1,
463
                               double y2, bool need_split, double minLevel)
464
0
    {
465
0
        if (need_split)
466
0
        {
467
            // The two cases are here to avoid numerical roundup errors, for two
468
            // points, we always compute the same interpolation. This condition
469
            // is ensured by the order left->right bottom->top in interpole
470
            // calls
471
            //
472
            // To obtain the same value for border (split) and non-border
473
            // element, we take the middle value and interpolate from this to
474
            // the end
475
0
            const double xm = .5 * (x1 + x2);
476
0
            const double ym = .5 * (y1 + y2);
477
0
            const double fy1 = fudge(y1, minLevel, level);
478
0
            const double fym = fudge(ym, minLevel, level);
479
0
            if ((fy1 < level && level < fym) || (fy1 > level && level > fym))
480
0
            {
481
0
                x2 = xm;
482
0
                y2 = ym;
483
0
            }
484
0
            else
485
0
            {
486
0
                x1 = xm;
487
0
                y1 = ym;
488
0
            }
489
0
        }
490
0
        const double fy1 = fudge(y1, minLevel, level);
491
0
        const double ratio = (level - fy1) / (fudge(y2, minLevel, level) - fy1);
492
0
        return x1 * (1. - ratio) + x2 * ratio;
493
0
    }
494
495
    Point interpolate(uint8_t border, double level, double minLevel) const
496
0
    {
497
0
        switch (border)
498
0
        {
499
0
            case LEFT_BORDER:
500
0
                return Point(upperLeft.x,
501
0
                             interpolate_(level, lowerLeft.y, upperLeft.y,
502
0
                                          lowerLeft.value, upperLeft.value,
503
0
                                          !split, minLevel));
504
0
            case LOWER_BORDER:
505
0
                return Point(interpolate_(level, lowerLeft.x, lowerRight.x,
506
0
                                          lowerLeft.value, lowerRight.value,
507
0
                                          !split, minLevel),
508
0
                             lowerLeft.y);
509
0
            case RIGHT_BORDER:
510
0
                return Point(upperRight.x,
511
0
                             interpolate_(level, lowerRight.y, upperRight.y,
512
0
                                          lowerRight.value, upperRight.value,
513
0
                                          !split, minLevel));
514
0
            case UPPER_BORDER:
515
0
                return Point(interpolate_(level, upperLeft.x, upperRight.x,
516
0
                                          upperLeft.value, upperRight.value,
517
0
                                          !split, minLevel),
518
0
                             upperLeft.y);
519
0
        }
520
0
        assert(false);
521
0
        return Point();
522
0
    }
523
};
524
}  // namespace marching_squares
525
#endif