/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 |