#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 #include #include #include 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 * * Note that the layer class uses mutable members inside to cache the * key and value tables. It can not be used safely in several threads * at once! */ 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 m_layer_reader{m_data}; mutable std::vector m_key_table; mutable std::vector 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 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 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(reader.tag())) + ", type=" + std::to_string(static_cast(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& 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& 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{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 bool for_each_feature(TFunc&& func) const { vtzero_assert(valid()); protozero::pbf_message layer_reader{m_data}; while (layer_reader.next(detail::pbf_layer::features, protozero::pbf_wire_type::length_delimited)) { if (!std::forward(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 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 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 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(it != m_properties.end()); const uint32_t vi = *it++; if (!index_value{vi}.valid()) { throw out_of_range_exception{vi}; } if (!std::forward(func)(property{m_layer->key(ki), m_layer->value(vi)})) { return false; } } return true; } template bool feature::for_each_property_indexes(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(it != m_properties.end()); const uint32_t vi = *it++; if (!index_value{vi}.valid()) { throw out_of_range_exception{vi}; } if (!std::forward(func)(index_value_pair{ki, vi})) { return false; } } return true; } } // namespace vtzero #endif // VTZERO_LAYER_HPP