951 lines
42 KiB
C++
951 lines
42 KiB
C++
#ifndef OSMIUM_IO_DETAIL_PBF_OUTPUT_FORMAT_HPP
|
|
#define OSMIUM_IO_DETAIL_PBF_OUTPUT_FORMAT_HPP
|
|
|
|
/*
|
|
|
|
This file is part of Osmium (http://osmcode.org/libosmium).
|
|
|
|
Copyright 2013,2014 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.
|
|
|
|
*/
|
|
|
|
/*
|
|
|
|
About the .osm.pbf file format
|
|
This is an excerpt of <http://wiki.openstreetmap.org/wiki/PBF_Format>
|
|
|
|
The .osm.pbf format and it's derived formats (.osh.pbf and .osc.pbf) are encoded
|
|
using googles protobuf library for the low-level storage. They are constructed
|
|
by nesting data on two levels:
|
|
|
|
On the lower level the file is constructed using BlobHeaders and Blobs. A .osm.pbf
|
|
file contains multiple sequences of
|
|
1. a 4-byte header size, stored in network-byte-order
|
|
2. a BlobHeader of exactly this size
|
|
3. a Blob
|
|
|
|
The BlobHeader tells the reader about the type and size of the following Blob. The
|
|
Blob can contain data in raw or zlib-compressed form. After uncompressing the blob
|
|
it is treated differently depending on the type specified in the BlobHeader.
|
|
|
|
The contents of the Blob belongs to the higher level. It contains either an HeaderBlock
|
|
(type="OSMHeader") or an PrimitiveBlock (type="OSMData"). The file needs to have
|
|
at least one HeaderBlock before the first PrimitiveBlock.
|
|
|
|
The HeaderBlock contains meta-information like the writing program or a bbox. It may
|
|
also contain multiple "required features" that describe what kinds of input a
|
|
reading program needs to handle in order to fully understand the files' contents.
|
|
|
|
The PrimitiveBlock can store multiple types of objects (i.e. 5 nodes, 2 ways and
|
|
1 relation). It contains one or more PrimitiveGroup which in turn contain multiple
|
|
nodes, ways or relations. A PrimitiveGroup should only contain one kind of object.
|
|
|
|
There's a special kind of "object type" called dense-nodes. It is used to store nodes
|
|
in a very dense format, avoiding message overheads and using delta-encoding for nearly
|
|
all ids.
|
|
|
|
All Strings are stored as indexes to rows in a StringTable. The StringTable contains
|
|
one row for each used string, so strings that are used multiple times need to be
|
|
stored only once. The StringTable is sorted by usage-count, so the most often used
|
|
string is stored at index 1.
|
|
|
|
A simple outline of a .osm.pbf file could look like this:
|
|
|
|
4-bytes header size
|
|
BlobHeader
|
|
Blob
|
|
HeaderBlock
|
|
4-bytes header size
|
|
BlobHeader
|
|
Blob
|
|
PrimitiveBlock
|
|
StringTable
|
|
PrimitiveGroup
|
|
5 nodes
|
|
PrimitiveGroup
|
|
2 ways
|
|
PrimitiveGroup
|
|
1 relation
|
|
|
|
More complete outlines of real .osm.pbf files can be created using the osmpbf-outline tool:
|
|
<https://github.com/MaZderMind/OSM-binary/tree/osmpbf-outline>
|
|
*/
|
|
|
|
#include <algorithm>
|
|
#include <chrono>
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <cstdlib>
|
|
#include <future>
|
|
#include <iostream>
|
|
#include <memory>
|
|
#include <ratio>
|
|
#include <string>
|
|
#include <thread>
|
|
#include <time.h>
|
|
#include <utility>
|
|
|
|
#include <osmium/handler.hpp>
|
|
#include <osmium/io/detail/output_format.hpp>
|
|
#include <osmium/io/detail/pbf.hpp> // IWYU pragma: export
|
|
#include <osmium/io/detail/pbf_stringtable.hpp>
|
|
#include <osmium/io/detail/zlib.hpp>
|
|
#include <osmium/io/file.hpp>
|
|
#include <osmium/io/file_format.hpp>
|
|
#include <osmium/io/header.hpp>
|
|
#include <osmium/memory/buffer.hpp>
|
|
#include <osmium/memory/collection.hpp>
|
|
#include <osmium/osm/box.hpp>
|
|
#include <osmium/osm/item_type.hpp>
|
|
#include <osmium/osm/location.hpp>
|
|
#include <osmium/osm/node.hpp>
|
|
#include <osmium/osm/object.hpp>
|
|
#include <osmium/osm/relation.hpp>
|
|
#include <osmium/osm/tag.hpp>
|
|
#include <osmium/osm/timestamp.hpp>
|
|
#include <osmium/osm/way.hpp>
|
|
#include <osmium/visitor.hpp>
|
|
|
|
namespace osmium {
|
|
|
|
namespace io {
|
|
|
|
namespace detail {
|
|
|
|
namespace {
|
|
|
|
/**
|
|
* Serialize a protobuf message into a Blob, optionally apply compression
|
|
* and return it together with a BlobHeader ready to be written to a file.
|
|
*
|
|
* @param type Type-string used in the BlobHeader.
|
|
* @param msg Protobuf-message.
|
|
* @param use_compression Should the output be compressed using zlib?
|
|
*/
|
|
std::string serialize_blob(const std::string& type, const google::protobuf::MessageLite& msg, bool use_compression) {
|
|
OSMPBF::Blob pbf_blob;
|
|
|
|
{
|
|
std::string content;
|
|
msg.SerializeToString(&content);
|
|
|
|
pbf_blob.set_raw_size(content.size());
|
|
|
|
if (use_compression) {
|
|
pbf_blob.set_zlib_data(osmium::io::detail::zlib_compress(content));
|
|
} else {
|
|
pbf_blob.set_raw(content);
|
|
}
|
|
}
|
|
|
|
std::string blob_data;
|
|
pbf_blob.SerializeToString(&blob_data);
|
|
|
|
OSMPBF::BlobHeader pbf_blob_header;
|
|
pbf_blob_header.set_type(type);
|
|
pbf_blob_header.set_datasize(blob_data.size());
|
|
|
|
std::string blob_header_data;
|
|
pbf_blob_header.SerializeToString(&blob_header_data);
|
|
|
|
uint32_t sz = htonl(blob_header_data.size());
|
|
|
|
// write to output: the 4-byte BlobHeader-Size followed by the BlobHeader followed by the Blob
|
|
std::string output;
|
|
output.reserve(sizeof(sz) + blob_header_data.size() + blob_data.size());
|
|
output.append(reinterpret_cast<const char*>(&sz), sizeof(sz));
|
|
output.append(blob_header_data);
|
|
output.append(blob_data);
|
|
|
|
return output;
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
class PBFOutputFormat : public osmium::io::detail::OutputFormat, public osmium::handler::Handler {
|
|
|
|
/**
|
|
* This class models a variable that keeps track of the value
|
|
* it was last set to and returns the delta between old and
|
|
* new value from the update() call.
|
|
*/
|
|
template <typename T>
|
|
class Delta {
|
|
|
|
T m_value;
|
|
|
|
public:
|
|
|
|
Delta() :
|
|
m_value(0) {
|
|
}
|
|
|
|
void clear() {
|
|
m_value = 0;
|
|
}
|
|
|
|
T update(T new_value) {
|
|
using std::swap;
|
|
swap(m_value, new_value);
|
|
return m_value - new_value;
|
|
}
|
|
|
|
}; // class Delta
|
|
|
|
/**
|
|
* Maximum number of items in a primitive block.
|
|
*
|
|
* The uncompressed length of a Blob *should* be less
|
|
* than 16 megabytes and *must* be less than 32 megabytes.
|
|
*
|
|
* A block may contain any number of entities, as long as
|
|
* the size limits for the surrounding blob are obeyed.
|
|
* However, for simplicity, the current Osmosis (0.38)
|
|
* as well as Osmium implementation always
|
|
* uses at most 8k entities in a block.
|
|
*/
|
|
static constexpr uint32_t max_block_contents = 8000;
|
|
|
|
/**
|
|
* The output buffer (block) will be filled to about
|
|
* 95% and then written to disk. This leaves more than
|
|
* enough space for the string table (which typically
|
|
* needs about 0.1 to 0.3% of the block size).
|
|
*/
|
|
static constexpr int buffer_fill_percent = 95;
|
|
|
|
/**
|
|
* protobuf-struct of a HeaderBlock
|
|
*/
|
|
OSMPBF::HeaderBlock pbf_header_block;
|
|
|
|
/**
|
|
* protobuf-struct of a PrimitiveBlock
|
|
*/
|
|
OSMPBF::PrimitiveBlock pbf_primitive_block;
|
|
|
|
/**
|
|
* pointer to PrimitiveGroups inside the current PrimitiveBlock,
|
|
* used for writing nodes, ways or relations
|
|
*/
|
|
OSMPBF::PrimitiveGroup* pbf_nodes;
|
|
OSMPBF::PrimitiveGroup* pbf_ways;
|
|
OSMPBF::PrimitiveGroup* pbf_relations;
|
|
|
|
/**
|
|
* To flexibly handle multiple resolutions, the granularity, or
|
|
* resolution used for representing locations is adjustable in
|
|
* multiples of 1 nanodegree. The default scaling factor is 100
|
|
* nanodegrees, corresponding to about ~1cm at the equator.
|
|
* This is the current resolution of the OSM database.
|
|
*/
|
|
int m_location_granularity;
|
|
|
|
/**
|
|
* The granularity used for representing timestamps is also adjustable in
|
|
* multiples of 1 millisecond. The default scaling factor is 1000
|
|
* milliseconds, which is the current resolution of the OSM database.
|
|
*/
|
|
int m_date_granularity;
|
|
|
|
/**
|
|
* should nodes be serialized into the dense format?
|
|
*
|
|
* nodes can be encoded one of two ways, as a Node
|
|
* (m_use_dense_nodes = false) and a special dense format.
|
|
* In the dense format, all information is stored 'column wise',
|
|
* as an array of ID's, array of latitudes, and array of
|
|
* longitudes. Each column is delta-encoded. This reduces
|
|
* header overheads and allows delta-coding to work very effectively.
|
|
*/
|
|
bool m_use_dense_nodes {true};
|
|
|
|
/**
|
|
* should the PBF blobs contain zlib compressed data?
|
|
*
|
|
* the zlib compression is optional, it's possible to store the
|
|
* blobs in raw format. Disabling the compression can improve the
|
|
* writing speed a little but the output will be 2x to 3x bigger.
|
|
*/
|
|
bool m_use_compression {true};
|
|
|
|
/**
|
|
* While the .osm.pbf-format is able to carry all meta information, it is
|
|
* also able to omit this information to reduce size.
|
|
*/
|
|
bool m_should_add_metadata {true};
|
|
|
|
/**
|
|
* Should the visible flag be added on objects?
|
|
*/
|
|
bool m_add_visible;
|
|
|
|
/**
|
|
* counter used to quickly check the number of objects stored inside
|
|
* the current PrimitiveBlock. When the counter reaches max_block_contents
|
|
* the PrimitiveBlock is serialized into a Blob and flushed to the file.
|
|
*
|
|
* this check is performed in check_block_contents_counter() which is
|
|
* called once for each object.
|
|
*/
|
|
uint16_t primitive_block_contents;
|
|
uint32_t primitive_block_size;
|
|
|
|
// StringTable management
|
|
StringTable string_table;
|
|
|
|
/**
|
|
* These variables are used to calculate the
|
|
* delta-encoding while storing dense-nodes. It holds the last seen values
|
|
* from which the difference is stored into the protobuf.
|
|
*/
|
|
Delta<int64_t> m_delta_id;
|
|
Delta<int64_t> m_delta_lat;
|
|
Delta<int64_t> m_delta_lon;
|
|
Delta<int64_t> m_delta_timestamp;
|
|
Delta<int64_t> m_delta_changeset;
|
|
Delta<int64_t> m_delta_uid;
|
|
Delta<uint32_t> m_delta_user_sid;
|
|
|
|
bool debug;
|
|
|
|
bool has_debug_level(int) {
|
|
return false;
|
|
}
|
|
|
|
///// Blob writing /////
|
|
|
|
/**
|
|
* Before a PrimitiveBlock gets serialized, all interim StringTable-ids needs to be
|
|
* mapped to the associated real StringTable ids. This is done in this function.
|
|
*
|
|
* This function needs to know about the concrete structure of all item types to find
|
|
* all occurrences of string-ids.
|
|
*/
|
|
void map_string_ids() {
|
|
// test, if the node-block has been allocated
|
|
if (pbf_nodes) {
|
|
// iterate over all nodes, passing them to the map_common_string_ids function
|
|
for (int i=0, l=pbf_nodes->nodes_size(); i<l; ++i) {
|
|
map_common_string_ids(pbf_nodes->mutable_nodes(i));
|
|
}
|
|
|
|
// test, if the node-block has a densenodes structure
|
|
if (pbf_nodes->has_dense()) {
|
|
// get a pointer to the densenodes structure
|
|
OSMPBF::DenseNodes* dense = pbf_nodes->mutable_dense();
|
|
|
|
// in the densenodes structure keys and vals are encoded in an intermixed
|
|
// array, individual nodes are seperated by a value of 0 (0 in the StringTable
|
|
// is always unused). String-ids of 0 are thus kept alone.
|
|
for (int i=0, l=dense->keys_vals_size(); i<l; ++i) {
|
|
// map interim string-ids > 0 to real string ids
|
|
auto sid = dense->keys_vals(i);
|
|
assert(sid >= 0);
|
|
assert(sid < std::numeric_limits<osmium::io::detail::StringTable::string_id_type>::max());
|
|
if (sid > 0) {
|
|
dense->set_keys_vals(i, string_table.map_string_id(static_cast<osmium::io::detail::StringTable::string_id_type>(sid)));
|
|
}
|
|
}
|
|
|
|
// test if the densenodes block has meta infos
|
|
if (dense->has_denseinfo()) {
|
|
// get a pointer to the denseinfo structure
|
|
OSMPBF::DenseInfo* denseinfo = dense->mutable_denseinfo();
|
|
|
|
// iterate over all username string-ids
|
|
for (int i=0, l=denseinfo->user_sid_size(); i<l; ++i) {
|
|
// map interim string-ids > 0 to real string ids
|
|
auto usid = denseinfo->user_sid(i);
|
|
assert(usid < std::numeric_limits<osmium::io::detail::StringTable::string_id_type>::max());
|
|
auto user_sid = string_table.map_string_id(static_cast<osmium::io::detail::StringTable::string_id_type>(usid));
|
|
|
|
// delta encode the string-id
|
|
denseinfo->set_user_sid(i, m_delta_user_sid.update(user_sid));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// test, if the ways-block has been allocated
|
|
if (pbf_ways) {
|
|
// iterate over all ways, passing them to the map_common_string_ids function
|
|
for (int i=0, l=pbf_ways->ways_size(); i<l; ++i) {
|
|
map_common_string_ids(pbf_ways->mutable_ways(i));
|
|
}
|
|
}
|
|
|
|
// test, if the relations-block has been allocated
|
|
if (pbf_relations) {
|
|
// iterate over all relations
|
|
for (int i=0, l=pbf_relations->relations_size(); i<l; ++i) {
|
|
// get a pointer to the relation
|
|
OSMPBF::Relation* relation = pbf_relations->mutable_relations(i);
|
|
|
|
// pass them to the map_common_string_ids function
|
|
map_common_string_ids(relation);
|
|
|
|
// iterate over all relation members, mapping the interim string-ids
|
|
// of the role to real string ids
|
|
for (int mi=0, ml=relation->roles_sid_size(); mi<ml; ++mi) {
|
|
relation->set_roles_sid(mi, string_table.map_string_id(relation->roles_sid(mi)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* a helper function used in map_string_ids to map common interim string-ids of the
|
|
* user name and all tags to real string ids.
|
|
*
|
|
* TPBFObject is either OSMPBF::Node, OSMPBF::Way or OSMPBF::Relation.
|
|
*/
|
|
template <class TPBFObject>
|
|
void map_common_string_ids(TPBFObject* in) {
|
|
// if the object has meta-info attached
|
|
if (in->has_info()) {
|
|
// map the interim-id of the user name to a real id
|
|
OSMPBF::Info* info = in->mutable_info();
|
|
info->set_user_sid(string_table.map_string_id(info->user_sid()));
|
|
}
|
|
|
|
// iterate over all tags and map the interim-ids of the key and the value to real ids
|
|
for (int i=0, l=in->keys_size(); i<l; ++i) {
|
|
in->set_keys(i, string_table.map_string_id(in->keys(i)));
|
|
in->set_vals(i, string_table.map_string_id(in->vals(i)));
|
|
}
|
|
}
|
|
|
|
|
|
///// MetaData helper /////
|
|
|
|
/**
|
|
* convert a double lat or lon value to an int, respecting the current blocks granularity
|
|
*/
|
|
int64_t lonlat2int(double lonlat) {
|
|
return round(lonlat * OSMPBF::lonlat_resolution / location_granularity());
|
|
}
|
|
|
|
/**
|
|
* convert a timestamp to an int, respecting the current blocks granularity
|
|
*/
|
|
int64_t timestamp2int(time_t timestamp) {
|
|
return round(timestamp * (static_cast<double>(1000) / date_granularity()));
|
|
}
|
|
|
|
/**
|
|
* helper function used in the write()-calls to apply common information from an osmium-object
|
|
* onto a pbf-object.
|
|
*
|
|
* TPBFObject is either OSMPBF::Node, OSMPBF::Way or OSMPBF::Relation.
|
|
*/
|
|
template <class TPBFObject>
|
|
void apply_common_info(const osmium::OSMObject& in, TPBFObject* out) {
|
|
// set the object-id
|
|
out->set_id(in.id());
|
|
|
|
// iterate over all tags and set the keys and vals, recording the strings in the
|
|
// interim StringTable and storing the interim ids
|
|
for (const auto& tag : in.tags()) {
|
|
out->add_keys(string_table.record_string(tag.key()));
|
|
out->add_vals(string_table.record_string(tag.value()));
|
|
}
|
|
|
|
if (m_should_add_metadata) {
|
|
// add an info-section to the pbf object and set the meta-info on it
|
|
OSMPBF::Info* out_info = out->mutable_info();
|
|
if (m_add_visible) {
|
|
out_info->set_visible(in.visible());
|
|
}
|
|
out_info->set_version(static_cast<::google::protobuf::int32>(in.version()));
|
|
out_info->set_timestamp(timestamp2int(in.timestamp()));
|
|
out_info->set_changeset(in.changeset());
|
|
out_info->set_uid(static_cast<::google::protobuf::int32>(in.uid()));
|
|
out_info->set_user_sid(string_table.record_string(in.user()));
|
|
}
|
|
}
|
|
|
|
|
|
///// High-Level Block writing /////
|
|
|
|
/**
|
|
* store the current pbf_header_block into a Blob and clear this struct afterwards.
|
|
*/
|
|
void store_header_block() {
|
|
if (debug && has_debug_level(1)) {
|
|
std::cerr << "storing header block" << std::endl;
|
|
}
|
|
|
|
std::promise<std::string> promise;
|
|
m_output_queue.push(promise.get_future());
|
|
promise.set_value(serialize_blob("OSMHeader", pbf_header_block, m_use_compression));
|
|
|
|
pbf_header_block.Clear();
|
|
}
|
|
|
|
/**
|
|
* store the interim StringTable to the current pbf_primitive_block, map all interim string ids
|
|
* to real StringTable ids and then store the current pbf_primitive_block into a Blob and clear
|
|
* this struct and all related pointers and maps afterwards.
|
|
*/
|
|
void store_primitive_block() {
|
|
if (debug && has_debug_level(1)) {
|
|
std::cerr << "storing primitive block with " << primitive_block_contents << " items" << std::endl;
|
|
}
|
|
|
|
// set the granularity
|
|
pbf_primitive_block.set_granularity(location_granularity());
|
|
pbf_primitive_block.set_date_granularity(date_granularity());
|
|
|
|
// store the interim StringTable into the protobuf object
|
|
string_table.store_stringtable(pbf_primitive_block.mutable_stringtable());
|
|
|
|
// map all interim string ids to real ids
|
|
map_string_ids();
|
|
|
|
std::promise<std::string> promise;
|
|
m_output_queue.push(promise.get_future());
|
|
promise.set_value(serialize_blob("OSMData", pbf_primitive_block, m_use_compression));
|
|
while (m_output_queue.size() > 10) {
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // XXX
|
|
}
|
|
|
|
// clear the PrimitiveBlock struct
|
|
pbf_primitive_block.Clear();
|
|
|
|
// clear the interim StringTable and its id map
|
|
string_table.clear();
|
|
|
|
// reset the delta variables
|
|
m_delta_id.clear();
|
|
m_delta_lat.clear();
|
|
m_delta_lon.clear();
|
|
m_delta_timestamp.clear();
|
|
m_delta_changeset.clear();
|
|
m_delta_uid.clear();
|
|
m_delta_user_sid.clear();
|
|
|
|
// reset the contents-counter to zero
|
|
primitive_block_contents = 0;
|
|
primitive_block_size = 0;
|
|
|
|
// reset the node/way/relation pointers to nullptr
|
|
pbf_nodes = nullptr;
|
|
pbf_ways = nullptr;
|
|
pbf_relations = nullptr;
|
|
}
|
|
|
|
/**
|
|
* this little function checks primitive_block_contents counter against its maximum and calls
|
|
* store_primitive_block to flush the block to the disk when it's reached. It's also responsible
|
|
* for increasing this counter.
|
|
*
|
|
* this function also checks the estimated size of the current block and calls store_primitive_block
|
|
* when the estimated size reaches buffer_fill_percent of the maximum uncompressed blob size.
|
|
*/
|
|
void check_block_contents_counter() {
|
|
if (primitive_block_contents >= max_block_contents) {
|
|
store_primitive_block();
|
|
} else if (primitive_block_size > (static_cast<uint32_t>(OSMPBF::max_uncompressed_blob_size) * buffer_fill_percent / 100)) {
|
|
if (debug && has_debug_level(1)) {
|
|
std::cerr << "storing primitive_block with only " << primitive_block_contents << " items, because its ByteSize (" << primitive_block_size << ") reached " <<
|
|
(static_cast<float>(primitive_block_size) / static_cast<float>(OSMPBF::max_uncompressed_blob_size) * 100.0) << "% of the maximum blob-size" << std::endl;
|
|
}
|
|
|
|
store_primitive_block();
|
|
}
|
|
|
|
++primitive_block_contents;
|
|
}
|
|
|
|
|
|
///// Block content writing /////
|
|
|
|
/**
|
|
* Add a node to the block.
|
|
*
|
|
* @param node The node to add.
|
|
*/
|
|
void write_node(const osmium::Node& node) {
|
|
// add a way to the group
|
|
OSMPBF::Node* pbf_node = pbf_nodes->add_nodes();
|
|
|
|
// copy the common meta-info from the osmium-object to the pbf-object
|
|
apply_common_info(node, pbf_node);
|
|
|
|
// modify lat & lon to integers, respecting the block's granularity and copy
|
|
// the ints to the pbf-object
|
|
pbf_node->set_lon(lonlat2int(node.location().lon_without_check()));
|
|
pbf_node->set_lat(lonlat2int(node.location().lat_without_check()));
|
|
}
|
|
|
|
/**
|
|
* Add a node to the block using DenseNodes.
|
|
*
|
|
* @param node The node to add.
|
|
*/
|
|
void write_dense_node(const osmium::Node& node) {
|
|
// add a DenseNodes-Section to the PrimitiveGroup
|
|
OSMPBF::DenseNodes* dense = pbf_nodes->mutable_dense();
|
|
|
|
// copy the id, delta encoded
|
|
dense->add_id(m_delta_id.update(node.id()));
|
|
|
|
// copy the longitude, delta encoded
|
|
dense->add_lon(m_delta_lon.update(lonlat2int(node.location().lon_without_check())));
|
|
|
|
// copy the latitude, delta encoded
|
|
dense->add_lat(m_delta_lat.update(lonlat2int(node.location().lat_without_check())));
|
|
|
|
// in the densenodes structure keys and vals are encoded in an intermixed
|
|
// array, individual nodes are seperated by a value of 0 (0 in the StringTable
|
|
// is always unused)
|
|
// so for three nodes the keys_vals array may look like this: 3 5 2 1 0 0 8 5
|
|
// the first node has two tags (3=>5 and 2=>1), the second node does not
|
|
// have any tags and the third node has a single tag (8=>5)
|
|
for (const auto& tag : node.tags()) {
|
|
dense->add_keys_vals(string_table.record_string(tag.key()));
|
|
dense->add_keys_vals(string_table.record_string(tag.value()));
|
|
}
|
|
dense->add_keys_vals(0);
|
|
|
|
if (m_should_add_metadata) {
|
|
// add a DenseInfo-Section to the PrimitiveGroup
|
|
OSMPBF::DenseInfo* denseinfo = dense->mutable_denseinfo();
|
|
|
|
denseinfo->add_version(static_cast<::google::protobuf::int32>(node.version()));
|
|
|
|
if (m_add_visible) {
|
|
denseinfo->add_visible(node.visible());
|
|
}
|
|
|
|
// copy the timestamp, delta encoded
|
|
denseinfo->add_timestamp(m_delta_timestamp.update(timestamp2int(node.timestamp())));
|
|
|
|
// copy the changeset, delta encoded
|
|
denseinfo->add_changeset(m_delta_changeset.update(node.changeset()));
|
|
|
|
// copy the user id, delta encoded
|
|
denseinfo->add_uid(m_delta_uid.update(node.uid()));
|
|
|
|
// record the user-name to the interim stringtable and copy the
|
|
// interim string-id to the pbf-object
|
|
denseinfo->add_user_sid(string_table.record_string(node.user()));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add a way to the block.
|
|
*
|
|
* @param way The way to add.
|
|
*/
|
|
void write_way(const osmium::Way& way) {
|
|
// add a way to the group
|
|
OSMPBF::Way* pbf_way = pbf_ways->add_ways();
|
|
|
|
// copy the common meta-info from the osmium-object to the pbf-object
|
|
apply_common_info(way, pbf_way);
|
|
|
|
// last way-node-id used for delta-encoding
|
|
Delta<int64_t> delta_id;
|
|
|
|
for (const auto& node_ref : way.nodes()) {
|
|
// copy the way-node-id, delta encoded
|
|
pbf_way->add_refs(delta_id.update(node_ref.ref()));
|
|
}
|
|
|
|
// count up blob size by the size of the Way
|
|
primitive_block_size += pbf_way->ByteSize();
|
|
}
|
|
|
|
/**
|
|
* Add a relation to the block.
|
|
*
|
|
* @param relation The relation to add.
|
|
*/
|
|
void write_relation(const osmium::Relation& relation) {
|
|
// add a relation to the group
|
|
OSMPBF::Relation* pbf_relation = pbf_relations->add_relations();
|
|
|
|
// copy the common meta-info from the osmium-object to the pbf-object
|
|
apply_common_info(relation, pbf_relation);
|
|
|
|
Delta<int64_t> delta_id;
|
|
|
|
for (const auto& member : relation.members()) {
|
|
// record the relation-member role to the interim stringtable and copy the
|
|
// interim string-id to the pbf-object
|
|
pbf_relation->add_roles_sid(string_table.record_string(member.role()));
|
|
|
|
// copy the relation-member-id, delta encoded
|
|
pbf_relation->add_memids(delta_id.update(member.ref()));
|
|
|
|
// copy the relation-member-type, mapped to the OSMPBF enum
|
|
pbf_relation->add_types(item_type_to_osmpbf_membertype(member.type()));
|
|
}
|
|
|
|
// count up blob size by the size of the Relation
|
|
primitive_block_size += pbf_relation->ByteSize();
|
|
}
|
|
|
|
// objects of this class can't be copied
|
|
PBFOutputFormat(const PBFOutputFormat&) = delete;
|
|
PBFOutputFormat& operator=(const PBFOutputFormat&) = delete;
|
|
|
|
public:
|
|
|
|
/**
|
|
* Create PBFOutputFormat object from File.
|
|
*/
|
|
explicit PBFOutputFormat(const osmium::io::File& file, data_queue_type& output_queue) :
|
|
OutputFormat(file, output_queue),
|
|
pbf_header_block(),
|
|
pbf_primitive_block(),
|
|
pbf_nodes(nullptr),
|
|
pbf_ways(nullptr),
|
|
pbf_relations(nullptr),
|
|
m_location_granularity(pbf_primitive_block.granularity()),
|
|
m_date_granularity(pbf_primitive_block.date_granularity()),
|
|
m_add_visible(file.has_multiple_object_versions()),
|
|
primitive_block_contents(0),
|
|
primitive_block_size(0),
|
|
string_table(),
|
|
m_delta_id(),
|
|
m_delta_lat(),
|
|
m_delta_lon(),
|
|
m_delta_timestamp(),
|
|
m_delta_changeset(),
|
|
m_delta_uid(),
|
|
m_delta_user_sid(),
|
|
debug(true) {
|
|
GOOGLE_PROTOBUF_VERIFY_VERSION;
|
|
if (file.get("pbf_dense_nodes") == "false") {
|
|
m_use_dense_nodes = false;
|
|
}
|
|
if (file.get("pbf_compression") == "none" || file.get("pbf_compression") == "false") {
|
|
m_use_compression = false;
|
|
}
|
|
if (file.get("pbf_add_metadata") == "false") {
|
|
m_should_add_metadata = false;
|
|
}
|
|
}
|
|
|
|
void write_buffer(osmium::memory::Buffer&& buffer) override final {
|
|
osmium::apply(buffer.cbegin(), buffer.cend(), *this);
|
|
}
|
|
|
|
|
|
/**
|
|
* getter to access the granularity
|
|
*/
|
|
int location_granularity() const {
|
|
return m_location_granularity;
|
|
}
|
|
|
|
/**
|
|
* setter to set the granularity
|
|
*/
|
|
PBFOutputFormat& location_granularity(int g) {
|
|
m_location_granularity = g;
|
|
return *this;
|
|
}
|
|
|
|
|
|
/**
|
|
* getter to access the date_granularity
|
|
*/
|
|
int date_granularity() const {
|
|
return m_date_granularity;
|
|
}
|
|
|
|
/**
|
|
* Set date granularity.
|
|
*/
|
|
PBFOutputFormat& date_granularity(int g) {
|
|
m_date_granularity = g;
|
|
return *this;
|
|
}
|
|
|
|
|
|
/**
|
|
* Initialize the writing process.
|
|
*
|
|
* This initializes the header-block, sets the required-features and
|
|
* the writing-program and adds the obligatory StringTable-Index 0.
|
|
*/
|
|
void write_header(const osmium::io::Header& header) override final {
|
|
// add the schema version as required feature to the HeaderBlock
|
|
pbf_header_block.add_required_features("OsmSchema-V0.6");
|
|
|
|
// when the densenodes-feature is used, add DenseNodes as required feature
|
|
if (m_use_dense_nodes) {
|
|
pbf_header_block.add_required_features("DenseNodes");
|
|
}
|
|
|
|
// when the resulting file will carry history information, add
|
|
// HistoricalInformation as required feature
|
|
if (this->m_file.has_multiple_object_versions()) {
|
|
pbf_header_block.add_required_features("HistoricalInformation");
|
|
}
|
|
|
|
// set the writing program
|
|
pbf_header_block.set_writingprogram(header.get("generator"));
|
|
|
|
if (!header.boxes().empty()) {
|
|
OSMPBF::HeaderBBox* pbf_bbox = pbf_header_block.mutable_bbox();
|
|
osmium::Box box = header.joined_boxes();
|
|
pbf_bbox->set_left(static_cast<::google::protobuf::int64>(box.bottom_left().lon() * OSMPBF::lonlat_resolution));
|
|
pbf_bbox->set_bottom(static_cast<::google::protobuf::int64>(box.bottom_left().lat() * OSMPBF::lonlat_resolution));
|
|
pbf_bbox->set_right(static_cast<::google::protobuf::int64>(box.top_right().lon() * OSMPBF::lonlat_resolution));
|
|
pbf_bbox->set_top(static_cast<::google::protobuf::int64>(box.top_right().lat() * OSMPBF::lonlat_resolution));
|
|
}
|
|
|
|
std::string osmosis_replication_timestamp = header.get("osmosis_replication_timestamp");
|
|
if (!osmosis_replication_timestamp.empty()) {
|
|
osmium::Timestamp ts(osmosis_replication_timestamp.c_str());
|
|
pbf_header_block.set_osmosis_replication_timestamp(ts);
|
|
}
|
|
|
|
std::string osmosis_replication_sequence_number = header.get("osmosis_replication_sequence_number");
|
|
if (!osmosis_replication_sequence_number.empty()) {
|
|
pbf_header_block.set_osmosis_replication_sequence_number(std::atoll(osmosis_replication_sequence_number.c_str()));
|
|
}
|
|
|
|
std::string osmosis_replication_base_url = header.get("osmosis_replication_base_url");
|
|
if (!osmosis_replication_base_url.empty()) {
|
|
pbf_header_block.set_osmosis_replication_base_url(osmosis_replication_base_url);
|
|
}
|
|
|
|
store_header_block();
|
|
}
|
|
|
|
/**
|
|
* Add a node to the pbf.
|
|
*
|
|
* A call to this method won't write the node to the file directly but
|
|
* cache it for later bulk-writing. Calling final() ensures that everything
|
|
* gets written and every file pointer is closed.
|
|
*/
|
|
void node(const osmium::Node& node) {
|
|
// first of we check the contents-counter which may flush the cached nodes to
|
|
// disk if the limit is reached. This call also increases the contents-counter
|
|
check_block_contents_counter();
|
|
|
|
if (debug && has_debug_level(2)) {
|
|
std::cerr << "node " << node.id() << " v" << node.version() << std::endl;
|
|
}
|
|
|
|
// if no PrimitiveGroup for nodes has been added, add one and save the pointer
|
|
if (!pbf_nodes) {
|
|
pbf_nodes = pbf_primitive_block.add_primitivegroup();
|
|
}
|
|
|
|
if (m_use_dense_nodes) {
|
|
write_dense_node(node);
|
|
} else {
|
|
write_node(node);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add a way to the pbf.
|
|
*
|
|
* A call to this method won't write the way to the file directly but
|
|
* cache it for later bulk-writing. Calling final() ensures that everything
|
|
* gets written and every file pointer is closed.
|
|
*/
|
|
void way(const osmium::Way& way) {
|
|
// first of we check the contents-counter which may flush the cached ways to
|
|
// disk if the limit is reached. This call also increases the contents-counter
|
|
check_block_contents_counter();
|
|
|
|
// if no PrimitiveGroup for nodes has been added, add one and save the pointer
|
|
if (!pbf_ways) {
|
|
pbf_ways = pbf_primitive_block.add_primitivegroup();
|
|
}
|
|
|
|
write_way(way);
|
|
}
|
|
|
|
/**
|
|
* Add a relation to the pbf.
|
|
*
|
|
* A call to this method won't write the way to the file directly but
|
|
* cache it for later bulk-writing. Calling final() ensures that everything
|
|
* gets written and every file pointer is closed.
|
|
*/
|
|
void relation(const osmium::Relation& relation) {
|
|
// first of we check the contents-counter which may flush the cached relations to
|
|
// disk if the limit is reached. This call also increases the contents-counter
|
|
check_block_contents_counter();
|
|
|
|
// if no PrimitiveGroup for relations has been added, add one and save the pointer
|
|
if (!pbf_relations) {
|
|
pbf_relations = pbf_primitive_block.add_primitivegroup();
|
|
}
|
|
|
|
write_relation(relation);
|
|
}
|
|
|
|
/**
|
|
* Finalize the writing process, flush any open primitive blocks to the file and
|
|
* close the file.
|
|
*/
|
|
void close() override final {
|
|
if (debug && has_debug_level(1)) {
|
|
std::cerr << "finishing" << std::endl;
|
|
}
|
|
|
|
// if the current block contains any elements, flush it to the protobuf
|
|
if (primitive_block_contents > 0) {
|
|
store_primitive_block();
|
|
}
|
|
|
|
std::promise<std::string> promise;
|
|
m_output_queue.push(promise.get_future());
|
|
promise.set_value(std::string());
|
|
}
|
|
|
|
}; // class PBFOutputFormat
|
|
|
|
namespace {
|
|
|
|
const bool registered_pbf_output = osmium::io::detail::OutputFormatFactory::instance().register_output_format(osmium::io::file_format::pbf,
|
|
[](const osmium::io::File& file, data_queue_type& output_queue) {
|
|
return new osmium::io::detail::PBFOutputFormat(file, output_queue);
|
|
});
|
|
|
|
} // anonymous namespace
|
|
|
|
} // namespace detail
|
|
|
|
} // namespace io
|
|
|
|
} // namespace osmium
|
|
|
|
#endif // OSMIUM_IO_DETAIL_PBF_OUTPUT_FORMAT_HPP
|