Coverage Report

Created: 2025-10-28 07:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/geos/src/operation/overlayng/OverlayNG.cpp
Line
Count
Source
1
/**********************************************************************
2
 *
3
 * GEOS - Geometry Engine Open Source
4
 * http://geos.osgeo.org
5
 *
6
 * Copyright (C) 2020 Sandro Santilli <strk@kbt.io>
7
 * Copyright (C) 2020 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
 * Last Port: operation/overlayng/OverlayNG.java 4c88fea52
17
 *
18
 **********************************************************************/
19
20
#include <geos/operation/overlayng/OverlayNG.h>
21
22
#include <geos/operation/overlayng/Edge.h>
23
#include <geos/operation/overlayng/EdgeNodingBuilder.h>
24
#include <geos/operation/overlayng/ElevationModel.h>
25
#include <geos/operation/overlayng/InputGeometry.h>
26
#include <geos/operation/overlayng/IntersectionPointBuilder.h>
27
#include <geos/operation/overlayng/LineBuilder.h>
28
#include <geos/operation/overlayng/OverlayEdge.h>
29
#include <geos/operation/overlayng/OverlayLabeller.h>
30
#include <geos/operation/overlayng/OverlayMixedPoints.h>
31
#include <geos/operation/overlayng/OverlayPoints.h>
32
#include <geos/operation/overlayng/OverlayUtil.h>
33
#include <geos/operation/overlayng/PolygonBuilder.h>
34
#include <geos/geom/CoordinateSequence.h>
35
#include <geos/geom/Envelope.h>
36
#include <geos/geom/Location.h>
37
#include <geos/geom/Geometry.h>
38
#include <geos/util/Interrupt.h>
39
#include <geos/util/TopologyException.h>
40
41
#include <algorithm>
42
43
#ifndef GEOS_DEBUG
44
#define GEOS_DEBUG 0
45
#endif
46
47
#if GEOS_DEBUG
48
# include <iostream>
49
# include <geos/io/WKTWriter.h>
50
#endif
51
52
53
54
namespace geos {      // geos
55
namespace operation { // geos.operation
56
namespace overlayng { // geos.operation.overlayng
57
58
using namespace geos::geom;
59
60
61
/*public static*/
62
bool
63
OverlayNG::isResultOfOpPoint(const OverlayLabel* label, int opCode)
64
0
{
65
0
    Location loc0 = label->getLocation(0);
66
0
    Location loc1 = label->getLocation(1);
67
0
    return isResultOfOp(opCode, loc0, loc1);
68
0
}
69
70
/*public static*/
71
bool
72
OverlayNG::isResultOfOp(int overlayOpCode, Location loc0, Location loc1)
73
6.05M
{
74
6.05M
    if (loc0 == Location::BOUNDARY) loc0 = Location::INTERIOR;
75
6.05M
    if (loc1 == Location::BOUNDARY) loc1 = Location::INTERIOR;
76
6.05M
    switch (overlayOpCode) {
77
662k
        case INTERSECTION:
78
662k
            return loc0 == Location::INTERIOR
79
574k
                && loc1 == Location::INTERIOR;
80
3.97M
        case UNION:
81
3.97M
            return loc0 == Location::INTERIOR
82
1.15M
                || loc1 == Location::INTERIOR;
83
1.42M
        case DIFFERENCE:
84
1.42M
            return loc0 == Location::INTERIOR
85
1.34M
                && loc1 != Location::INTERIOR;
86
0
        case SYMDIFFERENCE:
87
0
            return   (loc0 == Location::INTERIOR && loc1 != Location::INTERIOR)
88
0
                  || (loc0 != Location::INTERIOR && loc1 == Location::INTERIOR);
89
6.05M
    }
90
0
    return false;
91
6.05M
}
92
93
94
/*public static*/
95
std::unique_ptr<Geometry>
96
OverlayNG::overlay(const Geometry* geom0, const Geometry* geom1,
97
        int opCode, const PrecisionModel* pm)
98
244k
{
99
244k
    OverlayNG ov(geom0, geom1, pm, opCode);
100
244k
    return ov.getResult();
101
244k
}
102
103
/*public static*/
104
std::unique_ptr<Geometry>
105
OverlayNG::overlay(const Geometry* geom0, const Geometry* geom1,
106
        int opCode, const PrecisionModel* pm, noding::Noder* noder)
107
0
{
108
0
    OverlayNG ov(geom0, geom1, pm, opCode);
109
0
    ov.setNoder(noder);
110
0
    return ov.getResult();
111
0
}
112
113
/*public static*/
114
std::unique_ptr<Geometry>
115
OverlayNG::overlay(const Geometry* geom0, const Geometry* geom1,
116
        int opCode, noding::Noder* noder)
117
105k
{
118
105k
    OverlayNG ov(geom0, geom1, static_cast<PrecisionModel*>(nullptr), opCode);
119
105k
    ov.setNoder(noder);
120
105k
    return ov.getResult();
121
105k
}
122
123
/*public static*/
124
std::unique_ptr<Geometry>
125
OverlayNG::overlay(const Geometry* geom0, const Geometry* geom1, int opCode)
126
0
{
127
0
    OverlayNG ov(geom0, geom1, opCode);
128
0
    return ov.getResult();
129
0
}
130
131
/*public static*/
132
std::unique_ptr<Geometry>
133
OverlayNG::geomunion(const Geometry* geom, const PrecisionModel* pm)
134
17.4k
{
135
17.4k
    OverlayNG ov(geom, pm);
136
17.4k
    return ov.getResult();
137
17.4k
}
138
139
/*public static*/
140
std::unique_ptr<Geometry>
141
OverlayNG::geomunion(const Geometry* geom, const PrecisionModel* pm, noding::Noder* noder)
142
0
{
143
0
    OverlayNG ov(geom, pm);
144
0
    ov.setNoder(noder);
145
0
    ov.setStrictMode(true);
146
0
    return ov.getResult();
147
0
}
148
149
/*public*/
150
std::unique_ptr<Geometry>
151
OverlayNG::getResult()
152
467k
{
153
467k
    const Geometry *ig0 = inputGeom.getGeometry(0);
154
467k
    const Geometry *ig1 = inputGeom.getGeometry(1);
155
156
467k
    if ( OverlayUtil::isEmptyResult(opCode, ig0, ig1, pm) )
157
74.7k
    {
158
74.7k
        return createEmptyResult();
159
74.7k
    }
160
161
    /**
162
     * The elevation model is only computed if the input geometries have Z values.
163
     */
164
392k
    std::unique_ptr<ElevationModel> elevModel;
165
392k
    if ( ig1 ) {
166
278k
        elevModel = ElevationModel::create(*ig0, *ig1);
167
278k
    } else {
168
114k
        elevModel = ElevationModel::create(*ig0);
169
114k
    }
170
392k
    std::unique_ptr<Geometry> result;
171
172
392k
    if (inputGeom.isAllPoints()) {
173
        // handle Point-Point inputs
174
1.01k
        result = OverlayPoints::overlay(opCode, ig0, ig1, pm);
175
1.01k
    }
176
391k
    else if (! inputGeom.isSingle() &&  inputGeom.hasPoints()) {
177
        // handle Point-nonPoint inputs
178
43.3k
        result = OverlayMixedPoints::overlay(opCode, ig0, ig1, pm);
179
43.3k
    }
180
348k
    else {
181
        // handle case where both inputs are formed of edges (Lines and Polygons)
182
348k
        result = computeEdgeOverlay();
183
348k
    }
184
185
#if GEOS_DEBUG
186
    io::WKTWriter w;
187
188
    std::cerr << "Before populatingZ: " << w.write(result.get()) << std::endl;
189
#endif
190
191
    /**
192
     * This is a no-op if the elevation model was not computed due to
193
     * Z not present
194
     */
195
392k
    elevModel->populateZ(*result);
196
197
#if GEOS_DEBUG
198
    std::cerr << " After populatingZ: " << w.write(result.get()) << std::endl;
199
#endif
200
201
392k
    return result;
202
467k
}
203
204
205
/*private*/
206
std::unique_ptr<Geometry>
207
OverlayNG::computeEdgeOverlay()
208
348k
{
209
    /**
210
     * Node the edges, using whatever noder is being used
211
     * Formerly in nodeEdges())
212
     */
213
348k
    EdgeNodingBuilder nodingBuilder(pm, noder);
214
    // clipEnv not always used, but needs to remain in scope
215
    // as long as nodingBuilder when it is.
216
348k
    Envelope clipEnv;
217
218
348k
    GEOS_CHECK_FOR_INTERRUPTS();
219
220
348k
    if (isOptimized) {
221
348k
        bool gotClipEnv = OverlayUtil::clippingEnvelope(opCode, &inputGeom, pm, clipEnv);
222
348k
        if (gotClipEnv) {
223
32.1k
            nodingBuilder.setClipEnvelope(&clipEnv);
224
32.1k
        }
225
348k
    }
226
227
348k
    std::vector<Edge*> edges = nodingBuilder.build(
228
348k
        inputGeom.getGeometry(0),
229
348k
        inputGeom.getGeometry(1));
230
231
348k
    GEOS_CHECK_FOR_INTERRUPTS();
232
233
    /**
234
     * Record if an input geometry has collapsed.
235
     * This is used to avoid trying to locate disconnected edges
236
     * against a geometry which has collapsed completely.
237
     */
238
348k
    inputGeom.setCollapsed(0, ! nodingBuilder.hasEdgesFor(0));
239
348k
    inputGeom.setCollapsed(1, ! nodingBuilder.hasEdgesFor(1));
240
241
    /**
242
    * Inlined buildGraph() method here for memory purposes, so the
243
    * Edge* list allocated in the EdgeNodingBuilder survives
244
    * long enough to be copied into the OverlayGraph
245
    */
246
    // Sort the edges first, for comparison with JTS results
247
    // std::sort(edges.begin(), edges.end(), EdgeComparator);
248
348k
    OverlayGraph graph;
249
16.6M
    for (Edge* e : edges) {
250
        // Write out edge coordinates
251
        // std::cout << *e->getCoordinatesRO() << std::endl;
252
16.6M
        graph.addEdge(e);
253
16.6M
    }
254
255
348k
    if (isOutputNodedEdges) {
256
0
        return OverlayUtil::toLines(&graph, isOutputEdges, geomFact);
257
0
    }
258
259
348k
    GEOS_CHECK_FOR_INTERRUPTS();
260
348k
    labelGraph(&graph);
261
262
    // std::cout << std::endl << graph << std::endl;
263
264
348k
    if (isOutputEdges || isOutputResultEdges) {
265
0
        return OverlayUtil::toLines(&graph, isOutputEdges, geomFact);
266
0
    }
267
268
348k
    GEOS_CHECK_FOR_INTERRUPTS();
269
348k
    std::unique_ptr<Geometry> result = extractResult(opCode, &graph);
270
271
    /**
272
     * Heuristic check on result area.
273
     * Catches cases where noding causes vertex to move
274
     * and make topology graph area "invert".
275
     */
276
348k
    if (OverlayUtil::isFloating(pm)) {
277
210k
        bool isAreaConsistent = OverlayUtil::isResultAreaConsistent(
278
210k
            inputGeom.getGeometry(0),
279
210k
            inputGeom.getGeometry(1),
280
210k
            opCode, result.get());
281
210k
        if (! isAreaConsistent)
282
32.6k
            throw util::TopologyException("Result area inconsistent with overlay operation");
283
210k
    }
284
315k
    return result;
285
348k
}
286
287
/*private*/
288
void
289
OverlayNG::labelGraph(OverlayGraph* graph)
290
329k
{
291
329k
    OverlayLabeller labeller(graph, &inputGeom);
292
329k
    labeller.computeLabelling();
293
329k
    labeller.markResultAreaEdges(opCode);
294
329k
    labeller.unmarkDuplicateEdgesFromResultArea();
295
329k
}
296
297
298
/*private*/
299
std::unique_ptr<Geometry>
300
OverlayNG::extractResult(int p_opCode, OverlayGraph* graph)
301
236k
{
302
303
#if GEOS_DEBUG
304
    std::cerr << "OverlayNG::extractResult: graph: " << *graph << std::endl;
305
#endif
306
307
236k
    bool isAllowMixedIntResult = ! isStrictMode;
308
309
    //--- Build polygons
310
236k
    std::vector<OverlayEdge*> resultAreaEdges = graph->getResultAreaEdges();
311
236k
    PolygonBuilder polyBuilder(resultAreaEdges, geomFact);
312
236k
    std::vector<std::unique_ptr<Polygon>> resultPolyList = polyBuilder.getPolygons();
313
236k
    bool hasResultAreaComponents = (!resultPolyList.empty());
314
315
236k
    std::vector<std::unique_ptr<LineString>> resultLineList;
316
236k
    std::vector<std::unique_ptr<Point>> resultPointList;
317
318
236k
    GEOS_CHECK_FOR_INTERRUPTS();
319
236k
    if (!isAreaResultOnly) {
320
        //--- Build lines
321
212k
        bool allowResultLines = !hasResultAreaComponents ||
322
121k
                                isAllowMixedIntResult ||
323
21.5k
                                opCode == SYMDIFFERENCE ||
324
21.5k
                                opCode == UNION;
325
326
212k
        if (allowResultLines) {
327
212k
            LineBuilder lineBuilder(&inputGeom, graph, hasResultAreaComponents, p_opCode, geomFact);
328
212k
            lineBuilder.setStrictMode(isStrictMode);
329
212k
            resultLineList = lineBuilder.getLines();
330
212k
        }
331
        /**
332
         * Operations with point inputs are handled elsewhere.
333
         * Only an intersection op can produce point results
334
         * from non-point inputs.
335
         */
336
212k
        bool hasResultComponents = hasResultAreaComponents || (!resultLineList.empty());
337
212k
        bool allowResultPoints = ! hasResultComponents || isAllowMixedIntResult;
338
212k
        if (opCode == INTERSECTION && allowResultPoints) {
339
7.29k
            IntersectionPointBuilder pointBuilder(graph, geomFact);
340
7.29k
            pointBuilder.setStrictMode(isStrictMode);
341
7.29k
            resultPointList = pointBuilder.getPoints();
342
7.29k
        }
343
212k
    }
344
345
236k
    if (resultPolyList.empty() &&
346
91.0k
        resultLineList.empty() &&
347
25.8k
        resultPointList.empty())
348
25.0k
    {
349
25.0k
        return createEmptyResult();
350
25.0k
    }
351
352
211k
    std::unique_ptr<Geometry> resultGeom = OverlayUtil::createResultGeometry(resultPolyList, resultLineList, resultPointList, geomFact);
353
211k
    return resultGeom;
354
236k
}
355
356
/*private*/
357
std::unique_ptr<Geometry>
358
OverlayNG::createEmptyResult()
359
99.8k
{
360
99.8k
    uint8_t coordDim = OverlayUtil::resultCoordinateDimension(
361
99.8k
                            inputGeom.getCoordinateDimension(0), 
362
99.8k
                            inputGeom.getCoordinateDimension(1));
363
99.8k
    return OverlayUtil::createEmptyResult(
364
99.8k
                OverlayUtil::resultDimension(opCode,
365
99.8k
                    inputGeom.getDimension(0),
366
99.8k
                    inputGeom.getDimension(1)),
367
99.8k
                    coordDim,
368
99.8k
                    geomFact);
369
99.8k
}
370
371
372
373
374
} // namespace geos.operation.overlayng
375
} // namespace geos.operation
376
} // namespace geos