From e8ade13ccef2ad30f76c063eb5ac19eb26c30c2f Mon Sep 17 00:00:00 2001 From: VladiMihaylenko Date: Thu, 30 Aug 2018 15:01:26 +0300 Subject: [PATCH] [routing] Routing quality tool with mapbox support. --- routing/routing_quality/CMakeLists.txt | 5 + routing/routing_quality/mapbox/api.cpp | 176 ++++++++++++++++++ routing/routing_quality/mapbox/api.hpp | 51 +++++ routing/routing_quality/mapbox/types.hpp | 56 ++++++ .../routing_quality_tool/CMakeLists.txt | 38 ++++ .../parse_input_params.hpp | 73 ++++++++ .../routing_quality_tool.cpp | 82 ++++++++ 7 files changed, 481 insertions(+) create mode 100644 routing/routing_quality/mapbox/api.cpp create mode 100644 routing/routing_quality/mapbox/api.hpp create mode 100644 routing/routing_quality/mapbox/types.hpp create mode 100644 routing/routing_quality/routing_quality_tool/CMakeLists.txt create mode 100644 routing/routing_quality/routing_quality_tool/parse_input_params.hpp create mode 100644 routing/routing_quality/routing_quality_tool/routing_quality_tool.cpp diff --git a/routing/routing_quality/CMakeLists.txt b/routing/routing_quality/CMakeLists.txt index 16510ec74e..d6b374b113 100644 --- a/routing/routing_quality/CMakeLists.txt +++ b/routing/routing_quality/CMakeLists.txt @@ -2,6 +2,9 @@ project(routing_quality) set( SRC + mapbox/api.cpp + mapbox/api.hpp + mapbox/types.hpp utils.cpp utils.hpp waypoints.cpp @@ -32,6 +35,8 @@ omim_link_libraries( ${LIBZ} ) +add_subdirectory(routing_quality_tool) + omim_add_test_subdirectory(routing_quality_tests) link_qt5_core(${PROJECT_NAME}) diff --git a/routing/routing_quality/mapbox/api.cpp b/routing/routing_quality/mapbox/api.cpp new file mode 100644 index 0000000000..dba14ae6e3 --- /dev/null +++ b/routing/routing_quality/mapbox/api.cpp @@ -0,0 +1,176 @@ +#include "routing/routing_quality/mapbox/api.hpp" + +#include "routing/vehicle_mask.hpp" + +#include "coding/file_writer.hpp" +#include "coding/serdes_json.hpp" +#include "coding/url_encode.hpp" +#include "coding/writer.hpp" + +#include "platform/http_client.hpp" + +#include "geometry/latlon.hpp" + +#include + +using namespace std; +using namespace string_literals; + +namespace +{ +string const kBaseURL = "https://api.mapbox.com/"; +string const kDirectionsApiVersion = "v5"; +string const kStylesApiVersion = "v1"; + +string VehicleTypeToMapboxType(routing::VehicleType type) +{ + using routing::VehicleType; + + switch (type) + { + case VehicleType::Car: + return "mapbox/driving"s; + case VehicleType::Pedestrian: + return "mapbox/walking"s; + case VehicleType::Bicycle: + return "mapbox/cycling"s; + case VehicleType::Transit: + case VehicleType::Count: + CHECK(false, ()); + return ""s; + } + + CHECK_SWITCH(); +} + +string LatLonsToString(vector const & coords) +{ + ostringstream oss; + auto const size = coords.size(); + for (size_t i = 0; i < size; ++i) + { + auto const & ll = coords[i]; + oss << ll.lon << "," << ll.lat; + if (i + 1 != size) + oss << ";"; + } + + oss << ".json"; + return UrlEncode(oss.str()); +} +} // namespace + +namespace routing_quality +{ +namespace mapbox +{ +DirectionsResponse Api::MakeDirectionsRequest(RouteParams const & params) const +{ + CHECK(!m_accessToken.empty(), ()); + platform::HttpClient request(GetDirectionsURL(params)); + DirectionsResponse ret; + if (request.RunHttpRequest() && !request.WasRedirected() && request.ErrorCode() == 200) + { + auto const & response = request.ServerResponse(); + CHECK(!response.empty(), ()); + { + coding::DeserializerJson des(response); + des(ret); + } + } + else + { + CHECK(false, (request.ErrorCode(), request.ServerResponse())); + } + + return ret; +} + +void Api::DrawRoutes(Geometry const & mapboxRoute, Geometry const & mapsmeRoute, string const & snapshotPath) const +{ + CHECK(!m_accessToken.empty(), ()); + CHECK(!snapshotPath.empty(), ()); + + platform::HttpClient request(GetRoutesRepresentationURL(mapboxRoute, mapsmeRoute)); + if (request.RunHttpRequest() && !request.WasRedirected() && request.ErrorCode() == 200) + { + auto const & response = request.ServerResponse(); + FileWriter w(snapshotPath); + w.Write(response.data(), response.size()); + return; + } + + CHECK(false, ("Mapbox api error:", request.ErrorCode())); +} + +// static +vector Api::GenerateWaypointsBasedOn(DirectionsResponse response) +{ + auto & routes = response.m_routes; + CHECK(!routes.empty(), ()); + vector res; + res.reserve(routes.size()); + for (auto & r : routes) + { + auto & coords = r.m_geometry.m_coordinates; + // Leave at most 10 waypoints from mapbox route. + Api::Sieve(coords, 10 /* maxRemainingNumber */); + ostringstream oss; + oss << "{"; + auto const size = coords.size(); + for (size_t i = 0; i < size; ++i) + { + auto const & lonLat = coords[i]; + CHECK_EQUAL(lonLat.size(), 2, ()); + oss << "{" << lonLat[1] << ", " << lonLat[0] << "}"; + if (i + 1 != size) + oss << ",\n"; + } + + oss << "}"; + res.emplace_back(oss.str()); + } + + return res; +} + +string Api::GetDirectionsURL(RouteParams const & params) const +{ + ostringstream oss; + oss << kBaseURL << "directions/" << kDirectionsApiVersion << "/" << VehicleTypeToMapboxType(params.m_type) << "/"; + oss << LatLonsToString(params.m_waypoints) << "?"; + oss << "access_token=" << m_accessToken << "&"; + oss << "overview=simplified&" << "geometries=geojson"; + return oss.str(); +} + +string Api::GetRoutesRepresentationURL(Geometry const & mapboxRoute, Geometry const & mapsmeRoute) const +{ + using Sink = MemWriter; + + string mapboxData; + { + Sink sink(mapboxData); + coding::SerializerJson ser(sink); + ser(mapboxRoute); + } + + mapboxData = UrlEncode(mapboxData); + + string mapsmeData; + { + Sink sink(mapsmeData); + coding::SerializerJson ser(sink); + ser(mapsmeRoute); + } + + mapsmeData = UrlEncode(mapsmeData); + + ostringstream oss; + oss << kBaseURL << "styles/" << kStylesApiVersion << "/mapbox/streets-v10/static/"; + oss << "geojson(" << mapboxData << ")" + << ",geojson(" << mapsmeData << ")"<< "/auto/1000x1000?" << "access_token=" << m_accessToken; + return oss.str(); +} +} // namespace mapbox +} // namespace routing_quality diff --git a/routing/routing_quality/mapbox/api.hpp b/routing/routing_quality/mapbox/api.hpp new file mode 100644 index 0000000000..eeb740f261 --- /dev/null +++ b/routing/routing_quality/mapbox/api.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include "routing/routing_quality/mapbox/types.hpp" +#include "routing/routing_quality/utils.hpp" + +#include "base/assert.hpp" + +#include +#include +#include +#include + +namespace routing_quality +{ +namespace mapbox +{ +class Api +{ +public: + explicit Api(std::string const & accessToken) : m_accessToken(accessToken) {} + + DirectionsResponse MakeDirectionsRequest(RouteParams const & params) const; + void DrawRoutes(Geometry const & mapboxRoute, Geometry const & mapsmeRoute, std::string const & snapshotPath) const; + + static std::vector GenerateWaypointsBasedOn(DirectionsResponse response); + + template + static void Sieve(Container & cont, size_t maxRemainingNumber) + { + CHECK_GREATER(maxRemainingNumber, 0, ()); + auto const size = cont.size(); + if (size <= maxRemainingNumber) + return; + + Container res; + res.reserve(maxRemainingNumber); + auto const step = size / maxRemainingNumber; + for (size_t i = 0; i < size; i += step) + res.emplace_back(cont[i]); + + cont = std::move(res); + } + +private: + std::string GetDirectionsURL(RouteParams const & params) const; + std::string GetRoutesRepresentationURL(Geometry const & mapboxRoute, Geometry const & mapsmeRoute) const; + + std::string m_accessToken; +}; +} // namespace mapbox +} // namespace routing_quality diff --git a/routing/routing_quality/mapbox/types.hpp b/routing/routing_quality/mapbox/types.hpp new file mode 100644 index 0000000000..34231eea88 --- /dev/null +++ b/routing/routing_quality/mapbox/types.hpp @@ -0,0 +1,56 @@ +#pragma once + +#include "geometry/latlon.hpp" +#include "geometry/mercator.hpp" +#include "geometry/point2d.hpp" + +#include "base/visitor.hpp" + +#include +#include + +namespace routing_quality +{ +namespace mapbox +{ +using LonLat = std::vector; +using Coordinates = std::vector; + +struct Geometry +{ + DECLARE_VISITOR(visitor(m_coordinates, "coordinates"), + visitor(m_type, "type")) + + void Build(std::vector const & from) + { + m_coordinates.reserve(from.size()); + for (auto const & p : from) + { + auto const ll = MercatorBounds::ToLatLon(p); + m_coordinates.push_back({ll.lon, ll.lat}); + } + + m_type = "LineString"; + } + + Coordinates m_coordinates; + std::string m_type; +}; + +struct Route +{ + DECLARE_VISITOR(visitor(m_geometry, "geometry"), + visitor(m_duration, "duration")) + + Geometry m_geometry; + double m_duration = 0.0; +}; + +struct DirectionsResponse +{ + DECLARE_VISITOR(visitor(m_routes, "routes")) + + std::vector m_routes; +}; +} // namespace mapbox +} // namespace routing_quality diff --git a/routing/routing_quality/routing_quality_tool/CMakeLists.txt b/routing/routing_quality/routing_quality_tool/CMakeLists.txt new file mode 100644 index 0000000000..27d97aa36a --- /dev/null +++ b/routing/routing_quality/routing_quality_tool/CMakeLists.txt @@ -0,0 +1,38 @@ +project(routing_quality_tool) + +include_directories(${OMIM_ROOT}/3party/gflags/src) + +set( + SRC + parse_input_params.hpp + routing_quality_tool.cpp +) + +omim_add_executable(${PROJECT_NAME} ${SRC}) + +omim_link_libraries( + ${PROJECT_NAME} + routing_quality + generator + routing + traffic + routing_common + transit + storage + indexer + platform + mwm_diff + bsdiff + geometry + coding + base + icu + jansson + protobuf + stats_client + gflags + ${LIBZ} +) + +link_qt5_core(${PROJECT_NAME}) +link_qt5_network(${PROJECT_NAME}) diff --git a/routing/routing_quality/routing_quality_tool/parse_input_params.hpp b/routing/routing_quality/routing_quality_tool/parse_input_params.hpp new file mode 100644 index 0000000000..e2711f6cdd --- /dev/null +++ b/routing/routing_quality/routing_quality_tool/parse_input_params.hpp @@ -0,0 +1,73 @@ +#pragma once + +#include "routing/routing_quality/utils.hpp" + +#include "coding/file_reader.hpp" + +#include "geometry/mercator.hpp" + +#include "base/assert.hpp" +#include "base/string_utils.hpp" + +#include +#include +#include + +namespace routing_quality +{ +template +void SerializeLatLon(Iter begin, Iter end, Container & c) +{ + c.reserve(std::distance(begin, end)); + for (auto it = begin; it != end; ++it) + { + auto const & str = *it; + auto const coords = strings::Tokenize(str, ","); + CHECK_EQUAL(coords.size(), 2, ("Incorrect string", str)); + + double lat = 0.0; + double lon = 0.0; + CHECK(strings::to_double(coords[0], lat), ("Incorrect string", coords[0])); + CHECK(strings::to_double(coords[1], lon), ("Incorrect string", coords[1])); + CHECK(MercatorBounds::ValidLat(lat), ("Incorrect lat", lat)); + CHECK(MercatorBounds::ValidLon(lon), ("Incorrect lon", lon)); + + c.emplace_back(lat, lon); + } +} + +RouteParams SerializeRouteParamsFromFile(std::string const & routeParamsFilePath) +{ + FileReader r(routeParamsFilePath); + std::string data; + r.ReadAsString(data); + + auto const tokenized = strings::Tokenize(data, ";"); + auto const size = tokenized.size(); + CHECK_GREATER_OR_EQUAL(size, 3, ("Incorrect string", data)); + + RouteParams params; + SerializeLatLon(tokenized.begin(), tokenized.end() - 1, params.m_waypoints); + + int type = 0; + CHECK(strings::to_int(tokenized[size - 1], type), ("Incorrect string", tokenized[size - 1])); + CHECK_LESS(type, static_cast(routing::VehicleType::Count), ("Incorrect vehicle type", type)); + params.m_type = static_cast(type); + return params; +} + +RouteParams SerializeRouteParamsFromString(std::string const & waypoints, int vehicleType) +{ + auto const tokenized = strings::Tokenize(waypoints, ";"); + auto const size = tokenized.size(); + CHECK_GREATER_OR_EQUAL(size, 2, ("Incorrect string", waypoints)); + + RouteParams params; + SerializeLatLon(tokenized.begin(), tokenized.end(), params.m_waypoints); + + CHECK_LESS(vehicleType, static_cast(routing::VehicleType::Count), ("Incorrect vehicle type", vehicleType)); + CHECK_GREATER_OR_EQUAL(vehicleType, 0, ("Incorrect vehicle type", vehicleType)); + params.m_type = static_cast(vehicleType); + return params; +} +} // namespace routinq_quality diff --git a/routing/routing_quality/routing_quality_tool/routing_quality_tool.cpp b/routing/routing_quality/routing_quality_tool/routing_quality_tool.cpp new file mode 100644 index 0000000000..705a36497b --- /dev/null +++ b/routing/routing_quality/routing_quality_tool/routing_quality_tool.cpp @@ -0,0 +1,82 @@ +#include "routing/routing_quality/routing_quality_tool/parse_input_params.hpp" + +#include "routing/routing_quality/mapbox/api.hpp" +#include "routing/routing_quality/utils.hpp" +#include "routing/routing_quality/waypoints.hpp" + +#include "base/assert.hpp" +#include "base/logging.hpp" + +#include + +#include "3party/gflags/src/gflags/gflags.h" + +DEFINE_string(cmd, "", "command:\n" + "generate - generate route waypoints for testing."); + +DEFINE_string(routeParamsFile, "", "File contains two or more route points listed as latitude and longitude " + "separated by comma. Each coordinate should be separated by semicolon. " + "Last symbol is a numeric value of routing::VehicleType enum. " + "At least two points and vehicle type are required. " + "All points and vehicle type will be serialized into routing_quality::RouteParams."); + +DEFINE_string(routeWaypoints, "", "Two or more route points listed as latitude and longitude " + "separated by comma. Each coordinate should be separated by semicolon."); + +DEFINE_int32(vehicleType, 2, "Numeric value of routing::VehicleType enum."); + +DEFINE_string(mapboxAccessToken, "", "Access token for mapbox api."); + +DEFINE_bool(showHelp, false, "Show help on all flags."); + +using namespace std; +using namespace routing_quality; +using namespace mapbox; + +int main(int argc, char ** argv) +{ + google::SetUsageMessage("The tool is used to generate points by mapbox route engine and then " + "use these points as referenced waypoints. The usage could be as following: " + "-cmd=generate " + "-routeWaypoints=55.8840156,37.4403484;55.4173592,37.895966 " + "-mapboxAccessToken=accessToken. " + "You can use the access token from confluence or just get your own. " + "The tool will generate the representation of waypoints which you can use directly in code " + "as a vector's initializer list."); + + if (argc == 1 || FLAGS_showHelp) + { + google::ShowUsageWithFlags(argv[0]); + exit(0); + } + + google::ParseCommandLineFlags(&argc, &argv, true /* remove_flags */); + + auto const & cmd = FLAGS_cmd; + CHECK(!cmd.empty(), ("cmd parameter is empty")); + + if (cmd != "generate") + { + CHECK(false, ("Incorrect command", cmd)); + return 1; + } + + CHECK(!FLAGS_mapboxAccessToken.empty(), ()); + + RouteParams params; + if (FLAGS_routeParamsFile.empty()) + { + CHECK(!FLAGS_routeWaypoints.empty(), ("At least two waypoints are necessary")); + params = SerializeRouteParamsFromString(FLAGS_routeWaypoints, FLAGS_vehicleType); + } + else + { + params = SerializeRouteParamsFromFile(FLAGS_routeParamsFile); + } + + Api const api(FLAGS_mapboxAccessToken); + auto const generatedWaypoints = Api::GenerateWaypointsBasedOn(api.MakeDirectionsRequest(params)); + + LOG(LINFO, ("Result waypoints", generatedWaypoints)); + return 0; +}