Coverage Report

Created: 2026-09-14 06:16

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/geos/src/operation/overlayng/OverlayLabeller.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
#include <geos/operation/overlayng/OverlayLabeller.h>
16
#include <geos/operation/overlayng/OverlayEdge.h>
17
#include <geos/operation/overlayng/OverlayNG.h>
18
#include <geos/operation/overlayng/InputGeometry.h>
19
#include <geos/geom/Coordinate.h>
20
#include <geos/geom/Position.h>
21
#include <geos/geom/CoordinateSequence.h>
22
#include <geos/util/Assert.h>
23
#include <geos/util/TopologyException.h>
24
25
#include <sstream>
26
27
28
namespace geos {      // geos
29
namespace operation { // geos.operation
30
namespace overlayng { // geos.operation.overlayng
31
32
33
using namespace geos::geom;
34
35
/*public*/
36
void
37
OverlayLabeller::computeLabelling()
38
197k
{
39
197k
    std::vector<OverlayEdge*> nodes = graph->getNodeEdges();
40
197k
    labelAreaNodeEdges(nodes);
41
197k
    labelConnectedLinearEdges();
42
43
    //TODO: is there a way to avoid scanning all edges in these steps?
44
    /**
45
     * At this point collapsed edges labeled with location UNKNOWN
46
     * must be disconnected from the area edges of the parent.
47
     * They can be located based on their parent ring role (shell or hole).
48
     */
49
197k
    labelCollapsedEdges();
50
197k
    labelConnectedLinearEdges();
51
52
197k
    labelDisconnectedEdges();
53
197k
}
54
55
56
/*private*/
57
void
58
OverlayLabeller::labelAreaNodeEdges(std::vector<OverlayEdge*>& nodes)
59
197k
{
60
4.68M
    for (OverlayEdge* nodeEdge : nodes) {
61
4.68M
        propagateAreaLocations(nodeEdge, 0);
62
4.68M
        if (inputGeometry->hasEdges(1)) {
63
1.86M
            propagateAreaLocations(nodeEdge, 1);
64
1.86M
        }
65
4.68M
    }
66
197k
}
67
68
/*public*/
69
void
70
OverlayLabeller::propagateAreaLocations(OverlayEdge* nodeEdge, uint8_t geomIndex)
71
6.54M
{
72
    /*
73
     * Only propagate for area geometries
74
     */
75
6.54M
    if (!inputGeometry->isArea(geomIndex))
76
4.81M
        return;
77
78
    /**
79
     * No need to propagate if node has only one edge.
80
     * This handles dangling edges created by overlap limiting
81
     */
82
1.73M
    if (nodeEdge->degree() == 1)
83
177k
        return;
84
85
1.56M
    OverlayEdge* eStart = findPropagationStartEdge(nodeEdge, geomIndex);
86
    // no labelled edge found, so nothing to propagate
87
1.56M
    if (eStart == nullptr)
88
1.27M
        return;
89
90
    // initialize currLoc to location of L side
91
287k
    Location currLoc = eStart->getLocation(geomIndex, Position::LEFT);
92
287k
    OverlayEdge* e = eStart->oNextOE();
93
94
564k
    do {
95
564k
        OverlayLabel* label = e->getLabel();
96
564k
        if (!label->isBoundary(geomIndex)) {
97
            /**
98
            * If this is not a Boundary edge for this input area,
99
            * its location is now known relative to this input area
100
            */
101
200k
            label->setLocationLine(geomIndex, currLoc);
102
200k
        }
103
363k
        else {
104
363k
            util::Assert::isTrue(label->hasSides(geomIndex));
105
            /**
106
             *  This is a boundary edge for the input area geom.
107
             *  Update the current location from its labels.
108
             *  Also check for topological consistency.
109
             */
110
363k
            Location locRight = e->getLocation(geomIndex, Position::RIGHT);
111
363k
            if (locRight != currLoc) {
112
68.9k
                std::stringstream ss;
113
68.9k
                ss << "side location conflict at ";
114
68.9k
                ss << e->getCoordinate().toString();
115
68.9k
                ss << ". This can occur if the input geometry is invalid.";
116
68.9k
                throw util::TopologyException(ss.str());
117
68.9k
            }
118
294k
            Location locLeft = e->getLocation(geomIndex, Position::LEFT);
119
294k
            if (locLeft == Location::NONE) {
120
0
                util::Assert::shouldNeverReachHere("found single null side");
121
0
            }
122
294k
            currLoc = locLeft;
123
294k
        }
124
495k
        e = e->oNextOE();
125
495k
    } while (e != eStart);
126
287k
}
127
128
/*private*/
129
OverlayEdge*
130
OverlayLabeller::findPropagationStartEdge(OverlayEdge* nodeEdge, uint8_t geomIndex)
131
1.56M
{
132
1.56M
    OverlayEdge* eStart = nodeEdge;
133
5.75M
    do {
134
5.75M
        const OverlayLabel* label = eStart->getLabel();
135
5.75M
        if (label->isBoundary(geomIndex)) {
136
287k
            util::Assert::isTrue(label->hasSides(geomIndex));
137
287k
            return eStart;
138
287k
        }
139
5.46M
        eStart = static_cast<OverlayEdge*>(eStart->oNext());
140
5.46M
    } while (eStart != nodeEdge);
141
1.27M
    return nullptr;
142
1.56M
}
143
144
/*private*/
145
void
146
OverlayLabeller::labelCollapsedEdges()
147
128k
{
148
17.5M
    for (OverlayEdge* edge : edges) {
149
17.5M
        if (edge->getLabel()->isLineLocationUnknown(0)) {
150
14.8M
            labelCollapsedEdge(edge, 0);
151
14.8M
        }
152
17.5M
        if (edge->getLabel()->isLineLocationUnknown(1)) {
153
17.4M
            labelCollapsedEdge(edge, 1);
154
17.4M
        }
155
17.5M
    }
156
128k
}
157
158
/*private*/
159
void
160
OverlayLabeller::labelCollapsedEdge(OverlayEdge* edge, uint8_t geomIndex)
161
32.3M
{
162
32.3M
    OverlayLabel* label = edge->getLabel();
163
32.3M
    if (! label->isCollapse(geomIndex))
164
32.3M
        return;
165
    /**
166
    * This must be a collapsed edge which is disconnected
167
    * from any area edges (e.g. a fully collapsed shell or hole).
168
    * It can be labeled according to its parent source ring role.
169
    */
170
7.60k
    label->setLocationCollapse(geomIndex);
171
7.60k
}
172
173
/*private*/
174
void
175
OverlayLabeller::labelConnectedLinearEdges()
176
256k
{
177
    //TODO: can these be merged to avoid two scans?
178
256k
    propagateLinearLocations(0);
179
256k
    if (inputGeometry->hasEdges(1)) {
180
120k
        propagateLinearLocations(1);
181
120k
    }
182
256k
}
183
184
/*private*/
185
void
186
OverlayLabeller::propagateLinearLocations(uint8_t geomIndex)
187
377k
{
188
377k
    std::vector<OverlayEdge*> linearEdges = findLinearEdgesWithLocation(edges, geomIndex);
189
377k
    if (linearEdges.empty()) return;
190
191
50.1k
    std::deque<OverlayEdge*> edgeStack;
192
50.1k
    edgeStack.insert(edgeStack.begin(), linearEdges.begin(), linearEdges.end());
193
50.1k
    bool isInputLine = inputGeometry->isLine(geomIndex);
194
    // traverse connected linear edges, labeling unknown ones
195
1.27M
    while (! edgeStack.empty()) {
196
1.22M
        OverlayEdge* lineEdge = edgeStack.front();
197
1.22M
        edgeStack.pop_front();
198
199
        // for any edges around origin with unknown location for this geomIndex,
200
        // add those edges to stack to continue traversal
201
1.22M
        propagateLinearLocationAtNode(lineEdge, geomIndex, isInputLine, edgeStack);
202
1.22M
    }
203
50.1k
}
204
205
/*private static*/
206
void
207
OverlayLabeller::propagateLinearLocationAtNode(OverlayEdge* eNode,
208
                                               uint8_t geomIndex, bool isInputLine,
209
                                               std::deque<OverlayEdge*>& edgeStack)
210
1.22M
{
211
1.22M
    Location lineLoc = eNode->getLabel()->getLineLocation(geomIndex);
212
    /**
213
    * If the parent geom is a Line
214
    * then only propagate EXTERIOR locations.
215
    */
216
1.22M
    if (isInputLine && lineLoc != Location::EXTERIOR)
217
0
        return;
218
219
1.22M
    OverlayEdge* e = eNode->oNextOE();
220
3.62M
    do {
221
3.62M
        OverlayLabel* label = e->getLabel();
222
3.62M
        if (label->isLineLocationUnknown(geomIndex)) {
223
            /**
224
             * If edge is not a boundary edge,
225
             * its location is now known for this area
226
             */
227
1.00M
            label->setLocationLine(geomIndex, lineLoc);
228
            /**
229
             * Add sym edge to stack for graph traversal
230
             * (Don't add e itself, since e origin node has now been scanned)
231
             */
232
1.00M
            edgeStack.push_front(e->symOE());
233
1.00M
      }
234
3.62M
      e = e->oNextOE();
235
3.62M
    }
236
3.62M
    while (e != eNode);
237
1.22M
}
238
239
/*private static*/
240
std::vector<OverlayEdge*>
241
OverlayLabeller::findLinearEdgesWithLocation(const std::vector<OverlayEdge*>& edges, uint8_t geomIndex)
242
377k
{
243
377k
    std::vector<OverlayEdge*> linearEdges;
244
48.4M
    for (OverlayEdge* edge : edges) {
245
48.4M
        OverlayLabel* lbl = edge->getLabel();
246
        // keep if linear with known location
247
48.4M
        if (lbl->isLinear(geomIndex) && !lbl->isLineLocationUnknown(geomIndex)) {
248
215k
            linearEdges.push_back(edge);
249
215k
        }
250
48.4M
    }
251
377k
    return linearEdges;
252
377k
}
253
254
/*private*/
255
void
256
OverlayLabeller::labelDisconnectedEdges()
257
128k
{
258
17.5M
    for (OverlayEdge* edge : edges) {
259
17.5M
        if (edge->getLabel()->isLineLocationUnknown(0)) {
260
7.42M
            labelDisconnectedEdge(edge, 0);
261
7.42M
        }
262
17.5M
        if (edge->getLabel()->isLineLocationUnknown(1)) {
263
8.72M
            labelDisconnectedEdge(edge, 1);
264
8.72M
        }
265
17.5M
    }
266
128k
}
267
268
/*private*/
269
void
270
OverlayLabeller::labelDisconnectedEdge(OverlayEdge* edge, uint8_t geomIndex)
271
16.1M
{
272
16.1M
    OverlayLabel* label = edge->getLabel();
273
274
    /**
275
    * if target geom is not an area then
276
    * edge must be EXTERIOR, since to be
277
    * INTERIOR it would have been labelled
278
    * when it was created.
279
    */
280
16.1M
    if (!inputGeometry->isArea(geomIndex)) {
281
14.6M
        label->setLocationAll(geomIndex, Location::EXTERIOR);
282
14.6M
        return;
283
14.6M
    }
284
285
    /**
286
    * Locate edge in input area using a Point-In-Poly check.
287
    * This should be safe even with precision reduction,
288
    * because since the edge has remained disconnected
289
    * its interior-exterior relationship
290
    * can be determined relative to the original input geometry.
291
    */
292
1.45M
    Location edgeLoc = locateEdgeBothEnds(geomIndex, edge);
293
1.45M
    label->setLocationAll(geomIndex, edgeLoc);
294
1.45M
}
295
296
/*private*/
297
Location
298
OverlayLabeller::locateEdge(uint8_t geomIndex, OverlayEdge* edge)
299
0
{
300
0
    Location loc = inputGeometry->locatePointInArea(geomIndex, edge->orig());
301
0
    Location edgeLoc = loc != Location::EXTERIOR ? Location::INTERIOR : Location::EXTERIOR;
302
0
    return edgeLoc;
303
0
}
304
305
/*private*/
306
Location
307
OverlayLabeller::locateEdgeBothEnds(uint8_t geomIndex, OverlayEdge* edge)
308
1.45M
{
309
    /*
310
    * To improve the robustness of the point location,
311
    * check both ends of the edge.
312
    * Edge is only labelled INTERIOR if both ends are.
313
    */
314
1.45M
    Location locOrig = inputGeometry->locatePointInArea(geomIndex, edge->orig());
315
1.45M
    Location locDest = inputGeometry->locatePointInArea(geomIndex, edge->dest());
316
1.45M
    bool isInt = locOrig != Location::EXTERIOR && locDest != Location::EXTERIOR;
317
1.45M
    Location edgeLoc = isInt ? Location::INTERIOR : Location::EXTERIOR;
318
1.45M
    return edgeLoc;
319
1.45M
}
320
321
/*public*/
322
void
323
OverlayLabeller::markResultAreaEdges(int overlayOpCode)
324
128k
{
325
17.5M
    for (OverlayEdge* edge : edges) {
326
17.5M
        markInResultArea(edge, overlayOpCode);
327
17.5M
    }
328
128k
}
329
330
/*public*/
331
void
332
OverlayLabeller::markInResultArea(OverlayEdge* e, int overlayOpCode)
333
17.5M
{
334
17.5M
    const OverlayLabel* label = e->getLabel();
335
17.5M
    if (label->isBoundaryEither() && OverlayNG::isResultOfOp(overlayOpCode,
336
405k
        label->getLocationBoundaryOrLine(0, Position::RIGHT, e->isForward()),
337
405k
        label->getLocationBoundaryOrLine(1, Position::RIGHT, e->isForward())))
338
166k
    {
339
166k
        e->markInResultArea();
340
166k
    }
341
17.5M
}
342
343
/*public*/
344
void
345
OverlayLabeller::unmarkDuplicateEdgesFromResultArea()
346
128k
{
347
17.5M
    for (OverlayEdge* edge : edges) {
348
17.5M
        if (edge->isInResultAreaBoth()) {
349
1.65k
            edge->unmarkFromResultAreaBoth();
350
1.65k
        }
351
17.5M
    }
352
128k
}
353
354
355
356
357
358
359
} // namespace geos.operation.overlayng
360
} // namespace geos.operation
361
} // namespace geos