Skip to content

Commit 8d21d71

Browse files
authored
Merge pull request #1396 from joto/geom-refactor
Geom refactor
2 parents 8f809c7 + acd5aee commit 8d21d71

File tree

9 files changed

+1012
-290
lines changed

9 files changed

+1012
-290
lines changed

src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ set(osm2pgsql_lib_SOURCES
55
dependency-manager.cpp
66
expire-tiles.cpp
77
gazetteer-style.cpp
8+
geom.cpp
89
geometry-processor.cpp
910
input.cpp
1011
logging.cpp

src/geom.cpp

Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
/**
2+
* SPDX-License-Identifier: GPL-2.0-or-later
3+
*
4+
* This file is part of osm2pgsql (https://osm2pgsql.org/).
5+
*
6+
* Copyright (C) 2006-2020 by the osm2pgsql developer community.
7+
* For a full list of authors see the git log.
8+
*/
9+
10+
#include "geom.hpp"
11+
12+
#include "osmtypes.hpp"
13+
14+
#include <osmium/osm/way.hpp>
15+
16+
#include <algorithm>
17+
#include <iterator>
18+
19+
namespace geom {
20+
21+
double distance(osmium::geom::Coordinates p1,
22+
osmium::geom::Coordinates p2) noexcept
23+
{
24+
double const dx = p1.x - p2.x;
25+
double const dy = p1.y - p2.y;
26+
return std::sqrt(dx * dx + dy * dy);
27+
}
28+
29+
osmium::geom::Coordinates interpolate(osmium::geom::Coordinates p1,
30+
osmium::geom::Coordinates p2,
31+
double frac) noexcept
32+
{
33+
return osmium::geom::Coordinates{frac * (p1.x - p2.x) + p2.x,
34+
frac * (p1.y - p2.y) + p2.y};
35+
}
36+
37+
linestring_t::linestring_t(
38+
std::initializer_list<osmium::geom::Coordinates> coords)
39+
{
40+
std::copy(coords.begin(), coords.end(), std::back_inserter(m_coordinates));
41+
}
42+
43+
linestring_t::linestring_t(osmium::NodeRefList const &nodes,
44+
reprojection const &proj)
45+
{
46+
osmium::Location last{};
47+
for (auto const &node : nodes) {
48+
auto const loc = node.location();
49+
if (loc.valid() && loc != last) {
50+
add_point(proj.reproject(loc));
51+
last = loc;
52+
}
53+
}
54+
55+
if (size() <= 1) {
56+
m_coordinates.clear();
57+
}
58+
}
59+
60+
void split_linestring(linestring_t const &line, double split_at,
61+
std::vector<linestring_t> *out)
62+
{
63+
double dist = 0;
64+
osmium::geom::Coordinates prev_pt{};
65+
out->emplace_back();
66+
67+
for (auto const &this_pt : line) {
68+
if (prev_pt.valid()) {
69+
double const delta = distance(prev_pt, this_pt);
70+
71+
// figure out if the addition of this point would take the total
72+
// length of the line in `segment` over the `split_at` distance.
73+
74+
if (dist + delta > split_at) {
75+
auto const splits =
76+
(size_t)std::floor((dist + delta) / split_at);
77+
// use the splitting distance to split the current segment up
78+
// into as many parts as necessary to keep each part below
79+
// the `split_at` distance.
80+
osmium::geom::Coordinates ipoint;
81+
for (size_t j = 0; j < splits; ++j) {
82+
double const frac =
83+
((double)(j + 1) * split_at - dist) / delta;
84+
ipoint = interpolate(this_pt, prev_pt, frac);
85+
if (frac != 0.0) {
86+
out->back().add_point(ipoint);
87+
}
88+
// start a new segment
89+
out->emplace_back();
90+
out->back().add_point(ipoint);
91+
}
92+
// reset the distance based on the final splitting point for
93+
// the next iteration.
94+
if (this_pt == ipoint) {
95+
dist = 0;
96+
prev_pt = this_pt;
97+
continue;
98+
} else {
99+
dist = distance(this_pt, ipoint);
100+
}
101+
} else {
102+
dist += delta;
103+
}
104+
}
105+
106+
out->back().add_point(this_pt);
107+
108+
prev_pt = this_pt;
109+
}
110+
111+
if (out->back().size() <= 1) {
112+
out->pop_back();
113+
}
114+
}
115+
116+
void make_line(linestring_t const &line, double split_at,
117+
std::vector<linestring_t> *out)
118+
{
119+
if (line.empty()) {
120+
return;
121+
}
122+
123+
if (split_at > 0.0) {
124+
split_linestring(line, split_at, out);
125+
} else {
126+
out->emplace_back(std::move(line));
127+
}
128+
}
129+
130+
void make_multiline(osmium::memory::Buffer const &ways, double split_at,
131+
reprojection const &proj, std::vector<linestring_t> *out)
132+
133+
{
134+
// make a list of all endpoints
135+
struct endpoint_t {
136+
osmid_t id;
137+
std::size_t n;
138+
bool is_front;
139+
140+
endpoint_t(osmid_t ref, std::size_t size, bool front) noexcept
141+
: id(ref), n(size), is_front(front)
142+
{}
143+
144+
bool operator==(endpoint_t const &rhs) const noexcept
145+
{
146+
return id == rhs.id;
147+
}
148+
};
149+
150+
std::vector<endpoint_t> endpoints;
151+
152+
// and a list of way connections
153+
enum lmt : size_t
154+
{
155+
NOCONN = -1UL
156+
};
157+
158+
struct connection_t {
159+
std::size_t left = NOCONN;
160+
osmium::Way const *way;
161+
std::size_t right = NOCONN;
162+
163+
explicit connection_t(osmium::Way const *w) noexcept : way(w) {}
164+
};
165+
166+
std::vector<connection_t> conns;
167+
168+
// initialise the two lists
169+
for (auto const &w : ways.select<osmium::Way>()) {
170+
if (w.nodes().size() > 1) {
171+
endpoints.emplace_back(w.nodes().front().ref(), conns.size(), true);
172+
endpoints.emplace_back(w.nodes().back().ref(), conns.size(), false);
173+
conns.emplace_back(&w);
174+
}
175+
}
176+
177+
// sort by node id
178+
std::sort(endpoints.begin(), endpoints.end(), [
179+
](endpoint_t const &a, endpoint_t const &b) noexcept {
180+
return std::tuple<osmid_t, std::size_t, bool>(a.id, a.n, a.is_front) <
181+
std::tuple<osmid_t, std::size_t, bool>(b.id, b.n, b.is_front);
182+
});
183+
184+
// now fill the connection list based on the sorted list
185+
for (auto it = std::adjacent_find(endpoints.cbegin(), endpoints.cend());
186+
it != endpoints.cend();
187+
it = std::adjacent_find(it + 2, endpoints.cend())) {
188+
auto const previd = it->n;
189+
auto const ptid = std::next(it)->n;
190+
if (it->is_front) {
191+
conns[previd].left = ptid;
192+
} else {
193+
conns[previd].right = ptid;
194+
}
195+
if (std::next(it)->is_front) {
196+
conns[ptid].left = previd;
197+
} else {
198+
conns[ptid].right = previd;
199+
}
200+
}
201+
202+
// First find all open ends and use them as starting points to assemble
203+
// linestrings. Mark ways as "done" as we go.
204+
std::size_t done_ways = 0;
205+
std::size_t const todo_ways = conns.size();
206+
for (std::size_t i = 0; i < todo_ways; ++i) {
207+
if (!conns[i].way ||
208+
(conns[i].left != NOCONN && conns[i].right != NOCONN)) {
209+
continue; // way already done or not the beginning of a segment
210+
}
211+
212+
linestring_t linestring;
213+
{
214+
std::size_t prev = NOCONN;
215+
std::size_t cur = i;
216+
217+
do {
218+
auto &conn = conns[cur];
219+
assert(conn.way);
220+
auto const &nl = conn.way->nodes();
221+
bool const forward = conn.left == prev;
222+
prev = cur;
223+
// add way nodes
224+
if (forward) {
225+
add_nodes_to_linestring(linestring, proj, nl.cbegin(),
226+
nl.cend());
227+
cur = conn.right;
228+
} else {
229+
add_nodes_to_linestring(linestring, proj, nl.crbegin(),
230+
nl.crend());
231+
cur = conn.left;
232+
}
233+
// mark way as done
234+
conns[prev].way = nullptr;
235+
++done_ways;
236+
} while (cur != NOCONN);
237+
}
238+
239+
// found a line end, create the wkbs
240+
make_line(linestring, split_at, out);
241+
}
242+
243+
// If all ways have been "done", i.e. are part of a linestring now, we
244+
// are finished.
245+
if (done_ways >= todo_ways) {
246+
return;
247+
}
248+
249+
// oh dear, there must be circular ways without an end
250+
// need to do the same shebang again
251+
for (size_t i = 0; i < todo_ways; ++i) {
252+
if (!conns[i].way) {
253+
continue; // way already done
254+
}
255+
256+
linestring_t linestring;
257+
{
258+
size_t prev = conns[i].left;
259+
size_t cur = i;
260+
261+
do {
262+
auto &conn = conns[cur];
263+
assert(conn.way);
264+
auto const &nl = conn.way->nodes();
265+
bool const forward =
266+
(conn.left == prev &&
267+
(!conns[conn.left].way ||
268+
conns[conn.left].way->nodes().back() == nl.front()));
269+
prev = cur;
270+
if (forward) {
271+
// add way forwards
272+
add_nodes_to_linestring(linestring, proj, nl.cbegin(),
273+
nl.cend());
274+
cur = conn.right;
275+
} else {
276+
// add way backwards
277+
add_nodes_to_linestring(linestring, proj, nl.crbegin(),
278+
nl.crend());
279+
cur = conn.left;
280+
}
281+
// mark way as done
282+
conns[prev].way = nullptr;
283+
} while (cur != i);
284+
}
285+
286+
// found a line end, create the wkbs
287+
make_line(linestring, split_at, out);
288+
}
289+
}
290+
291+
} // namespace geom
292+

0 commit comments

Comments
 (0)