Coverage Report

Created: 2025-09-27 06:58

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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