cf89d56ac Release 1.1.0 6defc33ea Update changelog 8480ce447 Use basic_pbf_builder to make buffer type configurable 25fbe0caf Travis: Disable test that fails with out of memory error f6741c19a Travis: Test on different architectures 598031339 Explicitly return 0 or 1 in example code, don't use std::exit dd8d315ea Remove virtual inheritance in builder f8511cc9e Fix another unused variable 4002414f0 Switch to catch2 for testing 9bb27a31f Avoid unused variable warning b04163da6 Use std::array instead of C array 4701b6a57 Make string non-const so the return doesn't copy 454f7d711 Fix clang-tidy config 6105d7a62 "Modernize" travis config 7fefbf932 Use try/catch in main() b3a62df21 Various fixes based on clang-tidy reports 83f784641 Use uppercase letters for numeric literals 23fd5f5c9 CMake config: Also look for newer clang-tidy versions 25ba7a7f6 Remove unnecessary ref qualifier f1cc1728a Doxygen: Remove config settings not available in newer versions 0a0afc97d Fix doc. 630dc80bc fix docs e022ba387 Call commit on finished features. 5d3f6eaf9 Remember version in layer_builder_impl. Add accessor functions. bc9d5f977 Remove comment that doesn't apply (any more). e1618d063 Update appveyor config to use more recent visual studio version. e6f9e3e62 Remove unnecessary workaround in Appveyor config. e1df3894c Remove xcode6.4 build soon to be removed from travis. 808a7b6a1 Release 1.0.3 d4faa2fb0 catch exceptions in vtzero-stats 291572505 Add overload of copy_properties function using a property_manager. 189caf4b8 Use new copy_id() function in advanced documentation. 184bb33e1 Add feature::for_each_property_indexes() member function. 3e296a36f Bugfix: Wrong iterator was checked in assert. 1115c0910 Update submodule to use newest mvt-fixtures. f32e73893 Explicitly initialize member (needed by older GCCs). 8d37928ca More tests. bf68443be Add copy_properties() helper function to feature builders. 96ed8cbd7 Add copy_id() helper function to builders. b166fcc3a Document that layer class uses mutable. 8609cdf96 Add missing word 3484299f2 Release 1.0.2 6d3dd8940 Bugfix: layer_builder::add_feature() now commits features it adds. 8da72723f Update links in change log. git-subtree-dir: third_party/vtzero git-subtree-split: cf89d56ac22eee0a252aab8d2e87344e4ce73d70
566 lines
21 KiB
Markdown
566 lines
21 KiB
Markdown
|
|
# Writing vector tiles
|
|
|
|
Writing vector tiles start with creating a `tile_builder`. This builder will
|
|
then be used to add layers and features in those layers. Once all this is done,
|
|
you call `serialize()` to actually build the vector tile from the data you
|
|
provided to the builders:
|
|
|
|
```cpp
|
|
#include <vtzero/builder.hpp> // always needed when writing vector tiles
|
|
|
|
vtzero::tile_builder tbuilder;
|
|
// add lots of data to builder...
|
|
std::string buffer = tbuilder.serialize();
|
|
```
|
|
|
|
You can also serialize the data into an existing buffer instead:
|
|
|
|
```cpp
|
|
std::string buffer; // got buffer from somewhere
|
|
tbuilder.serialize(buffer);
|
|
```
|
|
|
|
## Adding layers to tiles
|
|
|
|
Once you have a tile builder, you'll first need some layers:
|
|
|
|
```cpp
|
|
vtzero::tile_builder tbuilder;
|
|
vtzero::layer_builder layer_pois{tbuilder, "pois", 2, 4096};
|
|
vtzero::layer_builder layer_roads{tbuilder, "roads"};
|
|
vtzero::layer_builder layer_forests{tbuilder, "forests"};
|
|
```
|
|
|
|
Here three layers called "pois", "roads", and "forests" are added. The first
|
|
one explicitly specifies the vector tile version used and the extent. The
|
|
values specified here are the default, so all layers in this example will have
|
|
a version of 2 and an extent of 4096.
|
|
|
|
If you have read a layer from an existing vector tile and want to copy over
|
|
some of the data, you can use this layer to initialize the new layer in the
|
|
new vector tile with the name, version and extent from the existing layer like
|
|
this:
|
|
|
|
```cpp
|
|
vtzero::layer some_layer = ...;
|
|
vtzero::layer_builder layer_pois{tbuilder, some_layer};
|
|
// same as...
|
|
vtzero::layer_builder layer_pois{tbuilder, some_layer.name(),
|
|
some_layer.version(),
|
|
some_layer.extent()};
|
|
```
|
|
|
|
If you want to copy over an existing layer completely, you can use the
|
|
`add_existing_layer()` function instead:
|
|
|
|
```cpp
|
|
vtzero::layer some_layer = ...;
|
|
vtzero::tile_builder tbuilder;
|
|
tbuilder.add_existing_layer(some_layer);
|
|
```
|
|
|
|
Or, if you have the encoded layer data available in a `data_view` this also
|
|
works:
|
|
|
|
```cpp
|
|
vtzero::data_view layer_data = ...;
|
|
vtzero::tile_builder tbuilder;
|
|
tbuilder.add_existing_layer(layer_data);
|
|
```
|
|
|
|
Note that this call will only store a reference to the data to be added in the
|
|
tile builder. The data will only be copied when the final `serialize()` is
|
|
called, so the input data must still be available then!
|
|
|
|
You can mix any of the ways of adding a layer to the tile mentioned above. The
|
|
layers will be added to the tile in the order you add them to the
|
|
`tile_builder`.
|
|
|
|
The tile builder is smart enough to not add empty layers, so you can start
|
|
out with all the layers you might need and if some of them stay empty, they
|
|
will not be added to the tile when `serialize()` is called.
|
|
|
|
## Adding features to layers
|
|
|
|
Once we have one or more `layer_builder`s instantiated, we can add features
|
|
to them. This is done through the following feature builder classes:
|
|
|
|
* `point_feature_builder` to add a feature with a (multi)point geometry,
|
|
* `linestring_feature_builder` to add a feature with a (multi)linestring
|
|
geometry,
|
|
* `polygon_feature_builder` to add a feature with a (multi)polygon geometry, or
|
|
* `geometry_feature_builder` to add a feature with an existing geometry you
|
|
got from reading a vector tile.
|
|
|
|
In all cases you need to instantiate the feature builder class, optionally
|
|
add the feature ID using the `set_id()` method, add the geometry and then
|
|
add all the properties of this feature. You have to keep to this order!
|
|
|
|
```cpp
|
|
...
|
|
vtzero::layer_builder lbuilder{...};
|
|
{
|
|
vtzero::point_feature_builder fbuilder{lbuilder};
|
|
// optionally set the ID
|
|
fbuilder.set_id(23);
|
|
// add the geometry (exact calls are different for different feature builders)
|
|
fbuilder.add_point(99, 33);
|
|
// add the properties
|
|
fbuilder.add_property("amenity", "restaurant");
|
|
// call commit() when you are done
|
|
fbuilder.commit()
|
|
}
|
|
```
|
|
|
|
You have to call `commit()` on the feature builder object after you set all the
|
|
data to actually add it to the layer. If you don't do this, the feature will
|
|
not be added to the layer! This can be useful, for instance, if you detect that
|
|
you have an invalid geometry while you are adding the geometry to the feature
|
|
builder. In that case you can call `rollback()` explicitly or just let the
|
|
feature builder go out of scope and it will do the rollback automatically.
|
|
|
|
Only the first call to `commit()` or `rollback()` will take effect, any further
|
|
calls to these functions on the same feature builder object are ignored.
|
|
|
|
## Adding a geometry to the feature
|
|
|
|
There are different ways of adding the geometry to the feature, depending on
|
|
the geometry type.
|
|
|
|
### Adding a point geometry
|
|
|
|
Simply call `add_point()` to set the point geometry. There are three different
|
|
overloads for this function. One takes a `vtzero::point`, one takes two
|
|
`uint32_t`s with the x and y coordinates and one takes any type `T` that can
|
|
be converted to a `vtzero::point` using the `create_vtzero_point` function.
|
|
This templated function works on any type that has `x` and `y` members and
|
|
you can create your own overload of this function. See the
|
|
[advanced.md](advanced topics documentation).
|
|
|
|
### Adding a multipoint geometry
|
|
|
|
Call `add_points()` with the number of points in the geometry as the only argument.
|
|
After that call `set_point()` for each of those points. `set_point()` has
|
|
multiple overloads just like the `add_point()` method described above.
|
|
|
|
There is also the `add_points_from_container()` function which copies the
|
|
point from any container type supporting the `size()` function and which
|
|
iterator yields a `vtzero::point` or something convertible to it.
|
|
|
|
### Adding a linestring geometry
|
|
|
|
Call `add_linestring()` with the number of points in the linestring as only
|
|
argument. After that call `set_point()` for each of those points. `set_point()`
|
|
has multiple overloads just like the `add_point()` method described above.
|
|
|
|
```cpp
|
|
...
|
|
vtzero::layer_builder lbuilder{...};
|
|
try {
|
|
vtzero::linestring_feature_builder fbuilder{lbuilder};
|
|
// optionally set the ID
|
|
fbuilder.set_id(23);
|
|
// add the geometry
|
|
fbuilder.add_linestring(2);
|
|
fbuilder.set_point(1, 2);
|
|
fbuilder.set_point(3, 4);
|
|
// add the properties
|
|
fbuilder.add_property("highway", "primary");
|
|
fbuilder.add_property("maxspeed", 80);
|
|
// call commit() when you are done
|
|
fbuilder.commit()
|
|
} catch (const vtzero::geometry_exception& e) {
|
|
// if we are here, something was wrong with the geometry.
|
|
}
|
|
```
|
|
|
|
Note that we have wrapped the feature builder in a try-catch-block here. This
|
|
will ignore all geometry errors (which can happen if two consective points
|
|
are the same creating a zero-length segment).
|
|
|
|
There are two other versions of the `add_linestring()` function. They take two
|
|
iterators defining a range to get the points from. Dereferencing those
|
|
iterators must yield a `vtzero::point` or something convertible to it. One of
|
|
these functions takes a third argument, the number of points the iterator will
|
|
yield. If this is not available `std::distance(begin, end)` is called which
|
|
internally by the `add_linestring()` function which might be slow depending on
|
|
your iterator type.
|
|
|
|
### Adding a multilinestring geometry
|
|
|
|
Adding a multilinestring works just like adding a linestring, just do the
|
|
calls to `add_linestring()` etc. repeatedly for each of the linestrings.
|
|
|
|
### Adding a polygon geometry
|
|
|
|
A polygon consists of one outer ring and zero or more inner rings. You have
|
|
to first add the outer ring and then the inner rings, if any.
|
|
|
|
Call `add_ring()` with the number of points in the ring as only argument. After
|
|
that call `set_point()` for each of those points. `set_point()` has multiple
|
|
overloads just like the `add_point()` method described above. The minimum
|
|
number of points is 4 and the last point must be the same as the first point
|
|
(or call `close_ring()` instead of the last `set_point()`).
|
|
|
|
```cpp
|
|
...
|
|
vtzero::layer_builder lbuilder{...};
|
|
try {
|
|
vtzero::polygon_feature_builder fbuilder{lbuilder};
|
|
// optionally set the ID
|
|
fbuilder.set_id(23);
|
|
// add the geometry
|
|
fbuilder.add_ring(5);
|
|
fbuilder.set_point(1, 1);
|
|
fbuilder.set_point(1, 2);
|
|
fbuilder.set_point(2, 2);
|
|
fbuilder.set_point(2, 1);
|
|
fbuilder.set_point(1, 1); // or call fbuilder.close_ring() instead
|
|
// add the properties
|
|
fbuilder.add_property("landuse", "forest");
|
|
// call commit() when you are done
|
|
fbuilder.commit()
|
|
} catch (const vtzero::geometry_exception& e) {
|
|
// if we are here, something was wrong with the geometry.
|
|
}
|
|
```
|
|
|
|
Note that we have wrapped the feature builder in a try-catch-block here. This
|
|
will ignore all geometry errors (which can happen if two consective points
|
|
are the same creating a zero-length segment or if the last point is not the
|
|
same as the first point).
|
|
|
|
There are two other versions of the `add_ring()` function. They take two
|
|
iterators defining a range to get the points from. Dereferencing those
|
|
iterators must yield a `vtzero::point` or something convertible to it. One of
|
|
these functions takes a third argument, the number of points the iterator will
|
|
yield. If this is not available `std::distance(begin, end)` is called which
|
|
internally by the `add_ring()` function which might be slow depending on your
|
|
iterator type.
|
|
|
|
### Adding a multipolygon geometry
|
|
|
|
Adding a multipolygon works just like adding a polygon, just do the calls to
|
|
`add_ring()` etc. repeatedly for each of the rings. Make sure to always first
|
|
add an outer ring, then the inner rings in this outer ring, then the next
|
|
outer ring and so on.
|
|
|
|
### Adding an existing geometry
|
|
|
|
The `geometry_feature_builder` class is used to add geometries you got from
|
|
reading a vector tile. This is useful when you want to copy over a geometry
|
|
from a feature without decoding it.
|
|
|
|
```cpp
|
|
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.add_geometry(geom) // add geometry
|
|
fb.add_property("foo", "bar"); // add properties
|
|
fb.commit();
|
|
...
|
|
```
|
|
|
|
## Adding properties to the feature
|
|
|
|
A feature can have any number of properties. They are added with the
|
|
`add_property()` method called on the feature builder. There are two different
|
|
ways of doing this. The *simple approach* which does all the work for you and
|
|
the *advanced approach* which can be more efficient, but you have to to some
|
|
more work. It is recommended that you start out with the simple approach and
|
|
only switch to the advanced approach once you have a working program and want
|
|
to get the last bit of performance out of it.
|
|
|
|
The difference stems from the way properties are encoded in vector tiles. While
|
|
properties "belong" to features, they are really stored in two tables (for the
|
|
keys and values) in the layer. The individual feature only references the
|
|
entries in those tables by index. This make the encoded tile smaller, but it
|
|
means somebody has to manage those tables. In the simple approach this is done
|
|
behind the scenes by the `layer_builder` object, in the advanced approach you
|
|
handle that yourself.
|
|
|
|
Do not mix the simple and the advanced approach unless you know what you are
|
|
doing.
|
|
|
|
### The simple approach to adding properties
|
|
|
|
For the simple approach call `add_property()` with two arguments. The first is
|
|
the key, it must have some kind of string type (`std::string`, `const char*`,
|
|
`vtzero::data_view`, anything really that converts to a `data_view`). The
|
|
second argument is the value, for which most basic C++ types are allowed
|
|
(string types, integer types, double, ...). See the API documentation for the
|
|
constructors of the `encoded_property_value` class for a list.
|
|
|
|
```cpp
|
|
vtzero::layer_builder lb{...};
|
|
vtzero::linestring_feature_builder fb{lb};
|
|
...
|
|
fb.add_property("waterway", "stream"); // string value
|
|
fb.add_property("name", "Little Creek");
|
|
fb.add_property("width", 1.5); // double value
|
|
...
|
|
```
|
|
|
|
Sometimes you need to specify exactly which type should be used in the
|
|
encoding. The `encoded_property_value` constructor can take special types for
|
|
that like in the following example, where you force the `sint` encoding:
|
|
|
|
```cpp
|
|
fb.add_property("layer", vtzero::sint_value_type(2));
|
|
```
|
|
|
|
You can also call `add_property()` with a single `vtzero::property` argument
|
|
(which is handy if you are copying this property over from a tile you are
|
|
reading):
|
|
|
|
```cpp
|
|
while (auto property = feature.next_property()) {
|
|
if (property.key() == "name") {
|
|
feature_builder.add_property(property);
|
|
}
|
|
}
|
|
```
|
|
|
|
### The advanced approach to adding properties
|
|
|
|
In the advanced approach you have to do the indexing yourself. Here is a very
|
|
basic example:
|
|
|
|
```cpp
|
|
vtzero::tile_builder tbuilder;
|
|
vtzero::layer_builder lbuilder{tbuilder, "test"};
|
|
const vtzero::index_value highway = lbuilder.add_key("highway");
|
|
const vtzero::index_value primary = lbuilder.add_value("primary");
|
|
...
|
|
vtzero::point_feature_builder fbuilder{lbuilder};
|
|
...
|
|
fbuilder.add_property(highway, primary);
|
|
...
|
|
```
|
|
|
|
The methods `add_key()` and `add_value()` on the layer builder are used to add
|
|
keys and values to the tables in the layer. They both return the index (of type
|
|
`vtzero::index_value`) of those keys or values in the tables. You store
|
|
those index values somewhere (in this case in the `highway` and `primary`
|
|
variables) and use them when calling `add_property()` on the feature builder.
|
|
|
|
In some cases you only have a few property keys and know them beforehand,
|
|
then storing the key indexes in individual variables might work. But for
|
|
values this usually doesn't make much sense, and if all your keys and values
|
|
are only known at runtime, it doesn't work either. For this you need some kind
|
|
of index data structure mapping from keys/values to index values. You can
|
|
implement this yourself, but it is easier to use some classes provided by
|
|
vtzero. Then the code looks like this:
|
|
|
|
```cpp
|
|
#include <vtzero/index.hpp> // use this include to get the index classes
|
|
...
|
|
vtzero::layer_builder lb{...};
|
|
vtzero::key_index<std::map> key_index{lb};
|
|
vtzero::value_index_internal<std::unordered_map> value_index{lb};
|
|
...
|
|
vtzero::point_feature_builder fb{lb};
|
|
...
|
|
fb.add_property(key_index("highway"), value_index("primary"));
|
|
...
|
|
```
|
|
|
|
In this example the `key_index` template class is used for keys, it uses
|
|
`std::map` internally as can be seen by its template argument. The
|
|
`value_index_internal` template class is used for values, it uses
|
|
`std::unordered_map` internally in this example. Whether you specify `std::map`
|
|
or `std::unordered_map` or something else (that needs to be compatible to those
|
|
classes) is up to you. Benchmark your use case and decide then.
|
|
|
|
Keys are always strings, so they are easy to handle. For keys there is only the
|
|
single `key_index` in vtzero.
|
|
|
|
For values this is more difficult. Basically there are two choices:
|
|
|
|
1. Encode the value according to the vector tile encoding rules which results
|
|
in a string and store this in the index. This is what the
|
|
`value_index_internal` class does.
|
|
2. Store the un-encoded value in the index. The index lookup will be faster,
|
|
but you need a different index type for each value type. This is what the
|
|
`value_index` classes do.
|
|
|
|
The `value_index` template classes need three template arguments: The type
|
|
used internally to encode the value, the type used externally, and the map
|
|
type.
|
|
|
|
In this example the user program has the values as `int`, the index will store
|
|
them in a `std::map<int>`. The integer value is then encoded in an `sint`
|
|
int the vector tile:
|
|
|
|
```cpp
|
|
vtzero::value_index<vtzero::sint_value:type, int, std::map> index;
|
|
```
|
|
|
|
Sometimes these generic indexes based on `std::map` or `std::unordered_map`
|
|
are inefficient, that's why there are specialized indexes for special cases:
|
|
|
|
* The `value_index_bool` class can only index boolean values.
|
|
* The `value_index_small_uint` class can only index small unsigned integer
|
|
values (up to `uint16_t`). It uses a vector internally, so if all your
|
|
numbers are small and densely packed, this is very efficient. This is
|
|
especially useful for `enum` types.
|
|
|
|
## The `add_property()` function.
|
|
|
|
The last chapters already talked about the `add_property()` function of the
|
|
`feature_builder` class. But because it is a bit difficult to see all the
|
|
different ways `add_property()` can be called, here is some more information.
|
|
|
|
The `add_property()` function is called with either two parameters for the
|
|
key and value or with one parameter that combines the key and value.
|
|
|
|
If it is called with an `index_value` for the key or value, that index value is
|
|
stored directly into the feature. If it is called with an `index_value_pair`,
|
|
the index values in the `index_value_pair` are stored directly in the feature.
|
|
|
|
If it is called with something that is not an `index_value` or
|
|
`index_value_pair`, the function will interpret the data as keys or values.
|
|
It will add those keys and values to the layer (if they are not already there),
|
|
find the corresponding index values and store them in the feature.
|
|
|
|
You can mix index-use with non-index use. For instance
|
|
|
|
```cpp
|
|
index_value key_maxspeed = lbuilder.add_key("maxspeed");
|
|
...
|
|
fbuilder.add_property(key_maxspeed, 30);
|
|
```
|
|
|
|
In this case the key ("maxspeed") was added to the layer once and its index
|
|
value (`key_maxspeed`) can later be reused. The value (30), on the other hand,
|
|
is only added to the layer in the `add_property()` call.
|
|
|
|
So for keys, you can have as argument:
|
|
* An `index_value`.
|
|
* A `data_view` or something that converts to it like a `const char*` or `std::string`.
|
|
|
|
For values, you can have as argument:
|
|
* An `index_value`.
|
|
* A `property_value`.
|
|
* An `encoded_property_value` or anything that converts to it.
|
|
|
|
For combined keys and values, you can have as argument:
|
|
* An `index_value_pair`.
|
|
* A `property`.
|
|
|
|
|
|
## Deriving from `layer_builder` and `feature_builder`
|
|
|
|
The `vtzero::layer_builder` and `vtzero::feature_builder` classes have been
|
|
designed in a way that they can be derived from easily. This allows you to
|
|
encapsulate part of your vector tile writing code if some aspects of your
|
|
layers/features are always the same, such as the layer name and the names
|
|
and types of properties.
|
|
|
|
Say you want to write a layer named "restaurants" with point geometries.
|
|
Each feature should have a name and a 5-star-rating. First you create a
|
|
class derived from the `layer_builder` with all the indexes you want to use.
|
|
For the keys you don't need indexes in this case, because there are only
|
|
two keys for which we can easily store the index values in the layer.
|
|
|
|
```cpp
|
|
class restaurant_layer_builder : public vtzero::layer_builder {
|
|
|
|
public:
|
|
|
|
// The index we'll use for the "name" property values
|
|
vtzero::value_index<vtzero::string_value_type, std::string, std::unordered_map> string_index;
|
|
|
|
// The index we'll use for the "stars" property values
|
|
vtzero::value_index_small_uint stars_index;
|
|
|
|
// The index value of the "name" key
|
|
vtzero::index_value key_name;
|
|
|
|
// The index value of the "stars" key
|
|
vtzero::index_value key_stars;
|
|
|
|
restaurant_layer_builder(vtzero::tile_builder& tile) :
|
|
layer_builder(tile, "restaurants"), // the name of the layer
|
|
string_index(*this),
|
|
stars_index(*this),
|
|
key_name(add_key_without_dup_check("name")),
|
|
key_stars(add_key_without_dup_check("stars")) {
|
|
}
|
|
|
|
};
|
|
```
|
|
|
|
The we'll add a class derived from `feature_builder` to help with adding
|
|
features:
|
|
|
|
```cpp
|
|
class restaurant_feature_builder : public vtzero::feature_builder {
|
|
|
|
restaurant_layer_builder& m_layer;
|
|
|
|
public:
|
|
|
|
restaurant_feature_builder(restaurant_layer_builder& layer, uint64_t id) :
|
|
vtzero::point_feature_builder(layer), // always a point geometry
|
|
m_layer(layer) {
|
|
set_id(id); // we always have an ID in this case
|
|
}
|
|
|
|
void add_location(mylocation& loc) { // restaurant location is stored in your own type
|
|
add_point(loc.lon(), loc.lat());
|
|
}
|
|
|
|
void set_name(const std::string& name) {
|
|
add_property(m_layer.key_name,
|
|
m_layer.string_index(vtzero::encoded_property_value{name}));
|
|
}
|
|
|
|
void set_stars(stars s) { // your own "stars" type
|
|
vtzero::encoded_property_value svalue{ s.num_stars() }; // convert stars type to small integer
|
|
|
|
add_property(m_layer.key_stars,
|
|
m_layer.stars_index(svalue));
|
|
}
|
|
|
|
};
|
|
```
|
|
|
|
This example only shows a general pattern you can follow to derive from the
|
|
`layer_builder` and `feature_builder` classes. In some cases this makes more
|
|
sense then in others. The derived classes make it easy for you to mix your
|
|
own functions (for instance when you need to convert from your own types to
|
|
vtzero types like with the `mylocation` and `stars` types above) or just use
|
|
the functions in the base classes.
|
|
|
|
## Using a custom buffer type
|
|
|
|
Usually `std::string` is used as a buffer type:
|
|
|
|
```cpp
|
|
std::string buffer;
|
|
tbuilder.serialize(buffer);
|
|
```
|
|
|
|
But you can also use other buffer types supported by Protozero, like
|
|
`std::vector<char>`:
|
|
|
|
```cpp
|
|
#include <protozero/buffer_vector.hpp>
|
|
|
|
std::vector<char> buffer;
|
|
tbuilder.serialize(buffer);
|
|
```
|
|
|
|
Or you can use your own buffer types and write special adaptors for it.
|
|
See the Protozero documentation for details.
|
|
|
|
Note that while in theory this allows you to also use fixed-sized buffers
|
|
through the `protozero::fixed_sized_buffer_adaptor` class, vtzero will still
|
|
use `std::string` for additional buffers internally.
|
|
|