mirror of
https://github.com/systemed/tilemaker
synced 2025-02-22 06:24:08 +01:00
315 lines
11 KiB
C++
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
|