485 lines
16 KiB
C++
485 lines
16 KiB
C++
|
#ifndef VTZERO_LAYER_HPP
|
||
|
#define VTZERO_LAYER_HPP
|
||
|
|
||
|
/*****************************************************************************
|
||
|
|
||
|
vtzero - Tiny and fast vector tile decoder and encoder in C++.
|
||
|
|
||
|
This file is from https://github.com/mapbox/vtzero where you can find more
|
||
|
documentation.
|
||
|
|
||
|
*****************************************************************************/
|
||
|
|
||
|
/**
|
||
|
* @file layer.hpp
|
||
|
*
|
||
|
* @brief Contains the layer class.
|
||
|
*/
|
||
|
|
||
|
#include "exception.hpp"
|
||
|
#include "feature.hpp"
|
||
|
#include "geometry.hpp"
|
||
|
#include "property_value.hpp"
|
||
|
#include "types.hpp"
|
||
|
|
||
|
#include <protozero/pbf_message.hpp>
|
||
|
|
||
|
#include <cstdint>
|
||
|
#include <iterator>
|
||
|
#include <vector>
|
||
|
|
||
|
namespace vtzero {
|
||
|
|
||
|
/**
|
||
|
* A layer according to spec 4.1. It contains a version, the extent,
|
||
|
* and a name. For the most efficient way to access the features in this
|
||
|
* layer call next_feature() until it returns an invalid feature:
|
||
|
*
|
||
|
* @code
|
||
|
* std::string data = ...;
|
||
|
* vector_tile tile{data};
|
||
|
* layer = tile.next_layer();
|
||
|
* while (auto feature = layer.next_feature()) {
|
||
|
* ...
|
||
|
* }
|
||
|
* @endcode
|
||
|
*
|
||
|
* If you know the ID of a feature, you can get it directly with
|
||
|
* @code
|
||
|
* layer.get_feature_by_id(7);
|
||
|
* @endcode
|
||
|
*/
|
||
|
class layer {
|
||
|
|
||
|
data_view m_data{};
|
||
|
uint32_t m_version = 1; // defaults to 1, see https://github.com/mapbox/vector-tile-spec/blob/master/2.1/vector_tile.proto#L55
|
||
|
uint32_t m_extent = 4096; // defaults to 4096, see https://github.com/mapbox/vector-tile-spec/blob/master/2.1/vector_tile.proto#L70
|
||
|
std::size_t m_num_features = 0;
|
||
|
data_view m_name{};
|
||
|
protozero::pbf_message<detail::pbf_layer> m_layer_reader{m_data};
|
||
|
mutable std::vector<data_view> m_key_table;
|
||
|
mutable std::vector<property_value> m_value_table;
|
||
|
mutable std::size_t m_key_table_size = 0;
|
||
|
mutable std::size_t m_value_table_size = 0;
|
||
|
|
||
|
void initialize_tables() const {
|
||
|
m_key_table.reserve(m_key_table_size);
|
||
|
m_key_table_size = 0;
|
||
|
|
||
|
m_value_table.reserve(m_value_table_size);
|
||
|
m_value_table_size = 0;
|
||
|
|
||
|
protozero::pbf_message<detail::pbf_layer> reader{m_data};
|
||
|
while (reader.next()) {
|
||
|
switch (reader.tag_and_type()) {
|
||
|
case protozero::tag_and_type(detail::pbf_layer::keys, protozero::pbf_wire_type::length_delimited):
|
||
|
m_key_table.push_back(reader.get_view());
|
||
|
break;
|
||
|
case protozero::tag_and_type(detail::pbf_layer::values, protozero::pbf_wire_type::length_delimited):
|
||
|
m_value_table.emplace_back(reader.get_view());
|
||
|
break;
|
||
|
default:
|
||
|
reader.skip(); // ignore unknown fields
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public:
|
||
|
|
||
|
/**
|
||
|
* Construct an invalid layer object.
|
||
|
*/
|
||
|
layer() = default;
|
||
|
|
||
|
/**
|
||
|
* Construct a layer object. This is usually not something done in
|
||
|
* user code, because layers are created by the tile_iterator.
|
||
|
*
|
||
|
* @throws format_exception if the layer data is ill-formed.
|
||
|
* @throws version_exception if the layer contains an unsupported version
|
||
|
* number (only version 1 and 2 are supported)
|
||
|
* @throws any protozero exception if the protobuf encoding is invalid.
|
||
|
*/
|
||
|
explicit layer(const data_view data) :
|
||
|
m_data(data) {
|
||
|
protozero::pbf_message<detail::pbf_layer> reader{data};
|
||
|
while (reader.next()) {
|
||
|
switch (reader.tag_and_type()) {
|
||
|
case protozero::tag_and_type(detail::pbf_layer::version, protozero::pbf_wire_type::varint):
|
||
|
m_version = reader.get_uint32();
|
||
|
break;
|
||
|
case protozero::tag_and_type(detail::pbf_layer::name, protozero::pbf_wire_type::length_delimited):
|
||
|
m_name = reader.get_view();
|
||
|
break;
|
||
|
case protozero::tag_and_type(detail::pbf_layer::features, protozero::pbf_wire_type::length_delimited):
|
||
|
reader.skip(); // ignore features for now
|
||
|
++m_num_features;
|
||
|
break;
|
||
|
case protozero::tag_and_type(detail::pbf_layer::keys, protozero::pbf_wire_type::length_delimited):
|
||
|
reader.skip();
|
||
|
++m_key_table_size;
|
||
|
break;
|
||
|
case protozero::tag_and_type(detail::pbf_layer::values, protozero::pbf_wire_type::length_delimited):
|
||
|
reader.skip();
|
||
|
++m_value_table_size;
|
||
|
break;
|
||
|
case protozero::tag_and_type(detail::pbf_layer::extent, protozero::pbf_wire_type::varint):
|
||
|
m_extent = reader.get_uint32();
|
||
|
break;
|
||
|
default:
|
||
|
throw format_exception{"unknown field in layer (tag=" +
|
||
|
std::to_string(static_cast<uint32_t>(reader.tag())) +
|
||
|
", type=" +
|
||
|
std::to_string(static_cast<uint32_t>(reader.wire_type())) +
|
||
|
")"};
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// This library can only handle version 1 and 2.
|
||
|
if (m_version < 1 || m_version > 2) {
|
||
|
throw version_exception{m_version};
|
||
|
}
|
||
|
|
||
|
// 4.1 "A layer MUST contain a name field."
|
||
|
if (m_name.data() == nullptr) {
|
||
|
throw format_exception{"missing name field in layer (spec 4.1)"};
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Is this a valid layer? Valid layers are those not created from the
|
||
|
* default constructor.
|
||
|
*/
|
||
|
bool valid() const noexcept {
|
||
|
return m_data.data() != nullptr;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Is this a valid layer? Valid layers are those not created from the
|
||
|
* default constructor.
|
||
|
*/
|
||
|
explicit operator bool() const noexcept {
|
||
|
return valid();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get a reference to the raw data this layer is created from.
|
||
|
*/
|
||
|
data_view data() const noexcept {
|
||
|
return m_data;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return the name of the layer.
|
||
|
*
|
||
|
* @pre @code valid() @endcode
|
||
|
*/
|
||
|
data_view name() const noexcept {
|
||
|
vtzero_assert_in_noexcept_function(valid());
|
||
|
|
||
|
return m_name;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return the version of this layer.
|
||
|
*
|
||
|
* @pre @code valid() @endcode
|
||
|
*/
|
||
|
std::uint32_t version() const noexcept {
|
||
|
vtzero_assert_in_noexcept_function(valid());
|
||
|
|
||
|
return m_version;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return the extent of this layer.
|
||
|
*
|
||
|
* @pre @code valid() @endcode
|
||
|
*/
|
||
|
std::uint32_t extent() const noexcept {
|
||
|
vtzero_assert_in_noexcept_function(valid());
|
||
|
|
||
|
return m_extent;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Does this layer contain any features?
|
||
|
*
|
||
|
* Complexity: Constant.
|
||
|
*/
|
||
|
bool empty() const noexcept {
|
||
|
return m_num_features == 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The number of features in this layer.
|
||
|
*
|
||
|
* Complexity: Constant.
|
||
|
*/
|
||
|
std::size_t num_features() const noexcept {
|
||
|
return m_num_features;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return a reference to the key table.
|
||
|
*
|
||
|
* Complexity: Amortized constant. First time the table is needed
|
||
|
* it needs to be created.
|
||
|
*
|
||
|
* @pre @code valid() @endcode
|
||
|
*/
|
||
|
const std::vector<data_view>& key_table() const {
|
||
|
vtzero_assert(valid());
|
||
|
|
||
|
if (m_key_table_size > 0) {
|
||
|
initialize_tables();
|
||
|
}
|
||
|
return m_key_table;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return a reference to the value table.
|
||
|
*
|
||
|
* Complexity: Amortized constant. First time the table is needed
|
||
|
* it needs to be created.
|
||
|
*
|
||
|
* @pre @code valid() @endcode
|
||
|
*/
|
||
|
const std::vector<property_value>& value_table() const {
|
||
|
vtzero_assert(valid());
|
||
|
|
||
|
if (m_value_table_size > 0) {
|
||
|
initialize_tables();
|
||
|
}
|
||
|
return m_value_table;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return the size of the key table. This returns the correct value
|
||
|
* whether the key table was already built or not.
|
||
|
*
|
||
|
* Complexity: Constant.
|
||
|
*
|
||
|
* @returns Size of the key table.
|
||
|
*/
|
||
|
std::size_t key_table_size() const noexcept {
|
||
|
return m_key_table_size > 0 ? m_key_table_size : m_key_table.size();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return the size of the value table. This returns the correct value
|
||
|
* whether the value table was already built or not.
|
||
|
*
|
||
|
* Complexity: Constant.
|
||
|
*
|
||
|
* @returns Size of the value table.
|
||
|
*/
|
||
|
std::size_t value_table_size() const noexcept {
|
||
|
return m_value_table_size > 0 ? m_value_table_size : m_value_table.size();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the property key with the given index.
|
||
|
*
|
||
|
* Complexity: Amortized constant. First time the table is needed
|
||
|
* it needs to be created.
|
||
|
*
|
||
|
* @throws out_of_range_exception if the index is out of range.
|
||
|
* @pre @code valid() @endcode
|
||
|
*/
|
||
|
data_view key(index_value index) const {
|
||
|
vtzero_assert(valid());
|
||
|
|
||
|
const auto& table = key_table();
|
||
|
if (index.value() >= table.size()) {
|
||
|
throw out_of_range_exception{index.value()};
|
||
|
}
|
||
|
|
||
|
return table[index.value()];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the property value with the given index.
|
||
|
*
|
||
|
* Complexity: Amortized constant. First time the table is needed
|
||
|
* it needs to be created.
|
||
|
*
|
||
|
* @throws out_of_range_exception if the index is out of range.
|
||
|
* @pre @code valid() @endcode
|
||
|
*/
|
||
|
property_value value(index_value index) const {
|
||
|
vtzero_assert(valid());
|
||
|
|
||
|
const auto& table = value_table();
|
||
|
if (index.value() >= table.size()) {
|
||
|
throw out_of_range_exception{index.value()};
|
||
|
}
|
||
|
|
||
|
return table[index.value()];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the next feature in this layer.
|
||
|
*
|
||
|
* Note that the feature returned will internally contain a pointer to
|
||
|
* the layer it came from. The layer has to stay valid as long as the
|
||
|
* feature is used.
|
||
|
*
|
||
|
* Complexity: Constant.
|
||
|
*
|
||
|
* @returns The next feature or the invalid feature if there are no
|
||
|
* more features.
|
||
|
* @throws format_exception if the layer data is ill-formed.
|
||
|
* @throws any protozero exception if the protobuf encoding is invalid.
|
||
|
* @pre @code valid() @endcode
|
||
|
*/
|
||
|
feature next_feature() {
|
||
|
vtzero_assert(valid());
|
||
|
|
||
|
const bool has_next = m_layer_reader.next(detail::pbf_layer::features,
|
||
|
protozero::pbf_wire_type::length_delimited);
|
||
|
|
||
|
return has_next ? feature{this, m_layer_reader.get_view()} : feature{};
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Reset the feature iterator. The next time next_feature() is called,
|
||
|
* it will begin from the first feature again.
|
||
|
*
|
||
|
* Complexity: Constant.
|
||
|
*
|
||
|
* @pre @code valid() @endcode
|
||
|
*/
|
||
|
void reset_feature() noexcept {
|
||
|
vtzero_assert_in_noexcept_function(valid());
|
||
|
|
||
|
m_layer_reader = protozero::pbf_message<detail::pbf_layer>{m_data};
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Call a function for each feature in this layer.
|
||
|
*
|
||
|
* @tparam The type of the function. It must take a single argument
|
||
|
* of type feature&& and return a bool. If the function returns
|
||
|
* false, the iteration will be stopped.
|
||
|
* @param func The function to call.
|
||
|
* @returns true if the iteration was completed and false otherwise.
|
||
|
* @pre @code valid() @endcode
|
||
|
*/
|
||
|
template <typename TFunc>
|
||
|
bool for_each_feature(TFunc&& func) const {
|
||
|
vtzero_assert(valid());
|
||
|
|
||
|
protozero::pbf_message<detail::pbf_layer> layer_reader{m_data};
|
||
|
while (layer_reader.next(detail::pbf_layer::features,
|
||
|
protozero::pbf_wire_type::length_delimited)) {
|
||
|
if (!std::forward<TFunc>(func)(feature{this, layer_reader.get_view()})) {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the feature with the specified ID. If there are several features
|
||
|
* with the same ID, it is undefined which one you'll get.
|
||
|
*
|
||
|
* Note that the feature returned will internally contain a pointer to
|
||
|
* the layer it came from. The layer has to stay valid as long as the
|
||
|
* feature is used.
|
||
|
*
|
||
|
* Complexity: Linear in the number of features.
|
||
|
*
|
||
|
* @param id The ID to look for.
|
||
|
* @returns Feature with the specified ID or the invalid feature if
|
||
|
* there is no feature with this ID.
|
||
|
* @throws format_exception if the layer data is ill-formed.
|
||
|
* @throws any protozero exception if the protobuf encoding is invalid.
|
||
|
* @pre @code valid() @endcode
|
||
|
*/
|
||
|
feature get_feature_by_id(uint64_t id) const {
|
||
|
vtzero_assert(valid());
|
||
|
|
||
|
protozero::pbf_message<detail::pbf_layer> layer_reader{m_data};
|
||
|
while (layer_reader.next(detail::pbf_layer::features, protozero::pbf_wire_type::length_delimited)) {
|
||
|
const auto feature_data = layer_reader.get_view();
|
||
|
protozero::pbf_message<detail::pbf_feature> feature_reader{feature_data};
|
||
|
if (feature_reader.next(detail::pbf_feature::id, protozero::pbf_wire_type::varint)) {
|
||
|
if (feature_reader.get_uint64() == id) {
|
||
|
return feature{this, feature_data};
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return feature{};
|
||
|
}
|
||
|
|
||
|
}; // class layer
|
||
|
|
||
|
inline property feature::next_property() {
|
||
|
const auto idxs = next_property_indexes();
|
||
|
property p{};
|
||
|
if (idxs.valid()) {
|
||
|
p = {m_layer->key(idxs.key()),
|
||
|
m_layer->value(idxs.value())};
|
||
|
}
|
||
|
return p;
|
||
|
}
|
||
|
|
||
|
inline index_value_pair feature::next_property_indexes() {
|
||
|
vtzero_assert(valid());
|
||
|
if (m_property_iterator == m_properties.end()) {
|
||
|
return {};
|
||
|
}
|
||
|
|
||
|
const auto ki = *m_property_iterator++;
|
||
|
if (!index_value{ki}.valid()) {
|
||
|
throw out_of_range_exception{ki};
|
||
|
}
|
||
|
|
||
|
assert(m_property_iterator != m_properties.end());
|
||
|
const auto vi = *m_property_iterator++;
|
||
|
if (!index_value{vi}.valid()) {
|
||
|
throw out_of_range_exception{vi};
|
||
|
}
|
||
|
|
||
|
if (ki >= m_layer->key_table_size()) {
|
||
|
throw out_of_range_exception{ki};
|
||
|
}
|
||
|
|
||
|
if (vi >= m_layer->value_table_size()) {
|
||
|
throw out_of_range_exception{vi};
|
||
|
}
|
||
|
|
||
|
return {ki, vi};
|
||
|
}
|
||
|
|
||
|
template <typename TFunc>
|
||
|
bool feature::for_each_property(TFunc&& func) const {
|
||
|
vtzero_assert(valid());
|
||
|
|
||
|
for (auto it = m_properties.begin(); it != m_properties.end();) {
|
||
|
const uint32_t ki = *it++;
|
||
|
if (!index_value{ki}.valid()) {
|
||
|
throw out_of_range_exception{ki};
|
||
|
}
|
||
|
|
||
|
assert(m_property_iterator != m_properties.end());
|
||
|
const uint32_t vi = *it++;
|
||
|
if (!index_value{vi}.valid()) {
|
||
|
throw out_of_range_exception{vi};
|
||
|
}
|
||
|
|
||
|
if (!std::forward<TFunc>(func)(property{m_layer->key(ki), m_layer->value(vi)})) {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
} // namespace vtzero
|
||
|
|
||
|
#endif // VTZERO_LAYER_HPP
|