diff --git a/include/engine/api/tile_parameters.hpp b/include/engine/api/tile_parameters.hpp new file mode 100644 index 000000000..5b82debcf --- /dev/null +++ b/include/engine/api/tile_parameters.hpp @@ -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 diff --git a/include/engine/engine.hpp b/include/engine/engine.hpp index 374e5b5de..027320ba3 100644 --- a/include/engine/engine.hpp +++ b/include/engine/engine.hpp @@ -31,6 +31,7 @@ struct TableParameters; struct NearestParameters; struct TripParameters; struct MatchParameters; +struct TileParameters; } namespace plugins { @@ -39,6 +40,7 @@ class TablePlugin; class NearestPlugin; class TripPlugin; class MatchPlugin; +class TilePlugin; } // End fwd decls @@ -66,6 +68,7 @@ class Engine final Status Nearest(const api::NearestParameters ¶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 Tile(const api::TileParameters ¶meters, std::string &result); private: std::unique_ptr lock; @@ -75,6 +78,7 @@ class Engine final std::unique_ptr nearest_plugin; std::unique_ptr trip_plugin; std::unique_ptr match_plugin; + std::unique_ptr tile_plugin; std::unique_ptr query_data_facade; }; diff --git a/include/engine/plugins/tile.hpp b/include/engine/plugins/tile.hpp index acaea5ae1..77ba357ac 100644 --- a/include/engine/plugins/tile.hpp +++ b/include/engine/plugins/tile.hpp @@ -2,17 +2,9 @@ #define TILEPLUGIN_HPP #include "engine/plugins/plugin_base.hpp" -#include "osrm/json_container.hpp" - -#include -#include +#include "engine/api/tile_parameters.hpp" #include -#include -#include - -#include -#include /* * This plugin generates Mapbox Vector tiles that show the internal @@ -29,405 +21,12 @@ 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; -using line_typed = std::vector; - -// 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(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 TilePlugin final : public BasePlugin +class TilePlugin final : public BasePlugin { 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 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(min_lat * COORDINATE_PRECISION), - static_cast(min_lon * COORDINATE_PRECISION)}; - FixedPointCoordinate northeast{static_cast(max_lat * COORDINATE_PRECISION), - static_cast(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 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 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( - 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( - 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; + Status HandleRequest(const api::TileParameters ¶meters, std::string &pbf_buffer); }; } } diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 39fa58e57..a75ec5030 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -9,7 +9,7 @@ //#include "engine/plugins/timestamp.hpp" #include "engine/plugins/trip.hpp" #include "engine/plugins/viaroute.hpp" -//#include "engine/plugins/tile.hpp" +#include "engine/plugins/tile.hpp" #include "engine/plugins/match.hpp" #include "engine/datafacade/datafacade_base.hpp" @@ -87,16 +87,16 @@ namespace { // Abstracted away the query locking into a template function // Works the same for every plugin. -template +template osrm::engine::Status RunQuery(const std::unique_ptr &lock, osrm::engine::datafacade::BaseDataFacade &facade, const ParameterT ¶meters, PluginT &plugin, - osrm::util::json::Object &json_result) + ResultT &result) { if (!lock) { - return plugin.HandleRequest(parameters, json_result); + return plugin.HandleRequest(parameters, result); } BOOST_ASSERT(lock); @@ -108,7 +108,7 @@ osrm::engine::Status RunQuery(const std::unique_ptr 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(); return status; @@ -148,6 +148,7 @@ Engine::Engine(EngineConfig &config) nearest_plugin = create(*query_data_facade); trip_plugin = create(*query_data_facade, config.max_locations_trip); match_plugin = create(*query_data_facade, config.max_locations_map_matching); + tile_plugin = create(*query_data_facade); } // 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); } +Status Engine::Tile(const api::TileParameters ¶ms, std::string &result) +{ + return RunQuery(lock, *query_data_facade, params, *tile_plugin, result); +} + } // engine ns } // osrm ns diff --git a/src/engine/plugins/tile.cpp b/src/engine/plugins/tile.cpp new file mode 100644 index 000000000..4db4008f7 --- /dev/null +++ b/src/engine/plugins/tile.cpp @@ -0,0 +1,393 @@ +#include "engine/plugins/plugin_base.hpp" +#include "engine/plugins/tile.hpp" + +#include +#include + +#include +#include +#include + +#include +#include + +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; +using line_typed = std::vector; + +// 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(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 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 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(util::toFloating(a.lon)), + static_cast(util::toFloating(a.lat))); + geo_line.emplace_back(static_cast(util::toFloating(b.lon)), + static_cast(util::toFloating(b.lat))); + + // Calculate the speed for this line + std::uint32_t speed = + static_cast(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(util::toFloating(b.lon)), + static_cast(util::toFloating(b.lat))); + geo_line.emplace_back(static_cast(util::toFloating(a.lon)), + static_cast(util::toFloating(a.lat))); + + const auto speed = + static_cast(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; +} +} +} +}