2016-03-01 11:56:55 -05:00
|
|
|
#ifndef OSMIUM_AREA_ASSEMBLER_HPP
|
|
|
|
#define OSMIUM_AREA_ASSEMBLER_HPP
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
This file is part of Osmium (http://osmcode.org/libosmium).
|
|
|
|
|
|
|
|
Copyright 2013-2016 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>
|
2016-10-03 13:08:59 -04:00
|
|
|
#include <cstdint>
|
|
|
|
#include <cstdlib>
|
2016-03-01 11:56:55 -05:00
|
|
|
#include <cstring>
|
|
|
|
#include <iostream>
|
|
|
|
#include <iterator>
|
|
|
|
#include <list>
|
|
|
|
#include <set>
|
|
|
|
#include <string>
|
|
|
|
#include <map>
|
2016-10-03 13:08:59 -04:00
|
|
|
#include <unordered_map>
|
|
|
|
#include <unordered_set>
|
|
|
|
#include <utility>
|
2016-03-01 11:56:55 -05:00
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
#include <osmium/builder/osm_object_builder.hpp>
|
|
|
|
#include <osmium/memory/buffer.hpp>
|
|
|
|
#include <osmium/osm/area.hpp>
|
2016-10-03 13:08:59 -04:00
|
|
|
#include <osmium/osm/item_type.hpp>
|
2016-03-01 11:56:55 -05:00
|
|
|
#include <osmium/osm/location.hpp>
|
2016-10-03 13:08:59 -04:00
|
|
|
#include <osmium/osm/node_ref.hpp>
|
2016-03-01 11:56:55 -05:00
|
|
|
#include <osmium/osm/relation.hpp>
|
2016-10-03 13:08:59 -04:00
|
|
|
#include <osmium/osm/tag.hpp>
|
|
|
|
#include <osmium/osm/types.hpp>
|
|
|
|
#include <osmium/osm/way.hpp>
|
2016-03-01 11:56:55 -05:00
|
|
|
#include <osmium/tags/filter.hpp>
|
2016-10-03 13:08:59 -04:00
|
|
|
#include <osmium/util/compatibility.hpp>
|
|
|
|
#include <osmium/util/iterator.hpp>
|
|
|
|
#include <osmium/util/timer.hpp>
|
2016-03-01 11:56:55 -05:00
|
|
|
|
|
|
|
#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>
|
2016-10-03 13:08:59 -04:00
|
|
|
#include <osmium/area/stats.hpp>
|
2016-03-01 11:56:55 -05:00
|
|
|
|
|
|
|
namespace osmium {
|
|
|
|
|
|
|
|
namespace area {
|
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*/
|
2016-03-01 11:56:55 -05:00
|
|
|
struct AssemblerConfig {
|
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
/**
|
|
|
|
* 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;
|
2016-03-01 11:56:55 -05:00
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
AssemblerConfig() noexcept = default;
|
2016-03-01 11:56:55 -05:00
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
/**
|
|
|
|
* Constructor
|
|
|
|
* @deprecated Use default constructor and set values afterwards.
|
|
|
|
*/
|
|
|
|
explicit AssemblerConfig(osmium::area::ProblemReporter* pr, bool d = false) :
|
2016-03-01 11:56:55 -05:00
|
|
|
problem_reporter(pr),
|
2016-10-03 13:08:59 -04:00
|
|
|
debug_level(d) {
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Enable or disable debug output to stderr. This is for Osmium
|
|
|
|
* developers only.
|
2016-10-03 13:08:59 -04:00
|
|
|
*
|
|
|
|
* @deprecated Set debug_level directly.
|
2016-03-01 11:56:55 -05:00
|
|
|
*/
|
2016-10-03 13:08:59 -04:00
|
|
|
OSMIUM_DEPRECATED void enable_debug_output(bool d = true) {
|
|
|
|
debug_level = d;
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
}; // struct AssemblerConfig
|
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
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& a, const location_to_ring_map& b) noexcept {
|
|
|
|
return a.location == b.location;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline bool operator<(const location_to_ring_map& a, const location_to_ring_map& b) noexcept {
|
|
|
|
return a.location < b.location;
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace detail
|
|
|
|
|
2016-03-01 11:56:55 -05:00
|
|
|
/**
|
2016-10-03 13:08:59 -04:00
|
|
|
* Assembles area objects from closed ways or multipolygon relations
|
|
|
|
* and their members.
|
2016-03-01 11:56:55 -05:00
|
|
|
*/
|
|
|
|
class Assembler {
|
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
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
|
2016-03-01 11:56:55 -05:00
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
// Configuration settings for this Assembler
|
|
|
|
const AssemblerConfig& m_config;
|
|
|
|
|
|
|
|
// List of segments (connection between two nodes)
|
2016-03-01 11:56:55 -05:00
|
|
|
osmium::area::detail::SegmentList m_segment_list;
|
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
// The rings we are building from the segments
|
|
|
|
std::list<detail::ProtoRing> m_rings;
|
|
|
|
|
|
|
|
// All node locations
|
|
|
|
std::vector<slocation> m_locations;
|
2016-03-01 11:56:55 -05:00
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
// All locations where more than two segments start/end
|
|
|
|
std::vector<Location> m_split_locations;
|
2016-03-01 11:56:55 -05:00
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
// Statistics
|
|
|
|
area_stats m_stats;
|
2016-03-01 11:56:55 -05:00
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
// The number of members the multipolygon relation has
|
|
|
|
size_t m_num_members = 0;
|
|
|
|
|
|
|
|
bool debug() const noexcept {
|
|
|
|
return m_config.debug_level > 1;
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
bool report_ways() const noexcept {
|
|
|
|
if (!m_config.problem_reporter) {
|
2016-03-01 11:56:55 -05:00
|
|
|
return false;
|
|
|
|
}
|
2016-10-03 13:08:59 -04:00
|
|
|
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;
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
void add_tags_to_area(osmium::builder::AreaBuilder& builder, const osmium::Way& way) const {
|
2016-10-03 13:08:59 -04:00
|
|
|
builder.add_item(&way.tags());
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
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];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
const size_t num_ways = ways.size();
|
2016-03-01 11:56:55 -05:00
|
|
|
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) {
|
2016-10-03 13:08:59 -04:00
|
|
|
const size_t len = std::strlen(t_c.first.c_str());
|
2016-03-01 11:56:55 -05:00
|
|
|
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
|
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
static const MPFilter& filter() noexcept {
|
|
|
|
static const MPFilter filter;
|
2016-03-01 11:56:55 -05:00
|
|
|
return filter;
|
|
|
|
}
|
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
static void copy_tags_without_type(osmium::builder::AreaBuilder& builder, const osmium::TagList& tags) {
|
|
|
|
osmium::builder::TagListBuilder tl_builder(builder.buffer(), &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());
|
2016-03-01 11:56:55 -05:00
|
|
|
|
|
|
|
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";
|
|
|
|
}
|
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
if (m_config.keep_type_tag) {
|
|
|
|
builder.add_item(&relation.tags());
|
|
|
|
} else {
|
|
|
|
copy_tags_without_type(builder, relation.tags());
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
|
|
|
} else {
|
2016-10-03 13:08:59 -04:00
|
|
|
++m_stats.no_tags_on_relation;
|
2016-03-01 11:56:55 -05:00
|
|
|
if (debug()) {
|
|
|
|
std::cerr << " use tags from outer ways\n";
|
|
|
|
}
|
|
|
|
std::set<const osmium::Way*> ways;
|
2016-10-03 13:08:59 -04:00
|
|
|
for (const auto& ring : m_rings) {
|
|
|
|
if (ring.is_outer()) {
|
|
|
|
ring.get_ways(ways);
|
|
|
|
}
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
|
|
|
if (ways.size() == 1) {
|
|
|
|
if (debug()) {
|
|
|
|
std::cerr << " only one outer way\n";
|
|
|
|
}
|
2016-10-03 13:08:59 -04:00
|
|
|
builder.add_item(&(*ways.cbegin())->tags());
|
2016-03-01 11:56:55 -05:00
|
|
|
} else {
|
|
|
|
if (debug()) {
|
|
|
|
std::cerr << " multiple outer ways, get common tags\n";
|
|
|
|
}
|
|
|
|
osmium::builder::TagListBuilder tl_builder(builder.buffer(), &builder);
|
|
|
|
add_common_tags(tl_builder, ways);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
template <typename TBuilder>
|
|
|
|
static void build_ring_from_proto_ring(osmium::builder::AreaBuilder& builder, const detail::ProtoRing& ring) {
|
|
|
|
TBuilder ring_builder(builder.buffer(), &builder);
|
|
|
|
ring_builder.add_node_ref(ring.get_node_ref_start());
|
|
|
|
for (const auto& segment : ring.segments()) {
|
|
|
|
ring_builder.add_node_ref(segment->stop());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-01 11:56:55 -05:00
|
|
|
/**
|
2016-10-03 13:08:59 -04:00
|
|
|
* Append each outer ring together with its inner rings to the
|
|
|
|
* area in the buffer.
|
2016-03-01 11:56:55 -05:00
|
|
|
*/
|
2016-10-03 13:08:59 -04:00
|
|
|
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);
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
void check_inner_outer_roles() {
|
2016-03-01 11:56:55 -05:00
|
|
|
if (debug()) {
|
2016-10-03 13:08:59 -04:00
|
|
|
std::cerr << " Checking inner/outer roles\n";
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
2016-10-03 13:08:59 -04:00
|
|
|
|
|
|
|
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;
|
2016-03-01 11:56:55 -05:00
|
|
|
if (debug()) {
|
2016-10-03 13:08:59 -04:00
|
|
|
std::cerr << " Segment " << *segment << " from way " << segment->way()->id() << " has role '" << segment->role_name()
|
|
|
|
<< "', but should have role '" << (ring.is_outer() ? "outer" : "inner") << "'\n";
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
2016-10-03 13:08:59 -04:00
|
|
|
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());
|
|
|
|
}
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
|
|
|
}
|
2016-10-03 13:08:59 -04:00
|
|
|
|
|
|
|
auto& r = way_rings[segment->way()];
|
|
|
|
if (!r) {
|
|
|
|
r = ˚
|
|
|
|
} else if (r != &ring) {
|
|
|
|
ways_in_multiple_rings.insert(segment->way());
|
|
|
|
}
|
|
|
|
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
|
|
|
}
|
2016-10-03 13:08:59 -04:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
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& a, const slocation& b) {
|
|
|
|
return a.location(m_segment_list, location) < b.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;
|
|
|
|
}
|
2016-03-01 11:56:55 -05:00
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
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) {
|
2016-03-01 11:56:55 -05:00
|
|
|
if (debug()) {
|
2016-10-03 13:08:59 -04:00
|
|
|
std::cerr << " Looking for ring enclosing " << *segment << "\n";
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
2016-10-03 13:08:59 -04:00
|
|
|
|
|
|
|
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;
|
2016-03-01 11:56:55 -05:00
|
|
|
if (debug()) {
|
2016-10-03 13:08:59 -04:00
|
|
|
std::cerr << " Segment is below (nesting=" << nesting << ")\n";
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
2016-10-03 13:08:59 -04:00
|
|
|
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";
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
2016-10-03 13:08:59 -04:00
|
|
|
|
|
|
|
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;
|
2016-03-01 11:56:55 -05:00
|
|
|
if (debug()) {
|
2016-10-03 13:08:59 -04:00
|
|
|
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());
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-10-03 13:08:59 -04:00
|
|
|
--segment;
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
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();
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
2016-10-03 13:08:59 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
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());
|
|
|
|
|
2016-03-01 11:56:55 -05:00
|
|
|
if (debug()) {
|
2016-10-03 13:08:59 -04:00
|
|
|
std::cerr << " Starting new ring at location " << node.location(m_segment_list) << " with segment " << *segment << "\n";
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
if (node.reverse) {
|
|
|
|
segment->reverse();
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
2016-10-03 13:08:59 -04:00
|
|
|
|
|
|
|
detail::ProtoRing* outer_ring = nullptr;
|
|
|
|
|
|
|
|
if (segment != &m_segment_list.front()) {
|
|
|
|
outer_ring = find_enclosing_ring(segment);
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
2016-10-03 13:08:59 -04:00
|
|
|
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";
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
2016-10-03 13:08:59 -04:00
|
|
|
outer_ring->add_inner_ring(ring);
|
|
|
|
ring->set_outer_ring(outer_ring);
|
|
|
|
} else if (debug()) {
|
|
|
|
std::cerr << " This is an outer ring\n";
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
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();
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
2016-10-03 13:08:59 -04:00
|
|
|
|
|
|
|
ring->fix_direction();
|
|
|
|
|
2016-03-01 11:56:55 -05:00
|
|
|
if (debug()) {
|
2016-10-03 13:08:59 -04:00
|
|
|
std::cerr << " Completed ring: " << *ring << "\n";
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
2016-10-03 13:08:59 -04:00
|
|
|
|
|
|
|
return nodes;
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
uint32_t add_new_ring_complex(slocation& node) {
|
|
|
|
detail::NodeRefSegment* segment = &m_segment_list[node.item];
|
|
|
|
assert(!segment->is_done());
|
|
|
|
|
2016-03-01 11:56:55 -05:00
|
|
|
if (debug()) {
|
2016-10-03 13:08:59 -04:00
|
|
|
std::cerr << " Starting new ring at location " << node.location(m_segment_list) << " with segment " << *segment << "\n";
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
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();
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
if (debug()) {
|
2016-10-03 13:08:59 -04:00
|
|
|
if (first_location == last_location) {
|
|
|
|
std::cerr << " Completed ring: " << *ring << "\n";
|
|
|
|
} else {
|
|
|
|
std::cerr << " Completed partial ring: " << *ring << "\n";
|
|
|
|
}
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
return nodes;
|
|
|
|
}
|
2016-03-01 11:56:55 -05:00
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
void create_locations_list() {
|
|
|
|
m_locations.reserve(m_segment_list.size() * 2);
|
2016-03-01 11:56:55 -05:00
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
for (uint32_t n = 0; n < m_segment_list.size(); ++n) {
|
|
|
|
m_locations.emplace_back(n, false);
|
|
|
|
m_locations.emplace_back(n, true);
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
std::stable_sort(m_locations.begin(), m_locations.end(), [this](const slocation& a, const slocation& b) {
|
|
|
|
return a.location(m_segment_list) < b.location(m_segment_list);
|
|
|
|
});
|
|
|
|
}
|
2016-03-01 11:56:55 -05:00
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
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();
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
void find_inner_outer_complex() {
|
2016-03-01 11:56:55 -05:00
|
|
|
if (debug()) {
|
2016-10-03 13:08:59 -04:00
|
|
|
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);
|
|
|
|
}
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
2016-10-03 13:08:59 -04:00
|
|
|
|
|
|
|
if (rings.empty()) {
|
|
|
|
return;
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
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();
|
2016-03-01 11:56:55 -05:00
|
|
|
if (debug()) {
|
2016-10-03 13:08:59 -04:00
|
|
|
std::cerr << " First ring is outer: " << *rings.front() << "\n";
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
2016-10-03 13:08:59 -04:00
|
|
|
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";
|
|
|
|
}
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2016-10-03 13:08:59 -04:00
|
|
|
* 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.
|
2016-03-01 11:56:55 -05:00
|
|
|
*/
|
2016-10-03 13:08:59 -04:00
|
|
|
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;
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
|
|
|
}
|
2016-10-03 13:08:59 -04:00
|
|
|
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;
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
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) {
|
2016-03-01 11:56:55 -05:00
|
|
|
if (debug()) {
|
2016-10-03 13:08:59 -04:00
|
|
|
std::cerr << " Ring: " << **it << "\n";
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
2016-10-03 13:08:59 -04:00
|
|
|
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) {
|
2016-03-01 11:56:55 -05:00
|
|
|
if (debug()) {
|
2016-10-03 13:08:59 -04:00
|
|
|
std::cerr << " Merging two rings\n";
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
2016-10-03 13:08:59 -04:00
|
|
|
merge_two_rings(open_ring_its, *it, *std::next(it));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
while (it != xrings.cend() && it->location == after->location) {
|
|
|
|
++it;
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
|
|
|
}
|
2016-10-03 13:08:59 -04:00
|
|
|
|
2016-03-01 11:56:55 -05:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
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) {
|
2016-03-01 11:56:55 -05:00
|
|
|
if (debug()) {
|
2016-10-03 13:08:59 -04:00
|
|
|
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";
|
|
|
|
}
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
const auto connections = make_range(std::equal_range(xrings.cbegin(),
|
|
|
|
xrings.cend(),
|
|
|
|
location_to_ring_map{cand.stop_location}));
|
2016-03-01 11:56:55 -05:00
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
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) {
|
2016-03-01 11:56:55 -05:00
|
|
|
if (debug()) {
|
2016-10-03 13:08:59 -04:00
|
|
|
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();
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
2016-10-03 13:08:59 -04:00
|
|
|
if (c.closed()) {
|
2016-03-01 11:56:55 -05:00
|
|
|
if (debug()) {
|
2016-10-03 13:08:59 -04:00
|
|
|
std::cerr << " found candidate\n";
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
2016-10-03 13:08:59 -04:00
|
|
|
candidates.push_back(c);
|
|
|
|
} else if (loc_done.count(c.stop_location) == 0) {
|
2016-03-01 11:56:55 -05:00
|
|
|
if (debug()) {
|
2016-10-03 13:08:59 -04:00
|
|
|
std::cerr << " recurse...\n";
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
2016-10-03 13:08:59 -04:00
|
|
|
loc_done.insert(c.stop_location);
|
|
|
|
find_candidates(candidates, loc_done, xrings, c);
|
|
|
|
if (debug()) {
|
|
|
|
std::cerr << " ...back\n";
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
2016-10-03 13:08:59 -04:00
|
|
|
} else if (debug()) {
|
|
|
|
std::cerr << " loop found\n";
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
2016-10-03 13:08:59 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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& a, const location_to_ring_map& b) {
|
|
|
|
return a.ring().min_segment() < b.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());
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-10-03 13:08:59 -04:00
|
|
|
return false;
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
if (debug()) {
|
2016-10-03 13:08:59 -04:00
|
|
|
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";
|
|
|
|
}
|
|
|
|
}
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
// 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& a, const candidate& b) {
|
|
|
|
return std::abs(a.sum) < std::abs(b.sum);
|
|
|
|
}) :
|
|
|
|
std::max_element(candidates.cbegin(), candidates.cend(), [](const candidate& a, const candidate& b) {
|
|
|
|
return std::abs(a.sum) < std::abs(b.sum);
|
|
|
|
});
|
2016-03-01 11:56:55 -05:00
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
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);
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
if (debug()) {
|
2016-10-03 13:08:59 -04:00
|
|
|
std::cerr << " Merged to " << first_ring.ring() << "\n";
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
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& a, const slocation& b) {
|
|
|
|
return a.location(m_segment_list, location) < b.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;
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
2016-10-03 13:08:59 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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;
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-10-03 13:08:59 -04:00
|
|
|
|
|
|
|
// 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()) {
|
2016-03-01 11:56:55 -05:00
|
|
|
if (debug()) {
|
2016-10-03 13:08:59 -04:00
|
|
|
std::cerr << " After joining obvious cases there are still " << open_ring_its.size() << " open rings\n";
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
2016-10-03 13:08:59 -04:00
|
|
|
if (!join_connected_rings(open_ring_its)) {
|
|
|
|
return false;
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-10-03 13:08:59 -04:00
|
|
|
|
|
|
|
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());
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
2016-10-03 13:08:59 -04:00
|
|
|
|
|
|
|
return ways_in_segments.size() < m_num_members;
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create rings from segments.
|
|
|
|
*/
|
|
|
|
bool create_rings() {
|
2016-10-03 13:08:59 -04:00
|
|
|
m_stats.nodes += m_segment_list.size();
|
|
|
|
|
|
|
|
// Sort the list of segments (from left to right and bottom
|
|
|
|
// to top).
|
|
|
|
osmium::Timer timer_sort;
|
2016-03-01 11:56:55 -05:00
|
|
|
m_segment_list.sort();
|
2016-10-03 13:08:59 -04:00
|
|
|
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";
|
|
|
|
}
|
|
|
|
}
|
2016-03-01 11:56:55 -05:00
|
|
|
|
|
|
|
// 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.
|
2016-10-03 13:08:59 -04:00
|
|
|
osmium::Timer timer_intersection;
|
|
|
|
m_stats.intersections = m_segment_list.find_intersections(m_config.problem_reporter);
|
|
|
|
timer_intersection.stop();
|
|
|
|
|
|
|
|
if (m_stats.intersections) {
|
2016-03-01 11:56:55 -05:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
// 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()) {
|
2016-03-01 11:56:55 -05:00
|
|
|
if (debug()) {
|
2016-10-03 13:08:59 -04:00
|
|
|
std::cerr << " Found split locations:\n";
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
2016-10-03 13:08:59 -04:00
|
|
|
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& a, const slocation& b) {
|
|
|
|
return a.location(m_segment_list, location) < b.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);
|
|
|
|
}
|
2016-03-01 11:56:55 -05:00
|
|
|
if (debug()) {
|
2016-10-03 13:08:59 -04:00
|
|
|
std::cerr << " " << location << "\n";
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
// 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";
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
2016-10-03 13:08:59 -04:00
|
|
|
++m_stats.area_simple_case;
|
2016-03-01 11:56:55 -05:00
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
timer_simple_case.start();
|
|
|
|
create_rings_simple_case();
|
|
|
|
timer_simple_case.stop();
|
|
|
|
} else {
|
2016-03-01 11:56:55 -05:00
|
|
|
if (debug()) {
|
2016-10-03 13:08:59 -04:00
|
|
|
std::cerr << " Found split locations -> using complex algorithm\n";
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
2016-10-03 13:08:59 -04:00
|
|
|
++m_stats.area_touching_rings_case;
|
|
|
|
|
|
|
|
timer_complex_case.start();
|
|
|
|
if (!create_rings_complex_case()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
timer_complex_case.stop();
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
// 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();
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
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";
|
2016-03-01 11:56:55 -05:00
|
|
|
} else {
|
2016-10-03 13:08:59 -04:00
|
|
|
std::cout << " 0" <<
|
|
|
|
' ' << timer_complex_case.elapsed_microseconds();
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
std::cout <<
|
|
|
|
# ifdef OSMIUM_AREA_CHECK_INNER_OUTER_ROLES
|
|
|
|
' ' << timer_roles.elapsed_microseconds() <<
|
|
|
|
# else
|
|
|
|
" 0" <<
|
|
|
|
# endif
|
|
|
|
'\n';
|
|
|
|
#endif
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
2016-03-01 11:56:55 -05:00
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
#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";
|
2016-03-01 11:56:55 -05:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2016-03-01 11:56:55 -05:00
|
|
|
public:
|
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
using config_type = osmium::area::AssemblerConfig;
|
2016-03-01 11:56:55 -05:00
|
|
|
|
|
|
|
explicit Assembler(const config_type& config) :
|
|
|
|
m_config(config),
|
2016-10-03 13:08:59 -04:00
|
|
|
m_segment_list(config.debug_level > 1) {
|
|
|
|
#ifdef OSMIUM_WITH_TIMER
|
|
|
|
init_header();
|
|
|
|
#endif
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
~Assembler() noexcept = default;
|
2016-03-01 11:56:55 -05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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) {
|
2016-10-03 13:08:59 -04:00
|
|
|
if (!m_config.create_way_polygons) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (way.tags().has_tag("area", "no")) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-03-01 11:56:55 -05:00
|
|
|
if (m_config.problem_reporter) {
|
|
|
|
m_config.problem_reporter->set_object(osmium::item_type::way, way.id());
|
2016-10-03 13:08:59 -04:00
|
|
|
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;
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!way.ends_have_same_id()) {
|
2016-10-03 13:08:59 -04:00
|
|
|
++m_stats.duplicate_nodes;
|
2016-03-01 11:56:55 -05:00
|
|
|
if (m_config.problem_reporter) {
|
|
|
|
m_config.problem_reporter->report_duplicate_node(way.nodes().front().ref(), way.nodes().back().ref(), way.nodes().front().location());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
++m_stats.from_ways;
|
|
|
|
m_stats.duplicate_nodes += m_segment_list.extract_segments_from_way(m_config.problem_reporter, way);
|
2016-03-01 11:56:55 -05:00
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
if (m_config.debug_level > 0) {
|
|
|
|
std::cerr << "\nAssembling way " << way.id() << " containing " << m_segment_list.size() << " nodes\n";
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// Now create the Area object and add the attributes and tags
|
|
|
|
// from the way.
|
2016-10-03 13:08:59 -04:00
|
|
|
if (create_area(out_buffer, way)) {
|
|
|
|
out_buffer.commit();
|
|
|
|
} else {
|
|
|
|
out_buffer.rollback();
|
|
|
|
}
|
2016-03-01 11:56:55 -05:00
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
if (debug()) {
|
|
|
|
std::cerr << "Done: " << m_stats << "\n";
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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.
|
2016-10-03 13:08:59 -04:00
|
|
|
*
|
|
|
|
* @deprecated
|
|
|
|
* This function is deprecated. Use the other form of the function
|
|
|
|
* instead.
|
2016-03-01 11:56:55 -05:00
|
|
|
*/
|
2016-10-03 13:08:59 -04:00
|
|
|
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());
|
|
|
|
|
2016-03-01 11:56:55 -05:00
|
|
|
if (m_config.problem_reporter) {
|
|
|
|
m_config.problem_reporter->set_object(osmium::item_type::relation, relation.id());
|
|
|
|
}
|
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
if (relation.members().empty()) {
|
|
|
|
++m_stats.no_way_in_mp_relation;
|
|
|
|
return;
|
|
|
|
}
|
2016-03-01 11:56:55 -05:00
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
++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";
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
const size_t area_offset = out_buffer.committed();
|
2016-03-01 11:56:55 -05:00
|
|
|
|
|
|
|
// Now create the Area object and add the attributes and tags
|
|
|
|
// from the relation.
|
2016-10-03 13:08:59 -04:00
|
|
|
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();
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
2016-10-03 13:08:59 -04:00
|
|
|
} else {
|
|
|
|
out_buffer.rollback();
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
2016-10-03 13:08:59 -04:00
|
|
|
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")) {
|
2016-03-01 11:56:55 -05:00
|
|
|
if (!way.nodes().empty() && way.is_closed() && way.tags().size() > 0) {
|
2016-10-03 13:08:59 -04:00
|
|
|
const auto d = std::count_if(way.tags().cbegin(), way.tags().cend(), filter());
|
2016-03-01 11:56:55 -05:00
|
|
|
if (d > 0) {
|
2016-10-03 13:08:59 -04:00
|
|
|
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());
|
2016-03-01 11:56:55 -05:00
|
|
|
|
|
|
|
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);
|
2016-10-03 13:08:59 -04:00
|
|
|
} else {
|
|
|
|
++m_stats.inner_with_same_tags;
|
|
|
|
if (m_config.problem_reporter) {
|
|
|
|
m_config.problem_reporter->report_inner_with_same_tags(way);
|
|
|
|
}
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-10-03 13:08:59 -04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (debug()) {
|
|
|
|
std::cerr << "Done: " << m_stats << "\n";
|
2016-03-01 11:56:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-03 13:08:59 -04:00
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
|
2016-03-01 11:56:55 -05:00
|
|
|
}; // class Assembler
|
|
|
|
|
|
|
|
} // namespace area
|
|
|
|
|
|
|
|
} // namespace osmium
|
|
|
|
|
|
|
|
#endif // OSMIUM_AREA_ASSEMBLER_HPP
|