1
0
Fork 0
mirror of https://github.com/systemed/tilemaker synced 2025-02-22 06:24:08 +01:00
tilemaker/include/vtzero/feature.hpp
2023-12-28 21:31:01 +00:00

315 lines
11 KiB
C++

#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 <protozero/pbf_message.hpp>
#include <cstddef>
#include <cstdint>
#include <iterator>
#include <utility>
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<protozero::pbf_reader::const_uint32_iterator>;
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<detail::pbf_feature> 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<GeomType>(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 <typename TFunc>
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 <typename TFunc>
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 <typename TMap,
typename TKey = typename TMap::key_type,
typename TValue = typename TMap::mapped_type,
typename TMapping = property_value_mapping>
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<TValue, TMapping>(p.value()));
return true;
});
return map;
}
} // namespace vtzero
#endif // VTZERO_FEATURE_HPP