Coverage Report

Created: 2025-10-10 06:41

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/geos/src/geomgraph/GeometryGraph.cpp
Line
Count
Source
1
/**********************************************************************
2
 *
3
 * GEOS - Geometry Engine Open Source
4
 * http://geos.osgeo.org
5
 *
6
 * Copyright (C) 2011 Sandro Santilli <strk@kbt.io>
7
 * Copyright (C) 2005-2006 Refractions Research Inc.
8
 * Copyright (C) 2001-2002 Vivid Solutions Inc.
9
 *
10
 * This is free software; you can redistribute and/or modify it under
11
 * the terms of the GNU Lesser General Public Licence as published
12
 * by the Free Software Foundation.
13
 * See the COPYING file for more information.
14
 *
15
 **********************************************************************
16
 *
17
 * Last port: geomgraph/GeometryGraph.java r428 (JTS-1.12+)
18
 *
19
 **********************************************************************/
20
21
#include <geos/algorithm/Orientation.h>
22
#include <geos/algorithm/BoundaryNodeRule.h>
23
24
#include <geos/util/UnsupportedOperationException.h>
25
#include <geos/util.h>
26
27
#include <geos/geomgraph/GeometryGraph.h>
28
#include <geos/geomgraph/Node.h>
29
#include <geos/geomgraph/Edge.h>
30
#include <geos/geomgraph/Label.h>
31
#include <geos/geom/Position.h>
32
33
#include <geos/geomgraph/index/SimpleMCSweepLineIntersector.h>
34
#include <geos/geomgraph/index/SegmentIntersector.h>
35
#include <geos/geomgraph/index/EdgeSetIntersector.h>
36
37
#include <geos/geom/CoordinateSequence.h>
38
#include <geos/geom/Location.h>
39
#include <geos/geom/Point.h>
40
#include <geos/geom/Envelope.h>
41
#include <geos/geom/LinearRing.h>
42
#include <geos/geom/LineString.h>
43
#include <geos/geom/Polygon.h>
44
#include <geos/geom/MultiPoint.h>
45
#include <geos/geom/MultiLineString.h>
46
#include <geos/geom/MultiPolygon.h>
47
#include <geos/geom/GeometryCollection.h>
48
#include <geos/util/Interrupt.h>
49
50
#include <geos/operation/valid/RepeatedPointRemover.h>
51
52
#include <vector>
53
#include <memory> // std::unique_ptr
54
#include <cassert>
55
#include <typeinfo>
56
57
#ifndef GEOS_DEBUG
58
#define GEOS_DEBUG 0
59
#endif
60
61
62
using namespace geos::geomgraph::index;
63
using namespace geos::algorithm;
64
using namespace geos::geom;
65
66
namespace geos {
67
namespace geomgraph { // geos.geomgraph
68
69
/*
70
 * This method implements the Boundary Determination Rule
71
 * for determining whether
72
 * a component (node or edge) that appears multiple times in elements
73
 * of a MultiGeometry is in the boundary or the interior of the Geometry
74
 *
75
 * The SFS uses the "Mod-2 Rule", which this function implements
76
 *
77
 * An alternative (and possibly more intuitive) rule would be
78
 * the "At Most One Rule":
79
 *    isInBoundary = (componentCount == 1)
80
 */
81
bool
82
GeometryGraph::isInBoundary(int boundaryCount)
83
35.7k
{
84
    // the "Mod-2 Rule"
85
35.7k
    return boundaryCount % 2 == 1;
86
35.7k
}
87
88
Location
89
GeometryGraph::determineBoundary(int boundaryCount)
90
0
{
91
0
    return isInBoundary(boundaryCount) ? Location::BOUNDARY : Location::INTERIOR;
92
0
}
93
94
95
EdgeSetIntersector*
96
GeometryGraph::createEdgeSetIntersector()
97
0
{
98
    // various options for computing intersections, from slowest to fastest
99
100
    //private EdgeSetIntersector esi = new SimpleEdgeSetIntersector();
101
    //private EdgeSetIntersector esi = new MonotoneChainIntersector();
102
    //private EdgeSetIntersector esi = new NonReversingChainIntersector();
103
    //private EdgeSetIntersector esi = new SimpleSweepLineIntersector();
104
    //private EdgeSetIntersector esi = new MCSweepLineIntersector();
105
106
    //return new SimpleEdgeSetIntersector();
107
0
    return new SimpleMCSweepLineIntersector();
108
0
}
109
110
/*public*/
111
std::vector<Node*>*
112
GeometryGraph::getBoundaryNodes()
113
0
{
114
0
    if(! boundaryNodes.get()) {
115
0
        boundaryNodes.reset(new std::vector<Node*>());
116
0
        getBoundaryNodes(*boundaryNodes);
117
0
    }
118
0
    return boundaryNodes.get();
119
0
}
120
121
/*public*/
122
CoordinateSequence*
123
GeometryGraph::getBoundaryPoints()
124
0
{
125
126
0
    if(! boundaryPoints.get()) {
127
        // Collection will be destroyed by GeometryGraph dtor
128
0
        std::vector<Node*>* coll = getBoundaryNodes();
129
0
        boundaryPoints.reset(new CoordinateSequence(coll->size()));
130
0
        std::size_t i = 0;
131
0
        for(std::vector<Node*>::iterator it = coll->begin(), endIt = coll->end();
132
0
                it != endIt; ++it) {
133
0
            Node* node = *it;
134
0
            boundaryPoints->setAt(node->getCoordinate(), i++);
135
0
        }
136
0
    }
137
138
    // We keep ownership of this, will be destroyed by destructor
139
0
    return boundaryPoints.get();
140
0
}
141
142
Edge*
143
GeometryGraph::findEdge(const LineString* line) const
144
0
{
145
0
    return lineEdgeMap.find(line)->second;
146
0
}
147
148
void
149
GeometryGraph::computeSplitEdges(std::vector<Edge*>* edgelist)
150
0
{
151
#if GEOS_DEBUG
152
    std::cerr << "[" << this << "] GeometryGraph::computeSplitEdges() scanning " << edges->size() << " local and " <<
153
         edgelist->size() << " provided edges" << std::endl;
154
#endif
155
0
    for(std::vector<Edge*>::iterator i = edges->begin(), endIt = edges->end();
156
0
            i != endIt; ++i) {
157
0
        Edge* e = *i;
158
#if GEOS_DEBUG
159
        std::cerr << "   " << e->print() << " adding split edges from arg" << std::endl;
160
#endif
161
0
        e->eiList.addSplitEdges(edgelist);
162
0
    }
163
#if GEOS_DEBUG
164
    std::cerr << "[" << this << "] GeometryGraph::computeSplitEdges() completed " << std::endl;
165
#endif
166
0
}
167
168
void
169
GeometryGraph::add(const Geometry* g)
170
//throw (UnsupportedOperationException *)
171
0
{
172
0
    util::ensureNoCurvedComponents(g);
173
174
0
    if(g->isEmpty()) {
175
0
        return;
176
0
    }
177
178
    // check if this Geometry should obey the Boundary Determination Rule
179
    // all collections except MultiPolygons obey the rule
180
0
    if(dynamic_cast<const MultiPolygon*>(g)) {
181
0
        useBoundaryDeterminationRule = false;
182
0
    }
183
184
185
0
    if(const Polygon* x1 = dynamic_cast<const Polygon*>(g)) {
186
0
        addPolygon(x1);
187
0
    }
188
189
    // LineString also handles LinearRings
190
0
    else if(const LineString* x2 = dynamic_cast<const LineString*>(g)) {
191
0
        addLineString(x2);
192
0
    }
193
194
0
    else if(const Point* x3 = dynamic_cast<const Point*>(g)) {
195
0
        addPoint(x3);
196
0
    }
197
198
0
    else if(const GeometryCollection* x4 =
199
0
                dynamic_cast<const GeometryCollection*>(g)) {
200
0
        addCollection(x4);
201
0
    }
202
203
0
    else {
204
0
        std::string out = typeid(*g).name();
205
0
        throw util::UnsupportedOperationException("GeometryGraph::add(Geometry *): unknown geometry type: " + out);
206
0
    }
207
0
}
208
209
void
210
GeometryGraph::addCollection(const GeometryCollection* gc)
211
0
{
212
0
    for(std::size_t i = 0, n = gc->getNumGeometries(); i < n; ++i) {
213
0
        add(gc->getGeometryN(i));
214
0
    }
215
0
}
216
217
/*
218
 * Add a Point to the graph.
219
 */
220
void
221
GeometryGraph::addPoint(const Point* p)
222
0
{
223
0
    const Coordinate& coord = p->getCoordinatesRO()->getAt(0);
224
0
    insertPoint(argIndex, coord, Location::INTERIOR);
225
0
}
226
227
/*
228
 * The left and right topological location arguments assume that the ring
229
 * is oriented CW.
230
 * If the ring is in the opposite orientation,
231
 * the left and right locations must be interchanged.
232
 */
233
void
234
GeometryGraph::addPolygonRing(const LinearRing* lr, Location cwLeft, Location cwRight)
235
// throw IllegalArgumentException (see below)
236
0
{
237
    // skip empty component (see bug #234)
238
0
    if(lr->isEmpty()) {
239
0
        return;
240
0
    }
241
242
0
    const CoordinateSequence* lrcl = lr->getCoordinatesRO();
243
244
0
    auto coord = geos::operation::valid::RepeatedPointRemover::removeRepeatedPoints(lrcl);
245
0
    if(coord->getSize() < 4) {
246
0
        hasTooFewPointsVar = true;
247
0
        invalidPoint = coord->getAt(0); // its now a Coordinate
248
0
        return;
249
0
    }
250
0
    Location left = cwLeft;
251
0
    Location right = cwRight;
252
253
    /*
254
     * the isCCW call might throw an
255
     * IllegalArgumentException if degenerate ring does
256
     * not contain 3 distinct points.
257
     */
258
0
    if(Orientation::isCCW(coord.get())) {
259
0
        left = cwRight;
260
0
        right = cwLeft;
261
0
    }
262
263
0
    auto coordRaw = coord.release();
264
0
    Edge* e = new Edge(coordRaw, Label(argIndex, Location::BOUNDARY, left, right));
265
0
    lineEdgeMap[lr] = e;
266
0
    insertEdge(e);
267
0
    insertPoint(argIndex, coordRaw->getAt(0), Location::BOUNDARY);
268
0
}
269
270
void
271
GeometryGraph::addPolygon(const Polygon* p)
272
0
{
273
0
    const LinearRing* lr = p->getExteriorRing();
274
275
0
    addPolygonRing(lr, Location::EXTERIOR, Location::INTERIOR);
276
0
    for(std::size_t i = 0, n = p->getNumInteriorRing(); i < n; ++i) {
277
        // Holes are topologically labelled opposite to the shell, since
278
        // the interior of the polygon lies on their opposite side
279
        // (on the left, if the hole is oriented CW)
280
0
        lr = p->getInteriorRingN(i);
281
0
        addPolygonRing(lr, Location::INTERIOR, Location::EXTERIOR);
282
0
    }
283
0
}
284
285
void
286
GeometryGraph::addLineString(const LineString* line)
287
0
{
288
0
    auto coord = operation::valid::RepeatedPointRemover::removeRepeatedPoints(line->getCoordinatesRO());
289
0
    if(coord->getSize() < 2) {
290
0
        hasTooFewPointsVar = true;
291
0
        invalidPoint = coord->getAt(0);
292
0
        return;
293
0
    }
294
295
0
    auto coordRaw = coord.release();
296
0
    Edge* e = new Edge(coordRaw, Label(argIndex, Location::INTERIOR));
297
0
    lineEdgeMap[line] = e;
298
0
    insertEdge(e);
299
300
    /*
301
     * Add the boundary points of the LineString, if any.
302
     * Even if the LineString is closed, add both points as if they
303
     * were endpoints.
304
     * This allows for the case that the node already exists and is
305
     * a boundary point.
306
     */
307
0
    assert(coordRaw->size() >= 2); // found LineString with single point
308
0
    insertBoundaryPoint(argIndex, coordRaw->getAt(0));
309
0
    insertBoundaryPoint(argIndex, coordRaw->getAt(coordRaw->getSize() - 1));
310
0
}
311
312
/*
313
 * Add an Edge computed externally.  The label on the Edge is assumed
314
 * to be correct.
315
 */
316
void
317
GeometryGraph::addEdge(Edge* e)
318
0
{
319
0
    insertEdge(e);
320
0
    const CoordinateSequence* coord = e->getCoordinates();
321
    // insert the endpoint as a node, to mark that it is on the boundary
322
0
    insertPoint(argIndex, coord->getAt(0), Location::BOUNDARY);
323
0
    insertPoint(argIndex, coord->getAt(coord->getSize() - 1), Location::BOUNDARY);
324
0
}
325
326
/*
327
 * Add a point computed externally.  The point is assumed to be a
328
 * Point Geometry part, which has a location of INTERIOR.
329
 */
330
void
331
GeometryGraph::addPoint(Coordinate& pt)
332
0
{
333
0
    insertPoint(argIndex, pt, Location::INTERIOR);
334
0
}
335
336
template <class T, class C>
337
void
338
collect_intersecting_edges(const Envelope* env, T start, T end, C& to)
339
0
{
340
0
    for(T i = start; i != end; ++i) {
341
0
        Edge* e = *i;
342
0
        if(e->getEnvelope()->intersects(env)) {
343
0
            to.push_back(e);
344
0
        }
345
0
    }
346
0
}
347
348
/*public*/
349
std::unique_ptr<SegmentIntersector>
350
GeometryGraph::computeSelfNodes(LineIntersector& li,
351
                                bool computeRingSelfNodes, const Envelope* env)
352
0
{
353
0
    auto si = detail::make_unique<SegmentIntersector>(&li, true, false);
354
0
    std::unique_ptr<EdgeSetIntersector> esi(createEdgeSetIntersector());
355
356
0
    typedef std::vector<Edge*> EC;
357
0
    EC* se = edges;
358
0
    EC self_edges_copy;
359
360
0
    if(env && ! env->covers(parentGeom->getEnvelopeInternal())) {
361
0
        collect_intersecting_edges(env, se->begin(), se->end(), self_edges_copy);
362
        //cerr << "(computeSelfNodes) Self edges reduced from " << se->size() << " to " << self_edges_copy.size() << std::endl;
363
0
        se = &self_edges_copy;
364
0
    }
365
366
0
    bool isRings = dynamic_cast<const LinearRing*>(parentGeom)
367
0
                   || dynamic_cast<const Polygon*>(parentGeom)
368
0
                   || dynamic_cast<const MultiPolygon*>(parentGeom);
369
370
0
    bool computeAllSegments = computeRingSelfNodes || ! isRings;
371
372
0
    esi->computeIntersections(se, si.get(), computeAllSegments);
373
374
#if GEOS_DEBUG
375
    std::cerr << "SegmentIntersector # tests = " << si->numTests << std::endl;
376
#endif // GEOS_DEBUG
377
378
0
    addSelfIntersectionNodes(argIndex);
379
0
    return si;
380
0
}
381
382
std::unique_ptr<SegmentIntersector>
383
GeometryGraph::computeEdgeIntersections(GeometryGraph* g,
384
                                        LineIntersector* li, bool includeProper, const Envelope* env)
385
0
{
386
#if GEOS_DEBUG
387
    std::cerr << "GeometryGraph::computeEdgeIntersections call" << std::endl;
388
#endif
389
0
    std::unique_ptr<SegmentIntersector> si(new SegmentIntersector(li, includeProper, true));
390
391
0
    si->setBoundaryNodes(getBoundaryNodes(), g->getBoundaryNodes());
392
0
    std::unique_ptr<EdgeSetIntersector> esi(createEdgeSetIntersector());
393
394
0
    typedef std::vector<Edge*> EC;
395
396
0
    EC self_edges_copy;
397
0
    EC other_edges_copy;
398
399
0
    EC* se = edges;
400
0
    EC* oe = g->edges;
401
0
    if(env && ! env->covers(parentGeom->getEnvelopeInternal())) {
402
0
        collect_intersecting_edges(env, se->begin(), se->end(), self_edges_copy);
403
        //cerr << "Self edges reduced from " << se->size() << " to " << self_edges_copy.size() << std::endl;
404
0
        se = &self_edges_copy;
405
0
    }
406
0
    if(env && ! env->covers(g->parentGeom->getEnvelopeInternal())) {
407
0
        collect_intersecting_edges(env, oe->begin(), oe->end(), other_edges_copy);
408
        //cerr << "Other edges reduced from " << oe->size() << " to " << other_edges_copy.size() << std::endl;
409
0
        oe = &other_edges_copy;
410
0
    }
411
0
    esi->computeIntersections(se, oe, si.get());
412
#if GEOS_DEBUG
413
    std::cerr << "GeometryGraph::computeEdgeIntersections returns" << std::endl;
414
#endif
415
0
    return si;
416
0
}
417
418
void
419
GeometryGraph::insertPoint(uint8_t p_argIndex, const Coordinate &coord,
420
                           geom::Location onLocation)
421
0
{
422
#if GEOS_DEBUG > 1
423
    std::cerr << "GeometryGraph::insertPoint(" << coord.toString() << " called" << std::endl;
424
#endif
425
0
    Node* n = nodes->addNode(coord);
426
0
    Label& lbl = n->getLabel();
427
0
    if(lbl.isNull()) {
428
0
        n->setLabel(p_argIndex, onLocation);
429
0
    }
430
0
    else {
431
0
        lbl.setLocation(p_argIndex, onLocation);
432
0
    }
433
0
}
434
435
/*
436
 * Adds points using the mod-2 rule of SFS.  This is used to add the boundary
437
 * points of dim-1 geometries (Curves/MultiCurves).  According to the SFS,
438
 * an endpoint of a Curve is on the boundary
439
 * iff if it is in the boundaries of an odd number of Geometries
440
 */
441
void
442
GeometryGraph::insertBoundaryPoint(uint8_t p_argIndex, const Coordinate &coord)
443
0
{
444
0
    Node* n = nodes->addNode(coord);
445
    // nodes always have labels
446
0
    Label& lbl = n->getLabel();
447
448
    // the new point to insert is on a boundary
449
0
    int boundaryCount = 1;
450
451
    // determine the current location for the point (if any)
452
0
    Location loc = lbl.getLocation(p_argIndex, Position::ON);
453
0
    if(loc == Location::BOUNDARY) {
454
0
        boundaryCount++;
455
0
    }
456
457
    // determine the boundary status of the point according to the
458
    // Boundary Determination Rule
459
0
    Location newLoc = determineBoundary(boundaryNodeRule, boundaryCount);
460
0
    lbl.setLocation(p_argIndex, newLoc);
461
0
}
462
463
/*private*/
464
void
465
GeometryGraph::addSelfIntersectionNodes(uint8_t p_argIndex)
466
0
{
467
0
    for(Edge* e : *edges) {
468
0
        Location eLoc = e->getLabel().getLocation(p_argIndex);
469
0
        const EdgeIntersectionList& eiL = e->eiList;
470
0
        for(const EdgeIntersection& ei : eiL) {
471
0
            addSelfIntersectionNode(p_argIndex, ei.coord, eLoc);
472
0
            GEOS_CHECK_FOR_INTERRUPTS();
473
0
        }
474
0
    }
475
0
}
476
477
/*private*/
478
void
479
GeometryGraph::addSelfIntersectionNode(uint8_t p_argIndex,
480
                                       const Coordinate &coord, Location loc)
481
0
{
482
    // if this node is already a boundary node, don't change it
483
0
    if(isBoundaryNode(p_argIndex, coord)) {
484
0
        return;
485
0
    }
486
0
    if(loc == Location::BOUNDARY && useBoundaryDeterminationRule) {
487
0
        insertBoundaryPoint(p_argIndex, coord);
488
0
    }
489
0
    else {
490
0
        insertPoint(p_argIndex, coord, loc);
491
0
    }
492
0
}
493
494
std::vector<Edge*>*
495
GeometryGraph::getEdges()
496
0
{
497
0
    return edges;
498
0
}
499
500
bool
501
GeometryGraph::hasTooFewPoints()
502
0
{
503
0
    return hasTooFewPointsVar;
504
0
}
505
506
const Coordinate&
507
GeometryGraph::getInvalidPoint()
508
0
{
509
0
    return invalidPoint;
510
0
}
511
512
GeometryGraph::GeometryGraph(uint8_t newArgIndex,
513
                             const geom::Geometry* newParentGeom)
514
    :
515
0
    PlanarGraph(),
516
0
    parentGeom(newParentGeom),
517
0
    useBoundaryDeterminationRule(true),
518
0
    boundaryNodeRule(algorithm::BoundaryNodeRule::getBoundaryOGCSFS()),
519
0
    argIndex(newArgIndex),
520
0
    hasTooFewPointsVar(false)
521
0
{
522
0
    if(parentGeom != nullptr) {
523
0
        add(parentGeom);
524
0
    }
525
0
}
526
527
GeometryGraph::GeometryGraph(uint8_t newArgIndex,
528
                             const geom::Geometry* newParentGeom,
529
                             const algorithm::BoundaryNodeRule& bnr)
530
    :
531
0
    PlanarGraph(),
532
0
    parentGeom(newParentGeom),
533
0
    useBoundaryDeterminationRule(true),
534
0
    boundaryNodeRule(bnr),
535
0
    argIndex(newArgIndex),
536
0
    hasTooFewPointsVar(false)
537
0
{
538
0
    if(parentGeom != nullptr) {
539
0
        add(parentGeom);
540
0
    }
541
0
}
542
543
/* public static */
544
Location
545
GeometryGraph::determineBoundary(
546
    const algorithm::BoundaryNodeRule& boundaryNodeRule,
547
    int boundaryCount)
548
0
{
549
0
    return boundaryNodeRule.isInBoundary(boundaryCount)
550
0
           ? Location::BOUNDARY : Location::INTERIOR;
551
0
}
552
553
} // namespace geos.geomgraph
554
} // namespace geos