788 lines
		
	
	
		
			33 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			788 lines
		
	
	
		
			33 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 <cstring>
 | 
						|
#include <iostream>
 | 
						|
#include <iterator>
 | 
						|
#include <list>
 | 
						|
#include <set>
 | 
						|
#include <string>
 | 
						|
#include <map>
 | 
						|
#include <vector>
 | 
						|
 | 
						|
#include <osmium/builder/osm_object_builder.hpp>
 | 
						|
#include <osmium/memory/buffer.hpp>
 | 
						|
#include <osmium/osm/area.hpp>
 | 
						|
#include <osmium/osm/location.hpp>
 | 
						|
#include <osmium/osm/relation.hpp>
 | 
						|
#include <osmium/tags/filter.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>
 | 
						|
 | 
						|
namespace osmium {
 | 
						|
 | 
						|
    namespace area {
 | 
						|
 | 
						|
        using osmium::area::detail::ProtoRing;
 | 
						|
 | 
						|
        struct AssemblerConfig {
 | 
						|
 | 
						|
            osmium::area::ProblemReporter* problem_reporter;
 | 
						|
 | 
						|
            // Enables debug output to stderr
 | 
						|
            bool debug;
 | 
						|
 | 
						|
            explicit AssemblerConfig(osmium::area::ProblemReporter* pr = nullptr, bool d = false) :
 | 
						|
                problem_reporter(pr),
 | 
						|
                debug(d) {
 | 
						|
            }
 | 
						|
 | 
						|
            /**
 | 
						|
             * Enable or disable debug output to stderr. This is for Osmium
 | 
						|
             * developers only.
 | 
						|
             */
 | 
						|
            void enable_debug_output(bool d = true) {
 | 
						|
                debug = d;
 | 
						|
            }
 | 
						|
 | 
						|
        }; // struct AssemblerConfig
 | 
						|
 | 
						|
        /**
 | 
						|
         * Assembles area objects from multipolygon relations and their
 | 
						|
         * members. This is called by the MultipolygonCollector object
 | 
						|
         * after all members have been collected.
 | 
						|
         */
 | 
						|
        class Assembler {
 | 
						|
 | 
						|
            const AssemblerConfig m_config;
 | 
						|
 | 
						|
            // The way segments
 | 
						|
            osmium::area::detail::SegmentList m_segment_list;
 | 
						|
 | 
						|
            // The rings we are building from the way segments
 | 
						|
            std::list<ProtoRing> m_rings;
 | 
						|
 | 
						|
            std::vector<ProtoRing*> m_outer_rings;
 | 
						|
            std::vector<ProtoRing*> m_inner_rings;
 | 
						|
 | 
						|
            int m_inner_outer_mismatches { 0 };
 | 
						|
 | 
						|
            bool debug() const {
 | 
						|
                return m_config.debug;
 | 
						|
            }
 | 
						|
 | 
						|
            /**
 | 
						|
             * Checks whether the given NodeRefs have the same location.
 | 
						|
             * Uses the actual location for the test, not the id. If both
 | 
						|
             * have the same location, but not the same id, a problem
 | 
						|
             * point will be added to the list of problem points.
 | 
						|
             */
 | 
						|
            bool has_same_location(const osmium::NodeRef& nr1, const osmium::NodeRef& nr2) {
 | 
						|
                if (nr1.location() != nr2.location()) {
 | 
						|
                    return false;
 | 
						|
                }
 | 
						|
                if (nr1.ref() != nr2.ref()) {
 | 
						|
                    if (m_config.problem_reporter) {
 | 
						|
                        m_config.problem_reporter->report_duplicate_node(nr1.ref(), nr2.ref(), nr1.location());
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                return true;
 | 
						|
            }
 | 
						|
 | 
						|
            void add_tags_to_area(osmium::builder::AreaBuilder& builder, const osmium::Way& way) const {
 | 
						|
                osmium::builder::TagListBuilder tl_builder(builder.buffer(), &builder);
 | 
						|
                for (const osmium::Tag& tag : way.tags()) {
 | 
						|
                    tl_builder.add_tag(tag.key(), tag.value());
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            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];
 | 
						|
                    }
 | 
						|
                }
 | 
						|
 | 
						|
                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) {
 | 
						|
                        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 MPFilter& filter() {
 | 
						|
                static MPFilter filter;
 | 
						|
                return filter;
 | 
						|
            }
 | 
						|
 | 
						|
            void add_tags_to_area(osmium::builder::AreaBuilder& builder, const osmium::Relation& relation) const {
 | 
						|
                const auto count = std::count_if(relation.tags().begin(), relation.tags().end(), 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";
 | 
						|
                    }
 | 
						|
 | 
						|
                    // write out all tags except type=*
 | 
						|
                    osmium::builder::TagListBuilder tl_builder(builder.buffer(), &builder);
 | 
						|
                    for (const osmium::Tag& tag : relation.tags()) {
 | 
						|
                        if (strcmp(tag.key(), "type")) {
 | 
						|
                            tl_builder.add_tag(tag.key(), tag.value());
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                } else {
 | 
						|
                    if (debug()) {
 | 
						|
                        std::cerr << "    use tags from outer ways\n";
 | 
						|
                    }
 | 
						|
                    std::set<const osmium::Way*> ways;
 | 
						|
                    for (const auto& ring : m_outer_rings) {
 | 
						|
                        ring->get_ways(ways);
 | 
						|
                    }
 | 
						|
                    if (ways.size() == 1) {
 | 
						|
                        if (debug()) {
 | 
						|
                            std::cerr << "      only one outer way\n";
 | 
						|
                        }
 | 
						|
                        osmium::builder::TagListBuilder tl_builder(builder.buffer(), &builder);
 | 
						|
                        for (const osmium::Tag& tag : (*ways.begin())->tags()) {
 | 
						|
                            tl_builder.add_tag(tag.key(), tag.value());
 | 
						|
                        }
 | 
						|
                    } else {
 | 
						|
                        if (debug()) {
 | 
						|
                            std::cerr << "      multiple outer ways, get common tags\n";
 | 
						|
                        }
 | 
						|
                        osmium::builder::TagListBuilder tl_builder(builder.buffer(), &builder);
 | 
						|
                        add_common_tags(tl_builder, ways);
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            /**
 | 
						|
             * Go through all the rings and find rings that are not closed.
 | 
						|
             * Problems are reported through the problem reporter.
 | 
						|
             *
 | 
						|
             * @returns true if any rings were not closed, false otherwise
 | 
						|
             */
 | 
						|
            bool check_for_open_rings() {
 | 
						|
                bool open_rings = false;
 | 
						|
 | 
						|
                for (const auto& ring : m_rings) {
 | 
						|
                    if (!ring.closed()) {
 | 
						|
                        open_rings = true;
 | 
						|
                        if (m_config.problem_reporter) {
 | 
						|
                            m_config.problem_reporter->report_ring_not_closed(ring.get_segment_front().first().location(), ring.get_segment_back().second().location());
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                }
 | 
						|
 | 
						|
                return open_rings;
 | 
						|
            }
 | 
						|
 | 
						|
            /**
 | 
						|
             * Check whether there are any rings that can be combined with the
 | 
						|
             * given ring to one larger ring by appending the other ring to
 | 
						|
             * the end of this ring.
 | 
						|
             * If the rings can be combined they are and the function returns
 | 
						|
             * true.
 | 
						|
             */
 | 
						|
            bool possibly_combine_rings_back(ProtoRing& ring) {
 | 
						|
                const osmium::NodeRef& nr = ring.get_segment_back().second();
 | 
						|
 | 
						|
                if (debug()) {
 | 
						|
                    std::cerr << "      possibly_combine_rings_back()\n";
 | 
						|
                }
 | 
						|
                for (auto it = m_rings.begin(); it != m_rings.end(); ++it) {
 | 
						|
                    if (&*it != &ring && !it->closed()) {
 | 
						|
                        if (has_same_location(nr, it->get_segment_front().first())) {
 | 
						|
                            if (debug()) {
 | 
						|
                                std::cerr << "      ring.last=it->first\n";
 | 
						|
                            }
 | 
						|
                            ring.merge_ring(*it, debug());
 | 
						|
                            m_rings.erase(it);
 | 
						|
                            return true;
 | 
						|
                        }
 | 
						|
                        if (has_same_location(nr, it->get_segment_back().second())) {
 | 
						|
                            if (debug()) {
 | 
						|
                                std::cerr << "      ring.last=it->last\n";
 | 
						|
                            }
 | 
						|
                            ring.merge_ring_reverse(*it, debug());
 | 
						|
                            m_rings.erase(it);
 | 
						|
                            return true;
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                return false;
 | 
						|
            }
 | 
						|
 | 
						|
            /**
 | 
						|
             * Check whether there are any rings that can be combined with the
 | 
						|
             * given ring to one larger ring by prepending the other ring to
 | 
						|
             * the start of this ring.
 | 
						|
             * If the rings can be combined they are and the function returns
 | 
						|
             * true.
 | 
						|
             */
 | 
						|
            bool possibly_combine_rings_front(ProtoRing& ring) {
 | 
						|
                const osmium::NodeRef& nr = ring.get_segment_front().first();
 | 
						|
 | 
						|
                if (debug()) {
 | 
						|
                    std::cerr << "      possibly_combine_rings_front()\n";
 | 
						|
                }
 | 
						|
                for (auto it = m_rings.begin(); it != m_rings.end(); ++it) {
 | 
						|
                    if (&*it != &ring && !it->closed()) {
 | 
						|
                        if (has_same_location(nr, it->get_segment_back().second())) {
 | 
						|
                            if (debug()) {
 | 
						|
                                std::cerr << "      ring.first=it->last\n";
 | 
						|
                            }
 | 
						|
                            ring.swap_segments(*it);
 | 
						|
                            ring.merge_ring(*it, debug());
 | 
						|
                            m_rings.erase(it);
 | 
						|
                            return true;
 | 
						|
                        }
 | 
						|
                        if (has_same_location(nr, it->get_segment_front().first())) {
 | 
						|
                            if (debug()) {
 | 
						|
                                std::cerr << "      ring.first=it->first\n";
 | 
						|
                            }
 | 
						|
                            ring.reverse();
 | 
						|
                            ring.merge_ring(*it, debug());
 | 
						|
                            m_rings.erase(it);
 | 
						|
                            return true;
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                return false;
 | 
						|
            }
 | 
						|
 | 
						|
            void split_off_subring(osmium::area::detail::ProtoRing& ring, osmium::area::detail::ProtoRing::segments_type::iterator it, osmium::area::detail::ProtoRing::segments_type::iterator it_begin, osmium::area::detail::ProtoRing::segments_type::iterator it_end) {
 | 
						|
                if (debug()) {
 | 
						|
                    std::cerr << "        subring found at: " << *it << "\n";
 | 
						|
                }
 | 
						|
                ProtoRing new_ring(it_begin, it_end);
 | 
						|
                ring.remove_segments(it_begin, it_end);
 | 
						|
                if (debug()) {
 | 
						|
                    std::cerr << "        split into two rings:\n";
 | 
						|
                    std::cerr << "          " << new_ring << "\n";
 | 
						|
                    std::cerr << "          " << ring << "\n";
 | 
						|
                }
 | 
						|
                m_rings.push_back(std::move(new_ring));
 | 
						|
            }
 | 
						|
 | 
						|
            bool has_closed_subring_back(ProtoRing& ring, const NodeRef& nr) {
 | 
						|
                if (ring.segments().size() < 3) {
 | 
						|
                    return false;
 | 
						|
                }
 | 
						|
                if (debug()) {
 | 
						|
                    std::cerr << "      has_closed_subring_back()\n";
 | 
						|
                }
 | 
						|
                const auto end = ring.segments().end();
 | 
						|
                for (auto it = ring.segments().begin() + 1; it != end - 1; ++it) {
 | 
						|
                    if (has_same_location(nr, it->first())) {
 | 
						|
                        split_off_subring(ring, it, it, end);
 | 
						|
                        return true;
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                return false;
 | 
						|
            }
 | 
						|
 | 
						|
            bool has_closed_subring_front(ProtoRing& ring, const NodeRef& nr) {
 | 
						|
                if (ring.segments().size() < 3) {
 | 
						|
                    return false;
 | 
						|
                }
 | 
						|
                if (debug()) {
 | 
						|
                    std::cerr << "      has_closed_subring_front()\n";
 | 
						|
                }
 | 
						|
                const auto end = ring.segments().end();
 | 
						|
                for (auto it = ring.segments().begin() + 1; it != end - 1; ++it) {
 | 
						|
                    if (has_same_location(nr, it->second())) {
 | 
						|
                        split_off_subring(ring, it, ring.segments().begin(), it+1);
 | 
						|
                        return true;
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                return false;
 | 
						|
            }
 | 
						|
 | 
						|
            bool check_for_closed_subring(ProtoRing& ring) {
 | 
						|
                if (debug()) {
 | 
						|
                    std::cerr << "      check_for_closed_subring()\n";
 | 
						|
                }
 | 
						|
 | 
						|
                osmium::area::detail::ProtoRing::segments_type segments(ring.segments().size());
 | 
						|
                std::copy(ring.segments().begin(), ring.segments().end(), segments.begin());
 | 
						|
                std::sort(segments.begin(), segments.end());
 | 
						|
                const auto it = std::adjacent_find(segments.begin(), segments.end(), [this](const osmium::area::detail::NodeRefSegment& s1, const osmium::area::detail::NodeRefSegment& s2) {
 | 
						|
                    return has_same_location(s1.first(), s2.first());
 | 
						|
                });
 | 
						|
                if (it == segments.end()) {
 | 
						|
                    return false;
 | 
						|
                }
 | 
						|
                const auto r1 = std::find_first_of(ring.segments().begin(), ring.segments().end(), it, it+1);
 | 
						|
                assert(r1 != ring.segments().end());
 | 
						|
                const auto r2 = std::find_first_of(ring.segments().begin(), ring.segments().end(), it+1, it+2);
 | 
						|
                assert(r2 != ring.segments().end());
 | 
						|
 | 
						|
                if (debug()) {
 | 
						|
                    std::cerr << "      found subring in ring " << ring << " at " << it->first() << "\n";
 | 
						|
                }
 | 
						|
 | 
						|
                const auto m = std::minmax(r1, r2);
 | 
						|
 | 
						|
                ProtoRing new_ring(m.first, m.second);
 | 
						|
                ring.remove_segments(m.first, m.second);
 | 
						|
 | 
						|
                if (debug()) {
 | 
						|
                    std::cerr << "        split ring1=" << new_ring << "\n";
 | 
						|
                    std::cerr << "        split ring2=" << ring << "\n";
 | 
						|
                }
 | 
						|
 | 
						|
                m_rings.emplace_back(new_ring);
 | 
						|
 | 
						|
                return true;
 | 
						|
            }
 | 
						|
 | 
						|
            void combine_rings_front(const osmium::area::detail::NodeRefSegment& segment, ProtoRing& ring) {
 | 
						|
                if (debug()) {
 | 
						|
                    std::cerr << " => match at front of ring\n";
 | 
						|
                }
 | 
						|
                ring.add_segment_front(segment);
 | 
						|
                has_closed_subring_front(ring, segment.first());
 | 
						|
                if (possibly_combine_rings_front(ring)) {
 | 
						|
                    check_for_closed_subring(ring);
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            void combine_rings_back(const osmium::area::detail::NodeRefSegment& segment, ProtoRing& ring) {
 | 
						|
                if (debug()) {
 | 
						|
                    std::cerr << " => match at back of ring\n";
 | 
						|
                }
 | 
						|
                ring.add_segment_back(segment);
 | 
						|
                has_closed_subring_back(ring, segment.second());
 | 
						|
                if (possibly_combine_rings_back(ring)) {
 | 
						|
                    check_for_closed_subring(ring);
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            /**
 | 
						|
             * 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_outer_rings) {
 | 
						|
                    if (debug()) {
 | 
						|
                        std::cerr << "    ring " << *ring << " is outer\n";
 | 
						|
                    }
 | 
						|
                    {
 | 
						|
                        osmium::builder::OuterRingBuilder ring_builder(builder.buffer(), &builder);
 | 
						|
                        ring_builder.add_node_ref(ring->get_segment_front().first());
 | 
						|
                        for (const auto& segment : ring->segments()) {
 | 
						|
                            ring_builder.add_node_ref(segment.second());
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                    for (ProtoRing* inner : ring->inner_rings()) {
 | 
						|
                        osmium::builder::InnerRingBuilder ring_builder(builder.buffer(), &builder);
 | 
						|
                        ring_builder.add_node_ref(inner->get_segment_front().first());
 | 
						|
                        for (const auto& segment : inner->segments()) {
 | 
						|
                            ring_builder.add_node_ref(segment.second());
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            bool add_to_existing_ring(osmium::area::detail::NodeRefSegment segment) {
 | 
						|
                int n = 0;
 | 
						|
                for (auto& ring : m_rings) {
 | 
						|
                    if (debug()) {
 | 
						|
                        std::cerr << "    check against ring " << n << " " << ring;
 | 
						|
                    }
 | 
						|
                    if (ring.closed()) {
 | 
						|
                        if (debug()) {
 | 
						|
                            std::cerr << " => ring CLOSED\n";
 | 
						|
                        }
 | 
						|
                    } else {
 | 
						|
                        if (has_same_location(ring.get_segment_back().second(), segment.first())) {
 | 
						|
                            combine_rings_back(segment, ring);
 | 
						|
                            return true;
 | 
						|
                        }
 | 
						|
                        if (has_same_location(ring.get_segment_back().second(), segment.second())) {
 | 
						|
                            segment.swap_locations();
 | 
						|
                            combine_rings_back(segment, ring);
 | 
						|
                            return true;
 | 
						|
                        }
 | 
						|
                        if (has_same_location(ring.get_segment_front().first(), segment.first())) {
 | 
						|
                            segment.swap_locations();
 | 
						|
                            combine_rings_front(segment, ring);
 | 
						|
                            return true;
 | 
						|
                        }
 | 
						|
                        if (has_same_location(ring.get_segment_front().first(), segment.second())) {
 | 
						|
                            combine_rings_front(segment, ring);
 | 
						|
                            return true;
 | 
						|
                        }
 | 
						|
                        if (debug()) {
 | 
						|
                            std::cerr << " => no match\n";
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
 | 
						|
                    ++n;
 | 
						|
                }
 | 
						|
                return false;
 | 
						|
            }
 | 
						|
 | 
						|
            void check_inner_outer(ProtoRing& ring) {
 | 
						|
                const osmium::NodeRef& min_node = ring.min_node();
 | 
						|
                if (debug()) {
 | 
						|
                    std::cerr << "    check_inner_outer min_node=" << min_node << "\n";
 | 
						|
                }
 | 
						|
 | 
						|
                int count = 0;
 | 
						|
                int above = 0;
 | 
						|
 | 
						|
                for (auto it = m_segment_list.begin(); it != m_segment_list.end() && it->first().location().x() <= min_node.location().x(); ++it) {
 | 
						|
                    if (!ring.contains(*it)) {
 | 
						|
                        if (debug()) {
 | 
						|
                            std::cerr << "      segments for count: " << *it;
 | 
						|
                        }
 | 
						|
                        if (it->to_left_of(min_node.location())) {
 | 
						|
                            ++count;
 | 
						|
                            if (debug()) {
 | 
						|
                                std::cerr << " counted\n";
 | 
						|
                            }
 | 
						|
                        } else {
 | 
						|
                            if (debug()) {
 | 
						|
                                std::cerr << " not counted\n";
 | 
						|
                            }
 | 
						|
                        }
 | 
						|
                        if (it->first().location() == min_node.location()) {
 | 
						|
                            if (it->second().location().y() > min_node.location().y()) {
 | 
						|
                                ++above;
 | 
						|
                            }
 | 
						|
                        }
 | 
						|
                        if (it->second().location() == min_node.location()) {
 | 
						|
                            if (it->first().location().y() > min_node.location().y()) {
 | 
						|
                                ++above;
 | 
						|
                            }
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                }
 | 
						|
 | 
						|
                if (debug()) {
 | 
						|
                    std::cerr << "      count=" << count << " above=" << above << "\n";
 | 
						|
                }
 | 
						|
 | 
						|
                count += above % 2;
 | 
						|
 | 
						|
                if (count % 2) {
 | 
						|
                    ring.set_inner();
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            void check_inner_outer_roles() {
 | 
						|
                if (debug()) {
 | 
						|
                    std::cerr << "    check_inner_outer_roles\n";
 | 
						|
                }
 | 
						|
 | 
						|
                for (const auto ringptr : m_outer_rings) {
 | 
						|
                    for (const auto& segment : ringptr->segments()) {
 | 
						|
                        if (!segment.role_outer()) {
 | 
						|
                            ++m_inner_outer_mismatches;
 | 
						|
                            if (debug()) {
 | 
						|
                                std::cerr << "      segment " << segment << " from way " << segment.way()->id() << " should have role 'outer'\n";
 | 
						|
                            }
 | 
						|
                            if (m_config.problem_reporter) {
 | 
						|
                                m_config.problem_reporter->report_role_should_be_outer(segment.way()->id(), segment.first().location(), segment.second().location());
 | 
						|
                            }
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                for (const auto ringptr : m_inner_rings) {
 | 
						|
                    for (const auto& segment : ringptr->segments()) {
 | 
						|
                        if (!segment.role_inner()) {
 | 
						|
                            ++m_inner_outer_mismatches;
 | 
						|
                            if (debug()) {
 | 
						|
                                std::cerr << "      segment " << segment << " from way " << segment.way()->id() << " should have role 'inner'\n";
 | 
						|
                            }
 | 
						|
                            if (m_config.problem_reporter) {
 | 
						|
                                m_config.problem_reporter->report_role_should_be_inner(segment.way()->id(), segment.first().location(), segment.second().location());
 | 
						|
                            }
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            /**
 | 
						|
             * Create rings from segments.
 | 
						|
             */
 | 
						|
            bool create_rings() {
 | 
						|
                m_segment_list.sort();
 | 
						|
                m_segment_list.erase_duplicate_segments();
 | 
						|
 | 
						|
                // 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.
 | 
						|
                if (m_segment_list.find_intersections(m_config.problem_reporter)) {
 | 
						|
                    return false;
 | 
						|
                }
 | 
						|
 | 
						|
                // Now iterator over all segments and add them to rings. Each segment
 | 
						|
                // is tacked on to either end of an existing ring if possible, or a
 | 
						|
                // new ring is started with it.
 | 
						|
                for (const auto& segment : m_segment_list) {
 | 
						|
                    if (debug()) {
 | 
						|
                        std::cerr << "  checking segment " << segment << "\n";
 | 
						|
                    }
 | 
						|
                    if (!add_to_existing_ring(segment)) {
 | 
						|
                        if (debug()) {
 | 
						|
                            std::cerr << "    new ring for segment " << segment << "\n";
 | 
						|
                        }
 | 
						|
                        m_rings.emplace_back(segment);
 | 
						|
                    }
 | 
						|
                }
 | 
						|
 | 
						|
                if (debug()) {
 | 
						|
                    std::cerr << "  Rings:\n";
 | 
						|
                    for (const auto& ring : m_rings) {
 | 
						|
                        std::cerr << "    " << ring;
 | 
						|
                        if (ring.closed()) {
 | 
						|
                            std::cerr << " (closed)";
 | 
						|
                        }
 | 
						|
                        std::cerr << "\n";
 | 
						|
                    }
 | 
						|
                }
 | 
						|
 | 
						|
                if (check_for_open_rings()) {
 | 
						|
                    if (debug()) {
 | 
						|
                        std::cerr << "  not all rings are closed\n";
 | 
						|
                    }
 | 
						|
                    return false;
 | 
						|
                }
 | 
						|
 | 
						|
                if (debug()) {
 | 
						|
                    std::cerr << "  Find inner/outer...\n";
 | 
						|
                }
 | 
						|
 | 
						|
                if (m_rings.size() == 1) {
 | 
						|
                    m_outer_rings.push_back(&m_rings.front());
 | 
						|
                } else {
 | 
						|
                    for (auto& ring : m_rings) {
 | 
						|
                        check_inner_outer(ring);
 | 
						|
                        if (ring.outer()) {
 | 
						|
                            if (!ring.is_cw()) {
 | 
						|
                                ring.reverse();
 | 
						|
                            }
 | 
						|
                            m_outer_rings.push_back(&ring);
 | 
						|
                        } else {
 | 
						|
                            if (ring.is_cw()) {
 | 
						|
                                ring.reverse();
 | 
						|
                            }
 | 
						|
                            m_inner_rings.push_back(&ring);
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
 | 
						|
                    if (m_outer_rings.size() == 1) {
 | 
						|
                        for (auto inner : m_inner_rings) {
 | 
						|
                            m_outer_rings.front()->add_inner_ring(inner);
 | 
						|
                        }
 | 
						|
                    } else {
 | 
						|
                        // sort outer rings by size, smallest first
 | 
						|
                        std::sort(m_outer_rings.begin(), m_outer_rings.end(), [](ProtoRing* a, ProtoRing* b) {
 | 
						|
                            return a->area() < b->area();
 | 
						|
                        });
 | 
						|
                        for (auto inner : m_inner_rings) {
 | 
						|
                            for (auto outer : m_outer_rings) {
 | 
						|
                                if (inner->is_in(outer)) {
 | 
						|
                                    outer->add_inner_ring(inner);
 | 
						|
                                    break;
 | 
						|
                                }
 | 
						|
                            }
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                }
 | 
						|
 | 
						|
                check_inner_outer_roles();
 | 
						|
 | 
						|
                return true;
 | 
						|
            }
 | 
						|
 | 
						|
        public:
 | 
						|
 | 
						|
            typedef osmium::area::AssemblerConfig config_type;
 | 
						|
 | 
						|
            explicit Assembler(const config_type& config) :
 | 
						|
                m_config(config),
 | 
						|
                m_segment_list(config.debug) {
 | 
						|
            }
 | 
						|
 | 
						|
            ~Assembler() = 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.problem_reporter) {
 | 
						|
                    m_config.problem_reporter->set_object(osmium::item_type::way, way.id());
 | 
						|
                }
 | 
						|
 | 
						|
                if (!way.ends_have_same_id()) {
 | 
						|
                    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_segment_list.extract_segments_from_way(way, "outer");
 | 
						|
 | 
						|
                if (debug()) {
 | 
						|
                    std::cerr << "\nBuild way id()=" << way.id() << " segments.size()=" << m_segment_list.size() << "\n";
 | 
						|
                }
 | 
						|
 | 
						|
                // Now create the Area object and add the attributes and tags
 | 
						|
                // from the relation.
 | 
						|
                {
 | 
						|
                    osmium::builder::AreaBuilder builder(out_buffer);
 | 
						|
                    builder.initialize_from_object(way);
 | 
						|
 | 
						|
                    if (create_rings()) {
 | 
						|
                        add_tags_to_area(builder, way);
 | 
						|
                        add_rings_to_area(builder);
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                out_buffer.commit();
 | 
						|
            }
 | 
						|
 | 
						|
            /**
 | 
						|
             * 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.
 | 
						|
             */
 | 
						|
            void operator()(const osmium::Relation& relation, const std::vector<size_t>& members, const osmium::memory::Buffer& in_buffer, osmium::memory::Buffer& out_buffer) {
 | 
						|
                if (m_config.problem_reporter) {
 | 
						|
                    m_config.problem_reporter->set_object(osmium::item_type::relation, relation.id());
 | 
						|
                }
 | 
						|
 | 
						|
                m_segment_list.extract_segments_from_ways(relation, members, in_buffer);
 | 
						|
 | 
						|
                if (debug()) {
 | 
						|
                    std::cerr << "\nBuild relation id()=" << relation.id() << " members.size()=" << members.size() << " segments.size()=" << m_segment_list.size() << "\n";
 | 
						|
                }
 | 
						|
 | 
						|
                size_t area_offset = out_buffer.committed();
 | 
						|
 | 
						|
                // Now create the Area object and add the attributes and tags
 | 
						|
                // from the relation.
 | 
						|
                {
 | 
						|
                    osmium::builder::AreaBuilder builder(out_buffer);
 | 
						|
                    builder.initialize_from_object(relation);
 | 
						|
 | 
						|
                    if (create_rings()) {
 | 
						|
                        add_tags_to_area(builder, relation);
 | 
						|
                        add_rings_to_area(builder);
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                out_buffer.commit();
 | 
						|
 | 
						|
                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_inner_outer_mismatches == 0) {
 | 
						|
                    auto memit = relation.members().begin();
 | 
						|
                    for (size_t offset : members) {
 | 
						|
                        if (!std::strcmp(memit->role(), "inner")) {
 | 
						|
                            const osmium::Way& way = in_buffer.get<const osmium::Way>(offset);
 | 
						|
                            if (!way.nodes().empty() && way.is_closed() && way.tags().size() > 0) {
 | 
						|
                                auto d = std::count_if(way.tags().begin(), way.tags().end(), filter());
 | 
						|
                                if (d > 0) {
 | 
						|
                                    osmium::tags::KeyFilter::iterator way_fi_begin(filter(), way.tags().begin(), way.tags().end());
 | 
						|
                                    osmium::tags::KeyFilter::iterator way_fi_end(filter(), way.tags().end(), way.tags().end());
 | 
						|
                                    osmium::tags::KeyFilter::iterator area_fi_begin(filter(), area_tags.begin(), area_tags.end());
 | 
						|
                                    osmium::tags::KeyFilter::iterator area_fi_end(filter(), area_tags.end(), area_tags.end());
 | 
						|
 | 
						|
                                    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);
 | 
						|
                                    }
 | 
						|
                                }
 | 
						|
                            }
 | 
						|
                        }
 | 
						|
                        ++memit;
 | 
						|
                    }
 | 
						|
                }
 | 
						|
 | 
						|
                // 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);
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
        }; // class Assembler
 | 
						|
 | 
						|
    } // namespace area
 | 
						|
 | 
						|
} // namespace osmium
 | 
						|
 | 
						|
#endif // OSMIUM_AREA_ASSEMBLER_HPP
 |