Coverage Report

Created: 2025-11-24 06:57

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