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,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