#ifndef VTZERO_FEATURE_HPP #define VTZERO_FEATURE_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 feature.hpp * * @brief Contains the feature class. */ #include "exception.hpp" #include "property.hpp" #include "property_value.hpp" #include "types.hpp" #include #include #include #include #include namespace vtzero { class layer; /** * A feature according to spec 4.2. * * Note that a feature will internally contain a pointer to the layer it * came from. The layer has to stay valid as long as the feature is used. */ class feature { using uint32_it_range = protozero::iterator_range; const layer* m_layer = nullptr; uint64_t m_id = 0; // defaults to 0, see https://github.com/mapbox/vector-tile-spec/blob/master/2.1/vector_tile.proto#L32 uint32_it_range m_properties{}; protozero::pbf_reader::const_uint32_iterator m_property_iterator{}; std::size_t m_num_properties = 0; data_view m_geometry{}; GeomType m_geometry_type = GeomType::UNKNOWN; // defaults to UNKNOWN, see https://github.com/mapbox/vector-tile-spec/blob/master/2.1/vector_tile.proto#L41 bool m_has_id = false; public: /** * Construct an invalid feature object. */ feature() = default; /** * Construct a feature object. * * @throws format_exception if the layer data is ill-formed. */ feature(const layer* layer, const data_view data) : m_layer(layer) { vtzero_assert(layer); vtzero_assert(data.data()); protozero::pbf_message reader{data}; while (reader.next()) { switch (reader.tag_and_type()) { case protozero::tag_and_type(detail::pbf_feature::id, protozero::pbf_wire_type::varint): m_id = reader.get_uint64(); m_has_id = true; break; case protozero::tag_and_type(detail::pbf_feature::tags, protozero::pbf_wire_type::length_delimited): if (m_properties.begin() != protozero::pbf_reader::const_uint32_iterator{}) { throw format_exception{"Feature has more than one tags field"}; } m_properties = reader.get_packed_uint32(); m_property_iterator = m_properties.begin(); break; case protozero::tag_and_type(detail::pbf_feature::type, protozero::pbf_wire_type::varint): { const auto type = reader.get_enum(); // spec 4.3.4 "Geometry Types" if (type < 0 || type > 3) { throw format_exception{"Unknown geometry type (spec 4.3.4)"}; } m_geometry_type = static_cast(type); } break; case protozero::tag_and_type(detail::pbf_feature::geometry, protozero::pbf_wire_type::length_delimited): if (!m_geometry.empty()) { throw format_exception{"Feature has more than one geometry field"}; } m_geometry = reader.get_view(); break; default: reader.skip(); // ignore unknown fields } } // spec 4.2 "A feature MUST contain a geometry field." if (m_geometry.empty()) { throw format_exception{"Missing geometry field in feature (spec 4.2)"}; } const auto size = m_properties.size(); if (size % 2 != 0) { throw format_exception{"unpaired property key/value indexes (spec 4.4)"}; } m_num_properties = size / 2; } /** * Is this a valid feature? Valid features are those not created from * the default constructor. * * Complexity: Constant. */ bool valid() const noexcept { return m_geometry.data() != nullptr; } /** * Is this a valid feature? Valid features are those not created from * the default constructor. * * Complexity: Constant. */ explicit operator bool() const noexcept { return valid(); } /** * The ID of this feature. According to the spec IDs should be unique * in a layer if they are set (spec 4.2). * * Complexity: Constant. * * Always returns 0 for invalid features. */ uint64_t id() const noexcept { return m_id; } /** * Does this feature have an ID? * * Complexity: Constant. * * Always returns false for invalid features. */ bool has_id() const noexcept { return m_has_id; } /** * The geometry type of this feature. * * Complexity: Constant. * * Always returns GeomType::UNKNOWN for invalid features. */ GeomType geometry_type() const noexcept { return m_geometry_type; } /** * Get the geometry of this feature. * * Complexity: Constant. * * @pre @code valid() @endcode */ vtzero::geometry geometry() const noexcept { vtzero_assert_in_noexcept_function(valid()); return {m_geometry, m_geometry_type}; } /** * Returns true if this feature doesn't have any properties. * * Complexity: Constant. * * Always returns true for invalid features. */ bool empty() const noexcept { return m_num_properties == 0; } /** * Returns the number of properties in this feature. * * Complexity: Constant. * * Always returns 0 for invalid features. */ std::size_t num_properties() const noexcept { return m_num_properties; } /** * Get the next property in this feature. * * Complexity: Constant. * * @returns The next property or the invalid property if there are no * more properties. * @throws format_exception if the feature data is ill-formed. * @throws any protozero exception if the protobuf encoding is invalid. * @pre @code valid() @endcode */ property next_property(); /** * Get the indexes into the key/value table for the next property in * this feature. * * Complexity: Constant. * * @returns The next index_value_pair or an invalid index_value_pair * if there are no more properties. * @throws format_exception if the feature data is ill-formed. * @throws out_of_range_exception if the key or value index is not * within the range of indexes in the layer key/value table. * @throws any protozero exception if the protobuf encoding is invalid. * @pre @code valid() @endcode */ index_value_pair next_property_indexes(); /** * Reset the property iterator. The next time next_property() or * next_property_indexes() is called, it will begin from the first * property again. * * Complexity: Constant. * * @pre @code valid() @endcode */ void reset_property() noexcept { vtzero_assert_in_noexcept_function(valid()); m_property_iterator = m_properties.begin(); } /** * Call a function for each property of this feature. * * @tparam TFunc The type of the function. It must take a single * argument of type property&& 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_property(TFunc&& func) const; /** * Call a function for each key/value index of this feature. * * @tparam TFunc The type of the function. It must take a single * argument of type index_value_pair&& 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_property_indexes(TFunc&& func) const; }; // class feature /** * Create some kind of mapping from property keys to property values. * * This can be used to read all properties into a std::map or similar * object. * * @tparam TMap Map type (std::map, std::unordered_map, ...) Must support * the emplace() method. * @tparam TKey Key type, usually the key of the map type. The data_view * of the property key is converted to this type before * adding it to the map. * @tparam TValue Value type, usally the value of the map type. The * property_value is converted to this type before * adding it to the map. * @tparam TMapping A struct derived from property_value_mapping with the * mapping for vtzero property value types to TValue-constructing * types. (See convert_property_value() for details.) * @param feature The feature to get the properties from. * @returns An object of type TMap with all the properties. * @pre @code feature.valid() @endcode */ template TMap create_properties_map(const vtzero::feature& feature) { TMap map; feature.for_each_property([&map](const property& p) { map.emplace(TKey(p.key()), convert_property_value(p.value())); return true; }); return map; } } // namespace vtzero #endif // VTZERO_FEATURE_HPP