osrm-backend/include/osmium/area/assembler.hpp
Michael Krasnyk 0c6dee4bef Squashed 'third_party/libosmium/' changes from d5ecf4d..c1f34c4
c1f34c4 Release v2.11.0
d3b72e0 Updated change log.
2982b8d Update embedded Protozero to version 1.5.1.
cc1ab2a Add non-const WayNodeList::operator[].
3da372e Add missing example to examples/README.md.
30604ba Add OSMIUM_USE_SLOW_MERCATOR_PROJECTION define.
47a92e0 Clearer CheckOrder handler doc.
f11106d Formatting fixes.
a870737 Use faster implementation of web mercator projection.
041bb42 Test cleanups.
8933bc5 Cleanup *Map::get() functions.
6b989ca Document that (Multipolygon)Collectors only work with unique Ids.
8fb5bd2 Updated included Protozero to version 1.5.0.
76e153d Removed Makefile.
35d7ec9 Update copyright date.
a7f8126 Rename guard define to common scheme.
a923c69 Cleanup I/O tests.
d353993 Add Map::get_noexcept() method for all index maps.
94fa5ac Add const overload for mmap_vector_base::operator[].
3cf9184 Add default constructed "invalid" Coordinates.
358f170 Add Tile constructor from web mercator coordinates.
006aa4c Add index::RelationsMap(Stash|Index) classes.
9cc842e Updated catch to v1.5.9.
bd8c3b6 Use initializer_list trick instead of recursive template.
2c82a6f Merge pull request #183 from daniel-j-h/rvalue-apply
0bf5404 Implements rvalue handler support for apply, resolves #180.
ccaab08 Merge pull request #182 from AMDmi3/freebsd-endianess
bffe626 Handle endianess on FreeBSD properly
7250222 Code formatting and test cleanup.
6652436 Merge pull request #179 from oxidase/add_match_key_std_regex
afadf5b Rename centroid variables and function in example.
8355284 Add envelope() functions to NodeRefList, Way, and Area.
fc83d2e Remove unnecessary include.
9ddd00e Add match_key<std::regex> tag
9c54a53 Update README. Moved some infos to manual.
89a90a6 Update readme and developer docs.
c3446ec Simplify subitem iteration code and made it more flexible.
542b07c Add some static_asserts.
f0fd690 Memory reporting on M68k doesn't work properly.
e8957c6 Compare doubles in test using Approx().
58ae4a6 Add amenity_list example.
53783f8 Fix doxygen config for reproducible builds.
de4e52d Release v2.10.3
0cc42a2 ObjectPointerCollection constructor can't be noexcept.
4472dfb Round out ObjectPointerCollection implementation and test it.
28cb35d Build with XCode 8 and GCC 6 on travis.
03e3e66 Upgrade to new protozero version 1.4.5.
2102c2f Add assertion in queue handling code.

git-subtree-dir: third_party/libosmium
git-subtree-split: c1f34c45507e233a2b9028663906679c610fe179
2017-01-20 14:05:21 +01:00

1590 lines
66 KiB
C++

#ifndef OSMIUM_AREA_ASSEMBLER_HPP
#define OSMIUM_AREA_ASSEMBLER_HPP
/*
This file is part of Osmium (http://osmcode.org/libosmium).
Copyright 2013-2017 Jochen Topf <jochen@topf.org> and others (see README).
Boost Software License - Version 1.0 - August 17th, 2003
Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the "Software") to use, reproduce, display, distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:
The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
#include <algorithm>
#include <cassert>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <iterator>
#include <list>
#include <set>
#include <string>
#include <map>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
#include <osmium/builder/osm_object_builder.hpp>
#include <osmium/memory/buffer.hpp>
#include <osmium/osm/area.hpp>
#include <osmium/osm/item_type.hpp>
#include <osmium/osm/location.hpp>
#include <osmium/osm/node_ref.hpp>
#include <osmium/osm/relation.hpp>
#include <osmium/osm/tag.hpp>
#include <osmium/osm/types.hpp>
#include <osmium/osm/way.hpp>
#include <osmium/tags/filter.hpp>
#include <osmium/util/compatibility.hpp>
#include <osmium/util/iterator.hpp>
#include <osmium/util/timer.hpp>
#include <osmium/area/detail/proto_ring.hpp>
#include <osmium/area/detail/node_ref_segment.hpp>
#include <osmium/area/detail/segment_list.hpp>
#include <osmium/area/problem_reporter.hpp>
#include <osmium/area/stats.hpp>
namespace osmium {
namespace area {
/**
* Configuration for osmium::area::Assembler objects. Create this
* once, set the options you want and then re-use it every time you
* create an Assembler object.
*/
struct AssemblerConfig {
/**
* Optional pointer to problem reporter.
*/
osmium::area::ProblemReporter* problem_reporter = nullptr;
/**
* Debug level. If this is greater than zero, debug messages will
* be printed to stderr. Available levels are 1 to 3. Note that
* level 2 and above will generate a lot of messages!
*/
int debug_level = 0;
/**
* The roles of multipolygon members are ignored when assembling
* multipolygons, because they are often missing or wrong. If this
* is set, the roles are checked after the multipolygons are built
* against what the assembly process decided where the inner and
* outer rings are. This slows down the processing, so it only
* makes sense if you want to get the problem reports.
*/
bool check_roles = false;
/**
* When the assembler can't create an area, usually because its
* geometry would be invalid, it will create an "empty" area object
* without rings. This allows you to detect where an area was
* invalid.
*
* If this is set to false, invalid areas will simply be discarded.
*/
bool create_empty_areas = true;
/**
* Create areas for (multi)polygons where the tags are on the
* relation.
*
* If this is set to false, those areas will simply be discarded.
*/
bool create_new_style_polygons = true;
/**
* Create areas for (multi)polygons where the tags are on the
* outer way(s).
*
* If this is set to false, those areas will simply be discarded.
*/
bool create_old_style_polygons = true;
/**
* Create areas for polygons created from ways.
*
* If this is set to false, those areas will simply be discarded.
*/
bool create_way_polygons = true;
/**
* Keep the type tag from multipolygon relations on the area
* object. By default this is false, and the type tag will be
* removed.
*/
bool keep_type_tag = false;
AssemblerConfig() noexcept = default;
/**
* Constructor
* @deprecated Use default constructor and set values afterwards.
*/
explicit AssemblerConfig(osmium::area::ProblemReporter* pr, bool d = false) :
problem_reporter(pr),
debug_level(d) {
}
/**
* Enable or disable debug output to stderr. This is for Osmium
* developers only.
*
* @deprecated Set debug_level directly.
*/
OSMIUM_DEPRECATED void enable_debug_output(bool d = true) {
debug_level = d;
}
}; // struct AssemblerConfig
namespace detail {
using open_ring_its_type = std::list<std::list<detail::ProtoRing>::iterator>;
struct location_to_ring_map {
osmium::Location location;
open_ring_its_type::iterator ring_it;
bool start;
location_to_ring_map(const osmium::Location& l, const open_ring_its_type::iterator& r, bool s) noexcept :
location(l),
ring_it(r),
start(s) {
}
explicit location_to_ring_map(const osmium::Location& l) noexcept :
location(l),
ring_it(),
start(false) {
}
const detail::ProtoRing& ring() const noexcept {
return **ring_it;
}
}; // struct location_to_ring_map
inline bool operator==(const location_to_ring_map& lhs, const location_to_ring_map& rhs) noexcept {
return lhs.location == rhs.location;
}
inline bool operator<(const location_to_ring_map& lhs, const location_to_ring_map& rhs) noexcept {
return lhs.location < rhs.location;
}
} // namespace detail
/**
* Assembles area objects from closed ways or multipolygon relations
* and their members.
*/
class Assembler {
using open_ring_its_type = detail::open_ring_its_type;
using location_to_ring_map = detail::location_to_ring_map;
struct slocation {
static constexpr const uint32_t invalid_item = 1 << 30;
uint32_t item : 31;
uint32_t reverse : 1;
slocation() noexcept :
item(invalid_item),
reverse(false) {
}
explicit slocation(uint32_t n, bool r = false) noexcept :
item(n),
reverse(r) {
}
osmium::Location location(const detail::SegmentList& segment_list) const noexcept {
const auto& segment = segment_list[item];
return reverse ? segment.second().location() : segment.first().location();
}
const osmium::NodeRef& node_ref(const detail::SegmentList& segment_list) const noexcept {
const auto& segment = segment_list[item];
return reverse ? segment.second() : segment.first();
}
osmium::Location location(const detail::SegmentList& segment_list, const osmium::Location& default_location) const noexcept {
if (item == invalid_item) {
return default_location;
}
return location(segment_list);
}
}; // struct slocation
// Configuration settings for this Assembler
const AssemblerConfig& m_config;
// List of segments (connection between two nodes)
osmium::area::detail::SegmentList m_segment_list;
// The rings we are building from the segments
std::list<detail::ProtoRing> m_rings;
// All node locations
std::vector<slocation> m_locations;
// All locations where more than two segments start/end
std::vector<Location> m_split_locations;
// Statistics
area_stats m_stats;
// The number of members the multipolygon relation has
size_t m_num_members = 0;
bool debug() const noexcept {
return m_config.debug_level > 1;
}
bool report_ways() const noexcept {
if (!m_config.problem_reporter) {
return false;
}
return m_stats.duplicate_nodes ||
m_stats.duplicate_segments ||
m_stats.intersections ||
m_stats.open_rings ||
m_stats.short_ways ||
m_stats.touching_rings ||
m_stats.ways_in_multiple_rings ||
m_stats.wrong_role;
}
void add_tags_to_area(osmium::builder::AreaBuilder& builder, const osmium::Way& way) const {
builder.add_item(way.tags());
}
void add_common_tags(osmium::builder::TagListBuilder& tl_builder, std::set<const osmium::Way*>& ways) const {
std::map<std::string, size_t> counter;
for (const osmium::Way* way : ways) {
for (const auto& tag : way->tags()) {
std::string kv {tag.key()};
kv.append(1, '\0');
kv.append(tag.value());
++counter[kv];
}
}
const size_t num_ways = ways.size();
for (const auto& t_c : counter) {
if (debug()) {
std::cerr << " tag " << t_c.first << " is used " << t_c.second << " times in " << num_ways << " ways\n";
}
if (t_c.second == num_ways) {
const size_t len = std::strlen(t_c.first.c_str());
tl_builder.add_tag(t_c.first.c_str(), t_c.first.c_str() + len + 1);
}
}
}
struct MPFilter : public osmium::tags::KeyFilter {
MPFilter() : osmium::tags::KeyFilter(true) {
add(false, "type");
add(false, "created_by");
add(false, "source");
add(false, "note");
add(false, "test:id");
add(false, "test:section");
}
}; // struct MPFilter
static const MPFilter& filter() noexcept {
static const MPFilter filter;
return filter;
}
static void copy_tags_without_type(osmium::builder::AreaBuilder& builder, const osmium::TagList& tags) {
osmium::builder::TagListBuilder tl_builder{builder};
for (const osmium::Tag& tag : tags) {
if (std::strcmp(tag.key(), "type")) {
tl_builder.add_tag(tag.key(), tag.value());
}
}
}
void add_tags_to_area(osmium::builder::AreaBuilder& builder, const osmium::Relation& relation) {
const auto count = std::count_if(relation.tags().cbegin(), relation.tags().cend(), filter());
if (debug()) {
std::cerr << " found " << count << " tags on relation (without ignored ones)\n";
}
if (count > 0) {
if (debug()) {
std::cerr << " use tags from relation\n";
}
if (m_config.keep_type_tag) {
builder.add_item(relation.tags());
} else {
copy_tags_without_type(builder, relation.tags());
}
} else {
++m_stats.no_tags_on_relation;
if (debug()) {
std::cerr << " use tags from outer ways\n";
}
std::set<const osmium::Way*> ways;
for (const auto& ring : m_rings) {
if (ring.is_outer()) {
ring.get_ways(ways);
}
}
if (ways.size() == 1) {
if (debug()) {
std::cerr << " only one outer way\n";
}
builder.add_item((*ways.cbegin())->tags());
} else {
if (debug()) {
std::cerr << " multiple outer ways, get common tags\n";
}
osmium::builder::TagListBuilder tl_builder{builder};
add_common_tags(tl_builder, ways);
}
}
}
template <typename TBuilder>
static void build_ring_from_proto_ring(osmium::builder::AreaBuilder& builder, const detail::ProtoRing& ring) {
TBuilder ring_builder{builder};
ring_builder.add_node_ref(ring.get_node_ref_start());
for (const auto& segment : ring.segments()) {
ring_builder.add_node_ref(segment->stop());
}
}
/**
* Append each outer ring together with its inner rings to the
* area in the buffer.
*/
void add_rings_to_area(osmium::builder::AreaBuilder& builder) const {
for (const detail::ProtoRing& ring : m_rings) {
if (ring.is_outer()) {
build_ring_from_proto_ring<osmium::builder::OuterRingBuilder>(builder, ring);
for (const detail::ProtoRing* inner : ring.inner_rings()) {
build_ring_from_proto_ring<osmium::builder::InnerRingBuilder>(builder, *inner);
}
}
}
}
void check_inner_outer_roles() {
if (debug()) {
std::cerr << " Checking inner/outer roles\n";
}
std::unordered_map<const osmium::Way*, const detail::ProtoRing*> way_rings;
std::unordered_set<const osmium::Way*> ways_in_multiple_rings;
for (const detail::ProtoRing& ring : m_rings) {
for (const auto& segment : ring.segments()) {
assert(segment->way());
if (!segment->role_empty() && (ring.is_outer() ? !segment->role_outer() : !segment->role_inner())) {
++m_stats.wrong_role;
if (debug()) {
std::cerr << " Segment " << *segment << " from way " << segment->way()->id() << " has role '" << segment->role_name()
<< "', but should have role '" << (ring.is_outer() ? "outer" : "inner") << "'\n";
}
if (m_config.problem_reporter) {
if (ring.is_outer()) {
m_config.problem_reporter->report_role_should_be_outer(segment->way()->id(), segment->first().location(), segment->second().location());
} else {
m_config.problem_reporter->report_role_should_be_inner(segment->way()->id(), segment->first().location(), segment->second().location());
}
}
}
auto& r = way_rings[segment->way()];
if (!r) {
r = &ring;
} else if (r != &ring) {
ways_in_multiple_rings.insert(segment->way());
}
}
}
for (const osmium::Way* way : ways_in_multiple_rings) {
++m_stats.ways_in_multiple_rings;
if (debug()) {
std::cerr << " Way " << way->id() << " is in multiple rings\n";
}
if (m_config.problem_reporter) {
m_config.problem_reporter->report_way_in_multiple_rings(*way);
}
}
}
detail::NodeRefSegment* get_next_segment(const osmium::Location& location) {
auto it = std::lower_bound(m_locations.begin(), m_locations.end(), slocation{}, [this, &location](const slocation& lhs, const slocation& rhs) {
return lhs.location(m_segment_list, location) < rhs.location(m_segment_list, location);
});
assert(it != m_locations.end());
if (m_segment_list[it->item].is_done()) {
++it;
}
assert(it != m_locations.end());
assert(!m_segment_list[it->item].is_done());
return &m_segment_list[it->item];
}
class rings_stack_element {
int32_t m_y;
detail::ProtoRing* m_ring_ptr;
public:
rings_stack_element(int32_t y, detail::ProtoRing* ring_ptr) :
m_y(y),
m_ring_ptr(ring_ptr) {
}
int32_t y() const noexcept {
return m_y;
}
const detail::ProtoRing& ring() const noexcept {
return *m_ring_ptr;
}
detail::ProtoRing* ring_ptr() noexcept {
return m_ring_ptr;
}
bool operator==(const rings_stack_element& rhs) const noexcept {
return m_ring_ptr == rhs.m_ring_ptr;
}
bool operator<(const rings_stack_element& rhs) const noexcept {
return m_y < rhs.m_y;
}
}; // class ring_stack_element
using rings_stack = std::vector<rings_stack_element>;
void remove_duplicates(rings_stack& outer_rings) {
while (true) {
const auto it = std::adjacent_find(outer_rings.begin(), outer_rings.end());
if (it == outer_rings.end()) {
return;
}
outer_rings.erase(it, std::next(it, 2));
}
}
detail::ProtoRing* find_enclosing_ring(detail::NodeRefSegment* segment) {
if (debug()) {
std::cerr << " Looking for ring enclosing " << *segment << "\n";
}
const auto location = segment->first().location();
const auto end_location = segment->second().location();
while (segment->first().location() == location) {
if (segment == &m_segment_list.back()) {
break;
}
++segment;
}
int nesting = 0;
rings_stack outer_rings;
while (segment >= &m_segment_list.front()) {
if (!segment->is_direction_done()) {
--segment;
continue;
}
if (debug()) {
std::cerr << " Checking against " << *segment << "\n";
}
const osmium::Location& a = segment->first().location();
const osmium::Location& b = segment->second().location();
if (segment->first().location() == location) {
const int64_t ax = a.x();
const int64_t bx = b.x();
const int64_t lx = end_location.x();
const int64_t ay = a.y();
const int64_t by = b.y();
const int64_t ly = end_location.y();
const auto z = (bx - ax)*(ly - ay) - (by - ay)*(lx - ax);
if (debug()) {
std::cerr << " Segment XXXX z=" << z << "\n";
}
if (z > 0) {
nesting += segment->is_reverse() ? -1 : 1;
if (debug()) {
std::cerr << " Segment is below (nesting=" << nesting << ")\n";
}
if (segment->ring()->is_outer()) {
if (debug()) {
std::cerr << " Segment belongs to outer ring\n";
}
outer_rings.emplace_back(a.y(), segment->ring());
}
}
} else if (a.x() <= location.x() && location.x() < b.x()) {
if (debug()) {
std::cerr << " Is in x range\n";
}
const int64_t ax = a.x();
const int64_t bx = b.x();
const int64_t lx = location.x();
const int64_t ay = a.y();
const int64_t by = b.y();
const int64_t ly = location.y();
const auto z = (bx - ax)*(ly - ay) - (by - ay)*(lx - ax);
if (z >= 0) {
nesting += segment->is_reverse() ? -1 : 1;
if (debug()) {
std::cerr << " Segment is below (nesting=" << nesting << ")\n";
}
if (segment->ring()->is_outer()) {
if (debug()) {
std::cerr << " Segment belongs to outer ring\n";
}
const int32_t y = int32_t(ay + (by - ay) * (lx - ax) / (bx - ax));
outer_rings.emplace_back(y, segment->ring());
}
}
}
--segment;
}
if (nesting % 2 == 0) {
if (debug()) {
std::cerr << " Decided that this is an outer ring\n";
}
return nullptr;
} else {
if (debug()) {
std::cerr << " Decided that this is an inner ring\n";
}
assert(!outer_rings.empty());
std::sort(outer_rings.rbegin(), outer_rings.rend());
if (debug()) {
for (const auto& o : outer_rings) {
std::cerr << " y=" << o.y() << " " << o.ring() << "\n";
}
}
remove_duplicates(outer_rings);
if (debug()) {
std::cerr << " after remove duplicates:\n";
for (const auto& o : outer_rings) {
std::cerr << " y=" << o.y() << " " << o.ring() << "\n";
}
}
assert(!outer_rings.empty());
return outer_rings.front().ring_ptr();
}
}
bool is_split_location(const osmium::Location& location) const noexcept {
return std::find(m_split_locations.cbegin(), m_split_locations.cend(), location) != m_split_locations.cend();
}
uint32_t add_new_ring(slocation& node) {
detail::NodeRefSegment* segment = &m_segment_list[node.item];
assert(!segment->is_done());
if (debug()) {
std::cerr << " Starting new ring at location " << node.location(m_segment_list) << " with segment " << *segment << "\n";
}
if (node.reverse) {
segment->reverse();
}
detail::ProtoRing* outer_ring = nullptr;
if (segment != &m_segment_list.front()) {
outer_ring = find_enclosing_ring(segment);
}
segment->mark_direction_done();
m_rings.emplace_back(segment);
detail::ProtoRing* ring = &m_rings.back();
if (outer_ring) {
if (debug()) {
std::cerr << " This is an inner ring. Outer ring is " << *outer_ring << "\n";
}
outer_ring->add_inner_ring(ring);
ring->set_outer_ring(outer_ring);
} else if (debug()) {
std::cerr << " This is an outer ring\n";
}
const osmium::Location& first_location = node.location(m_segment_list);
osmium::Location last_location = segment->stop().location();
uint32_t nodes = 1;
while (first_location != last_location) {
++nodes;
detail::NodeRefSegment* next_segment = get_next_segment(last_location);
next_segment->mark_direction_done();
if (next_segment->start().location() != last_location) {
next_segment->reverse();
}
ring->add_segment_back(next_segment);
if (debug()) {
std::cerr << " Next segment is " << *next_segment << "\n";
}
last_location = next_segment->stop().location();
}
ring->fix_direction();
if (debug()) {
std::cerr << " Completed ring: " << *ring << "\n";
}
return nodes;
}
uint32_t add_new_ring_complex(slocation& node) {
detail::NodeRefSegment* segment = &m_segment_list[node.item];
assert(!segment->is_done());
if (debug()) {
std::cerr << " Starting new ring at location " << node.location(m_segment_list) << " with segment " << *segment << "\n";
}
if (node.reverse) {
segment->reverse();
}
m_rings.emplace_back(segment);
detail::ProtoRing* ring = &m_rings.back();
const osmium::Location& first_location = node.location(m_segment_list);
osmium::Location last_location = segment->stop().location();
uint32_t nodes = 1;
while (first_location != last_location && !is_split_location(last_location)) {
++nodes;
detail::NodeRefSegment* next_segment = get_next_segment(last_location);
if (next_segment->start().location() != last_location) {
next_segment->reverse();
}
ring->add_segment_back(next_segment);
if (debug()) {
std::cerr << " Next segment is " << *next_segment << "\n";
}
last_location = next_segment->stop().location();
}
if (debug()) {
if (first_location == last_location) {
std::cerr << " Completed ring: " << *ring << "\n";
} else {
std::cerr << " Completed partial ring: " << *ring << "\n";
}
}
return nodes;
}
void create_locations_list() {
m_locations.reserve(m_segment_list.size() * 2);
for (uint32_t n = 0; n < m_segment_list.size(); ++n) {
m_locations.emplace_back(n, false);
m_locations.emplace_back(n, true);
}
std::stable_sort(m_locations.begin(), m_locations.end(), [this](const slocation& lhs, const slocation& rhs) {
return lhs.location(m_segment_list) < rhs.location(m_segment_list);
});
}
void find_inner_outer_complex(detail::ProtoRing* ring) {
detail::ProtoRing* outer_ring = find_enclosing_ring(ring->min_segment());
if (outer_ring) {
outer_ring->add_inner_ring(ring);
ring->set_outer_ring(outer_ring);
}
ring->fix_direction();
ring->mark_direction_done();
}
void find_inner_outer_complex() {
if (debug()) {
std::cerr << " Finding inner/outer rings\n";
}
std::vector<detail::ProtoRing*> rings;
rings.reserve(m_rings.size());
for (auto& ring : m_rings) {
if (ring.closed()) {
rings.push_back(&ring);
}
}
if (rings.empty()) {
return;
}
std::sort(rings.begin(), rings.end(), [](detail::ProtoRing* a, detail::ProtoRing* b) {
return a->min_segment() < b->min_segment();
});
rings.front()->fix_direction();
rings.front()->mark_direction_done();
if (debug()) {
std::cerr << " First ring is outer: " << *rings.front() << "\n";
}
for (auto it = std::next(rings.begin()); it != rings.end(); ++it) {
if (debug()) {
std::cerr << " Checking (at min segment " << *((*it)->min_segment()) << ") ring " << **it << "\n";
}
find_inner_outer_complex(*it);
if (debug()) {
std::cerr << " Ring is " << ((*it)->is_outer() ? "OUTER: " : "INNER: ") << **it << "\n";
}
}
}
/**
* Finds all locations where more than two segments meet. If there
* are any open rings found along the way, they are reported
* and the function returns false.
*/
bool find_split_locations() {
osmium::Location previous_location;
for (auto it = m_locations.cbegin(); it != m_locations.cend(); ++it) {
const osmium::NodeRef& nr = it->node_ref(m_segment_list);
const osmium::Location& loc = nr.location();
if (std::next(it) == m_locations.cend() || loc != std::next(it)->location(m_segment_list)) {
if (debug()) {
std::cerr << " Found open ring at " << nr << "\n";
}
if (m_config.problem_reporter) {
const auto& segment = m_segment_list[it->item];
m_config.problem_reporter->report_ring_not_closed(nr, segment.way());
}
++m_stats.open_rings;
} else {
if (loc == previous_location && (m_split_locations.empty() || m_split_locations.back() != previous_location )) {
m_split_locations.push_back(previous_location);
}
++it;
if (it == m_locations.end()) {
break;
}
}
previous_location = loc;
}
return m_stats.open_rings == 0;
}
void create_rings_simple_case() {
auto count_remaining = m_segment_list.size();
for (slocation& sl : m_locations) {
const detail::NodeRefSegment& segment = m_segment_list[sl.item];
if (!segment.is_done()) {
count_remaining -= add_new_ring(sl);
if (count_remaining == 0) {
return;
}
}
}
}
std::vector<location_to_ring_map> create_location_to_ring_map(open_ring_its_type& open_ring_its) {
std::vector<location_to_ring_map> xrings;
xrings.reserve(open_ring_its.size() * 2);
for (auto it = open_ring_its.begin(); it != open_ring_its.end(); ++it) {
if (debug()) {
std::cerr << " Ring: " << **it << "\n";
}
xrings.emplace_back((*it)->get_node_ref_start().location(), it, true);
xrings.emplace_back((*it)->get_node_ref_stop().location(), it, false);
}
std::sort(xrings.begin(), xrings.end());
return xrings;
}
void merge_two_rings(open_ring_its_type& open_ring_its, const location_to_ring_map& m1, const location_to_ring_map& m2) {
auto& r1 = *m1.ring_it;
auto& r2 = *m2.ring_it;
if (r1->get_node_ref_stop().location() == r2->get_node_ref_start().location()) {
r1->join_forward(*r2);
} else if (r1->get_node_ref_stop().location() == r2->get_node_ref_stop().location()) {
r1->join_backward(*r2);
} else if (r1->get_node_ref_start().location() == r2->get_node_ref_start().location()) {
r1->reverse();
r1->join_forward(*r2);
} else if (r1->get_node_ref_start().location() == r2->get_node_ref_stop().location()) {
r1->reverse();
r1->join_backward(*r2);
} else {
assert(false);
}
m_rings.erase(r2);
open_ring_its.remove(r2);
if (r1->closed()) {
open_ring_its.remove(r1);
}
}
bool try_to_merge(open_ring_its_type& open_ring_its) {
if (open_ring_its.empty()) {
return false;
}
if (debug()) {
std::cerr << " Trying to merge " << open_ring_its.size() << " open rings\n";
}
std::vector<location_to_ring_map> xrings = create_location_to_ring_map(open_ring_its);
auto it = xrings.cbegin();
while (it != xrings.cend()) {
it = std::adjacent_find(it, xrings.cend());
if (it == xrings.cend()) {
return false;
}
auto after = std::next(it, 2);
if (after == xrings.cend() || after->location != it->location) {
if (debug()) {
std::cerr << " Merging two rings\n";
}
merge_two_rings(open_ring_its, *it, *std::next(it));
return true;
}
while (it != xrings.cend() && it->location == after->location) {
++it;
}
}
return false;
}
bool there_are_open_rings() const noexcept {
return std::any_of(m_rings.cbegin(), m_rings.cend(), [](const detail::ProtoRing& ring){
return !ring.closed();
});
}
struct candidate {
int64_t sum;
std::vector<std::pair<location_to_ring_map, bool>> rings;
osmium::Location start_location;
osmium::Location stop_location;
explicit candidate(location_to_ring_map& ring, bool reverse) :
sum(ring.ring().sum()),
rings(),
start_location(ring.ring().get_node_ref_start().location()),
stop_location(ring.ring().get_node_ref_stop().location()) {
rings.emplace_back(ring, reverse);
}
bool closed() const noexcept {
return start_location == stop_location;
}
};
void find_candidates(std::vector<candidate>& candidates, std::unordered_set<osmium::Location>& loc_done, const std::vector<location_to_ring_map>& xrings, candidate& cand) {
if (debug()) {
std::cerr << " find_candidates sum=" << cand.sum << " start=" << cand.start_location << " stop=" << cand.stop_location << "\n";
for (const auto& ring : cand.rings) {
std::cerr << " " << ring.first.ring() << (ring.second ? " reverse" : "") << "\n";
}
}
const auto connections = make_range(std::equal_range(xrings.cbegin(),
xrings.cend(),
location_to_ring_map{cand.stop_location}));
assert(connections.begin() != connections.end());
assert(!cand.rings.empty());
const detail::ProtoRing* ring_leading_here = &cand.rings.back().first.ring();
for (const location_to_ring_map& m : connections) {
const detail::ProtoRing& ring = m.ring();
if (&ring != ring_leading_here) {
if (debug()) {
std::cerr << " next possible connection: " << ring << (m.start ? "" : " reverse") << "\n";
}
candidate c = cand;
if (m.start) {
c.rings.emplace_back(m, false);
c.stop_location = ring.get_node_ref_stop().location();
c.sum += ring.sum();
} else {
c.rings.emplace_back(m, true);
c.stop_location = ring.get_node_ref_start().location();
c.sum -= ring.sum();
}
if (c.closed()) {
if (debug()) {
std::cerr << " found candidate\n";
}
candidates.push_back(c);
} else if (loc_done.count(c.stop_location) == 0) {
if (debug()) {
std::cerr << " recurse...\n";
}
loc_done.insert(c.stop_location);
find_candidates(candidates, loc_done, xrings, c);
if (debug()) {
std::cerr << " ...back\n";
}
} else if (debug()) {
std::cerr << " loop found\n";
}
}
}
}
/**
* If there are multiple open rings and mltiple ways to join them,
* this function is called. It will take the first open ring and
* try recursively all ways of closing it. Of all the candidates
* the one with the smallest area is chosen and closed. If it
* can't close this ring, an error is reported and the function
* returns false.
*/
bool join_connected_rings(open_ring_its_type& open_ring_its) {
assert(!open_ring_its.empty());
if (debug()) {
std::cerr << " Trying to merge " << open_ring_its.size() << " open rings\n";
}
std::vector<location_to_ring_map> xrings = create_location_to_ring_map(open_ring_its);
const auto ring_min = std::min_element(xrings.begin(), xrings.end(), [](const location_to_ring_map& lhs, const location_to_ring_map& rhs) {
return lhs.ring().min_segment() < rhs.ring().min_segment();
});
find_inner_outer_complex();
detail::ProtoRing* outer_ring = find_enclosing_ring(ring_min->ring().min_segment());
bool ring_min_is_outer = !outer_ring;
if (debug()) {
std::cerr << " Open ring is " << (ring_min_is_outer ? "outer" : "inner") << " ring\n";
}
for (auto& ring : m_rings) {
ring.reset();
}
candidate cand{*ring_min, false};
// Locations we have visited while finding candidates, used
// to detect loops.
std::unordered_set<osmium::Location> loc_done;
loc_done.insert(cand.stop_location);
std::vector<candidate> candidates;
find_candidates(candidates, loc_done, xrings, cand);
if (candidates.empty()) {
if (debug()) {
std::cerr << " Found no candidates\n";
}
if (!open_ring_its.empty()) {
++m_stats.open_rings;
if (m_config.problem_reporter) {
for (auto& it : open_ring_its) {
m_config.problem_reporter->report_ring_not_closed(it->get_node_ref_start());
m_config.problem_reporter->report_ring_not_closed(it->get_node_ref_stop());
}
}
}
return false;
}
if (debug()) {
std::cerr << " Found candidates:\n";
for (const auto& cand : candidates) {
std::cerr << " sum=" << cand.sum << "\n";
for (const auto& ring : cand.rings) {
std::cerr << " " << ring.first.ring() << (ring.second ? " reverse" : "") << "\n";
}
}
}
// Find the candidate with the smallest/largest area
const auto chosen_cand = ring_min_is_outer ?
std::min_element(candidates.cbegin(), candidates.cend(), [](const candidate& lhs, const candidate& rhs) {
return std::abs(lhs.sum) < std::abs(rhs.sum);
}) :
std::max_element(candidates.cbegin(), candidates.cend(), [](const candidate& lhs, const candidate& rhs) {
return std::abs(lhs.sum) < std::abs(rhs.sum);
});
if (debug()) {
std::cerr << " Decided on: sum=" << chosen_cand->sum << "\n";
for (const auto& ring : chosen_cand->rings) {
std::cerr << " " << ring.first.ring() << (ring.second ? " reverse" : "") << "\n";
}
}
// Join all (open) rings in the candidate to get one closed ring.
assert(chosen_cand->rings.size() > 1);
const auto& first_ring = chosen_cand->rings.front().first;
for (auto it = chosen_cand->rings.begin() + 1; it != chosen_cand->rings.end(); ++it) {
merge_two_rings(open_ring_its, first_ring, it->first);
}
if (debug()) {
std::cerr << " Merged to " << first_ring.ring() << "\n";
}
return true;
}
bool create_rings_complex_case() {
// First create all the (partial) rings starting at the split locations
auto count_remaining = m_segment_list.size();
for (const osmium::Location& location : m_split_locations) {
const auto locs = make_range(std::equal_range(m_locations.begin(),
m_locations.end(),
slocation{},
[this, &location](const slocation& lhs, const slocation& rhs) {
return lhs.location(m_segment_list, location) < rhs.location(m_segment_list, location);
}));
for (auto& loc : locs) {
if (!m_segment_list[loc.item].is_done()) {
count_remaining -= add_new_ring_complex(loc);
if (count_remaining == 0) {
break;
}
}
}
}
// Now find all the rest of the rings (ie not starting at split locations)
if (count_remaining > 0) {
for (slocation& sl : m_locations) {
const detail::NodeRefSegment& segment = m_segment_list[sl.item];
if (!segment.is_done()) {
count_remaining -= add_new_ring_complex(sl);
if (count_remaining == 0) {
break;
}
}
}
}
// Now all segments are in exactly one (partial) ring.
// If there are open rings, try to join them to create closed
// rings.
if (there_are_open_rings()) {
++m_stats.area_really_complex_case;
open_ring_its_type open_ring_its;
for (auto it = m_rings.begin(); it != m_rings.end(); ++it) {
if (!it->closed()) {
open_ring_its.push_back(it);
}
}
while (!open_ring_its.empty()) {
if (debug()) {
std::cerr << " There are " << open_ring_its.size() << " open rings\n";
}
while (try_to_merge(open_ring_its));
if (!open_ring_its.empty()) {
if (debug()) {
std::cerr << " After joining obvious cases there are still " << open_ring_its.size() << " open rings\n";
}
if (!join_connected_rings(open_ring_its)) {
return false;
}
}
}
if (debug()) {
std::cerr << " Joined all open rings\n";
}
}
// Now all rings are complete.
find_inner_outer_complex();
return true;
}
/**
* Checks if any ways were completely removed in the
* erase_duplicate_segments step.
*/
bool ways_were_lost() {
std::unordered_set<const osmium::Way*> ways_in_segments;
for (const auto& segment : m_segment_list) {
ways_in_segments.insert(segment.way());
}
return ways_in_segments.size() < m_num_members;
}
/**
* Create rings from segments.
*/
bool create_rings() {
m_stats.nodes += m_segment_list.size();
// Sort the list of segments (from left to right and bottom
// to top).
osmium::Timer timer_sort;
m_segment_list.sort();
timer_sort.stop();
// Remove duplicate segments. Removal is in pairs, so if there
// are two identical segments, they will both be removed. If
// there are three, two will be removed and one remains.
osmium::Timer timer_dupl;
m_stats.duplicate_segments = m_segment_list.erase_duplicate_segments(m_config.problem_reporter);
timer_dupl.stop();
// If there are no segments left at this point, this isn't
// a valid area.
if (m_segment_list.empty()) {
if (debug()) {
std::cerr << " No segments left\n";
}
return false;
}
// If one or more complete ways was removed because of
// duplicate segments, this isn't a valid area.
if (ways_were_lost()) {
if (debug()) {
std::cerr << " Complete ways removed because of duplicate segments\n";
}
return false;
}
if (m_config.debug_level >= 3) {
std::cerr << "Sorted de-duplicated segment list:\n";
for (const auto& s : m_segment_list) {
std::cerr << " " << s << "\n";
}
}
// Now we look for segments crossing each other. If there are
// any, the multipolygon is invalid.
// In the future this could be improved by trying to fix those
// cases.
osmium::Timer timer_intersection;
m_stats.intersections = m_segment_list.find_intersections(m_config.problem_reporter);
timer_intersection.stop();
if (m_stats.intersections) {
return false;
}
// This creates an ordered list of locations of both endpoints
// of all segments with pointers back to the segments. We will
// use this list later to quickly find which segment(s) fits
// onto a known segment.
osmium::Timer timer_locations_list;
create_locations_list();
timer_locations_list.stop();
// Find all locations where more than two segments start or
// end. We call those "split" locations. If there are any
// "spike" segments found while doing this, we know the area
// geometry isn't valid and return.
osmium::Timer timer_split;
if (!find_split_locations()) {
return false;
}
timer_split.stop();
// Now report all split locations to the problem reporter.
m_stats.touching_rings += m_split_locations.size();
if (!m_split_locations.empty()) {
if (debug()) {
std::cerr << " Found split locations:\n";
}
for (const auto& location : m_split_locations) {
if (m_config.problem_reporter) {
auto it = std::lower_bound(m_locations.cbegin(), m_locations.cend(), slocation{}, [this, &location](const slocation& lhs, const slocation& rhs) {
return lhs.location(m_segment_list, location) < rhs.location(m_segment_list, location);
});
assert(it != m_locations.cend());
const osmium::object_id_type id = it->node_ref(m_segment_list).ref();
m_config.problem_reporter->report_touching_ring(id, location);
}
if (debug()) {
std::cerr << " " << location << "\n";
}
}
}
// From here on we use two different algorithms depending on
// whether there were any split locations or not. If there
// are no splits, we use the faster "simple algorithm", if
// there are, we use the slower "complex algorithm".
osmium::Timer timer_simple_case;
osmium::Timer timer_complex_case;
if (m_split_locations.empty()) {
if (debug()) {
std::cerr << " No split locations -> using simple algorithm\n";
}
++m_stats.area_simple_case;
timer_simple_case.start();
create_rings_simple_case();
timer_simple_case.stop();
} else {
if (debug()) {
std::cerr << " Found split locations -> using complex algorithm\n";
}
++m_stats.area_touching_rings_case;
timer_complex_case.start();
if (!create_rings_complex_case()) {
return false;
}
timer_complex_case.stop();
}
// If the assembler was so configured, now check whether the
// member roles are correctly tagged.
if (m_config.check_roles && m_stats.from_relations) {
osmium::Timer timer_roles;
check_inner_outer_roles();
timer_roles.stop();
}
m_stats.outer_rings = std::count_if(m_rings.cbegin(), m_rings.cend(), [](const detail::ProtoRing& ring){
return ring.is_outer();
});
m_stats.inner_rings = m_rings.size() - m_stats.outer_rings;
#ifdef OSMIUM_WITH_TIMER
std::cout << m_stats.nodes << ' ' << m_stats.outer_rings << ' ' << m_stats.inner_rings <<
' ' << timer_sort.elapsed_microseconds() <<
' ' << timer_dupl.elapsed_microseconds() <<
' ' << timer_intersection.elapsed_microseconds() <<
' ' << timer_locations_list.elapsed_microseconds() <<
' ' << timer_split.elapsed_microseconds();
if (m_split_locations.empty()) {
std::cout << ' ' << timer_simple_case.elapsed_microseconds() <<
" 0";
} else {
std::cout << " 0" <<
' ' << timer_complex_case.elapsed_microseconds();
}
std::cout <<
# ifdef OSMIUM_AREA_CHECK_INNER_OUTER_ROLES
' ' << timer_roles.elapsed_microseconds() <<
# else
" 0" <<
# endif
'\n';
#endif
return true;
}
#ifdef OSMIUM_WITH_TIMER
static bool print_header() {
std::cout << "nodes outer_rings inner_rings sort dupl intersection locations split simple_case complex_case roles_check\n";
return true;
}
static bool init_header() {
static bool printed_print_header = print_header();
return printed_print_header;
}
#endif
bool create_area(osmium::memory::Buffer& out_buffer, const osmium::Way& way) {
osmium::builder::AreaBuilder builder{out_buffer};
builder.initialize_from_object(way);
const bool area_okay = create_rings();
if (area_okay || m_config.create_empty_areas) {
add_tags_to_area(builder, way);
}
if (area_okay) {
add_rings_to_area(builder);
}
if (report_ways()) {
m_config.problem_reporter->report_way(way);
}
return area_okay || m_config.create_empty_areas;
}
bool create_area(osmium::memory::Buffer& out_buffer, const osmium::Relation& relation, const std::vector<const osmium::Way*>& members) {
m_num_members = members.size();
osmium::builder::AreaBuilder builder{out_buffer};
builder.initialize_from_object(relation);
const bool area_okay = create_rings();
if (area_okay || m_config.create_empty_areas) {
add_tags_to_area(builder, relation);
}
if (area_okay) {
add_rings_to_area(builder);
}
if (report_ways()) {
for (const osmium::Way* way : members) {
m_config.problem_reporter->report_way(*way);
}
}
return area_okay || m_config.create_empty_areas;
}
public:
using config_type = osmium::area::AssemblerConfig;
explicit Assembler(const config_type& config) :
m_config(config),
m_segment_list(config.debug_level > 1) {
#ifdef OSMIUM_WITH_TIMER
init_header();
#endif
}
~Assembler() noexcept = default;
/**
* Assemble an area from the given way.
* The resulting area is put into the out_buffer.
*/
void operator()(const osmium::Way& way, osmium::memory::Buffer& out_buffer) {
if (!m_config.create_way_polygons) {
return;
}
if (way.tags().has_tag("area", "no")) {
return;
}
if (m_config.problem_reporter) {
m_config.problem_reporter->set_object(osmium::item_type::way, way.id());
m_config.problem_reporter->set_nodes(way.nodes().size());
}
// Ignore (but count) ways without segments.
if (way.nodes().size() < 2) {
++m_stats.short_ways;
return;
}
if (!way.ends_have_same_id()) {
++m_stats.duplicate_nodes;
if (m_config.problem_reporter) {
m_config.problem_reporter->report_duplicate_node(way.nodes().front().ref(), way.nodes().back().ref(), way.nodes().front().location());
}
}
++m_stats.from_ways;
m_stats.duplicate_nodes += m_segment_list.extract_segments_from_way(m_config.problem_reporter, way);
if (m_config.debug_level > 0) {
std::cerr << "\nAssembling way " << way.id() << " containing " << m_segment_list.size() << " nodes\n";
}
// Now create the Area object and add the attributes and tags
// from the way.
if (create_area(out_buffer, way)) {
out_buffer.commit();
} else {
out_buffer.rollback();
}
if (debug()) {
std::cerr << "Done: " << m_stats << "\n";
}
}
/**
* Assemble an area from the given relation and its members.
* All members are to be found in the in_buffer at the offsets
* given by the members parameter.
* The resulting area is put into the out_buffer.
*
* @deprecated
* This function is deprecated. Use the other form of the function
* instead.
*/
OSMIUM_DEPRECATED void operator()(const osmium::Relation& relation, const std::vector<size_t>& members, const osmium::memory::Buffer& in_buffer, osmium::memory::Buffer& out_buffer) {
std::vector<const osmium::Way*> ways;
for (size_t offset : members) {
const osmium::Way& way = in_buffer.get<const osmium::Way>(offset);
ways.push_back(&way);
}
operator()(relation, ways, out_buffer);
}
/**
* Assemble an area from the given relation and its members.
* The resulting area is put into the out_buffer.
*/
void operator()(const osmium::Relation& relation, const std::vector<const osmium::Way*>& members, osmium::memory::Buffer& out_buffer) {
assert(relation.members().size() >= members.size());
if (m_config.problem_reporter) {
m_config.problem_reporter->set_object(osmium::item_type::relation, relation.id());
}
if (relation.members().empty()) {
++m_stats.no_way_in_mp_relation;
return;
}
++m_stats.from_relations;
m_stats.duplicate_nodes += m_segment_list.extract_segments_from_ways(m_config.problem_reporter, relation, members);
m_stats.member_ways = members.size();
if (m_stats.member_ways == 1) {
++m_stats.single_way_in_mp_relation;
}
if (m_config.debug_level > 0) {
std::cerr << "\nAssembling relation " << relation.id() << " containing " << members.size() << " way members with " << m_segment_list.size() << " nodes\n";
}
const size_t area_offset = out_buffer.committed();
// Now create the Area object and add the attributes and tags
// from the relation.
if (create_area(out_buffer, relation, members)) {
if ((m_config.create_new_style_polygons && m_stats.no_tags_on_relation == 0) ||
(m_config.create_old_style_polygons && m_stats.no_tags_on_relation != 0)) {
out_buffer.commit();
} else {
out_buffer.rollback();
}
} else {
out_buffer.rollback();
}
const osmium::TagList& area_tags = out_buffer.get<osmium::Area>(area_offset).tags(); // tags of the area we just built
// Find all closed ways that are inner rings and check their
// tags. If they are not the same as the tags of the area we
// just built, add them to a list and later build areas for
// them, too.
std::vector<const osmium::Way*> ways_that_should_be_areas;
if (m_stats.wrong_role == 0) {
detail::for_each_member(relation, members, [this, &ways_that_should_be_areas, &area_tags](const osmium::RelationMember& member, const osmium::Way& way) {
if (!std::strcmp(member.role(), "inner")) {
if (!way.nodes().empty() && way.is_closed() && way.tags().size() > 0) {
const auto d = std::count_if(way.tags().cbegin(), way.tags().cend(), filter());
if (d > 0) {
osmium::tags::KeyFilter::iterator way_fi_begin(filter(), way.tags().cbegin(), way.tags().cend());
osmium::tags::KeyFilter::iterator way_fi_end(filter(), way.tags().cend(), way.tags().cend());
osmium::tags::KeyFilter::iterator area_fi_begin(filter(), area_tags.cbegin(), area_tags.cend());
osmium::tags::KeyFilter::iterator area_fi_end(filter(), area_tags.cend(), area_tags.cend());
if (!std::equal(way_fi_begin, way_fi_end, area_fi_begin) || d != std::distance(area_fi_begin, area_fi_end)) {
ways_that_should_be_areas.push_back(&way);
} else {
++m_stats.inner_with_same_tags;
if (m_config.problem_reporter) {
m_config.problem_reporter->report_inner_with_same_tags(way);
}
}
}
}
}
});
}
if (debug()) {
std::cerr << "Done: " << m_stats << "\n";
}
// Now build areas for all ways found in the last step.
for (const osmium::Way* way : ways_that_should_be_areas) {
Assembler assembler(m_config);
assembler(*way, out_buffer);
}
}
/**
* Get statistics from assembler. Call this after running the
* assembler to get statistics and data about errors.
*/
const osmium::area::area_stats& stats() const noexcept {
return m_stats;
}
}; // class Assembler
} // namespace area
} // namespace osmium
#endif // OSMIUM_AREA_ASSEMBLER_HPP