Coverage Report

Created: 2025-11-24 06:57

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/geos/src/operation/polygonize/EdgeRing.cpp
Line
Count
Source
1
/**********************************************************************
2
 *
3
 * GEOS - Geometry Engine Open Source
4
 * http://geos.osgeo.org
5
 *
6
 * Copyright (C) 2005-2006 Refractions Research Inc.
7
 * Copyright (C) 2001-2002 Vivid Solutions Inc.
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/polygonize/EdgeRing.java 0b3c7e3eb0d3e
17
 *
18
 **********************************************************************/
19
20
#include <geos/operation/polygonize/EdgeRing.h>
21
#include <geos/operation/polygonize/PolygonizeEdge.h>
22
#include <geos/planargraph/DirectedEdge.h>
23
#include <geos/geom/CoordinateSequence.h>
24
#include <geos/geom/LinearRing.h>
25
#include <geos/geom/Coordinate.h>
26
#include <geos/geom/Envelope.h>
27
#include <geos/geom/GeometryFactory.h>
28
#include <geos/algorithm/PointLocation.h>
29
#include <geos/algorithm/Orientation.h>
30
#include <geos/util/IllegalArgumentException.h>
31
#include <geos/util.h> // TODO: drop this, includes too much
32
#include <geos/algorithm/locate/IndexedPointInAreaLocator.h>
33
#include <geos/geom/Location.h>
34
35
#include <vector>
36
#include <cassert>
37
38
//#define DEBUG_ALLOC 1
39
//#define GEOS_PARANOIA_LEVEL 2
40
41
42
using namespace geos::planargraph;
43
using namespace geos::algorithm;
44
using namespace geos::geom;
45
46
namespace geos {
47
namespace operation { // geos.operation
48
namespace polygonize { // geos.operation.polygonize
49
50
/*public*/
51
EdgeRing*
52
EdgeRing::findEdgeRingContaining(const std::vector<EdgeRing*> & erList)
53
0
{
54
0
    const LinearRing* testRing = getRingInternal();
55
0
    if(! testRing) {
56
0
        return nullptr;
57
0
    }
58
0
    const Envelope* testEnv = testRing->getEnvelopeInternal();
59
0
    EdgeRing* minRing = nullptr;
60
0
    const Envelope* minRingEnv = nullptr;
61
62
0
    for(auto& tryEdgeRing : erList) {
63
0
        auto tryRing = tryEdgeRing->getRingInternal();
64
0
        auto tryShellEnv = tryRing->getEnvelopeInternal();
65
        // the hole envelope cannot equal the shell envelope
66
        // (also guards against testing rings against themselves)
67
0
        if (tryShellEnv->equals(testEnv)) {
68
0
            continue;
69
0
        }
70
        // hole must be contained in shell
71
0
        if (!tryShellEnv->contains(testEnv)) {
72
0
            continue;
73
0
        }
74
75
0
        auto tryCoords = tryRing->getCoordinatesRO();
76
0
        const Coordinate& testPt = ptNotInList(testRing->getCoordinatesRO(), tryCoords);
77
78
        // check if this new containing ring is smaller than the current minimum ring
79
0
        if(tryEdgeRing->isInRing(testPt)) {
80
0
            if(minRing == nullptr || minRingEnv->contains(tryShellEnv)) {
81
0
                minRing = tryEdgeRing;
82
0
                minRingEnv = minRing->getRingInternal()->getEnvelopeInternal();
83
0
            }
84
0
        }
85
0
    }
86
0
    return minRing;
87
0
}
88
89
std::vector<PolygonizeDirectedEdge*>
90
0
EdgeRing::findDirEdgesInRing(PolygonizeDirectedEdge* startDE) {
91
0
    auto de = startDE;
92
0
    std::vector<decltype(de)> edges;
93
94
0
    do {
95
0
        edges.push_back(de);
96
0
        de = de->getNext();
97
0
    } while (de != startDE);
98
99
0
    return edges;
100
0
}
101
102
/*public static*/
103
const Coordinate&
104
EdgeRing::ptNotInList(const CoordinateSequence* testPts,
105
                      const CoordinateSequence* pts)
106
11.3k
{
107
11.3k
    const std::size_t npts = testPts->getSize();
108
12.7k
    for(std::size_t i = 0; i < npts; ++i) {
109
12.6k
        const Coordinate& testPt = testPts->getAt(i);
110
12.6k
        if(!isInList(testPt, pts)) {
111
11.2k
            return testPt;
112
11.2k
        }
113
12.6k
    }
114
197
    return Coordinate::getNull();
115
11.3k
}
116
117
/*public static*/
118
bool
119
EdgeRing::isInList(const Coordinate& pt,
120
                   const CoordinateSequence* pts)
121
12.6k
{
122
12.6k
    const std::size_t npts = pts->getSize();
123
222k
    for(std::size_t i = 0; i < npts; ++i) {
124
211k
        if(pt == pts->getAt(i)) {
125
1.40k
            return true;
126
1.40k
        }
127
211k
    }
128
11.2k
    return false;
129
12.6k
}
130
131
/*public*/
132
EdgeRing::EdgeRing(const GeometryFactory* newFactory)
133
    :
134
0
    factory(newFactory),
135
0
    ring(nullptr),
136
0
    ringPts(nullptr),
137
0
    holes(nullptr),
138
0
    is_hole(false)
139
0
{
140
#ifdef DEBUG_ALLOC
141
    std::cerr << "[" << this << "] EdgeRing(factory)" << std::endl;
142
#endif // DEBUG_ALLOC
143
0
}
144
145
void
146
0
EdgeRing::build(PolygonizeDirectedEdge* startDE) {
147
0
    auto de = startDE;
148
0
    do {
149
0
        add(de);
150
0
        de->setRing(this);
151
0
        de = de->getNext();
152
0
    } while (de != startDE);
153
0
}
154
155
/*public*/
156
void
157
EdgeRing::add(const PolygonizeDirectedEdge* de)
158
0
{
159
0
    deList.push_back(de);
160
0
}
161
162
/*public*/
163
void
164
EdgeRing::computeHole()
165
0
{
166
0
    getRingInternal();
167
0
    is_hole = Orientation::isCCW(ring->getCoordinatesRO());
168
0
}
169
170
/*public*/
171
void
172
EdgeRing::addHole(LinearRing* hole)
173
0
{
174
0
    if(holes == nullptr) {
175
0
        holes.reset(new std::vector<std::unique_ptr<LinearRing>>());
176
0
    }
177
0
    holes->emplace_back(hole);
178
0
}
179
180
void
181
0
EdgeRing::addHole(EdgeRing* holeER) {
182
0
    holeER->setShell(this);
183
0
    auto hole = holeER->getRingOwnership(); // TODO is this right method?
184
0
    addHole(hole.release());
185
0
}
186
187
/*public*/
188
std::unique_ptr<Polygon>
189
EdgeRing::getPolygon()
190
0
{
191
0
    if (holes) {
192
0
        return factory->createPolygon(std::move(ring), std::move(*holes));
193
0
    } else {
194
0
        return factory->createPolygon(std::move(ring));
195
0
    }
196
0
}
197
198
/*public*/
199
bool
200
EdgeRing::isValid() const
201
0
{
202
0
    return is_valid;
203
0
}
204
205
void
206
EdgeRing::computeValid()
207
0
{
208
0
    getCoordinates();
209
0
    if (ringPts->size() <= 3) {
210
0
        is_valid = false;
211
0
        return;
212
0
    }
213
0
    getRingInternal();
214
0
    is_valid = ring->isValid();
215
0
}
216
217
/*private*/
218
const CoordinateSequence*
219
EdgeRing::getCoordinates()
220
0
{
221
0
    if(ringPts == nullptr) {
222
0
        ringPts = detail::make_unique<CoordinateSequence>(0u, 0u);
223
0
        for(const auto& de : deList) {
224
0
            auto edge = dynamic_cast<PolygonizeEdge*>(de->getEdge());
225
0
            addEdge(edge->getLine()->getCoordinatesRO(),
226
0
                    de->getEdgeDirection(), ringPts.get());
227
0
        }
228
0
    }
229
0
    return ringPts.get();
230
0
}
231
232
/*public*/
233
std::unique_ptr<LineString>
234
EdgeRing::getLineString()
235
0
{
236
0
    getCoordinates();
237
0
    return std::unique_ptr<LineString>(factory->createLineString(*ringPts));
238
0
}
239
240
/*public*/
241
LinearRing*
242
EdgeRing::getRingInternal()
243
0
{
244
0
    if(ring != nullptr) {
245
0
        return ring.get();
246
0
    }
247
248
0
    getCoordinates();
249
0
    try {
250
0
        ring = factory->createLinearRing(*ringPts);
251
0
    }
252
0
    catch(const geos::util::IllegalArgumentException& e) {
253
#if GEOS_DEBUG
254
        // FIXME: print also ringPts
255
        std::cerr << "EdgeRing::getRingInternal: "
256
                  << e.what()
257
                  << std::endl;
258
#endif
259
0
        ::geos::ignore_unused_variable_warning(e);
260
0
    }
261
0
    return ring.get();
262
0
}
263
264
/*public*/
265
std::unique_ptr<LinearRing>
266
EdgeRing::getRingOwnership()
267
0
{
268
0
    getRingInternal(); // active lazy generation
269
0
    return std::move(ring);
270
0
}
271
272
/*private*/
273
void
274
EdgeRing::addEdge(const CoordinateSequence* coords, bool isForward,
275
                  CoordinateSequence* coordList)
276
0
{
277
0
    const std::size_t npts = coords->getSize();
278
0
    if(isForward) {
279
0
        for(std::size_t i = 0; i < npts; ++i) {
280
0
            coordList->add(coords->getAt(i), false);
281
0
        }
282
0
    }
283
0
    else {
284
0
        for(std::size_t i = npts; i > 0; --i) {
285
0
            coordList->add(coords->getAt(i - 1), false);
286
0
        }
287
0
    }
288
0
}
289
290
EdgeRing*
291
0
EdgeRing::getOuterHole() const {
292
    // Only shells can have outer holes
293
0
    if (isHole()) {
294
0
        return nullptr;
295
0
    }
296
297
    // A shell is an outer shell if any edge is also in an outer hole.
298
    // A hole is an outer shell if it is not contained by a shell.
299
0
    for (auto& de : deList) {
300
0
        auto adjRing = (dynamic_cast<PolygonizeDirectedEdge*>(de->getSym()))->getRing();
301
0
        if (adjRing->isOuterHole()) {
302
0
            return adjRing;
303
0
        }
304
0
    }
305
306
0
    return nullptr;
307
0
}
308
309
void
310
0
EdgeRing::updateIncludedRecursive() {
311
0
    visitedByUpdateIncludedRecursive = true;
312
313
0
    if (isHole()) {
314
0
        return;
315
0
    }
316
317
0
    for (const auto& de : deList) {
318
0
        auto adjShell = (dynamic_cast<const PolygonizeDirectedEdge*>(de->getSym()))->getRing()->getShell();
319
320
0
        if (adjShell != nullptr) {
321
0
            if (!adjShell->isIncludedSet() && !adjShell->visitedByUpdateIncludedRecursive) {
322
0
                adjShell->updateIncludedRecursive();
323
0
            }
324
0
        }
325
0
    }
326
327
0
    for (const auto& de : deList) {
328
0
        auto adjShell = (dynamic_cast<const PolygonizeDirectedEdge*>(de->getSym()))->getRing()->getShell();
329
330
0
        if (adjShell != nullptr) {
331
0
            if (adjShell->isIncludedSet()) {
332
0
                setIncluded(!adjShell->isIncluded());
333
0
                return;
334
0
            }
335
0
        }
336
0
    }
337
338
0
}
339
340
} // namespace geos.operation.polygonize
341
} // namespace geos.operation
342
} // namespace geos