Coverage Report

Created: 2025-12-14 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/geos/src/coverage/CoverageCleaner.cpp
Line
Count
Source
1
/**********************************************************************
2
 *
3
 * GEOS - Geometry Engine Open Source
4
 * http://geos.osgeo.org
5
 *
6
 * Copyright (c) 2025 Martin Davis
7
 * Copyright (C) 2025 Paul Ramsey <pramsey@cleverelephant.ca>
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/coverage/CoverageCleaner.h>
17
18
#include <geos/algorithm/construct/MaximumInscribedCircle.h>
19
#include <geos/algorithm/locate/SimplePointInAreaLocator.h>
20
#include <geos/dissolve/LineDissolver.h>
21
#include <geos/geom/Envelope.h>
22
#include <geos/geom/Geometry.h>
23
#include <geos/geom/GeometryFactory.h>
24
#include <geos/geom/MultiPolygon.h>
25
#include <geos/geom/Point.h>
26
#include <geos/geom/Polygon.h>
27
#include <geos/noding/Noder.h>
28
#include <geos/noding/SegmentStringUtil.h>
29
#include <geos/noding/snap/SnappingNoder.h>
30
#include <geos/operation/polygonize/Polygonizer.h>
31
#include <geos/operation/relateng/RelateNG.h>
32
#include <geos/util/IllegalArgumentException.h>
33
34
35
using geos::algorithm::construct::MaximumInscribedCircle;
36
using geos::algorithm::locate::SimplePointInAreaLocator;
37
using geos::dissolve::LineDissolver;
38
using geos::geom::Envelope;
39
using geos::geom::Geometry;
40
using geos::geom::GeometryFactory;
41
using geos::geom::MultiPolygon;
42
using geos::geom::Point;
43
using geos::geom::Polygon;
44
using geos::noding::Noder;
45
using geos::noding::SegmentStringUtil;
46
using geos::noding::snap::SnappingNoder;
47
using geos::operation::polygonize::Polygonizer;
48
using geos::operation::relateng::RelateNG;
49
50
51
namespace geos {     // geos
52
namespace coverage { // geos.coverage
53
54
55
/* public static */
56
std::vector<std::unique_ptr<Geometry>>
57
CoverageCleaner::clean(std::vector<const Geometry*>& p_coverage,
58
    double p_snappingDistance,
59
    int p_overlapMergeStrategy,
60
    double p_maxGapWidth)
61
0
{
62
0
    CoverageCleaner cc(p_coverage);
63
0
    cc.setSnappingDistance(p_snappingDistance);
64
0
    cc.setGapMaximumWidth(p_maxGapWidth);
65
0
    cc.setOverlapMergeStrategy(p_overlapMergeStrategy);
66
0
    cc.clean();
67
0
    return cc.getResult();
68
0
}
69
70
71
/* public static */
72
std::vector<std::unique_ptr<Geometry>>
73
CoverageCleaner::clean(std::vector<const Geometry*>& p_coverage,
74
    double p_snappingDistance,
75
    double p_maxGapWidth)
76
0
{
77
0
    CoverageCleaner cc(p_coverage);
78
0
    cc.setSnappingDistance(p_snappingDistance);
79
0
    cc.setGapMaximumWidth(p_maxGapWidth);
80
0
    cc.clean();
81
0
    return cc.getResult();
82
0
}
83
84
85
/* public static */
86
std::vector<std::unique_ptr<Geometry>>
87
CoverageCleaner::cleanOverlapGap(std::vector<const Geometry*>& p_coverage,
88
      int p_overlapMergeStrategy,
89
      double p_maxGapWidth)
90
0
{
91
0
    return clean(p_coverage, -1, p_overlapMergeStrategy, p_maxGapWidth);
92
0
}
93
94
95
/* public static */
96
std::vector<std::unique_ptr<Geometry>>
97
CoverageCleaner::cleanGapWidth(std::vector<const Geometry*>& p_coverage,
98
    double p_maxGapWidth)
99
0
{
100
0
    return clean(p_coverage, -1, p_maxGapWidth);
101
0
}
102
103
104
/* public */
105
CoverageCleaner::CoverageCleaner(std::vector<const Geometry*>& p_coverage)
106
0
    : coverage(p_coverage)
107
0
    , geomFactory(p_coverage.empty() ? nullptr : coverage[0]->getFactory())
108
0
    , snappingDistance(computeDefaultSnappingDistance(p_coverage))
109
0
{}
110
111
112
/* public */
113
void
114
CoverageCleaner::setSnappingDistance(double p_snappingDistance)
115
0
{
116
    //-- use default distance if invalid argument
117
0
    if (p_snappingDistance < 0)
118
0
        return;
119
0
    snappingDistance = p_snappingDistance;
120
0
}
121
122
123
/* public */
124
void
125
CoverageCleaner::setOverlapMergeStrategy(int mergeStrategy)
126
0
{
127
0
    if (mergeStrategy < MERGE_LONGEST_BORDER ||
128
0
        mergeStrategy > MERGE_MIN_INDEX)
129
0
        throw util::IllegalArgumentException("Invalid merge strategy code");
130
131
0
    overlapMergeStrategy = mergeStrategy;
132
0
}
133
134
135
/* public */
136
void
137
CoverageCleaner::setGapMaximumWidth(double maxWidth)
138
0
{
139
0
    if (maxWidth < 0)
140
0
        return;
141
0
    gapMaximumWidth = maxWidth;
142
0
}
143
144
145
/* public */
146
void
147
CoverageCleaner::clean()
148
0
{
149
0
    computeResultants(snappingDistance);
150
    //System.out.format("Overlaps: %d    Gaps: %d\n", overlaps.size(), mergableGaps.size());
151
152
    //Stopwatch sw = new Stopwatch();
153
0
    mergeOverlaps(overlapParentMap);
154
    //System.out.println("Merge Overlaps: " + sw.getTimeString());
155
    //sw.reset();
156
0
    cleanCov->mergeGaps(mergableGaps);
157
    //System.out.println("Merge Gaps: " + sw.getTimeString());
158
0
}
159
160
161
/* public */
162
std::vector<std::unique_ptr<Geometry>>
163
CoverageCleaner::getResult()
164
0
{
165
0
    return cleanCov->toCoverage(geomFactory);
166
0
}
167
168
169
/* public */
170
std::vector<const Polygon*>
171
CoverageCleaner::getOverlaps()
172
0
{
173
0
    return overlaps;
174
0
}
175
176
177
/* public */
178
std::vector<const Polygon*>
179
CoverageCleaner::getMergedGaps()
180
0
{
181
0
    return mergableGaps;
182
0
}
183
184
//-------------------------------------------------
185
186
/* private static */
187
double
188
CoverageCleaner::computeDefaultSnappingDistance(std::vector<const Geometry*>& geoms)
189
0
{
190
0
    double diameter = extent(geoms).getDiameter();
191
0
    return diameter / DEFAULT_SNAPPING_FACTOR;
192
0
}
193
194
195
/* private static */
196
Envelope
197
CoverageCleaner::extent(std::vector<const Geometry*>& geoms)
198
0
{
199
0
    Envelope env;
200
0
    for (const Geometry* geom : geoms) {
201
0
        env.expandToInclude(geom->getEnvelopeInternal());
202
0
    }
203
0
    return env;
204
0
}
205
206
207
/* private */
208
void
209
CoverageCleaner::mergeOverlaps(
210
    std::map<std::size_t, std::vector<std::size_t>>& overlapParentMap_p)
211
0
{
212
0
    for (const auto& [resIndex, _] : overlapParentMap_p) {
213
0
        auto ms = mergeStrategy(overlapMergeStrategy);
214
0
        cleanCov->mergeOverlap(
215
0
            resultants[resIndex].get(),
216
0
            *ms,
217
0
            overlapParentMap_p[resIndex]);
218
0
    }
219
0
}
220
221
222
/* private */
223
std::unique_ptr<CleanCoverage::MergeStrategy>
224
CoverageCleaner::mergeStrategy(int mergeStrategyId)
225
0
{
226
0
    switch (mergeStrategyId) {
227
0
        case MERGE_LONGEST_BORDER:
228
0
            return std::make_unique<CleanCoverage::BorderMergeStrategy>();
229
0
        case MERGE_MAX_AREA:
230
0
            return std::make_unique<CleanCoverage::AreaMergeStrategy>(true);
231
0
        case MERGE_MIN_AREA:
232
0
            return std::make_unique<CleanCoverage::AreaMergeStrategy>(false);
233
0
        case MERGE_MIN_INDEX:
234
0
            return std::make_unique<CleanCoverage::IndexMergeStrategy>(false);
235
0
    }
236
0
    throw util::IllegalArgumentException("CoverageCleaner::mergeStrategy - Unknown merge strategy");
237
0
}
238
239
240
/* private */
241
void
242
CoverageCleaner::computeResultants(double tolerance)
243
0
{
244
    //System.out.println("Coverage Cleaner ===> polygons: " + coverage.length);
245
    //System.out.format("Snapping distance: %f\n", snappingDistance);
246
    //Stopwatch sw = new Stopwatch();
247
    //sw.start();
248
249
0
    std::unique_ptr<Geometry> nodedEdges = node(coverage, tolerance);
250
    //System.out.println("Noding: " + sw.getTimeString());
251
252
    //sw.reset();
253
0
    std::unique_ptr<Geometry> cleanEdges = LineDissolver::dissolve(nodedEdges.get());
254
    //System.out.println("Dissolve: " + sw.getTimeString());
255
256
    //sw.reset();
257
0
    resultants = polygonize(cleanEdges.get());
258
    //System.out.println("Polygonize: " + sw.getTimeString());
259
260
0
    cleanCov = std::make_unique<CleanCoverage>(coverage.size());
261
262
    //sw.reset();
263
0
    createCoverageIndex();
264
0
    classifyResult(resultants);
265
    //System.out.println("Classify: " + sw.getTimeString());
266
267
0
    mergableGaps = findMergableGaps(gaps);
268
0
 }
269
270
271
/* private */
272
void
273
CoverageCleaner::createCoverageIndex()
274
0
{
275
0
    covIndex = std::make_unique<index::strtree::TemplateSTRtree<std::size_t>>();
276
0
    for (std::size_t i = 0; i < coverage.size(); i++) {
277
0
        covIndex->insert(*(coverage[i]->getEnvelopeInternal()), i);
278
0
    }
279
0
}
280
281
282
/* private */
283
void
284
CoverageCleaner::classifyResult(std::vector<std::unique_ptr<Polygon>>& rs)
285
0
{
286
0
    for (std::size_t i = 0; i < rs.size(); i++) {
287
0
        classifyResultant(i, rs[i].get());
288
0
    }
289
0
}
290
291
292
/* private */
293
void
294
CoverageCleaner::classifyResultant(std::size_t resultIndex, const Polygon* resPoly)
295
0
{
296
0
    std::unique_ptr<Point> intPt = resPoly->getInteriorPoint();
297
0
    std::size_t parentIndex = INDEX_UNKNOWN;
298
0
    std::vector<std::size_t> overlapIndexes;
299
300
0
    std::vector<std::size_t> candidateParentIndex;
301
0
    covIndex->query(*(intPt->getEnvelopeInternal()), candidateParentIndex);
302
303
0
    for (std::size_t i : candidateParentIndex) {
304
0
        const Geometry* parent = coverage[i];
305
0
        if (covers(parent, intPt.get())) {
306
            //-- found first parent
307
0
            if (parentIndex == INDEX_UNKNOWN) {
308
0
                parentIndex = i;
309
0
            }
310
0
            else {
311
                //-- more than one parent - record them all
312
0
                overlapIndexes.push_back(parentIndex);
313
0
                overlapIndexes.push_back(i);
314
0
            }
315
0
        }
316
0
    }
317
    /**
318
     * Classify resultant based on # of parents:
319
     * 0 - gap
320
     * 1 - single polygon face
321
     * >1 - overlap
322
     */
323
0
    if (parentIndex == INDEX_UNKNOWN) {
324
0
        gaps.push_back(resPoly);
325
0
    }
326
0
    else if (!overlapIndexes.empty()) {
327
0
        overlapParentMap[resultIndex] = overlapIndexes;
328
0
        overlaps.push_back(resPoly);
329
0
    }
330
0
    else {
331
0
        cleanCov->add(parentIndex, resPoly);
332
0
    }
333
0
}
334
335
336
/* private static */
337
bool
338
CoverageCleaner::covers(const Geometry* poly, const Point* intPt)
339
0
{
340
0
    return SimplePointInAreaLocator::isContained(
341
0
        *(intPt->getCoordinate()),
342
0
        poly);
343
0
}
344
345
346
/* private */
347
std::vector<const Polygon*>
348
CoverageCleaner::findMergableGaps(std::vector<const Polygon*> p_gaps)
349
0
{
350
0
    std::vector<const Polygon*> filtered;
351
352
0
    std::copy_if(p_gaps.begin(), p_gaps.end(),
353
0
                 std::back_inserter(filtered),
354
0
                 [this](const Polygon* gap) { return isMergableGap(gap); }
355
0
                 );
356
357
0
    return filtered;
358
359
    // return gaps.stream().filter(gap -> isMergableGap(gap)).collect(Collectors.toList());
360
0
}
361
362
363
/* private */
364
bool
365
CoverageCleaner::isMergableGap(const Polygon* gap)
366
0
{
367
0
    if (gapMaximumWidth <= 0) {
368
0
        return false;
369
0
    }
370
0
    return MaximumInscribedCircle::isRadiusWithin(gap, gapMaximumWidth / 2.0);
371
0
}
372
373
374
/* private static */
375
std::vector<std::unique_ptr<geom::Polygon>>
376
CoverageCleaner::polygonize(const Geometry* cleanEdges)
377
0
{
378
0
    Polygonizer polygonizer;
379
0
    polygonizer.add(cleanEdges);
380
0
    return polygonizer.getPolygons();
381
0
}
382
383
384
/* public static */
385
std::unique_ptr<Geometry>
386
CoverageCleaner::toGeometry(
387
    std::vector<std::unique_ptr<SegmentString>>& segStrings,
388
    const GeometryFactory* geomFact)
389
0
{
390
0
    std::vector<std::unique_ptr<LineString>> lines;
391
0
    for (auto& ss : segStrings) {
392
0
        auto cs = ss->getCoordinates()->clone();
393
0
        std::unique_ptr<LineString> line = geomFact->createLineString(std::move(cs));
394
0
        lines.emplace_back(line.release());
395
0
    }
396
0
    if (lines.size() == 1) return lines[0]->clone();
397
0
    return geomFact->createMultiLineString(std::move(lines));
398
0
}
399
400
401
/* public static */
402
std::unique_ptr<Geometry>
403
CoverageCleaner::node(std::vector<const Geometry*>& p_coverage, double p_snapDistance)
404
0
{
405
0
    std::vector<const SegmentString*> csegs;
406
407
0
    for (const Geometry* geom : p_coverage) {
408
        //-- skip non-polygonal and empty elements
409
0
        if (! isPolygonal(geom))
410
0
            continue;
411
0
        if (geom->isEmpty())
412
0
            continue;
413
0
        SegmentStringUtil::extractSegmentStrings(geom, csegs);
414
0
    }
415
416
0
    std::vector<SegmentString*> segs;
417
0
    for (auto* css : csegs) {
418
0
        segs.push_back(const_cast<SegmentString*>(css));
419
0
    }
420
421
0
    SnappingNoder noder(p_snapDistance);
422
0
    noder.computeNodes(segs);
423
0
    auto nodedSegStrings= noder.getNodedSubstrings();
424
0
    for (auto* ss : segs) {
425
0
        delete ss;
426
0
    }
427
428
0
    auto result = toGeometry(nodedSegStrings, geomFactory);
429
430
0
    return result;
431
0
}
432
433
/* private static */
434
bool
435
CoverageCleaner::isPolygonal(const Geometry* geom)
436
0
{
437
0
    return geom->getGeometryTypeId() == geom::GEOS_POLYGON ||
438
0
           geom->getGeometryTypeId() == geom::GEOS_MULTIPOLYGON;
439
0
}
440
441
442
/* private static */
443
std::vector<const Polygon*>
444
CoverageCleaner::toPolygonArray(const Geometry* geom)
445
0
{
446
0
    std::size_t sz = geom->getNumGeometries();
447
0
    std::vector<const Polygon*> geoms;
448
0
    geoms.resize(sz);
449
0
    for (std::size_t i = 0; i < sz; i++) {
450
0
        const Geometry* subgeom = geom->getGeometryN(i);
451
0
        geoms.push_back(static_cast<const Polygon*>(subgeom));
452
0
    }
453
0
    return geoms;
454
0
}
455
456
457
} // namespace geos.coverage
458
} // namespace geos
459
460