Coverage Report

Created: 2026-01-17 06:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/geos/src/dissolve/LineDissolver.cpp
Line
Count
Source
1
/**********************************************************************
2
 *
3
 * GEOS - Geometry Engine Open Source
4
 * http://geos.osgeo.org
5
 *
6
 * Copyright (c) 2025 Martin Davis
7
 * Copyright (C) 2025 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
#include <geos/dissolve/LineDissolver.h>
17
18
#include <geos/dissolve/DissolveHalfEdge.h>
19
#include <geos/edgegraph/HalfEdge.h>
20
#include <geos/edgegraph/MarkHalfEdge.h>
21
#include <geos/geom/Coordinate.h>
22
#include <geos/geom/CoordinateList.h>
23
#include <geos/geom/CoordinateSequence.h>
24
#include <geos/geom/Geometry.h>
25
#include <geos/geom/GeometryComponentFilter.h>
26
#include <geos/geom/GeometryFactory.h>
27
#include <geos/geom/LineString.h>
28
29
30
using namespace geos::geom;
31
using geos::edgegraph::HalfEdge;
32
using geos::edgegraph::MarkHalfEdge;
33
34
35
namespace geos {      // geos
36
namespace dissolve {  // geos.dissolve
37
38
39
/* public */
40
std::unique_ptr<Geometry>
41
LineDissolver::dissolve(const Geometry* g)
42
0
{
43
0
    LineDissolver d;
44
0
    d.add(g);
45
0
    return d.getResult();
46
0
}
47
48
49
/* public */
50
void
51
LineDissolver::add(const Geometry* geom)
52
0
{
53
0
    if (factory == nullptr) {
54
0
        factory = geom->getFactory();
55
0
    }
56
57
0
    struct LineStringFilter : public GeometryComponentFilter {
58
59
0
        LineDissolver *m_ld;
60
61
0
        LineStringFilter(LineDissolver* ld) : m_ld(ld) {};
62
63
0
        void filter_ro(const Geometry* g) override {
64
0
            GeometryTypeId type = g->getGeometryTypeId();
65
0
            if (type == GEOS_LINEARRING || type == GEOS_LINESTRING) {
66
0
                m_ld->add(static_cast<const LineString*>(g));
67
0
            }
68
0
        }
69
0
    };
70
71
0
    LineStringFilter filter(this);
72
0
    geom->apply_ro(&filter);
73
0
}
74
75
76
/* public */
77
void
78
LineDissolver::add(std::vector<const Geometry*> geometries)
79
0
{
80
0
    for (const Geometry* geom : geometries) {
81
0
        add(geom);
82
0
    }
83
0
}
84
85
86
/* private */
87
void
88
LineDissolver::add(const LineString* lineString)
89
0
{
90
0
    const CoordinateSequence* seq = lineString->getCoordinatesRO();
91
0
    constructZ |= seq->hasZ();
92
0
    constructM |= seq->hasM();
93
94
0
    bool doneStart = false;
95
0
    for (std::size_t i = 1; i < seq->size(); i++) {
96
0
        CoordinateXYZM orig, dest;
97
0
        seq->getAt(i-1, orig);
98
0
        seq->getAt(i,   dest);
99
0
        DissolveHalfEdge* e = static_cast<DissolveHalfEdge*>(graph.addEdge(orig, dest));
100
        // skip zero-length edges
101
0
        if (e == nullptr) continue;
102
        /**
103
         * Record source initial segments, so that they can be reflected in output when needed
104
         * (i.e. during formation of isolated rings)
105
         */
106
0
        if (! doneStart) {
107
0
            e->setStart();
108
0
            doneStart = true;
109
0
        }
110
0
    }
111
0
}
112
113
114
/* public */
115
std::unique_ptr<Geometry>
116
LineDissolver::getResult()
117
0
{
118
0
    if (result == nullptr)
119
0
        computeResult();
120
0
    return std::move(result);
121
0
}
122
123
124
/* private */
125
void
126
LineDissolver::computeResult()
127
0
{
128
0
    std::vector<const HalfEdge*> edges;
129
0
    graph.getVertexEdges(edges);
130
131
0
    for (const HalfEdge* ce : edges) {
132
0
        HalfEdge* e = const_cast<HalfEdge*>(ce);
133
0
        if (MarkHalfEdge::isMarked(e)) continue;
134
0
        process(e);
135
0
    }
136
137
0
    result = factory->buildGeometry(std::move(lines));
138
0
}
139
140
141
/* private */
142
void
143
LineDissolver::process(HalfEdge* e)
144
0
{
145
0
    HalfEdge* eNode = e->prevNode();
146
    // if edge is in a ring, just process this edge
147
0
    if (eNode == nullptr)
148
0
        eNode = e;
149
0
    stackEdges(eNode);
150
    // extract lines from node edges in stack
151
0
    buildLines();
152
0
}
153
154
155
/* private */
156
void
157
LineDissolver::stackEdges(HalfEdge* node)
158
0
{
159
0
    HalfEdge* e = node;
160
0
    do {
161
0
        if (! MarkHalfEdge::isMarked(e))
162
0
            nodeEdgeStack.push(e);
163
0
        e = e->oNext();
164
0
    } while (e != node);
165
0
}
166
167
168
/* private */
169
void
170
LineDissolver::buildLines()
171
0
{
172
0
    while (! nodeEdgeStack.empty()) {
173
0
        HalfEdge* e = nodeEdgeStack.top();
174
0
        nodeEdgeStack.pop();
175
0
        if (MarkHalfEdge::isMarked(e))
176
0
            continue;
177
0
        buildLine(e);
178
0
    }
179
0
}
180
181
182
/* private */
183
void
184
LineDissolver::updateRingStartEdge(DissolveHalfEdge* e)
185
0
{
186
0
    if (! e->isStart()) {
187
0
        e = static_cast<DissolveHalfEdge*>(e->sym());
188
0
        if (! e->isStart()) return;
189
0
    }
190
    // here e is known to be a start edge
191
0
    if (ringStartEdge == nullptr) {
192
0
        ringStartEdge = e;
193
0
        return;
194
0
    }
195
0
    if (e->orig().compareTo(ringStartEdge->orig()) < 0) {
196
0
        ringStartEdge = e;
197
0
    }
198
0
}
199
200
201
/* private */
202
void
203
LineDissolver::buildLine(HalfEdge* eStart)
204
0
{
205
0
    auto line = std::make_shared<CoordinateSequence>(0, constructZ, constructM);
206
0
    DissolveHalfEdge* e = static_cast<DissolveHalfEdge*>(eStart);
207
0
    ringStartEdge = nullptr;
208
209
0
    MarkHalfEdge::markBoth(e);
210
0
    line->add(e->orig(), false);
211
212
    // scan along the path until a node is found (if one exists)
213
0
    while (e->sym()->degree() == 2) {
214
0
        updateRingStartEdge(e);
215
0
        DissolveHalfEdge* eNext = static_cast<DissolveHalfEdge*>(e->next());
216
        // check if edges form a ring - if so, we're done
217
0
        if (eNext == eStart)  {
218
0
            buildRing(ringStartEdge);
219
0
            return;
220
0
        }
221
222
        // add point to line, and move to next edge
223
0
        line->add(eNext->orig(), false);
224
225
0
        e = eNext;
226
0
        MarkHalfEdge::markBoth(e);
227
0
    }
228
229
    // add final node
230
0
    line->add(e->dest(), false);
231
232
    // queue up the final node edges
233
0
    stackEdges(e->sym());
234
    // store the scanned line
235
0
    addLine(line);
236
0
}
237
238
239
/* private */
240
void
241
LineDissolver::buildRing(HalfEdge* eStartRing)
242
0
{
243
0
    auto line = std::make_shared<CoordinateSequence>(0, constructZ, constructM);
244
0
    HalfEdge* e = eStartRing;
245
246
    // add first node
247
0
    line->add(e->orig(), false);
248
249
    // scan along the path until a node is found (if one exists)
250
0
    while (e->sym()->degree() == 2) {
251
0
        HalfEdge* eNext = e->next();
252
        // check if edges form a ring - if so, we're done
253
0
        if (eNext == eStartRing)
254
0
            break;
255
256
        // add point to line, and move to next edge
257
0
        line->add(eNext->orig(), false);
258
0
        e = eNext;
259
0
    }
260
    // add final node
261
0
    line->add(e->dest(), false);
262
263
    // store the scanned line
264
0
    addLine(line);
265
0
}
266
267
268
/* private */
269
void
270
LineDissolver::addLine(const std::shared_ptr<CoordinateSequence>& cs)
271
0
{
272
0
    auto ls = factory->createLineString(cs);
273
0
    lines.emplace_back(ls.release());
274
0
}
275
276
277
} // namespace geos.dissolve
278
} // namespace geos
279
280