Coverage Report

Created: 2026-05-30 06:18

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/geos/src/operation/relateng/TopologyComputer.cpp
Line
Count
Source
1
/**********************************************************************
2
 *
3
 * GEOS - Geometry Engine Open Source
4
 * http://geos.osgeo.org
5
 *
6
 * Copyright (c) 2024 Martin Davis
7
 * Copyright (C) 2024 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/algorithm/PolygonNodeTopology.h>
17
#include <geos/geom/Dimension.h>
18
#include <geos/geom/Location.h>
19
#include <geos/geom/Position.h>
20
#include <geos/geom/Coordinate.h>
21
#include <geos/geom/Geometry.h>
22
#include <geos/operation/relateng/TopologyComputer.h>
23
#include <geos/operation/relateng/TopologyPredicate.h>
24
#include <geos/operation/relateng/RelateGeometry.h>
25
#include <geos/operation/relateng/RelateNode.h>
26
#include <geos/operation/relateng/NodeSection.h>
27
#include <geos/util/IllegalStateException.h>
28
#include <sstream>
29
30
31
using geos::algorithm::PolygonNodeTopology;
32
using geos::geom::Coordinate;
33
using geos::geom::CoordinateXY;
34
using geos::geom::Geometry;
35
using geos::geom::Dimension;
36
using geos::geom::Location;
37
using geos::geom::Position;
38
using geos::util::IllegalStateException;
39
40
41
namespace geos {      // geos
42
namespace operation { // geos.operation
43
namespace relateng {  // geos.operation.relateng
44
45
46
/* private */
47
void
48
TopologyComputer::initExteriorDims()
49
0
{
50
0
    int dimRealA = geomA.getDimensionReal();
51
0
    int dimRealB = geomB.getDimensionReal();
52
53
    /**
54
     * For P/L case, P exterior intersects L interior
55
     */
56
0
    if (dimRealA == Dimension::P && dimRealB == Dimension::L) {
57
0
        updateDim(Location::EXTERIOR, Location::INTERIOR, Dimension::L);
58
0
    }
59
0
    else if (dimRealA == Dimension::L && dimRealB == Dimension::P) {
60
0
        updateDim(Location::INTERIOR, Location::EXTERIOR, Dimension::L);
61
0
    }
62
    /**
63
     * For P/A case, the Area Int and Bdy intersect the Point exterior.
64
     */
65
0
    else if (dimRealA == Dimension::P && dimRealB == Dimension::A) {
66
0
        updateDim(Location::EXTERIOR, Location::INTERIOR, Dimension::A);
67
0
        updateDim(Location::EXTERIOR, Location::BOUNDARY, Dimension::L);
68
0
    }
69
0
    else if (dimRealA == Dimension::A && dimRealB == Dimension::P) {
70
0
        updateDim(Location::INTERIOR, Location::EXTERIOR, Dimension::A);
71
0
        updateDim(Location::BOUNDARY, Location::EXTERIOR, Dimension::L);
72
0
    }
73
0
    else if (dimRealA == Dimension::L && dimRealB == Dimension::A) {
74
0
        updateDim(Location::EXTERIOR, Location::INTERIOR, Dimension::A);
75
0
     }
76
0
    else if (dimRealA == Dimension::A && dimRealB == Dimension::L) {
77
0
        updateDim(Location::INTERIOR, Location::EXTERIOR, Dimension::A);
78
0
    }
79
    //-- cases where one geom is EMPTY
80
0
    else if (dimRealA == Dimension::False || dimRealB == Dimension::False) {
81
0
        if (dimRealA != Dimension::False) {
82
0
            initExteriorEmpty(RelateGeometry::GEOM_A);
83
0
        }
84
0
        if (dimRealB != Dimension::False) {
85
0
            initExteriorEmpty(RelateGeometry::GEOM_B);
86
0
        }
87
0
    }
88
0
}
89
90
91
/* private */
92
void
93
TopologyComputer::initExteriorEmpty(bool geomNonEmpty)
94
0
{
95
0
    int dimNonEmpty = getDimension(geomNonEmpty);
96
0
    switch (dimNonEmpty) {
97
0
        case Dimension::P:
98
0
            updateDim(geomNonEmpty, Location::INTERIOR, Location::EXTERIOR, Dimension::P);
99
0
            break;
100
0
        case Dimension::L:
101
0
            if (getGeometry(geomNonEmpty).hasBoundary()) {
102
0
                updateDim(geomNonEmpty, Location::BOUNDARY, Location::EXTERIOR, Dimension::P);
103
0
            }
104
0
            updateDim(geomNonEmpty, Location::INTERIOR, Location::EXTERIOR, Dimension::L);
105
0
            break;
106
0
        case Dimension::A:
107
0
            updateDim(geomNonEmpty, Location::BOUNDARY, Location::EXTERIOR, Dimension::L);
108
0
            updateDim(geomNonEmpty, Location::INTERIOR, Location::EXTERIOR, Dimension::A);
109
0
            break;
110
0
    }
111
0
}
112
113
114
/* public */
115
bool
116
TopologyComputer::isAreaArea() const
117
0
{
118
0
    return getDimension(RelateGeometry::GEOM_A) == Dimension::A
119
0
        && getDimension(RelateGeometry::GEOM_B) == Dimension::A;
120
0
}
121
122
123
/* public */
124
int
125
TopologyComputer::getDimension(bool isA) const
126
0
{
127
0
    return getGeometry(isA).getDimension();
128
0
}
129
130
131
/* public */
132
bool
133
TopologyComputer::isSelfNodingRequired() const
134
0
{
135
0
    if (! predicate.requireSelfNoding())
136
0
      return false;
137
    
138
0
    if (geomA.isSelfNodingRequired()) 
139
0
      return true;
140
    
141
    //-- if B is a mixed GC with A and L require full noding
142
0
    if (geomB.hasAreaAndLine())
143
0
      return true;
144
145
0
    return false;
146
0
}
147
148
149
/* public */
150
bool
151
TopologyComputer::isExteriorCheckRequired(bool isA) const
152
0
{
153
0
    return predicate.requireExteriorCheck(isA);
154
0
}
155
156
// static char
157
// toSymbol(Location loc) {
158
//     switch (loc) {
159
//         case Location::NONE: return '-';
160
//         case Location::INTERIOR: return 'I';
161
//         case Location::BOUNDARY: return 'B';
162
//         case Location::EXTERIOR: return 'E';
163
//     }
164
//     return ' ';
165
// }
166
167
/* private */
168
void
169
TopologyComputer::updateDim(Location locA, Location locB, int dimension)
170
0
{
171
    //std::cout << toSymbol(locA) << toSymbol(locB) << " <- " << dimension << std::endl;
172
0
    predicate.updateDimension(locA, locB, dimension);
173
0
}
174
175
/* private */
176
void
177
TopologyComputer::updateDim(bool isAB, Location loc1, Location loc2, int dimension)
178
0
{
179
0
    if (isAB) {
180
0
        updateDim(loc1, loc2, dimension);
181
0
    }
182
0
    else {
183
        // is ordered BA
184
0
        updateDim(loc2, loc1, dimension);
185
0
    }
186
0
}
187
188
189
/* public */
190
bool
191
TopologyComputer::isResultKnown() const
192
0
{
193
0
    return predicate.isKnown();
194
0
}
195
196
197
/* public */
198
bool
199
TopologyComputer::getResult() const
200
0
{
201
0
    return predicate.value();
202
0
}
203
204
205
/* public */
206
void
207
TopologyComputer::finish()
208
0
{
209
0
    predicate.finish();
210
0
}
211
212
213
/* private */
214
NodeSections *
215
TopologyComputer::getNodeSections(const CoordinateXY& nodePt)
216
0
{
217
0
    NodeSections* ns;
218
0
    auto result = nodeMap.find(nodePt);
219
0
    if (result == nodeMap.end()) {
220
0
        ns = new NodeSections(&nodePt);
221
0
        nodeSectionsStore.emplace_back(ns);
222
0
        nodeMap[nodePt] = ns;
223
0
    }
224
0
    else {
225
0
        ns = result->second;
226
0
    }
227
0
    return ns;
228
0
}
229
230
231
/* public */
232
void
233
TopologyComputer::addIntersection(NodeSection* a, NodeSection* b)
234
0
{
235
    // add edges to node to allow full topology evaluation later
236
    // we run this first (unlike JTS) in case the subsequent test throws
237
    // an exception and the NodeSection pointers are not correctly
238
    // saved in the memory managed store on the NodeSections, causing
239
    // a small memory leak
240
0
    addNodeSections(a, b);
241
242
0
    if (! a->isSameGeometry(b)) {
243
0
        updateIntersectionAB(a, b);
244
0
    }
245
0
}
246
247
248
/* private */
249
void
250
TopologyComputer::updateIntersectionAB(const NodeSection* a, const NodeSection* b)
251
0
{
252
0
    if (NodeSection::isAreaArea(*a, *b)) {
253
0
        updateAreaAreaCross(a, b);
254
0
    }
255
0
    updateNodeLocation(a, b);
256
0
}
257
258
259
/* private */
260
void
261
TopologyComputer::updateAreaAreaCross(const NodeSection* a, const NodeSection* b)
262
0
{
263
0
    bool isProper = NodeSection::isProper(*a, *b);
264
0
    if (isProper || PolygonNodeTopology::isCrossing(&(a->nodePt()),
265
0
        a->getVertex(0), a->getVertex(1),
266
0
        b->getVertex(0), b->getVertex(1)))
267
0
    {
268
0
        updateDim(Location::INTERIOR, Location::INTERIOR, Dimension::A);
269
0
    }
270
0
}
271
272
273
/* private */
274
void
275
TopologyComputer::updateNodeLocation(const NodeSection* a, const NodeSection* b)
276
0
{
277
0
    const CoordinateXY& pt = a->nodePt();
278
0
    Location locA = geomA.locateNode(&pt, a->getPolygonal());
279
0
    Location locB = geomB.locateNode(&pt, b->getPolygonal());
280
0
    updateDim(locA, locB, Dimension::P);
281
0
}
282
283
/* private */
284
void
285
TopologyComputer::addNodeSections(NodeSection* ns0, NodeSection* ns1)
286
0
{
287
0
    NodeSections* sections = getNodeSections(ns0->nodePt());
288
0
    sections->addNodeSection(ns0);
289
0
    sections->addNodeSection(ns1);
290
0
}
291
292
/* public */
293
void
294
TopologyComputer::addPointOnPointInterior(const CoordinateXY* pt)
295
0
{
296
0
    (void)pt;
297
0
    updateDim(Location::INTERIOR, Location::INTERIOR, Dimension::P);
298
0
}
299
300
/* public */
301
void
302
TopologyComputer::addPointOnPointExterior(bool isGeomA, const CoordinateXY* pt)
303
0
{
304
0
    (void)pt;
305
0
    updateDim(isGeomA, Location::INTERIOR, Location::EXTERIOR, Dimension::P);
306
0
}
307
308
/* public */
309
void
310
TopologyComputer::addPointOnGeometry(bool isPointA, Location locTarget, int dimTarget, const CoordinateXY* pt)
311
0
{
312
0
    (void)pt;
313
    //-- update entry for Point interior
314
0
    updateDim(isPointA, Location::INTERIOR, locTarget, Dimension::P);
315
316
    //-- an empty geometry has no points to infer entries from
317
0
    if (getGeometry(! isPointA).isEmpty())
318
0
      return;
319
320
0
    switch (dimTarget) {
321
0
    case Dimension::P:
322
0
        return;
323
0
    case Dimension::L:
324
        /**
325
         * Because zero-length lines are handled,
326
         * a point lying in the exterior of the line target
327
         * may imply either P or L for the Exterior interaction
328
         */
329
        //TODO: determine if effective dimension of linear target is L?
330
        //updateDim(isGeomA, Location::EXTERIOR, locTarget, Dimension::P);
331
0
        return;
332
0
    case Dimension::A:
333
        /**
334
         * If a point intersects an area target, then the area interior and boundary
335
         * must extend beyond the point and thus interact with its exterior.
336
         */
337
0
        updateDim(isPointA, Location::EXTERIOR, Location::INTERIOR, Dimension::A);
338
0
        updateDim(isPointA, Location::EXTERIOR, Location::BOUNDARY, Dimension::L);
339
0
        return;
340
0
    }
341
0
    throw IllegalStateException("Unknown target dimension: " + std::to_string(dimTarget));
342
0
}
343
344
/* public */
345
void
346
TopologyComputer::addLineEndOnGeometry(bool isLineA, Location locLineEnd, Location locTarget, int dimTarget, const CoordinateXY* pt)
347
0
{
348
0
    (void)pt;
349
350
    //-- record topology at line end point
351
0
    updateDim(isLineA, locLineEnd, locTarget, Dimension::P);
352
353
    //-- an empty geometry has no points to infer entries from
354
0
    if (getGeometry(! isLineA).isEmpty())
355
0
      return;
356
      
357
    //-- Line and Area targets may have additional topology
358
0
    switch (dimTarget) {
359
0
    case Dimension::P:
360
0
        return;
361
0
    case Dimension::L:
362
0
        addLineEndOnLine(isLineA, locLineEnd, locTarget, pt);
363
0
        return;
364
0
    case Dimension::A:
365
0
        addLineEndOnArea(isLineA, locLineEnd, locTarget, pt);
366
0
        return;
367
0
    }
368
0
    throw IllegalStateException("Unknown target dimension: " + std::to_string(dimTarget));
369
0
}
370
371
372
/* private */
373
void
374
TopologyComputer::addLineEndOnLine(bool isLineA, Location locLineEnd, Location locLine, const CoordinateXY* pt)
375
0
{
376
0
    (void)pt;
377
0
    (void)locLineEnd;
378
    /**
379
     * When a line end is in the EXTERIOR of a Line,
380
     * some length of the source Line INTERIOR
381
     * is also in the target Line EXTERIOR.
382
     * This works for zero-length lines as well.
383
     */
384
0
    if (locLine == Location::EXTERIOR) {
385
0
        updateDim(isLineA, Location::INTERIOR, Location::EXTERIOR, Dimension::L);
386
0
    }
387
0
}
388
389
390
/* private */
391
void
392
TopologyComputer::addLineEndOnArea(bool isLineA, Location locLineEnd, Location locArea, const CoordinateXY* pt)
393
0
{
394
0
    (void)pt;
395
0
    (void)locLineEnd;
396
0
    if (locArea != Location::BOUNDARY) {
397
        /**
398
         * When a line end is in an Area INTERIOR or EXTERIOR
399
         * some length of the source Line Interior
400
         * AND the Exterior of the line
401
         * is also in that location of the target.
402
         * NOTE: this assumes the line end is NOT also in an Area of a mixed-dim GC
403
         */
404
        //TODO: handle zero-length lines?
405
0
        updateDim(isLineA, Location::INTERIOR, locArea, Dimension::L);
406
0
        updateDim(isLineA, Location::EXTERIOR, locArea, Dimension::A);
407
0
    }
408
0
}
409
410
411
/* public */
412
void
413
TopologyComputer::addAreaVertex(bool isAreaA, Location locArea, Location locTarget, int dimTarget, const CoordinateXY* pt)
414
0
{
415
0
    (void)pt;
416
0
    if (locTarget == Location::EXTERIOR) {
417
0
        updateDim(isAreaA, Location::INTERIOR, Location::EXTERIOR, Dimension::A);
418
        /**
419
         * If area vertex is on Boundary further topology can be deduced
420
         * from the neighbourhood around the boundary vertex.
421
         * This is always the case for polygonal geometries.
422
         * For GCs, the vertex may be either on boundary or in interior
423
         * (i.e. of overlapping or adjacent polygons)
424
         */
425
0
        if (locArea == Location::BOUNDARY) {
426
0
            updateDim(isAreaA, Location::BOUNDARY, Location::EXTERIOR, Dimension::L);
427
0
            updateDim(isAreaA, Location::EXTERIOR, Location::EXTERIOR, Dimension::A);
428
0
        }
429
0
        return;
430
0
    }
431
432
0
    switch (dimTarget) {
433
0
        case Dimension::P:
434
0
            addAreaVertexOnPoint(isAreaA, locArea, pt);
435
0
            return;
436
0
        case Dimension::L:
437
0
            addAreaVertexOnLine(isAreaA, locArea, locTarget, pt);
438
0
            return;
439
0
        case Dimension::A:
440
0
            addAreaVertexOnArea(isAreaA, locArea, locTarget, pt);
441
0
            return;
442
0
    }
443
0
    throw IllegalStateException("Unknown target dimension: " + std::to_string(dimTarget));
444
0
}
445
446
447
/* private */
448
void
449
TopologyComputer::addAreaVertexOnPoint(bool isAreaA, Location locArea, const CoordinateXY* pt)
450
0
{
451
0
    (void)pt;
452
    //-- Assert: locArea != EXTERIOR
453
    //-- Assert: locTarget == INTERIOR
454
    /**
455
     * The vertex location intersects the Point.
456
     */
457
0
    updateDim(isAreaA, locArea, Location::INTERIOR, Dimension::P);
458
    /**
459
     * The area interior intersects the point's exterior neighbourhood.
460
     */
461
0
    updateDim(isAreaA, Location::INTERIOR, Location::EXTERIOR, Dimension::A);
462
    /**
463
     * If the area vertex is on the boundary,
464
     * the area boundary and exterior intersect the point's exterior neighbourhood
465
     */
466
0
    if (locArea == Location::BOUNDARY) {
467
0
        updateDim(isAreaA, Location::BOUNDARY, Location::EXTERIOR, Dimension::L);
468
0
        updateDim(isAreaA, Location::EXTERIOR, Location::EXTERIOR, Dimension::A);
469
0
    }
470
0
}
471
472
473
/* private */
474
void
475
TopologyComputer::addAreaVertexOnLine(bool isAreaA, Location locArea, Location locTarget, const CoordinateXY* pt)
476
0
{
477
0
    (void)pt;
478
    //-- Assert: locArea != EXTERIOR
479
    /**
480
     * If an area vertex intersects a line, all we know is the
481
     * intersection at that point.
482
     * e.g. the line may or may not be collinear with the area boundary,
483
     * and the line may or may not intersect the area interior.
484
     * Full topology is determined later by node analysis
485
     */
486
0
    updateDim(isAreaA, locArea, locTarget, Dimension::P);
487
0
    if (locArea == Location::INTERIOR) {
488
        /**
489
         * The area interior intersects the line's exterior neighbourhood.
490
         */
491
0
        updateDim(isAreaA, Location::INTERIOR, Location::EXTERIOR, Dimension::A);
492
0
    }
493
0
}
494
495
496
/* public */
497
void
498
TopologyComputer::addAreaVertexOnArea(bool isAreaA, Location locArea, Location locTarget, const CoordinateXY* pt)
499
0
{
500
0
    (void)pt;
501
0
    if (locTarget == Location::BOUNDARY) {
502
0
        if (locArea == Location::BOUNDARY) {
503
            //-- B/B topology is fully computed later by node analysis
504
0
            updateDim(isAreaA, Location::BOUNDARY, Location::BOUNDARY, Dimension::P);
505
0
        }
506
0
        else {
507
            // locArea == INTERIOR
508
0
            updateDim(isAreaA, Location::INTERIOR, Location::INTERIOR, Dimension::A);
509
0
            updateDim(isAreaA, Location::INTERIOR, Location::BOUNDARY, Dimension::L);
510
0
            updateDim(isAreaA, Location::INTERIOR, Location::EXTERIOR, Dimension::A);
511
0
        }
512
0
    }
513
0
    else {
514
        //-- locTarget is INTERIOR or EXTERIOR`
515
0
        updateDim(isAreaA, Location::INTERIOR, locTarget, Dimension::A);
516
        /**
517
         * If area vertex is on Boundary further topology can be deduced
518
         * from the neighbourhood around the boundary vertex.
519
         * This is always the case for polygonal geometries.
520
         * For GCs, the vertex may be either on boundary or in interior
521
         * (i.e. of overlapping or adjacent polygons)
522
         */
523
0
        if (locArea == Location::BOUNDARY) {
524
0
            updateDim(isAreaA, Location::BOUNDARY, locTarget, Dimension::L);
525
0
            updateDim(isAreaA, Location::EXTERIOR, locTarget, Dimension::A);
526
0
        }
527
0
    }
528
0
}
529
530
531
/* public */
532
void
533
TopologyComputer::evaluateNodes()
534
0
{
535
0
    for (auto& kv : nodeMap) {
536
0
        NodeSections* nodeSections = kv.second;
537
0
        if (nodeSections->hasInteractionAB()) {
538
0
            evaluateNode(nodeSections);
539
0
            if (isResultKnown())
540
0
                return;
541
0
        }
542
0
    }
543
0
}
544
545
546
/* private */
547
void
548
TopologyComputer::evaluateNode(NodeSections* nodeSections)
549
0
{
550
0
    const CoordinateXY* p = nodeSections->getCoordinate();
551
0
    std::unique_ptr<RelateNode> node = nodeSections->createNode();
552
    //-- Node must have edges for geom, but may also be in interior of a overlapping GC
553
0
    bool isAreaInteriorA = geomA.isNodeInArea(p, nodeSections->getPolygonal(RelateGeometry::GEOM_A));
554
0
    bool isAreaInteriorB = geomB.isNodeInArea(p, nodeSections->getPolygonal(RelateGeometry::GEOM_B));
555
0
    node->finish(isAreaInteriorA, isAreaInteriorB);
556
0
    evaluateNodeEdges(node.get());
557
0
}
558
559
560
/* private */
561
void
562
TopologyComputer::evaluateNodeEdges(const RelateNode* node)
563
0
{
564
    //TODO: collect distinct dim settings by using temporary matrix?
565
0
    for (const std::unique_ptr<RelateEdge>& e : node->getEdges()) {
566
        //-- An optimization to avoid updates for cases with a linear geometry
567
0
        if (isAreaArea()) {
568
0
            updateDim(e->location(RelateGeometry::GEOM_A, Position::LEFT),
569
0
                      e->location(RelateGeometry::GEOM_B, Position::LEFT), Dimension::A);
570
0
            updateDim(e->location(RelateGeometry::GEOM_A, Position::RIGHT),
571
0
                      e->location(RelateGeometry::GEOM_B, Position::RIGHT), Dimension::A);
572
0
        }
573
0
        updateDim(e->location(RelateGeometry::GEOM_A, Position::ON),
574
0
                  e->location(RelateGeometry::GEOM_B, Position::ON), Dimension::L);
575
0
    }
576
0
}
577
578
579
} // namespace geos.operation.overlayng
580
} // namespace geos.operation
581
} // namespace geos
582
583