/src/geos/src/triangulate/polygon/PolygonEarClipper.cpp
Line | Count | Source |
1 | | /********************************************************************** |
2 | | * |
3 | | * GEOS - Geometry Engine Open Source |
4 | | * http://geos.osgeo.org |
5 | | * |
6 | | * Copyright (C) 2020 Paul Ramsey <pramsey@cleverelephant.ca> |
7 | | * |
8 | | * This is free software; you can redistribute and/or modify it under |
9 | | * the terms of the GNU Lesser General Public Licence as published |
10 | | * by the Free Software Foundation. |
11 | | * See the COPYING file for more information. |
12 | | * |
13 | | **********************************************************************/ |
14 | | |
15 | | #include <geos/algorithm/Angle.h> |
16 | | #include <geos/algorithm/Orientation.h> |
17 | | #include <geos/geom/Coordinate.h> |
18 | | #include <geos/geom/CoordinateSequence.h> |
19 | | #include <geos/geom/Envelope.h> |
20 | | #include <geos/geom/GeometryFactory.h> |
21 | | #include <geos/geom/Polygon.h> |
22 | | #include <geos/geom/Triangle.h> |
23 | | #include <geos/triangulate/tri/TriList.h> |
24 | | #include <geos/triangulate/polygon/PolygonEarClipper.h> |
25 | | #include <geos/util/IllegalStateException.h> |
26 | | |
27 | | using geos::geom::Envelope; |
28 | | using geos::geom::Polygon; |
29 | | using geos::algorithm::Orientation; |
30 | | using geos::algorithm::Angle; |
31 | | using geos::triangulate::tri::TriList; |
32 | | |
33 | | namespace geos { |
34 | | namespace triangulate { |
35 | | namespace polygon { |
36 | | |
37 | | |
38 | | /* public */ |
39 | | PolygonEarClipper::PolygonEarClipper(const geom::CoordinateSequence& polyShell) |
40 | 0 | : vertex(polyShell) |
41 | 0 | , vertexSize(polyShell.size()-1) |
42 | 0 | , vertexFirst(0) |
43 | 0 | , vertexCoordIndex(polyShell) |
44 | 0 | { |
45 | 0 | vertexNext = createNextLinks(vertexSize); |
46 | 0 | initCornerIndex(); |
47 | 0 | } |
48 | | |
49 | | |
50 | | /* private */ |
51 | | std::vector<std::size_t> |
52 | | PolygonEarClipper::createNextLinks(std::size_t size) const |
53 | 0 | { |
54 | 0 | std::vector<std::size_t> next(size); |
55 | 0 | for (std::size_t i = 0; i < size; i++) { |
56 | 0 | next[i] = i + 1; |
57 | 0 | } |
58 | 0 | next[size - 1] = 0; |
59 | 0 | return next; |
60 | 0 | } |
61 | | |
62 | | |
63 | | /* public static */ |
64 | | void |
65 | | PolygonEarClipper::triangulate(const geom::CoordinateSequence& polyShell, TriList<Tri>& triListResult) |
66 | 0 | { |
67 | 0 | PolygonEarClipper clipper(polyShell); |
68 | 0 | clipper.compute(triListResult); |
69 | 0 | } |
70 | | |
71 | | |
72 | | /* public */ |
73 | | void |
74 | | PolygonEarClipper::setSkipFlatCorners(bool p_isFlatCornersSkipped) |
75 | 0 | { |
76 | 0 | isFlatCornersSkipped = p_isFlatCornersSkipped; |
77 | 0 | } |
78 | | |
79 | | |
80 | | /* public */ |
81 | | void |
82 | | PolygonEarClipper::compute(TriList<Tri>& triList) |
83 | 0 | { |
84 | | /** |
85 | | * Count scanned corners, to catch infinite loops |
86 | | * (which indicate an algorithm bug) |
87 | | */ |
88 | 0 | std::size_t cornerScanCount = 0; |
89 | |
|
90 | 0 | std::array<Coordinate, 3> corner; |
91 | 0 | fetchCorner(corner); |
92 | | |
93 | | /** |
94 | | * Scan continuously around vertex ring, |
95 | | * until all ears have been found. |
96 | | */ |
97 | 0 | while (true) { |
98 | | /** |
99 | | * Non-convex corner- remove if flat, or skip |
100 | | * (a concave corner will turn into a convex corner |
101 | | * after enough ears are removed) |
102 | | */ |
103 | 0 | if (! isConvex(corner)) { |
104 | | // remove the corner if it is invalid or flat (if required) |
105 | 0 | bool isCornerRemoved = isCornerInvalid(corner) |
106 | 0 | || (isFlatCornersSkipped && isFlat(corner)); |
107 | 0 | if (isCornerRemoved) { |
108 | 0 | removeCorner(); |
109 | 0 | } |
110 | 0 | cornerScanCount++; |
111 | 0 | if (cornerScanCount > 2 * vertexSize) { |
112 | 0 | throw util::IllegalStateException("Unable to find a convex corner"); |
113 | 0 | } |
114 | 0 | } |
115 | | /** |
116 | | * Convex corner - check if it is a valid ear |
117 | | */ |
118 | 0 | else if (isValidEar(cornerIndex[1], corner)) { |
119 | 0 | triList.add(corner[0], corner[1], corner[2]); |
120 | 0 | removeCorner(); |
121 | 0 | cornerScanCount = 0; |
122 | 0 | } |
123 | 0 | if (cornerScanCount > 2 * vertexSize) { |
124 | 0 | throw util::IllegalStateException("Unable to find a valid ear"); |
125 | 0 | } |
126 | | |
127 | | //--- done when all corners are processed and removed |
128 | 0 | if (vertexSize < 3) { |
129 | 0 | return; |
130 | 0 | } |
131 | | |
132 | | /** |
133 | | * Skip to next corner. |
134 | | * This is done even after an ear is removed, |
135 | | * since that creates fewer skinny triangles. |
136 | | */ |
137 | 0 | nextCorner(corner); |
138 | 0 | } |
139 | 0 | } |
140 | | |
141 | | /* private */ |
142 | | bool |
143 | | PolygonEarClipper::isValidEar(std::size_t cornerIdx, const std::array<Coordinate, 3>& corner) |
144 | 0 | { |
145 | 0 | std::size_t intApexIndex = findIntersectingVertex(cornerIdx, corner); |
146 | | //--- no intersections found |
147 | 0 | if (intApexIndex == NO_COORD_INDEX) |
148 | 0 | return true; |
149 | | //--- check for duplicate corner apex vertex |
150 | 0 | if ( vertex[intApexIndex].equals2D(corner[1]) ) { |
151 | | //--- a duplicate corner vertex requires a full scan |
152 | 0 | return isValidEarScan(cornerIdx, corner); |
153 | 0 | } |
154 | 0 | return false; |
155 | 0 | } |
156 | | |
157 | | |
158 | | /* private */ |
159 | | std::size_t |
160 | | PolygonEarClipper::findIntersectingVertex(std::size_t cornerIdx, const std::array<Coordinate, 3>& corner) const |
161 | 0 | { |
162 | 0 | Envelope cornerEnv = envelope(corner); |
163 | 0 | std::vector<std::size_t> result; |
164 | 0 | vertexCoordIndex.query(cornerEnv, result); |
165 | |
|
166 | 0 | std::size_t dupApexIndex = NO_COORD_INDEX; |
167 | | //--- check for duplicate vertices |
168 | 0 | for (std::size_t i = 0; i < result.size(); i++) { |
169 | 0 | std::size_t vertIndex = result[i]; |
170 | |
|
171 | 0 | if (vertIndex == cornerIdx || |
172 | 0 | vertIndex == vertex.size() - 1 || |
173 | 0 | isRemoved(vertIndex)) |
174 | 0 | continue; |
175 | | |
176 | 0 | const Coordinate& v = vertex[vertIndex]; |
177 | | /** |
178 | | * If another vertex at the corner is found, |
179 | | * need to do a full scan to check the incident segments. |
180 | | * This happens when the polygon ring self-intersects, |
181 | | * usually due to hold joining. |
182 | | * But only report this if no properly intersecting vertex is found, |
183 | | * for efficiency. |
184 | | */ |
185 | 0 | if (v.equals2D(corner[1])) { |
186 | 0 | dupApexIndex = vertIndex; |
187 | 0 | } |
188 | | //--- don't need to check other corner vertices |
189 | 0 | else if (v.equals2D(corner[0]) || v.equals2D(corner[2])) { |
190 | 0 | continue; |
191 | 0 | } |
192 | | //--- this is a properly intersecting vertex |
193 | 0 | else if (geom::Triangle::intersects(corner[0], corner[1], corner[2], v)) |
194 | 0 | return vertIndex; |
195 | 0 | } |
196 | 0 | if (dupApexIndex != NO_COORD_INDEX) { |
197 | 0 | return dupApexIndex; |
198 | 0 | } |
199 | 0 | return NO_COORD_INDEX; |
200 | 0 | } |
201 | | |
202 | | |
203 | | /* private */ |
204 | | bool |
205 | | PolygonEarClipper::isValidEarScan(std::size_t cornerIdx, const std::array<Coordinate, 3>& corner) const |
206 | 0 | { |
207 | 0 | double cornerAngle = Angle::angleBetweenOriented(corner[0], corner[1], corner[2]); |
208 | |
|
209 | 0 | std::size_t currIndex = nextIndex(vertexFirst); |
210 | 0 | std::size_t prevIndex = vertexFirst; |
211 | 0 | for (std::size_t i = 0; i < vertexSize; i++) { |
212 | 0 | const Coordinate& vPrev = vertex[prevIndex]; |
213 | 0 | const Coordinate& v = vertex[currIndex]; |
214 | | /** |
215 | | * Because of hole-joining vertices can occur more than once. |
216 | | * If vertex is same as corner[1], |
217 | | * check whether either adjacent edge lies inside the ear corner. |
218 | | * If so the ear is invalid. |
219 | | */ |
220 | 0 | if (currIndex != cornerIdx && v.equals2D(corner[1])) { |
221 | 0 | const Coordinate& vNext = vertex[nextIndex(currIndex)]; |
222 | | |
223 | | //TODO: for robustness use segment orientation instead |
224 | 0 | double aOut = Angle::angleBetweenOriented(corner[0], corner[1], vNext); |
225 | 0 | double aIn = Angle::angleBetweenOriented(corner[0], corner[1], vPrev); |
226 | 0 | if (aOut > 0 && aOut < cornerAngle ) { |
227 | 0 | return false; |
228 | 0 | } |
229 | 0 | if (aIn > 0 && aIn < cornerAngle) { |
230 | 0 | return false; |
231 | 0 | } |
232 | 0 | if (aOut == 0 && aIn == cornerAngle) { |
233 | 0 | return false; |
234 | 0 | } |
235 | 0 | } |
236 | | |
237 | | //--- move to next vertex |
238 | 0 | prevIndex = currIndex; |
239 | 0 | currIndex = nextIndex(currIndex); |
240 | 0 | } |
241 | 0 | return true; |
242 | 0 | } |
243 | | |
244 | | |
245 | | /* private static */ |
246 | | Envelope |
247 | | PolygonEarClipper::envelope(const std::array<Coordinate, 3>& corner) |
248 | 0 | { |
249 | 0 | Envelope cornerEnv(corner[0], corner[1]); |
250 | 0 | cornerEnv.expandToInclude(corner[2]); |
251 | 0 | return cornerEnv; |
252 | 0 | } |
253 | | |
254 | | |
255 | | /* private */ |
256 | | void |
257 | | PolygonEarClipper::removeCorner() |
258 | 0 | { |
259 | 0 | std::size_t cornerApexIndex = cornerIndex[1]; |
260 | 0 | if (vertexFirst == cornerApexIndex) { |
261 | 0 | vertexFirst = vertexNext[cornerApexIndex]; |
262 | 0 | } |
263 | 0 | vertexNext[cornerIndex[0]] = vertexNext[cornerApexIndex]; |
264 | 0 | vertexCoordIndex.remove(cornerApexIndex); |
265 | 0 | vertexNext[cornerApexIndex] = NO_COORD_INDEX; |
266 | 0 | vertexSize--; |
267 | | //-- adjust following corner indexes |
268 | 0 | cornerIndex[1] = nextIndex(cornerIndex[0]); |
269 | 0 | cornerIndex[2] = nextIndex(cornerIndex[1]); |
270 | 0 | } |
271 | | |
272 | | |
273 | | /* private */ |
274 | | bool |
275 | | PolygonEarClipper::isRemoved(std::size_t vertexIndex) const |
276 | 0 | { |
277 | 0 | return NO_COORD_INDEX == vertexNext[vertexIndex]; |
278 | 0 | } |
279 | | |
280 | | |
281 | | /* private */ |
282 | | void |
283 | | PolygonEarClipper::initCornerIndex() |
284 | 0 | { |
285 | 0 | cornerIndex[0] = 0; |
286 | 0 | cornerIndex[1] = 1; |
287 | 0 | cornerIndex[2] = 2; |
288 | 0 | } |
289 | | |
290 | | |
291 | | /* private */ |
292 | | void |
293 | | PolygonEarClipper::fetchCorner(std::array<Coordinate, 3>& cornerVertex) const |
294 | 0 | { |
295 | 0 | cornerVertex[0] = vertex[cornerIndex[0]]; |
296 | 0 | cornerVertex[1] = vertex[cornerIndex[1]]; |
297 | 0 | cornerVertex[2] = vertex[cornerIndex[2]]; |
298 | 0 | } |
299 | | |
300 | | |
301 | | /* private */ |
302 | | void |
303 | | PolygonEarClipper::nextCorner(std::array<Coordinate, 3>& cornerVertex) |
304 | 0 | { |
305 | 0 | if ( vertexSize < 3 ) { |
306 | 0 | return; |
307 | 0 | } |
308 | 0 | cornerIndex[0] = nextIndex(cornerIndex[0]); |
309 | 0 | cornerIndex[1] = nextIndex(cornerIndex[0]); |
310 | 0 | cornerIndex[2] = nextIndex(cornerIndex[1]); |
311 | 0 | fetchCorner(cornerVertex); |
312 | 0 | } |
313 | | |
314 | | |
315 | | /* private */ |
316 | | std::size_t |
317 | | PolygonEarClipper::nextIndex(std::size_t index) const |
318 | 0 | { |
319 | 0 | return vertexNext[index]; |
320 | 0 | } |
321 | | |
322 | | |
323 | | /* private static */ |
324 | | bool |
325 | | PolygonEarClipper::isConvex(const std::array<Coordinate, 3>& pts) const |
326 | 0 | { |
327 | 0 | return Orientation::CLOCKWISE == Orientation::index(pts[0], pts[1], pts[2]); |
328 | 0 | } |
329 | | |
330 | | |
331 | | /* private static */ |
332 | | bool |
333 | | PolygonEarClipper::isFlat(const std::array<Coordinate, 3>& pts) const |
334 | 0 | { |
335 | 0 | return Orientation::COLLINEAR == Orientation::index(pts[0], pts[1], pts[2]); |
336 | 0 | } |
337 | | |
338 | | |
339 | | /* private static */ |
340 | | bool |
341 | | PolygonEarClipper::isCornerInvalid(const std::array<Coordinate, 3>& pts) const |
342 | 0 | { |
343 | 0 | return pts[1].equals2D(pts[0]) || pts[1].equals2D(pts[2]) || pts[0].equals2D(pts[2]); |
344 | 0 | } |
345 | | |
346 | | |
347 | | /* public */ |
348 | | std::unique_ptr<Polygon> |
349 | | PolygonEarClipper::toGeometry() const |
350 | 0 | { |
351 | 0 | auto gf = geom::GeometryFactory::create(); |
352 | 0 | std::unique_ptr<geom::CoordinateSequence> cs(new geom::CoordinateSequence()); |
353 | 0 | std::size_t index = vertexFirst; |
354 | 0 | for (std::size_t i = 0; i < vertexSize; i++) { |
355 | 0 | const Coordinate& v = vertex[index]; |
356 | 0 | index = nextIndex(index); |
357 | 0 | cs->add(v, true); |
358 | 0 | } |
359 | 0 | cs->closeRing(); |
360 | 0 | auto lr = gf->createLinearRing(std::move(cs)); |
361 | 0 | return gf->createPolygon(std::move(lr)); |
362 | 0 | } |
363 | | |
364 | | |
365 | | } // namespace geos.triangulate.polygon |
366 | | } // namespace geos.triangulate |
367 | | } // namespace geos |
368 | | |
369 | | |