40c4a48 Release v2.10.0 6addb2e Search for protozero also in the place where libosmium was found. 9c0d4bb Consistently use lhs and rhs as parameter names in operators etc. 6179759 Update change log. 894eb7d Explicitly use size_t to get no narrowing conversions. e549e73 Fix CMake config for index_lookup. ccf0bc2 Fix bug where some compilers deduce the wrong type. fc3507d Cleaned and documented index example and renamed to osmium_index_lookup. d3c3036 Rename serdump example to dump_internal and document it. 0e9822e Also forward set_uid_from_signed() function. 29ef95c Rename build_taglist function because it was to similar to build_tag_list. c088dd0 Move static constant out of class so clang will compile it. 606cdc4 Fix CMake build script: Path for finding libosmium. 65f91fe Removed unused typedef. 07174f5 Use condition_variable to tell producer when thread queue is not full. b35e957 Some code cleanup in XML parser. c703dff Fix problem with MemberMeta. 1ccbbef Refactoring CompressionFactory. 6561bd6 Use our own exception type for map factory errors. 56e31fa Throw not_found error directly instead of using helper function. 28230c3 Test empty value for node location store, reorganize tests. 2ba316c Reorganize and fix projection tests. f949485 Don't run quite as many tests with projections because they are slow. 1bad16d Add information about build environment to benchmark output. 99617bb Change proj_geojson benchmark into mercator benchmark. 553b946 Allow optional parameters on Reader in any order. dcc3d8f Factor out some common code. 004d8cd Fix forwarding constructor. 9702978 Fix metadata check. 6cfb6c4 Faster implementation of decode_dense_nodes without reading metdata. 4ba4638 Add additional read_metadata parameter to other Reader constructors. d005937 Optionally ignore metadata when reading file. 63961da Mark all CRC32 update functions as noexcept. cc4ca75 Refactor set_user() function to speed it up. 38d19dc Update comments with file sizes. OSM has grown... f7d0824 Add new benchmark that shows performance when main thread is busy. 25070dc Use const ref params. 88e8d96 Mark add_user() as deprecated. f58d9db Refactor some low-level collection code to clean up code. 4680def Add example showing how to create OSM data out of thin air. d42fd50 Add an example showing how tags in OSM files can be changed. 49bf5bc Add additional constructors to Builders taking a reference to parent. 7b91d63 Change Builder::add_item() to take a reference instead of pointer. 2957e48 Some cleanup of examples. d0b458d Calculate size of object at compile time. 3fbb6e7 Use explicit cast. 1851f3d Remove a test that depends on math details. f6a0802 Various cleanups of example programs. ba4921f Rename add_user() to set_user(). d7637c9 Various cleanups related to builders. 07827bc Fix add_user(). 9a5b395 Also refactor OSMObjectBuilder like ChangesetBuilder before. b1f423c Use call chaining on the builder. e49473d Get rid of ObjectBuilder class. 67d70b9 Refactor ChangesetBuilder::add_user(). 8199c33 Make ChangesetBuilder derive directly from Builder. 61d1b67 Simplify some code. d38467a Change derivation hierarchy of some builder classes. b52f8af Refactor Builder code. d012bfa Refactored some code setting attributes on objects using builder instead. 6a05f60 Also forward set_removed function from builders. 8d63b7d Return *this from Builder setter functions and test it. 72a1266 Update catch.hpp to newest version. 3424a74 Check GEOS version is <= 3.5. aee9f9d Cleanup test code. aef198c Improved asserts in Buffer. a98b9bf Code cleanup in tests. a150466 Use GDAL/OGR instead of GEOS to test our WKB implementation. b04a525 Refactor test. 39aa932 Refactored test_factory_with_projection so it works without GEOS. 648f43a Remove unused dependency on geos from tile test. f1748ae Add setters to Builder classes forwarding to underlying objects. 8166879 In debug mode check that Builders are correctly called and destructed. 1c4257e Call commit() on buffer only after all builders are destructed. 2618636 Add functions to check availability of relation members. b45a4d9 Mark RelationMember::set_ref() as deprecated. 7886771 Move "basic" and "buffer" tests to "osm" and "memory", respectively. b664685 Use functions instead of macros in location test for faster compile. b4929ac Add more tests for number parser. 02662a7 Merge pull request #171 from lonvia/fix-long-exponentials 5344a6c fix parsing of numbers in e-notiation with many post-comma digits 3aeaff3 Add some typedefs to NodeRefList and memory::Collection. e750665 Add iterators to IdSetSmall and add docs and tests to IdSet classes. 50ecb2a Add more features to IdSetDense, including unset and iterator. e3dec78 Make IdSet virtual base class with two implementations. 8ea0153 Use C array instead of std::array in IdSet and clear explicitly. 3ba9905 Deprecate osmium::index::BoolVector in favour of new IdSet. 453d1ca Add osmium::index::IdSet. c78254e Add function to (temporarily) disable the progress bar. 4d88a9f Better document osmium::io::Header class. 320e3af Look for protozero includes in CMake config. 838d25e Allow optional checking for libosmium version number in CMake config. 6ce60c1 Fix entity_bits static_assert() tests. f054731 Update change log. 77ac4c2 Make sleep duration for full queues much smaller. 7e39c01 Make some entity_bits functions constexpr. 69ea72f Fix ~ operator on entity_bits and more tests for entity bits. dafe4cf Update embedded Catch unit test header to version 1.5.7. a41c832 Fixed parsing of location coordinates starting with decimal dot. 6523bae README cosmetics. 229acac Add tests for some examples. f1e753d Merge pull request #163 from sebastic/executable-not-elf-or-script ccea2d5 Remove executable bit from .cpp files. af77fb4 Changelog formatting fixes. git-subtree-dir: third_party/libosmium git-subtree-split: 40c4a48f88d25edace6f0b9e079c306308c7760b
1590 lines
66 KiB
C++
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-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>
|
|
#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 = ˚
|
|
} 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
|