614 lines
25 KiB
C++
614 lines
25 KiB
C++
#ifndef OSMIUM_RELATIONS_COLLECTOR_HPP
|
|
#define OSMIUM_RELATIONS_COLLECTOR_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/handler.hpp>
|
|
#include <osmium/handler/check_order.hpp>
|
|
#include <osmium/memory/buffer.hpp>
|
|
#include <osmium/osm/item_type.hpp>
|
|
#include <osmium/osm/object.hpp>
|
|
#include <osmium/osm/relation.hpp>
|
|
#include <osmium/osm/types.hpp>
|
|
#include <osmium/relations/detail/member_meta.hpp>
|
|
#include <osmium/relations/detail/relation_meta.hpp>
|
|
#include <osmium/util/iterator.hpp>
|
|
#include <osmium/visitor.hpp>
|
|
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <functional>
|
|
#include <iomanip>
|
|
#include <iostream>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
namespace osmium {
|
|
|
|
class Node;
|
|
class Way;
|
|
|
|
/**
|
|
* @brief Code related to the assembly of OSM relations
|
|
*/
|
|
namespace relations {
|
|
|
|
/**
|
|
* The Collector class collects members of a relation. This is a
|
|
* generic base class that can be used to assemble all kinds of
|
|
* relations. It has numerous hooks you can implement in derived
|
|
* classes to customize its behaviour.
|
|
*
|
|
* The collector provides two handlers (HandlerPass1 and HandlerPass2)
|
|
* for a first and second pass through an input file, respectively. In
|
|
* the first pass all relations we are interested in are stored in
|
|
* RelationMeta objects in the m_relations vector. All members we are
|
|
* interested in are stored in MemberMeta objects in the m_member_meta
|
|
* vectors. The MemberMeta objects also store the information where the
|
|
* relations containing those members are to be found.
|
|
*
|
|
* Later the m_member_meta vectors are sorted according to the member
|
|
* ids so that a binary search (with std::equal_range) can be used in
|
|
* the second pass to find the parent relations for each node, way, or
|
|
* relation coming along. The member objects are stored together with
|
|
* their relation and once a relation is complete the
|
|
* complete_relation() method is called which you must overwrite in a
|
|
* derived class of Collector.
|
|
*
|
|
* @tparam TCollector Derived class of this class.
|
|
* @tparam TNodes Are we interested in member nodes?
|
|
* @tparam TWays Are we interested in member ways?
|
|
* @tparam TRelations Are we interested in member relations?
|
|
* @pre The Ids of all objects must be unique in the input data.
|
|
*/
|
|
template <typename TCollector, bool TNodes, bool TWays, bool TRelations>
|
|
class Collector {
|
|
|
|
/**
|
|
* This is the handler class for the first pass of the Collector.
|
|
*/
|
|
class HandlerPass1 : public osmium::handler::Handler {
|
|
|
|
TCollector& m_collector;
|
|
|
|
public:
|
|
|
|
explicit HandlerPass1(TCollector& collector) noexcept :
|
|
m_collector(collector) {
|
|
}
|
|
|
|
void relation(const osmium::Relation& relation) {
|
|
if (m_collector.keep_relation(relation)) {
|
|
m_collector.add_relation(relation);
|
|
}
|
|
}
|
|
|
|
}; // class HandlerPass1
|
|
|
|
public:
|
|
|
|
/**
|
|
* This is the handler class for the second pass of the Collector.
|
|
*/
|
|
class HandlerPass2 : public osmium::handler::Handler {
|
|
|
|
osmium::handler::CheckOrder m_check_order;
|
|
TCollector& m_collector;
|
|
|
|
public:
|
|
|
|
explicit HandlerPass2(TCollector& collector) noexcept :
|
|
m_collector(collector) {
|
|
}
|
|
|
|
void node(const osmium::Node& node) {
|
|
if (TNodes) {
|
|
m_check_order.node(node);
|
|
if (!m_collector.find_and_add_object(node)) {
|
|
m_collector.node_not_in_any_relation(node);
|
|
}
|
|
}
|
|
}
|
|
|
|
void way(const osmium::Way& way) {
|
|
if (TWays) {
|
|
m_check_order.way(way);
|
|
if (!m_collector.find_and_add_object(way)) {
|
|
m_collector.way_not_in_any_relation(way);
|
|
}
|
|
}
|
|
}
|
|
|
|
void relation(const osmium::Relation& relation) {
|
|
if (TRelations) {
|
|
m_check_order.relation(relation);
|
|
if (!m_collector.find_and_add_object(relation)) {
|
|
m_collector.relation_not_in_any_relation(relation);
|
|
}
|
|
}
|
|
}
|
|
|
|
void flush() {
|
|
m_collector.flush();
|
|
}
|
|
|
|
}; // class HandlerPass2
|
|
|
|
private:
|
|
|
|
HandlerPass2 m_handler_pass2;
|
|
|
|
// All relations we are interested in will be kept in this buffer
|
|
osmium::memory::Buffer m_relations_buffer;
|
|
|
|
// All members we are interested in will be kept in this buffer
|
|
osmium::memory::Buffer m_members_buffer;
|
|
|
|
/// Vector with all relations we are interested in
|
|
std::vector<RelationMeta> m_relations;
|
|
|
|
/**
|
|
* One vector each for nodes, ways, and relations containing all
|
|
* mappings from member ids to their relations.
|
|
*/
|
|
using mm_vector_type = std::vector<MemberMeta>;
|
|
using mm_iterator = mm_vector_type::iterator;
|
|
mm_vector_type m_member_meta[3];
|
|
|
|
int m_count_complete = 0;
|
|
|
|
using callback_func_type = std::function<void(osmium::memory::Buffer&&)>;
|
|
callback_func_type m_callback;
|
|
|
|
static constexpr size_t initial_buffer_size = 1024 * 1024;
|
|
|
|
iterator_range<mm_iterator> find_member_meta(osmium::item_type type, osmium::object_id_type id) {
|
|
auto& mmv = member_meta(type);
|
|
return make_range(std::equal_range(mmv.begin(), mmv.end(), MemberMeta(id)));
|
|
}
|
|
|
|
public:
|
|
|
|
/**
|
|
* Create an Collector.
|
|
*/
|
|
Collector() :
|
|
m_handler_pass2(*static_cast<TCollector*>(this)),
|
|
m_relations_buffer(initial_buffer_size, osmium::memory::Buffer::auto_grow::yes),
|
|
m_members_buffer(initial_buffer_size, osmium::memory::Buffer::auto_grow::yes) {
|
|
}
|
|
|
|
protected:
|
|
|
|
std::vector<MemberMeta>& member_meta(const item_type type) {
|
|
return m_member_meta[static_cast<uint16_t>(type) - 1];
|
|
}
|
|
|
|
callback_func_type callback() {
|
|
return m_callback;
|
|
}
|
|
|
|
const std::vector<RelationMeta>& relations() const {
|
|
return m_relations;
|
|
}
|
|
|
|
/**
|
|
* This method is called from the first pass handler for every
|
|
* relation in the input, to check whether it should be kept.
|
|
*
|
|
* Overwrite this method in a child class to only add relations
|
|
* you are interested in, for instance depending on the type tag.
|
|
* Storing relations takes a lot of memory, so it makes sense to
|
|
* filter this as much as possible.
|
|
*/
|
|
bool keep_relation(const osmium::Relation& /*relation*/) const {
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* This method is called for every member of every relation that
|
|
* should be kept. It should decide if the member is interesting or
|
|
* not and return true or false to signal that. Only interesting
|
|
* members are later added to the relation.
|
|
*
|
|
* Overwrite this method in a child class. In the MultiPolygonCollector
|
|
* this is for instance used to only keep members of type way and
|
|
* ignore all others.
|
|
*/
|
|
bool keep_member(const RelationMeta& /*relation_meta*/, const osmium::RelationMember& /*member*/) const {
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* This method is called for all nodes that are not a member of
|
|
* any relation.
|
|
*
|
|
* Overwrite this method in a child class if you are interested
|
|
* in this.
|
|
*/
|
|
void node_not_in_any_relation(const osmium::Node& /*node*/) {
|
|
}
|
|
|
|
/**
|
|
* This method is called for all ways that are not a member of
|
|
* any relation.
|
|
*
|
|
* Overwrite this method in a child class if you are interested
|
|
* in this.
|
|
*/
|
|
void way_not_in_any_relation(const osmium::Way& /*way*/) {
|
|
}
|
|
|
|
/**
|
|
* This method is called for all relations that are not a member of
|
|
* any relation.
|
|
*
|
|
* Overwrite this method in a child class if you are interested
|
|
* in this.
|
|
*/
|
|
void relation_not_in_any_relation(const osmium::Relation& /*relation*/) {
|
|
}
|
|
|
|
/**
|
|
* This method is called from the 2nd pass handler when all objects
|
|
* of types we are interested in have been seen.
|
|
*
|
|
* Overwrite this method in a child class if you are interested
|
|
* in this.
|
|
*
|
|
* Note that even after this call members might be missing if they
|
|
* were not in the input file! The derived class has to handle this
|
|
* case.
|
|
*/
|
|
void flush() {
|
|
}
|
|
|
|
const osmium::Relation& get_relation(size_t offset) const {
|
|
assert(m_relations_buffer.committed() > offset);
|
|
return m_relations_buffer.get<osmium::Relation>(offset);
|
|
}
|
|
|
|
/**
|
|
* Get the relation from a relation_meta.
|
|
*/
|
|
const osmium::Relation& get_relation(const RelationMeta& relation_meta) const {
|
|
return get_relation(relation_meta.relation_offset());
|
|
}
|
|
|
|
/**
|
|
* Get the relation from a member_meta.
|
|
*/
|
|
const osmium::Relation& get_relation(const MemberMeta& member_meta) const {
|
|
return get_relation(m_relations[member_meta.relation_pos()]);
|
|
}
|
|
|
|
osmium::OSMObject& get_member(size_t offset) const {
|
|
assert(m_members_buffer.committed() > offset);
|
|
return m_members_buffer.get<osmium::OSMObject>(offset);
|
|
}
|
|
|
|
private:
|
|
|
|
/**
|
|
* Tell the Collector that you are interested in this relation
|
|
* and want it kept until all members have been assembled and
|
|
* it is handed back to you.
|
|
*
|
|
* The relation is copied and stored in a buffer inside the
|
|
* collector.
|
|
*/
|
|
void add_relation(const osmium::Relation& relation) {
|
|
const size_t offset = m_relations_buffer.committed();
|
|
m_relations_buffer.add_item(relation);
|
|
|
|
RelationMeta relation_meta{offset};
|
|
|
|
int n = 0;
|
|
for (auto& member : m_relations_buffer.get<osmium::Relation>(offset).members()) {
|
|
if (static_cast<TCollector*>(this)->keep_member(relation_meta, member)) {
|
|
member_meta(member.type()).emplace_back(member.ref(), m_relations.size(), n);
|
|
relation_meta.increment_need_members();
|
|
} else {
|
|
member.set_ref(0); // set member id to zero to indicate we are not interested
|
|
}
|
|
++n;
|
|
}
|
|
|
|
assert(offset == m_relations_buffer.committed());
|
|
if (relation_meta.has_all_members()) {
|
|
m_relations_buffer.rollback();
|
|
} else {
|
|
m_relations_buffer.commit();
|
|
m_relations.push_back(relation_meta);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sort the vectors with the member infos so that we can do binary
|
|
* search on them.
|
|
*/
|
|
void sort_member_meta() {
|
|
std::sort(m_member_meta[0].begin(), m_member_meta[0].end());
|
|
std::sort(m_member_meta[1].begin(), m_member_meta[1].end());
|
|
std::sort(m_member_meta[2].begin(), m_member_meta[2].end());
|
|
}
|
|
|
|
static typename iterator_range<mm_iterator>::iterator::difference_type count_not_removed(const iterator_range<mm_iterator>& range) {
|
|
return std::count_if(range.begin(), range.end(), [](MemberMeta& mm) {
|
|
return !mm.removed();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Find this object in the member vectors and add it to all
|
|
* relations that need it.
|
|
*
|
|
* @returns true if the member was added to at least one
|
|
* relation and false otherwise
|
|
*/
|
|
bool find_and_add_object(const osmium::OSMObject& object) {
|
|
auto range = find_member_meta(object.type(), object.id());
|
|
|
|
if (count_not_removed(range) == 0) {
|
|
// nothing found
|
|
return false;
|
|
}
|
|
|
|
{
|
|
members_buffer().add_item(object);
|
|
const size_t member_offset = members_buffer().commit();
|
|
|
|
for (auto& member_meta : range) {
|
|
member_meta.set_buffer_offset(member_offset);
|
|
}
|
|
}
|
|
|
|
for (auto& member_meta : range) {
|
|
if (member_meta.removed()) {
|
|
break;
|
|
}
|
|
assert(member_meta.member_id() == object.id());
|
|
assert(member_meta.relation_pos() < m_relations.size());
|
|
RelationMeta& relation_meta = m_relations[member_meta.relation_pos()];
|
|
assert(member_meta.member_pos() < get_relation(relation_meta).members().size());
|
|
relation_meta.got_one_member();
|
|
if (relation_meta.has_all_members()) {
|
|
const size_t relation_offset = member_meta.relation_pos();
|
|
static_cast<TCollector*>(this)->complete_relation(relation_meta);
|
|
clear_member_metas(relation_meta);
|
|
m_relations[relation_offset] = RelationMeta{};
|
|
possibly_purge_removed_members();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void clear_member_metas(const RelationMeta& relation_meta) {
|
|
const osmium::Relation& relation = get_relation(relation_meta);
|
|
for (const auto& member : relation.members()) {
|
|
if (member.ref() != 0) {
|
|
const auto range = find_member_meta(member.type(), member.ref());
|
|
assert(!range.empty());
|
|
|
|
// if this is the last time this object was needed
|
|
// then mark it as removed
|
|
if (count_not_removed(range) == 1) {
|
|
get_member(range.begin()->buffer_offset()).set_removed(true);
|
|
}
|
|
|
|
for (auto& member_meta : range) {
|
|
if (!member_meta.removed() && relation.id() == get_relation(member_meta).id()) {
|
|
member_meta.remove();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public:
|
|
|
|
uint64_t used_memory() const {
|
|
const uint64_t nmembers = m_member_meta[0].capacity() + m_member_meta[1].capacity() + m_member_meta[2].capacity();
|
|
const uint64_t members = nmembers * sizeof(MemberMeta);
|
|
const uint64_t relations = m_relations.capacity() * sizeof(RelationMeta);
|
|
const uint64_t relations_buffer_capacity = m_relations_buffer.capacity();
|
|
const uint64_t members_buffer_capacity = m_members_buffer.capacity();
|
|
|
|
std::cerr << " nR = m_relations.capacity() ........... = " << std::setw(12) << m_relations.capacity() << "\n";
|
|
std::cerr << " nMN = m_member_meta[NODE].capacity() ... = " << std::setw(12) << m_member_meta[0].capacity() << "\n";
|
|
std::cerr << " nMW = m_member_meta[WAY].capacity() .... = " << std::setw(12) << m_member_meta[1].capacity() << "\n";
|
|
std::cerr << " nMR = m_member_meta[RELATION].capacity() = " << std::setw(12) << m_member_meta[2].capacity() << "\n";
|
|
std::cerr << " nM = m_member_meta[*].capacity() ...... = " << std::setw(12) << nmembers << "\n";
|
|
|
|
std::cerr << " sRM = sizeof(RelationMeta) ............. = " << std::setw(12) << sizeof(RelationMeta) << "\n";
|
|
std::cerr << " sMM = sizeof(MemberMeta) ............... = " << std::setw(12) << sizeof(MemberMeta) << "\n\n";
|
|
|
|
std::cerr << " nR * sRM ............................... = " << std::setw(12) << relations << "\n";
|
|
std::cerr << " nM * sMM ............................... = " << std::setw(12) << members << "\n";
|
|
std::cerr << " relations_buffer_capacity .............. = " << std::setw(12) << relations_buffer_capacity << "\n";
|
|
std::cerr << " members_buffer_capacity ................ = " << std::setw(12) << members_buffer_capacity << "\n";
|
|
|
|
const uint64_t total = relations + members + relations_buffer_capacity + members_buffer_capacity;
|
|
|
|
std::cerr << " total .................................. = " << std::setw(12) << total << "\n";
|
|
std::cerr << " =======================================================\n";
|
|
|
|
return relations_buffer_capacity + members_buffer_capacity + relations + members;
|
|
}
|
|
|
|
/**
|
|
* Return reference to second pass handler.
|
|
*/
|
|
HandlerPass2& handler(const callback_func_type& callback = nullptr) {
|
|
m_callback = callback;
|
|
return m_handler_pass2;
|
|
}
|
|
|
|
osmium::memory::Buffer& members_buffer() {
|
|
return m_members_buffer;
|
|
}
|
|
|
|
/**
|
|
* Is the given member available in the members buffer?
|
|
*
|
|
* If you also need the offset of the object, use
|
|
* get_availability_and_offset() instead, it is more efficient
|
|
* that way.
|
|
*
|
|
* @param type Item type
|
|
* @param id Object Id
|
|
* @returns True if the object is available, false otherwise.
|
|
*/
|
|
bool is_available(osmium::item_type type, osmium::object_id_type id) {
|
|
const auto range = find_member_meta(type, id);
|
|
assert(!range.empty());
|
|
return range.begin()->is_available();
|
|
}
|
|
|
|
/**
|
|
* Get offset of a member in the members buffer.
|
|
*
|
|
* @pre The member must be available. If you are not sure, call
|
|
* get_availability_and_offset() instead.
|
|
* @param type Item type
|
|
* @param id Object Id
|
|
* @returns The offset of the object in the members buffer.
|
|
*/
|
|
size_t get_offset(osmium::item_type type, osmium::object_id_type id) {
|
|
const auto range = find_member_meta(type, id);
|
|
assert(!range.empty());
|
|
assert(range.begin()->is_available());
|
|
return range.begin()->buffer_offset();
|
|
}
|
|
|
|
/**
|
|
* Checks whether a member is available in the members buffer
|
|
* and returns its offset.
|
|
*
|
|
* If the member is not available, the boolean returned as the
|
|
* first element in the pair is false. In that case the offset
|
|
* in the second element is undefined.
|
|
*
|
|
* If the member is available, the boolean returned as the first
|
|
* element in the pair is true and the second element of the
|
|
* pair contains the offset into the members buffer.
|
|
*
|
|
* @param type Item type
|
|
* @param id Object Id
|
|
* @returns Pair of bool (showing availability) and the offset.
|
|
*/
|
|
std::pair<bool, size_t> get_availability_and_offset(osmium::item_type type, osmium::object_id_type id) {
|
|
const auto range = find_member_meta(type, id);
|
|
assert(!range.empty());
|
|
if (range.begin()->is_available()) {
|
|
return std::make_pair(true, range.begin()->buffer_offset());
|
|
}
|
|
return std::make_pair(false, 0);
|
|
}
|
|
|
|
template <typename TIter>
|
|
void read_relations(TIter begin, TIter end) {
|
|
HandlerPass1 handler(*static_cast<TCollector*>(this));
|
|
osmium::apply(begin, end, handler);
|
|
sort_member_meta();
|
|
}
|
|
|
|
template <typename TSource>
|
|
void read_relations(TSource& source) {
|
|
using std::begin;
|
|
using std::end;
|
|
read_relations(begin(source), end(source));
|
|
source.close();
|
|
}
|
|
|
|
void moving_in_buffer(size_t old_offset, size_t new_offset) {
|
|
const osmium::OSMObject& object = m_members_buffer.get<osmium::OSMObject>(old_offset);
|
|
auto range = find_member_meta(object.type(), object.id());
|
|
for (auto& member_meta : range) {
|
|
assert(member_meta.buffer_offset() == old_offset);
|
|
member_meta.set_buffer_offset(new_offset);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Decide whether to purge removed members and then do it.
|
|
*
|
|
* Currently the purging is done every 10000 calls.
|
|
* This could probably be improved upon.
|
|
*/
|
|
void possibly_purge_removed_members() {
|
|
++m_count_complete;
|
|
if (m_count_complete > 10000) { // XXX
|
|
// const size_t size_before = m_members_buffer.committed();
|
|
m_members_buffer.purge_removed(this);
|
|
/*
|
|
const size_t size_after = m_members_buffer.committed();
|
|
double percent = static_cast<double>(size_before - size_after);
|
|
percent /= size_before;
|
|
percent *= 100;
|
|
std::cerr << "PURGE (size before=" << size_before << " after=" << size_after << " purged=" << (size_before - size_after) << " / " << static_cast<int>(percent) << "%)\n";
|
|
*/
|
|
m_count_complete = 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get a vector with pointers to all Relations that could not
|
|
* be completed, because members were missing in the input
|
|
* data.
|
|
*
|
|
* Note that these pointers point into memory allocated and
|
|
* owned by the Collector object.
|
|
*/
|
|
std::vector<const osmium::Relation*> get_incomplete_relations() const {
|
|
std::vector<const osmium::Relation*> relations;
|
|
for (const auto& relation_meta : m_relations) {
|
|
if (!relation_meta.has_all_members()) {
|
|
relations.push_back(&get_relation(relation_meta));
|
|
}
|
|
}
|
|
return relations;
|
|
}
|
|
|
|
}; // class Collector
|
|
|
|
} // namespace relations
|
|
|
|
} // namespace osmium
|
|
|
|
#endif // OSMIUM_RELATIONS_COLLECTOR_HPP
|