/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 | | |