Files
osm2pgsql/src/osmium-builder.cpp
Jochen Topf e2b6d1afe7 Various code cleanups
* Fix typos
* Modernize C++
* Fix clang-tidy warnings
* Formatting cleanups
2020-03-25 10:57:11 +01:00

414 lines
13 KiB
C++

#include <algorithm>
#include <cassert>
#include <tuple>
#include <vector>
#include <osmium/area/geom_assembler.hpp>
#include "osmium-builder.hpp"
namespace {
inline double distance(osmium::geom::Coordinates p1,
osmium::geom::Coordinates p2)
{
double dx = p1.x - p2.x;
double dy = p1.y - p2.y;
return std::sqrt(dx * dx + dy * dy);
}
inline osmium::geom::Coordinates interpolate(osmium::geom::Coordinates p1,
osmium::geom::Coordinates p2,
double frac)
{
return osmium::geom::Coordinates(frac * (p1.x - p2.x) + p2.x,
frac * (p1.y - p2.y) + p2.y);
}
template <typename ITERATOR>
inline void add_nodes_to_builder(osmium::builder::WayNodeListBuilder &builder,
ITERATOR const &begin, ITERATOR const &end,
bool skip_first)
{
auto it = begin;
if (skip_first) {
++it;
}
while (it != end) {
if (it->location().valid()) {
builder.add_node_ref(*it);
}
++it;
}
}
} // namespace
namespace geom {
osmium_builder_t::wkb_t
osmium_builder_t::get_wkb_node(osmium::Location const &loc) const
{
return m_writer.make_point(m_proj->reproject(loc));
}
osmium_builder_t::wkbs_t
osmium_builder_t::get_wkb_line(osmium::WayNodeList const &nodes,
double split_at)
{
wkbs_t ret;
bool do_split = split_at > 0.0;
double dist = 0;
osmium::geom::Coordinates prev_pt;
m_writer.linestring_start();
size_t curlen = 0;
for (auto const &node : nodes) {
if (!node.location().valid()) {
continue;
}
auto const this_pt = m_proj->reproject(node.location());
if (prev_pt.valid()) {
if (prev_pt == this_pt) {
continue;
}
if (do_split) {
double const delta = distance(prev_pt, this_pt);
// figure out if the addition of this point would take the total
// length of the line in `segment` over the `split_at` distance.
if (dist + delta > split_at) {
size_t const splits =
(size_t)std::floor((dist + delta) / split_at);
// use the splitting distance to split the current segment up
// into as many parts as necessary to keep each part below
// the `split_at` distance.
osmium::geom::Coordinates ipoint;
for (size_t j = 0; j < splits; ++j) {
double const frac =
((double)(j + 1) * split_at - dist) / delta;
ipoint = interpolate(this_pt, prev_pt, frac);
m_writer.add_location(ipoint);
ret.push_back(m_writer.linestring_finish(curlen + 1));
// start a new segment
m_writer.linestring_start();
m_writer.add_location(ipoint);
curlen = 1;
}
// reset the distance based on the final splitting point for
// the next iteration.
if (this_pt == ipoint) {
dist = 0;
m_writer.linestring_finish(0);
m_writer.linestring_start();
curlen = 0;
} else {
dist = distance(this_pt, ipoint);
}
} else {
dist += delta;
}
}
}
m_writer.add_location(this_pt);
++curlen;
prev_pt = this_pt;
}
auto wkb = m_writer.linestring_finish(curlen);
if (curlen > 1) {
ret.push_back(wkb);
}
return ret;
}
osmium_builder_t::wkb_t
osmium_builder_t::get_wkb_polygon(osmium::Way const &way)
{
osmium::area::AssemblerConfig area_config;
area_config.ignore_invalid_locations = true;
osmium::area::GeomAssembler assembler{area_config};
m_buffer.clear();
if (!assembler(way, m_buffer)) {
return wkb_t();
}
auto wkbs = create_polygons(m_buffer.get<osmium::Area>(0));
return wkbs.empty() ? wkb_t() : wkbs[0];
}
osmium_builder_t::wkbs_t
osmium_builder_t::get_wkb_multipolygon(osmium::Relation const &rel,
osmium::memory::Buffer const &ways,
bool build_multigeoms)
{
wkbs_t ret;
osmium::area::AssemblerConfig area_config;
area_config.ignore_invalid_locations = true;
osmium::area::GeomAssembler assembler{area_config};
m_buffer.clear();
if (assembler(rel, ways, m_buffer)) {
if (build_multigeoms) {
ret.push_back(create_multipolygon(m_buffer.get<osmium::Area>(0)));
} else {
ret = create_polygons(m_buffer.get<osmium::Area>(0));
}
}
return ret;
}
osmium_builder_t::wkbs_t
osmium_builder_t::get_wkb_multiline(osmium::memory::Buffer const &ways,
double split_at)
{
// make a list of all endpoints
using endpoint_t = std::tuple<osmium::object_id_type, size_t, bool>;
std::vector<endpoint_t> endpoints;
// and a list of way connections
enum lmt : size_t
{
NOCONN = -1UL
};
std::vector<std::tuple<size_t, osmium::Way const *, size_t>> conns;
// initialise the two lists
for (auto const &w : ways.select<osmium::Way>()) {
if (w.nodes().size() > 1) {
endpoints.emplace_back(w.nodes().front().ref(), conns.size(), true);
endpoints.emplace_back(w.nodes().back().ref(), conns.size(), false);
conns.emplace_back(NOCONN, &w, NOCONN);
}
}
// sort by node id
std::sort(endpoints.begin(), endpoints.end());
// now fill the connection list based on the sorted list
{
endpoint_t const *prev = nullptr;
for (auto const &pt : endpoints) {
if (prev) {
if (std::get<0>(*prev) == std::get<0>(pt)) {
auto previd = std::get<1>(*prev);
auto ptid = std::get<1>(pt);
if (std::get<2>(*prev)) {
std::get<0>(conns[previd]) = ptid;
} else {
std::get<2>(conns[previd]) = ptid;
}
if (std::get<2>(pt)) {
std::get<0>(conns[ptid]) = previd;
} else {
std::get<2>(conns[ptid]) = previd;
}
prev = nullptr;
continue;
}
}
prev = &pt;
}
}
wkbs_t ret;
size_t done_ways = 0;
size_t todo_ways = conns.size();
for (size_t i = 0; i < todo_ways; ++i) {
if (!std::get<1>(conns[i]) || (std::get<0>(conns[i]) != NOCONN &&
std::get<2>(conns[i]) != NOCONN)) {
continue; // way already done or not the beginning of a segment
}
m_buffer.clear();
{
osmium::builder::WayNodeListBuilder wnl_builder(m_buffer);
size_t prev = NOCONN;
size_t cur = i;
do {
auto &conn = conns[cur];
assert(std::get<1>(conn));
auto &nl = std::get<1>(conn)->nodes();
bool skip_first = prev != NOCONN;
bool forward = std::get<0>(conn) == prev;
prev = cur;
// add way nodes
if (forward) {
add_nodes_to_builder(wnl_builder, nl.cbegin(), nl.cend(),
skip_first);
cur = std::get<2>(conn);
} else {
add_nodes_to_builder(wnl_builder, nl.crbegin(), nl.crend(),
skip_first);
cur = std::get<0>(conn);
}
// mark way as done
std::get<1>(conns[prev]) = nullptr;
++done_ways;
} while (cur != NOCONN);
}
// found a line end, create the wkbs
m_buffer.commit();
auto linewkbs =
get_wkb_line(m_buffer.get<osmium::WayNodeList>(0), split_at);
std::move(linewkbs.begin(), linewkbs.end(),
std::inserter(ret, ret.end()));
}
if (done_ways < todo_ways) {
// oh dear, there must be circular ways without an end
// need to do the same shebang again
for (size_t i = 0; i < todo_ways; ++i) {
if (!std::get<1>(conns[i])) {
continue; // way already done
}
m_buffer.clear();
{
osmium::builder::WayNodeListBuilder wnl_builder(m_buffer);
size_t prev = std::get<0>(conns[i]);
size_t cur = i;
bool skip_first = false;
do {
auto &conn = conns[cur];
assert(std::get<1>(conn));
auto &nl = std::get<1>(conn)->nodes();
bool forward = std::get<0>(conn) == prev;
prev = cur;
if (forward) {
// add way forwards
add_nodes_to_builder(wnl_builder, nl.cbegin(),
nl.cend(), skip_first);
cur = std::get<2>(conn);
} else {
// add way backwards
add_nodes_to_builder(wnl_builder, nl.crbegin(),
nl.crend(), skip_first);
cur = std::get<0>(conn);
}
// mark way as done
std::get<1>(conns[prev]) = nullptr;
skip_first = true;
} while (cur != i);
}
// found a line end, create the wkbs
m_buffer.commit();
auto linewkbs =
get_wkb_line(m_buffer.get<osmium::WayNodeList>(0), split_at);
std::move(linewkbs.begin(), linewkbs.end(),
std::inserter(ret, ret.end()));
}
}
if (split_at <= 0.0 && !ret.empty()) {
auto num_lines = ret.size();
m_writer.multilinestring_start();
for (auto const &line : ret) {
m_writer.add_sub_geometry(line);
}
ret.clear();
ret.push_back(m_writer.multilinestring_finish(num_lines));
}
return ret;
}
size_t osmium_builder_t::add_mp_points(const osmium::NodeRefList &nodes)
{
size_t num_points = 0;
osmium::Location last_location;
for (const osmium::NodeRef &node_ref : nodes) {
if (node_ref.location().valid() &&
last_location != node_ref.location()) {
last_location = node_ref.location();
m_writer.add_location(m_proj->reproject(last_location));
++num_points;
}
}
return num_points;
}
osmium_builder_t::wkb_t
osmium_builder_t::create_multipolygon(osmium::Area const &area)
{
wkb_t ret;
auto polys = create_polygons(area);
switch (polys.size()) {
case 0:
break; //nothing
case 1:
ret = polys[0];
break;
default:
m_writer.multipolygon_start();
for (auto const &p : polys) {
m_writer.add_sub_geometry(p);
}
ret = m_writer.multipolygon_finish(polys.size());
break;
}
return ret;
}
osmium_builder_t::wkbs_t
osmium_builder_t::create_polygons(osmium::Area const &area)
{
wkbs_t ret;
try {
size_t num_rings = 0;
for (const auto &item : area) {
if (item.type() == osmium::item_type::outer_ring) {
auto const &ring = static_cast<const osmium::OuterRing &>(item);
if (num_rings > 0) {
ret.push_back(m_writer.polygon_finish(num_rings));
num_rings = 0;
}
m_writer.polygon_start();
m_writer.polygon_ring_start();
auto const num_points = add_mp_points(ring);
m_writer.polygon_ring_finish(num_points);
++num_rings;
} else if (item.type() == osmium::item_type::inner_ring) {
auto const &ring = static_cast<const osmium::InnerRing &>(item);
m_writer.polygon_ring_start();
auto const num_points = add_mp_points(ring);
m_writer.polygon_ring_finish(num_points);
++num_rings;
}
}
auto const wkb = m_writer.polygon_finish(num_rings);
if (num_rings > 0) {
ret.push_back(wkb);
}
} catch (const osmium::geometry_error &) { /* ignored */
}
return ret;
}
} // namespace geom