Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-22 13:58:55 +01:00
parent 4af19165ec
commit 68073add76
12458 changed files with 12350765 additions and 2 deletions

View file

@ -0,0 +1,17 @@
project(routing_quality)
set(SRC
waypoints.cpp
waypoints.hpp
)
omim_add_library(${PROJECT_NAME} ${SRC})
target_link_libraries(${PROJECT_NAME}
routes_builder
)
add_subdirectory(api)
omim_add_tool_subdirectory(routing_quality_tool)
omim_add_test_subdirectory(routing_quality_tests)

View file

@ -0,0 +1,22 @@
project(routing_api)
set(SRC
api.cpp
api.hpp
google/google_api.cpp
google/google_api.hpp
google/types.cpp
google/types.hpp
mapbox/mapbox_api.cpp
mapbox/mapbox_api.hpp
mapbox/types.hpp
)
omim_add_library(${PROJECT_NAME} ${SRC})
target_link_libraries(${PROJECT_NAME}
PUBLIC
coding
geometry
routing
)

View file

@ -0,0 +1,156 @@
#include "routing/routing_quality/api/api.hpp"
#include "coding/write_to_sink.hpp"
#include "geometry/mercator.hpp"
#include <string>
namespace routing_quality
{
namespace api
{
// static
void Params::Dump(Params const & route, FileWriter & writer)
{
auto const start = mercator::ToLatLon(route.m_waypoints.GetPointFrom());
auto const finish = mercator::ToLatLon(route.m_waypoints.GetPointTo());
WriteToSink(writer, static_cast<int>(route.m_type));
writer.Write(&start.m_lat, sizeof(start.m_lat));
writer.Write(&start.m_lon, sizeof(start.m_lon));
writer.Write(&finish.m_lat, sizeof(finish.m_lat));
writer.Write(&finish.m_lon, sizeof(finish.m_lon));
}
// static
Params Params::Load(ReaderSource<FileReader> & src)
{
Params params;
params.m_type = static_cast<VehicleType>(ReadPrimitiveFromSource<int>(src));
ms::LatLon start;
ms::LatLon finish;
start.m_lat = ReadPrimitiveFromSource<double>(src);
start.m_lon = ReadPrimitiveFromSource<double>(src);
finish.m_lat = ReadPrimitiveFromSource<double>(src);
finish.m_lon = ReadPrimitiveFromSource<double>(src);
auto const startPoint = mercator::FromLatLon(start);
auto const finishPoint = mercator::FromLatLon(finish);
params.m_waypoints = routing::Checkpoints(startPoint, finishPoint);
return params;
}
// static
void Route::Dump(Route const & route, FileWriter & writer)
{
writer.Write(&route.m_eta, sizeof(route.m_eta));
writer.Write(&route.m_distance, sizeof(route.m_distance));
WriteToSink(writer, route.m_waypoints.size());
for (auto const & latlon : route.m_waypoints)
{
writer.Write(&latlon.m_lat, sizeof(latlon.m_lat));
writer.Write(&latlon.m_lon, sizeof(latlon.m_lon));
}
}
// static
Route Route::Load(ReaderSource<FileReader> & src)
{
Route route;
route.m_eta = ReadPrimitiveFromSource<double>(src);
route.m_distance = ReadPrimitiveFromSource<double>(src);
auto const n = ReadPrimitiveFromSource<size_t>(src);
route.m_waypoints.resize(n);
ms::LatLon latlon;
for (size_t i = 0; i < n; ++i)
{
latlon.m_lat = ReadPrimitiveFromSource<double>(src);
latlon.m_lon = ReadPrimitiveFromSource<double>(src);
route.m_waypoints[i] = latlon;
}
return route;
}
// static
std::string const Response::kDumpExtension = ".api.dump";
// static
void Response::Dump(std::string const & filepath, Response const & response)
{
FileWriter writer(filepath);
WriteToSink(writer, static_cast<int>(response.m_code));
Params::Dump(response.m_params, writer);
WriteToSink(writer, response.m_routes.size());
for (auto const & route : response.m_routes)
Route::Dump(route, writer);
}
// static
Response Response::Load(std::string const & filepath)
{
FileReader reader(filepath);
ReaderSource<FileReader> src(reader);
Response response;
response.m_code = static_cast<ResultCode>(ReadPrimitiveFromSource<int>(src));
response.m_params = Params::Load(src);
auto const n = ReadPrimitiveFromSource<size_t>(src);
response.m_routes.resize(n);
for (size_t i = 0; i < n; ++i)
response.m_routes[i] = Route::Load(src);
return response;
}
routing::VehicleType Response::GetVehicleType() const
{
switch (m_params.m_type)
{
case VehicleType::Car: return routing::VehicleType::Car;
}
UNREACHABLE();
}
RoutingApi::RoutingApi(std::string name, std::string token, uint32_t maxRPS)
: m_apiName(std::move(name))
, m_accessToken(std::move(token))
, m_maxRPS(maxRPS)
{}
Response RoutingApi::CalculateRoute(Params const & params, int32_t startTimeZoneUTC) const
{
return {};
}
uint32_t RoutingApi::GetMaxRPS() const
{
return m_maxRPS;
}
std::string const & RoutingApi::GetApiName() const
{
return m_apiName;
}
std::string const & RoutingApi::GetAccessToken() const
{
return m_accessToken;
}
} // namespace api
} // namespace routing_quality

View file

@ -0,0 +1,102 @@
#pragma once
#include "routing/checkpoints.hpp"
#include "routing/routing_callbacks.hpp"
#include "routing/vehicle_mask.hpp"
#include "coding/file_reader.hpp"
#include "coding/file_writer.hpp"
#include "coding/reader.hpp"
#include "geometry/latlon.hpp"
#include <fstream>
#include <string>
#include <vector>
namespace routing_quality
{
namespace api
{
enum class ResultCode
{
ResponseOK,
Error
};
enum class VehicleType
{
Car
};
struct Params
{
static void Dump(Params const & route, FileWriter & writer);
static Params Load(ReaderSource<FileReader> & src);
VehicleType m_type = VehicleType::Car;
routing::Checkpoints m_waypoints;
};
struct Route
{
static void Dump(Route const & route, FileWriter & writer);
static Route Load(ReaderSource<FileReader> & src);
std::vector<ms::LatLon> const & GetWaypoints() const { return m_waypoints; }
double GetETA() const { return m_eta; }
double m_eta = 0.0;
double m_distance = 0.0;
std::vector<ms::LatLon> m_waypoints;
};
struct Response
{
static std::string const kDumpExtension;
static void Dump(std::string const & filepath, Response const & response);
static Response Load(std::string const & filepath);
routing::VehicleType GetVehicleType() const;
m2::PointD const & GetStartPoint() const { return m_params.m_waypoints.GetPointFrom(); }
m2::PointD const & GetFinishPoint() const { return m_params.m_waypoints.GetPointTo(); }
bool IsCodeOK() const { return m_code == api::ResultCode::ResponseOK; }
std::vector<Route> const & GetRoutes() const { return m_routes; }
ResultCode m_code = ResultCode::Error;
Params m_params;
std::vector<Route> m_routes;
};
class RoutingApiInterface
{
public:
virtual ~RoutingApiInterface() = default;
virtual Response CalculateRoute(Params const & params, int32_t startTimeZoneUTC) const = 0;
};
class RoutingApi : public RoutingApiInterface
{
public:
RoutingApi(std::string name, std::string token, uint32_t maxRPS = 1);
// RoutingApiInterface overrides:
// @{
Response CalculateRoute(Params const & params, int32_t startTimeZoneUTC) const override;
// @}
uint32_t GetMaxRPS() const;
std::string const & GetApiName() const;
std::string const & GetAccessToken() const;
private:
std::string m_apiName;
std::string m_accessToken;
// Maximum requests per second provided with api
uint32_t m_maxRPS;
};
} // namespace api
} // namespace routing_quality

View file

@ -0,0 +1,145 @@
#include "routing/routing_quality/api/google/google_api.hpp"
#include "platform/http_client.hpp"
#include "coding/serdes_json.hpp"
#include "geometry/mercator.hpp"
#include "base/assert.hpp"
#include "base/logging.hpp"
#include <chrono>
#include <ctime>
#include <sstream>
#include <utility>
namespace
{
auto GetNextDayAtNight(int32_t startTimeZoneUTC)
{
auto now = std::chrono::system_clock::now();
std::time_t nowTimeT = std::chrono::system_clock::to_time_t(now);
std::tm * date = std::localtime(&nowTimeT);
std::time_t constexpr kSecondsInDay = 24 * 60 * 60;
std::time_t nextDay = std::mktime(date) + kSecondsInDay;
std::tm * nextDayDate = std::localtime(&nextDay);
long constexpr kSecondsInHour = 3600;
int const curUTCOffset = static_cast<int>(nextDayDate->tm_gmtoff / kSecondsInHour);
nextDayDate->tm_sec = 0;
nextDayDate->tm_min = 0;
int constexpr kStartHour = 22;
// If in Moscow (UTC+3) it is 20:00, in New York (UTC-5):
// 20:00 - 03:00 (Moscow) = 19:00 (UTC+0), so in New York: 19:00 - 05:00 = 14:00 (UTC-5).
// Thus if we want to find such time (Y hours) in Moscow (UTC+3) so that in New York (UTC-5)
// it's must be X hours, we use such formula:
// 1) Y (UTC+3) - 03:00 = UTC+0
// 2) Y (UTC+3) - 03:00 + 05:00 = X (UTC+5)
// 2 => Y = X + 03:00 - 05:00
// For google api we want to find such Y, that it's will be |kStartHour| hours in any place.
nextDayDate->tm_hour = kStartHour + curUTCOffset - startTimeZoneUTC;
return std::chrono::system_clock::from_time_t(std::mktime(nextDayDate));
}
} // namespace
namespace routing_quality
{
namespace api
{
namespace google
{
// static
std::string const GoogleApi::kApiName = "google";
GoogleApi::GoogleApi(std::string const & token) : RoutingApi(kApiName, token, kMaxRPS) {}
Response GoogleApi::CalculateRoute(Params const & params, int32_t startTimeZoneUTC) const
{
Response response;
GoogleResponse googleResponse = MakeRequest(params, startTimeZoneUTC);
response.m_params = params;
response.m_code = googleResponse.m_code;
for (auto const & route : googleResponse.m_routes)
{
CHECK_EQUAL(route.m_legs.size(), 1, ("No waypoints support for google api."));
auto const & leg = route.m_legs.back();
api::Route apiRoute;
apiRoute.m_eta = leg.m_duration.m_seconds;
apiRoute.m_distance = leg.m_distance.m_meters;
auto const startLatLon = leg.m_steps.front().m_startLocation;
apiRoute.m_waypoints.emplace_back(startLatLon.m_lat, startLatLon.m_lon);
for (auto const & step : leg.m_steps)
{
auto const & prev = step.m_startLocation;
auto const & next = step.m_endLocation;
auto const & prevWaypoint = apiRoute.m_waypoints.back();
CHECK(prevWaypoint.m_lat == prev.m_lat && prevWaypoint.m_lon == prev.m_lon, ());
apiRoute.m_waypoints.emplace_back(next.m_lat, next.m_lon);
}
response.m_routes.emplace_back(std::move(apiRoute));
}
return response;
}
GoogleResponse GoogleApi::MakeRequest(Params const & params, int32_t startTimeZoneUTC) const
{
GoogleResponse googleResponse;
platform::HttpClient request(GetDirectionsURL(params, startTimeZoneUTC));
if (request.RunHttpRequest() && !request.WasRedirected() && request.ErrorCode() == 200)
{
auto const & response = request.ServerResponse();
CHECK(!response.empty(), ());
{
googleResponse.m_code = ResultCode::ResponseOK;
coding::DeserializerJson des(response);
des(googleResponse);
}
}
else
{
LOG(LWARNING, (request.ErrorCode(), request.ServerResponse()));
googleResponse.m_code = ResultCode::Error;
}
return googleResponse;
}
std::string GoogleApi::GetDirectionsURL(Params const & params, int32_t startTimeZoneUTC) const
{
CHECK(-12 <= startTimeZoneUTC && startTimeZoneUTC <= 12, ("Invalid UTC zone."));
static std::string const kBaseUrl = "https://maps.googleapis.com/maps/api/directions/json?";
auto const start = mercator::ToLatLon(params.m_waypoints.GetPointFrom());
auto const finish = mercator::ToLatLon(params.m_waypoints.GetPointTo());
auto const nextDayAtNight = GetNextDayAtNight(startTimeZoneUTC);
auto const secondFromEpoch = nextDayAtNight.time_since_epoch().count() / 1000000;
LOG(LDEBUG, ("&departure_time =", secondFromEpoch));
std::stringstream ss;
ss << kBaseUrl << "&origin=" << std::to_string(start.m_lat) << "," << std::to_string(start.m_lon)
<< "&destination=" << std::to_string(finish.m_lat) << "," << std::to_string(finish.m_lon)
<< "&key=" << GetAccessToken() << "&alternatives=true"
<< "&departure_time=" << secondFromEpoch;
return ss.str();
}
} // namespace google
} // namespace api
} // namespace routing_quality

View file

@ -0,0 +1,37 @@
#pragma once
#include "routing/routing_quality/api/api.hpp"
#include "routing/routing_quality/api/google/types.hpp"
#include <cstdint>
#include <string>
namespace routing_quality
{
namespace api
{
namespace google
{
class GoogleApi : public RoutingApi
{
public:
explicit GoogleApi(std::string const & token);
// According to:
// https://developers.google.com/maps/faq#usage_apis
static uint32_t constexpr kMaxRPS = 50;
static std::string const kApiName;
// RoutingApi overrides:
// @{
Response CalculateRoute(Params const & params, int32_t startTimeZoneUTC) const override;
// @}
private:
GoogleResponse MakeRequest(Params const & params, int32_t startTimeZoneUTC) const;
std::string GetDirectionsURL(Params const & params, int32_t startTimeZoneUTC) const;
};
} // namespace google
} // namespace api
} // namespace routing_quality

View file

@ -0,0 +1,32 @@
#include "routing/routing_quality/api/google/types.hpp"
#include <iomanip>
#include <sstream>
#include <tuple>
namespace routing_quality
{
namespace api
{
namespace google
{
bool LatLon::operator==(LatLon const & rhs) const
{
return std::tie(m_lat, m_lon) == std::tie(rhs.m_lat, rhs.m_lon);
}
bool Step::operator==(Step const & rhs) const
{
return std::tie(m_startLocation, m_endLocation) == std::tie(rhs.m_startLocation, rhs.m_endLocation);
}
std::string DebugPrint(LatLon const & latlon)
{
std::stringstream ss;
ss << std::setprecision(20);
ss << "google::LatLon(" << latlon.m_lat << ", " << latlon.m_lon << ")";
return ss.str();
}
} // namespace google
} // namespace api
} // namespace routing_quality

View file

@ -0,0 +1,77 @@
#pragma once
#include "routing/routing_quality/api/api.hpp"
#include "base/visitor.hpp"
#include <string>
#include <vector>
namespace routing_quality
{
namespace api
{
namespace google
{
struct LatLon
{
DECLARE_VISITOR(visitor(m_lat, "lat"), visitor(m_lon, "lng"))
bool operator==(LatLon const & rhs) const;
double m_lat = 0.0;
double m_lon = 0.0;
};
struct Step
{
DECLARE_VISITOR(visitor(m_startLocation, "start_location"), visitor(m_endLocation, "end_location"))
bool operator==(Step const & rhs) const;
LatLon m_startLocation;
LatLon m_endLocation;
};
struct Duration
{
DECLARE_VISITOR(visitor(m_seconds, "value"))
double m_seconds = 0.0;
};
struct Distance
{
DECLARE_VISITOR(visitor(m_meters, "value"))
double m_meters = 0.0;
};
struct Leg
{
DECLARE_VISITOR(visitor(m_distance, "distance"), visitor(m_duration, "duration"), visitor(m_steps, "steps"))
Distance m_distance;
Duration m_duration;
std::vector<Step> m_steps;
};
struct Route
{
DECLARE_VISITOR(visitor(m_legs, "legs"))
std::vector<Leg> m_legs;
};
struct GoogleResponse
{
DECLARE_VISITOR(visitor(m_routes, "routes"))
ResultCode m_code = ResultCode::Error;
std::vector<Route> m_routes;
};
std::string DebugPrint(LatLon const & latlon);
} // namespace google
} // namespace api
} // namespace routing_quality

View file

@ -0,0 +1,130 @@
#include "routing/routing_quality/api/mapbox/mapbox_api.hpp"
#include "routing/vehicle_mask.hpp"
#include "coding/file_writer.hpp"
#include "coding/serdes_json.hpp"
#include "coding/url.hpp"
#include "coding/writer.hpp"
#include "platform/http_client.hpp"
#include "geometry/latlon.hpp"
#include "geometry/mercator.hpp"
#include "base/assert.hpp"
#include "base/logging.hpp"
#include <sstream>
namespace
{
std::string const kBaseURL = "https://api.mapbox.com/";
std::string const kDirectionsApiVersion = "v5";
std::string const kCarType = "mapbox/driving";
std::string VehicleTypeToMapboxType(routing_quality::api::VehicleType type)
{
switch (type)
{
case routing_quality::api::VehicleType::Car: return kCarType;
}
UNREACHABLE();
}
std::string LatLonsToString(std::vector<ms::LatLon> const & coords)
{
std::ostringstream oss;
auto const size = coords.size();
for (size_t i = 0; i < size; ++i)
{
auto const & ll = coords[i];
oss << ll.m_lon << "," << ll.m_lat;
if (i + 1 != size)
oss << ";";
}
oss << ".json";
return url::UrlEncode(oss.str());
}
} // namespace
namespace routing_quality::api::mapbox
{
// static
std::string const MapboxApi::kApiName = "mapbox";
MapboxApi::MapboxApi(std::string const & token) : RoutingApi(kApiName, token, kMaxRPS) {}
Response MapboxApi::CalculateRoute(Params const & params, int32_t /* startTimeZoneUTC */) const
{
Response response;
MapboxResponse mapboxResponse = MakeRequest(params);
response.m_params = params;
response.m_code = mapboxResponse.m_code;
for (auto const & route : mapboxResponse.m_routes)
{
api::Route apiRoute;
apiRoute.m_eta = route.m_duration;
apiRoute.m_distance = route.m_distance;
for (auto const & lonlat : route.m_geometry.m_coordinates)
{
CHECK_EQUAL(lonlat.size(), 2, ());
auto const lon = lonlat.front();
auto const lat = lonlat.back();
apiRoute.m_waypoints.emplace_back(lat, lon);
}
response.m_routes.emplace_back(std::move(apiRoute));
}
return response;
}
MapboxResponse MapboxApi::MakeRequest(Params const & params) const
{
MapboxResponse mapboxResponse;
platform::HttpClient request(GetDirectionsURL(params));
if (request.RunHttpRequest() && !request.WasRedirected() && request.ErrorCode() == 200)
{
auto const & response = request.ServerResponse();
CHECK(!response.empty(), ());
{
mapboxResponse.m_code = ResultCode::ResponseOK;
coding::DeserializerJson des(response);
des(mapboxResponse);
}
}
else
{
LOG(LWARNING, (request.ErrorCode(), request.ServerResponse()));
mapboxResponse.m_code = ResultCode::Error;
}
return mapboxResponse;
}
std::string MapboxApi::GetDirectionsURL(Params const & params) const
{
CHECK(!GetAccessToken().empty(), ());
std::vector<ms::LatLon> coords;
coords.reserve(params.m_waypoints.GetPoints().size());
for (auto const & point : params.m_waypoints.GetPoints())
coords.emplace_back(mercator::ToLatLon(point));
std::ostringstream oss;
oss << kBaseURL << "directions/" << kDirectionsApiVersion << "/" << VehicleTypeToMapboxType(params.m_type) << "/";
oss << LatLonsToString(coords) << "?";
oss << "access_token=" << GetAccessToken() << "&";
oss << "overview=simplified&"
<< "geometries=geojson";
oss << "&alternatives=true";
return oss.str();
}
} // namespace routing_quality::api::mapbox

View file

@ -0,0 +1,39 @@
#pragma once
#include "routing/routing_quality/api/api.hpp"
#include "routing/routing_quality/api/mapbox/types.hpp"
#include <cstdint>
#include <string>
#include <utility>
#include <vector>
namespace routing_quality
{
namespace api
{
namespace mapbox
{
class MapboxApi : public RoutingApi
{
public:
explicit MapboxApi(std::string const & token);
// According to:
// https://docs.mapbox.com/api/navigation/#directions-api-restrictions-and-limits
static uint32_t constexpr kMaxRPS = 5;
static std::string const kApiName;
// RoutingApi overrides:
// @{
Response CalculateRoute(Params const & params, int32_t /* startTimeZoneUTC */) const override;
// @}
private:
MapboxResponse MakeRequest(Params const & params) const;
std::string GetDirectionsURL(Params const & params) const;
};
} // namespace mapbox
} // namespace api
} // namespace routing_quality

View file

@ -0,0 +1,46 @@
#pragma once
#include "routing/routing_quality/api/api.hpp"
#include "geometry/latlon.hpp"
#include "geometry/mercator.hpp"
#include "geometry/point2d.hpp"
#include "base/visitor.hpp"
#include <string>
#include <vector>
namespace routing_quality
{
namespace api
{
namespace mapbox
{
using LonLat = std::vector<double>;
struct Geometry
{
DECLARE_VISITOR(visitor(m_coordinates, "coordinates"))
std::vector<LonLat> m_coordinates;
};
struct Route
{
DECLARE_VISITOR(visitor(m_geometry, "geometry"), visitor(m_duration, "duration"), visitor(m_distance, "distance"))
Geometry m_geometry;
double m_duration = 0.0;
double m_distance = 0.0;
};
struct MapboxResponse
{
DECLARE_VISITOR(visitor(m_routes, "routes"))
ResultCode m_code = ResultCode::Error;
std::vector<Route> m_routes;
};
} // namespace mapbox
} // namespace api
} // namespace routing_quality

View file

@ -0,0 +1,17 @@
project(routing_quality_tests)
set(SRC
barriers_tests.cpp
bigger_roads_tests.cpp
ferry_tests.cpp
is_built_tests.cpp
leaps_postprocessing_tests.cpp
passby_roads_tests.cpp
waypoints_tests.cpp
)
omim_add_test(${PROJECT_NAME} ${SRC})
target_link_libraries(${PROJECT_NAME}
routing_quality
)

View file

@ -0,0 +1,41 @@
#include "testing/testing.hpp"
#include "routing/routing_quality/waypoints.hpp"
using namespace routing_quality;
// There is a category of barriers through which no road must be built:
// ice roads (highway = ice_road).
// And there is a category of barriers through which the road should be built:
// fords (highway = ford).
// Tests on this kind of cases are grouped in this file.
namespace
{
UNIT_TEST(RoutingQuality_Broad_Node_Jamaica)
{
TEST(CheckCarRoute({17.94727, -76.25429} /* start */, {17.94499, -76.25459} /* finish */,
{{{17.945150, -76.25442}}} /* reference point */),
());
}
UNIT_TEST(RoutingQuality_Broad_Way_Jamaica)
{
TEST(CheckCarRoute({18.10260, -76.98374} /* start */, {18.10031, -76.98374} /* finish */,
{{{18.10078, -76.98412}}} /* reference point */),
());
}
UNIT_TEST(RoutingQuality_Broad_Node_Spain)
{
TEST(CheckCarRoute({41.95027, -0.54596} /* start */, {56.98358, 9.77815} /* finish */,
{{{41.95026, -0.54562}}} /* reference point */),
());
}
UNIT_TEST(RoutingQuality_Broad_Node_Norway)
{
TEST(CheckCarRoute({56.20247, 8.77519} /* start */, {56.19732, 8.79190} /* finish */,
{{{56.20172, 8.77879}}} /* reference point */),
());
}
} // namespace

View file

@ -0,0 +1,131 @@
#include "testing/testing.hpp"
#include "routing/routing_quality/waypoints.hpp"
using namespace routing_quality;
// Test on preferring better but longer roads should be grouped in this file.
namespace
{
// Secondary should be preferred against residential.
UNIT_TEST(RoutingQuality_RussiaMoscowTushino)
{
TEST(CheckCarRoute({55.84367, 37.44724} /* start */, {55.85489, 37.43784} /* finish */,
{{{55.84343, 37.43949}}} /* reference track */),
());
}
UNIT_TEST(RoutingQuality_TurkeyIzmirArea)
{
TEST(CheckCarRoute({38.80146, 26.97696} /* start */, {39.0837, 26.90977} /* finish */,
{{{39.08124, 27.11829}}} /* reference track */),
());
}
UNIT_TEST(RoutingQuality_BosniaAndHerzegovina)
{
TEST(CheckCarRoute({42.71401, 18.30412} /* start */, {42.95101, 18.08966} /* finish */,
{{{42.88222, 17.9919}}} /* reference track */),
());
}
UNIT_TEST(RoutingQuality_CzechiaPrague)
{
TEST(CheckCarRoute({50.10159, 14.43324} /* start */, {50.20976, 14.43361} /* finish */,
{{{50.15078, 14.49205}}} /* reference track */),
());
}
UNIT_TEST(RoutingQuality_FinlandHelsinki)
{
TEST(CheckCarRoute({60.16741, 24.94255} /* start */, {64.13182, 28.38784} /* finish */,
{{{60.95453, 25.6951}}} /* reference track */),
());
}
UNIT_TEST(RoutingQuality_USAOklahoma)
{
TEST(CheckCarRoute({35.39166, -97.55402} /* start */, {35.38452, -97.5742} /* finish */,
{{{35.39912, -97.57622}}} /* reference track */),
());
}
UNIT_TEST(RoutingQuality_IranSouth)
{
TEST(CheckCarRoute({32.45088, 51.76419} /* start */, {32.97067, 51.50399} /* finish */,
{{{32.67021, 51.64323}, {32.68752, 51.63387}},
{{32.67021, 51.64323}, {32.7501, 51.64661}}} /* reference track */),
());
}
UNIT_TEST(RoutingQuality_EindhovenNetherlands)
{
TEST(CheckCarRoute({50.91974, 5.33535} /* start */, {51.92532, 5.49066} /* finish */,
{{{51.42016, 5.42881}, {51.44316, 5.42723}}} /* reference track */),
());
}
UNIT_TEST(RoutingQuality_GeteborgasSweden)
{
TEST(CheckCarRoute({57.77064, 11.88079} /* start */, {57.71231, 11.93157} /* finish */,
{{{57.74912, 11.87343}}} /* reference track */),
());
}
UNIT_TEST(RoutingQuality_CigilTurkey)
{
TEST(CheckCarRoute({38.48175, 27.12952} /* start */, {38.47558, 27.06765} /* finish */,
{{{38.4898049, 27.1016266}}} /* reference track */),
());
}
UNIT_TEST(RoutingQuality_KatowicePoland)
{
TEST(CheckCarRoute({50.37282, 18.75667} /* start */, {50.83499, 19.14612} /* finish */,
{{{50.422229, 19.04746}}} /* reference track */),
());
}
UNIT_TEST(RoutingQuality_KrasnoyarskBratsk)
{
TEST(CheckCarRoute({56.009, 92.873} /* start */, {56.163, 101.611} /* finish */,
{{{55.89285, 97.99953}, {54.59928, 100.60402}}} /* reference track */),
());
}
UNIT_TEST(RoutingQuality_VoronezhSochi)
{
TEST(CheckCarRoute({51.65487, 39.21293} /* start */, {43.58547, 39.72311} /* finish */,
{{{46.14169, 39.85306},
{45.17069, 39.10869},
{45.02157, 39.12510},
{44.54344, 38.95853}}} /* reference track */),
());
}
UNIT_TEST(RoutingQuality_BerlinkaWarsawPoland)
{
TEST(CheckCarRoute({54.41616, 20.05675} /* start */, {52.18937, 20.94026} /* finish */,
{{{54.24278, 19.66106},
{54.13679, 19.45166},
{54.06452, 19.62416},
{53.69769, 19.98204},
{53.11194, 20.40002},
{52.62966, 20.38488}}} /* reference track */),
());
}
UNIT_TEST(RoutingQuality_MosOblBadPaving)
{
TEST(CheckCarRoute({55.92961, 36.04081} /* start */, {55.93567, 36.0533} /* finish */,
{{{55.92321, 36.04630}}} /* reference track */),
());
}
UNIT_TEST(RoutingQuality_LatviaUnpaved)
{
TEST(CheckCarRoute({56.62992, 25.77175} /* start */, {56.61453, 25.78400} /* finish */,
{{{56.62377, 25.81015}, {56.61755, 25.80894}}} /* reference track */),
());
}
} // namespace

View file

@ -0,0 +1,133 @@
#include "testing/testing.hpp"
#include "routing/routing_quality/waypoints.hpp"
using namespace routing_quality;
namespace
{
UNIT_TEST(Ferry_RoutingQuality_FinlandBridgeInsteadOfFerry)
{
TEST(CheckCarRoute({56.11155, 12.81101} /* start */, {55.59857, 12.3069} /* finish */,
{{{55.56602, 12.88537}}} /* reference track */),
());
}
// TODO: This test doesn't pass because routing::RouteWeight::operator<
// prefer roads with less number of barriers. It will be more useful to consider
// barriers only with access=no/private/etc tag.
// UNIT_TEST(Ferry_RoutingQuality_RussiaToCrimeaFerry)
//{
// // From Russia to Crimea
// TEST(CheckCarRoute({45.34123, 36.67679} /* start */, {45.36479, 36.62194} /* finish */,
// {{{45.3532, 36.64912}}} /* reference track */),
// ());
//}
UNIT_TEST(Ferry_RoutingQuality_RussiaFromCrimeaFerry)
{
TEST(CheckCarRoute({45.36479, 36.62194} /* start */, {45.34123, 36.67679} /* finish */,
{{{45.3532, 36.64912}}} /* reference track */),
());
}
// For tests Ferry_RoutingQuality_1 - Ferry_RoutingQuality_15
// Look at: https://confluence.mail.ru/display/MAPSME/Ferries for more details.
UNIT_TEST(Ferry_RoutingQuality_1)
{
TEST(CheckCarRoute({67.89425, 13.00747} /* start */, {67.28024, 14.37397} /* finish */,
{{{67.60291, 13.65267}}} /* reference track */),
());
}
UNIT_TEST(Ferry_RoutingQuality_2)
{
TEST(CheckCarRoute({51.91347, 5.24265} /* start */, {51.98885, 5.20058} /* finish */,
{{{51.97494, 5.10631}}} /* reference track */),
());
}
UNIT_TEST(Ferry_RoutingQuality_3)
{
TEST(CheckCarRoute({50.68323, 7.15683} /* start */, {50.58042, 7.32259} /* finish */,
{{{50.72206, 7.14872}, {50.70747, 7.17801}}} /* reference track */),
());
}
UNIT_TEST(Ferry_RoutingQuality_4)
{
TEST(CheckCarRoute({49.37098, 0.84813} /* start */, {49.47950, 0.80918} /* finish */,
{{{49.36829, 0.81359}, {49.41177, 0.79639}}} /* reference track */),
());
}
UNIT_TEST(Ferry_RoutingQuality_5)
{
TEST(CheckCarRoute({53.59885, 9.32285} /* start */, {53.93504, 9.48396} /* finish */,
{{{53.55170, 9.89848}}} /* reference track */),
());
}
UNIT_TEST(Ferry_RoutingQuality_6)
{
TEST(CheckCarRoute({48.268548, 21.483862} /* start */, {48.24756, 21.54246} /* finish */,
{{{48.32574, 21.56094}}} /* reference track */),
());
}
UNIT_TEST(Ferry_RoutingQuality_7)
{
TEST(CheckCarRoute({52.09319, 25.96368} /* start */, {52.02190, 25.98767} /* finish */,
{{{52.08914, 26.13001}}} /* reference track */),
());
}
UNIT_TEST(Ferry_RoutingQuality_8)
{
TEST(CheckCarRoute({53.36914, 17.95644} /* start */, {53.46632, 18.01876} /* finish */,
{{{53.31395, 17.98559}}} /* reference track */),
());
}
UNIT_TEST(Ferry_RoutingQuality_9)
{
TEST(CheckCarRoute({52.57264, 14.83948} /* start */, {52.74278, 14.95435} /* finish */,
{{{52.65097, 14.99906}}} /* reference track */),
());
}
UNIT_TEST(Ferry_RoutingQuality_10)
{
TEST(CheckCarRoute({63.33900, 10.27831} /* start */, {63.33338, 10.22694} /* finish */,
{{{63.32261, 10.26896}}} /* reference track */),
());
}
UNIT_TEST(Ferry_RoutingQuality_11)
{
TEST(CheckCarRoute({56.51167, 10.28726} /* start */, {56.57695, 10.11188} /* finish */,
{{{56.44610, 10.26030}}} /* reference track */),
());
}
UNIT_TEST(Ferry_RoutingQuality_13)
{
TEST(CheckCarRoute({51.53923, 11.78523} /* start */, {51.62372, 11.86635} /* finish */,
{{{51.50772, 11.94096}}} /* reference track */),
());
}
UNIT_TEST(Ferry_RoutingQuality_14)
{
TEST(CheckCarRoute({52.68467, 16.24031} /* start */, {52.74559, 16.27202} /* finish */,
{{{52.71631, 16.37917}}} /* reference track */),
());
}
UNIT_TEST(Ferry_RoutingQuality_15)
{
TEST(CheckCarRoute({48.29162, 22.21412} /* start */, {48.27409, 22.46155} /* finish */,
{{{48.43306, 22.23317}}} /* reference track */),
());
}
} // namespace

View file

@ -0,0 +1,37 @@
#include "testing/testing.hpp"
#include "routing/routing_quality/waypoints.hpp"
using namespace routing_quality;
// Tests on availability to build route.
namespace
{
// Test on building route from and to Bilbau Airport.
UNIT_TEST(RoutingQuality_BilbauAirport)
{
// From Bilbau Airport.
TEST(CheckCarRoute({43.3017, -2.9151} /* start */, {43.27637, -2.86924} /* finish */,
{{{43.27759, -2.87367}}} /* reference track */),
());
// To Bilbau Airport.
TEST(CheckCarRoute({43.27651, -2.86918} /* start */, {43.2805, -2.87853} /* finish */,
{{{43.27788, -2.87385}}} /* reference track */),
());
}
// Test on building route from and to Fairbanks International Airport.
UNIT_TEST(RoutingQuality_FairbanksAirport)
{
// From Fairbanks International Airport.
TEST(CheckCarRoute({64.81398, -147.85897} /* start */, {64.85873, -147.69372} /* finish */,
{{{64.85886, -147.70319}}} /* reference track */),
());
// To Fairbanks International Airport.
TEST(CheckCarRoute({64.85893, -147.69438} /* start */, {64.81398, -147.85897} /* finish */,
{{{64.85825, -147.71106}}} /* reference track */),
());
}
} // namespace

View file

@ -0,0 +1,99 @@
#include "testing/testing.hpp"
#include "routing/vehicle_mask.hpp"
#include "routing/routing_quality/waypoints.hpp"
#include <utility>
#include <vector>
namespace leaps_postprocessing_tests
{
using namespace routing;
using namespace routing_quality;
using namespace std;
UNIT_TEST(RoutingQuality_NoLoop_Canada)
{
TEST(!CheckCarRoute({53.53540, -113.50798} /* start */, {69.44402, -133.03189} /* finish */,
{{{61.74073, -121.21379}}} /* reference point */),
());
}
UNIT_TEST(RoutingQuality_NoLoop_ZhitomirTver)
{
TEST(!CheckCarRoute({50.94928, 28.64163} /* start */, {54.50750, 30.47854} /* finish */,
{{{51.62925, 29.08458}}} /* reference point */),
());
}
UNIT_TEST(RoutingQuality_NoLoop_MacedoniaMontenegro_1)
{
TEST(!CheckCarRoute({42.02901, 21.44826} /* start */, {42.77394, 18.94886} /* finish */,
{{{42.66290, 20.20949}}} /* reference point */),
());
}
UNIT_TEST(RoutingQuality_NoLoop_MacedoniaMontenegro_2)
{
TEST(!CheckCarRoute({41.14033, 22.50236} /* start */, {42.42424, 18.77128} /* finish */,
{{{42.66181, 20.28980}}} /* reference point */),
());
}
UNIT_TEST(RoutingQuality_NoLoop_MacedoniaMontenegro_3)
{
TEST(!CheckCarRoute({42.00375, 21.50582} /* start */, {42.14698, 19.04367} /* finish */,
{{{42.69257, 20.08659}}} /* reference point */),
());
}
UNIT_TEST(RoutingQuality_NoLoop_AbkhaziaDonetsk)
{
TEST(!CheckCarRoute({43.17286, 40.39015} /* start */, {48.01587, 37.80132} /* finish */,
{{{47.69821, 38.67685}}} /* reference point */),
());
}
UNIT_TEST(RoutingQuality_NoLoop_RussiaKomiChuvashia)
{
TEST(!CheckCarRoute({60.175920, 49.641070} /* start */, {56.077370, 47.875380} /* finish */,
{{{56.16673, 47.80223}}} /* reference point */),
());
}
UNIT_TEST(RoutingQuality_NoLoop_SaratovMoscow)
{
TEST(!CheckCarRoute({51.54151, 46.23666} /* start */, {56.12105, 37.61638} /* finish */,
{{{55.91385, 37.58531}}} /* reference point */),
());
}
UNIT_TEST(RoutingQuality_NoLoop_BucharestMontenegro)
{
TEST(!CheckCarRoute({44.41418, 26.11567} /* start */, {42.80962, 19.50849} /* finish */,
{{{42.66125, 20.26862}}} /* reference point */),
());
}
UNIT_TEST(RoutingQuality_NoLoop_RyazanOblastGorshkovo)
{
TEST(!CheckCarRoute({54.30282, 38.93610} /* start */, {56.37584, 37.40839} /* finish */,
{{{55.91318, 37.58136}}} /* reference point */),
());
}
UNIT_TEST(RoutingQuality_NoLoop_BulgariaEastMontenegro)
{
TEST(!CheckCarRoute({42.82058, 27.87946} /* start */, {42.42889, 18.70008} /* finish */,
{{{42.70045, 20.11199}}} /* reference point */),
());
}
UNIT_TEST(RoutingQuality_NoLoop_SkopjeMontenegro)
{
TEST(!CheckCarRoute({41.99137, 21.44921} /* start */, {42.25520, 18.89884} /* finish */,
{{{42.68296, 20.18158}}} /* reference point */),
());
}
} // namespace leaps_postprocessing_tests

View file

@ -0,0 +1,149 @@
#include "testing/testing.hpp"
#include "routing/routing_quality/waypoints.hpp"
using namespace routing_quality;
// In most cases a passby road should be preferred in case of going pass a city.
// Test on such cases should be grouped in this file.
namespace
{
UNIT_TEST(RoutingQuality_RussiaZelegonrad2Domodedovo)
{
// From Zelenograd to Domodedovo. MKAD should be preferred.
TEST(CheckCarRoute({55.98301, 37.21141} /* start */, {55.42081, 37.89361} /* finish */,
{{{55.99751, 37.23804}, // Through M-11 and MKAD.
{56.00719, 37.28533},
{55.88759, 37.48068},
{55.83513, 37.39569}},
{{55.99775, 37.24941}, // Through M-10 and MKAD.
{55.88627, 37.43915},
{55.86882, 37.40784},
{55.58645, 37.71672},
{55.57855, 37.75468}}} /* reference tracks */),
());
}
UNIT_TEST(RoutingQuality_BelarusKobrin)
{
// Test on using a passby road around Kobirn.
TEST(CheckCarRoute({52.18429, 24.20225} /* start */, {52.24404, 24.45842} /* finish */,
{{{52.18694, 24.39903}}} /* reference point */),
());
}
UNIT_TEST(RoutingQuality_BelarusBobruisk)
{
TEST(CheckCarRoute({53.24596, 28.93816} /* start */, {53.04386, 29.58098} /* finish */,
{{{53.24592, 29.29409}}} /* reference point */),
());
}
UNIT_TEST(RoutingQuality_RussiaStPetersburg)
{
TEST(CheckCarRoute({60.08634, 30.10277} /* start */, {59.94584, 30.57703} /* finish */,
{{{60.03478, 30.44084}}} /* reference point */),
());
}
UNIT_TEST(RoutingQuality_BelarusMinsk)
{
TEST(CheckCarRoute({53.75958, 28.005} /* start */, {54.03957, 26.83097} /* finish */,
{{{53.70668, 27.4487}}} /* reference point */),
());
}
UNIT_TEST(RoutingQuality_BelarusMinskMKAD)
{
TEST(CheckCarRoute({53.81784, 27.76789} /* start */, {53.94655, 27.36398} /* finish */,
{{{53.95037, 27.65361}}} /* reference point */),
());
}
UNIT_TEST(RoutingQuality_EnglandLondon)
{
TEST(CheckCarRoute({51.90356, -0.20133} /* start */, {51.23253, -0.33076} /* finish */,
{{{51.57098, -0.53503}}} /* reference point */),
());
}
// After map update to 190719 the route starts go throw Chernigov instead of using
// passby way. It should be fix and the test should be uncommented.
// UNIT_TEST(RoutingQuality_UkraineChernigov)
//{
// TEST(CheckCarRoute({51.29419, 31.25718} /* start */, {51.62678, 31.21787} /* finish */,
// {{{51.48362, 31.18757}}} /* reference point */),
// ());
//}
UNIT_TEST(RoutingQuality_PolandSiedlce)
{
TEST(CheckCarRoute({52.17525, 22.19702} /* start */, {52.119802, 22.35855} /* finish */,
{{{52.14355, 22.231}}} /* reference point */),
());
}
UNIT_TEST(RoutingQuality_HungarySzolnok)
{
TEST(CheckCarRoute({47.18462, 20.04432} /* start */, {47.17919, 20.33486} /* finish */,
{{{47.14467, 20.17032}}} /* reference point */),
());
}
UNIT_TEST(RoutingQuality_USATexasAbilene)
{
TEST(CheckCarRoute({32.46041, -99.93058} /* start */, {32.43085, -99.59475} /* finish */,
{{{32.49038, -99.7269}}} /* reference point */),
());
}
UNIT_TEST(RoutingQuality_ItalyParma)
{
TEST(CheckCarRoute({44.81937, 10.2403} /* start */, {44.78228, 10.38824} /* finish */,
{{{44.81625, 10.34545}}} /* reference point */),
());
}
UNIT_TEST(RoutingQuality_SlovenijaLjubljana)
{
TEST(CheckCarRoute({45.99272, 14.59186} /* start */, {46.10318, 14.46829} /* finish */,
{{{46.04449, 14.44669}}} /* reference point */),
());
}
// TODO: Uncomment this test when correct city boundaries or crossroads will be ready.
// UNIT_TEST(RoutingQuality_FrancePoitiers)
//{
// TEST(CheckCarRoute({46.63612, 0.35762} /* start */, {46.49, 0.36787} /* finish */,
// {{{46.58706, 0.39232}}} /* reference point */),
// ());
//}
UNIT_TEST(RoutingQuality_FranceLoudun)
{
TEST(CheckCarRoute({47.03437, 0.04437} /* start */, {46.97887, 0.09692} /* finish */,
{{{47.00307, 0.06713}}} /* reference point */),
());
}
UNIT_TEST(RoutingQuality_FranceDoueIaFontaine)
{
TEST(CheckCarRoute({47.22972, -0.30962} /* start */, {47.17023, -0.2185} /* finish */,
{{{47.19117, -0.31334}}} /* reference point */),
());
}
UNIT_TEST(RoutingQuality_BelgiumBrussel)
{
TEST(CheckCarRoute({50.88374, 4.2195} /* start */, {50.91494, 4.38122} /* finish */,
{{{50.91727, 4.36858}}} /* reference point */),
());
}
UNIT_TEST(RoutingQuality_SouthernDenmarkPastUnclassified)
{
TEST(CheckCarRoute({55.44681, 10.29} /* start */, {55.45877, 10.26456} /* finish */,
{{{55.45505, 10.26972}}} /* reference point */),
());
}
} // namespace

View file

@ -0,0 +1,37 @@
#include "testing/testing.hpp"
#include "routing/vehicle_mask.hpp"
#include "routing/routing_quality/waypoints.hpp"
#include <utility>
#include <vector>
namespace waypoins_tests
{
using namespace routing;
using namespace routing_quality;
using namespace std;
UNIT_TEST(RoutingQuality_CompareSmoke)
{
// From office to Aseeva 6.
TEST(CheckCarRoute({55.79723, 37.53777} /* start */, {55.80634, 37.52886} /* finish */,
{{{55.79676, 37.54138},
{55.79914, 37.53582},
{55.80353, 37.52478},
{55.80556, 37.52770}}} /* reference track */),
());
}
UNIT_TEST(RoutingQuality_Sokol2Mayakovskaya)
{
// From Sokol to Mayakovskaya through Leningradsky Avenue but not through its alternate.
Params params(VehicleType::Car, {55.80432, 37.51603} /* start */, {55.77019, 37.59558} /* finish */);
// All points lie on the alternate so the result should be 0.
ReferenceRoutes waypoints = {{{55.79599, 37.54114}, {55.78142, 37.57364}, {55.77863, 37.57989}}};
TEST_EQUAL(CheckWaypoints(params, std::move(waypoints)), 0.0, ());
}
} // namespace waypoins_tests

View file

@ -0,0 +1,22 @@
project(routing_quality_tool)
set(SRC
benchmark_results.cpp
benchmark_results.hpp
benchmark_stat.cpp
benchmark_stat.hpp
error_type_counter.cpp
error_type_counter.hpp
routing_quality_tool.cpp
utils.cpp
utils.hpp
)
omim_add_executable(${PROJECT_NAME} ${SRC})
target_link_libraries(${PROJECT_NAME}
routing_quality
routing_api
kml
gflags::gflags
)

View file

@ -0,0 +1,18 @@
#include "routing/routing_quality/routing_quality_tool/benchmark_results.hpp"
namespace routing_quality::routing_quality_tool
{
double BenchmarkResults::GetAverageBuildTime() const
{
if (m_buildTimes.empty())
return 0.0;
return m_summaryBuildTimeSeconds / static_cast<double>(m_buildTimes.size());
}
void BenchmarkResults::PushBuildTime(double time)
{
m_summaryBuildTimeSeconds += time;
m_buildTimes.emplace_back(time);
}
} // namespace routing_quality::routing_quality_tool

View file

@ -0,0 +1,31 @@
#pragma once
#include "routing/routing_callbacks.hpp"
#include <map>
#include <string>
#include <vector>
namespace routing_quality::routing_quality_tool
{
class BenchmarkResults
{
public:
double GetAverageBuildTime() const;
void PushBuildTime(double time);
std::vector<double> const & GetBuildTimes() const { return m_buildTimes; }
private:
double m_summaryBuildTimeSeconds = 0.0;
// m_buildTimes[i] stores build time of i-th route.
std::vector<double> m_buildTimes;
};
struct TimeInfo
{
TimeInfo(double oldTime, double newTime) : m_oldTime(oldTime), m_newTime(newTime) {}
double m_oldTime;
double m_newTime;
};
} // namespace routing_quality::routing_quality_tool

View file

@ -0,0 +1,303 @@
#include "routing/routing_quality/routing_quality_tool/benchmark_stat.hpp"
#include "routing/routing_quality/routing_quality_tool/benchmark_results.hpp"
#include "routing/routing_quality/routing_quality_tool/error_type_counter.hpp"
#include "routing/routing_quality/routing_quality_tool/utils.hpp"
#include "routing/routing_callbacks.hpp"
#include "geometry/point2d.hpp"
#include "base/assert.hpp"
#include "base/file_name_utils.hpp"
#include "base/logging.hpp"
#include "base/string_utils.hpp"
#include <algorithm>
namespace
{
using namespace routing;
using namespace routes_builder;
using namespace routing_quality::routing_quality_tool;
bool IsErrorCode(RouterResultCode code)
{
return code != RouterResultCode::NoError;
}
void LogIfNotConsistent(RoutesBuilder::Result const & oldRes, RoutesBuilder::Result const & newRes)
{
auto const start = mercator::ToLatLon(oldRes.m_params.m_checkpoints.GetStart());
auto const finish = mercator::ToLatLon(oldRes.m_params.m_checkpoints.GetFinish());
CHECK(!oldRes.m_routes.empty(), ());
CHECK(!newRes.m_routes.empty(), ());
auto const & oldRoute = oldRes.m_routes.back();
auto const & newRoute = newRes.m_routes.back();
bool const sameETA = AlmostEqualAbs(oldRoute.m_eta, newRoute.m_eta, 1.0);
bool const sameDistance = AlmostEqualAbs(oldRoute.m_distance, newRoute.m_distance, 1.0);
if (!sameETA || !sameDistance)
{
LOG(LINFO, ("old ETA:", oldRoute.m_eta, "old distance:", oldRoute.m_distance, "new ETA:", newRoute.m_eta,
"new distance:", newRoute.m_distance, start, finish));
}
}
/// \brief Helps to compare route time building for routes group by old time building.
void FillInfoAboutBuildTimeGroupByPreviousResults(std::vector<std::string> & labels,
std::vector<std::vector<double>> & bars,
std::vector<TimeInfo> && times)
{
bars.clear();
labels.clear();
std::sort(times.begin(), times.end(), [](auto const & a, auto const & b) { return a.m_oldTime < b.m_oldTime; });
size_t constexpr kSteps = 10;
size_t const step = times.size() / kSteps;
size_t startFrom = 0;
size_t curCount = 0;
bars.resize(2);
double curSumOld = 0;
double curSumNew = 0;
for (size_t i = 0; i < times.size(); ++i)
{
if (curCount < step && i + 1 != times.size())
{
++curCount;
curSumOld += times[i].m_oldTime;
curSumNew += times[i].m_newTime;
continue;
}
double const curLeft = times[startFrom].m_oldTime;
startFrom = i + 1;
double const curRight = times[i].m_oldTime;
labels.emplace_back("[" + strings::to_string_dac(curLeft, 2 /* dac */) + "s, " +
strings::to_string_dac(curRight, 2 /* dac */) + "s]\\n" + "Routes count:\\n" +
std::to_string(curCount));
if (curCount != 0)
{
curSumOld /= curCount;
curSumNew /= curCount;
}
double const k = curSumOld == 0.0 ? 0.0 : curSumNew / curSumOld;
bars[0].emplace_back(100.0);
bars[1].emplace_back(100.0 * k);
curCount = 0;
}
}
std::vector<double> GetBoostPercents(BenchmarkResults const & oldResults, BenchmarkResults const & newResults)
{
std::vector<double> boostPercents;
for (size_t i = 0; i < oldResults.GetBuildTimes().size(); ++i)
{
auto const oldTime = oldResults.GetBuildTimes()[i];
auto const newTime = newResults.GetBuildTimes()[i];
if (AlmostEqualAbs(oldTime, newTime, 1e-2))
continue;
auto const diffPercent = (oldTime - newTime) / oldTime * 100.0;
boostPercents.emplace_back(diffPercent);
}
return boostPercents;
}
} // namespace
namespace routing_quality::routing_quality_tool
{
using namespace routing;
using namespace routes_builder;
// Shows distribution of routes time building.
static std::string const kPythonDistTimeBuilding = "show_route_time_building_dist.py";
// Shows graph of "how many routes in percents build in less time than some T".
static std::string const kPythonGraphTimeAndCount = "show_time_count_graph.py";
// Bar graph of routing errors. Labels - string representation of errors, heights - number of such
// errors.
static std::string const kPythonBarError = "show_errors_bar.py";
// Shows percent of boost distribution. Where boost percent equals:
// 100.0 * (old_time - new_time) / old_time
static std::string const kPythonBarBoostPercentDistr = "show_boost_distr.py";
// Shows distribution of non-zero difference of ETA between old and new mapsme version.
static std::string const kPythonEtaDiff = "eta_diff.py";
// The same as above but in percents.
static std::string const kPythonEtaDiffPercent = "eta_diff_percent.py";
// Groups routes by previous time building and draws two types of bars. The first one (old mapsme)
// has the same height in all groups and the second ones' height is proportional less or more
// according to difference in average time building between old and new version. The example you can
// see here: https://github.com/mapsme/omim/pull/12401
static std::string const kPythonSmartDistr = "show_smart_boost_distr.py";
void RunBenchmarkStat(std::vector<std::pair<RoutesBuilder::Result, std::string>> const & mapsmeResults,
std::string const & dirForResults)
{
BenchmarkResults benchmarkResults;
ErrorTypeCounter errorTypeCounter;
for (auto const & resultItem : mapsmeResults)
{
auto const & result = resultItem.first;
errorTypeCounter.PushError(result.m_code);
if (result.m_code != RouterResultCode::NoError)
continue;
CHECK(!result.m_routes.empty(), ());
benchmarkResults.PushBuildTime(result.m_buildTimeSeconds);
}
auto pythonScriptPath = base::JoinPath(dirForResults, kPythonDistTimeBuilding);
CreatePythonScriptForDistribution(pythonScriptPath, "Route building time, seconds", benchmarkResults.GetBuildTimes());
LOG(LINFO, ("Average route time building:", benchmarkResults.GetAverageBuildTime(), "seconds."));
auto times = benchmarkResults.GetBuildTimes();
std::sort(times.begin(), times.end());
std::vector<m2::PointD> countToTimes(times.size());
for (size_t i = 0; i < countToTimes.size(); ++i)
{
countToTimes[i].x = times[i];
countToTimes[i].y = static_cast<double>(i + 1) / times.size() * 100.0;
}
pythonScriptPath = base::JoinPath(dirForResults, kPythonGraphTimeAndCount);
CreatePythonGraphByPointsXY(pythonScriptPath, "Building time, seconds" /* xlabel */,
"Percent of routes built less than" /* ylabel */, {countToTimes},
{"mapsme"} /* legends */);
pythonScriptPath = base::JoinPath(dirForResults, kPythonBarError);
std::vector<std::string> labels;
std::vector<double> heights;
FillLabelsAndErrorTypeDistribution(labels, heights, errorTypeCounter);
CreatePythonBarByMap(pythonScriptPath, labels, {heights}, {"mapsme"} /* legends */, "Type of errors" /* xlabel */,
"Number of errors" /* ylabel */);
}
void RunBenchmarkComparison(std::vector<std::pair<RoutesBuilder::Result, std::string>> && mapsmeResults,
std::vector<std::pair<RoutesBuilder::Result, std::string>> && mapsmeOldResults,
std::string const & dirForResults)
{
BenchmarkResults benchmarkResults;
BenchmarkResults benchmarkOldResults;
ErrorTypeCounter errorTypeCounter;
ErrorTypeCounter errorTypeCounterOld;
std::vector<double> etaDiffsPercent;
std::vector<double> etaDiffs;
std::vector<TimeInfo> times;
for (size_t i = 0; i < mapsmeResults.size(); ++i)
{
auto const & mapsmeResult = mapsmeResults[i].first;
std::pair<RoutesBuilder::Result, std::string> mapsmeOldResultPair;
if (!FindAnotherResponse(mapsmeResult, mapsmeOldResults, mapsmeOldResultPair))
{
LOG(LDEBUG, ("Can not find pair for:", i));
continue;
}
auto const & mapsmeOldResult = mapsmeOldResultPair.first;
errorTypeCounter.PushError(mapsmeResult.m_code);
errorTypeCounterOld.PushError(mapsmeOldResult.m_code);
if (IsErrorCode(mapsmeResult.m_code) && !IsErrorCode(mapsmeOldResult.m_code))
{
auto const start = mercator::ToLatLon(mapsmeResult.m_params.m_checkpoints.GetStart());
auto const finish = mercator::ToLatLon(mapsmeResult.m_params.m_checkpoints.GetFinish());
LOG_FORCE(LWARNING, ("New version returns error code:", mapsmeResult.m_code,
"but old returns NoError for:", start, finish));
}
if (IsErrorCode(mapsmeResult.m_code) || IsErrorCode(mapsmeOldResult.m_code))
continue;
LogIfNotConsistent(mapsmeOldResult, mapsmeResult);
CHECK(!mapsmeOldResult.m_routes.empty() && !mapsmeResult.m_routes.empty(), ());
auto const etaDiff = (mapsmeOldResult.m_routes.back().m_eta - mapsmeResult.m_routes.back().m_eta);
auto const etaDiffPercent = etaDiff / mapsmeOldResult.m_routes.back().m_eta * 100.0;
etaDiffs.emplace_back(etaDiff);
etaDiffsPercent.emplace_back(etaDiffPercent);
auto const oldTime = mapsmeOldResult.m_buildTimeSeconds;
auto const newTime = mapsmeResult.m_buildTimeSeconds;
auto const diffPercent = (oldTime - newTime) / oldTime * 100.0;
// Warn about routes building time degradation.
double constexpr kSlowdownPercent = -10.0;
if (diffPercent < kSlowdownPercent)
{
auto const start = mercator::ToLatLon(mapsmeResult.m_params.m_checkpoints.GetStart());
auto const finish = mercator::ToLatLon(mapsmeResult.m_params.m_checkpoints.GetFinish());
LOG(LINFO, ("oldTime:", oldTime, "newTime:", newTime, "diffPercent:", diffPercent, start, finish));
}
benchmarkResults.PushBuildTime(mapsmeResult.m_buildTimeSeconds);
benchmarkOldResults.PushBuildTime(mapsmeOldResult.m_buildTimeSeconds);
times.emplace_back(mapsmeOldResult.m_buildTimeSeconds, mapsmeResult.m_buildTimeSeconds);
}
LOG(LINFO, ("Comparing", benchmarkResults.GetBuildTimes().size(), "routes."));
auto const oldAverage = benchmarkOldResults.GetAverageBuildTime();
auto const newAverage = benchmarkResults.GetAverageBuildTime();
auto const averageTimeDiff = (oldAverage - newAverage) / oldAverage * 100.0;
LOG(LINFO, ("Average route time building. "
"Old version:",
oldAverage, "New version:", newAverage, "(", -averageTimeDiff, "% )"));
std::vector<std::vector<m2::PointD>> graphics;
for (auto const & results : {benchmarkOldResults, benchmarkResults})
{
auto times = results.GetBuildTimes();
std::sort(times.begin(), times.end());
std::vector<m2::PointD> countToTimes(times.size());
for (size_t i = 0; i < countToTimes.size(); ++i)
{
countToTimes[i].x = times[i];
countToTimes[i].y = static_cast<double>(i + 1) / times.size() * 100.0;
}
graphics.emplace_back(std::move(countToTimes));
}
auto pythonScriptPath = base::JoinPath(dirForResults, kPythonGraphTimeAndCount);
CreatePythonGraphByPointsXY(pythonScriptPath, "Building time, seconds" /* xlabel */,
"Percent of routes built less than" /* ylabel */, graphics,
{"old mapsme", "new mapsme"} /* legends */);
std::vector<std::string> labels;
std::vector<std::vector<double>> errorsCount;
FillLabelsAndErrorTypeDistribution(labels, errorsCount, errorTypeCounter, errorTypeCounterOld);
pythonScriptPath = base::JoinPath(dirForResults, kPythonBarError);
CreatePythonBarByMap(pythonScriptPath, labels, errorsCount, {"old mapsme", "new mapsme"} /* legends */,
"Type of errors" /* xlabel */, "Number of errors" /* ylabel */);
auto const boostPercents = GetBoostPercents(benchmarkOldResults, benchmarkResults);
pythonScriptPath = base::JoinPath(dirForResults, kPythonBarBoostPercentDistr);
CreatePythonScriptForDistribution(pythonScriptPath, "Boost percent" /* title */, boostPercents);
pythonScriptPath = base::JoinPath(dirForResults, kPythonEtaDiff);
CreatePythonScriptForDistribution(pythonScriptPath, "ETA diff distribution" /* title */, etaDiffs);
pythonScriptPath = base::JoinPath(dirForResults, kPythonEtaDiffPercent);
CreatePythonScriptForDistribution(pythonScriptPath, "ETA diff percent distribution" /* title */, etaDiffsPercent);
std::vector<std::vector<double>> bars;
FillInfoAboutBuildTimeGroupByPreviousResults(labels, bars, std::move(times));
pythonScriptPath = base::JoinPath(dirForResults, kPythonSmartDistr);
CreatePythonBarByMap(pythonScriptPath, labels, bars, {"old mapsme", "new mapsme"} /* legends */,
"Intervals of groups (build time in old mapsme)" /* xlabel */,
"Boost\\nRight column is so lower/higher than the left\\n how much the average build time "
"has decreased in each group)" /* ylabel */,
false /* drawPercents */);
}
} // namespace routing_quality::routing_quality_tool

View file

@ -0,0 +1,18 @@
#pragma once
#include "routing/routes_builder/routes_builder.hpp"
#include <string>
#include <utility>
#include <vector>
namespace routing_quality::routing_quality_tool
{
using RouteResult = routing::routes_builder::RoutesBuilder::Result;
void RunBenchmarkStat(std::vector<std::pair<RouteResult, std::string>> const & mapsmeResults,
std::string const & dirForResults);
void RunBenchmarkComparison(std::vector<std::pair<RouteResult, std::string>> && mapsmeResults,
std::vector<std::pair<RouteResult, std::string>> && mapsmeOldResults,
std::string const & dirForResults);
} // namespace routing_quality::routing_quality_tool

View file

@ -0,0 +1,49 @@
#include "routing/routing_quality/routing_quality_tool/error_type_counter.hpp"
#include "base/assert.hpp"
namespace routing_quality::routing_quality_tool
{
void ErrorTypeCounter::PushError(routing::RouterResultCode code)
{
++m_errorCounter[routing::ToString(code)];
}
void ErrorTypeCounter::PushError(api::ResultCode code)
{
routing::RouterResultCode routingCode = routing::RouterResultCode::InternalError;
switch (code)
{
case api::ResultCode::ResponseOK: routingCode = routing::RouterResultCode::NoError; break;
case api::ResultCode::Error: routingCode = routing::RouterResultCode::RouteNotFound; break;
}
CHECK_NOT_EQUAL(routingCode, routing::RouterResultCode::InternalError,
("Wrong value of api::ResultCode:", static_cast<int>(code)));
PushError(routingCode);
}
void FillLabelsAndErrorTypeDistribution(std::vector<std::string> & labels, std::vector<double> & errorsTypeDistribution,
ErrorTypeCounter const & counter)
{
errorsTypeDistribution.clear();
for (auto const & [errorName, errorCount] : counter.GetErrorsDistribution())
{
labels.emplace_back(errorName);
errorsTypeDistribution.emplace_back(errorCount);
}
}
void FillLabelsAndErrorTypeDistribution(std::vector<std::string> & labels,
std::vector<std::vector<double>> & errorsTypeDistribution,
ErrorTypeCounter const & counter, ErrorTypeCounter const & counterOld)
{
errorsTypeDistribution.clear();
errorsTypeDistribution.resize(2);
FillLabelsAndErrorTypeDistribution(labels, errorsTypeDistribution[0], counter);
for (auto const & [_, errorCount] : counterOld.GetErrorsDistribution())
errorsTypeDistribution[1].emplace_back(errorCount);
}
} // namespace routing_quality::routing_quality_tool

View file

@ -0,0 +1,31 @@
#pragma once
#include "routing/routing_quality/api/api.hpp"
#include "routing/routing_callbacks.hpp"
#include <cstddef>
#include <map>
#include <string>
namespace routing_quality::routing_quality_tool
{
class ErrorTypeCounter
{
public:
void PushError(routing::RouterResultCode code);
void PushError(api::ResultCode code);
std::map<std::string, size_t> const & GetErrorsDistribution() const { return m_errorCounter; }
private:
// string representation of RouterResultCode to number of such codes.
std::map<std::string, size_t> m_errorCounter;
};
void FillLabelsAndErrorTypeDistribution(std::vector<std::string> & labels, std::vector<double> & errorsTypeDistribution,
ErrorTypeCounter const & counter);
void FillLabelsAndErrorTypeDistribution(std::vector<std::string> & labels,
std::vector<std::vector<double>> & errorsTypeDistribution,
ErrorTypeCounter const & counter, ErrorTypeCounter const & counterOld);
} // namespace routing_quality::routing_quality_tool

View file

@ -0,0 +1,336 @@
#include "routing/routing_quality/routing_quality_tool/benchmark_stat.hpp"
#include "routing/routing_quality/routing_quality_tool/error_type_counter.hpp"
#include "routing/routing_quality/routing_quality_tool/utils.hpp"
#include "routing/routing_quality/api/api.hpp"
#include "routing/routing_quality/waypoints.hpp"
#include "routing/routes_builder/routes_builder.hpp"
#include "platform/platform.hpp"
#include "base/exception.hpp"
#include "base/file_name_utils.hpp"
#include "base/logging.hpp"
#include <algorithm>
#include <exception>
#include <limits>
#include <utility>
#include <vector>
#include <gflags/gflags.h>
DEFINE_string(mapsme_old_results, "", "Path to directory with previous mapsme router results.");
DEFINE_string(mapsme_results, "", "Path to directory with mapsme router results.");
DEFINE_string(api_results, "", "Path to directory with api router results.");
DEFINE_string(save_results, "", "The directory where results of tool will be saved.");
DEFINE_double(kml_percent, 0.0,
"The percent of routes for which kml file will be generated."
"With kml files you can make screenshots with a desktop app");
DEFINE_bool(benchmark_stat, false, "Dump statistics about route time building.");
namespace
{
// Shows distribution of simularity in comparison mode.
static std::string const kPythonDistribution = "show_distribution.py";
static std::string const kPythonBarDistributionError = "show_errors_distribution.py";
double constexpr kBadETADiffPercent = std::numeric_limits<double>::max();
void PrintHelpAndExit()
{
std::stringstream usage;
usage << std::boolalpha;
usage << R"(Description:
This tool takes two paths to directory with routes, that were dumped by routes_builder_tool and calculate some metrics. Here possible options of execution:
--mapsme_results and --api_results are required to compare mapsme and api.
or
--mapsme_results and --mapsme_old_results are required to compare mapsme and old mapsme version.
or
--mapsme_results and --benchmark_stat are required to calculate benchmark statistic of mapsme.
or
--mapsme_results and --mapsme_old_results and --benchmark_stat are required to calculate different statistics of comparison mapsme and old mapsme version.
--save_results is required for saving results in this directory.
--kml_percent may be used in non-benchamrk mode for dumping kmls and visual comparison different routes.
)";
auto const addStringInfo = [&usage](auto const & arg, auto const & argValue)
{
using T = decltype(argValue);
usage << "\n\t" << arg << " is";
if (argValue == T{})
usage << " not set.";
else
usage << " set to: " << argValue;
};
addStringInfo("--mapsme_results", FLAGS_mapsme_results);
addStringInfo("--api_results", FLAGS_api_results);
addStringInfo("--mapsme_old_results", FLAGS_mapsme_old_results);
addStringInfo("--benchmark_stat", FLAGS_benchmark_stat);
addStringInfo("--save_results", FLAGS_save_results);
addStringInfo("--kml_percent", FLAGS_kml_percent);
usage << "\n\nType --help for usage.\n";
std::cout << usage.str();
exit(0);
}
bool HasHelpFlags(int argc, char ** argv)
{
for (int i = 0; i < argc; ++i)
{
auto const value = std::string(argv[i]);
if (value == "--help" || value == "-h")
return true;
}
return false;
}
} // namespace
using namespace routing;
using namespace routes_builder;
using namespace routing_quality;
using namespace routing_quality_tool;
void PrintResults(std::vector<Result> && results, RoutesSaver & routesSaver)
{
double sumSimilarity = 0.0;
double sumETADiffPercent = 0.0;
double goodETANumber = 0.0;
for (auto const & result : results)
{
sumSimilarity += result.m_similarity;
if (result.m_etaDiffPercent != kBadETADiffPercent)
{
sumETADiffPercent += result.m_etaDiffPercent;
goodETANumber += 1;
}
}
LOG(LINFO, ("Matched routes:", results.size()));
LOG(LINFO, ("Average similarity:", sumSimilarity / results.size()));
LOG(LINFO, ("Average eta difference by fullmathed routes:", sumETADiffPercent / goodETANumber, "%"));
auto const pythonScriptPath = base::JoinPath(FLAGS_save_results, kPythonDistribution);
std::vector<double> values;
values.reserve(results.size());
for (auto const & result : results)
values.emplace_back(result.m_similarity);
CreatePythonScriptForDistribution(pythonScriptPath, "Simularity distribution", values);
SimilarityCounter similarityCounter(routesSaver);
std::sort(results.begin(), results.end());
for (auto const & result : results)
similarityCounter.Push(result);
similarityCounter.CreateKmlFiles(FLAGS_kml_percent, results);
}
bool IsMapsmeVsApi()
{
return !FLAGS_mapsme_results.empty() && !FLAGS_api_results.empty();
}
bool IsMapsmeVsMapsme()
{
return !FLAGS_mapsme_results.empty() && !FLAGS_mapsme_old_results.empty() && !FLAGS_benchmark_stat;
}
bool IsMapsmeBenchmarkStat()
{
return !FLAGS_mapsme_results.empty() && FLAGS_benchmark_stat && FLAGS_mapsme_old_results.empty();
}
bool IsMapsmeVsMapsmeBenchmarkStat()
{
return !FLAGS_mapsme_results.empty() && FLAGS_benchmark_stat && !FLAGS_mapsme_old_results.empty();
}
void CheckDirExistence(std::string const & dir)
{
CHECK(Platform::IsDirectory(dir), ("Can not find directory:", dir));
}
template <typename AnotherResult>
void RunComparison(std::vector<std::pair<RoutesBuilder::Result, std::string>> && mapsmeResults,
std::vector<std::pair<AnotherResult, std::string>> && anotherResults)
{
ErrorTypeCounter mapsmeErrorCounter;
ErrorTypeCounter anotherErrorCounter;
ComparisonType type = IsMapsmeVsApi() ? ComparisonType::MapsmeVsApi : ComparisonType::MapsmeVsMapsme;
RoutesSaver routesSaver(FLAGS_save_results, type);
std::vector<Result> results;
size_t apiErrors = 0;
for (size_t i = 0; i < mapsmeResults.size(); ++i)
{
auto const & mapsmeResult = mapsmeResults[i].first;
auto const & mapsmeFile = mapsmeResults[i].second;
std::pair<AnotherResult, std::string> anotherResultPair;
if (!FindAnotherResponse(mapsmeResult, anotherResults, anotherResultPair))
{
LOG(LDEBUG, ("Can not find pair for:", i));
continue;
}
auto const & anotherResult = anotherResultPair.first;
auto const & anotherFile = anotherResultPair.second;
auto const & startLatLon = mercator::ToLatLon(anotherResult.GetStartPoint());
auto const & finishLatLon = mercator::ToLatLon(anotherResult.GetFinishPoint());
mapsmeErrorCounter.PushError(mapsmeResult.m_code);
anotherErrorCounter.PushError(anotherResult.m_code);
if (!mapsmeResult.IsCodeOK() && anotherResult.IsCodeOK())
{
routesSaver.PushError(mapsmeResult.m_code, startLatLon, finishLatLon);
continue;
}
if (anotherResult.GetRoutes().empty() || !anotherResult.IsCodeOK())
{
routesSaver.PushRivalError(startLatLon, finishLatLon);
++apiErrors;
continue;
}
auto maxSimilarity = std::numeric_limits<Similarity>::min();
double etaDiff = kBadETADiffPercent;
auto const & mapsmeRoute = mapsmeResult.GetRoutes().back();
for (auto const & route : anotherResult.GetRoutes())
{
auto const similarity =
metrics::CompareByNumberOfMatchedWaypoints(mapsmeRoute.m_followedPolyline, route.GetWaypoints());
if (maxSimilarity < similarity)
{
maxSimilarity = similarity;
if (maxSimilarity == 1.0)
{
etaDiff =
100.0 * std::abs(route.GetETA() - mapsmeRoute.GetETA()) / std::max(route.GetETA(), mapsmeRoute.GetETA());
}
}
}
results.emplace_back(mapsmeFile, anotherFile, startLatLon, finishLatLon, maxSimilarity, etaDiff);
}
std::string const anotherSourceName = IsMapsmeVsMapsme() ? "old mapsme," : "api,";
LOG(LINFO, (apiErrors, "routes can not build via", anotherSourceName, "but mapsme do built them."));
PrintResults(std::move(results), routesSaver);
std::vector<std::string> errorLabels;
std::vector<std::vector<double>> errorsCount;
FillLabelsAndErrorTypeDistribution(errorLabels, errorsCount, mapsmeErrorCounter, anotherErrorCounter);
auto const pythonScriptPath = base::JoinPath(FLAGS_save_results, kPythonBarDistributionError);
CreatePythonBarByMap(pythonScriptPath, errorLabels, errorsCount,
{"mapsme", IsMapsmeVsMapsme() ? "old mapsme" : "api"} /* legends */,
"Type of errors" /* xlabel */, "Number of errors" /* ylabel */);
}
void CheckArgs()
{
bool const modeIsChosen =
IsMapsmeVsApi() || IsMapsmeVsMapsme() || IsMapsmeBenchmarkStat() || IsMapsmeVsMapsmeBenchmarkStat();
if (!modeIsChosen)
PrintHelpAndExit();
CHECK(!FLAGS_save_results.empty(),
("\n\n\t--save_results is required. Tool will save results there.", "\n\nType --help for usage."));
}
int Main(int argc, char ** argv)
{
if (HasHelpFlags(argc, argv))
PrintHelpAndExit();
gflags::ParseCommandLineNonHelpFlags(&argc, &argv, true);
CheckArgs();
if (Platform::IsFileExistsByFullPath(FLAGS_save_results))
CheckDirExistence(FLAGS_save_results);
else
CHECK_EQUAL(Platform::MkDir(FLAGS_save_results), Platform::EError::ERR_OK, ());
CheckDirExistence(FLAGS_mapsme_results);
CHECK(0.0 <= FLAGS_kml_percent && FLAGS_kml_percent <= 100.0, ("--kml_percent should be in interval: [0.0, 100.0]."));
LOG(LINFO, ("Start loading mapsme results."));
auto mapsmeResults = LoadResults<RoutesBuilder::Result>(FLAGS_mapsme_results);
LOG(LINFO, ("Receive:", mapsmeResults.size(), "routes from --mapsme_results."));
if (IsMapsmeVsApi())
{
LOG(LINFO, ("Start loading api results."));
auto apiResults = LoadResults<api::Response>(FLAGS_api_results);
LOG(LINFO, ("Receive:", apiResults.size(), "routes from --api_results."));
RunComparison(std::move(mapsmeResults), std::move(apiResults));
}
else if (IsMapsmeVsMapsmeBenchmarkStat())
{
LOG(LINFO, ("Benchmark different mapsme versions. Start loading old mapsme results."));
auto oldMapsmeResults = LoadResults<RoutesBuilder::Result>(FLAGS_mapsme_old_results);
LOG(LINFO, ("Receive:", oldMapsmeResults.size(), "routes from --mapsme_old_results."));
RunBenchmarkComparison(std::move(mapsmeResults), std::move(oldMapsmeResults), FLAGS_save_results);
}
else if (IsMapsmeVsMapsme())
{
LOG(LINFO, ("Start loading another mapsme results."));
auto oldMapsmeResults = LoadResults<RoutesBuilder::Result>(FLAGS_mapsme_old_results);
LOG(LINFO, ("Receive:", oldMapsmeResults.size(), "routes from --mapsme_old_results."));
RunComparison(std::move(mapsmeResults), std::move(oldMapsmeResults));
}
else if (IsMapsmeBenchmarkStat())
{
LOG(LINFO, ("Running in benchmark stat mode."));
RunBenchmarkStat(mapsmeResults, FLAGS_save_results);
}
else
{
UNREACHABLE();
}
return 0;
}
int main(int argc, char ** argv)
{
try
{
return Main(argc, argv);
}
catch (RootException const & e)
{
LOG(LCRITICAL, ("Core exception:", e.Msg()));
}
catch (std::exception const & e)
{
LOG(LCRITICAL, ("Std exception:", e.what()));
}
catch (...)
{
LOG(LCRITICAL, ("Unknown exception."));
}
LOG(LINFO, ("Done."));
return 0;
}

View file

@ -0,0 +1,514 @@
#include "routing/routing_quality/routing_quality_tool/utils.hpp"
#include "kml/serdes.hpp"
#include "kml/types.hpp"
#include "geometry/point_with_altitude.hpp"
#include "base/assert.hpp"
#include "base/file_name_utils.hpp"
#include "base/logging.hpp"
#include <array>
#include <iomanip>
#include <utility>
using namespace routing::routes_builder;
namespace
{
using namespace routing_quality;
void PrintWithSpaces(std::string const & str, size_t maxN)
{
std::cout << str;
if (maxN <= str.size())
return;
maxN -= str.size();
for (size_t i = 0; i < maxN; ++i)
std::cout << " ";
}
std::vector<geometry::PointWithAltitude> ConvertToPointsWithAltitudes(std::vector<ms::LatLon> const & latlons)
{
std::vector<geometry::PointWithAltitude> result;
result.reserve(latlons.size());
for (size_t i = 0; i < latlons.size(); ++i)
result.emplace_back(mercator::FromLatLon(latlons[i]), geometry::kDefaultAltitudeMeters);
return result;
}
std::string MakeStringFromPercent(double percent)
{
std::stringstream ss;
ss << std::setprecision(2);
ss << percent;
std::string percentStr;
ss >> percentStr;
percentStr += "%";
return percentStr;
}
kml::BookmarkData CreateBookmark(m2::PointD const & point, bool isStart)
{
kml::BookmarkData bookmarkData;
bookmarkData.m_color = {isStart ? kml::PredefinedColor::Red : kml::PredefinedColor::Blue, 0};
bookmarkData.m_point = point;
return bookmarkData;
}
template <typename AnotherResult>
void SaveKmlFileDataTo(RoutesBuilder::Result const & mapsmeResult, AnotherResult const & apiResult,
std::string const & kmlFile)
{
static std::array<uint32_t, 5> const kColors = {
0xff0000ff, // Red
0x0000ffff, // Blue
0x00ff00ff, // Green
0xffa500ff, // Orange
0xa52a2aff // Brown
};
kml::FileData kml;
size_t colorNumber = 0;
auto const addTrack = [&](kml::MultiGeometry::LineT && line)
{
kml::TrackData track;
track.m_geometry.m_lines.push_back(std::move(line));
CHECK_LESS(colorNumber, kColors.size(), ());
track.m_layers = {{5.0 /* lineWidth */, {kml::PredefinedColor::None, kColors[colorNumber++]}}};
kml.m_tracksData.emplace_back(std::move(track));
};
auto const & start = apiResult.GetStartPoint();
auto const & finish = apiResult.GetFinishPoint();
kml.m_bookmarksData.emplace_back(CreateBookmark(start, true /* isStart */));
kml.m_bookmarksData.emplace_back(CreateBookmark(finish, false /* isStart */));
auto const & resultPoints = mapsmeResult.GetRoutes().back().m_followedPolyline.GetPolyline().GetPoints();
kml::MultiGeometry::LineT mmTrack;
mmTrack.reserve(resultPoints.size());
for (auto const & pt : resultPoints)
mmTrack.emplace_back(pt, geometry::kDefaultAltitudeMeters);
addTrack(std::move(mmTrack));
for (auto const & route : apiResult.GetRoutes())
addTrack(ConvertToPointsWithAltitudes(route.GetWaypoints()));
kml::SerializerKml ser(kml);
FileWriter sink(kmlFile);
ser.Serialize(sink);
}
template <typename T>
std::string CreatePythonArray(std::vector<T> const & data, bool isString = false)
{
std::stringstream ss;
ss << "[";
for (auto const & item : data)
{
if (isString)
ss << "\"";
ss << item;
if (isString)
ss << "\"";
ss << ",";
}
auto result = ss.str();
if (data.empty())
result += "]";
else
result.back() = ']';
return result;
}
} // namespace
namespace routing_quality
{
namespace routing_quality_tool
{
Result::Result(std::string mapsmeDumpPath, std::string anotherDumpPath, ms::LatLon const & start,
ms::LatLon const & finish, double similarity, double etaDiffPercent)
: m_mapsmeDumpPath(std::move(mapsmeDumpPath))
, m_anotherDumpPath(std::move(anotherDumpPath))
, m_start(start)
, m_finish(finish)
, m_similarity(similarity)
, m_etaDiffPercent(etaDiffPercent)
{}
bool Result::operator<(Result const & rhs) const
{
return m_similarity < rhs.m_similarity;
}
RoutesSaver::RoutesSaver(std::string targetDir, ComparisonType comparisonType)
: m_targetDir(std::move(targetDir))
, m_comparisonType(comparisonType)
{
OpenOutputStream();
}
void RoutesSaver::PushRoute(Result const & result)
{
WriteStartAndFinish(m_output, result.m_start, result.m_finish);
m_output << result.m_mapsmeDumpPath << " " << result.m_anotherDumpPath << std::endl;
}
void RoutesSaver::PushError(routing::RouterResultCode code, ms::LatLon const & start, ms::LatLon const & finish)
{
CHECK_NOT_EQUAL(code, routing::RouterResultCode::NoError, ("Only errors codes."));
auto & ofstream = m_errorRoutes[code];
if (!ofstream.is_open())
{
std::string const fullpath = base::JoinPath(m_targetDir, DebugPrint(code) + ".routes");
ofstream.open(fullpath);
CHECK(ofstream.good(), ("Can not open:", fullpath, "for writing."));
ofstream << std::setprecision(20);
LOG(LINFO, ("Save routes with error:", code, "to:", fullpath));
}
WriteStartAndFinish(ofstream, start, finish);
}
void RoutesSaver::PushRivalError(ms::LatLon const & start, ms::LatLon const & finish)
{
static std::string const kApiErrorsFile = "api_errors.routes";
if (!m_apiErrors.is_open())
{
std::string const & fullpath = base::JoinPath(m_targetDir, kApiErrorsFile);
m_apiErrors.open(fullpath);
CHECK(m_apiErrors.good(), ("Can not open:", fullpath, "for writing."));
m_apiErrors << std::setprecision(20);
LOG(LINFO, ("Routes that api can not build, but mapsme do, placed:", fullpath));
}
WriteStartAndFinish(m_apiErrors, start, finish);
}
void RoutesSaver::TurnToNextFile()
{
++m_fileNumber;
if (m_output.is_open())
m_output.close();
OpenOutputStream();
}
std::string RoutesSaver::GetCurrentPath() const
{
std::string const fullpath = base::JoinPath(m_targetDir, std::to_string(m_fileNumber) + ".routes");
return fullpath;
}
std::string const & RoutesSaver::GetTargetDir() const
{
return m_targetDir;
}
ComparisonType RoutesSaver::GetComparsionType() const
{
return m_comparisonType;
}
void RoutesSaver::OpenOutputStream()
{
std::string const & fullpath = GetCurrentPath();
m_output.open(fullpath);
CHECK(m_output.good(), ("Can not open:", fullpath, "for writing."));
m_output << std::setprecision(20);
}
void RoutesSaver::WriteStartAndFinish(std::ofstream & output, ms::LatLon const & start, ms::LatLon const & finish)
{
output << start.m_lat << " " << start.m_lon << " " << finish.m_lat << " " << finish.m_lon << std::endl;
}
// This function creates python script that shows the distribution error.
void CreatePythonScriptForDistribution(std::string const & pythonScriptPath, std::string const & title,
std::vector<double> const & values)
{
std::ofstream python(pythonScriptPath);
CHECK(python.good(), ("Can not open:", pythonScriptPath, "for writing."));
std::string pythonArray = "[";
for (auto const & value : values)
pythonArray += std::to_string(value) + ",";
if (values.empty())
pythonArray += "]";
else
pythonArray.back() = ']';
python << R"(
import numpy as np
import matplotlib.pyplot as plt
a = np.hstack()" +
pythonArray + R"()
plt.hist(a, bins='auto') # arguments are passed to np.histogram
plt.title(")" + title +
R"(")
plt.show()
)";
LOG(LINFO, ("Run: python", pythonScriptPath, "to look at:", title));
}
void CreatePythonGraphByPointsXY(std::string const & pythonScriptPath, std::string const & xlabel,
std::string const & ylabel, std::vector<std::vector<m2::PointD>> const & graphics,
std::vector<std::string> const & legends)
{
CHECK_EQUAL(legends.size(), graphics.size(), ());
std::ofstream python(pythonScriptPath);
CHECK(python.good(), ("Can not open:", pythonScriptPath, "for writing."));
std::string pythonArrayX = "[";
std::string pythonArrayY = "[";
std::string legendsArray = "[";
for (size_t i = 0; i < graphics.size(); ++i)
{
auto const & points = graphics[i];
pythonArrayX += "[";
pythonArrayY += "[";
legendsArray += "\"" + legends[i] + "\",";
for (auto const & point : points)
{
pythonArrayX += std::to_string(point.x) + ",";
pythonArrayY += std::to_string(point.y) + ",";
}
pythonArrayX += "],";
pythonArrayY += "],";
}
pythonArrayX.back() = ']';
pythonArrayY.back() = ']';
legendsArray.back() = ']';
python << R"(
import pylab
legends = )" + legendsArray +
R"(
xlist = )" + pythonArrayX +
R"(
ylist = )" + pythonArrayY +
R"(
for (x, y, l) in zip(xlist, ylist, legends):
pylab.plot(x, y, label=l)
pylab.xlabel(")" +
xlabel + R"(")
pylab.ylabel(")" +
ylabel + R"(")
pylab.legend()
pylab.tight_layout()
pylab.show()
)";
LOG(LINFO, ("Run: python", pythonScriptPath, "to look at:", ylabel, "versus", xlabel));
}
void CreatePythonBarByMap(std::string const & pythonScriptPath, std::vector<std::string> const & barLabels,
std::vector<std::vector<double>> const & barHeights, std::vector<std::string> const & legends,
std::string const & xlabel, std::string const & ylabel, bool drawPercents)
{
std::ofstream python(pythonScriptPath);
CHECK(python.good(), ("Can not open:", pythonScriptPath, "for writing."));
std::string labelsArray = CreatePythonArray(barLabels, true /* isString */);
std::string legendsArray = CreatePythonArray(legends, true /* isString */);
std::string counts = "[";
for (auto const & heights : barHeights)
counts += CreatePythonArray(heights) + ",";
if (barHeights.empty())
counts += "]";
else
counts.back() = ']';
std::string const formatString =
drawPercents ? "f'{round(height, 2)}({round(height / summ * 100, 2)}%)'" : "f'{round(height, 2)}'";
python << R"(
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
bar_width = 0.35
labels = )" + labelsArray +
R"(
legends = )" + legendsArray +
R"(
counts = )" + counts +
R"(
x = np.arange(len(labels)) # the label locations
width = 0.35 # the width of the bars
fig, ax = plt.subplots()
bars = []
for i in range(len(counts)):
bar = ax.bar(x + i * bar_width, counts[i], bar_width, label=legends[i])
bars.append(bar)
ax.set_ylabel(')" +
ylabel + R"(')
ax.set_title(')" +
xlabel + R"(')
pos = (bar_width * (len(counts) - 1)) / 2
ax.set_xticks(x + pos)
ax.set_xticklabels(labels)
ax.legend()
def autolabel(rects, counts_ith):
summ = 0
for count in counts_ith:
summ += count
for rect in rects:
height = rect.get_height()
ax.annotate()" +
formatString + R"(,
xy=(rect.get_x() + rect.get_width() / 2, height),
xytext=(0, 3), # 3 points vertical offset
textcoords="offset points",
ha='center', va='bottom')
for i in range(len(counts)):
autolabel(bars[i], counts[i])
fig.tight_layout()
plt.show()
)";
LOG(LINFO, ("Run: python", pythonScriptPath, "to look at bar:", ylabel, "versus", xlabel));
}
/// \brief |SimilarityCounter| groups routes that we compare by similarity, here we tune these groups.
// static
std::vector<SimilarityCounter::Interval> const SimilarityCounter::kIntervals = {
{"[0.0, 0.0]", 0, 0 + 1e-5}, {"[0.0, 0.1)", 0, 0.1}, {"[0.1, 0.2)", 0.1, 0.2}, {"[0.2, 0.3)", 0.2, 0.3},
{"[0.3, 0.6)", 0.3, 0.6}, {"[0.6, 0.8)", 0.6, 0.8}, {"[0.8, 1.0)", 0.8, 1.0}, {"[1.0, 1.0]", 1.0, 1.0 + 1e-5},
};
SimilarityCounter::SimilarityCounter(RoutesSaver & routesSaver) : m_routesSaver(routesSaver)
{
for (auto const & interval : kIntervals)
m_routesCounter.emplace_back(interval.m_name, 0);
}
SimilarityCounter::~SimilarityCounter()
{
size_t maxNumberLength = 0;
size_t maxKeyLength = 0;
size_t sum = 0;
for (auto const & item : m_routesCounter)
{
sum += item.m_routesNumber;
maxNumberLength = std::max(maxNumberLength, std::to_string(item.m_routesNumber).size());
maxKeyLength = std::max(maxKeyLength, item.m_intervalName.size());
}
for (auto const & item : m_routesCounter)
{
auto const percent = static_cast<double>(item.m_routesNumber) / sum * 100.0;
auto const percentStr = MakeStringFromPercent(percent);
PrintWithSpaces(item.m_intervalName, maxKeyLength + 1);
std::cout << " => ";
PrintWithSpaces(std::to_string(item.m_routesNumber), maxNumberLength + 1);
std::cout << "( " << percentStr << " )" << std::endl;
}
}
void SimilarityCounter::Push(Result const & result)
{
auto left = kIntervals[m_currentInterval].m_left;
auto right = kIntervals[m_currentInterval].m_right;
while (!(left <= result.m_similarity && result.m_similarity < right))
{
++m_currentInterval;
m_routesSaver.TurnToNextFile();
CHECK_LESS(m_currentInterval, m_routesCounter.size(), ());
left = kIntervals[m_currentInterval].m_left;
right = kIntervals[m_currentInterval].m_right;
}
CHECK_LESS(m_currentInterval, m_routesCounter.size(), ());
if (m_routesCounter[m_currentInterval].m_routesNumber == 0)
{
LOG(LINFO, ("Save routes with:", m_routesCounter[m_currentInterval].m_intervalName,
"similarity to:", m_routesSaver.GetCurrentPath()));
}
CHECK_LESS(m_currentInterval, m_routesCounter.size(), ());
++m_routesCounter[m_currentInterval].m_routesNumber;
m_routesSaver.PushRoute(result);
}
void SimilarityCounter::CreateKmlFiles(double percent, std::vector<Result> const & results)
{
size_t realResultIndex = 0;
for (size_t intervalId = 0; intervalId < m_routesCounter.size(); ++intervalId)
{
std::string savePath = base::JoinPath(m_routesSaver.GetTargetDir(), std::to_string(intervalId));
auto const currentSize = m_routesCounter[intervalId].m_routesNumber;
auto const resultSize = static_cast<size_t>(currentSize * percent / 100.0);
if (resultSize == 0)
continue;
auto const mkdirRes = Platform::MkDir(savePath);
CHECK(mkdirRes == Platform::EError::ERR_OK || mkdirRes == Platform::EError::ERR_FILE_ALREADY_EXISTS,
("Cannot create dir:", savePath));
LOG(LINFO, ("Saving", resultSize, "kmls for:", m_routesCounter[intervalId].m_intervalName, "to:", savePath));
auto const step = static_cast<size_t>(currentSize / resultSize);
for (size_t i = 0; i < currentSize; ++i)
{
if (i % step != 0)
{
++realResultIndex;
continue;
}
CHECK_LESS(realResultIndex, results.size(), ());
auto const mapsmeResult = RoutesBuilder::Result::Load(results[realResultIndex].m_mapsmeDumpPath);
std::string const kmlFile = base::JoinPath(savePath, std::to_string(i) + ".kml");
if (m_routesSaver.GetComparsionType() == ComparisonType::MapsmeVsApi)
{
auto const apiResult = api::Response::Load(results[realResultIndex].m_anotherDumpPath);
SaveKmlFileDataTo(mapsmeResult, apiResult, kmlFile);
}
else
{
auto const mapsmeAnotherResult = RoutesBuilder::Result::Load(results[realResultIndex].m_anotherDumpPath);
SaveKmlFileDataTo(mapsmeResult, mapsmeAnotherResult, kmlFile);
}
++realResultIndex;
}
}
}
} // namespace routing_quality_tool
} // namespace routing_quality

View file

@ -0,0 +1,184 @@
#pragma once
#include "routing/routes_builder/routes_builder.hpp"
#include "routing/routing_quality/api/api.hpp"
#include "routing/routing_callbacks.hpp"
#include "platform/platform.hpp"
#include "geometry/latlon.hpp"
#include "base/file_name_utils.hpp"
#include "base/logging.hpp"
#include <algorithm>
#include <fstream>
#include <map>
#include <string>
#include <utility>
#include <vector>
namespace routing_quality
{
namespace routing_quality_tool
{
enum class ComparisonType
{
MapsmeVsMapsme,
MapsmeVsApi
};
template <typename Result>
std::vector<std::pair<Result, std::string>> LoadResults(std::string const & dir)
{
std::vector<std::pair<Result, std::string>> result;
std::vector<std::string> files;
Platform::GetFilesByExt(dir, Result::kDumpExtension, files);
std::sort(files.begin(), files.end());
double lastPercent = 0.0;
double count = 0;
for (auto const & file : files)
{
double const curPercent = (count + 1) / files.size() * 100.0;
if (curPercent - lastPercent > 5.0 || count + 1 == files.size())
{
lastPercent = curPercent;
LOG(LINFO, ("Progress:", lastPercent, "%"));
}
auto const fullPath = base::JoinPath(dir, file);
result.emplace_back(Result::Load(fullPath), fullPath);
++count;
}
return result;
}
template <typename AnotherResult>
bool AreRoutesWithSameEnds(routing::routes_builder::RoutesBuilder::Result const & mapsmeResult,
AnotherResult const & anotherResult)
{
CHECK_EQUAL(mapsmeResult.m_params.m_checkpoints.GetPoints().size(), 2, ());
CHECK_EQUAL(mapsmeResult.GetVehicleType(), anotherResult.GetVehicleType(), ());
auto const & start = mapsmeResult.GetStartPoint();
auto const & finish = mapsmeResult.GetFinishPoint();
auto const & anotherStart = anotherResult.GetStartPoint();
auto const & anotherFinish = anotherResult.GetFinishPoint();
double constexpr kEps = 1e-10;
return AlmostEqualAbs(start, anotherStart, kEps) && AlmostEqualAbs(finish, anotherFinish, kEps);
}
template <typename AnotherResult>
bool FindAnotherResponse(routing::routes_builder::RoutesBuilder::Result const & mapsmeResult,
std::vector<std::pair<AnotherResult, std::string>> const & apiResults,
std::pair<AnotherResult, std::string> & anotherResult)
{
for (auto const & result : apiResults)
{
if (!AreRoutesWithSameEnds(mapsmeResult, result.first))
continue;
anotherResult = result;
return true;
}
return false;
}
struct Result
{
Result(std::string mapsmeDumpPath, std::string anotherDumpPath, ms::LatLon const & start, ms::LatLon const & finish,
double similarity, double etaDiffPercent);
bool operator<(Result const & rhs) const;
std::string m_mapsmeDumpPath;
std::string m_anotherDumpPath;
ms::LatLon m_start;
ms::LatLon m_finish;
// value in range: [0, 1]
double m_similarity;
double m_etaDiffPercent;
};
class RoutesSaver
{
public:
RoutesSaver(std::string targetDir, ComparisonType comparisonType);
void PushRoute(Result const & result);
void PushError(routing::RouterResultCode code, ms::LatLon const & start, ms::LatLon const & finish);
void PushRivalError(ms::LatLon const & start, ms::LatLon const & finish);
void TurnToNextFile();
std::string GetCurrentPath() const;
std::string const & GetTargetDir() const;
ComparisonType GetComparsionType() const;
private:
void OpenOutputStream();
void WriteStartAndFinish(std::ofstream & output, ms::LatLon const & start, ms::LatLon const & finish);
std::ofstream m_apiErrors;
std::string m_targetDir;
uint32_t m_fileNumber = 0;
std::ofstream m_output;
std::map<routing::RouterResultCode, std::ofstream> m_errorRoutes;
ComparisonType m_comparisonType;
};
class SimilarityCounter
{
public:
struct Interval
{
std::string m_name;
// [m_left, m_right)
double m_left;
double m_right;
};
struct Item
{
Item(std::string name, uint32_t n) : m_intervalName(std::move(name)), m_routesNumber(n) {}
std::string m_intervalName;
uint32_t m_routesNumber = 0;
};
static std::vector<Interval> const kIntervals;
explicit SimilarityCounter(RoutesSaver & routesSaver);
~SimilarityCounter();
void Push(Result const & result);
void CreateKmlFiles(double percent, std::vector<Result> const & results);
private:
RoutesSaver & m_routesSaver;
size_t m_currentInterval = 0;
std::vector<Item> m_routesCounter;
};
void CreatePythonScriptForDistribution(std::string const & pythonScriptPath, std::string const & title,
std::vector<double> const & values);
void CreatePythonGraphByPointsXY(std::string const & pythonScriptPath, std::string const & xlabel,
std::string const & ylabel, std::vector<std::vector<m2::PointD>> const & graphics,
std::vector<std::string> const & legends);
/// \brief Create python file, that show bar graph, where labels of bars are keys of |stat| and
/// heights area values of |stat|.
void CreatePythonBarByMap(std::string const & pythonScriptPath, std::vector<std::string> const & barLabels,
std::vector<std::vector<double>> const & barHeights, std::vector<std::string> const & legends,
std::string const & xlabel, std::string const & ylabel, bool drawPercents = true);
} // namespace routing_quality_tool
} // namespace routing_quality

View file

@ -0,0 +1,79 @@
#include "routing/routing_quality/waypoints.hpp"
#include "routing/base/followed_polyline.hpp"
#include "geometry/mercator.hpp"
#include "geometry/point2d.hpp"
#include "base/assert.hpp"
#include "base/logging.hpp"
#include <algorithm>
#include <utility>
namespace routing_quality
{
namespace metrics
{
Similarity CompareByNumberOfMatchedWaypoints(routing::FollowedPolyline polyline, Waypoints const & waypoints)
{
double constexpr kMaxDistanceFromRouteM = 15.0;
auto const size = waypoints.size();
CHECK_GREATER(size, 0, ());
size_t numberOfErrors = 0;
for (size_t i = 0; i < size; ++i)
{
auto const & ll = waypoints[i];
m2::RectD const rect = mercator::MetersToXY(ll.m_lon, ll.m_lat, kMaxDistanceFromRouteM /* metresR */);
auto const iter = polyline.UpdateProjection(rect);
if (iter.IsValid())
{
auto const distFromRouteM = mercator::DistanceOnEarth(iter.m_pt, mercator::FromLatLon(ll));
if (distFromRouteM <= kMaxDistanceFromRouteM)
continue;
}
++numberOfErrors;
}
CHECK_LESS_OR_EQUAL(numberOfErrors, size, ());
auto const result = ((size - numberOfErrors) / static_cast<double>(size));
return result;
}
Similarity CompareByNumberOfMatchedWaypoints(routing::FollowedPolyline const & polyline, ReferenceRoutes && candidates)
{
Similarity bestResult = 0.0;
for (size_t j = 0; j < candidates.size(); ++j)
{
auto const result = CompareByNumberOfMatchedWaypoints(polyline, candidates[j]);
bestResult = std::max(bestResult, result);
}
LOG(LDEBUG, ("Best result:", bestResult));
return bestResult;
}
} // namespace metrics
Similarity CheckWaypoints(Params const & params, ReferenceRoutes && referenceRoutes)
{
auto & builder = routing::routes_builder::RoutesBuilder::GetSimpleRoutesBuilder();
auto result = builder.ProcessTask(params);
return metrics::CompareByNumberOfMatchedWaypoints(result.GetRoutes().back().m_followedPolyline,
std::move(referenceRoutes));
}
bool CheckRoute(Params const & params, ReferenceRoutes && referenceRoutes)
{
return CheckWaypoints(params, std::move(referenceRoutes)) == 1.0;
}
bool CheckCarRoute(ms::LatLon const & start, ms::LatLon const & finish, ReferenceRoutes && referenceTracks)
{
Params const params(routing::VehicleType::Car, start, finish);
return CheckRoute(params, std::move(referenceTracks));
}
} // namespace routing_quality

View file

@ -0,0 +1,39 @@
#pragma once
#include "routing/routes_builder/routes_builder.hpp"
#include "routing/vehicle_mask.hpp"
#include "routing/base/followed_polyline.hpp"
#include "geometry/latlon.hpp"
#include <vector>
namespace routing_quality
{
using Params = routing::routes_builder::RoutesBuilder::Params;
using Waypoints = std::vector<ms::LatLon>;
/// \brief There can be more than one reference route.
using ReferenceRoutes = std::vector<Waypoints>;
// Value in range: [0, 1]
using Similarity = double;
namespace metrics
{
Similarity CompareByNumberOfMatchedWaypoints(routing::FollowedPolyline polyline, Waypoints const & waypoints);
Similarity CompareByNumberOfMatchedWaypoints(routing::FollowedPolyline const & polyline, ReferenceRoutes && candidates);
} // namespace metrics
/// \brief Checks how many reference waypoints the route contains.
/// \returns normalized value in range [0.0; 1.0].
Similarity CheckWaypoints(Params const & params, ReferenceRoutes && referenceRoutes);
/// \returns true if route from |start| to |finish| fully conforms one of |candidates|
/// and false otherwise.
bool CheckRoute(Params const & params, ReferenceRoutes && referenceRoutes);
bool CheckCarRoute(ms::LatLon const & start, ms::LatLon const & finish, ReferenceRoutes && referenceRoutes);
} // namespace routing_quality