[routing] Routing quality tool with mapbox support.

This commit is contained in:
VladiMihaylenko 2018-08-30 15:01:26 +03:00 committed by Vladimir Byko-Ianko
parent c0d3133e67
commit e8ade13cce
7 changed files with 481 additions and 0 deletions

View file

@ -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})

View file

@ -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 <sstream>
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<ms::LatLon> 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<string> Api::GenerateWaypointsBasedOn(DirectionsResponse response)
{
auto & routes = response.m_routes;
CHECK(!routes.empty(), ());
vector<string> 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>;
string mapboxData;
{
Sink sink(mapboxData);
coding::SerializerJson<Sink> ser(sink);
ser(mapboxRoute);
}
mapboxData = UrlEncode(mapboxData);
string mapsmeData;
{
Sink sink(mapsmeData);
coding::SerializerJson<Sink> 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

View file

@ -0,0 +1,51 @@
#pragma once
#include "routing/routing_quality/mapbox/types.hpp"
#include "routing/routing_quality/utils.hpp"
#include "base/assert.hpp"
#include <cstdint>
#include <string>
#include <utility>
#include <vector>
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<std::string> GenerateWaypointsBasedOn(DirectionsResponse response);
template <typename Container>
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

View file

@ -0,0 +1,56 @@
#pragma once
#include "geometry/latlon.hpp"
#include "geometry/mercator.hpp"
#include "geometry/point2d.hpp"
#include "base/visitor.hpp"
#include <string>
#include <vector>
namespace routing_quality
{
namespace mapbox
{
using LonLat = std::vector<double>;
using Coordinates = std::vector<LonLat>;
struct Geometry
{
DECLARE_VISITOR(visitor(m_coordinates, "coordinates"),
visitor(m_type, "type"))
void Build(std::vector<m2::PointD> 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<Route> m_routes;
};
} // namespace mapbox
} // namespace routing_quality

View file

@ -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})

View file

@ -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 <iterator>
#include <string>
#include <vector>
namespace routing_quality
{
template <typename Iter, typename Container>
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<int>(routing::VehicleType::Count), ("Incorrect vehicle type", type));
params.m_type = static_cast<routing::VehicleType>(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<int>(routing::VehicleType::Count), ("Incorrect vehicle type", vehicleType));
CHECK_GREATER_OR_EQUAL(vehicleType, 0, ("Incorrect vehicle type", vehicleType));
params.m_type = static_cast<routing::VehicleType>(vehicleType);
return params;
}
} // namespace routinq_quality

View file

@ -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 <string>
#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;
}