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

1365 lines
52 KiB
C++

#ifndef VTZERO_BUILDER_HPP
#define VTZERO_BUILDER_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 builder.hpp
*
* @brief Contains the classes and functions to build vector tiles.
*/
#include "builder_impl.hpp"
#include "feature_builder_impl.hpp"
#include "geometry.hpp"
#include "types.hpp"
#include "vector_tile.hpp"
#include <protozero/basic_pbf_builder.hpp>
#include <cstdlib>
#include <iostream>
#include <memory>
#include <numeric>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
namespace vtzero {
/**
* Used to build vector tiles. Whenever you are building a new vector
* tile, start with an object of this class and add layers. After all
* the data is added, call serialize().
*
* @code
* layer some_existing_layer = ...;
*
* tile_builder builder;
* layer_builder layer_roads{builder, "roads"};
* builder.add_existing_layer(some_existing_layer);
* ...
* std::string data = builder.serialize();
* @endcode
*/
class tile_builder {
friend class layer_builder;
std::vector<std::unique_ptr<detail::layer_builder_impl>> m_layers;
/**
* Add a new layer to the vector tile based on an existing layer. The
* new layer will have the same name, version, and extent as the
* existing layer. The new layer will not contain any features. This
* method is handy when copying some (but not all) data from an
* existing layer.
*/
detail::layer_builder_impl* add_layer(const layer& layer) {
auto* ptr = new detail::layer_builder_impl{layer.name(), layer.version(), layer.extent()};
m_layers.emplace_back(ptr);
return ptr;
}
/**
* Add a new layer to the vector tile with the specified name, version,
* and extent.
*
* @tparam TString Some string type (const char*, std::string,
* vtzero::data_view) or something that converts to one of
* these types.
* @param name Name of this layer.
* @param version Version of this layer (only version 1 and 2 are
* supported)
* @param extent Extent used for this layer.
*/
template <typename TString>
detail::layer_builder_impl* add_layer(TString&& name, uint32_t version, uint32_t extent) {
auto* ptr = new detail::layer_builder_impl{std::forward<TString>(name), version, extent};
m_layers.emplace_back(ptr);
return ptr;
}
public:
/// Constructor
tile_builder() = default;
/// Destructor
~tile_builder() noexcept = default;
/// Tile builders can not be copied.
tile_builder(const tile_builder&) = delete;
/// Tile builders can not be copied.
tile_builder& operator=(const tile_builder&) = delete;
/// Tile builders can be moved.
tile_builder(tile_builder&&) = default;
/// Tile builders can be moved.
tile_builder& operator=(tile_builder&&) = default;
/**
* Add an existing layer to the vector tile. The layer data will be
* copied over into the new vector_tile when the serialize() method
* is called. Until then, the data referenced here must stay available.
*
* @param data Reference to some data that must be a valid encoded
* layer.
*/
void add_existing_layer(data_view&& data) {
m_layers.emplace_back(new detail::layer_builder_impl{std::forward<data_view>(data)});
}
/**
* Add an existing layer to the vector tile. The layer data will be
* copied over into the new vector_tile when the serialize() method
* is called. Until then, the data referenced here must stay available.
*
* @param layer Reference to the layer to be copied.
*/
void add_existing_layer(const layer& layer) {
add_existing_layer(layer.data());
}
/**
* Serialize the data accumulated in this builder into a vector tile.
* The data will be appended to the specified buffer. The buffer
* doesn't have to be empty.
*
* @tparam TBuffer Type of buffer. Must be std:string or other buffer
* type supported by protozero.
* @param buffer Buffer to append the encoded vector tile to.
*/
template <typename TBuffer>
void serialize(TBuffer& buffer) const {
const std::size_t estimated_size = std::accumulate(m_layers.cbegin(), m_layers.cend(), 0ULL, [](std::size_t sum, const std::unique_ptr<detail::layer_builder_impl>& layer) {
return sum + layer->estimated_size();
});
protozero::basic_pbf_builder<TBuffer, detail::pbf_tile> pbf_tile_builder{buffer};
pbf_tile_builder.reserve(estimated_size);
for (const auto& layer : m_layers) {
layer->build(pbf_tile_builder);
}
}
/**
* Serialize the data accumulated in this builder into a vector_tile
* and return it.
*
* If you want to use an existing buffer instead, use the serialize()
* member function taking a TBuffer& as parameter.
*
* @returns std::string Buffer with encoded vector_tile data.
*/
std::string serialize() const {
std::string data;
serialize(data);
return data;
}
}; // class tile_builder
/**
* The layer_builder is used to add a new layer to a vector tile that is
* being built.
*/
class layer_builder {
vtzero::detail::layer_builder_impl* m_layer;
friend class geometry_feature_builder;
friend class point_feature_builder;
friend class linestring_feature_builder;
friend class polygon_feature_builder;
vtzero::detail::layer_builder_impl& get_layer_impl() noexcept {
return *m_layer;
}
template <typename T>
using is_layer = std::is_same<typename std::remove_cv<typename std::remove_reference<T>::type>::type, layer>;
public:
/**
* Construct a layer_builder to build a new layer with the same name,
* version, and extent as an existing layer.
*
* @param tile The tile builder we want to create this layer in.
* @param layer Existing layer we want to use the name, version, and
* extent from
*/
layer_builder(vtzero::tile_builder& tile, const layer& layer) :
m_layer(tile.add_layer(layer)) {
}
/**
* Construct a layer_builder to build a completely new layer.
*
* @tparam TString Some string type (such as std::string or const char*)
* @param tile The tile builder we want to create this layer in.
* @param name The name of the new layer.
* @param version The vector tile spec version of the new layer.
* @param extent The extent of the new layer.
*/
template <typename TString, typename std::enable_if<!is_layer<TString>::value, int>::type = 0>
layer_builder(vtzero::tile_builder& tile, TString&& name, uint32_t version = 2, uint32_t extent = 4096) :
m_layer(tile.add_layer(std::forward<TString>(name), version, extent)) {
}
/**
* Add key to the keys table without checking for duplicates. This
* function is usually used when an external index is used which takes
* care of the duplication check.
*
* @param text The key.
* @returns The index value of this key.
*/
index_value add_key_without_dup_check(const data_view text) {
return m_layer->add_key_without_dup_check(text);
}
/**
* Add key to the keys table. This function will consult the internal
* index in the layer to make sure the key is only in the table once.
* It will either return the index value of an existing key or add the
* new key and return its index value.
*
* @param text The key.
* @returns The index value of this key.
*/
index_value add_key(const data_view text) {
return m_layer->add_key(text);
}
/**
* Add value to the values table without checking for duplicates. This
* function is usually used when an external index is used which takes
* care of the duplication check.
*
* @param value The property value.
* @returns The index value of this value.
*/
index_value add_value_without_dup_check(const property_value value) {
return m_layer->add_value_without_dup_check(value);
}
/**
* Add value to the values table without checking for duplicates. This
* function is usually used when an external index is used which takes
* care of the duplication check.
*
* @param value The property value.
* @returns The index value of this value.
*/
index_value add_value_without_dup_check(const encoded_property_value& value) {
return m_layer->add_value_without_dup_check(value);
}
/**
* Add value to the values table. This function will consult the
* internal index in the layer to make sure the value is only in the
* table once. It will either return the index value of an existing
* value or add the new value and return its index value.
*
* @param value The property value.
* @returns The index value of this value.
*/
index_value add_value(const property_value value) {
return m_layer->add_value(value);
}
/**
* Add value to the values table. This function will consult the
* internal index in the layer to make sure the value is only in the
* table once. It will either return the index value of an existing
* value or add the new value and return its index value.
*
* @param value The property value.
* @returns The index value of this value.
*/
index_value add_value(const encoded_property_value& value) {
return m_layer->add_value(value);
}
/**
* Add a feature from an existing layer to the new layer. The feature
* will be copied completely over to the new layer including its
* geometry and all its properties.
*/
void add_feature(const feature& feature);
}; // class layer_builder
/**
* Parent class for the point_feature_builder, linestring_feature_builder
* and polygon_feature_builder classes. You can not instantiate this class
* directly, use it through its derived classes.
*/
class feature_builder : public detail::feature_builder_base {
class countdown_value {
uint32_t m_value = 0;
public:
countdown_value() noexcept = default;
~countdown_value() noexcept {
assert_is_zero();
}
countdown_value(const countdown_value&) = delete;
countdown_value& operator=(const countdown_value&) = delete;
countdown_value(countdown_value&& other) noexcept :
m_value(other.m_value) {
other.m_value = 0;
}
countdown_value& operator=(countdown_value&& other) noexcept {
m_value = other.m_value;
other.m_value = 0;
return *this;
}
uint32_t value() const noexcept {
return m_value;
}
void set(const uint32_t value) noexcept {
m_value = value;
}
void decrement() {
vtzero_assert(m_value > 0 && "too many calls to set_point()");
--m_value;
}
void assert_is_zero() const noexcept {
vtzero_assert_in_noexcept_function(m_value == 0 &&
"not enough calls to set_point()");
}
}; // countdown_value
protected:
/// Encoded geometry.
protozero::packed_field_uint32 m_pbf_geometry{};
/// Number of points still to be set for the geometry to be complete.
countdown_value m_num_points;
/// Last point (used to calculate delta between coordinates)
point m_cursor{0, 0};
/// Constructor.
explicit feature_builder(detail::layer_builder_impl* layer) :
feature_builder_base(layer) {
}
/// Helper function to check size isn't too large
template <typename T>
uint32_t check_num_points(T size) {
if (size >= (1UL << 29U)) {
throw geometry_exception{"Maximum of 2^29 - 1 points allowed in geometry"};
}
return static_cast<uint32_t>(size);
}
/// Helper function to make sure we have everything before adding a property
void prepare_to_add_property() {
if (m_pbf_geometry.valid()) {
m_num_points.assert_is_zero();
m_pbf_geometry.commit();
}
if (!m_pbf_tags.valid()) {
m_pbf_tags = {m_feature_writer, detail::pbf_feature::tags};
}
}
public:
/**
* If the feature was not committed, the destructor will roll back all
* the changes.
*/
~feature_builder() {
try {
rollback();
} catch (...) {
// ignore exceptions
}
}
/// Builder classes can not be copied
feature_builder(const feature_builder&) = delete;
/// Builder classes can not be copied
feature_builder& operator=(const feature_builder&) = delete;
/// Builder classes can be moved
feature_builder(feature_builder&& other) noexcept = default;
/// Builder classes can be moved
feature_builder& operator=(feature_builder&& other) noexcept = default;
/**
* Set the ID of this feature.
*
* You can only call this method once and it must be before calling
* any method manipulating the geometry.
*
* @param id The ID.
*/
void set_id(uint64_t id) {
vtzero_assert(m_feature_writer.valid() &&
"Can not call set_id() after commit() or rollback()");
vtzero_assert(!m_pbf_geometry.valid() &&
!m_pbf_tags.valid() &&
"Call set_id() before setting the geometry or adding properties");
set_id_impl(id);
}
/**
* Copy the ID of an existing feature to this feature. If the
* feature doesn't have an ID, no ID is set.
*
* You can only call this method once and it must be before calling
* any method manipulating the geometry.
*
* @param feature The feature to copy the ID from.
*/
void copy_id(const feature& feature) {
vtzero_assert(m_feature_writer.valid() &&
"Can not call copy_id() after commit() or rollback()");
vtzero_assert(!m_pbf_geometry.valid() &&
!m_pbf_tags.valid() &&
"Call copy_id() before setting the geometry or adding properties");
if (feature.has_id()) {
set_id_impl(feature.id());
}
}
/**
* Add a property to this feature. Can only be called after all the
* methods manipulating the geometry.
*
* @tparam TProp Can be type index_value_pair or property.
* @param prop The property to add.
*/
template <typename TProp>
void add_property(TProp&& prop) {
vtzero_assert(m_feature_writer.valid() &&
"Can not call add_property() after commit() or rollback()");
prepare_to_add_property();
add_property_impl(std::forward<TProp>(prop));
}
/**
* Copy all properties of an existing feature to the one being built.
*
* @param feature The feature to copy the properties from.
*/
void copy_properties(const feature& feature) {
vtzero_assert(m_feature_writer.valid() &&
"Can not call copy_properties() after commit() or rollback()");
prepare_to_add_property();
feature.for_each_property([this](const property& prop) {
add_property_impl(prop);
return true;
});
}
/**
* Copy all properties of an existing feature to the one being built
* using a property_mapper.
*
* @tparam TMapper Must be the property_mapper class or something
* equivalent.
* @param feature The feature to copy the properties from.
* @param mapper Instance of the property_mapper class.
*/
template <typename TMapper>
void copy_properties(const feature& feature, TMapper& mapper) {
vtzero_assert(m_feature_writer.valid() &&
"Can not call copy_properties() after commit() or rollback()");
prepare_to_add_property();
feature.for_each_property_indexes([this, &mapper](const index_value_pair& idxs) {
add_property_impl(mapper(idxs));
return true;
});
}
/**
* Add a property to this feature. Can only be called after all the
* methods manipulating the geometry.
*
* @tparam TKey Can be type index_value or data_view or anything that
* converts to it.
* @tparam TValue Can be type index_value or property_value or
* encoded_property or anything that converts to it.
* @param key The key.
* @param value The value.
*/
template <typename TKey, typename TValue>
void add_property(TKey&& key, TValue&& value) {
vtzero_assert(m_feature_writer.valid() &&
"Can not call add_property() after commit() or rollback()");
prepare_to_add_property();
add_property_impl(std::forward<TKey>(key), std::forward<TValue>(value));
}
/**
* Commit this feature. Call this after all the details of this
* feature have been added. If this is not called, the feature
* will be rolled back when the destructor of the feature_builder is
* called.
*
* Once a feature has been committed or rolled back, further calls
* to commit() or rollback() don't do anything.
*/
void commit() {
if (m_feature_writer.valid()) {
vtzero_assert((m_pbf_geometry.valid() || m_pbf_tags.valid()) &&
"Can not call commit before geometry was added");
if (m_pbf_geometry.valid()) {
m_pbf_geometry.commit();
}
do_commit();
}
}
/**
* Rollback this feature. Removed all traces of this feature from
* the layer_builder. Useful when you started creating a feature
* but then find out that its geometry is invalid or something like
* it. This will also happen automatically when the feature_builder
* is destructed and commit() hasn't been called on it.
*
* Once a feature has been committed or rolled back, further calls
* to commit() or rollback() don't do anything.
*/
void rollback() {
if (m_feature_writer.valid()) {
if (m_pbf_geometry.valid()) {
m_pbf_geometry.rollback();
}
do_rollback();
}
}
}; // class feature_builder
/**
* Used for adding a feature with a point geometry to a layer. After
* creating an object of this class you can add data to the feature in a
* specific order:
*
* * Optionally add the ID using set_id().
* * Add the (multi)point geometry using add_point(), add_points() and
* set_point(), or add_points_from_container().
* * Optionally add any number of properties using add_property().
*
* @code
* vtzero::tile_builder tb;
* vtzero::layer_builder lb{tb};
* vtzero::point_feature_builder fb{lb};
* fb.set_id(123); // optionally set ID
* fb.add_point(10, 20) // add point geometry
* fb.add_property("foo", "bar"); // add property
* @endcode
*/
class point_feature_builder : public feature_builder {
public:
/**
* Constructor
*
* @param layer The layer we want to create this feature in.
*/
explicit point_feature_builder(layer_builder layer) :
feature_builder(&layer.get_layer_impl()) {
m_feature_writer.add_enum(detail::pbf_feature::type, static_cast<int32_t>(GeomType::POINT));
}
/**
* Add a single point as the geometry to this feature.
*
* @param p The point to add.
*
* @pre You must not have any calls to add_property() before calling
* this method.
*/
void add_point(const point p) {
vtzero_assert(m_feature_writer.valid() &&
"Can not add geometry after commit() or rollback()");
vtzero_assert(!m_pbf_geometry.valid() &&
!m_pbf_tags.valid() &&
"add_point() can only be called once");
m_pbf_geometry = {m_feature_writer, detail::pbf_feature::geometry};
m_pbf_geometry.add_element(detail::command_move_to(1));
m_pbf_geometry.add_element(protozero::encode_zigzag32(p.x));
m_pbf_geometry.add_element(protozero::encode_zigzag32(p.y));
}
/**
* Add a single point as the geometry to this feature.
*
* @param x X coordinate of the point to add.
* @param y Y coordinate of the point to add.
*
* @pre You must not have any calls to add_property() before calling
* this method.
*/
void add_point(const int32_t x, const int32_t y) {
add_point(point{x, y});
}
/**
* Add a single point as the geometry to this feature.
*
* @tparam TPoint A type that can be converted to vtzero::point using
* the create_vtzero_point function.
* @param p The point to add.
*
* @pre You must not have any calls to add_property() before calling
* this method.
*/
template <typename TPoint>
void add_point(TPoint&& p) {
add_point(create_vtzero_point(std::forward<TPoint>(p)));
}
/**
* Declare the intent to add a multipoint geometry with *count* points
* to this feature.
*
* @param count The number of points in the multipoint geometry.
*
* @pre @code count > 0 && count < 2^29 @endcode
*
* @pre You must not have any calls to add_property() before calling
* this method.
*/
void add_points(uint32_t count) {
vtzero_assert(m_feature_writer.valid() &&
"Can not add geometry after commit() or rollback()");
vtzero_assert(!m_pbf_geometry.valid() &&
"can not call add_points() twice or mix with add_point()");
vtzero_assert(!m_pbf_tags.valid() &&
"add_points() has to be called before properties are added");
vtzero_assert(count > 0 && count < (1UL << 29U) && "add_points() must be called with 0 < count < 2^29");
m_num_points.set(count);
m_pbf_geometry = {m_feature_writer, detail::pbf_feature::geometry};
m_pbf_geometry.add_element(detail::command_move_to(count));
}
/**
* Set a point in the multipoint geometry.
*
* @param p The point.
*
* @pre There must have been less than *count* calls to set_point()
* already after a call to add_points(count).
*
* @pre You must not have any calls to add_property() before calling
* this method.
*/
void set_point(const point p) {
vtzero_assert(m_feature_writer.valid() &&
"Can not add geometry after commit() or rollback()");
vtzero_assert(m_pbf_geometry.valid() &&
"call add_points() before set_point()");
vtzero_assert(!m_pbf_tags.valid() &&
"set_point() has to be called before properties are added");
m_num_points.decrement();
m_pbf_geometry.add_element(protozero::encode_zigzag32(p.x - m_cursor.x));
m_pbf_geometry.add_element(protozero::encode_zigzag32(p.y - m_cursor.y));
m_cursor = p;
}
/**
* Set a point in the multipoint geometry.
*
* @param x X coordinate of the point to set.
* @param y Y coordinate of the point to set.
*
* @pre There must have been less than *count* calls to set_point()
* already after a call to add_points(count).
*
* @pre You must not have any calls to add_property() before calling
* this method.
*/
void set_point(const int32_t x, const int32_t y) {
set_point(point{x, y});
}
/**
* Set a point in the multipoint geometry.
*
* @tparam TPoint A type that can be converted to vtzero::point using
* the create_vtzero_point function.
* @param p The point to add.
*
* @pre There must have been less than *count* calls to set_point()
* already after a call to add_points(count).
*
* @pre You must not have any calls to add_property() before calling
* this method.
*/
template <typename TPoint>
void set_point(TPoint&& p) {
set_point(create_vtzero_point(std::forward<TPoint>(p)));
}
/**
* Add the points from the specified container as multipoint geometry
* to this feature.
*
* @tparam TContainer The container type. Must support the size()
* method, be iterable using a range for loop, and contain
* objects of type vtzero::point or something convertible to
* it.
* @param container The container to read the points from.
*
* @throws geometry_exception If there are more than 2^32-1 members in
* the container.
*
* @pre You must not have any calls to add_property() before calling
* this method.
*/
template <typename TContainer>
void add_points_from_container(const TContainer& container) {
add_points(check_num_points(container.size()));
for (const auto& element : container) {
set_point(element);
}
}
}; // class point_feature_builder
/**
* Used for adding a feature with a (multi)linestring geometry to a layer.
* After creating an object of this class you can add data to the
* feature in a specific order:
*
* * Optionally add the ID using set_id().
* * Add the (multi)linestring geometry using add_linestring() or
* add_linestring_from_container().
* * Optionally add any number of properties using add_property().
*
* @code
* vtzero::tile_builder tb;
* vtzero::layer_builder lb{tb};
* vtzero::linestring_feature_builder fb{lb};
* fb.set_id(123); // optionally set ID
* fb.add_linestring(2);
* fb.set_point(10, 10);
* fb.set_point(10, 20);
* fb.add_property("foo", "bar"); // add property
* @endcode
*/
class linestring_feature_builder : public feature_builder {
bool m_start_line = false;
public:
/**
* Constructor
*
* @param layer The layer we want to create this feature in.
*/
explicit linestring_feature_builder(layer_builder layer) :
feature_builder(&layer.get_layer_impl()) {
m_feature_writer.add_enum(detail::pbf_feature::type, static_cast<int32_t>(GeomType::LINESTRING));
}
/**
* Declare the intent to add a linestring geometry with *count* points
* to this feature.
*
* @param count The number of points in the linestring.
*
* @pre @code count > 1 && count < 2^29 @endcode
*
* @pre You must not have any calls to add_property() before calling
* this method.
*/
void add_linestring(const uint32_t count) {
vtzero_assert(m_feature_writer.valid() &&
"Can not add geometry after commit() or rollback()");
vtzero_assert(!m_pbf_tags.valid() &&
"add_linestring() has to be called before properties are added");
vtzero_assert(count > 1 && count < (1UL << 29U) && "add_linestring() must be called with 1 < count < 2^29");
m_num_points.assert_is_zero();
if (!m_pbf_geometry.valid()) {
m_pbf_geometry = {m_feature_writer, detail::pbf_feature::geometry};
}
m_num_points.set(count);
m_start_line = true;
}
/**
* Set a point in the multilinestring geometry opened with
* add_linestring().
*
* @param p The point.
*
* @throws geometry_exception if the point set is the same as the
* previous point. This would create zero-length segments
* which are not allowed according to the vector tile spec.
*
* @pre There must have been less than *count* calls to set_point()
* already after a call to add_linestring(count).
*
* @pre You must not have any calls to add_property() before calling
* this method.
*/
void set_point(const point p) {
vtzero_assert(m_feature_writer.valid() &&
"Can not add geometry after commit() or rollback()");
vtzero_assert(m_pbf_geometry.valid() &&
"call add_linestring() before set_point()");
vtzero_assert(!m_pbf_tags.valid() &&
"set_point() has to be called before properties are added");
m_num_points.decrement();
if (m_start_line) {
m_pbf_geometry.add_element(detail::command_move_to(1));
m_pbf_geometry.add_element(protozero::encode_zigzag32(p.x - m_cursor.x));
m_pbf_geometry.add_element(protozero::encode_zigzag32(p.y - m_cursor.y));
m_pbf_geometry.add_element(detail::command_line_to(m_num_points.value()));
m_start_line = false;
} else {
if (p == m_cursor) {
throw geometry_exception{"Zero-length segments in linestrings are not allowed."};
}
m_pbf_geometry.add_element(protozero::encode_zigzag32(p.x - m_cursor.x));
m_pbf_geometry.add_element(protozero::encode_zigzag32(p.y - m_cursor.y));
}
m_cursor = p;
}
/**
* Set a point in the multilinestring geometry opened with
* add_linestring().
*
* @param x X coordinate of the point to set.
* @param y Y coordinate of the point to set.
*
* @throws geometry_exception if the point set is the same as the
* previous point. This would create zero-length segments
* which are not allowed according to the vector tile spec.
*
* @pre There must have been less than *count* calls to set_point()
* already after a call to add_linestring(count).
*
* @pre You must not have any calls to add_property() before calling
* this method.
*/
void set_point(const int32_t x, const int32_t y) {
set_point(point{x, y});
}
/**
* Set a point in the multilinestring geometry opened with
* add_linestring().
*
* @tparam TPoint A type that can be converted to vtzero::point using
* the create_vtzero_point function.
* @param p The point to add.
*
* @throws geometry_exception if the point set is the same as the
* previous point. This would create zero-length segments
* which are not allowed according to the vector tile spec.
*
* @pre There must have been less than *count* calls to set_point()
* already after a call to add_linestring(count).
*
* @pre You must not have any calls to add_property() before calling
* this method.
*/
template <typename TPoint>
void set_point(TPoint&& p) {
set_point(create_vtzero_point(std::forward<TPoint>(p)));
}
/**
* Add the points from the specified container as a linestring geometry
* to this feature.
*
* @tparam TContainer The container type. Must support the size()
* method, be iterable using a range for loop, and contain
* objects of type vtzero::point or something convertible to
* it.
* @param container The container to read the points from.
*
* @throws geometry_exception If there are more than 2^32-1 members in
* the container or if two consecutive points in the container
* are identical.
*
* @pre You must not have any calls to add_property() before calling
* this method.
*/
template <typename TContainer>
void add_linestring_from_container(const TContainer& container) {
add_linestring(check_num_points(container.size()));
for (const auto& element : container) {
set_point(element);
}
}
}; // class linestring_feature_builder
/**
* Used for adding a feature with a (multi)polygon geometry to a layer.
* After creating an object of this class you can add data to the
* feature in a specific order:
*
* * Optionally add the ID using set_id().
* * Add the (multi)polygon geometry using add_ring() or
* add_ring_from_container().
* * Optionally add any number of properties using add_property().
*
* @code
* vtzero::tile_builder tb;
* vtzero::layer_builder lb{tb};
* vtzero::polygon_feature_builder fb{lb};
* fb.set_id(123); // optionally set ID
* fb.add_ring(5);
* fb.set_point(10, 10);
* ...
* fb.add_property("foo", "bar"); // add property
* @endcode
*/
class polygon_feature_builder : public feature_builder {
point m_first_point{0, 0};
bool m_start_ring = false;
public:
/**
* Constructor
*
* @param layer The layer we want to create this feature in.
*/
explicit polygon_feature_builder(layer_builder layer) :
feature_builder(&layer.get_layer_impl()) {
m_feature_writer.add_enum(detail::pbf_feature::type, static_cast<int32_t>(GeomType::POLYGON));
}
/**
* Declare the intent to add a ring with *count* points to this
* feature.
*
* @param count The number of points in the ring.
*
* @pre @code count > 3 && count < 2^29 @endcode
*
* @pre You must not have any calls to add_property() before calling
* this method.
*/
void add_ring(const uint32_t count) {
vtzero_assert(m_feature_writer.valid() &&
"Can not add geometry after commit() or rollback()");
vtzero_assert(!m_pbf_tags.valid() &&
"add_ring() has to be called before properties are added");
vtzero_assert(count > 3 && count < (1UL << 29U) && "add_ring() must be called with 3 < count < 2^29");
m_num_points.assert_is_zero();
if (!m_pbf_geometry.valid()) {
m_pbf_geometry = {m_feature_writer, detail::pbf_feature::geometry};
}
m_num_points.set(count);
m_start_ring = true;
}
/**
* Set a point in the ring opened with add_ring().
*
* @param p The point.
*
* @throws geometry_exception if the point set is the same as the
* previous point. This would create zero-length segments
* which are not allowed according to the vector tile spec.
* This exception is also thrown when the last point in the
* ring is not equal to the first point, because this would
* not create a closed ring.
*
* @pre There must have been less than *count* calls to set_point()
* already after a call to add_ring(count).
*
* @pre You must not have any calls to add_property() before calling
* this method.
*/
void set_point(const point p) {
vtzero_assert(m_feature_writer.valid() &&
"Can not add geometry after commit() or rollback()");
vtzero_assert(m_pbf_geometry.valid() &&
"call add_ring() before set_point()");
vtzero_assert(!m_pbf_tags.valid() &&
"set_point() has to be called before properties are added");
m_num_points.decrement();
if (m_start_ring) {
m_first_point = p;
m_pbf_geometry.add_element(detail::command_move_to(1));
m_pbf_geometry.add_element(protozero::encode_zigzag32(p.x - m_cursor.x));
m_pbf_geometry.add_element(protozero::encode_zigzag32(p.y - m_cursor.y));
m_pbf_geometry.add_element(detail::command_line_to(m_num_points.value() - 1));
m_start_ring = false;
m_cursor = p;
} else if (m_num_points.value() == 0) {
if (p != m_first_point) {
throw geometry_exception{"Last point in a ring must be the same as the first point."};
}
// spec 4.3.3.3 "A ClosePath command MUST have a command count of 1"
m_pbf_geometry.add_element(detail::command_close_path());
} else {
if (p == m_cursor) {
throw geometry_exception{"Zero-length segments in rings are not allowed."};
}
m_pbf_geometry.add_element(protozero::encode_zigzag32(p.x - m_cursor.x));
m_pbf_geometry.add_element(protozero::encode_zigzag32(p.y - m_cursor.y));
m_cursor = p;
}
}
/**
* Set a point in the ring opened with add_ring().
*
* @param x X coordinate of the point to set.
* @param y Y coordinate of the point to set.
*
* @throws geometry_exception if the point set is the same as the
* previous point. This would create zero-length segments
* which are not allowed according to the vector tile spec.
* This exception is also thrown when the last point in the
* ring is not equal to the first point, because this would
* not create a closed ring.
*
* @pre There must have been less than *count* calls to set_point()
* already after a call to add_ring(count).
*
* @pre You must not have any calls to add_property() before calling
* this method.
*/
void set_point(const int32_t x, const int32_t y) {
set_point(point{x, y});
}
/**
* Set a point in the ring opened with add_ring().
*
* @tparam TPoint A type that can be converted to vtzero::point using
* the create_vtzero_point function.
* @param p The point to add.
*
* @throws geometry_exception if the point set is the same as the
* previous point. This would create zero-length segments
* which are not allowed according to the vector tile spec.
* This exception is also thrown when the last point in the
* ring is not equal to the first point, because this would
* not create a closed ring.
*
* @pre There must have been less than *count* calls to set_point()
* already after a call to add_ring(count).
*
* @pre You must not have any calls to add_property() before calling
* this method.
*/
template <typename TPoint>
void set_point(TPoint&& p) {
set_point(create_vtzero_point(std::forward<TPoint>(p)));
}
/**
* Close a ring opened with add_ring(). This can be called for the
* last point (which will be equal to the first point) in the ring
* instead of calling set_point().
*
* @pre There must have been *count* - 1 calls to set_point()
* already after a call to add_ring(count).
*
* @pre You must not have any calls to add_property() before calling
* this method.
*/
void close_ring() {
vtzero_assert(m_feature_writer.valid() &&
"Can not add geometry after commit() or rollback()");
vtzero_assert(m_pbf_geometry.valid() &&
"Call add_ring() before you can call close_ring()");
vtzero_assert(!m_pbf_tags.valid() &&
"close_ring() has to be called before properties are added");
vtzero_assert(m_num_points.value() == 1 &&
"wrong number of points in ring");
m_pbf_geometry.add_element(detail::command_close_path());
m_num_points.decrement();
}
/**
* Add the points from the specified container as a ring to this
* feature.
*
* @tparam TContainer The container type. Must support the size()
* method, be iterable using a range for loop, and contain
* objects of type vtzero::point or something convertible to
* it.
* @param container The container to read the points from.
*
* @throws geometry_exception If there are more than 2^32-1 members in
* the container or if two consecutive points in the container
* are identical or if the last point is not the same as the
* first point.
*
* @pre You must not have any calls to add_property() before calling
* this method.
*/
template <typename TContainer>
void add_ring_from_container(const TContainer& container) {
add_ring(check_num_points(container.size()));
for (const auto& element : container) {
set_point(element);
}
}
}; // class polygon_feature_builder
/**
* Used for adding a feature to a layer using an existing geometry. After
* creating an object of this class you can add data to the feature in a
* specific order:
*
* * Optionally add the ID using set_id().
* * Add the geometry using set_geometry().
* * Optionally add any number of properties using add_property().
*
* @code
* auto geom = ... // get geometry from a feature you are reading
* ...
* vtzero::tile_builder tb;
* vtzero::layer_builder lb{tb};
* vtzero::geometry_feature_builder fb{lb};
* fb.set_id(123); // optionally set ID
* fb.set_geometry(geom) // add geometry
* fb.add_property("foo", "bar"); // add property
* @endcode
*/
class geometry_feature_builder : public detail::feature_builder_base {
public:
/**
* Constructor
*
* @param layer The layer we want to create this feature in.
*/
explicit geometry_feature_builder(layer_builder layer) :
feature_builder_base(&layer.get_layer_impl()) {
}
/**
* If the feature was not committed, the destructor will roll back all
* the changes.
*/
~geometry_feature_builder() noexcept {
try {
rollback();
} catch (...) {
// ignore exceptions
}
}
/// Feature builders can not be copied.
geometry_feature_builder(const geometry_feature_builder&) = delete;
/// Feature builders can not be copied.
geometry_feature_builder& operator=(const geometry_feature_builder&) = delete;
/// Feature builders can be moved.
geometry_feature_builder(geometry_feature_builder&&) noexcept = default;
/// Feature builders can be moved.
geometry_feature_builder& operator=(geometry_feature_builder&&) noexcept = default;
/**
* Set the ID of this feature.
*
* You can only call this function once and it must be before calling
* set_geometry().
*
* @param id The ID.
*/
void set_id(uint64_t id) {
vtzero_assert(m_feature_writer.valid() &&
"Can not call set_id() after commit() or rollback()");
vtzero_assert(!m_pbf_tags.valid());
set_id_impl(id);
}
/**
* Copy the ID of an existing feature to this feature. If the
* feature doesn't have an ID, no ID is set.
*
* You can only call this function once and it must be before calling
* set_geometry().
*
* @param feature The feature to copy the ID from.
*/
void copy_id(const feature& feature) {
vtzero_assert(m_feature_writer.valid() &&
"Can not call copy_id() after commit() or rollback()");
vtzero_assert(!m_pbf_tags.valid());
if (feature.has_id()) {
set_id_impl(feature.id());
}
}
/**
* Set the geometry of this feature.
*
* You can only call this method once and it must be before calling the
* add_property() method.
*
* @param geometry The geometry.
*/
void set_geometry(const geometry& geometry) {
vtzero_assert(m_feature_writer.valid() &&
"Can not add geometry after commit() or rollback()");
vtzero_assert(!m_pbf_tags.valid());
m_feature_writer.add_enum(detail::pbf_feature::type, static_cast<int32_t>(geometry.type()));
m_feature_writer.add_string(detail::pbf_feature::geometry, geometry.data());
m_pbf_tags = {m_feature_writer, detail::pbf_feature::tags};
}
/**
* Add a property to this feature. Can only be called after the
* set_geometry method.
*
* @tparam TProp Can be type index_value_pair or property.
* @param prop The property to add.
*/
template <typename TProp>
void add_property(TProp&& prop) {
vtzero_assert(m_feature_writer.valid() &&
"Can not call add_property() after commit() or rollback()");
add_property_impl(std::forward<TProp>(prop));
}
/**
* Add a property to this feature. Can only be called after the
* set_geometry method.
*
* @tparam TKey Can be type index_value or data_view or anything that
* converts to it.
* @tparam TValue Can be type index_value or property_value or
* encoded_property or anything that converts to it.
* @param key The key.
* @param value The value.
*/
template <typename TKey, typename TValue>
void add_property(TKey&& key, TValue&& value) {
vtzero_assert(m_feature_writer.valid() &&
"Can not call add_property() after commit() or rollback()");
add_property_impl(std::forward<TKey>(key), std::forward<TValue>(value));
}
/**
* Copy all properties of an existing feature to the one being built.
*
* @param feature The feature to copy the properties from.
*/
void copy_properties(const feature& feature) {
vtzero_assert(m_feature_writer.valid() &&
"Can not call copy_properties() after commit() or rollback()");
feature.for_each_property([this](const property& prop) {
add_property_impl(prop);
return true;
});
}
/**
* Copy all properties of an existing feature to the one being built
* using a property_mapper.
*
* @tparam TMapper Must be the property_mapper class or something
* equivalent.
* @param feature The feature to copy the properties from.
* @param mapper Instance of the property_mapper class.
*/
template <typename TMapper>
void copy_properties(const feature& feature, TMapper& mapper) {
vtzero_assert(m_feature_writer.valid() &&
"Can not call copy_properties() after commit() or rollback()");
feature.for_each_property_indexes([this, &mapper](const index_value_pair& idxs) {
add_property_impl(mapper(idxs));
return true;
});
}
/**
* Commit this feature. Call this after all the details of this
* feature have been added. If this is not called, the feature
* will be rolled back when the destructor of the feature_builder is
* called.
*
* Once a feature has been committed or rolled back, further calls
* to commit() or rollback() don't do anything.
*/
void commit() {
if (m_feature_writer.valid()) {
vtzero_assert(m_pbf_tags.valid() &&
"Can not call commit before geometry was added");
do_commit();
}
}
/**
* Rollback this feature. Removed all traces of this feature from
* the layer_builder. Useful when you started creating a feature
* but then find out that its geometry is invalid or something like
* it. This will also happen automatically when the feature_builder
* is destructed and commit() hasn't been called on it.
*
* Once a feature has been committed or rolled back, further calls
* to commit() or rollback() don't do anything.
*/
void rollback() {
if (m_feature_writer.valid()) {
do_rollback();
}
}
}; // class geometry_feature_builder
inline void layer_builder::add_feature(const feature& feature) {
geometry_feature_builder feature_builder{*this};
if (feature.has_id()) {
feature_builder.set_id(feature.id());
}
feature_builder.set_geometry(feature.geometry());
feature.for_each_property([&feature_builder](const property& p) {
feature_builder.add_property(p);
return true;
});
feature_builder.commit();
}
} // namespace vtzero
#endif // VTZERO_BUILDER_HPP