/src/geos/src/algorithm/hull/ConcaveHullOfPolygons.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /********************************************************************** |
2 | | * |
3 | | * GEOS - Geometry Engine Open Source |
4 | | * http://geos.osgeo.org |
5 | | * |
6 | | * Copyright (C) 2022 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/hull/ConcaveHullOfPolygons.h> |
16 | | #include <geos/algorithm/hull/OuterShellsExtracter.h> |
17 | | #include <geos/geom/Coordinate.h> |
18 | | #include <geos/geom/Envelope.h> |
19 | | #include <geos/geom/Geometry.h> |
20 | | #include <geos/geom/GeometryCollection.h> |
21 | | #include <geos/geom/GeometryFactory.h> |
22 | | #include <geos/geom/LinearRing.h> |
23 | | #include <geos/geom/MultiPolygon.h> |
24 | | #include <geos/geom/Polygon.h> |
25 | | #include <geos/operation/overlayng/CoverageUnion.h> |
26 | | #include <geos/triangulate/polygon/ConstrainedDelaunayTriangulator.h> |
27 | | #include <geos/triangulate/tri/Tri.h> |
28 | | #include <geos/util/IllegalArgumentException.h> |
29 | | |
30 | | #include "geos/util.h" |
31 | | |
32 | | |
33 | | using geos::geom::Coordinate; |
34 | | using geos::geom::Geometry; |
35 | | using geos::geom::Envelope; |
36 | | using geos::geom::GeometryCollection; |
37 | | using geos::geom::GeometryFactory; |
38 | | using geos::geom::LinearRing; |
39 | | using geos::geom::MultiPolygon; |
40 | | using geos::geom::Polygon; |
41 | | using geos::operation::overlayng::CoverageUnion; |
42 | | using geos::triangulate::polygon::ConstrainedDelaunayTriangulator; |
43 | | using geos::triangulate::tri::Tri; |
44 | | |
45 | | |
46 | | namespace geos { |
47 | | namespace algorithm { // geos.algorithm |
48 | | namespace hull { // geos.algorithm.hulll |
49 | | |
50 | | /* public static */ |
51 | | std::unique_ptr<Geometry> |
52 | | ConcaveHullOfPolygons::concaveHullByLength(const Geometry* polygons, double maxLength) |
53 | 0 | { |
54 | 0 | return concaveHullByLength(polygons, maxLength, false, false); |
55 | 0 | } |
56 | | |
57 | | /* public static */ |
58 | | std::unique_ptr<Geometry> |
59 | | ConcaveHullOfPolygons::concaveHullByLength( |
60 | | const Geometry* polygons, double maxLength, |
61 | | bool isTight, bool isHolesAllowed) |
62 | 0 | { |
63 | 0 | ConcaveHullOfPolygons hull(polygons); |
64 | 0 | hull.setMaximumEdgeLength(maxLength); |
65 | 0 | hull.setHolesAllowed(isHolesAllowed); |
66 | 0 | hull.setTight(isTight); |
67 | 0 | return hull.getHull(); |
68 | 0 | } |
69 | | |
70 | | /* public static */ |
71 | | std::unique_ptr<Geometry> |
72 | | ConcaveHullOfPolygons::concaveHullByLengthRatio(const Geometry* polygons, double lengthRatio) |
73 | 0 | { |
74 | 0 | return concaveHullByLengthRatio(polygons, lengthRatio, false, false); |
75 | 0 | } |
76 | | |
77 | | /* public static */ |
78 | | std::unique_ptr<Geometry> |
79 | | ConcaveHullOfPolygons::concaveHullByLengthRatio( |
80 | | const Geometry* polygons, double lengthRatio, |
81 | | bool isTight, bool isHolesAllowed) |
82 | 0 | { |
83 | 0 | ConcaveHullOfPolygons hull(polygons); |
84 | 0 | hull.setMaximumEdgeLengthRatio(lengthRatio); |
85 | 0 | hull.setHolesAllowed(isHolesAllowed); |
86 | 0 | hull.setTight(isTight); |
87 | 0 | return hull.getHull(); |
88 | 0 | } |
89 | | |
90 | | /* public static */ |
91 | | std::unique_ptr<Geometry> |
92 | | ConcaveHullOfPolygons::concaveFillByLength(const Geometry* polygons, double maxLength) |
93 | 0 | { |
94 | 0 | ConcaveHullOfPolygons hull(polygons); |
95 | 0 | hull.setMaximumEdgeLength(maxLength); |
96 | 0 | return hull.getFill(); |
97 | 0 | } |
98 | | |
99 | | /* public static */ |
100 | | std::unique_ptr<Geometry> |
101 | | ConcaveHullOfPolygons::concaveFillByLengthRatio(const Geometry* polygons, double lengthRatio) |
102 | 0 | { |
103 | 0 | ConcaveHullOfPolygons hull(polygons); |
104 | 0 | hull.setMaximumEdgeLengthRatio(lengthRatio); |
105 | 0 | return hull.getFill(); |
106 | 0 | } |
107 | | |
108 | | /* public */ |
109 | | ConcaveHullOfPolygons::ConcaveHullOfPolygons(const Geometry* geom) |
110 | 0 | : inputPolygons(geom) |
111 | 0 | , geomFactory(geom->getFactory()) |
112 | 0 | , maxEdgeLength(-1.0) |
113 | 0 | , maxEdgeLengthRatio(-1.0) |
114 | 0 | , isHolesAllowed(false) |
115 | 0 | , isTight(false) |
116 | 0 | { |
117 | 0 | util::ensureNoCurvedComponents(geom); |
118 | 0 | if (! geom->isPolygonal()) { |
119 | 0 | throw util::IllegalArgumentException("Input must be polygonal"); |
120 | 0 | } |
121 | 0 | } |
122 | | |
123 | | /* public */ |
124 | | void |
125 | | ConcaveHullOfPolygons::setMaximumEdgeLength(double edgeLength) |
126 | 0 | { |
127 | 0 | if (edgeLength < 0) |
128 | 0 | throw util::IllegalArgumentException("Edge length must be non-negative"); |
129 | 0 | maxEdgeLength = edgeLength; |
130 | 0 | maxEdgeLengthRatio = -1; |
131 | 0 | } |
132 | | |
133 | | /* public */ |
134 | | void |
135 | | ConcaveHullOfPolygons::setMaximumEdgeLengthRatio(double edgeLengthRatio) |
136 | 0 | { |
137 | 0 | if (edgeLengthRatio < 0 || edgeLengthRatio > 1) |
138 | 0 | throw util::IllegalArgumentException("Edge length ratio must be in range [0,1]"); |
139 | 0 | maxEdgeLengthRatio = edgeLengthRatio; |
140 | 0 | } |
141 | | |
142 | | /* public */ |
143 | | void |
144 | | ConcaveHullOfPolygons::setHolesAllowed(bool p_isHolesAllowed) |
145 | 0 | { |
146 | 0 | isHolesAllowed = p_isHolesAllowed; |
147 | 0 | } |
148 | | |
149 | | /* public */ |
150 | | void |
151 | | ConcaveHullOfPolygons::setTight(bool p_isTight) |
152 | 0 | { |
153 | 0 | isTight = p_isTight; |
154 | 0 | } |
155 | | |
156 | | /* public */ |
157 | | std::unique_ptr<Geometry> |
158 | | ConcaveHullOfPolygons::getHull() |
159 | 0 | { |
160 | 0 | if (inputPolygons->isEmpty() || inputPolygons->getArea() == 0) { |
161 | 0 | return createEmptyHull(); |
162 | 0 | } |
163 | 0 | buildHullTris(); |
164 | 0 | return createHullGeometry(true); |
165 | 0 | } |
166 | | |
167 | | /* public */ |
168 | | std::unique_ptr<Geometry> |
169 | | ConcaveHullOfPolygons::getFill() |
170 | 0 | { |
171 | 0 | isTight = true; |
172 | 0 | if (inputPolygons->isEmpty()) { |
173 | 0 | return createEmptyHull(); |
174 | 0 | } |
175 | 0 | buildHullTris(); |
176 | 0 | return createHullGeometry(false); |
177 | 0 | } |
178 | | |
179 | | /* private */ |
180 | | std::unique_ptr<Geometry> |
181 | | ConcaveHullOfPolygons::createEmptyHull() |
182 | 0 | { |
183 | 0 | return geomFactory->createPolygon(); |
184 | 0 | } |
185 | | |
186 | | /* private */ |
187 | | void |
188 | | ConcaveHullOfPolygons::buildHullTris() |
189 | 0 | { |
190 | 0 | OuterShellsExtracter::extractShells(inputPolygons, polygonRings); |
191 | 0 | std::unique_ptr<Polygon> frame = createFrame(inputPolygons->getEnvelopeInternal()); |
192 | 0 | ConstrainedDelaunayTriangulator::triangulatePolygon(frame.get(), triList); |
193 | | //System.out.println(tris); |
194 | |
|
195 | 0 | const CoordinateSequence* framePts = frame->getExteriorRing()->getCoordinatesRO(); |
196 | 0 | if (maxEdgeLengthRatio >= 0) { |
197 | 0 | maxEdgeLength = computeTargetEdgeLength(triList, framePts, maxEdgeLengthRatio); |
198 | 0 | } |
199 | |
|
200 | 0 | removeFrameCornerTris(triList, framePts); |
201 | 0 | removeBorderTris(); |
202 | 0 | if (isHolesAllowed) removeHoleTris(); |
203 | 0 | } |
204 | | |
205 | | /* private static */ |
206 | | std::unique_ptr<Polygon> |
207 | | ConcaveHullOfPolygons::createFrame(const Envelope* polygonsEnv) |
208 | 0 | { |
209 | 0 | double diam = polygonsEnv->getDiameter(); |
210 | 0 | Envelope envFrame = *polygonsEnv; |
211 | 0 | envFrame.expandBy(FRAME_EXPAND_FACTOR * diam); |
212 | 0 | std::unique_ptr<Geometry> frameGeom = geomFactory->toGeometry(&envFrame); |
213 | 0 | Polygon* framePoly = dynamic_cast<Polygon*>(frameGeom.get()); |
214 | 0 | if (!framePoly) return nullptr; |
215 | 0 | const LinearRing* frameShell = framePoly->getExteriorRing(); |
216 | 0 | std::unique_ptr<LinearRing> shell = frameShell->clone(); |
217 | 0 | std::vector<std::unique_ptr<LinearRing>> holes; |
218 | 0 | for (const LinearRing* lr : polygonRings) { |
219 | 0 | holes.emplace_back(lr->clone().release()); |
220 | 0 | } |
221 | 0 | return geomFactory->createPolygon(std::move(shell), std::move(holes)); |
222 | 0 | } |
223 | | |
224 | | |
225 | | /* private static */ |
226 | | double |
227 | | ConcaveHullOfPolygons::computeTargetEdgeLength( |
228 | | TriList<Tri>& tris, |
229 | | const CoordinateSequence* frameCorners, |
230 | | double edgeLengthRatio) const |
231 | 0 | { |
232 | 0 | if (edgeLengthRatio == 0) return 0.0; |
233 | 0 | double maxEdgeLen = -1; |
234 | 0 | double minEdgeLen = -1; |
235 | 0 | for (Tri* tri : tris.getTris()) { |
236 | | //-- don't include frame triangles |
237 | 0 | if (isFrameTri(tri, frameCorners)) |
238 | 0 | continue; |
239 | | |
240 | 0 | for (TriIndex i = 0; i < 3; i++) { |
241 | | //-- constraint edges are not used to determine ratio |
242 | 0 | if (! tri->hasAdjacent(i)) |
243 | 0 | continue; |
244 | | |
245 | 0 | double len = tri->getLength(i); |
246 | 0 | if (len > maxEdgeLen) |
247 | 0 | maxEdgeLen = len; |
248 | 0 | if (minEdgeLen < 0 || len < minEdgeLen) |
249 | 0 | minEdgeLen = len; |
250 | 0 | } |
251 | 0 | } |
252 | | //-- if ratio = 1 ensure all edges are included |
253 | 0 | if (edgeLengthRatio == 1) |
254 | 0 | return 2 * maxEdgeLen; |
255 | | |
256 | 0 | return edgeLengthRatio * (maxEdgeLen - minEdgeLen) + minEdgeLen; |
257 | 0 | } |
258 | | |
259 | | /* private static */ |
260 | | bool |
261 | | ConcaveHullOfPolygons::isFrameTri( |
262 | | const Tri* tri, |
263 | | const CoordinateSequence* frameCorners) const |
264 | 0 | { |
265 | 0 | TriIndex index = vertexIndex(tri, frameCorners); |
266 | 0 | bool bIsFrameTri = (index >= 0); |
267 | 0 | return bIsFrameTri; |
268 | 0 | } |
269 | | |
270 | | /* private */ |
271 | | void |
272 | | ConcaveHullOfPolygons::removeFrameCornerTris( |
273 | | TriList<Tri>& tris, |
274 | | const CoordinateSequence* frameCorners) |
275 | 0 | { |
276 | 0 | hullTris.clear(); |
277 | 0 | borderTriQue.clear(); |
278 | 0 | for (Tri* tri : tris.getTris()) { |
279 | 0 | TriIndex index = vertexIndex(tri, frameCorners); |
280 | 0 | bool bIsFrameTri = (index >= 0); |
281 | 0 | if (bIsFrameTri) { |
282 | | /** |
283 | | * Frame tris are adjacent to at most one border tri, |
284 | | * which is opposite the frame corner vertex. |
285 | | * Or, the opposite tri may be another frame tri, |
286 | | * which is not added as a border tri. |
287 | | */ |
288 | 0 | TriIndex oppIndex = Tri::oppEdge(index); |
289 | 0 | Tri* oppTri = tri->getAdjacent(oppIndex); |
290 | 0 | bool bBorderTri = oppTri != nullptr && ! isFrameTri(oppTri, frameCorners); |
291 | 0 | if (bBorderTri) { |
292 | 0 | addBorderTri(tri, oppIndex); |
293 | 0 | } |
294 | 0 | tri->remove(); |
295 | 0 | } |
296 | 0 | else { |
297 | 0 | hullTris.insert(tri); |
298 | 0 | } |
299 | 0 | } |
300 | 0 | return; |
301 | 0 | } |
302 | | |
303 | | /* private static */ |
304 | | TriIndex |
305 | | ConcaveHullOfPolygons::vertexIndex( |
306 | | const Tri* tri, |
307 | | const CoordinateSequence* pts) const |
308 | 0 | { |
309 | 0 | for (std::size_t i = 0; i < pts->size(); ++i) { |
310 | 0 | const Coordinate& p = pts->getAt(i); |
311 | 0 | TriIndex index = tri->getIndex(p); |
312 | 0 | if (index >= 0) |
313 | 0 | return index; |
314 | 0 | } |
315 | 0 | return -1; |
316 | 0 | } |
317 | | |
318 | | /* private */ |
319 | | void |
320 | | ConcaveHullOfPolygons::removeBorderTris() |
321 | 0 | { |
322 | 0 | while (! borderTriQue.empty()) { |
323 | 0 | Tri* tri = borderTriQue.back(); |
324 | 0 | borderTriQue.pop_back(); |
325 | | //-- tri might have been removed already |
326 | 0 | if (hullTris.find(tri) == hullTris.end()) { |
327 | 0 | continue; |
328 | 0 | } |
329 | 0 | if (isRemovable(tri)) { |
330 | 0 | addBorderTris(tri); |
331 | 0 | removeBorderTri(tri); |
332 | 0 | } |
333 | 0 | } |
334 | 0 | } |
335 | | |
336 | | /* private */ |
337 | | void |
338 | | ConcaveHullOfPolygons::removeHoleTris() |
339 | 0 | { |
340 | 0 | while (true) { |
341 | 0 | Tri* holeTri = findHoleSeedTri(); |
342 | 0 | if (holeTri == nullptr) |
343 | 0 | return; |
344 | 0 | addBorderTris(holeTri); |
345 | 0 | removeBorderTri(holeTri); |
346 | 0 | removeBorderTris(); |
347 | 0 | } |
348 | 0 | } |
349 | | |
350 | | /* private */ |
351 | | Tri* |
352 | | ConcaveHullOfPolygons::findHoleSeedTri() const |
353 | 0 | { |
354 | 0 | for (Tri* tri : hullTris) { |
355 | 0 | if (isHoleSeedTri(tri)) |
356 | 0 | return tri; |
357 | 0 | } |
358 | 0 | return nullptr; |
359 | 0 | } |
360 | | |
361 | | /* private */ |
362 | | bool |
363 | | ConcaveHullOfPolygons::isHoleSeedTri(const Tri* tri) const |
364 | 0 | { |
365 | 0 | if (isBorderTri(tri)) |
366 | 0 | return false; |
367 | 0 | for (TriIndex i = 0; i < 3; i++) { |
368 | 0 | if (tri->hasAdjacent(i) |
369 | 0 | && tri->getLength(i) > maxEdgeLength) |
370 | 0 | return true; |
371 | 0 | } |
372 | 0 | return false; |
373 | 0 | } |
374 | | |
375 | | /* private */ |
376 | | bool |
377 | | ConcaveHullOfPolygons::isBorderTri(const Tri* tri) const |
378 | 0 | { |
379 | 0 | for (TriIndex i = 0; i < 3; i++) { |
380 | 0 | if (! tri->hasAdjacent(i)) |
381 | 0 | return true; |
382 | 0 | } |
383 | 0 | return false; |
384 | 0 | } |
385 | | |
386 | | /* private */ |
387 | | bool |
388 | | ConcaveHullOfPolygons::isRemovable(const Tri* tri) const |
389 | 0 | { |
390 | | //-- remove non-bridging tris if keeping hull boundary tight |
391 | 0 | if (isTight && isTouchingSinglePolygon(tri)) |
392 | 0 | return true; |
393 | | |
394 | | //-- check if outside edge is longer than threshold |
395 | 0 | auto search = borderEdgeMap.find(const_cast<Tri*>(tri)); |
396 | 0 | if (search != borderEdgeMap.end()) { |
397 | 0 | TriIndex borderEdgeIndex = search->second; |
398 | 0 | double edgeLen = tri->getLength(borderEdgeIndex); |
399 | 0 | if (edgeLen > maxEdgeLength) |
400 | 0 | return true; |
401 | 0 | } |
402 | 0 | return false; |
403 | 0 | } |
404 | | |
405 | | /* private */ |
406 | | bool |
407 | | ConcaveHullOfPolygons::isTouchingSinglePolygon(const Tri* tri) const |
408 | 0 | { |
409 | 0 | Envelope envTri; |
410 | 0 | envelope(tri, envTri); |
411 | 0 | for (const LinearRing* ring : polygonRings) { |
412 | | //-- optimization heuristic: a touching tri must be in ring envelope |
413 | 0 | if (ring->getEnvelopeInternal()->intersects(envTri)) { |
414 | 0 | if (hasAllVertices(ring, tri)) |
415 | 0 | return true; |
416 | 0 | } |
417 | 0 | } |
418 | 0 | return false; |
419 | 0 | } |
420 | | |
421 | | /* private */ |
422 | | void |
423 | | ConcaveHullOfPolygons::addBorderTris(Tri* tri) |
424 | 0 | { |
425 | 0 | addBorderTri(tri, 0); |
426 | 0 | addBorderTri(tri, 1); |
427 | 0 | addBorderTri(tri, 2); |
428 | 0 | } |
429 | | |
430 | | /* private */ |
431 | | void |
432 | | ConcaveHullOfPolygons::addBorderTri(Tri* tri, TriIndex index) |
433 | 0 | { |
434 | 0 | Tri* adj = tri->getAdjacent(index); |
435 | 0 | if (!adj) |
436 | 0 | return; |
437 | 0 | borderTriQue.push_back(adj); |
438 | 0 | TriIndex borderEdgeIndex = adj->getIndex(tri); |
439 | 0 | borderEdgeMap.insert({adj, borderEdgeIndex}); |
440 | 0 | } |
441 | | |
442 | | /* private */ |
443 | | void |
444 | | ConcaveHullOfPolygons::removeBorderTri(Tri* tri) |
445 | 0 | { |
446 | 0 | tri->remove(); |
447 | 0 | hullTris.erase(tri); |
448 | 0 | borderEdgeMap.erase(tri); |
449 | 0 | } |
450 | | |
451 | | /* private */ |
452 | | bool |
453 | | ConcaveHullOfPolygons::hasAllVertices(const LinearRing* ring, const Tri* tri) const |
454 | 0 | { |
455 | 0 | for (TriIndex i = 0; i < 3; i++) { |
456 | 0 | const Coordinate& v = tri->getCoordinate(i); |
457 | 0 | if (! hasVertex(ring, v)) { |
458 | 0 | return false; |
459 | 0 | } |
460 | 0 | } |
461 | 0 | return true; |
462 | 0 | } |
463 | | |
464 | | /* private */ |
465 | | bool |
466 | | ConcaveHullOfPolygons::hasVertex(const LinearRing* ring, const Coordinate& v) const |
467 | 0 | { |
468 | 0 | for(std::size_t i = 1; i < ring->getNumPoints(); i++) { |
469 | 0 | if (v.equals2D(ring->getCoordinateN(i))) { |
470 | 0 | return true; |
471 | 0 | } |
472 | 0 | } |
473 | 0 | return false; |
474 | 0 | } |
475 | | |
476 | | /* private */ |
477 | | void |
478 | | ConcaveHullOfPolygons::envelope(const Tri* tri, Envelope& env) const |
479 | 0 | { |
480 | 0 | env.init(tri->getCoordinate(0), tri->getCoordinate(1)); |
481 | 0 | env.expandToInclude(tri->getCoordinate(2)); |
482 | 0 | return; |
483 | 0 | } |
484 | | |
485 | | /* private */ |
486 | | std::unique_ptr<Geometry> |
487 | | ConcaveHullOfPolygons::createHullGeometry(bool isIncludeInput) |
488 | 0 | { |
489 | 0 | if (! isIncludeInput && hullTris.empty()) |
490 | 0 | return createEmptyHull(); |
491 | | |
492 | | //-- union triangulation |
493 | 0 | std::unique_ptr<Geometry> triCoverage = Tri::toGeometry(hullTris, geomFactory); |
494 | | //System.out.println(triCoverage); |
495 | 0 | std::unique_ptr<Geometry> fillGeometry = CoverageUnion::geomunion(triCoverage.get()); |
496 | |
|
497 | 0 | if (! isIncludeInput) { |
498 | 0 | return fillGeometry; |
499 | 0 | } |
500 | 0 | if (fillGeometry->isEmpty()) { |
501 | 0 | return inputPolygons->clone(); |
502 | 0 | } |
503 | | //-- union with input polygons |
504 | 0 | std::vector<std::unique_ptr<Geometry>> geoms; |
505 | 0 | geoms.emplace_back(fillGeometry.release()); |
506 | 0 | geoms.emplace_back(inputPolygons->clone().release()); |
507 | |
|
508 | 0 | std::unique_ptr<GeometryCollection> geomColl = geomFactory->createGeometryCollection(std::move(geoms)); |
509 | 0 | std::unique_ptr<Geometry> hull = CoverageUnion::geomunion(geomColl.get()); |
510 | 0 | return hull; |
511 | 0 | } |
512 | | |
513 | | |
514 | | |
515 | | } // namespace geos.algorithm.hull |
516 | | } // namespace geos.algorithm |
517 | | } // namespace geos |