/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 |