Skip to content

Commit 2a52b6c

Browse files
committed
Move multiline generation into geom.cpp and fix bug
Relations consisting of exactly two ways connected end-to-end where one way needs to be turned around to form a real ring were not built correctly. This fixes this problem and adds a lot of tests.
1 parent a986fb9 commit 2a52b6c

File tree

4 files changed

+485
-158
lines changed

4 files changed

+485
-158
lines changed

src/geom.cpp

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99

1010
#include "geom.hpp"
1111

12+
#include "osmtypes.hpp"
13+
14+
#include <osmium/osm/way.hpp>
15+
1216
#include <algorithm>
1317
#include <iterator>
1418

@@ -125,5 +129,183 @@ void make_line(osmium::NodeRefList const &nodes, reprojection const &proj,
125129
}
126130
}
127131

132+
void make_line(linestring_t const &line, double split_at,
133+
std::vector<linestring_t> *out)
134+
{
135+
if (line.empty()) {
136+
return;
137+
}
138+
139+
if (split_at > 0.0) {
140+
split_linestring(line, split_at, out);
141+
} else {
142+
out->emplace_back(std::move(line));
143+
}
144+
}
145+
146+
void make_multiline(osmium::memory::Buffer const &ways, double split_at,
147+
reprojection const &proj, std::vector<linestring_t> *out)
148+
149+
{
150+
// make a list of all endpoints
151+
struct endpoint_t {
152+
osmid_t id;
153+
std::size_t n;
154+
bool is_front;
155+
156+
endpoint_t(osmid_t ref, std::size_t size, bool front) noexcept
157+
: id(ref), n(size), is_front(front)
158+
{}
159+
160+
bool operator==(endpoint_t const &rhs) const noexcept
161+
{
162+
return id == rhs.id;
163+
}
164+
165+
bool operator<(endpoint_t const &rhs) const noexcept
166+
{
167+
return std::tuple<osmid_t, std::size_t, bool>(id, n, is_front) <
168+
std::tuple<osmid_t, std::size_t, bool>(rhs.id, rhs.n,
169+
rhs.is_front);
170+
}
171+
};
172+
173+
std::vector<endpoint_t> endpoints;
174+
175+
// and a list of way connections
176+
enum lmt : size_t
177+
{
178+
NOCONN = -1UL
179+
};
180+
181+
struct connection_t {
182+
std::size_t left = NOCONN;
183+
osmium::Way const *way;
184+
std::size_t right = NOCONN;
185+
186+
explicit connection_t(osmium::Way const *w) noexcept : way(w) {}
187+
};
188+
189+
std::vector<connection_t> conns;
190+
191+
// initialise the two lists
192+
for (auto const &w : ways.select<osmium::Way>()) {
193+
if (w.nodes().size() > 1) {
194+
endpoints.emplace_back(w.nodes().front().ref(), conns.size(), true);
195+
endpoints.emplace_back(w.nodes().back().ref(), conns.size(), false);
196+
conns.emplace_back(&w);
197+
}
198+
}
199+
200+
// sort by node id
201+
std::sort(endpoints.begin(), endpoints.end());
202+
203+
// now fill the connection list based on the sorted list
204+
for (auto it = std::adjacent_find(endpoints.cbegin(), endpoints.cend());
205+
it != endpoints.cend();
206+
it = std::adjacent_find(it + 2, endpoints.cend())) {
207+
auto const previd = it->n;
208+
auto const ptid = std::next(it)->n;
209+
if (it->is_front) {
210+
conns[previd].left = ptid;
211+
} else {
212+
conns[previd].right = ptid;
213+
}
214+
if (std::next(it)->is_front) {
215+
conns[ptid].left = previd;
216+
} else {
217+
conns[ptid].right = previd;
218+
}
219+
}
220+
221+
// First find all open ends and use them as starting points to assemble
222+
// linestrings. Mark ways as "done" as we go.
223+
std::size_t done_ways = 0;
224+
std::size_t const todo_ways = conns.size();
225+
for (std::size_t i = 0; i < todo_ways; ++i) {
226+
if (!conns[i].way ||
227+
(conns[i].left != NOCONN && conns[i].right != NOCONN)) {
228+
continue; // way already done or not the beginning of a segment
229+
}
230+
231+
linestring_t linestring;
232+
{
233+
std::size_t prev = NOCONN;
234+
std::size_t cur = i;
235+
236+
do {
237+
auto &conn = conns[cur];
238+
assert(conn.way);
239+
auto const &nl = conn.way->nodes();
240+
bool const forward = conn.left == prev;
241+
prev = cur;
242+
// add way nodes
243+
if (forward) {
244+
add_nodes_to_linestring(linestring, proj, nl.cbegin(),
245+
nl.cend());
246+
cur = conn.right;
247+
} else {
248+
add_nodes_to_linestring(linestring, proj, nl.crbegin(),
249+
nl.crend());
250+
cur = conn.left;
251+
}
252+
// mark way as done
253+
conns[prev].way = nullptr;
254+
++done_ways;
255+
} while (cur != NOCONN);
256+
}
257+
258+
// found a line end, create the wkbs
259+
make_line(linestring, split_at, out);
260+
}
261+
262+
// If all ways have been "done", i.e. are part of a linestring now, we
263+
// are finished.
264+
if (done_ways >= todo_ways) {
265+
return;
266+
}
267+
268+
// oh dear, there must be circular ways without an end
269+
// need to do the same shebang again
270+
for (size_t i = 0; i < todo_ways; ++i) {
271+
if (!conns[i].way) {
272+
continue; // way already done
273+
}
274+
275+
linestring_t linestring;
276+
{
277+
size_t prev = conns[i].left;
278+
size_t cur = i;
279+
280+
do {
281+
auto &conn = conns[cur];
282+
assert(conn.way);
283+
auto const &nl = conn.way->nodes();
284+
bool const forward =
285+
(conn.left == prev &&
286+
(!conns[conn.left].way ||
287+
conns[conn.left].way->nodes().back() == nl.front()));
288+
prev = cur;
289+
if (forward) {
290+
// add way forwards
291+
add_nodes_to_linestring(linestring, proj, nl.cbegin(),
292+
nl.cend());
293+
cur = conn.right;
294+
} else {
295+
// add way backwards
296+
add_nodes_to_linestring(linestring, proj, nl.crbegin(),
297+
nl.crend());
298+
cur = conn.left;
299+
}
300+
// mark way as done
301+
conns[prev].way = nullptr;
302+
} while (cur != i);
303+
}
304+
305+
// found a line end, create the wkbs
306+
make_line(linestring, split_at, out);
307+
}
308+
}
309+
128310
} // namespace geom
129311

src/geom.hpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
#include <osmium/geom/coordinates.hpp>
2222
#include <osmium/osm/node_ref_list.hpp>
23+
#include <osmium/memory/buffer.hpp>
2324

2425
#include <initializer_list>
2526
#include <ostream>
@@ -142,6 +143,39 @@ void split_linestring(linestring_t const &line, double split_at,
142143
void make_line(osmium::NodeRefList const &nodes, reprojection const &proj,
143144
double split_at, std::vector<linestring_t> *out);
144145

146+
void make_line(linestring_t const &line, double split_at,
147+
std::vector<linestring_t> *out);
148+
149+
/**
150+
* Add nodes specified by iterators to the linestring projecting them in the
151+
* process. If linestring is not empty, do not add the first node returned
152+
* by *begin.
153+
*/
154+
template <typename ITERATOR>
155+
void add_nodes_to_linestring(geom::linestring_t &linestring,
156+
reprojection const &proj, ITERATOR const &begin,
157+
ITERATOR const &end)
158+
{
159+
auto it = begin;
160+
if (!linestring.empty()) {
161+
assert(it != end);
162+
++it;
163+
}
164+
165+
osmium::Location last{};
166+
while (it != end) {
167+
auto const loc = it->location();
168+
if (loc.valid() && loc != last) {
169+
linestring.add_point(proj.reproject(loc));
170+
last = loc;
171+
}
172+
++it;
173+
}
174+
}
175+
176+
void make_multiline(osmium::memory::Buffer const &ways, double split_at,
177+
reprojection const &proj, std::vector<linestring_t> *out);
178+
145179
} // namespace geom
146180

147181
#endif // OSM2PGSQL_GEOM_HPP

0 commit comments

Comments
 (0)