#ifndef TILEPLUGIN_HPP #define TILEPLUGIN_HPP #include "engine/plugins/plugin_base.hpp" #include "osrm/json_container.hpp" #include "util/tile_bbox.hpp" #include #include #include #include /* * 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 static const double EARTH_RADIUS = 6378137.0; static const double EARTH_DIAMETER = EARTH_RADIUS * 2.0; static const double EARTH_CIRCUMFERENCE = EARTH_DIAMETER * M_PI; static const double MAXEXTENT = EARTH_CIRCUMFERENCE / 2.0; static const double M_PI_by2 = M_PI / 2; static const double D2R = M_PI / 180; static const double R2D = 180 / M_PI; static const double M_PIby360 = M_PI / 360; static const double MAXEXTENTby180 = MAXEXTENT / 180; static const double MAX_LATITUDE = R2D * (2 * std::atan(std::exp(180 * D2R)) - M_PI_by2); // from mapnik-vector-tile namespace detail_pbf { inline unsigned encode_length(unsigned len) { return (len << 3u) | 2u; } } 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; } const static int tile_size_ = 256; void from_pixels(double shift, double & x, double & y) { double b = shift/2.0; x = (x - b)/(shift/360.0); double g = (y - b)/-(shift/(2 * M_PI)); y = R2D * (2.0 * std::atan(std::exp(g)) - M_PI_by2); } void xyz(int x, int y, 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_; 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); } void xyz2wsg84(int x, int y, 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_; double shift = std::pow(2.0,z) * tile_size_; from_pixels(shift,minx,miny); from_pixels(shift,maxx,maxy); } // emulates mapbox::box2d class bbox { public: double minx; double miny; double maxx; double maxy; bbox(double _minx,double _miny,double _maxx,double _maxy) : minx(_minx), miny(_miny), maxx(_maxx), maxy(_maxy) { } double width() const { return maxx - minx; } double height() const { return maxy - miny; } }; // should start using core geometry class across mapnik, osrm, mapbox-gl-native class point_type_d { public: double x; double y; point_type_d(double _x, double _y) : x(_x), y(_y) { } }; class point_type_i { public: std::int64_t x; std::int64_t y; point_type_i(std::int64_t _x, std::int64_t _y) : x(_x), y(_y) { } }; using line_type = std::vector; using line_typed = std::vector; // from mapnik-vector-tile inline bool encode_linestring(line_type line, protozero::packed_field_uint32 & geometry, int32_t & start_x, int32_t & start_y) { std::size_t line_size = line.size(); //line_size -= detail_pbf::repeated_point_count(line); if (line_size < 2) { return false; } 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) { int32_t dx = pt->x - start_x; 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 { 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 { const unsigned tile_extent = 4096; double min_lon, min_lat, max_lon, max_lat; 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) }; auto edges = facade->GetEdgesInBox(southwest, northeast); xyz(route_parameters.x, route_parameters.y, route_parameters.z, min_lon, min_lat, max_lon, max_lat); bbox tile_bbox(min_lon, min_lat, max_lon, max_lat); std::string buffer; protozero::pbf_writer tile_writer(buffer); { 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 layer_writer.add_string(1,"speeds"); // name layer_writer.add_uint32(5,4096); // extent std::vector speeds; std::vector is_smalls; { unsigned id = 1; for (const auto & edge : edges) { const auto a = facade->GetCoordinateOfNode(edge.u); const auto b = facade->GetCoordinateOfNode(edge.v); double length = osrm::util::coordinate_calculation::haversineDistance( a.lon, a.lat, b.lon, b.lat ); if (edge.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); double speed = round(length / edge.forward_weight * 10 ) * 3.6; speeds.push_back(speed); is_smalls.push_back(edge.component.is_tiny); 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 std::int64_t px = std::round((px_merc - tile_bbox.minx) * tile_extent/16 / tile_bbox.width()); std::int64_t py = std::round((tile_bbox.maxy - py_merc) * tile_extent/16 / tile_bbox.height()); tile_line.emplace_back(px*tile_extent/256,py*tile_extent/256); } 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((speeds.size()-1)*2); // "speed" tag value offset field.add_element(1); // "is_small" tag key offset field.add_element((is_smalls.size()-1)*2+1); // "is_small" tag value offset } { protozero::packed_field_uint32 geometry(feature_writer,4); encode_linestring(tile_line,geometry,start_x,start_y); } } if (edge.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); double speed = round(length / edge.reverse_weight * 10 ) * 3.6; speeds.push_back(speed); is_smalls.push_back(edge.component.is_tiny); 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 std::int64_t px = std::round((px_merc - tile_bbox.minx) * tile_extent/16 / tile_bbox.width()); std::int64_t py = std::round((tile_bbox.maxy - py_merc) * tile_extent/16 / tile_bbox.height()); tile_line.emplace_back(px*tile_extent/256,py*tile_extent/256); } 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((speeds.size()-1)*2); // "speed" tag value offset field.add_element(1); // "is_small" tag key offset field.add_element((is_smalls.size()-1)*2+1); // "is_small" tag value offset } { protozero::packed_field_uint32 geometry(feature_writer,4); encode_linestring(tile_line,geometry,start_x,start_y); } } } } layer_writer.add_string(3,"speed"); layer_writer.add_string(3,"is_small"); for (size_t i=0; i