Coverage Report

Created: 2025-07-23 06:51

/src/geos/src/operation/overlayng/OverlayLabeller.cpp
Line
Count
Source (jump to first uncovered line)
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
265k
{
39
265k
    std::vector<OverlayEdge*> nodes = graph->getNodeEdges();
40
265k
    labelAreaNodeEdges(nodes);
41
265k
    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
265k
    labelCollapsedEdges();
50
265k
    labelConnectedLinearEdges();
51
52
265k
    labelDisconnectedEdges();
53
265k
}
54
55
56
/*private*/
57
void
58
OverlayLabeller::labelAreaNodeEdges(std::vector<OverlayEdge*>& nodes)
59
265k
{
60
2.27M
    for (OverlayEdge* nodeEdge : nodes) {
61
2.27M
        propagateAreaLocations(nodeEdge, 0);
62
2.27M
        if (inputGeometry->hasEdges(1)) {
63
1.07M
            propagateAreaLocations(nodeEdge, 1);
64
1.07M
        }
65
2.27M
    }
66
265k
}
67
68
/*public*/
69
void
70
OverlayLabeller::propagateAreaLocations(OverlayEdge* nodeEdge, uint8_t geomIndex)
71
3.35M
{
72
    /*
73
     * Only propagate for area geometries
74
     */
75
3.35M
    if (!inputGeometry->isArea(geomIndex))
76
1.72M
        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.62M
    if (nodeEdge->degree() == 1)
83
282k
        return;
84
85
1.34M
    OverlayEdge* eStart = findPropagationStartEdge(nodeEdge, geomIndex);
86
    // no labelled edge found, so nothing to propagate
87
1.34M
    if (eStart == nullptr)
88
210k
        return;
89
90
    // initialize currLoc to location of L side
91
1.13M
    Location currLoc = eStart->getLocation(geomIndex, Position::LEFT);
92
1.13M
    OverlayEdge* e = eStart->oNextOE();
93
94
2.07M
    do {
95
2.07M
        OverlayLabel* label = e->getLabel();
96
2.07M
        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
767k
            label->setLocationLine(geomIndex, currLoc);
102
767k
        }
103
1.30M
        else {
104
1.30M
            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
1.30M
            Location locRight = e->getLocation(geomIndex, Position::RIGHT);
111
1.30M
            if (locRight != currLoc) {
112
64.8k
                std::stringstream ss;
113
64.8k
                ss << "side location conflict at ";
114
64.8k
                ss << e->getCoordinate().toString();
115
64.8k
                ss << ". This can occur if the input geometry is invalid.";
116
64.8k
                throw util::TopologyException(ss.str());
117
64.8k
            }
118
1.23M
            Location locLeft = e->getLocation(geomIndex, Position::LEFT);
119
1.23M
            if (locLeft == Location::NONE) {
120
0
                util::Assert::shouldNeverReachHere("found single null side");
121
0
            }
122
1.23M
            currLoc = locLeft;
123
1.23M
        }
124
2.00M
        e = e->oNextOE();
125
2.00M
    } while (e != eStart);
126
1.13M
}
127
128
/*private*/
129
OverlayEdge*
130
OverlayLabeller::findPropagationStartEdge(OverlayEdge* nodeEdge, uint8_t geomIndex)
131
1.34M
{
132
1.34M
    OverlayEdge* eStart = nodeEdge;
133
1.90M
    do {
134
1.90M
        const OverlayLabel* label = eStart->getLabel();
135
1.90M
        if (label->isBoundary(geomIndex)) {
136
1.13M
            util::Assert::isTrue(label->hasSides(geomIndex));
137
1.13M
            return eStart;
138
1.13M
        }
139
774k
        eStart = static_cast<OverlayEdge*>(eStart->oNext());
140
774k
    } while (eStart != nodeEdge);
141
210k
    return nullptr;
142
1.34M
}
143
144
/*private*/
145
void
146
OverlayLabeller::labelCollapsedEdges()
147
200k
{
148
7.53M
    for (OverlayEdge* edge : edges) {
149
7.53M
        if (edge->getLabel()->isLineLocationUnknown(0)) {
150
5.80M
            labelCollapsedEdge(edge, 0);
151
5.80M
        }
152
7.53M
        if (edge->getLabel()->isLineLocationUnknown(1)) {
153
6.11M
            labelCollapsedEdge(edge, 1);
154
6.11M
        }
155
7.53M
    }
156
200k
}
157
158
/*private*/
159
void
160
OverlayLabeller::labelCollapsedEdge(OverlayEdge* edge, uint8_t geomIndex)
161
11.9M
{
162
11.9M
    OverlayLabel* label = edge->getLabel();
163
11.9M
    if (! label->isCollapse(geomIndex))
164
11.8M
        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
114k
    label->setLocationCollapse(geomIndex);
171
114k
}
172
173
/*private*/
174
void
175
OverlayLabeller::labelConnectedLinearEdges()
176
400k
{
177
    //TODO: can these be merged to avoid two scans?
178
400k
    propagateLinearLocations(0);
179
400k
    if (inputGeometry->hasEdges(1)) {
180
275k
        propagateLinearLocations(1);
181
275k
    }
182
400k
}
183
184
/*private*/
185
void
186
OverlayLabeller::propagateLinearLocations(uint8_t geomIndex)
187
676k
{
188
676k
    std::vector<OverlayEdge*> linearEdges = findLinearEdgesWithLocation(edges, geomIndex);
189
676k
    if (linearEdges.empty()) return;
190
191
151k
    std::deque<OverlayEdge*> edgeStack;
192
151k
    edgeStack.insert(edgeStack.begin(), linearEdges.begin(), linearEdges.end());
193
151k
    bool isInputLine = inputGeometry->isLine(geomIndex);
194
    // traverse connected linear edges, labeling unknown ones
195
815k
    while (! edgeStack.empty()) {
196
664k
        OverlayEdge* lineEdge = edgeStack.front();
197
664k
        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
664k
        propagateLinearLocationAtNode(lineEdge, geomIndex, isInputLine, edgeStack);
202
664k
    }
203
151k
}
204
205
/*private static*/
206
void
207
OverlayLabeller::propagateLinearLocationAtNode(OverlayEdge* eNode,
208
                                               uint8_t geomIndex, bool isInputLine,
209
                                               std::deque<OverlayEdge*>& edgeStack)
210
664k
{
211
664k
    Location lineLoc = eNode->getLabel()->getLineLocation(geomIndex);
212
    /**
213
    * If the parent geom is a Line
214
    * then only propagate EXTERIOR locations.
215
    */
216
664k
    if (isInputLine && lineLoc != Location::EXTERIOR)
217
0
        return;
218
219
664k
    OverlayEdge* e = eNode->oNextOE();
220
1.43M
    do {
221
1.43M
        OverlayLabel* label = e->getLabel();
222
1.43M
        if (label->isLineLocationUnknown(geomIndex)) {
223
            /**
224
             * If edge is not a boundary edge,
225
             * its location is now known for this area
226
             */
227
59.1k
            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
59.1k
            edgeStack.push_front(e->symOE());
233
59.1k
      }
234
1.43M
      e = e->oNextOE();
235
1.43M
    }
236
1.43M
    while (e != eNode);
237
664k
}
238
239
/*private static*/
240
std::vector<OverlayEdge*>
241
OverlayLabeller::findLinearEdgesWithLocation(const std::vector<OverlayEdge*>& edges, uint8_t geomIndex)
242
676k
{
243
676k
    std::vector<OverlayEdge*> linearEdges;
244
21.1M
    for (OverlayEdge* edge : edges) {
245
21.1M
        OverlayLabel* lbl = edge->getLabel();
246
        // keep if linear with known location
247
21.1M
        if (lbl->isLinear(geomIndex) && !lbl->isLineLocationUnknown(geomIndex)) {
248
604k
            linearEdges.push_back(edge);
249
604k
        }
250
21.1M
    }
251
676k
    return linearEdges;
252
676k
}
253
254
/*private*/
255
void
256
OverlayLabeller::labelDisconnectedEdges()
257
200k
{
258
7.53M
    for (OverlayEdge* edge : edges) {
259
7.53M
        if (edge->getLabel()->isLineLocationUnknown(0)) {
260
2.85M
            labelDisconnectedEdge(edge, 0);
261
2.85M
        }
262
7.53M
        if (edge->getLabel()->isLineLocationUnknown(1)) {
263
3.02M
            labelDisconnectedEdge(edge, 1);
264
3.02M
        }
265
7.53M
    }
266
200k
}
267
268
/*private*/
269
void
270
OverlayLabeller::labelDisconnectedEdge(OverlayEdge* edge, uint8_t geomIndex)
271
5.88M
{
272
5.88M
    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
5.88M
    if (!inputGeometry->isArea(geomIndex)) {
281
5.77M
        label->setLocationAll(geomIndex, Location::EXTERIOR);
282
5.77M
        return;
283
5.77M
    }
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
102k
    Location edgeLoc = locateEdgeBothEnds(geomIndex, edge);
293
102k
    label->setLocationAll(geomIndex, edgeLoc);
294
102k
}
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
102k
{
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
102k
    Location locOrig = inputGeometry->locatePointInArea(geomIndex, edge->orig());
315
102k
    Location locDest = inputGeometry->locatePointInArea(geomIndex, edge->dest());
316
102k
    bool isInt = locOrig != Location::EXTERIOR && locDest != Location::EXTERIOR;
317
102k
    Location edgeLoc = isInt ? Location::INTERIOR : Location::EXTERIOR;
318
102k
    return edgeLoc;
319
102k
}
320
321
/*public*/
322
void
323
OverlayLabeller::markResultAreaEdges(int overlayOpCode)
324
200k
{
325
7.53M
    for (OverlayEdge* edge : edges) {
326
7.53M
        markInResultArea(edge, overlayOpCode);
327
7.53M
    }
328
200k
}
329
330
/*public*/
331
void
332
OverlayLabeller::markInResultArea(OverlayEdge* e, int overlayOpCode)
333
7.53M
{
334
7.53M
    const OverlayLabel* label = e->getLabel();
335
7.53M
    if (label->isBoundaryEither() && OverlayNG::isResultOfOp(overlayOpCode,
336
1.50M
        label->getLocationBoundaryOrLine(0, Position::RIGHT, e->isForward()),
337
1.50M
        label->getLocationBoundaryOrLine(1, Position::RIGHT, e->isForward())))
338
881k
    {
339
881k
        e->markInResultArea();
340
881k
    }
341
7.53M
}
342
343
/*public*/
344
void
345
OverlayLabeller::unmarkDuplicateEdgesFromResultArea()
346
200k
{
347
7.53M
    for (OverlayEdge* edge : edges) {
348
7.53M
        if (edge->isInResultAreaBoth()) {
349
144k
            edge->unmarkFromResultAreaBoth();
350
144k
        }
351
7.53M
    }
352
200k
}
353
354
355
356
357
358
359
} // namespace geos.operation.overlayng
360
} // namespace geos.operation
361
} // namespace geos