Coverage Report

Created: 2026-07-16 06:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/geos/src/operation/overlayng/EdgeNodingBuilder.cpp
Line
Count
Source
1
/**********************************************************************
2
 *
3
 * GEOS - Geometry Engine Open Source
4
 * http://geos.osgeo.org
5
 *
6
 * Copyright (C) 2020 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
 * Last port: operation/overlayng/EdgeNodingBuilder.java 6ef89b096
16
 *
17
 **********************************************************************/
18
19
#include <geos/algorithm/Orientation.h>
20
#include <geos/geom/CompoundCurve.h>
21
#include <geos/geom/CircularString.h>
22
#include <geos/noding/ValidatingNoder.h>
23
#include <geos/noding/NodedSegmentString.h>
24
#include <geos/noding/MCIndexNoder.h>
25
#include <geos/noding/NodableArcString.h>
26
#include <geos/noding/SimpleNoder.h>
27
#include <geos/noding/snapround/SnapRoundingNoder.h>
28
#include <geos/operation/overlayng/EdgeNodingBuilder.h>
29
#include <geos/operation/overlayng/EdgeMerger.h>
30
#include <geos/operation/valid/RepeatedPointRemover.h>
31
#include <geos/operation/overlayng/OverlayUtil.h>
32
#include <geos/util.h>
33
34
35
namespace geos {      // geos
36
namespace operation { // geos.operation
37
namespace overlayng { // geos.operation.overlayng
38
39
using namespace geos::geom;
40
using geos::operation::valid::RepeatedPointRemover;
41
using geos::noding::snapround::SnapRoundingNoder;
42
using geos::noding::Noder;
43
using geos::noding::NodableArcString;
44
using geos::noding::MCIndexNoder;
45
using geos::noding::ValidatingNoder;
46
using geos::noding::SegmentString;
47
using geos::noding::SimpleNoder;
48
using geos::noding::NodedSegmentString;
49
using geos::noding::PathString;
50
51
52
EdgeNodingBuilder::~EdgeNodingBuilder()
53
0
{
54
0
    for (auto* ss: inputEdges) {
55
0
        delete ss;
56
0
    }
57
0
}
58
59
/*private*/
60
Noder*
61
EdgeNodingBuilder::getNoder()
62
0
{
63
0
    if (customNoder != nullptr) {
64
0
        return customNoder;
65
0
    }
66
67
0
    if (OverlayUtil::isFloating(pm)) {
68
0
        internalNoder = createFloatingPrecisionNoder(IS_NODING_VALIDATED);
69
0
    }
70
0
    else {
71
0
        internalNoder = createFixedPrecisionNoder(pm);
72
0
    }
73
0
    return internalNoder.get();
74
0
}
75
76
/*private*/
77
std::unique_ptr<Noder>
78
EdgeNodingBuilder::createFixedPrecisionNoder(const PrecisionModel* p_pm)
79
0
{
80
0
    std::unique_ptr<Noder> srNoder(new SnapRoundingNoder(p_pm));
81
0
    return srNoder;
82
0
}
83
84
/*private*/
85
std::unique_ptr<Noder>
86
EdgeNodingBuilder::createFloatingPrecisionNoder(bool doValidation)
87
0
{
88
0
    std::unique_ptr<Noder> ret;
89
90
0
    if (inputHasCurves) {
91
0
        auto simpleNoder = std::make_unique<SimpleNoder>();
92
0
        simpleNoder->setArcIntersector(arcIntAdder);
93
0
        ret = std::move(simpleNoder);
94
0
    } else {
95
0
        auto mcNoder = std::make_unique<MCIndexNoder>();
96
0
        mcNoder->setSegmentIntersector(&intAdder);
97
0
        ret = std::move(mcNoder);
98
0
    }
99
100
    // TODO: Support arcs in ValidatingNoder
101
0
    if (doValidation && !inputHasCurves) {
102
0
        spareInternalNoder = std::move(ret);
103
0
        std::unique_ptr<Noder> validNoder(new ValidatingNoder(*spareInternalNoder));
104
0
        return validNoder;
105
0
    }
106
107
0
    return ret;
108
0
}
109
110
/*public*/
111
void
112
EdgeNodingBuilder::setClipEnvelope(const Envelope* p_clipEnv)
113
0
{
114
0
    clipEnv = p_clipEnv;
115
0
    clipper.reset(new RingClipper(p_clipEnv));
116
0
    limiter.reset(new LineLimiter(p_clipEnv));
117
0
}
118
119
/*public*/
120
std::vector<Edge*>
121
EdgeNodingBuilder::build(const Geometry* geom0, const Geometry* geom1)
122
0
{
123
0
    inputHasZ = geom0->hasZ() || (geom1 != nullptr && geom1->hasZ());
124
0
    inputHasM = geom0->hasM() || (geom1 != nullptr && geom1->hasM());
125
0
    inputHasCurves = geom0->hasCurvedComponents() || (geom1 != nullptr && geom1->hasCurvedComponents());
126
127
0
    add(geom0, 0);
128
0
    add(geom1, 1);
129
0
    std::vector<Edge*> nodedEdges = node(inputEdges);
130
131
    /**
132
     * Merge the noded edges to eliminate duplicates.
133
     * Labels are combined.
134
     */
135
0
    return EdgeMerger::merge(nodedEdges);
136
0
}
137
138
/*private*/
139
std::vector<Edge*>
140
EdgeNodingBuilder::node(const std::vector<PathString*>& segStrings)
141
0
{
142
0
    std::vector<Edge*> nodedEdges;
143
144
0
    Noder* noder = getNoder();
145
0
    noder->computePathNodes(segStrings);
146
147
0
    auto nodedSS = noder->getNodedPaths();
148
149
0
    nodedEdges = createEdges(nodedSS);
150
151
0
    return nodedEdges;
152
0
}
153
154
/*private*/
155
std::vector<Edge*>
156
EdgeNodingBuilder::createEdges(std::vector<std::unique_ptr<PathString>>& segStrings)
157
0
{
158
0
    std::vector<Edge*> createdEdges;
159
160
0
    for (auto& ss : segStrings) {
161
0
        const auto& pts = ss->getCoordinates();
162
163
0
        const bool isCurved = dynamic_cast<const noding::ArcString*>(ss.get());
164
165
        // don't create edges from collapsed lines
166
0
        if (Edge::isCollapsed(pts.get())) continue;
167
168
        // This EdgeSourceInfo is already managed locally in a std::deque
169
0
        const EdgeSourceInfo* info = static_cast<const EdgeSourceInfo*>(ss->getData());
170
        // Record that a non-collapsed edge exists for the parent geometry
171
0
        hasEdges[info->getIndex()] = true;
172
        // Allocate the new Edge locally in a std::deque
173
0
        edgeQue.emplace_back(ss->getCoordinates(), info, isCurved);
174
0
        Edge* newEdge = &(edgeQue.back());
175
0
        createdEdges.push_back(newEdge);
176
0
    }
177
0
    return createdEdges;
178
0
}
179
180
181
/*public*/
182
bool
183
EdgeNodingBuilder::hasEdgesFor(uint8_t geomIndex) const
184
0
{
185
0
    assert(geomIndex < 2);
186
0
    return hasEdges[geomIndex];
187
0
}
188
189
/*private*/
190
void
191
EdgeNodingBuilder::add(const Geometry* g, uint8_t geomIndex)
192
0
{
193
0
    if (g == nullptr || g->isEmpty())
194
0
        return;
195
196
0
    if (isClippedCompletely(g->getEnvelopeInternal()))
197
0
        return;
198
199
0
    switch (g->getGeometryTypeId())
200
0
    {
201
0
        case GEOS_POLYGON:
202
0
        case GEOS_CURVEPOLYGON:
203
0
            return addPolygon(static_cast<const Surface*>(g), geomIndex);
204
0
        case GEOS_LINESTRING:
205
0
        case GEOS_LINEARRING:
206
0
        case GEOS_CIRCULARSTRING:
207
0
            return addSimpleCurve(static_cast<const SimpleCurve*>(g), geomIndex);
208
0
        case GEOS_COMPOUNDCURVE:
209
0
            return addCompoundCurve(static_cast<const CompoundCurve*>(g), geomIndex);
210
0
        case GEOS_MULTILINESTRING:
211
0
        case GEOS_MULTIPOLYGON:
212
0
        case GEOS_MULTICURVE:
213
0
        case GEOS_MULTISURFACE:
214
0
            return addCollection(static_cast<const GeometryCollection*>(g), geomIndex);
215
0
        case GEOS_GEOMETRYCOLLECTION:
216
0
            return addGeometryCollection(static_cast<const GeometryCollection*>(g), geomIndex, g->getDimension());
217
0
        case GEOS_POINT:
218
0
        case GEOS_MULTIPOINT:
219
0
            return; // do nothing
220
0
        default:
221
0
            throw util::IllegalArgumentException("Unexpected geometry type: " + g->getGeometryType());
222
0
            return; // do nothing
223
0
    }
224
0
}
225
226
/*private*/
227
void
228
EdgeNodingBuilder::addCollection(const GeometryCollection* gc, uint8_t geomIndex)
229
0
{
230
0
    for (std::size_t i = 0; i < gc->getNumGeometries(); i++) {
231
0
        const Geometry* g = gc->getGeometryN(i);
232
0
        add(g, geomIndex);
233
0
    }
234
0
}
235
236
/*private*/
237
void
238
EdgeNodingBuilder::addGeometryCollection(const GeometryCollection* gc, uint8_t geomIndex, int expectedDim)
239
0
{
240
0
    for (std::size_t i = 0; i < gc->getNumGeometries(); i++) {
241
0
        const Geometry* g = gc->getGeometryN(i);
242
0
        if (g->getDimension() != expectedDim) {
243
0
            throw geos::util::IllegalArgumentException("Overlay input is mixed-dimension");
244
0
        }
245
0
        add(g, geomIndex);
246
0
    }
247
0
}
248
249
/*private*/
250
void
251
EdgeNodingBuilder::addPolygon(const Surface* poly, uint8_t geomIndex)
252
0
{
253
0
    const Curve* shell = poly->getExteriorRing();
254
0
    addPolygonRing(shell, false, geomIndex);
255
256
0
    for (std::size_t i = 0; i < poly->getNumInteriorRing(); i++) {
257
0
        const Curve* hole = poly->getInteriorRingN(i);
258
259
        // Holes are topologically labelled opposite to the shell, since
260
        // the interior of the polygon lies on their opposite side
261
        // (on the left, if the hole is oriented CW)
262
0
        addPolygonRing(hole, true, geomIndex);
263
0
    }
264
0
}
265
266
/*private*/
267
void
268
EdgeNodingBuilder::addPolygonRing(const Curve* ring, bool isHole, uint8_t geomIndex)
269
0
{
270
    // don't add empty rings
271
0
    if (ring->isEmpty()) return;
272
273
0
    if (isClippedCompletely(ring->getEnvelopeInternal()))
274
0
      return;
275
276
0
    const auto type = ring->getGeometryTypeId();
277
278
0
    if (type == GEOS_COMPOUNDCURVE) {
279
0
        const CompoundCurve* cc = static_cast<const CompoundCurve*>(ring);
280
0
        const auto coords = ring->getCoordinates();
281
0
        const int depthDelta = computeDepthDelta(coords.get(), isHole);
282
283
0
        const EdgeSourceInfo* eso = createEdgeSourceInfo(geomIndex, depthDelta, isHole);
284
285
0
        for (std::size_t i = 0; i < cc->getNumCurves(); i++) {
286
0
            const SimpleCurve* section = cc->getCurveN(i);
287
0
            if (section->getNumPoints() >= 2) {
288
0
                if (section->getGeometryTypeId() == GEOS_CIRCULARSTRING) {
289
0
                    const CircularString* cs = static_cast<const CircularString*>(section);
290
0
                    addCurvedEdge(cs->getSharedCoordinates(), cs->getArcs(), eso);
291
0
                } else {
292
0
                    addEdge(section->getSharedCoordinates(), eso);
293
0
                }
294
0
            }
295
0
        }
296
297
0
        return;
298
0
    }
299
300
0
    const SimpleCurve* scRing = static_cast<const SimpleCurve*>(ring);
301
0
    const int depthDelta = computeDepthDelta(scRing->getCoordinatesRO(), isHole);
302
0
    const EdgeSourceInfo* esInfo = createEdgeSourceInfo(geomIndex, depthDelta, isHole);
303
304
0
    if (ring->getGeometryTypeId() == GEOS_LINEARRING || ring->getGeometryTypeId() == GEOS_LINESTRING) {
305
        // TODO: Support CircularString in RingClipper
306
0
        std::shared_ptr<const CoordinateSequence> pts = clip(static_cast<const LineString*>(ring));
307
308
        /**
309
        * Don't add edges that collapse to a point
310
        */
311
0
        if (pts->size() < 2) {
312
0
            return;
313
0
        }
314
315
0
        addEdge(pts, esInfo);
316
0
    } else {
317
0
        assert(ring->getGeometryTypeId() == GEOS_CIRCULARSTRING);
318
319
0
        const CircularString* cs = static_cast<const CircularString*>(ring);
320
0
        addCurvedEdge(cs->getSharedCoordinates(), cs->getArcs(), esInfo);
321
0
    }
322
0
}
323
324
/*private*/
325
const EdgeSourceInfo*
326
EdgeNodingBuilder::createEdgeSourceInfo(uint8_t index)
327
0
{
328
    // Concentrate small memory allocations via std::deque and
329
    // retain ownership of the EdgeSourceInfo* in the EdgeNodingBuilder
330
0
    edgeSourceInfoQue.emplace_back(index);
331
0
    return &(edgeSourceInfoQue.back());
332
0
}
333
334
/*private*/
335
const EdgeSourceInfo*
336
EdgeNodingBuilder::createEdgeSourceInfo(uint8_t index, int depthDelta, bool isHole)
337
0
{
338
    // Concentrate small memory allocations via std::deque and
339
    // retain ownership of the EdgeSourceInfo* in the EdgeNodingBuilder
340
0
    edgeSourceInfoQue.emplace_back(index, depthDelta, isHole);
341
0
    return &(edgeSourceInfoQue.back());
342
0
}
343
344
/*private*/
345
void
346
EdgeNodingBuilder::addEdge(const std::shared_ptr<const CoordinateSequence>& cas, const EdgeSourceInfo* info)
347
0
{
348
    // TODO: manage these internally to EdgeNodingBuilder in a std::deque,
349
    // since they do not have a life span longer than the EdgeNodingBuilder
350
    // in OverlayNG::buildGraph()
351
0
    NodedSegmentString* ss = new NodedSegmentString(cas, inputHasZ, inputHasM, reinterpret_cast<const void*>(info));
352
0
    inputEdges.push_back(ss);
353
0
}
354
355
void
356
EdgeNodingBuilder::addCurvedEdge(const std::shared_ptr<const CoordinateSequence>& cas, const std::vector<CircularArc>& arcs, const EdgeSourceInfo* info)
357
0
{
358
0
    NodableArcString* as = new NodableArcString(arcs, cas, inputHasZ, inputHasM, reinterpret_cast<const void*>(info));
359
0
    inputEdges.push_back(as);
360
0
}
361
362
/*private*/
363
bool
364
EdgeNodingBuilder::isClippedCompletely(const Envelope* env) const
365
0
{
366
0
    if (clipEnv == nullptr) return false;
367
0
    return clipEnv->disjoint(env);
368
0
}
369
370
/* private */
371
std::shared_ptr<const CoordinateSequence>
372
EdgeNodingBuilder::clip(const LineString* ring) const
373
0
{
374
0
    const Envelope* env = ring->getEnvelopeInternal();
375
376
    /**
377
     * If no clipper or ring is completely contained then no need to clip.
378
     * We also cannot clip if any input contains curves, because curved
379
     * rings are not currently clipped and clipping only some rings could
380
     * disrupt the polygon topology.
381
     * But repeated points must be removed to ensure correct noding.
382
     */
383
0
    if (clipper == nullptr || clipEnv->covers(env) || inputHasCurves) {
384
0
        return removeRepeatedPoints(ring);
385
0
    }
386
387
0
    return clipper->clip(ring->getCoordinatesRO());
388
0
}
389
390
/*private*/
391
std::shared_ptr<const CoordinateSequence>
392
EdgeNodingBuilder::removeRepeatedPoints(const LineString* line)
393
0
{
394
0
    const std::shared_ptr<const CoordinateSequence>& pts = line->getSharedCoordinates();
395
0
    if (pts->hasRepeatedPoints()) {
396
0
        return RepeatedPointRemover::removeRepeatedPoints(pts.get());
397
0
    } else {
398
0
        return pts;
399
0
    }
400
0
}
401
402
/*private*/
403
int
404
EdgeNodingBuilder::computeDepthDelta(const CoordinateSequence* ringCoords, bool isHole)
405
0
{
406
    /**
407
     * Compute the orientation of the ring, to
408
     * allow assigning side interior/exterior labels correctly.
409
     * JTS canonical orientation is that shells are CW, holes are CCW.
410
     *
411
     * It is important to compute orientation on the original ring,
412
     * since topology collapse can make the orientation computation give the wrong answer.
413
     */
414
415
0
    const bool isCCW = algorithm::Orientation::isCCW(ringCoords);
416
417
    /**
418
     * Compute whether ring is in canonical orientation or not.
419
     * Canonical orientation for the overlay process is
420
     * Shells : CW, Holes: CCW
421
     */
422
0
    bool isOriented = true;
423
0
    if (!isHole) {
424
0
        isOriented = !isCCW;
425
0
    }
426
0
    else {
427
0
        isOriented = isCCW;
428
0
    }
429
    /**
430
     * Depth delta can now be computed.
431
     * Canonical depth delta is 1 (Exterior on L, Interior on R).
432
     * It is flipped to -1 if the ring is oppositely oriented.
433
     */
434
0
    int depthDelta = isOriented ? 1 : -1;
435
0
    return depthDelta;
436
0
}
437
438
/*private*/
439
void
440
EdgeNodingBuilder::addSimpleCurve(const SimpleCurve* curve, uint8_t geomIndex)
441
0
{
442
    // don't add empty lines
443
0
    if (curve->isEmpty()) return;
444
445
0
    if (isClippedCompletely(curve->getEnvelopeInternal()))
446
0
        return;
447
448
0
    if (curve->getGeometryTypeId() == GEOS_CIRCULARSTRING) {
449
        // TODO: Support CircularString in LineLimiter
450
0
        const CircularString* cs = static_cast<const CircularString*>(curve);
451
0
        addCurve(cs->getSharedCoordinates(), cs->getArcs(), geomIndex);
452
0
        return;
453
0
    }
454
455
0
    const LineString* line = detail::down_cast<const LineString*>(curve);
456
457
0
    if (isToBeLimited(line)) {
458
0
        std::vector<std::unique_ptr<CoordinateSequence>>& sections = limit(line);
459
0
        for (auto& pts : sections) {
460
0
            addLine(std::move(pts), geomIndex);
461
0
        }
462
0
    }
463
0
    else {
464
0
        const auto ptsNoRepeat = removeRepeatedPoints(line);
465
0
        addLine(ptsNoRepeat, geomIndex);
466
0
    }
467
0
}
468
469
void
470
EdgeNodingBuilder::addCompoundCurve(const CompoundCurve* curve, uint8_t geomIndex)
471
0
{
472
0
    for (std::size_t i = 0; i < curve->getNumCurves(); i++) {
473
0
        addSimpleCurve(curve->getCurveN(i), geomIndex);
474
0
    }
475
0
}
476
477
/*private*/
478
void
479
EdgeNodingBuilder::addLine(const std::shared_ptr<const CoordinateSequence>& pts, uint8_t geomIndex)
480
0
{
481
    /**
482
     * Don't add edges that collapse to a point
483
     */
484
0
    if (pts->size() < 2) {
485
0
        return;
486
0
    }
487
488
0
    addEdge(pts, createEdgeSourceInfo(geomIndex));
489
0
}
490
491
/*private*/
492
void
493
EdgeNodingBuilder::addCurve(const std::shared_ptr<const CoordinateSequence>& pts, const std::vector<CircularArc>& arcs, uint8_t geomIndex)
494
0
{
495
    /**
496
     * Don't add edges that collapse to a point
497
     */
498
0
    if (pts->size() < 2) {
499
0
        return;
500
0
    }
501
502
0
    addCurvedEdge(pts, arcs, createEdgeSourceInfo(geomIndex));
503
0
}
504
505
/*private*/
506
bool
507
EdgeNodingBuilder::isToBeLimited(const LineString* line) const
508
0
{
509
0
    const CoordinateSequence* pts = line->getCoordinatesRO();
510
0
    if (limiter == nullptr || pts->size() <= MIN_LIMIT_PTS) {
511
0
        return false;
512
0
    }
513
0
    const Envelope* env = line->getEnvelopeInternal();
514
    /**
515
     * If line is completely contained then no need to limit
516
     */
517
0
    if (clipEnv->covers(env)) {
518
0
        return false;
519
0
    }
520
0
    return true;
521
0
}
522
523
/*private*/
524
std::vector<std::unique_ptr<CoordinateSequence>>&
525
EdgeNodingBuilder::limit(const LineString* line) const
526
0
{
527
0
    const CoordinateSequence* pts = line->getCoordinatesRO();
528
0
    return limiter->limit(pts);
529
0
}
530
531
532
533
} // namespace geos.operation.overlayng
534
} // namespace geos.operation
535
} // namespace geos