Coverage Report

Created: 2026-09-14 06:18

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/geos/src/operation/overlayng/OverlayNGRobust.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
 * Last port: operation/overlayng/OverlayNGRobust.java 6ef89b09
16
 *
17
 **********************************************************************/
18
19
#include <geos/operation/overlayng/OverlayNGRobust.h>
20
21
#include <geos/operation/overlayng/OverlayNG.h>
22
#include <geos/operation/overlayng/OverlayUtil.h>
23
#include <geos/operation/overlayng/PrecisionUtil.h>
24
#include <geos/operation/union/UnionStrategy.h>
25
#include <geos/operation/union/UnaryUnionOp.h>
26
#include <geos/noding/snap/SnappingNoder.h>
27
#include <geos/geom/Geometry.h>
28
#include <geos/geom/PrecisionModel.h>
29
#include <geos/util/TopologyException.h>
30
31
#include <stdexcept>
32
33
#ifndef GEOS_DEBUG
34
# define GEOS_DEBUG 0
35
#endif
36
37
namespace geos {      // geos
38
namespace operation { // geos.operation
39
namespace overlayng { // geos.operation.overlayng
40
41
using namespace geos::geom;
42
43
44
/*public static*/
45
std::unique_ptr<Geometry>
46
OverlayNGRobust::Intersection(const Geometry* g0, const Geometry* g1)
47
0
{
48
0
    return Overlay(g0, g1, OverlayNG::INTERSECTION);
49
0
}
50
51
/*public static*/
52
std::unique_ptr<Geometry>
53
OverlayNGRobust::Union(const Geometry* g0, const Geometry* g1)
54
0
{
55
0
    return Overlay(g0, g1, OverlayNG::UNION);
56
0
}
57
58
/*public static*/
59
std::unique_ptr<Geometry>
60
OverlayNGRobust::Difference(const Geometry* g0, const Geometry* g1)
61
0
{
62
0
    return Overlay(g0, g1, OverlayNG::DIFFERENCE);
63
0
}
64
65
/*public static*/
66
std::unique_ptr<Geometry>
67
OverlayNGRobust::SymDifference(const Geometry* g0, const Geometry* g1)
68
0
{
69
0
    return Overlay(g0, g1, OverlayNG::SYMDIFFERENCE);
70
0
}
71
72
/*public static*/
73
std::unique_ptr<Geometry>
74
OverlayNGRobust::Union(const Geometry* a)
75
55.6k
{
76
55.6k
    geounion::UnaryUnionOp op(*a);
77
55.6k
    SRUnionStrategy unionSRFun;
78
55.6k
    op.setUnionFunction(&unionSRFun);
79
55.6k
    geos::util::ProgressFunction* progressFunction = nullptr;
80
55.6k
    return op.Union(progressFunction);
81
55.6k
}
82
83
/*public static*/
84
std::unique_ptr<Geometry>
85
OverlayNGRobust::Overlay(const Geometry* geom0, const Geometry* geom1, int opCode)
86
158k
{
87
158k
    std::unique_ptr<Geometry> result;
88
158k
    std::runtime_error exOriginal("");
89
90
    /**
91
    * If input geometry has a non-floating precision model, just run
92
    * in snap-rounding mode with that precision.
93
    */
94
158k
    if (!geom0->getPrecisionModel()->isFloating()) {
95
#if GEOS_DEBUG
96
        std::cerr << "Using fixed precision overlay." << std::endl;
97
#endif
98
0
        return OverlayNG::overlay(geom0, geom1, opCode, geom0->getPrecisionModel());
99
0
    }
100
101
    /**
102
     * First try overlay with a FLOAT noder, which is fastest and causes least
103
     * change to geometry coordinates
104
     * By default the noder is validated, which is required in order
105
     * to detect certain invalid noding situations which otherwise
106
     * cause incorrect overlay output.
107
     */
108
158k
    try {
109
158k
        geom::PrecisionModel PM_FLOAT;
110
#if GEOS_DEBUG
111
        std::cerr << "Using floating point overlay." << std::endl;
112
#endif
113
158k
        result = OverlayNG::overlay(geom0, geom1, opCode, &PM_FLOAT);
114
115
        // Simple noding with no validation
116
        // There are cases where this succeeds with invalid noding (e.g. STMLF 1608).
117
        // So currently it is NOT safe to run overlay without noding validation
118
        //result = OverlayNG.overlay(geom0, geom1, opCode, createFloatingNoValidNoder());
119
        // std::cerr << "Floating point overlay success." << std::endl;
120
158k
        return result;
121
158k
    }
122
158k
    catch (const std::runtime_error &ex) {
123
35.2k
        if (geom0->hasCurvedComponents() || geom1->hasCurvedComponents()) {
124
            // Snapping strategies do not support curved geometries.
125
4.22k
            throw;
126
4.22k
        }
127
        /**
128
        * Capture original exception,
129
        * so it can be rethrown if the remaining strategies all fail.
130
        */
131
31.0k
        exOriginal = ex;
132
#if GEOS_DEBUG
133
        std::cerr << "Floating point overlay FAILURE: " << ex.what() << std::endl;
134
#endif
135
31.0k
    }
136
137
138
    /**
139
     * On failure retry using snapping noding with a "safe" tolerance.
140
     * if this throws an exception just let it go,
141
     * since it is something that is not a TopologyException
142
     */
143
31.0k
    result = overlaySnapTries(geom0, geom1, opCode);
144
31.0k
    if (result != nullptr)
145
10.6k
        return result;
146
147
    /**
148
     * On failure retry using snap-rounding with a heuristic scale factor (grid size).
149
     */
150
20.3k
    result = overlaySR(geom0, geom1, opCode);
151
20.3k
    if (result != nullptr)
152
2.21k
        return result;
153
154
    /**
155
     * Just can't get overlay to work, so throw original error.
156
     */
157
18.1k
    throw exOriginal;
158
20.3k
}
159
160
161
/*private static*/
162
std::unique_ptr<Geometry>
163
OverlayNGRobust::overlaySnapTries(const Geometry* geom0, const Geometry* geom1, int opCode)
164
31.0k
{
165
31.0k
    std::unique_ptr<Geometry> result;
166
31.0k
    double snapTol = snapTolerance(geom0, geom1);
167
168
99.5k
    for (std::size_t i = 0; i < NUM_SNAP_TRIES; i++) {
169
170
#if GEOS_DEBUG
171
        std::cerr << "Trying overlaySnapping(tol " << snapTol << ")." << std::endl;
172
#endif
173
174
79.2k
        result = overlaySnapping(geom0, geom1, opCode, snapTol);
175
79.2k
        if (result != nullptr) return result;
176
177
      /**
178
       * Now try snapping each input individually,
179
       * and then doing the overlay.
180
       */
181
70.4k
      result = overlaySnapBoth(geom0, geom1, opCode, snapTol);
182
70.4k
      if (result != nullptr) return result;
183
184
      // increase the snap tolerance and try again
185
68.5k
      snapTol = snapTol * 10;
186
68.5k
    }
187
    // failed to compute overlay
188
20.3k
    return nullptr;
189
31.0k
}
190
191
/*private static*/
192
std::unique_ptr<Geometry>
193
OverlayNGRobust::overlaySnapping(const Geometry* geom0, const Geometry* geom1, int opCode, double snapTol)
194
79.2k
{
195
79.2k
    try {
196
79.2k
        return overlaySnapTol(geom0, geom1, opCode, snapTol);
197
79.2k
    }
198
79.2k
    catch (const geos::util::TopologyException& ex)
199
79.2k
    {
200
61.0k
        ::geos::ignore_unused_variable_warning(ex);
201
        //---- ignore exception, return null result to indicate failure
202
#if GEOS_DEBUG
203
        std::cerr << "overlaySnapping(tol " << snapTol << ") FAILURE: " << ex.what() << std::endl;
204
#endif
205
61.0k
    }
206
61.0k
    return nullptr;
207
79.2k
}
208
209
/*private static*/
210
std::unique_ptr<Geometry>
211
OverlayNGRobust::overlaySnapBoth(const Geometry* geom0, const Geometry* geom1, int opCode, double snapTol)
212
61.0k
{
213
61.0k
    try {
214
61.0k
        std::unique_ptr<Geometry> snap0 = snapSelf(geom0, snapTol);
215
61.0k
        std::unique_ptr<Geometry> snap1 = snapSelf(geom1, snapTol);
216
61.0k
        return overlaySnapTol(snap0.get(), snap1.get(), opCode, snapTol);
217
61.0k
    }
218
61.0k
    catch (const geos::util::TopologyException& ex)
219
61.0k
    {
220
58.8k
        ::geos::ignore_unused_variable_warning(ex);
221
        //---- ignore this exception, just return a nullptr result to indicate failure
222
#if GEOS_DEBUG
223
        std::cerr << "overlaySnapBoth(tol " << snapTol << ") FAILURE: " << ex.what() << std::endl;
224
#endif
225
58.8k
    }
226
58.8k
    return nullptr;
227
61.0k
}
228
229
/*private static*/
230
std::unique_ptr<Geometry>
231
OverlayNGRobust::snapSelf(const Geometry* geom, double snapTol)
232
85.9k
{
233
85.9k
  OverlayNG ov(geom, nullptr);
234
85.9k
  noding::snap::SnappingNoder snapNoder(snapTol);
235
85.9k
  ov.setNoder(&snapNoder);
236
  /**
237
   * Ensure the result is not mixed-dimension,
238
   * since it will be used in further overlay computation.
239
   * It may however be lower dimension, if it collapses completely due to snapping.
240
   */
241
85.9k
  ov.setStrictMode(true);
242
85.9k
  return ov.getResult();
243
85.9k
}
244
245
/*private static*/
246
std::unique_ptr<Geometry>
247
OverlayNGRobust::overlaySnapTol(const Geometry* geom0, const Geometry* geom1, int opCode, double snapTol)
248
98.2k
{
249
98.2k
    noding::snap::SnappingNoder snapNoder(snapTol);
250
98.2k
    return OverlayNG::overlay(geom0, geom1, opCode, &snapNoder);
251
98.2k
}
252
253
/*public static*/
254
double
255
OverlayNGRobust::snapTolerance(const Geometry* geom0, const Geometry* geom1)
256
31.0k
{
257
31.0k
    double tol0 = snapTolerance(geom0);
258
31.0k
    double tol1 = snapTolerance(geom1);
259
31.0k
    double snapTol = std::max(tol0,  tol1);
260
31.0k
    return snapTol;
261
31.0k
}
262
263
/*private static*/
264
double
265
OverlayNGRobust::snapTolerance(const Geometry* geom)
266
62.0k
{
267
62.0k
    double magnitude = ordinateMagnitude(geom);
268
62.0k
    return magnitude / SNAP_TOL_FACTOR;
269
62.0k
}
270
271
272
/*private static*/
273
double
274
OverlayNGRobust::ordinateMagnitude(const Geometry* geom)
275
62.0k
{
276
62.0k
    if (geom == nullptr || geom->isEmpty()) return 0;
277
53.4k
    const Envelope* env = geom->getEnvelopeInternal();
278
53.4k
    double magMax = std::max(
279
53.4k
        std::abs(env->getMaxX()),
280
53.4k
        std::abs(env->getMaxY())
281
53.4k
        );
282
53.4k
    double magMin = std::max(
283
53.4k
        std::abs(env->getMinX()),
284
53.4k
        std::abs(env->getMinY())
285
53.4k
        );
286
53.4k
    return std::max(magMax, magMin);
287
62.0k
}
288
289
290
/*private static*/
291
std::unique_ptr<Geometry>
292
OverlayNGRobust::overlaySR(const Geometry* geom0, const Geometry* geom1, int opCode)
293
10.5k
{
294
10.5k
    std::unique_ptr<Geometry> result;
295
10.5k
    try {
296
10.5k
        double scaleSafe = PrecisionUtil::safeScale(geom0, geom1);
297
10.5k
        PrecisionModel pmSafe(scaleSafe);
298
10.5k
        result = OverlayNG::overlay(geom0, geom1, opCode, &pmSafe);
299
10.5k
        return result;
300
10.5k
    }
301
10.5k
    catch (const geos::util::TopologyException& ex)
302
10.5k
    {
303
5.73k
        ::geos::ignore_unused_variable_warning(ex);
304
        // ignore this exception, since the operation will be rerun
305
#if GEOS_DEBUG
306
        std::cerr << "OverlayNGRobust::overlaySR FAILURE: " << ex.what() << std::endl;
307
#endif
308
5.73k
    }
309
5.73k
    return nullptr;
310
10.5k
}
311
312
313
314
315
316
317
} // namespace geos.operation.overlayng
318
} // namespace geos.operation
319
} // namespace geos