d2c1d872b Release v2.14.0 9c9aedd50 Update embedded catch.hpp to version 1.12.1. f94e70d32 Change comment on namespace closing brace to make clang-tidy happy. ad9f03afc Prefer prefix increment operator. a141b9c0c Update change log. 363fc7a15 Add more XML tests. 303a3090f Refactor XML tests. a1ee5dd56 Make util namespace inline. 6f1709a83 Cleanup progress bar code. 1b02b65e0 Use Location setters taking a string in osmium_tiles example. 7ffdc7618 Disable clang-tidy warning. 882631085 Better implementation of str_to_int. 4cbf53725 Fix another bitwise op on signed integer. 903bfac9d Disable clang-tidy warning. 01de3f518 Don't change location if set_lon() or set_lat() functions throw. 9a27e899d Fix bitwise operation on signed integer. 61650224f Disable some clang-tidy warnings. 9dfa8437e Provide a way to better mock the getenv function in tests. ae2c172a2 Explicitly set integer type for integers in test code. 878850068 Simpler implementation. We don't need to look at errno at all. 8f9b886f5 Formatting cf4760cb0 Use bit operations on unsigned where possible. c7cb4cde8 No conversion needed later if we declare it this way. 01357a540 Add missing inline for free function. 3feef81ee Disable clang-tidy warning in place we can't do anything about. bf083b8ee Avoid binary bitwise operations on signed integers. f70b1c803 Move content of osmium::util::detail namespace into osmium::detail. caf2f0033 Remove use of atoi and atol from code. f564cef07 Formatting. f0885fc73 Disable travis build with sanitizers. 858c58444 Use explicit asserts and then casts, not static_cast_with_assert. a613b1b0a Disable clang-tidy warning. 50e327677 Always build and run data tests on travis. 20d7a09b0 Update submodule to add more XML tests. 6c8f652bf Merge pull request #250 from Nakaner/object-comparison-without-timestamp 38ea437c0 Make XML parser more strict. 7788f5108 Remove object_order_type_id_reverse_version_without_timestamp fe2223d25 Ignore timestamps if only one object has a valid timestamp a4515161a Code cleanup. eb5ea1a0e Use explicit conversions. 105905c45 Only disable warning on non-MSVC compilers. 80fc1b35d Change the way IDs are parsed from strings. 78aedf91e More tests for StringMatcher and disable a clang-tidy warning. 644a00752 Move catch.hpp into its own directory. 19842756c Update to newest osm-testdata submodule. 0402f64d7 Fix the problem with "git submodule" in appveyor. ec4095d32 Cleanup metadata_options code. d7390741e Add static_cast to avoid conversion warning. dd16be1c3 Remove else after throw. ef4d618de Remove unused variable. 0fdb58d13 Check out submodule in appveyor builds. 6406282d3 Merge pull request #248 from Nakaner/metadata-options-more eb138a902 make clang < 3.9 work e703b0d1d make sections their own independent test cases f68888006 detect_available_metadata must be inline b53224588 move metadata_options into osmium namespace edd363cfa make const what could be const 302a485e5 remove unnecessary include, add missing include 93a76e07e give osmium::io::metadata_from_object a better name 91dfbc427 update documentation 453cd6395 replace REQUIRE(!foo) by REQUIRE_FALSE(foo) 247c3b021 replace friend function by setter methods 0a62e589c performance improvement: check first character instead using std::strlen() 73568bbff move ctor of metadata_options which taks an OSMObject out of class 1879c9e47 restore metadata_options::to_string 71a303608 add more unit tests for osmium::io::metadata_options 843cdc89c replace osmium::io::metdata_options::to_string fully by operator<< 6402a8793 Fix bug in code which determines if an OSM object has a valid user. a6d8143ae Move metadata_options out of "detail" subnamespace 61e32df10 add metadata_options::operator|= d5f7cc0ab Use newest osm-testdata submodule. 31e34f10c add operator&=, operator<< and a to_string method to metadata_options 77d507172 Formatting fixes. 1c38b2a61 Update some URLs. Use https where possible. 10066cb3e Merge pull request #247 from Nakaner/issue-245 3c938ce82 Allow version=-1 and changeset=-1 for PBF input. 9a542c133 Add ReaderWithProgressBar class. 49839a1ce Replace strftime by our own implementation. b32b90942 Update osm-testdata version used. f88b6e548 Add more tests. cb7e778f6 Handle NULL geometries in test cases. 1961a9c95 Clean up some test cases. d7f45e071 Update osm-testdata submodule. 73a2ef18f Remove check for lost ways in multipolygon assembler. 1244e498a Update submodule also in msys2 build. 229fc2453 Revert "Make util namespace inline." 9f942d2e9 Revert "Add inline declaration to one util namespace we forgot." 40d4835ea Really fix appveyor build (I hope). 5ae071135 Add inline declaration to one util namespace we forgot. e0916257c Clean up debug message. 2ddc474cc Make util namespace inline. 4b4e25f87 Travis config: Do not install clang-tidy, we are not using it. 5a608fba1 Fix travis build. a075cc323 Fix appveyor build. 41ee9920b Add submodule with osm-testdata repository. 4d42245c0 Revert "Add clang-tidy build to travis." 9df355b64 Fix issue on some compilers. c8a3c00d6 Add clang-tidy build to travis. 2fc80ad17 Fixed clang-tidy config file order. 2adf05267 Fix various clang-tidy warnings 1dca92459 Use the right syntax to override clang-tidy settings in the code. 25baecb46 Fix some issues found by clang-tidy. 54a8de6c0 Disable more clang-tidy tests. de46d562e Also look for clang-tidy-6.0 in CMake config. 2172553d8 Add build with sanitizers on travis. 7e4e45562 Add object comparators ignoring the timestamp. 92fb231a1 Fix test code. e541df0fc Cleanup test code. 5640269da Add comment in appveyor script about unusual parameter. acb872376 Workaround for failing appveyor MSYS2 build. 0634bcf70 Set default value for m_file_size member. 3a2fe5781 Set default values on output options. 43d289fda Make metadata_options constructor explicit. 6e54992b4 Update change log. e54b955cf Update included `catch.hpp` to version 1.12.0. e50b40d53 Update copyright date. 0d8b401b6 Merge branch 'metadata' c7da3a3b6 Remove superfluous has_visibles boolean. 7f74d33d3 Some cleanups of metadata code and fixes test. 5b3c34990 Output XML attributes uid and user independently of each other. 384167334 Merge pull request #243 from Nakaner/metadata_other 733f1916c Change path of nuget installs in appveyor config. e48672999 Debug output from FindOsmium.cmake script is better formatted now. 79663be5d Change appveyor scripts to work without deprecated prebuilt libs. 443f7a9e4 Remove dependency on (win)getopt completely. c133b8b14 Fix osmium_convert: Long options with = syntax. b55daf00a Remove dependency on (win)getopt from area_test and convert examples. 92f7275a9 output visibility directly after the object ID in debug format a771279f0 deprecate format option pbf_add_metadata 18e3f6005 The value "no" should be treated as "none" by metadata_options c8b0b2e28 re-assign metadata_options instead of calling a public method of it 15bc4a200 Add more flexible metadata handling to PBF output b085632b7 add more flexible metadata handling to OPL and debug output a12c65974 Remove now wrong check. 767ad883a Allow PBF DenseNodes with only some attributes to be read. fd5a7eb6c More flexible metadata encoding in OSM files. d16c18581 Add instructions for bug reporting. db52591ab Use __COUNTER__ instead of name for internal dummy name. 89e634dfc Cleanup. 931ec30ca Marks some false positives for clang-tidy google-runtime-int warning. b59ef02a2 More rule of zero/rule of five fixes. 1b72b3386 Pass by value and use std::move. b7d7017cc Handle some clang-tidy google-runtime-int warnings. c043ca016 Fix special functions. 41d9f0795 Use fixed sized ints where possible. 0899a66a7 Revert some changes older compilers don't like. 41e05989a Fix several rule of zero/rule of five issues. 4e78a828f Various fixes based on clang-tidy reports. 636bda372 Default initialize arrays. 466e5aa6a Handle various clang-tidy warnings. 9f47aa07d Disable clang-tidy misc-forwarding-reference-overload warning. 5e97bf29f Mark Reader and Writer non-movable. 3a2221b27 Add minimal tests for thread-safe queue class. affdeed6f Add some tests. 5ed055d50 Handle some warnings regarding "explicit". b9d4c9bf5 Disable clang-tidy cert-err58-cpp warning. 254c25349 Make ADL work for begin()/end() of InputIterator<Reader>. b64cb32ae Disable clang-tidy android-cloexec-* warnings. 6d6934ffc Fix various warnings reported by clang-tidy. 30dcabaed Another travis build fix. 88c59e669 Fix travis build. 2facb461f Fix travis and appveyor builds. bba631a51 Remove protozero from repository. aebe4a914 Use pass by value and std::move in some constructors. c8c24c8fd Name unused parameters in comments. 9c2e321f9 Make deleted special functions public. 265c16284 Fix some problems with older compilers. 174e95211 Modernize class initialization code for some classes. 4e3e584f8 Disable more clang-tidy warnings. 6b53981bb Fix some class initializations. fe1405ef8 Add/remove special functions for some classes. c6dd930d9 Disable cppcoreguidelines-pro-type-static-cast-downcast warning. 0d09210ec Disable misc-unused-parameters warning in clang-tidy. 1dbc43112 Refactor to avoid misc-suspicious-string-compare clang-tidy warning. 91262cc86 Merge pull request #232 from worace/wkt-geojson-polygons 438076b09 Cleanup: Avoid else after return/break. e0368e92c Disable modernize-raw-string-literal and hicpp-no-assembler check. c822888fc Mark C style cast in macro from a library as NOLINT. 662d4a641 Moving a TypedMemoryMapping is noexcept. aec1c4526 Use auto (clang-tidy check modernize-use-auto). 64857589d Consistent include order. d1ce8de00 Disable hicpp-invalid-access-moved clang-tidy check. 0d3902420 Refactor test without SECTION. 32e9e9e1e Rewrite test avoiding nested sections. eeb532902 Reorder includes according to best practice. 249e7b553 Revert "Add build with sanitizers to travis." 362cb419a Set CC env var in travis config. 9f520eca3 Add build with sanitizers to travis. cfc18fef0 Travis config cleanup. 212c6cd42 Remove tests that triggers undefined behaviour in the std lib. f30985884 Bugfix: Avoid undefined behaviour. 90191619a Make test work even if unused memory isn't returned to OS. 876867e68 Make older compilers happy. 925d1a797 Add names for unused parameters. 014b3a056 Avoid repeating the return type from the declaration c4e705f92 Disable hicpp-invalid-access-moved clang-tidy warnings. 8c03b3827 Disable some warnings. c49962ffe Refactor test to avoid spurious use-after-move warning. 04a0afbba Sort includes. 2aaab9a89 Use auto where possible. e2d84109b More changes for old compilers. 5364ca2a8 Changes for old compilers. 098fc1fa2 Move initializations of class members to the declaration. eea463026 Fix buffer overflow in o5m parser. 5dc74f2d5 Remove check from static cast (and document why). 7a2dfdcdc Add NOLINT for using namespace osmium::builder::attr. 01fa05f1e Clang-tidy config cleanup. 5e8f8df3f Order includes. 6b78d9b24 Various small fixes for problems detected by clang-tidy. b815f71b7 Fix gdal check in CMake config. d47487093 Fix integer type error. dec9de9ec Bugfix: Throw if elemens in XML file are nested too deep. 85e37723b Various small cleanups. 5f40d726a Additional checks in o5m parser. b59b4b6a6 Check before static cast and throw. e57b15700 Disable compiling some header files if gdal lib is not available. 1d3056646 Better checking that PBF data is in range. c586521d3 Add some consts to the code. df17e8189 Trying to get the types right... 8e9b759e0 Check read and write system call for EINTR. 28c676054 Rename license file so that Github finds it. be29e9a52 Remove duplicate space character. 36c14136f Update protozero to newest version (1.6.0). a19181998 Make libosmium work with different protozero versions. 5c5d9fb36 Use tag and type from protozero to make PBF parser more robust. 03f894400 Add repology.org badge to README.md. 1840739dd Merge pull request #234 from AMDmi3/patch-1 d8e737196 Add support for DragonFly BSD 8de181c8d Revert "Return const object from postfix ++ operator." 0dfb1d649 Add polygon implementation for WKT and GeoJSON Geometry Factories 150e05aea Fix indentation. 6fdee974a Merge pull request #229 from alex85k/no-winsock f302f08ce Merge pull request #228 from alex85k/fix-test-win bfb60c888 remove ws2_32 from needed libraries ef187851c no winsock2 on windows: replace htonl with protozero::detail::byteswap_inplace 9343f8453 Fix testdata-multipolygon test on Windows (executable path differs) 381cf9272 Do not return const from postfix increment operator. 46ec985e9 Add const version of OutputIterator::operator*. c67de374f Disable clang-tidy test for const params. f5eda165f Make functions noexcept and return const value from post increment op. 1e4e3160f Use {} when calling constructors. Use .empty() to check empty strings. 8893d16be Mark some tests using size() with NOLINT. f8a74a8aa Make functions noexcept. 45c1e072c Use REQUIRE_FALSE(...) instead of REQUIRE(!...) for readability. fc173dae7 Merge pull request #227 from alex85k/msys2-appveor 0a281c064 Add MSYS2 building to Appveyor c93f73120 fix FindGem for Windows Ruby distributions 8bf6702b3 Merge pull request #226 from alex85k/master e8dad731d gmtime_s should be used on both MSVC and GCC from MinGW64 d0d98e9c1 remove "osmium::util::MemoryMapping::" from class body 6830ee9bd ftruncate is not available only on MSVC (MinGW has it) c0597584f Disable some clang-tidy checks. 578db8e4a Use empty() instead of size() or == "" to check for empty collections. 53c8f3a21 Return const object from postfix ++ operator. bd5e4d109 Use emplace_back() instead of push_back(). 0a0a4f24c Use correct C++ header. 35226ea86 Remove unused move. df843a9cf Fix header guard. 759e76131 Avoid default argument on virtual function. db4f1bdb9 Update included catch.hpp to version 1.10.0. 59e70e5c6 Pass constructor arguments by value and std::move() into members. ee9b598ed Add tests for thread_handler. ac661209a CMake config: Update list of clang-tidy versions we look for. d7281215c Add comment to unusual code. cbb4c09fb Split up some overlong test functions. 9870a6b90 Fix noexcept specification. 5e27c37d0 Use array instead of map to store input/output format creators. 987d12922 Mark some constructors noexcept. 17f4d8b7e Avoid static variable only needed as constant value. fe705cef0 Avoid else after return in several cases. d4a85878e Beautifying travis config. f07d1e375 Make some special functions noexcept. 4bb0f7f22 Do not declare special functions on some classes. bc6c5943d Disable some clang-tidy checks that produce false positives. 6b6598a5b Remove superfluous std::move() of POD type. 71c3f5e16 Make some special functions noexcept. 1cb32778c Remove explicit true/false in tests. 929ba89cf Always catch by const ref. a9a255820 Code readability: No else clause after return. f14364bd3 Code formatting fixes. f41feb6a5 Simplify code. 727f6eff4 Mark dummy includes for IWYU. 72d20f835 Switch travis build to 'trusty'. c480e6808 Do not run gcov from codecov script. d16c6e90a Clean up lambda captures in xml_input_format.hpp. git-subtree-dir: third_party/libosmium git-subtree-split: d2c1d872bc915d9579350007383ce1ebfea3665f
1175 lines
52 KiB
C++
1175 lines
52 KiB
C++
#ifndef OSMIUM_AREA_DETAIL_BASIC_ASSEMBLER_HPP
|
|
#define OSMIUM_AREA_DETAIL_BASIC_ASSEMBLER_HPP
|
|
|
|
/*
|
|
|
|
This file is part of Osmium (http://osmcode.org/libosmium).
|
|
|
|
Copyright 2013-2018 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 <osmium/area/assembler_config.hpp>
|
|
#include <osmium/area/detail/node_ref_segment.hpp>
|
|
#include <osmium/area/detail/proto_ring.hpp>
|
|
#include <osmium/area/detail/segment_list.hpp>
|
|
#include <osmium/area/problem_reporter.hpp>
|
|
#include <osmium/area/stats.hpp>
|
|
#include <osmium/builder/osm_object_builder.hpp>
|
|
#include <osmium/osm/location.hpp>
|
|
#include <osmium/osm/node_ref.hpp>
|
|
#include <osmium/osm/types.hpp>
|
|
#include <osmium/osm/way.hpp>
|
|
#include <osmium/util/iterator.hpp>
|
|
#include <osmium/util/timer.hpp>
|
|
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <cstdint>
|
|
#include <cstdlib>
|
|
#include <iostream>
|
|
#include <iterator>
|
|
#include <list>
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
namespace osmium {
|
|
|
|
namespace area {
|
|
|
|
namespace detail {
|
|
|
|
using open_ring_its_type = std::list<std::list<ProtoRing>::iterator>;
|
|
|
|
struct location_to_ring_map {
|
|
osmium::Location location;
|
|
open_ring_its_type::iterator ring_it{};
|
|
bool start{false};
|
|
|
|
location_to_ring_map(osmium::Location l, open_ring_its_type::iterator r, const bool s) noexcept :
|
|
location(l),
|
|
ring_it(r),
|
|
start(s) {
|
|
}
|
|
|
|
explicit location_to_ring_map(osmium::Location l) noexcept :
|
|
location(l) {
|
|
}
|
|
|
|
const 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;
|
|
}
|
|
|
|
/**
|
|
* Class for assembling ways and relations into multipolygons
|
|
* (areas). Contains the basic functionality needed but is not
|
|
* used directly. Use the osmium::area::Assembler class instead.
|
|
*/
|
|
class BasicAssembler {
|
|
|
|
struct slocation {
|
|
|
|
static constexpr const uint32_t invalid_item = 1u << 30u;
|
|
|
|
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 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 SegmentList& segment_list) const noexcept {
|
|
const auto& segment = segment_list[item];
|
|
return reverse ? segment.second() : segment.first();
|
|
}
|
|
|
|
osmium::Location location(const 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)
|
|
SegmentList m_segment_list;
|
|
|
|
// The rings we are building from the segments
|
|
std::list<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
|
|
std::size_t m_num_members = 0;
|
|
|
|
template <typename TBuilder>
|
|
static void build_ring_from_proto_ring(osmium::builder::AreaBuilder& builder, const 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());
|
|
}
|
|
}
|
|
|
|
void check_inner_outer_roles() {
|
|
if (debug()) {
|
|
std::cerr << " Checking inner/outer roles\n";
|
|
}
|
|
|
|
std::unordered_map<const osmium::Way*, const ProtoRing*> way_rings;
|
|
std::unordered_set<const osmium::Way*> ways_in_multiple_rings;
|
|
|
|
for (const 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);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
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 {
|
|
|
|
double m_y;
|
|
ProtoRing* m_ring_ptr;
|
|
|
|
public:
|
|
|
|
rings_stack_element(double y, ProtoRing* ring_ptr) :
|
|
m_y(y),
|
|
m_ring_ptr(ring_ptr) {
|
|
}
|
|
|
|
double y() const noexcept {
|
|
return m_y;
|
|
}
|
|
|
|
const ProtoRing& ring() const noexcept {
|
|
return *m_ring_ptr;
|
|
}
|
|
|
|
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 rings_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));
|
|
}
|
|
}
|
|
|
|
ProtoRing* find_enclosing_ring(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 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 (y=" << a.y() << " ring=" << *segment->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()) {
|
|
const double y = ay + (by - ay) * (lx - ax) / double(bx - ax);
|
|
if (debug()) {
|
|
std::cerr << " Segment belongs to outer ring (y=" << y << " ring=" << *segment->ring() << ")\n";
|
|
}
|
|
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;
|
|
}
|
|
|
|
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) {
|
|
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();
|
|
}
|
|
|
|
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);
|
|
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;
|
|
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) {
|
|
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);
|
|
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;
|
|
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(ProtoRing* ring) {
|
|
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<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(), [](ProtoRing* a, 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 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 << " " << **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) {
|
|
const auto r1 = *m1.ring_it;
|
|
const 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);
|
|
}
|
|
|
|
open_ring_its.erase(std::find(open_ring_its.begin(), open_ring_its.end(), r2));
|
|
m_rings.erase(r2);
|
|
|
|
if (r1->closed()) {
|
|
open_ring_its.erase(std::find(open_ring_its.begin(), open_ring_its.end(), 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 (try_to_merge)\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 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()),
|
|
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 ProtoRing* ring_leading_here = &cand.rings.back().first.ring();
|
|
for (const location_to_ring_map& m : connections) {
|
|
const 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);
|
|
loc_done.erase(c.stop_location);
|
|
if (debug()) {
|
|
std::cerr << " ...back\n";
|
|
}
|
|
} else if (debug()) {
|
|
std::cerr << " loop found\n";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* If there are multiple open rings and multiple 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 (join_connected_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();
|
|
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(), nullptr);
|
|
m_config.problem_reporter->report_ring_not_closed(it->get_node_ref_stop(), nullptr);
|
|
}
|
|
}
|
|
}
|
|
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;
|
|
const ProtoRing& remaining_ring = first_ring.ring();
|
|
for (auto it = std::next(chosen_cand->rings.begin()); it != chosen_cand->rings.end(); ++it) {
|
|
merge_two_rings(open_ring_its, first_ring, it->first);
|
|
}
|
|
|
|
if (debug()) {
|
|
std::cerr << " Merged to " << remaining_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 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)) {
|
|
// intentionally left blank
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
#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
|
|
|
|
protected:
|
|
|
|
const std::list<ProtoRing>& rings() const noexcept {
|
|
return m_rings;
|
|
}
|
|
|
|
void set_num_members(std::size_t size) noexcept {
|
|
m_num_members = size;
|
|
}
|
|
|
|
SegmentList& segment_list() noexcept {
|
|
return m_segment_list;
|
|
}
|
|
|
|
/**
|
|
* 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 ProtoRing& ring : m_rings) {
|
|
if (ring.is_outer()) {
|
|
build_ring_from_proto_ring<osmium::builder::OuterRingBuilder>(builder, ring);
|
|
for (const ProtoRing* inner : ring.inner_rings()) {
|
|
build_ring_from_proto_ring<osmium::builder::InnerRingBuilder>(builder, *inner);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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_segment_list.erase_duplicate_segments(m_config.problem_reporter, m_stats.duplicate_segments, m_stats.overlapping_segments);
|
|
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 (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 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;
|
|
}
|
|
|
|
public:
|
|
|
|
using config_type = osmium::area::AssemblerConfig;
|
|
|
|
explicit BasicAssembler(const config_type& config) :
|
|
m_config(config),
|
|
m_segment_list(config.debug_level > 1) {
|
|
#ifdef OSMIUM_WITH_TIMER
|
|
init_header();
|
|
#endif
|
|
}
|
|
|
|
const AssemblerConfig& config() const noexcept {
|
|
return m_config;
|
|
}
|
|
|
|
bool debug() const noexcept {
|
|
return m_config.debug_level > 1;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
osmium::area::area_stats& stats() noexcept {
|
|
return m_stats;
|
|
}
|
|
|
|
}; // class BasicAssembler
|
|
|
|
} // namespace detail
|
|
|
|
} // namespace area
|
|
|
|
} // namespace osmium
|
|
|
|
#endif // OSMIUM_AREA_DETAIL_BASIC_ASSEMBLER_HPP
|