Coverage Report

Created: 2025-07-23 06:51

/src/geos/src/operation/buffer/PolygonBuilder.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) 2001-2002 Vivid Solutions Inc.
7
 * Copyright (C) 2005 Refractions Research 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/overlay/PolygonBuilder.java rev. 1.20 (JTS-1.10)
17
 *
18
 **********************************************************************/
19
20
#include <geos/operation/buffer/PolygonBuilder.h>
21
#include <geos/operation/buffer/MaximalEdgeRing.h>
22
#include <geos/operation/buffer/MinimalEdgeRing.h>
23
#include <geos/operation/polygonize/EdgeRing.h>
24
#include <geos/geomgraph/Node.h>
25
#include <geos/geomgraph/NodeMap.h>
26
#include <geos/geomgraph/DirectedEdgeStar.h>
27
#include <geos/geomgraph/PlanarGraph.h>
28
#include <geos/geom/GeometryFactory.h>
29
#include <geos/geom/LinearRing.h>
30
#include <geos/geom/Polygon.h>
31
#include <geos/algorithm/PointLocation.h>
32
#include <geos/util/TopologyException.h>
33
#include <geos/util/GEOSException.h>
34
#include <geos/util.h>
35
36
37
#include <vector>
38
#include <cassert>
39
40
#ifndef GEOS_DEBUG
41
#define GEOS_DEBUG 0
42
#endif
43
44
#if GEOS_DEBUG
45
#include <iostream>
46
#endif
47
48
using namespace geos::geomgraph;
49
using namespace geos::algorithm;
50
using namespace geos::geom;
51
52
namespace geos {
53
namespace operation { // geos.operation
54
namespace buffer { // geos.operation.buffer
55
56
PolygonBuilder::PolygonBuilder(const GeometryFactory* newGeometryFactory)
57
    :
58
0
    geometryFactory(newGeometryFactory)
59
0
{
60
0
}
61
62
PolygonBuilder::~PolygonBuilder()
63
0
{
64
0
    for(std::size_t i = 0, n = shellList.size(); i < n; ++i) {
65
0
        delete shellList[i];
66
0
    }
67
0
}
68
69
/*public*/
70
void
71
PolygonBuilder::add(PlanarGraph* graph)
72
//throw(TopologyException *)
73
0
{
74
0
    const std::vector<EdgeEnd*>* eeptr = graph->getEdgeEnds();
75
0
    assert(eeptr);
76
0
    const std::vector<EdgeEnd*>& ee = *eeptr;
77
78
0
    std::size_t eeSize = ee.size();
79
80
#if GEOS_DEBUG
81
    std::cerr << __FUNCTION__ << ": PlanarGraph has " << eeSize << " EdgeEnds" << std::endl;
82
#endif
83
84
0
    std::vector<DirectedEdge*> dirEdges(eeSize);
85
0
    for(std::size_t i = 0; i < eeSize; ++i) {
86
0
        DirectedEdge* de = detail::down_cast<DirectedEdge*>(ee[i]);
87
0
        dirEdges[i] = de;
88
0
    }
89
90
0
    const auto& nodeMap = graph->getNodeMap()->nodeMap;
91
0
    std::vector<Node*> nodes;
92
0
    nodes.reserve(nodeMap.size());
93
0
    for(const auto& nodeIt: nodeMap) {
94
0
        Node* node = nodeIt.second.get();
95
0
        nodes.push_back(node);
96
0
    }
97
98
0
    add(&dirEdges, &nodes); // might throw a TopologyException *
99
0
}
100
101
/*public*/
102
void
103
PolygonBuilder::add(const std::vector<DirectedEdge*>* dirEdges,
104
                    const std::vector<Node*>* nodes)
105
//throw(TopologyException *)
106
0
{
107
0
    PlanarGraph::linkResultDirectedEdges(nodes->begin(), nodes->end());
108
109
0
    std::vector<MaximalEdgeRing*> maxEdgeRings;
110
0
    buildMaximalEdgeRings(dirEdges, maxEdgeRings);
111
112
0
    std::vector<EdgeRing*> freeHoleList;
113
0
    std::vector<MaximalEdgeRing*> edgeRings;
114
0
    buildMinimalEdgeRings(maxEdgeRings, shellList, freeHoleList, edgeRings);
115
116
0
    sortShellsAndHoles(edgeRings, shellList, freeHoleList);
117
118
0
    std::vector<FastPIPRing> indexedshellist;
119
0
    for(auto const& shell : shellList) {
120
0
        FastPIPRing pipRing { shell, new geos::algorithm::locate::IndexedPointInAreaLocator(*shell->getLinearRing()) };
121
0
        indexedshellist.push_back(pipRing);
122
0
    }
123
0
    placeFreeHoles(indexedshellist, freeHoleList);
124
    //Assert: every hole on freeHoleList has a shell assigned to it
125
126
0
    for(auto const& shell : indexedshellist) {
127
0
        delete shell.pipLocator;
128
0
    }
129
0
}
130
131
/*public*/
132
std::vector<std::unique_ptr<Geometry>>
133
PolygonBuilder::getPolygons()
134
0
{
135
0
    std::vector<std::unique_ptr<Geometry>> resultPolyList = computePolygons(shellList);
136
0
    return resultPolyList;
137
0
}
138
139
140
/*private*/
141
void
142
PolygonBuilder::buildMaximalEdgeRings(const std::vector<DirectedEdge*>* dirEdges,
143
                                      std::vector<MaximalEdgeRing*>& maxEdgeRings)
144
// throw(const TopologyException &)
145
0
{
146
#if GEOS_DEBUG
147
    std::cerr << "PolygonBuilder::buildMaximalEdgeRings got " << dirEdges->size() << " dirEdges" << std::endl;
148
#endif
149
150
0
    std::vector<MaximalEdgeRing*>::size_type oldSize = maxEdgeRings.size();
151
152
0
    for(std::size_t i = 0, n = dirEdges->size(); i < n; i++) {
153
0
        DirectedEdge* de = (*dirEdges)[i];
154
#if GEOS_DEBUG
155
        std::cerr << "  dirEdge " << i << std::endl
156
             << de->printEdge() << std::endl
157
             << " inResult:" << de->isInResult() << std::endl
158
             << " isArea:" << de->getLabel().isArea() << std::endl;
159
#endif
160
0
        if(de->isInResult() && de->getLabel().isArea()) {
161
            // if this edge has not yet been processed
162
0
            if(de->getEdgeRing() == nullptr) {
163
0
                MaximalEdgeRing* er;
164
0
                try {
165
                    // MaximalEdgeRing constructor may throw
166
0
                    er = new MaximalEdgeRing(de, geometryFactory);
167
0
                }
168
0
                catch(util::GEOSException&) {
169
                    // cleanup if that happens (see stmlf-cases-20061020.xml)
170
0
                    for(std::size_t p_i = oldSize, p_n = maxEdgeRings.size(); p_i < p_n; p_i++) {
171
0
                        delete maxEdgeRings[p_i];
172
0
                    }
173
                    //cerr << "Exception! " << e.what() << std::endl;
174
0
                    throw;
175
0
                }
176
0
                maxEdgeRings.push_back(er);
177
0
                er->setInResult();
178
                //System.out.println("max node degree=" + er.getMaxDegree());
179
0
            }
180
0
        }
181
0
    }
182
#if GEOS_DEBUG
183
    std::cerr << "  pushed " << maxEdgeRings.size() - oldSize << " maxEdgeRings" << std::endl;
184
#endif
185
0
}
186
187
/*private*/
188
void
189
PolygonBuilder::buildMinimalEdgeRings(
190
    std::vector<MaximalEdgeRing*>& maxEdgeRings,
191
    std::vector<EdgeRing*>& newShellList, std::vector<EdgeRing*>& freeHoleList,
192
    std::vector<MaximalEdgeRing*>& edgeRings)
193
0
{
194
0
    for(std::size_t i = 0, n = maxEdgeRings.size(); i < n; ++i) {
195
0
        MaximalEdgeRing* er = maxEdgeRings[i];
196
#if GEOS_DEBUG
197
        std::cerr << "buildMinimalEdgeRings: maxEdgeRing " << i << " has " << er->getMaxNodeDegree() << " maxNodeDegree" << std::endl;
198
#endif
199
0
        if(er->getMaxNodeDegree() > 2) {
200
0
            er->linkDirectedEdgesForMinimalEdgeRings();
201
0
            std::vector<MinimalEdgeRing*> minEdgeRings;
202
0
            er->buildMinimalRings(minEdgeRings);
203
            // at this point we can go ahead and attempt to place
204
            // holes, if this EdgeRing is a polygon
205
0
            EdgeRing* shell = findShell(&minEdgeRings);
206
0
            if(shell != nullptr) {
207
0
                placePolygonHoles(shell, &minEdgeRings);
208
0
                newShellList.push_back(shell);
209
0
            }
210
0
            else {
211
0
                freeHoleList.insert(freeHoleList.end(),
212
0
                                    minEdgeRings.begin(),
213
0
                                    minEdgeRings.end());
214
0
            }
215
0
            delete er;
216
0
        }
217
0
        else {
218
0
            edgeRings.push_back(er);
219
0
        }
220
0
    }
221
0
}
222
223
/*private*/
224
EdgeRing*
225
PolygonBuilder::findShell(std::vector<MinimalEdgeRing*>* minEdgeRings)
226
0
{
227
0
    int shellCount = 0;
228
0
    EdgeRing* shell = nullptr;
229
230
#if GEOS_DEBUG
231
    std::cerr << "PolygonBuilder::findShell got " << minEdgeRings->size() << " minEdgeRings" << std::endl;
232
#endif
233
234
0
    for(std::size_t i = 0, n = minEdgeRings->size(); i < n; ++i) {
235
0
        EdgeRing* er = (*minEdgeRings)[i];
236
0
        if(! er->isHole()) {
237
0
            shell = er;
238
0
            ++shellCount;
239
0
        }
240
0
    }
241
242
0
    if(shellCount > 1) {
243
0
        throw util::TopologyException("found two shells in MinimalEdgeRing list");
244
0
    }
245
246
0
    return shell;
247
0
}
248
249
/*private*/
250
void
251
PolygonBuilder::placePolygonHoles(EdgeRing* shell,
252
                                  std::vector<MinimalEdgeRing*>* minEdgeRings)
253
0
{
254
0
    for(std::size_t i = 0, n = minEdgeRings->size(); i < n; ++i) {
255
0
        MinimalEdgeRing* er = (*minEdgeRings)[i];
256
0
        if(er->isHole()) {
257
0
            er->setShell(shell);
258
0
        }
259
0
    }
260
0
}
261
262
/*private*/
263
void
264
PolygonBuilder::sortShellsAndHoles(std::vector<MaximalEdgeRing*>& edgeRings,
265
                                   std::vector<EdgeRing*>& newShellList, std::vector<EdgeRing*>& freeHoleList)
266
0
{
267
0
    for(std::size_t i = 0, n = edgeRings.size(); i < n; i++) {
268
0
        EdgeRing* er = edgeRings[i];
269
        //er->setInResult();
270
0
        if(er->isHole()) {
271
0
            freeHoleList.push_back(er);
272
0
        }
273
0
        else {
274
0
            newShellList.push_back(er);
275
0
        }
276
0
    }
277
0
}
278
279
/*private*/
280
void
281
PolygonBuilder::placeFreeHoles(std::vector<FastPIPRing>& newShellList,
282
                               std::vector<EdgeRing*>& freeHoleList)
283
0
{
284
0
    for(std::vector<EdgeRing*>::iterator
285
0
            it = freeHoleList.begin(), itEnd = freeHoleList.end();
286
0
            it != itEnd;
287
0
            ++it) {
288
0
        EdgeRing* hole = *it;
289
        // only place this hole if it doesn't yet have a shell
290
0
        if(hole->getShell() == nullptr) {
291
0
            EdgeRing* shell = findEdgeRingContaining(hole, newShellList);
292
            /**
293
             * If hole lies outside shell, discard it.
294
             */
295
0
            if(shell != nullptr) {
296
0
                hole->setShell(shell);
297
0
            }
298
0
            else {
299
0
                delete hole;
300
0
            }
301
302
0
        }
303
0
    }
304
0
}
305
306
/*private*/
307
EdgeRing*
308
PolygonBuilder::findEdgeRingContaining(EdgeRing* testEr,
309
                                       std::vector<FastPIPRing>& newShellList)
310
0
{
311
0
    LinearRing* testRing = testEr->getLinearRing();
312
0
    const Envelope* testEnv = testRing->getEnvelopeInternal();
313
0
    EdgeRing* minShell = nullptr;
314
0
    const Envelope* minShellEnv = nullptr;
315
316
0
    for(auto const& tryShell : newShellList) {
317
0
        LinearRing* tryShellRing = tryShell.edgeRing->getLinearRing();
318
0
        const Envelope* tryShellEnv = tryShellRing->getEnvelopeInternal();
319
        // the hole envelope cannot equal the shell envelope
320
        // (also guards against testing rings against themselves)
321
0
        if(tryShellEnv->equals(testEnv)) {
322
0
            continue;
323
0
        }
324
        // hole must be contained in shell
325
0
        if(!tryShellEnv->contains(testEnv)) {
326
0
            continue;
327
0
        }
328
329
0
        const CoordinateSequence* tsrcs = tryShellRing->getCoordinatesRO();
330
0
        const Coordinate& testPt = operation::polygonize::EdgeRing::ptNotInList(testRing->getCoordinatesRO(), tsrcs);
331
332
0
        bool isContained = false;
333
0
        if(tryShell.pipLocator->locate(&testPt) != Location::EXTERIOR) {
334
0
            isContained = true;
335
0
        }
336
337
        // check if this new containing ring is smaller than
338
        // the current minimum ring
339
0
        if(isContained) {
340
0
            if(minShell == nullptr
341
0
                    || minShellEnv->contains(tryShellEnv)) {
342
0
                minShell = tryShell.edgeRing;
343
0
                minShellEnv = minShell->getLinearRing()->getEnvelopeInternal();
344
0
            }
345
0
        }
346
0
    }
347
0
    return minShell;
348
0
}
349
350
/*private*/
351
std::vector<std::unique_ptr<Geometry>>
352
PolygonBuilder::computePolygons(std::vector<EdgeRing*>& newShellList)
353
0
{
354
#if GEOS_DEBUG
355
    std::cerr << "PolygonBuilder::computePolygons: got " << newShellList.size() << " shells" << std::endl;
356
#endif
357
0
    std::vector<std::unique_ptr<Geometry>> resultPolyList;
358
359
    // add Polygons for all shells
360
0
    for(std::size_t i = 0, n = newShellList.size(); i < n; i++) {
361
0
        EdgeRing* er = newShellList[i];
362
0
        auto poly = er->toPolygon(geometryFactory);
363
0
        resultPolyList.push_back(std::move(poly));
364
0
    }
365
0
    return resultPolyList;
366
0
}
367
368
369
} // namespace geos.operation.buffer
370
} // namespace geos.operation
371
} // namespace geos
372