rather than being cached in the StaticRTree. This means we can freely apply traffic data and not have stale values lying around. It reduces the size of the RTree on disk, at the expense of some additional data in RAM.
437 lines
18 KiB
C++
437 lines
18 KiB
C++
#ifndef TILEPLUGIN_HPP
|
|
#define TILEPLUGIN_HPP
|
|
|
|
#include "engine/plugins/plugin_base.hpp"
|
|
#include "osrm/json_container.hpp"
|
|
|
|
#include <protozero/varint.hpp>
|
|
#include <protozero/pbf_writer.hpp>
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
#include <utility>
|
|
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
|
|
/*
|
|
* This plugin generates Mapbox Vector tiles that show the internal
|
|
* routing geometry and speed values on all road segments.
|
|
* You can use this along with a vector-tile viewer, like Mapbox GL,
|
|
* to display maps that show the exact road network that
|
|
* OSRM is routing. This is very useful for debugging routing
|
|
* errors
|
|
*/
|
|
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();
|
|
// 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:
|
|
explicit TilePlugin(DataFacadeT *facade) : facade(facade), descriptor_string("tile") {}
|
|
|
|
const std::string GetDescriptor() const override final { return descriptor_string; }
|
|
|
|
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;
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif /* TILEPLUGIN_HPP */
|