/src/geos/src/coverage/CoveragePolygonValidator.cpp
Line | Count | Source |
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/coverage/CoveragePolygonValidator.h> |
16 | | |
17 | | #include <geos/coverage/InvalidSegmentDetector.h> |
18 | | #include <geos/coverage/CoveragePolygon.h> |
19 | | |
20 | | #include <geos/algorithm/Orientation.h> |
21 | | #include <geos/geom/Coordinate.h> |
22 | | #include <geos/geom/Envelope.h> |
23 | | #include <geos/geom/Geometry.h> |
24 | | #include <geos/geom/GeometryFactory.h> |
25 | | #include <geos/geom/LineSegment.h> |
26 | | #include <geos/geom/LineString.h> |
27 | | #include <geos/geom/Location.h> |
28 | | #include <geos/geom/Polygon.h> |
29 | | #include <geos/geom/util/PolygonExtracter.h> |
30 | | #include <geos/noding/MCIndexSegmentSetMutualIntersector.h> |
31 | | #include <geos/operation/valid/RepeatedPointRemover.h> |
32 | | |
33 | | |
34 | | using geos::algorithm::locate::IndexedPointInAreaLocator; |
35 | | using geos::algorithm::Orientation; |
36 | | using geos::geom::CoordinateXY; |
37 | | using geos::geom::Envelope; |
38 | | using geos::geom::Geometry; |
39 | | using geos::geom::GeometryFactory; |
40 | | using geos::geom::LineString; |
41 | | using geos::geom::LineSegment; |
42 | | using geos::geom::Location; |
43 | | using geos::geom::Polygon; |
44 | | using geos::geom::util::PolygonExtracter; |
45 | | using geos::noding::MCIndexSegmentSetMutualIntersector; |
46 | | using geos::noding::SegmentString; |
47 | | using geos::operation::valid::RepeatedPointRemover; |
48 | | |
49 | | namespace geos { // geos |
50 | | namespace coverage { // geos.coverage |
51 | | |
52 | | |
53 | | /* public static */ |
54 | | std::unique_ptr<Geometry> |
55 | | CoveragePolygonValidator::validate(const Geometry* targetPolygon, std::vector<const Geometry*>& adjPolygons) |
56 | 0 | { |
57 | 0 | CoveragePolygonValidator v(targetPolygon, adjPolygons); |
58 | 0 | return v.validate(); |
59 | 0 | } |
60 | | |
61 | | |
62 | | /* public static */ |
63 | | std::unique_ptr<Geometry> |
64 | | CoveragePolygonValidator::validate(const Geometry* targetPolygon, std::vector<const Geometry*>& adjPolygons, double gapWidth) |
65 | 0 | { |
66 | 0 | CoveragePolygonValidator v(targetPolygon, adjPolygons); |
67 | 0 | v.setGapWidth(gapWidth); |
68 | 0 | return v.validate(); |
69 | 0 | } |
70 | | |
71 | | |
72 | | /* public */ |
73 | | CoveragePolygonValidator::CoveragePolygonValidator( |
74 | | const Geometry* geom, |
75 | | std::vector<const Geometry*>& p_adjGeoms) |
76 | 0 | : targetGeom(geom) |
77 | 0 | , adjGeoms(p_adjGeoms) |
78 | 0 | , geomFactory(geom->getFactory()) |
79 | 0 | {} |
80 | | |
81 | | |
82 | | /* public */ |
83 | | void |
84 | | CoveragePolygonValidator::setGapWidth(double p_gapWidth) |
85 | 0 | { |
86 | 0 | gapWidth = p_gapWidth; |
87 | 0 | } |
88 | | |
89 | | |
90 | | /* public */ |
91 | | std::unique_ptr<Geometry> |
92 | | CoveragePolygonValidator::validate() |
93 | 0 | { |
94 | 0 | std::vector<const Polygon*> adjPolygons = extractPolygons(adjGeoms); |
95 | 0 | m_adjCovPolygons = toCoveragePolygons(adjPolygons); |
96 | 0 | std::vector<CoverageRing*> targetRings = createRings(targetGeom); |
97 | 0 | std::vector<CoverageRing*> adjRings = createRings(adjPolygons); |
98 | | |
99 | | /** |
100 | | * Mark matching segments first. |
101 | | * Matched segments are not considered for further checks. |
102 | | * This improves performance substantially for mostly-valid coverages. |
103 | | */ |
104 | 0 | Envelope targetEnv = *(targetGeom->getEnvelopeInternal()); |
105 | 0 | targetEnv.expandBy(gapWidth); |
106 | |
|
107 | 0 | checkTargetRings(targetRings, adjRings, targetEnv); |
108 | |
|
109 | 0 | return createInvalidLines(targetRings); |
110 | 0 | } |
111 | | |
112 | | /* private static */ |
113 | | std::vector<std::unique_ptr<CoveragePolygon>> |
114 | 0 | CoveragePolygonValidator::toCoveragePolygons(const std::vector<const Polygon*> polygons) { |
115 | 0 | std::vector<std::unique_ptr<CoveragePolygon>> covPolys; |
116 | 0 | for (const Polygon* poly : polygons) { |
117 | 0 | covPolys.push_back( std::make_unique<CoveragePolygon>(poly) ); |
118 | 0 | } |
119 | 0 | return covPolys; |
120 | 0 | } |
121 | | |
122 | | /* private */ |
123 | | void |
124 | | CoveragePolygonValidator::checkTargetRings( |
125 | | std::vector<CoverageRing*>& targetRings, |
126 | | std::vector<CoverageRing*>& adjRings, |
127 | | const Envelope& targetEnv) |
128 | 0 | { |
129 | 0 | markMatchedSegments(targetRings, adjRings, targetEnv); |
130 | | |
131 | | /** |
132 | | * Short-circuit if target is fully known (matched or invalid). |
133 | | * This often happens in clean coverages, |
134 | | * when the target is surrounded by matching polygons. |
135 | | * It can also happen in invalid coverages |
136 | | * which have polygons which are duplicates, |
137 | | * or perfectly overlap other polygons. |
138 | | */ |
139 | 0 | if (CoverageRing::isKnown(targetRings)) |
140 | 0 | return; |
141 | | |
142 | | /** |
143 | | * Here target has at least one unmatched segment. |
144 | | * Do further checks to see if any of them are are invalid. |
145 | | */ |
146 | 0 | markInvalidInteractingSegments(targetRings, adjRings, gapWidth); |
147 | 0 | markInvalidInteriorSegments(targetRings, m_adjCovPolygons); |
148 | 0 | } |
149 | | |
150 | | /* private static */ |
151 | | std::vector<const Polygon*> |
152 | | CoveragePolygonValidator::extractPolygons(std::vector<const Geometry*>& geoms) |
153 | 0 | { |
154 | 0 | std::vector<const Polygon*> polygons; |
155 | 0 | for (const Geometry* geom : geoms) { |
156 | 0 | PolygonExtracter::getPolygons(*geom, polygons); |
157 | 0 | } |
158 | 0 | return polygons; |
159 | 0 | } |
160 | | |
161 | | |
162 | | /* private */ |
163 | | std::unique_ptr<Geometry> |
164 | | CoveragePolygonValidator::createEmptyResult() |
165 | 0 | { |
166 | 0 | return geomFactory->createLineString(); |
167 | 0 | } |
168 | | |
169 | | |
170 | | /* private */ |
171 | | void |
172 | | CoveragePolygonValidator::markMatchedSegments( |
173 | | std::vector<CoverageRing*>& targetRings, |
174 | | std::vector<CoverageRing*>& adjRngs, |
175 | | const Envelope& targetEnv) |
176 | 0 | { |
177 | 0 | CoverageRingSegmentMap segmentMap; |
178 | 0 | markMatchedSegments(targetRings, targetEnv, segmentMap); |
179 | 0 | markMatchedSegments(adjRngs, targetEnv, segmentMap); |
180 | 0 | } |
181 | | |
182 | | |
183 | | /* private */ |
184 | | void |
185 | | CoveragePolygonValidator::markMatchedSegments( |
186 | | std::vector<CoverageRing*>& rings, |
187 | | const Envelope& envLimit, |
188 | | CoverageRingSegmentMap& segmentMap) |
189 | 0 | { |
190 | 0 | for (CoverageRing* ring : rings) { |
191 | 0 | for (std::size_t i = 0; i < ring->size() - 1; i++) { |
192 | 0 | const Coordinate& p0 = ring->getCoordinate(i); |
193 | 0 | const Coordinate& p1 = ring->getCoordinate(i + 1); |
194 | | |
195 | | //-- skip segments which lie outside the limit envelope |
196 | 0 | if (! envLimit.intersects(p0, p1)) { |
197 | 0 | continue; |
198 | 0 | } |
199 | | //-- if segment keys match, mark them as matched (or invalid) |
200 | 0 | CoverageRingSegment* seg = createCoverageRingSegment(ring, i); |
201 | 0 | auto search = segmentMap.find(seg); |
202 | 0 | if (search != segmentMap.end()) { |
203 | 0 | CoverageRingSegment* segMatch = search->second; |
204 | 0 | seg->match(segMatch); |
205 | 0 | } |
206 | 0 | else { |
207 | 0 | segmentMap[seg] = seg; |
208 | 0 | } |
209 | 0 | } |
210 | 0 | } |
211 | 0 | } |
212 | | |
213 | | |
214 | | /* private */ |
215 | | CoveragePolygonValidator::CoverageRingSegment* |
216 | | CoveragePolygonValidator::createCoverageRingSegment(CoverageRing* ring, std::size_t index) |
217 | 0 | { |
218 | 0 | const Coordinate& p0 = ring->getCoordinate(index); |
219 | 0 | const Coordinate& p1 = ring->getCoordinate(index + 1); |
220 | |
|
221 | 0 | if(ring->isInteriorOnRight()) { |
222 | 0 | coverageRingSegmentStore.emplace_back(p0, p1, ring, index); |
223 | 0 | } |
224 | 0 | else { |
225 | 0 | coverageRingSegmentStore.emplace_back(p1, p0, ring, index); |
226 | 0 | } |
227 | 0 | CoverageRingSegment& seg = coverageRingSegmentStore.back(); |
228 | 0 | return &seg; |
229 | 0 | } |
230 | | |
231 | | |
232 | | /* private */ |
233 | | void |
234 | | CoveragePolygonValidator::markInvalidInteractingSegments( |
235 | | std::vector<CoverageRing*>& targetRings, |
236 | | std::vector<CoverageRing*>& adjRings, |
237 | | double distanceTolerance) |
238 | 0 | { |
239 | 0 | std::vector<const SegmentString*> targetSS; |
240 | 0 | for (auto cr: targetRings) { |
241 | 0 | targetSS.push_back(static_cast<SegmentString*>(cr)); |
242 | 0 | } |
243 | 0 | std::vector<const SegmentString*> adjSS; |
244 | 0 | for (auto cr: adjRings) { |
245 | 0 | adjSS.push_back(static_cast<SegmentString*>(cr)); |
246 | 0 | } |
247 | |
|
248 | 0 | InvalidSegmentDetector detector(distanceTolerance); |
249 | 0 | MCIndexSegmentSetMutualIntersector segSetMutInt(distanceTolerance); |
250 | 0 | segSetMutInt.setBaseSegments(&targetSS); |
251 | 0 | segSetMutInt.setSegmentIntersector(&detector); |
252 | 0 | segSetMutInt.process(&adjSS); |
253 | 0 | } |
254 | | |
255 | | |
256 | | /* private */ |
257 | | void |
258 | | CoveragePolygonValidator::markInvalidInteriorSegments( |
259 | | std::vector<CoverageRing*>& targetRings, |
260 | | std::vector<std::unique_ptr<CoveragePolygon>>& adjCovPolygons ) |
261 | 0 | { |
262 | 0 | for (CoverageRing* ring : targetRings) { |
263 | 0 | std::size_t stride = 1000; //-- RING_SECTION_STRIDE; |
264 | 0 | for (std::size_t i = 0; i < ring->size() - 1; i += stride) { |
265 | 0 | std::size_t iEnd = i + stride; |
266 | 0 | if (iEnd >= ring->size()) |
267 | 0 | iEnd = ring->size() - 1; |
268 | 0 | markInvalidInteriorSection(*ring, i, iEnd, adjCovPolygons); |
269 | 0 | } |
270 | 0 | } |
271 | 0 | } |
272 | | |
273 | | /* private */ |
274 | | void |
275 | | CoveragePolygonValidator::markInvalidInteriorSection( |
276 | | CoverageRing& ring, |
277 | | std::size_t iStart, |
278 | | std::size_t iEnd, |
279 | | std::vector<std::unique_ptr<CoveragePolygon>>& adjCovPolygons ) |
280 | 0 | { |
281 | 0 | Envelope sectionEnv = ring.getEnvelope(iStart, iEnd); |
282 | | //TODO: is it worth indexing polygons? |
283 | 0 | for (auto& adjPoly : adjCovPolygons) { |
284 | 0 | if (adjPoly->intersectsEnv(sectionEnv)) { |
285 | | //-- test vertices in section |
286 | 0 | for (auto i = iStart; i < iEnd; i++) { |
287 | 0 | markInvalidInteriorSegment(ring, i, adjPoly.get()); |
288 | 0 | } |
289 | 0 | } |
290 | 0 | } |
291 | 0 | } |
292 | | |
293 | | /* private */ |
294 | | void |
295 | | CoveragePolygonValidator::markInvalidInteriorSegment( |
296 | | CoverageRing& ring, std::size_t i, CoveragePolygon* adjPoly) |
297 | 0 | { |
298 | | //-- skip check for segments with known state. |
299 | 0 | if (ring.isKnown(i)) |
300 | 0 | return; |
301 | | |
302 | | /** |
303 | | * Check if vertex is in interior of an adjacent polygon. |
304 | | * If so, the segments on either side are in the interior. |
305 | | * Mark them invalid, unless they are already matched. |
306 | | */ |
307 | 0 | const CoordinateXY& p = ring.getCoordinate(i); |
308 | 0 | if (adjPoly->contains(p)) { |
309 | 0 | ring.markInvalid(i); |
310 | | //-- previous segment may be interior (but may also be matched) |
311 | 0 | auto iPrev = i == 0 ? ring.size()-2 : i-1; |
312 | 0 | if (! ring.isKnown(iPrev)) |
313 | 0 | ring.markInvalid(iPrev); |
314 | 0 | } |
315 | 0 | } |
316 | | |
317 | | /* private */ |
318 | | std::unique_ptr<Geometry> |
319 | | CoveragePolygonValidator::createInvalidLines(std::vector<CoverageRing*>& rings) |
320 | 0 | { |
321 | 0 | std::vector<std::unique_ptr<LineString>> lines; |
322 | 0 | for (CoverageRing* ring : rings) { |
323 | 0 | ring->createInvalidLines(geomFactory, lines); |
324 | 0 | } |
325 | |
|
326 | 0 | if (lines.size() == 0) { |
327 | 0 | return createEmptyResult(); |
328 | 0 | } |
329 | 0 | else if (lines.size() == 1) { |
330 | 0 | return lines[0]->clone(); |
331 | 0 | } |
332 | | |
333 | 0 | return geomFactory->createMultiLineString(std::move(lines)); |
334 | 0 | } |
335 | | |
336 | | /**********************************************************************************/ |
337 | | |
338 | | /* private */ |
339 | | std::vector<CoverageRing*> |
340 | | CoveragePolygonValidator::createRings(const Geometry* geom) |
341 | 0 | { |
342 | 0 | std::vector<const Polygon*> polygons; |
343 | 0 | geom::util::PolygonExtracter::getPolygons(*geom, polygons); |
344 | 0 | return createRings(polygons); |
345 | 0 | } |
346 | | |
347 | | /* private */ |
348 | | std::vector<CoverageRing*> |
349 | | CoveragePolygonValidator::createRings(std::vector<const Polygon*>& polygons) |
350 | 0 | { |
351 | 0 | std::vector<CoverageRing*> rings; |
352 | 0 | for (const Polygon* poly : polygons) { |
353 | 0 | createRings(poly, rings); |
354 | 0 | } |
355 | 0 | return rings; |
356 | 0 | } |
357 | | |
358 | | /* private */ |
359 | | void |
360 | | CoveragePolygonValidator::createRings( |
361 | | const Polygon* poly, |
362 | | std::vector<CoverageRing*>& rings) |
363 | 0 | { |
364 | | // Create exterior shell ring |
365 | 0 | addRing( poly->getExteriorRing(), true, rings); |
366 | | |
367 | | // Create hole rings |
368 | 0 | for (std::size_t i = 0; i < poly->getNumInteriorRing(); i++) { |
369 | 0 | addRing( poly->getInteriorRingN(i), false, rings); |
370 | 0 | } |
371 | 0 | } |
372 | | |
373 | | /* private */ |
374 | | void |
375 | | CoveragePolygonValidator::addRing( |
376 | | const LinearRing* ring, |
377 | | bool isShell, |
378 | | std::vector<CoverageRing*>& rings) |
379 | 0 | { |
380 | 0 | if (ring->isEmpty()) |
381 | 0 | return; |
382 | 0 | rings.push_back(createRing(ring, isShell)); |
383 | 0 | } |
384 | | |
385 | | /* private */ |
386 | | CoverageRing* |
387 | | CoveragePolygonValidator::createRing(const LinearRing* ring, bool isShell) |
388 | 0 | { |
389 | 0 | CoordinateSequence* pts = const_cast<CoordinateSequence*>(ring->getCoordinatesRO()); |
390 | 0 | if (pts->hasRepeatedOrInvalidPoints()) { |
391 | 0 | CoordinateSequence* cleanPts = RepeatedPointRemover::removeRepeatedAndInvalidPoints(pts).release(); |
392 | 0 | localCoordinateSequences.emplace_back(cleanPts); |
393 | 0 | pts = cleanPts; |
394 | 0 | } |
395 | 0 | bool isCCW = Orientation::isCCW(pts); |
396 | 0 | bool isInteriorOnRight = isShell ? ! isCCW : isCCW; |
397 | 0 | coverageRingStore.emplace_back(pts, isInteriorOnRight); |
398 | 0 | CoverageRing& cRing = coverageRingStore.back(); |
399 | 0 | return &cRing; |
400 | 0 | } |
401 | | |
402 | | |
403 | | |
404 | | |
405 | | } // namespace geos.coverage |
406 | | } // namespace geos |