1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290
|
#ifndef VTZERO_VECTOR_TILE_HPP
#define VTZERO_VECTOR_TILE_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 vector_tile.hpp
*
* @brief Contains the vector_tile class.
*/
#include "exception.hpp"
#include "layer.hpp"
#include "types.hpp"
#include <protozero/pbf_message.hpp>
#include <cstddef>
#include <cstring>
#include <iterator>
#include <string>
namespace vtzero {
/**
* A vector tile is basically nothing more than an ordered collection
* of named layers. For the most efficient way to access the layers,
* call next_layer() until it returns an invalid layer:
*
* @code
* std::string data = ...;
* vector_tile tile{data};
* while (auto layer = tile.next_layer()) {
* ...
* }
* @endcode
*
* If you know the index of the layer, you can get it directly with
* @code
* tile.get_layer(4);
* @endcode
*
* You can also access the layer by name:
* @code
* tile.get_layer_by_name("foobar");
* @endcode
*/
class vector_tile {
data_view m_data;
protozero::pbf_message<detail::pbf_tile> m_tile_reader;
public:
/**
* Construct the vector_tile from a data_view. The vector_tile object
* will keep a reference to the data referenced by the data_view. No
* copy of the data is created.
*/
explicit vector_tile(const data_view data) noexcept :
m_data(data),
m_tile_reader(m_data) {
}
/**
* Construct the vector_tile from a string. The vector_tile object
* will keep a reference to the data referenced by the string. No
* copy of the data is created.
*/
explicit vector_tile(const std::string& data) noexcept :
m_data(data.data(), data.size()),
m_tile_reader(m_data) {
}
/**
* Construct the vector_tile from a ptr and size. The vector_tile
* object will keep a reference to the data. No copy of the data is
* created.
*/
vector_tile(const char* data, std::size_t size) noexcept :
m_data(data, size),
m_tile_reader(m_data) {
}
/**
* Is this vector tile empty?
*
* @returns true if there are no layers in this vector tile, false
* otherwise
* Complexity: Constant.
*/
bool empty() const noexcept {
return m_data.empty();
}
/**
* Return the number of layers in this tile.
*
* Complexity: Linear in the number of layers.
*
* @returns the number of layers in this tile
* @throws any protozero exception if the protobuf encoding is invalid.
*/
std::size_t count_layers() const {
std::size_t size = 0;
protozero::pbf_message<detail::pbf_tile> tile_reader{m_data};
while (tile_reader.next(detail::pbf_tile::layers,
protozero::pbf_wire_type::length_delimited)) {
tile_reader.skip();
++size;
}
return size;
}
/**
* Get the next layer in this tile.
*
* Complexity: Constant.
*
* @returns layer The next layer or the invalid layer if there are no
* more layers.
* @throws format_exception if the tile data is ill-formed.
* @throws any protozero exception if the protobuf encoding is invalid.
*/
layer next_layer() {
const bool has_next = m_tile_reader.next(detail::pbf_tile::layers,
protozero::pbf_wire_type::length_delimited);
return has_next ? layer{m_tile_reader.get_view()} : layer{};
}
/**
* Reset the layer iterator. The next time next_layer() is called,
* it will begin from the first layer again.
*
* Complexity: Constant.
*/
void reset_layer() noexcept {
m_tile_reader = protozero::pbf_message<detail::pbf_tile>{m_data};
}
/**
* Call a function for each layer in this tile.
*
* @tparam The type of the function. It must take a single argument
* of type layer&& 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.
*/
template <typename TFunc>
bool for_each_layer(TFunc&& func) const {
protozero::pbf_message<detail::pbf_tile> tile_reader{m_data};
while (tile_reader.next(detail::pbf_tile::layers,
protozero::pbf_wire_type::length_delimited)) {
if (!std::forward<TFunc>(func)(layer{tile_reader.get_view()})) {
return false;
}
}
return true;
}
/**
* Returns the layer with the specified zero-based index.
*
* Complexity: Linear in the number of layers.
*
* @returns The specified layer or the invalid layer if index is
* larger than the number of layers.
* @throws format_exception if the tile data is ill-formed.
* @throws any protozero exception if the protobuf encoding is invalid.
*/
layer get_layer(std::size_t index) const {
protozero::pbf_message<detail::pbf_tile> tile_reader{m_data};
while (tile_reader.next(detail::pbf_tile::layers,
protozero::pbf_wire_type::length_delimited)) {
if (index == 0) {
return layer{tile_reader.get_view()};
}
tile_reader.skip();
--index;
}
return layer{};
}
/**
* Returns the layer with the specified name.
*
* Complexity: Linear in the number of layers.
*
* If there are several layers with the same name (which is against
* the spec 4.1 "A Vector Tile MUST NOT contain two or more layers
* whose name values are byte-for-byte identical.") it is unspecified
* which will be returned.
*
* @returns The specified layer or the invalid layer if there is no
* layer with this name.
* @throws format_exception if the tile data is ill-formed.
* @throws any protozero exception if the protobuf encoding is invalid.
*/
layer get_layer_by_name(const data_view name) const {
protozero::pbf_message<detail::pbf_tile> tile_reader{m_data};
while (tile_reader.next(detail::pbf_tile::layers,
protozero::pbf_wire_type::length_delimited)) {
const auto layer_data = tile_reader.get_view();
protozero::pbf_message<detail::pbf_layer> layer_reader{layer_data};
if (layer_reader.next(detail::pbf_layer::name,
protozero::pbf_wire_type::length_delimited)) {
if (layer_reader.get_view() == name) {
return layer{layer_data};
}
} else {
// 4.1 "A layer MUST contain a name field."
throw format_exception{"missing name in layer (spec 4.1)"};
}
}
return layer{};
}
/**
* Returns the layer with the specified name.
*
* Complexity: Linear in the number of layers.
*
* If there are several layers with the same name (which is against
* the spec 4.1 "A Vector Tile MUST NOT contain two or more layers
* whose name values are byte-for-byte identical.") it is unspecified
* which will be returned.
*
* @returns The specified layer or the invalid layer if there is no
* layer with this name.
* @throws format_exception if the tile data is ill-formed.
* @throws any protozero exception if the protobuf encoding is invalid.
*/
layer get_layer_by_name(const std::string& name) const {
return get_layer_by_name(data_view{name.data(), name.size()});
}
/**
* Returns the layer with the specified name.
*
* Complexity: Linear in the number of layers.
*
* If there are several layers with the same name (which is against
* the spec 4.1 "A Vector Tile MUST NOT contain two or more layers
* whose name values are byte-for-byte identical.") it is unspecified
* which will be returned.
*
* @returns The specified layer or the invalid layer if there is no
* layer with this name.
* @throws format_exception if the tile data is ill-formed.
* @throws any protozero exception if the protobuf encoding is invalid.
*/
layer get_layer_by_name(const char* name) const {
return get_layer_by_name(data_view{name, std::strlen(name)});
}
}; // class vector_tile
/**
* Helper function to determine whether some data could represent a
* vector tile. This takes advantage of the fact that the first byte of
* a vector tile is always 0x1a. It can't be 100% reliable though, because
* some other data could still contain that byte.
*
* @returns false if this is definitely no vector tile
* true if this could be a vector tile
*/
inline bool is_vector_tile(const data_view data) noexcept {
return !data.empty() && data.data()[0] == 0x1a;
}
} // namespace vtzero
#endif // VTZERO_VECTOR_TILE_HPP
|