Coverage Report

Created: 2025-03-15 06:58

/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