Preliminary integration of the tile plugin
This commit is contained in:
parent
0182b9a584
commit
666d363c93
28
include/engine/api/tile_parameters.hpp
Normal file
28
include/engine/api/tile_parameters.hpp
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
#ifndef ENGINE_API_TILE_PARAMETERS_HPP
|
||||||
|
#define ENGINE_API_TILE_PARAMETERS_HPP
|
||||||
|
|
||||||
|
namespace osrm
|
||||||
|
{
|
||||||
|
namespace engine
|
||||||
|
{
|
||||||
|
namespace api
|
||||||
|
{
|
||||||
|
|
||||||
|
struct TileParameters final
|
||||||
|
{
|
||||||
|
unsigned x;
|
||||||
|
unsigned y;
|
||||||
|
unsigned z;
|
||||||
|
|
||||||
|
// FIXME check if x and y work with z
|
||||||
|
bool IsValid()
|
||||||
|
{
|
||||||
|
return z < 20;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -31,6 +31,7 @@ struct TableParameters;
|
|||||||
struct NearestParameters;
|
struct NearestParameters;
|
||||||
struct TripParameters;
|
struct TripParameters;
|
||||||
struct MatchParameters;
|
struct MatchParameters;
|
||||||
|
struct TileParameters;
|
||||||
}
|
}
|
||||||
namespace plugins
|
namespace plugins
|
||||||
{
|
{
|
||||||
@ -39,6 +40,7 @@ class TablePlugin;
|
|||||||
class NearestPlugin;
|
class NearestPlugin;
|
||||||
class TripPlugin;
|
class TripPlugin;
|
||||||
class MatchPlugin;
|
class MatchPlugin;
|
||||||
|
class TilePlugin;
|
||||||
}
|
}
|
||||||
// End fwd decls
|
// End fwd decls
|
||||||
|
|
||||||
@ -66,6 +68,7 @@ class Engine final
|
|||||||
Status Nearest(const api::NearestParameters ¶meters, util::json::Object &result);
|
Status Nearest(const api::NearestParameters ¶meters, util::json::Object &result);
|
||||||
Status Trip(const api::TripParameters ¶meters, util::json::Object &result);
|
Status Trip(const api::TripParameters ¶meters, util::json::Object &result);
|
||||||
Status Match(const api::MatchParameters ¶meters, util::json::Object &result);
|
Status Match(const api::MatchParameters ¶meters, util::json::Object &result);
|
||||||
|
Status Tile(const api::TileParameters ¶meters, std::string &result);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<EngineLock> lock;
|
std::unique_ptr<EngineLock> lock;
|
||||||
@ -75,6 +78,7 @@ class Engine final
|
|||||||
std::unique_ptr<plugins::NearestPlugin> nearest_plugin;
|
std::unique_ptr<plugins::NearestPlugin> nearest_plugin;
|
||||||
std::unique_ptr<plugins::TripPlugin> trip_plugin;
|
std::unique_ptr<plugins::TripPlugin> trip_plugin;
|
||||||
std::unique_ptr<plugins::MatchPlugin> match_plugin;
|
std::unique_ptr<plugins::MatchPlugin> match_plugin;
|
||||||
|
std::unique_ptr<plugins::TilePlugin> tile_plugin;
|
||||||
|
|
||||||
std::unique_ptr<datafacade::BaseDataFacade> query_data_facade;
|
std::unique_ptr<datafacade::BaseDataFacade> query_data_facade;
|
||||||
};
|
};
|
||||||
|
@ -2,17 +2,9 @@
|
|||||||
#define TILEPLUGIN_HPP
|
#define TILEPLUGIN_HPP
|
||||||
|
|
||||||
#include "engine/plugins/plugin_base.hpp"
|
#include "engine/plugins/plugin_base.hpp"
|
||||||
#include "osrm/json_container.hpp"
|
#include "engine/api/tile_parameters.hpp"
|
||||||
|
|
||||||
#include <protozero/varint.hpp>
|
|
||||||
#include <protozero/pbf_writer.hpp>
|
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
#include <cmath>
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This plugin generates Mapbox Vector tiles that show the internal
|
* This plugin generates Mapbox Vector tiles that show the internal
|
||||||
@ -29,405 +21,12 @@ namespace engine
|
|||||||
namespace plugins
|
namespace plugins
|
||||||
{
|
{
|
||||||
|
|
||||||
// from mapnik/well_known_srs.hpp
|
class TilePlugin final : public BasePlugin
|
||||||
const constexpr double EARTH_RADIUS = 6378137.0;
|
|
||||||
const constexpr double EARTH_DIAMETER = EARTH_RADIUS * 2.0;
|
|
||||||
const constexpr double EARTH_CIRCUMFERENCE = EARTH_DIAMETER * M_PI;
|
|
||||||
const constexpr double MAXEXTENT = EARTH_CIRCUMFERENCE / 2.0;
|
|
||||||
const constexpr double M_PI_by2 = M_PI / 2.0;
|
|
||||||
const constexpr double D2R = M_PI / 180.0;
|
|
||||||
const constexpr double R2D = 180.0 / M_PI;
|
|
||||||
const constexpr double M_PIby360 = M_PI / 360.0;
|
|
||||||
const constexpr double MAXEXTENTby180 = MAXEXTENT / 180.0;
|
|
||||||
const double MAX_LATITUDE = R2D * (2.0 * std::atan(std::exp(180.0 * D2R)) - M_PI_by2);
|
|
||||||
// ^ math functions are not constexpr since they have side-effects (setting errno) :(
|
|
||||||
|
|
||||||
// from mapnik-vector-tile
|
|
||||||
namespace detail_pbf
|
|
||||||
{
|
|
||||||
|
|
||||||
inline unsigned encode_length(const unsigned len) { return (len << 3u) | 2u; }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Converts a regular WSG84 lon/lat pair into
|
|
||||||
// a mercator coordinate
|
|
||||||
inline void lonlat2merc(double &x, double &y)
|
|
||||||
{
|
|
||||||
if (x > 180)
|
|
||||||
x = 180;
|
|
||||||
else if (x < -180)
|
|
||||||
x = -180;
|
|
||||||
if (y > MAX_LATITUDE)
|
|
||||||
y = MAX_LATITUDE;
|
|
||||||
else if (y < -MAX_LATITUDE)
|
|
||||||
y = -MAX_LATITUDE;
|
|
||||||
x = x * MAXEXTENTby180;
|
|
||||||
y = std::log(std::tan((90 + y) * M_PIby360)) * R2D;
|
|
||||||
y = y * MAXEXTENTby180;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is the global default tile size for all Mapbox Vector Tiles
|
|
||||||
const constexpr double tile_size_ = 256.0;
|
|
||||||
|
|
||||||
//
|
|
||||||
inline void from_pixels(const double shift, double &x, double &y)
|
|
||||||
{
|
|
||||||
const double b = shift / 2.0;
|
|
||||||
x = (x - b) / (shift / 360.0);
|
|
||||||
const double g = (y - b) / -(shift / (2 * M_PI));
|
|
||||||
y = R2D * (2.0 * std::atan(std::exp(g)) - M_PI_by2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Converts a WMS tile coordinate (z,x,y) into a mercator bounding box
|
|
||||||
inline void xyz2mercator(
|
|
||||||
const int x, const int y, const int z, double &minx, double &miny, double &maxx, double &maxy)
|
|
||||||
{
|
|
||||||
minx = x * tile_size_;
|
|
||||||
miny = (y + 1.0) * tile_size_;
|
|
||||||
maxx = (x + 1.0) * tile_size_;
|
|
||||||
maxy = y * tile_size_;
|
|
||||||
const double shift = std::pow(2.0, z) * tile_size_;
|
|
||||||
from_pixels(shift, minx, miny);
|
|
||||||
from_pixels(shift, maxx, maxy);
|
|
||||||
lonlat2merc(minx, miny);
|
|
||||||
lonlat2merc(maxx, maxy);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Converts a WMS tile coordinate (z,x,y) into a wsg84 bounding box
|
|
||||||
inline void xyz2wsg84(
|
|
||||||
const int x, const int y, const int z, double &minx, double &miny, double &maxx, double &maxy)
|
|
||||||
{
|
|
||||||
minx = x * tile_size_;
|
|
||||||
miny = (y + 1.0) * tile_size_;
|
|
||||||
maxx = (x + 1.0) * tile_size_;
|
|
||||||
maxy = y * tile_size_;
|
|
||||||
const double shift = std::pow(2.0, z) * tile_size_;
|
|
||||||
from_pixels(shift, minx, miny);
|
|
||||||
from_pixels(shift, maxx, maxy);
|
|
||||||
}
|
|
||||||
|
|
||||||
// emulates mapbox::box2d, just a simple container for
|
|
||||||
// a box
|
|
||||||
struct bbox final
|
|
||||||
{
|
|
||||||
bbox(const double _minx, const double _miny, const double _maxx, const double _maxy)
|
|
||||||
: minx(_minx), miny(_miny), maxx(_maxx), maxy(_maxy)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
double width() const { return maxx - minx; }
|
|
||||||
double height() const { return maxy - miny; }
|
|
||||||
|
|
||||||
const double minx;
|
|
||||||
const double miny;
|
|
||||||
const double maxx;
|
|
||||||
const double maxy;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Simple container class for WSG84 coordinates
|
|
||||||
struct point_type_d final
|
|
||||||
{
|
|
||||||
point_type_d(double _x, double _y) : x(_x), y(_y) {}
|
|
||||||
|
|
||||||
const double x;
|
|
||||||
const double y;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Simple container for integer coordinates (i.e. pixel coords)
|
|
||||||
struct point_type_i final
|
|
||||||
{
|
|
||||||
point_type_i(std::int64_t _x, std::int64_t _y) : x(_x), y(_y) {}
|
|
||||||
|
|
||||||
const std::int64_t x;
|
|
||||||
const std::int64_t y;
|
|
||||||
};
|
|
||||||
|
|
||||||
using line_type = std::vector<point_type_i>;
|
|
||||||
using line_typed = std::vector<point_type_d>;
|
|
||||||
|
|
||||||
// from mapnik-vector-tile
|
|
||||||
// Encodes a linestring using protobuf zigzag encoding
|
|
||||||
inline bool encode_linestring(line_type line,
|
|
||||||
protozero::packed_field_uint32 &geometry,
|
|
||||||
std::int32_t &start_x,
|
|
||||||
std::int32_t &start_y)
|
|
||||||
{
|
|
||||||
const std::size_t line_size = line.size();
|
|
||||||
// line_size -= detail_pbf::repeated_point_count(line);
|
|
||||||
if (line_size < 2)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const unsigned line_to_length = static_cast<const unsigned>(line_size) - 1;
|
|
||||||
|
|
||||||
auto pt = line.begin();
|
|
||||||
geometry.add_element(9); // move_to | (1 << 3)
|
|
||||||
geometry.add_element(protozero::encode_zigzag32(pt->x - start_x));
|
|
||||||
geometry.add_element(protozero::encode_zigzag32(pt->y - start_y));
|
|
||||||
start_x = pt->x;
|
|
||||||
start_y = pt->y;
|
|
||||||
geometry.add_element(detail_pbf::encode_length(line_to_length));
|
|
||||||
for (++pt; pt != line.end(); ++pt)
|
|
||||||
{
|
|
||||||
const std::int32_t dx = pt->x - start_x;
|
|
||||||
const std::int32_t dy = pt->y - start_y;
|
|
||||||
/*if (dx == 0 && dy == 0)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}*/
|
|
||||||
geometry.add_element(protozero::encode_zigzag32(dx));
|
|
||||||
geometry.add_element(protozero::encode_zigzag32(dy));
|
|
||||||
start_x = pt->x;
|
|
||||||
start_y = pt->y;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class DataFacadeT> class TilePlugin final : public BasePlugin
|
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit TilePlugin(DataFacadeT *facade) : facade(facade), descriptor_string("tile") {}
|
TilePlugin(datafacade::BaseDataFacade &facade) : BasePlugin(facade) {}
|
||||||
|
|
||||||
const std::string GetDescriptor() const override final { return descriptor_string; }
|
Status HandleRequest(const api::TileParameters ¶meters, std::string &pbf_buffer);
|
||||||
|
|
||||||
Status HandleRequest(const RouteParameters &route_parameters,
|
|
||||||
util::json::Object &json_result) override final
|
|
||||||
{
|
|
||||||
|
|
||||||
// Vector tiles are 4096 virtual pixels on each side
|
|
||||||
const double tile_extent = 4096.0;
|
|
||||||
double min_lon, min_lat, max_lon, max_lat;
|
|
||||||
|
|
||||||
// Convert the z,x,y mercator tile coordinates into WSG84 lon/lat values
|
|
||||||
xyz2wsg84(route_parameters.x, route_parameters.y, route_parameters.z, min_lon, min_lat,
|
|
||||||
max_lon, max_lat);
|
|
||||||
|
|
||||||
FixedPointCoordinate southwest{static_cast<std::int32_t>(min_lat * COORDINATE_PRECISION),
|
|
||||||
static_cast<std::int32_t>(min_lon * COORDINATE_PRECISION)};
|
|
||||||
FixedPointCoordinate northeast{static_cast<std::int32_t>(max_lat * COORDINATE_PRECISION),
|
|
||||||
static_cast<std::int32_t>(max_lon * COORDINATE_PRECISION)};
|
|
||||||
|
|
||||||
// Fetch all the segments that are in our bounding box.
|
|
||||||
// This hits the OSRM StaticRTree
|
|
||||||
const auto edges = facade->GetEdgesInBox(southwest, northeast);
|
|
||||||
|
|
||||||
// TODO: extract speed values for compressed and uncompressed geometries
|
|
||||||
|
|
||||||
// Convert tile coordinates into mercator coordinates
|
|
||||||
xyz2mercator(route_parameters.x, route_parameters.y, route_parameters.z, min_lon, min_lat,
|
|
||||||
max_lon, max_lat);
|
|
||||||
const bbox tile_bbox{min_lon, min_lat, max_lon, max_lat};
|
|
||||||
|
|
||||||
// Protobuf serialized blocks when objects go out of scope, hence
|
|
||||||
// the extra scoping below.
|
|
||||||
std::string buffer;
|
|
||||||
protozero::pbf_writer tile_writer(buffer);
|
|
||||||
{
|
|
||||||
// Add a layer object to the PBF stream. 3=='layer' from the vector tile spec (2.1)
|
|
||||||
protozero::pbf_writer layer_writer(tile_writer, 3);
|
|
||||||
// TODO: don't write a layer if there are no features
|
|
||||||
// Field 15 is the "version field, and it's a uint32
|
|
||||||
layer_writer.add_uint32(15, 2); // version
|
|
||||||
// Field 1 is the "layer name" field, it's a string
|
|
||||||
layer_writer.add_string(1, "speeds"); // name
|
|
||||||
// Field 5 is the tile extent. It's a uint32 and should be set to 4096
|
|
||||||
// for normal vector tiles.
|
|
||||||
layer_writer.add_uint32(5, 4096); // extent
|
|
||||||
|
|
||||||
// Begin the layer features block
|
|
||||||
{
|
|
||||||
// Each feature gets a unique id, starting at 1
|
|
||||||
unsigned id = 1;
|
|
||||||
for (const auto &edge : edges)
|
|
||||||
{
|
|
||||||
// Get coordinates for start/end nodes of segmet (NodeIDs u and v)
|
|
||||||
const auto a = facade->GetCoordinateOfNode(edge.u);
|
|
||||||
const auto b = facade->GetCoordinateOfNode(edge.v);
|
|
||||||
// Calculate the length in meters, using the same calculation used to set the
|
|
||||||
// weight, so we can back-calculate the speed value that was set.
|
|
||||||
const double length = osrm::util::coordinate_calculation::greatCircleDistance(
|
|
||||||
a.lat, a.lon, b.lat, b.lon);
|
|
||||||
|
|
||||||
int forward_weight = 0;
|
|
||||||
int reverse_weight = 0;
|
|
||||||
|
|
||||||
if (edge.forward_packed_geometry_id != SPECIAL_EDGEID) {
|
|
||||||
std::vector<EdgeWeight> forward_weight_vector;
|
|
||||||
facade->GetUncompressedWeights(edge.forward_packed_geometry_id,
|
|
||||||
forward_weight_vector);
|
|
||||||
forward_weight = forward_weight_vector[edge.fwd_segment_position];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (edge.reverse_packed_geometry_id != SPECIAL_EDGEID) {
|
|
||||||
std::vector<EdgeWeight> reverse_weight_vector;
|
|
||||||
facade->GetUncompressedWeights(edge.reverse_packed_geometry_id,
|
|
||||||
reverse_weight_vector);
|
|
||||||
|
|
||||||
BOOST_ASSERT(edge.fwd_segment_position < reverse_weight_vector.size());
|
|
||||||
|
|
||||||
reverse_weight = reverse_weight_vector[reverse_weight_vector.size() -
|
|
||||||
edge.fwd_segment_position - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this is a valid forward edge, go ahead and add it to the tile
|
|
||||||
if (forward_weight != 0 &&
|
|
||||||
edge.forward_edge_based_node_id != SPECIAL_NODEID)
|
|
||||||
{
|
|
||||||
std::int32_t start_x = 0;
|
|
||||||
std::int32_t start_y = 0;
|
|
||||||
|
|
||||||
line_typed geo_line;
|
|
||||||
geo_line.emplace_back(a.lon / COORDINATE_PRECISION,
|
|
||||||
a.lat / COORDINATE_PRECISION);
|
|
||||||
geo_line.emplace_back(b.lon / COORDINATE_PRECISION,
|
|
||||||
b.lat / COORDINATE_PRECISION);
|
|
||||||
|
|
||||||
// Calculate the speed for this line
|
|
||||||
std::uint32_t speed = static_cast<std::uint32_t>(
|
|
||||||
round(length / forward_weight * 10 * 3.6));
|
|
||||||
|
|
||||||
line_type tile_line;
|
|
||||||
for (auto const &pt : geo_line)
|
|
||||||
{
|
|
||||||
double px_merc = pt.x;
|
|
||||||
double py_merc = pt.y;
|
|
||||||
lonlat2merc(px_merc, py_merc);
|
|
||||||
// convert lon/lat to tile coordinates
|
|
||||||
const auto px = std::round(((px_merc - tile_bbox.minx) * tile_extent /
|
|
||||||
16.0 / tile_bbox.width()) *
|
|
||||||
tile_extent / 256.0);
|
|
||||||
const auto py = std::round(((tile_bbox.maxy - py_merc) * tile_extent /
|
|
||||||
16.0 / tile_bbox.height()) *
|
|
||||||
tile_extent / 256.0);
|
|
||||||
tile_line.emplace_back(px, py);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Here, we save the two attributes for our feature: the speed and the
|
|
||||||
// is_small
|
|
||||||
// boolean. We onl serve up speeds from 0-139, so all we do is save the
|
|
||||||
// first
|
|
||||||
protozero::pbf_writer feature_writer(layer_writer, 2);
|
|
||||||
// Field 3 is the "geometry type" field. Value 2 is "line"
|
|
||||||
feature_writer.add_enum(3, 2); // geometry type
|
|
||||||
// Field 1 for the feature is the "id" field.
|
|
||||||
feature_writer.add_uint64(1, id++); // id
|
|
||||||
{
|
|
||||||
// When adding attributes to a feature, we have to write
|
|
||||||
// pairs of numbers. The first value is the index in the
|
|
||||||
// keys array (written later), and the second value is the
|
|
||||||
// index into the "values" array (also written later). We're
|
|
||||||
// not writing the actual speed or bool value here, we're saving
|
|
||||||
// an index into the "values" array. This means many features
|
|
||||||
// can share the same value data, leading to smaller tiles.
|
|
||||||
protozero::packed_field_uint32 field(feature_writer, 2);
|
|
||||||
|
|
||||||
field.add_element(0); // "speed" tag key offset
|
|
||||||
field.add_element(
|
|
||||||
std::min(speed, 127u)); // save the speed value, capped at 127
|
|
||||||
field.add_element(1); // "is_small" tag key offset
|
|
||||||
field.add_element(edge.component.is_tiny ? 0 : 1); // is_small feature
|
|
||||||
}
|
|
||||||
{
|
|
||||||
// Encode the geometry for the feature
|
|
||||||
protozero::packed_field_uint32 geometry(feature_writer, 4);
|
|
||||||
encode_linestring(tile_line, geometry, start_x, start_y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Repeat the above for the coordinates reversed and using the `reverse`
|
|
||||||
// properties
|
|
||||||
if (reverse_weight != 0 &&
|
|
||||||
edge.reverse_edge_based_node_id != SPECIAL_NODEID)
|
|
||||||
{
|
|
||||||
std::int32_t start_x = 0;
|
|
||||||
std::int32_t start_y = 0;
|
|
||||||
|
|
||||||
line_typed geo_line;
|
|
||||||
geo_line.emplace_back(b.lon / COORDINATE_PRECISION,
|
|
||||||
b.lat / COORDINATE_PRECISION);
|
|
||||||
geo_line.emplace_back(a.lon / COORDINATE_PRECISION,
|
|
||||||
a.lat / COORDINATE_PRECISION);
|
|
||||||
|
|
||||||
const auto speed = static_cast<const std::uint32_t>(
|
|
||||||
round(length / reverse_weight * 10 * 3.6));
|
|
||||||
|
|
||||||
line_type tile_line;
|
|
||||||
for (auto const &pt : geo_line)
|
|
||||||
{
|
|
||||||
double px_merc = pt.x;
|
|
||||||
double py_merc = pt.y;
|
|
||||||
lonlat2merc(px_merc, py_merc);
|
|
||||||
// convert to integer tile coordinat
|
|
||||||
const auto px = std::round(((px_merc - tile_bbox.minx) * tile_extent /
|
|
||||||
16.0 / tile_bbox.width()) *
|
|
||||||
tile_extent / 256.0);
|
|
||||||
const auto py = std::round(((tile_bbox.maxy - py_merc) * tile_extent /
|
|
||||||
16.0 / tile_bbox.height()) *
|
|
||||||
tile_extent / 256.0);
|
|
||||||
tile_line.emplace_back(px, py);
|
|
||||||
}
|
|
||||||
|
|
||||||
protozero::pbf_writer feature_writer(layer_writer, 2);
|
|
||||||
feature_writer.add_enum(3, 2); // geometry type
|
|
||||||
feature_writer.add_uint64(1, id++); // id
|
|
||||||
{
|
|
||||||
protozero::packed_field_uint32 field(feature_writer, 2);
|
|
||||||
field.add_element(0); // "speed" tag key offset
|
|
||||||
field.add_element(
|
|
||||||
std::min(speed, 127u)); // save the speed value, capped at 127
|
|
||||||
field.add_element(1); // "is_small" tag key offset
|
|
||||||
field.add_element(edge.component.is_tiny ? 0 : 1); // is_small feature
|
|
||||||
}
|
|
||||||
{
|
|
||||||
protozero::packed_field_uint32 geometry(feature_writer, 4);
|
|
||||||
encode_linestring(tile_line, geometry, start_x, start_y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Field id 3 is the "keys" attribute
|
|
||||||
// We need two "key" fields, these are referred to with 0 and 1 (their array indexes)
|
|
||||||
// earlier
|
|
||||||
layer_writer.add_string(3, "speed");
|
|
||||||
layer_writer.add_string(3, "is_small");
|
|
||||||
|
|
||||||
// Now, we write out the possible speed value arrays and possible is_tiny
|
|
||||||
// values. Field type 4 is the "values" field. It's a variable type field,
|
|
||||||
// so requires a two-step write (create the field, then write its value)
|
|
||||||
for (std::size_t i = 0; i < 128; i++)
|
|
||||||
{
|
|
||||||
{
|
|
||||||
// Writing field type 4 == variant type
|
|
||||||
protozero::pbf_writer values_writer(layer_writer, 4);
|
|
||||||
// Attribute value 5 == uin64 type
|
|
||||||
values_writer.add_uint64(5, i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{
|
|
||||||
protozero::pbf_writer values_writer(layer_writer, 4);
|
|
||||||
// Attribute value 7 == bool type
|
|
||||||
values_writer.add_bool(7, true);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
protozero::pbf_writer values_writer(layer_writer, 4);
|
|
||||||
// Attribute value 7 == bool type
|
|
||||||
values_writer.add_bool(7, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encode the PBF result as a special Buffer object on the response.
|
|
||||||
// This will allow downstream consumers to handle this type differently
|
|
||||||
// to the String type.
|
|
||||||
json_result.values["pbf"] = osrm::util::json::Buffer(buffer);
|
|
||||||
|
|
||||||
return Status::Ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
DataFacadeT *const facade;
|
|
||||||
const std::string descriptor_string;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
//#include "engine/plugins/timestamp.hpp"
|
//#include "engine/plugins/timestamp.hpp"
|
||||||
#include "engine/plugins/trip.hpp"
|
#include "engine/plugins/trip.hpp"
|
||||||
#include "engine/plugins/viaroute.hpp"
|
#include "engine/plugins/viaroute.hpp"
|
||||||
//#include "engine/plugins/tile.hpp"
|
#include "engine/plugins/tile.hpp"
|
||||||
#include "engine/plugins/match.hpp"
|
#include "engine/plugins/match.hpp"
|
||||||
|
|
||||||
#include "engine/datafacade/datafacade_base.hpp"
|
#include "engine/datafacade/datafacade_base.hpp"
|
||||||
@ -87,16 +87,16 @@ namespace
|
|||||||
{
|
{
|
||||||
// Abstracted away the query locking into a template function
|
// Abstracted away the query locking into a template function
|
||||||
// Works the same for every plugin.
|
// Works the same for every plugin.
|
||||||
template <typename ParameterT, typename PluginT>
|
template <typename ParameterT, typename PluginT, typename ResultT>
|
||||||
osrm::engine::Status RunQuery(const std::unique_ptr<osrm::engine::Engine::EngineLock> &lock,
|
osrm::engine::Status RunQuery(const std::unique_ptr<osrm::engine::Engine::EngineLock> &lock,
|
||||||
osrm::engine::datafacade::BaseDataFacade &facade,
|
osrm::engine::datafacade::BaseDataFacade &facade,
|
||||||
const ParameterT ¶meters,
|
const ParameterT ¶meters,
|
||||||
PluginT &plugin,
|
PluginT &plugin,
|
||||||
osrm::util::json::Object &json_result)
|
ResultT &result)
|
||||||
{
|
{
|
||||||
if (!lock)
|
if (!lock)
|
||||||
{
|
{
|
||||||
return plugin.HandleRequest(parameters, json_result);
|
return plugin.HandleRequest(parameters, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_ASSERT(lock);
|
BOOST_ASSERT(lock);
|
||||||
@ -108,7 +108,7 @@ osrm::engine::Status RunQuery(const std::unique_ptr<osrm::engine::Engine::Engine
|
|||||||
// things while the query is running
|
// things while the query is running
|
||||||
boost::shared_lock<boost::shared_mutex> data_lock{shared_facade.data_mutex};
|
boost::shared_lock<boost::shared_mutex> data_lock{shared_facade.data_mutex};
|
||||||
|
|
||||||
osrm::engine::Status status = plugin.HandleRequest(parameters, json_result);
|
osrm::engine::Status status = plugin.HandleRequest(parameters, result);
|
||||||
|
|
||||||
lock->DecreaseQueryCount();
|
lock->DecreaseQueryCount();
|
||||||
return status;
|
return status;
|
||||||
@ -148,6 +148,7 @@ Engine::Engine(EngineConfig &config)
|
|||||||
nearest_plugin = create<NearestPlugin>(*query_data_facade);
|
nearest_plugin = create<NearestPlugin>(*query_data_facade);
|
||||||
trip_plugin = create<TripPlugin>(*query_data_facade, config.max_locations_trip);
|
trip_plugin = create<TripPlugin>(*query_data_facade, config.max_locations_trip);
|
||||||
match_plugin = create<MatchPlugin>(*query_data_facade, config.max_locations_map_matching);
|
match_plugin = create<MatchPlugin>(*query_data_facade, config.max_locations_map_matching);
|
||||||
|
tile_plugin = create<TilePlugin>(*query_data_facade);
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure we deallocate the unique ptr at a position where we know the size of the plugins
|
// make sure we deallocate the unique ptr at a position where we know the size of the plugins
|
||||||
@ -180,5 +181,10 @@ Status Engine::Match(const api::MatchParameters ¶ms, util::json::Object &res
|
|||||||
return RunQuery(lock, *query_data_facade, params, *match_plugin, result);
|
return RunQuery(lock, *query_data_facade, params, *match_plugin, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Status Engine::Tile(const api::TileParameters ¶ms, std::string &result)
|
||||||
|
{
|
||||||
|
return RunQuery(lock, *query_data_facade, params, *tile_plugin, result);
|
||||||
|
}
|
||||||
|
|
||||||
} // engine ns
|
} // engine ns
|
||||||
} // osrm ns
|
} // osrm ns
|
||||||
|
393
src/engine/plugins/tile.cpp
Normal file
393
src/engine/plugins/tile.cpp
Normal file
@ -0,0 +1,393 @@
|
|||||||
|
#include "engine/plugins/plugin_base.hpp"
|
||||||
|
#include "engine/plugins/tile.hpp"
|
||||||
|
|
||||||
|
#include <protozero/varint.hpp>
|
||||||
|
#include <protozero/pbf_writer.hpp>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace osrm
|
||||||
|
{
|
||||||
|
namespace engine
|
||||||
|
{
|
||||||
|
namespace plugins
|
||||||
|
{
|
||||||
|
|
||||||
|
// from mapnik/well_known_srs.hpp
|
||||||
|
const constexpr double EARTH_RADIUS = 6378137.0;
|
||||||
|
const constexpr double EARTH_DIAMETER = EARTH_RADIUS * 2.0;
|
||||||
|
const constexpr double EARTH_CIRCUMFERENCE = EARTH_DIAMETER * M_PI;
|
||||||
|
const constexpr double MAXEXTENT = EARTH_CIRCUMFERENCE / 2.0;
|
||||||
|
const constexpr double M_PI_by2 = M_PI / 2.0;
|
||||||
|
const constexpr double D2R = M_PI / 180.0;
|
||||||
|
const constexpr double R2D = 180.0 / M_PI;
|
||||||
|
const constexpr double M_PIby360 = M_PI / 360.0;
|
||||||
|
const constexpr double MAXEXTENTby180 = MAXEXTENT / 180.0;
|
||||||
|
const double MAX_LATITUDE = R2D * (2.0 * std::atan(std::exp(180.0 * D2R)) - M_PI_by2);
|
||||||
|
// ^ math functions are not constexpr since they have side-effects (setting errno) :(
|
||||||
|
|
||||||
|
// from mapnik-vector-tile
|
||||||
|
namespace detail_pbf
|
||||||
|
{
|
||||||
|
|
||||||
|
inline unsigned encode_length(const unsigned len) { return (len << 3u) | 2u; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converts a regular WSG84 lon/lat pair into
|
||||||
|
// a mercator coordinate
|
||||||
|
inline void lonlat2merc(double &x, double &y)
|
||||||
|
{
|
||||||
|
if (x > 180)
|
||||||
|
x = 180;
|
||||||
|
else if (x < -180)
|
||||||
|
x = -180;
|
||||||
|
if (y > MAX_LATITUDE)
|
||||||
|
y = MAX_LATITUDE;
|
||||||
|
else if (y < -MAX_LATITUDE)
|
||||||
|
y = -MAX_LATITUDE;
|
||||||
|
x = x * MAXEXTENTby180;
|
||||||
|
y = std::log(std::tan((90 + y) * M_PIby360)) * R2D;
|
||||||
|
y = y * MAXEXTENTby180;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is the global default tile size for all Mapbox Vector Tiles
|
||||||
|
const constexpr double tile_size_ = 256.0;
|
||||||
|
|
||||||
|
//
|
||||||
|
inline void from_pixels(const double shift, double &x, double &y)
|
||||||
|
{
|
||||||
|
const double b = shift / 2.0;
|
||||||
|
x = (x - b) / (shift / 360.0);
|
||||||
|
const double g = (y - b) / -(shift / (2 * M_PI));
|
||||||
|
y = R2D * (2.0 * std::atan(std::exp(g)) - M_PI_by2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converts a WMS tile coordinate (z,x,y) into a mercator bounding box
|
||||||
|
inline void xyz2mercator(
|
||||||
|
const int x, const int y, const int z, double &minx, double &miny, double &maxx, double &maxy)
|
||||||
|
{
|
||||||
|
minx = x * tile_size_;
|
||||||
|
miny = (y + 1.0) * tile_size_;
|
||||||
|
maxx = (x + 1.0) * tile_size_;
|
||||||
|
maxy = y * tile_size_;
|
||||||
|
const double shift = std::pow(2.0, z) * tile_size_;
|
||||||
|
from_pixels(shift, minx, miny);
|
||||||
|
from_pixels(shift, maxx, maxy);
|
||||||
|
lonlat2merc(minx, miny);
|
||||||
|
lonlat2merc(maxx, maxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converts a WMS tile coordinate (z,x,y) into a wsg84 bounding box
|
||||||
|
inline void xyz2wsg84(
|
||||||
|
const int x, const int y, const int z, double &minx, double &miny, double &maxx, double &maxy)
|
||||||
|
{
|
||||||
|
minx = x * tile_size_;
|
||||||
|
miny = (y + 1.0) * tile_size_;
|
||||||
|
maxx = (x + 1.0) * tile_size_;
|
||||||
|
maxy = y * tile_size_;
|
||||||
|
const double shift = std::pow(2.0, z) * tile_size_;
|
||||||
|
from_pixels(shift, minx, miny);
|
||||||
|
from_pixels(shift, maxx, maxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
// emulates mapbox::box2d, just a simple container for
|
||||||
|
// a box
|
||||||
|
struct bbox final
|
||||||
|
{
|
||||||
|
bbox(const double _minx, const double _miny, const double _maxx, const double _maxy)
|
||||||
|
: minx(_minx), miny(_miny), maxx(_maxx), maxy(_maxy)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
double width() const { return maxx - minx; }
|
||||||
|
double height() const { return maxy - miny; }
|
||||||
|
|
||||||
|
const double minx;
|
||||||
|
const double miny;
|
||||||
|
const double maxx;
|
||||||
|
const double maxy;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Simple container class for WSG84 coordinates
|
||||||
|
struct point_type_d final
|
||||||
|
{
|
||||||
|
point_type_d(double _x, double _y) : x(_x), y(_y) {}
|
||||||
|
|
||||||
|
const double x;
|
||||||
|
const double y;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Simple container for integer coordinates (i.e. pixel coords)
|
||||||
|
struct point_type_i final
|
||||||
|
{
|
||||||
|
point_type_i(std::int64_t _x, std::int64_t _y) : x(_x), y(_y) {}
|
||||||
|
|
||||||
|
const std::int64_t x;
|
||||||
|
const std::int64_t y;
|
||||||
|
};
|
||||||
|
|
||||||
|
using line_type = std::vector<point_type_i>;
|
||||||
|
using line_typed = std::vector<point_type_d>;
|
||||||
|
|
||||||
|
// from mapnik-vector-tile
|
||||||
|
// Encodes a linestring using protobuf zigzag encoding
|
||||||
|
inline bool encode_linestring(line_type line,
|
||||||
|
protozero::packed_field_uint32 &geometry,
|
||||||
|
std::int32_t &start_x,
|
||||||
|
std::int32_t &start_y)
|
||||||
|
{
|
||||||
|
const std::size_t line_size = line.size();
|
||||||
|
if (line_size < 2)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const unsigned line_to_length = static_cast<const unsigned>(line_size) - 1;
|
||||||
|
|
||||||
|
auto pt = line.begin();
|
||||||
|
geometry.add_element(9); // move_to | (1 << 3)
|
||||||
|
geometry.add_element(protozero::encode_zigzag32(pt->x - start_x));
|
||||||
|
geometry.add_element(protozero::encode_zigzag32(pt->y - start_y));
|
||||||
|
start_x = pt->x;
|
||||||
|
start_y = pt->y;
|
||||||
|
geometry.add_element(detail_pbf::encode_length(line_to_length));
|
||||||
|
for (++pt; pt != line.end(); ++pt)
|
||||||
|
{
|
||||||
|
const std::int32_t dx = pt->x - start_x;
|
||||||
|
const std::int32_t dy = pt->y - start_y;
|
||||||
|
geometry.add_element(protozero::encode_zigzag32(dx));
|
||||||
|
geometry.add_element(protozero::encode_zigzag32(dy));
|
||||||
|
start_x = pt->x;
|
||||||
|
start_y = pt->y;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Status TilePlugin::HandleRequest(const api::TileParameters ¶meters, std::string &pbf_buffer)
|
||||||
|
{
|
||||||
|
|
||||||
|
// Vector tiles are 4096 virtual pixels on each side
|
||||||
|
const double tile_extent = 4096.0;
|
||||||
|
double min_lon, min_lat, max_lon, max_lat;
|
||||||
|
|
||||||
|
// Convert the z,x,y mercator tile coordinates into WSG84 lon/lat values
|
||||||
|
xyz2wsg84(parameters.x, parameters.y, parameters.z, min_lon, min_lat, max_lon, max_lat);
|
||||||
|
|
||||||
|
util::Coordinate southwest{util::FloatLongitude(min_lon), util::FloatLatitude(min_lat)};
|
||||||
|
util::Coordinate northeast{util::FloatLongitude(max_lon), util::FloatLatitude(max_lat)};
|
||||||
|
|
||||||
|
// Fetch all the segments that are in our bounding box.
|
||||||
|
// This hits the OSRM StaticRTree
|
||||||
|
const auto edges = facade.GetEdgesInBox(southwest, northeast);
|
||||||
|
|
||||||
|
// TODO: extract speed values for compressed and uncompressed geometries
|
||||||
|
|
||||||
|
// Convert tile coordinates into mercator coordinates
|
||||||
|
xyz2mercator(parameters.x, parameters.y, parameters.z, min_lon, min_lat, max_lon, max_lat);
|
||||||
|
const bbox tile_bbox{min_lon, min_lat, max_lon, max_lat};
|
||||||
|
|
||||||
|
// Protobuf serialized blocks when objects go out of scope, hence
|
||||||
|
// the extra scoping below.
|
||||||
|
protozero::pbf_writer tile_writer{pbf_buffer};
|
||||||
|
{
|
||||||
|
// Add a layer object to the PBF stream. 3=='layer' from the vector tile spec (2.1)
|
||||||
|
protozero::pbf_writer layer_writer(tile_writer, 3);
|
||||||
|
// TODO: don't write a layer if there are no features
|
||||||
|
|
||||||
|
layer_writer.add_uint32(15, 2); // version
|
||||||
|
// Field 1 is the "layer name" field, it's a string
|
||||||
|
layer_writer.add_string(1, "speeds"); // name
|
||||||
|
// Field 5 is the tile extent. It's a uint32 and should be set to 4096
|
||||||
|
// for normal vector tiles.
|
||||||
|
layer_writer.add_uint32(5, 4096); // extent
|
||||||
|
|
||||||
|
// Begin the layer features block
|
||||||
|
{
|
||||||
|
// Each feature gets a unique id, starting at 1
|
||||||
|
unsigned id = 1;
|
||||||
|
for (const auto &edge : edges)
|
||||||
|
{
|
||||||
|
// Get coordinates for start/end nodes of segmet (NodeIDs u and v)
|
||||||
|
const auto a = facade.GetCoordinateOfNode(edge.u);
|
||||||
|
const auto b = facade.GetCoordinateOfNode(edge.v);
|
||||||
|
// Calculate the length in meters
|
||||||
|
const double length = osrm::util::coordinate_calculation::haversineDistance(a, b);
|
||||||
|
|
||||||
|
int forward_weight = 0;
|
||||||
|
int reverse_weight = 0;
|
||||||
|
|
||||||
|
if (edge.forward_packed_geometry_id != SPECIAL_EDGEID)
|
||||||
|
{
|
||||||
|
std::vector<EdgeWeight> forward_weight_vector;
|
||||||
|
facade.GetUncompressedWeights(edge.forward_packed_geometry_id,
|
||||||
|
forward_weight_vector);
|
||||||
|
forward_weight = forward_weight_vector[edge.fwd_segment_position];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (edge.reverse_packed_geometry_id != SPECIAL_EDGEID)
|
||||||
|
{
|
||||||
|
std::vector<EdgeWeight> reverse_weight_vector;
|
||||||
|
facade.GetUncompressedWeights(edge.reverse_packed_geometry_id,
|
||||||
|
reverse_weight_vector);
|
||||||
|
|
||||||
|
BOOST_ASSERT(edge.fwd_segment_position < reverse_weight_vector.size());
|
||||||
|
|
||||||
|
reverse_weight = reverse_weight_vector[reverse_weight_vector.size() -
|
||||||
|
edge.fwd_segment_position - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this is a valid forward edge, go ahead and add it to the tile
|
||||||
|
if (forward_weight != 0 && edge.forward_edge_based_node_id != SPECIAL_NODEID)
|
||||||
|
{
|
||||||
|
std::int32_t start_x = 0;
|
||||||
|
std::int32_t start_y = 0;
|
||||||
|
|
||||||
|
line_typed geo_line;
|
||||||
|
geo_line.emplace_back(static_cast<double>(util::toFloating(a.lon)),
|
||||||
|
static_cast<double>(util::toFloating(a.lat)));
|
||||||
|
geo_line.emplace_back(static_cast<double>(util::toFloating(b.lon)),
|
||||||
|
static_cast<double>(util::toFloating(b.lat)));
|
||||||
|
|
||||||
|
// Calculate the speed for this line
|
||||||
|
std::uint32_t speed =
|
||||||
|
static_cast<std::uint32_t>(round(length / forward_weight * 10 * 3.6));
|
||||||
|
|
||||||
|
line_type tile_line;
|
||||||
|
for (auto const &pt : geo_line)
|
||||||
|
{
|
||||||
|
double px_merc = pt.x;
|
||||||
|
double py_merc = pt.y;
|
||||||
|
lonlat2merc(px_merc, py_merc);
|
||||||
|
// convert lon/lat to tile coordinates
|
||||||
|
const auto px = std::round(
|
||||||
|
((px_merc - tile_bbox.minx) * tile_extent / 16.0 / tile_bbox.width()) *
|
||||||
|
tile_extent / 256.0);
|
||||||
|
const auto py = std::round(
|
||||||
|
((tile_bbox.maxy - py_merc) * tile_extent / 16.0 / tile_bbox.height()) *
|
||||||
|
tile_extent / 256.0);
|
||||||
|
tile_line.emplace_back(px, py);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Here, we save the two attributes for our feature: the speed and the
|
||||||
|
// is_small
|
||||||
|
// boolean. We onl serve up speeds from 0-139, so all we do is save the
|
||||||
|
// first
|
||||||
|
protozero::pbf_writer feature_writer(layer_writer, 2);
|
||||||
|
// Field 3 is the "geometry type" field. Value 2 is "line"
|
||||||
|
feature_writer.add_enum(3, 2); // geometry type
|
||||||
|
// Field 1 for the feature is the "id" field.
|
||||||
|
feature_writer.add_uint64(1, id++); // id
|
||||||
|
{
|
||||||
|
// When adding attributes to a feature, we have to write
|
||||||
|
// pairs of numbers. The first value is the index in the
|
||||||
|
// keys array (written later), and the second value is the
|
||||||
|
// index into the "values" array (also written later). We're
|
||||||
|
// not writing the actual speed or bool value here, we're saving
|
||||||
|
// an index into the "values" array. This means many features
|
||||||
|
// can share the same value data, leading to smaller tiles.
|
||||||
|
protozero::packed_field_uint32 field(feature_writer, 2);
|
||||||
|
|
||||||
|
field.add_element(0); // "speed" tag key offset
|
||||||
|
field.add_element(
|
||||||
|
std::min(speed, 127u)); // save the speed value, capped at 127
|
||||||
|
field.add_element(1); // "is_small" tag key offset
|
||||||
|
field.add_element(edge.component.is_tiny ? 0 : 1); // is_small feature
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Encode the geometry for the feature
|
||||||
|
protozero::packed_field_uint32 geometry(feature_writer, 4);
|
||||||
|
encode_linestring(tile_line, geometry, start_x, start_y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Repeat the above for the coordinates reversed and using the `reverse`
|
||||||
|
// properties
|
||||||
|
if (reverse_weight != 0 && edge.reverse_edge_based_node_id != SPECIAL_NODEID)
|
||||||
|
{
|
||||||
|
std::int32_t start_x = 0;
|
||||||
|
std::int32_t start_y = 0;
|
||||||
|
|
||||||
|
line_typed geo_line;
|
||||||
|
geo_line.emplace_back(static_cast<double>(util::toFloating(b.lon)),
|
||||||
|
static_cast<double>(util::toFloating(b.lat)));
|
||||||
|
geo_line.emplace_back(static_cast<double>(util::toFloating(a.lon)),
|
||||||
|
static_cast<double>(util::toFloating(a.lat)));
|
||||||
|
|
||||||
|
const auto speed =
|
||||||
|
static_cast<const std::uint32_t>(round(length / reverse_weight * 10 * 3.6));
|
||||||
|
|
||||||
|
line_type tile_line;
|
||||||
|
for (auto const &pt : geo_line)
|
||||||
|
{
|
||||||
|
double px_merc = pt.x;
|
||||||
|
double py_merc = pt.y;
|
||||||
|
lonlat2merc(px_merc, py_merc);
|
||||||
|
// convert to integer tile coordinat
|
||||||
|
const auto px = std::round(
|
||||||
|
((px_merc - tile_bbox.minx) * tile_extent / 16.0 / tile_bbox.width()) *
|
||||||
|
tile_extent / 256.0);
|
||||||
|
const auto py = std::round(
|
||||||
|
((tile_bbox.maxy - py_merc) * tile_extent / 16.0 / tile_bbox.height()) *
|
||||||
|
tile_extent / 256.0);
|
||||||
|
tile_line.emplace_back(px, py);
|
||||||
|
}
|
||||||
|
|
||||||
|
protozero::pbf_writer feature_writer(layer_writer, 2);
|
||||||
|
feature_writer.add_enum(3, 2); // geometry type
|
||||||
|
feature_writer.add_uint64(1, id++); // id
|
||||||
|
{
|
||||||
|
protozero::packed_field_uint32 field(feature_writer, 2);
|
||||||
|
field.add_element(0); // "speed" tag key offset
|
||||||
|
field.add_element(
|
||||||
|
std::min(speed, 127u)); // save the speed value, capped at 127
|
||||||
|
field.add_element(1); // "is_small" tag key offset
|
||||||
|
field.add_element(edge.component.is_tiny ? 0 : 1); // is_small feature
|
||||||
|
}
|
||||||
|
{
|
||||||
|
protozero::packed_field_uint32 geometry(feature_writer, 4);
|
||||||
|
encode_linestring(tile_line, geometry, start_x, start_y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Field id 3 is the "keys" attribute
|
||||||
|
// We need two "key" fields, these are referred to with 0 and 1 (their array indexes)
|
||||||
|
// earlier
|
||||||
|
layer_writer.add_string(3, "speed");
|
||||||
|
layer_writer.add_string(3, "is_small");
|
||||||
|
|
||||||
|
// Now, we write out the possible speed value arrays and possible is_tiny
|
||||||
|
// values. Field type 4 is the "values" field. It's a variable type field,
|
||||||
|
// so requires a two-step write (create the field, then write its value)
|
||||||
|
for (std::size_t i = 0; i < 128; i++)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
// Writing field type 4 == variant type
|
||||||
|
protozero::pbf_writer values_writer(layer_writer, 4);
|
||||||
|
// Attribute value 5 == uin64 type
|
||||||
|
values_writer.add_uint64(5, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
protozero::pbf_writer values_writer(layer_writer, 4);
|
||||||
|
// Attribute value 7 == bool type
|
||||||
|
values_writer.add_bool(7, true);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
protozero::pbf_writer values_writer(layer_writer, 4);
|
||||||
|
// Attribute value 7 == bool type
|
||||||
|
values_writer.add_bool(7, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Status::Ok;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user