Coverage Report

Created: 2025-06-13 06:29

/src/gdal/alg/marching_squares/polygon_ring_appender.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_POLYGON_RING_APPENDER_H
13
#define MARCHING_SQUARE_POLYGON_RING_APPENDER_H
14
15
#include <vector>
16
#include <list>
17
#include <map>
18
#include <deque>
19
#include <cassert>
20
#include <iterator>
21
22
#include "point.h"
23
#include "ogr_geometry.h"
24
25
namespace marching_squares
26
{
27
28
// Receive rings of different levels and organize them
29
// into multi-polygons with possible interior rings when requested.
30
template <typename PolygonWriter> class PolygonRingAppender
31
{
32
  private:
33
    struct Ring
34
    {
35
0
        Ring() : points(), interiorRings()
36
0
        {
37
0
        }
38
39
0
        Ring(const Ring &other) = default;
40
0
        Ring &operator=(const Ring &other) = default;
41
42
        LineString points;
43
44
        mutable std::vector<Ring> interiorRings;
45
46
        const Ring *closestExterior = nullptr;
47
48
        bool isIn(const Ring &other) const
49
0
        {
50
            // Check if this is inside other using the winding number algorithm
51
0
            auto checkPoint = this->points.front();
52
0
            int windingNum = 0;
53
0
            auto otherIter = other.points.begin();
54
            // p1 and p2 define each segment of the ring other that will be
55
            // tested
56
0
            auto p1 = *otherIter;
57
0
            while (true)
58
0
            {
59
0
                otherIter++;
60
0
                if (otherIter == other.points.end())
61
0
                {
62
0
                    break;
63
0
                }
64
0
                auto p2 = *otherIter;
65
0
                if (p1.y <= checkPoint.y)
66
0
                {
67
0
                    if (p2.y > checkPoint.y)
68
0
                    {
69
0
                        if (isLeft(p1, p2, checkPoint))
70
0
                        {
71
0
                            ++windingNum;
72
0
                        }
73
0
                    }
74
0
                }
75
0
                else
76
0
                {
77
0
                    if (p2.y <= checkPoint.y)
78
0
                    {
79
0
                        if (!isLeft(p1, p2, checkPoint))
80
0
                        {
81
0
                            --windingNum;
82
0
                        }
83
0
                    }
84
0
                }
85
0
                p1 = p2;
86
0
            }
87
0
            return windingNum != 0;
88
0
        }
89
90
#ifdef DEBUG
91
        size_t id() const
92
        {
93
            return size_t(static_cast<const void *>(this)) & 0xffff;
94
        }
95
96
        void print(std::ostream &ostr) const
97
        {
98
            ostr << id() << ":";
99
            for (const auto &pt : points)
100
            {
101
                ostr << pt.x << "," << pt.y << " ";
102
            }
103
        }
104
#endif
105
    };
106
107
    void processTree(const std::vector<Ring> &tree, int level)
108
0
    {
109
0
        if (level % 2 == 0)
110
0
        {
111
0
            for (auto &r : tree)
112
0
            {
113
0
                writer_.addPart(r.points);
114
0
                for (auto &innerRing : r.interiorRings)
115
0
                {
116
0
                    writer_.addInteriorRing(innerRing.points);
117
0
                }
118
0
            }
119
0
        }
120
0
        for (auto &r : tree)
121
0
        {
122
0
            processTree(r.interiorRings, level + 1);
123
0
        }
124
0
    }
125
126
    // level -> rings
127
    std::map<double, std::vector<Ring>> rings_;
128
129
    PolygonWriter &writer_;
130
131
  public:
132
    const bool polygonize = true;
133
134
0
    PolygonRingAppender(PolygonWriter &writer) : rings_(), writer_(writer)
135
0
    {
136
0
    }
137
138
    void addLine(double level, LineString &ls, bool)
139
0
    {
140
0
        auto &levelRings = rings_[level];
141
0
        if (ls.empty())
142
0
        {
143
0
            return;
144
0
        }
145
        // Create a new ring from the LineString
146
0
        Ring newRing;
147
0
        newRing.points.swap(ls);
148
        // This queue holds the rings to be checked
149
0
        std::deque<Ring *> queue;
150
0
        std::transform(levelRings.begin(), levelRings.end(),
151
0
                       std::back_inserter(queue), [](Ring &r) { return &r; });
152
0
        Ring *parentRing = nullptr;
153
0
        while (!queue.empty())
154
0
        {
155
0
            Ring *curRing = queue.front();
156
0
            queue.pop_front();
157
0
            if (newRing.isIn(*curRing))
158
0
            {
159
                // We know that there should only be one ring per level that we
160
                // should fit in, so we can discard the rest of the queue and
161
                // try again with the children of this ring
162
0
                parentRing = curRing;
163
0
                queue.clear();
164
0
                std::transform(curRing->interiorRings.begin(),
165
0
                               curRing->interiorRings.end(),
166
0
                               std::back_inserter(queue),
167
0
                               [](Ring &r) { return &r; });
168
0
            }
169
0
        }
170
        // Get a pointer to the list we need to check for rings to include in
171
        // this ring
172
0
        std::vector<Ring> *parentRingList;
173
0
        if (parentRing == nullptr)
174
0
        {
175
0
            parentRingList = &levelRings;
176
0
        }
177
0
        else
178
0
        {
179
0
            parentRingList = &(parentRing->interiorRings);
180
0
        }
181
        // We found a valid parent, so we need to:
182
        // 1. Find all the inner rings of the parent that are inside the new
183
        // ring
184
0
        auto trueGroupIt = std::partition(
185
0
            parentRingList->begin(), parentRingList->end(),
186
0
            [newRing](Ring &pRing) { return !pRing.isIn(newRing); });
187
        // 2. Move those rings out of the parent and into the new ring's
188
        // interior rings
189
0
        std::move(trueGroupIt, parentRingList->end(),
190
0
                  std::back_inserter(newRing.interiorRings));
191
        // 3. Get rid of the moved-from elements in the parent's interior rings
192
0
        parentRingList->erase(trueGroupIt, parentRingList->end());
193
        // 4. Add the new ring to the parent's interior rings
194
0
        parentRingList->push_back(newRing);
195
0
    }
196
197
    ~PolygonRingAppender()
198
0
    {
199
        // If there's no rings, nothing to do here
200
0
        if (rings_.size() == 0)
201
0
            return;
202
203
        // Traverse tree of rings
204
0
        for (auto &r : rings_)
205
0
        {
206
            // For each level, create a multipolygon by traversing the tree of
207
            // rings and adding a part for every other level
208
0
            writer_.startPolygon(r.first);
209
0
            processTree(r.second, 0);
210
0
            writer_.endPolygon();
211
0
        }
212
0
    }
213
};
214
215
}  // namespace marching_squares
216
217
#endif