Coverage Report

Created: 2025-07-23 06:51

/src/geos/src/operation/valid/PolygonTopologyAnalyzer.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) 2021 Paul Ramsey <pramsey@cleverelephant.ca>
7
 * Copyright (C) 2021 Martin Davis
8
 *
9
 * This is free software; you can redistribute and/or modify it under
10
 * the terms of the GNU Lesser General Public Licence as published
11
 * by the Free Software Foundation.
12
 * See the COPYING file for more information.
13
 *
14
 **********************************************************************/
15
16
#include <geos/algorithm/LineIntersector.h>
17
#include <geos/algorithm/Orientation.h>
18
#include <geos/algorithm/PointLocation.h>
19
#include <geos/algorithm/PolygonNodeTopology.h>
20
#include <geos/geom/Coordinate.h>
21
#include <geos/geom/Geometry.h>
22
#include <geos/geom/LinearRing.h>
23
#include <geos/geom/Location.h>
24
#include <geos/geom/Polygon.h>
25
#include <geos/noding/BasicSegmentString.h>
26
#include <geos/noding/MCIndexNoder.h>
27
#include <geos/noding/SegmentString.h>
28
#include <geos/operation/valid/PolygonIntersectionAnalyzer.h>
29
#include <geos/operation/valid/PolygonRing.h>
30
#include <geos/operation/valid/PolygonTopologyAnalyzer.h>
31
#include <geos/operation/valid/RepeatedPointRemover.h>
32
#include <geos/util/IllegalArgumentException.h>
33
34
using namespace geos::geom;
35
using geos::noding::SegmentString;
36
37
namespace geos {      // geos
38
namespace operation { // geos.operation
39
namespace valid {     // geos.operation.valid
40
41
42
/* public */
43
PolygonTopologyAnalyzer::PolygonTopologyAnalyzer(const Geometry* geom, bool p_isInvertedRingValid)
44
0
    : isInvertedRingValid(p_isInvertedRingValid)
45
0
    , segInt(p_isInvertedRingValid)
46
0
    , disconnectionPt(Coordinate::getNull())
47
0
{
48
0
    if (geom->isEmpty()){
49
0
        return;
50
0
    }
51
    // Code copied in from analyze()
52
0
    std::vector<SegmentString*> segStrings = createSegmentStrings(geom, p_isInvertedRingValid);
53
0
    polyRings = getPolygonRings(segStrings);
54
    // Code copied in from analyzeIntersections()
55
0
    noding::MCIndexNoder noder;
56
0
    noder.setSegmentIntersector(&segInt);
57
0
    noder.computeNodes(&segStrings);
58
0
    if (segInt.hasDoubleTouch()) {
59
0
        disconnectionPt = segInt.getDoubleTouchLocation();
60
0
    }
61
0
}
62
63
64
/* public static */
65
CoordinateXY
66
PolygonTopologyAnalyzer::findSelfIntersection(const LinearRing* ring)
67
0
{
68
0
    PolygonTopologyAnalyzer ata(ring, false);
69
0
    if (ata.hasInvalidIntersection())
70
0
        return ata.getInvalidLocation();
71
0
    return Coordinate::getNull();
72
0
}
73
74
/* public static */
75
bool
76
PolygonTopologyAnalyzer::isRingNested(const LinearRing* test,
77
    const LinearRing* target)
78
0
{
79
0
    const CoordinateXY& p0 = test->getCoordinatesRO()->getAt<CoordinateXY>(0);
80
0
    const CoordinateSequence* targetPts = target->getCoordinatesRO();
81
0
    Location loc = algorithm::PointLocation::locateInRing(p0, *targetPts);
82
0
    if (loc == Location::EXTERIOR) return false;
83
0
    if (loc == Location::INTERIOR) return true;
84
85
    /**
86
     * The start point is on the boundary of the ring.
87
     * Use the topology at the node to check if the segment
88
     * is inside or outside the ring.
89
     */
90
0
    const CoordinateXY& p1 = findNonEqualVertex(test, p0);
91
0
    return isIncidentSegmentInRing(&p0, &p1, targetPts);
92
0
}
93
94
/* private static */
95
const CoordinateXY&
96
PolygonTopologyAnalyzer::findNonEqualVertex(const LinearRing* ring, const CoordinateXY& p)
97
0
{
98
0
    std::size_t i = 1;
99
0
    const CoordinateSequence& ringPts = *ring->getCoordinatesRO();
100
0
    const CoordinateXY* next = &(ringPts.getAt<CoordinateXY>(i));
101
0
    while (next->equals2D(p) && i < ring->getNumPoints() - 1) {
102
0
        i += 1;
103
0
        next = &(ringPts.getAt<CoordinateXY>(i));
104
0
    }
105
0
    return ringPts.getAt<CoordinateXY>(i);
106
0
}
107
108
/* private static */
109
bool
110
PolygonTopologyAnalyzer::isIncidentSegmentInRing(
111
    const CoordinateXY* p0, const CoordinateXY* p1,
112
    const CoordinateSequence* ringPts)
113
0
{
114
0
    std::size_t index = intersectingSegIndex(ringPts, p0);
115
0
    const CoordinateXY* rPrev = &findRingVertexPrev(ringPts, index, *p0);
116
0
    const CoordinateXY* rNext = &findRingVertexNext(ringPts, index, *p0);
117
    /**
118
    * If ring orientation is not normalized, flip the corner orientation
119
    */
120
0
    bool isInteriorOnRight = ! algorithm::Orientation::isCCW(ringPts);
121
0
    if (! isInteriorOnRight) {
122
0
        const CoordinateXY* temp = rPrev;
123
0
        rPrev = rNext;
124
0
        rNext = temp;
125
0
    }
126
0
    return algorithm::PolygonNodeTopology::isInteriorSegment(p0, rPrev, rNext, p1);
127
0
}
128
129
/* private static */
130
const CoordinateXY&
131
PolygonTopologyAnalyzer::findRingVertexPrev(const CoordinateSequence* ringPts, std::size_t index, const CoordinateXY& node)
132
0
{
133
0
    std::size_t iPrev = index;
134
0
    const CoordinateXY* prev = &(ringPts->getAt<CoordinateXY>(iPrev));
135
0
    while (prev->equals2D(node)) {
136
0
      iPrev = ringIndexPrev(ringPts, iPrev);
137
0
      prev = &(ringPts->getAt<CoordinateXY>(iPrev));
138
0
    }
139
0
    return ringPts->getAt<CoordinateXY>(iPrev);
140
0
}
141
142
/* private static */
143
const CoordinateXY&
144
PolygonTopologyAnalyzer::findRingVertexNext(const CoordinateSequence* ringPts, std::size_t index, const CoordinateXY& node)
145
0
{
146
    //-- safe, since index is always the start of a ring segment
147
0
    std::size_t iNext = index + 1;
148
0
    const CoordinateXY* next = &(ringPts->getAt<CoordinateXY>(iNext));
149
0
    while (next->equals2D(node)) {
150
0
      iNext = ringIndexNext(ringPts, iNext);
151
0
      next = &(ringPts->getAt<CoordinateXY>(iNext));
152
0
    }
153
0
    return ringPts->getAt<CoordinateXY>(iNext);
154
0
}
155
156
/* private static */
157
std::size_t
158
PolygonTopologyAnalyzer::ringIndexPrev(const CoordinateSequence* ringPts, std::size_t index)
159
0
{
160
0
    if (index == 0) {
161
0
        return ringPts->getSize() - 2;
162
0
    }
163
0
    return index - 1;
164
0
}
165
166
/* private static */
167
std::size_t
168
PolygonTopologyAnalyzer::ringIndexNext(const CoordinateSequence* ringPts, std::size_t index)
169
0
{
170
0
    if (index >= ringPts->getSize() - 2) {
171
0
        return 0;
172
0
    }
173
0
    return index + 1;
174
0
}
175
176
/* private static */
177
std::size_t
178
PolygonTopologyAnalyzer::intersectingSegIndex(const CoordinateSequence* ringPts,
179
    const CoordinateXY* pt)
180
0
{
181
0
    algorithm::LineIntersector li;
182
0
    for (std::size_t i = 0; i < ringPts->size() - 1; i++) {
183
0
      if ( algorithm::PointLocation::isOnSegment(*pt, ringPts->getAt<CoordinateXY>(i), ringPts->getAt<CoordinateXY>(i+1)) ) {
184
        //-- check if pt is the start point of the next segment
185
0
        if (pt->equals2D(ringPts->getAt<CoordinateXY>(i + 1))) {
186
0
          return i + 1;
187
0
        }
188
0
        return i;
189
0
      }
190
0
    }
191
0
    throw util::IllegalArgumentException("Segment vertex does not intersect ring");
192
0
}
193
194
/* public */
195
bool
196
PolygonTopologyAnalyzer::isInteriorDisconnected()
197
0
{
198
    /**
199
     * May already be set by a double-touching hole
200
     */
201
0
    if (!disconnectionPt.isNull()) {
202
0
        return true;
203
0
    }
204
0
    if (isInvertedRingValid) {
205
0
        checkInteriorDisconnectedBySelfTouch();
206
0
        if (!disconnectionPt.isNull()) {
207
0
            return true;
208
0
        }
209
0
    }
210
0
    checkInteriorDisconnectedByHoleCycle();
211
0
    if (!disconnectionPt.isNull()) {
212
0
        return true;
213
0
    }
214
0
    return false;
215
0
}
216
217
218
/* public */
219
void
220
PolygonTopologyAnalyzer::checkInteriorDisconnectedBySelfTouch()
221
0
{
222
0
    if (! polyRings.empty()) {
223
0
        const CoordinateXY* dPt = PolygonRing::findInteriorSelfNode(polyRings);
224
0
        if (dPt)
225
0
            disconnectionPt = *dPt;
226
0
    }
227
0
}
228
229
230
/* public */
231
void
232
PolygonTopologyAnalyzer::checkInteriorDisconnectedByHoleCycle()
233
0
{
234
    /**
235
    * PolyRings will be null for empty, no hole or LinearRing inputs
236
    */
237
0
    if (! polyRings.empty()) {
238
0
        const CoordinateXY* dPt = PolygonRing::findHoleCycleLocation(polyRings);
239
0
        if (dPt)
240
0
            disconnectionPt = *dPt;
241
0
    }
242
0
}
243
244
245
/* private static */
246
std::vector<SegmentString*>
247
PolygonTopologyAnalyzer::createSegmentStrings(const Geometry* geom, bool bIsInvertedRingValid)
248
0
{
249
0
    std::vector<SegmentString*> segStrings;
250
0
    int typeId = geom->getGeometryTypeId();
251
0
    if (typeId == GEOS_LINEARRING) {
252
0
        const LinearRing* ring = static_cast<const LinearRing*>(geom);
253
0
        segStrings.push_back(createSegString(ring, nullptr));
254
0
        return segStrings;
255
0
    }
256
0
    if (! (typeId == GEOS_POLYGON || typeId == GEOS_MULTIPOLYGON)) {
257
0
        throw util::IllegalArgumentException("Cannot process non-polygonal input");
258
0
    }
259
0
    for (std::size_t i = 0; i < geom->getNumGeometries(); i++) {
260
0
        const Polygon* poly = static_cast<const Polygon*>(geom->getGeometryN(i));
261
0
        if (poly->isEmpty()) continue;
262
0
        bool hasHoles = poly->getNumInteriorRing() > 0;
263
264
        //--- polygons with no holes do not need connected interior analysis
265
0
        PolygonRing* shellRing = nullptr;
266
0
        if (hasHoles || bIsInvertedRingValid) {
267
0
            shellRing = createPolygonRing(poly->getExteriorRing());
268
0
        }
269
0
        segStrings.push_back(createSegString(poly->getExteriorRing(), shellRing));
270
271
0
        for (std::size_t j = 0 ; j < poly->getNumInteriorRing(); j++) {
272
0
            const LinearRing* hole = poly->getInteriorRingN(j);
273
0
            if (hole->isEmpty()) continue;
274
0
            PolygonRing* holeRing = createPolygonRing(hole, static_cast<int>(j), shellRing);
275
0
            segStrings.push_back(createSegString(hole, holeRing));
276
0
        }
277
0
    }
278
0
    return segStrings;
279
0
}
280
281
282
/* private */
283
PolygonRing*
284
PolygonTopologyAnalyzer::createPolygonRing(const LinearRing* p_ring)
285
0
{
286
0
    polyRingStore.emplace_back(p_ring);
287
0
    return &(polyRingStore.back());
288
0
}
289
290
291
/* private */
292
PolygonRing*
293
PolygonTopologyAnalyzer::createPolygonRing(const LinearRing* p_ring, int p_index, PolygonRing* p_shell)
294
0
{
295
0
    polyRingStore.emplace_back(p_ring, p_index, p_shell);
296
0
    return &(polyRingStore.back());
297
0
}
298
299
300
/* private static */
301
std::vector<PolygonRing*>
302
PolygonTopologyAnalyzer::getPolygonRings(const std::vector<SegmentString*>& segStrings)
303
0
{
304
0
    std::vector<PolygonRing*> polygonRings;
305
0
    for (SegmentString* ss : segStrings) {
306
307
0
        PolygonRing* polyRing = const_cast<PolygonRing*>(static_cast<const PolygonRing*>(ss->getData()));
308
0
        if (polyRing != nullptr) {
309
0
            polygonRings.push_back(polyRing);
310
0
        }
311
0
    }
312
0
    return polygonRings;
313
0
}
314
315
316
/* private static */
317
SegmentString*
318
PolygonTopologyAnalyzer::createSegString(const LinearRing* ring, const PolygonRing* polyRing)
319
0
{
320
    // Let the input LinearRing retain ownership of the
321
    // CoordinateSequence, and pass it directly into the BasicSegmentString
322
    // constructor.
323
0
    CoordinateSequence* pts = const_cast<CoordinateSequence*>(ring->getCoordinatesRO());
324
325
    // Repeated points must be removed for accurate intersection detection
326
    // So, in this case we create a de-duped copy of the CoordinateSequence
327
    // and manage the lifecycle locally. This we pass on to the SegmentString
328
0
    if (pts->hasRepeatedPoints()) {
329
0
        std::unique_ptr<CoordinateSequence> newPts = RepeatedPointRemover::removeRepeatedPoints(pts);
330
0
        pts = newPts.get();
331
0
        coordSeqStore.emplace_back(newPts.release());
332
0
    }
333
334
    // Allocate the BasicSegmentString in the store and return a
335
    // pointer into the store. This way we don't have to track the
336
    // individual SegmentStrings, they just go away when the
337
    // PolygonTopologyAnalyzer deallocates.
338
0
    segStringStore.emplace_back(pts, polyRing);
339
0
    SegmentString* ss = static_cast<SegmentString*>(&(segStringStore.back()));
340
0
    return ss;
341
0
}
342
343
344
} // namespace geos.operation.valid
345
} // namespace geos.operation
346
} // namespace geos