2018-04-19 15:03:52 -04:00
|
|
|
|
|
|
|
# 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
|
|
|
|
|
2024-05-07 16:19:48 -04:00
|
|
|
Call `add_points()` with the number of points in the geometry as the only argument.
|
2018-04-19 15:03:52 -04:00
|
|
|
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.
|
|
|
|
|
2024-05-07 16:19:48 -04:00
|
|
|
## 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.
|
|
|
|
|