diff --git a/routing/CMakeLists.txt b/routing/CMakeLists.txt index e330e6426a..ab58d8f5db 100644 --- a/routing/CMakeLists.txt +++ b/routing/CMakeLists.txt @@ -134,7 +134,10 @@ set( ) omim_add_library(${PROJECT_NAME} ${SRC}) -omim_add_test_subdirectory(routing_tests) -omim_add_test_subdirectory(routing_integration_tests) -omim_add_test_subdirectory(routing_consistency_tests) + +add_subdirectory(routing_quality) + omim_add_test_subdirectory(routing_benchmarks) +omim_add_test_subdirectory(routing_consistency_tests) +omim_add_test_subdirectory(routing_integration_tests) +omim_add_test_subdirectory(routing_tests) diff --git a/routing/routing_quality/CMakeLists.txt b/routing/routing_quality/CMakeLists.txt new file mode 100644 index 0000000000..16510ec74e --- /dev/null +++ b/routing/routing_quality/CMakeLists.txt @@ -0,0 +1,38 @@ +project(routing_quality) + +set( + SRC + utils.cpp + utils.hpp + waypoints.cpp + waypoints.hpp +) + +omim_add_library(${PROJECT_NAME} ${SRC}) + +omim_link_libraries( + ${PROJECT_NAME} + generator + routing + traffic + routing_common + transit + storage + indexer + platform + mwm_diff + bsdiff + geometry + coding + base + icu + jansson + protobuf + stats_client + ${LIBZ} +) + +omim_add_test_subdirectory(routing_quality_tests) + +link_qt5_core(${PROJECT_NAME}) +link_qt5_network(${PROJECT_NAME}) diff --git a/routing/routing_quality/routing_quality_tests/CMakeLists.txt b/routing/routing_quality/routing_quality_tests/CMakeLists.txt new file mode 100644 index 0000000000..3838f4df2f --- /dev/null +++ b/routing/routing_quality/routing_quality_tests/CMakeLists.txt @@ -0,0 +1,34 @@ +project(routing_quality_tests) + +set( + SRC + waypoints_tests.cpp +) + +omim_add_test(${PROJECT_NAME} ${SRC}) + +omim_link_libraries( + ${PROJECT_NAME} + routing_quality + generator + routing + traffic + routing_common + transit + storage + indexer + platform + mwm_diff + bsdiff + geometry + coding + base + icu + jansson + protobuf + stats_client + ${LIBZ} +) + +link_qt5_core(${PROJECT_NAME}) +link_qt5_network(${PROJECT_NAME}) diff --git a/routing/routing_quality/routing_quality_tests/waypoints_tests.cpp b/routing/routing_quality/routing_quality_tests/waypoints_tests.cpp new file mode 100644 index 0000000000..3c4d5b43b4 --- /dev/null +++ b/routing/routing_quality/routing_quality_tests/waypoints_tests.cpp @@ -0,0 +1,98 @@ +#include "testing/testing.hpp" + +#include "routing/routing_quality/utils.hpp" +#include "routing/routing_quality/waypoints.hpp" + +#include +#include + +using namespace std; +using namespace routing_quality; + +namespace +{ +UNIT_TEST(RoutingQuality_CompareSmoke) +{ + // From office to Aseeva 6. + RouteParams params; + params.m_waypoints = {{55.79723, 37.53777}, + {55.80634, 37.52886}}; + + ReferenceRoutes candidates; + ReferenceRoute first; + first.m_waypoints = {{55.79676, 37.54138}, + {55.79914, 37.53582}, + {55.80353, 37.52478}, + {55.80556, 37.52770}}; + first.m_factor = 1.0; + candidates.emplace_back(move(first)); + + TEST_EQUAL(CheckWaypoints(move(params), move(candidates)), 1.0, ()); +} + +UNIT_TEST(RoutingQuality_Zelegonrad2Domodedovo) +{ + // From Zelenograd to Domodedovo. + RouteParams params; + params.m_waypoints = {{55.98301, 37.21141}, + {55.42081, 37.89361}}; + + ReferenceRoutes candidates; + + // Through M-11 and MCA. + ReferenceRoute first; + first.m_waypoints = {{55.99751, 37.23804}, + {56.00719, 37.28533}, + {55.88759, 37.48068}, + {55.83513, 37.39569}, + {55.57103, 37.69203}}; + first.m_factor = 1.0; + candidates.emplace_back(move(first)); + + // Through M-10 and MCA. + ReferenceRoute second; + second.m_waypoints = {{55.99775, 37.24941}, + {55.88627, 37.43915}, + {55.86882, 37.40784}, + {55.58645, 37.71672}, + {55.57855, 37.75468}}; + second.m_factor = 1.0; + candidates.emplace_back(move(second)); + + // Through M-10 and Moscow center. + ReferenceRoute third; + third.m_waypoints = {{55.98974, 37.26966}, + {55.87625, 37.45129}, + {55.78288, 37.57118}, + {55.76092, 37.60087}, + {55.75662, 37.59861}, + {55.74976, 37.60654}, + {55.73173, 37.62060}, + {55.71785, 37.62237}, + {55.65615, 37.64623}, + {55.57855, 37.75468}}; + third.m_factor = 1.0; + candidates.emplace_back(move(third)); + + TEST_EQUAL(CheckWaypoints(move(params), move(candidates)), 1.0, ()); +} + +UNIT_TEST(RoutingQuality_Sokol2Mayakovskaya) +{ + // From Sokol to Mayakovskaya through Leningradsky Avenue but not through its alternate. + RouteParams params; + params.m_waypoints = {{55.80432, 37.51603}, + {55.77019, 37.59558}}; + + ReferenceRoutes candidates; + ReferenceRoute first; + // All points lie on the alternate so the result should be 0. + first.m_waypoints = {{55.79599, 37.54114}, + {55.78142, 37.57364}, + {55.77863, 37.57989}}; + first.m_factor = 1.0; + candidates.emplace_back(move(first)); + + TEST_EQUAL(CheckWaypoints(move(params), move(candidates)), 0.0, ()); +} +} // namespace diff --git a/routing/routing_quality/utils.cpp b/routing/routing_quality/utils.cpp new file mode 100644 index 0000000000..db108c2954 --- /dev/null +++ b/routing/routing_quality/utils.cpp @@ -0,0 +1,117 @@ +#include "routing/routing_quality/utils.hpp" + +#include "routing_common/num_mwm_id.hpp" + +#include "routing/index_router.hpp" +#include "routing/route.hpp" +#include "routing/routing_callbacks.hpp" + +#include "indexer/classificator_loader.hpp" +#include "indexer/data_source.hpp" + +#include "storage/country_info_getter.hpp" +#include "storage/country_parent_getter.hpp" +#include "storage/routing_helpers.hpp" + +#include "platform/local_country_file.hpp" +#include "platform/local_country_file_utils.hpp" +#include "platform/platform.hpp" + +#include "geometry/mercator.hpp" + +#include "base/assert.hpp" + +#include +#include +#include + +using namespace std; + +namespace +{ +class PolylineGetter +{ +public: + PolylineGetter() + { + classificator::Load(); + vector localFiles; + platform::FindAllLocalMapsAndCleanup(numeric_limits::max(), localFiles); + m_numMwmIds = make_shared(); + + for (auto const & localFile : localFiles) + { + UNUSED_VALUE(m_dataSource.RegisterMap(localFile)); + auto const & countryFile = localFile.GetCountryFile(); + auto const mwmId = m_dataSource.GetMwmIdByCountryFile(countryFile); + CHECK(mwmId.IsAlive(), ()); + // We have to exclude minsk-pass because we can't register mwm which is not from + // countries.txt. + if (mwmId.GetInfo()->GetType() == MwmInfo::COUNTRY && countryFile.GetName() != "minsk-pass") + m_numMwmIds->RegisterFile(countryFile); + } + } + + routing::FollowedPolyline operator()(routing_quality::RouteParams && params) + { + using namespace routing; + + unique_ptr cig = + storage::CountryInfoReader::CreateCountryInfoReader(GetPlatform()); + CHECK(cig, ()); + + auto const & infoGetter = *cig.get(); + + auto const countryFileGetter = [&infoGetter](m2::PointD const & pt) { + return infoGetter.GetRegionCountryId(pt); + }; + + auto const getMwmRectByName = [&infoGetter](string const & countryId) { + return infoGetter.GetLimitRectForLeaf(countryId); + }; + + auto countryParentGetter = make_unique(); + CHECK(countryParentGetter, ()); + traffic::TrafficCache cache; + IndexRouter indexRouter(params.m_type, false /* load altitudes */, *countryParentGetter, + countryFileGetter, getMwmRectByName, m_numMwmIds, + MakeNumMwmTree(*m_numMwmIds, infoGetter), cache, m_dataSource); + + RouterDelegate delegate; + Route route("" /* router */, 0 /* routeId */); + auto const result = indexRouter.CalculateRoute( + Checkpoints(routing_quality::FromLatLon(params.m_waypoints)), m2::PointD::Zero(), + false /* adjustToPrevRoute */, delegate, route); + + CHECK_EQUAL(result, RouterResultCode::NoError, ()); + CHECK(route.IsValid(), ()); + + return route.GetFollowedPolyline(); + } + +private: + FrozenDataSource m_dataSource; + shared_ptr m_numMwmIds; +}; +} // namespace + +namespace routing_quality +{ +routing::FollowedPolyline GetRouteFollowedPolyline(RouteParams && params) +{ + CHECK_GREATER_OR_EQUAL(params.m_waypoints.size(), 2, ()); + PolylineGetter get; + return get(move(params)); +} + +RoutePoints FromLatLon(Coordinates const & coords) +{ + CHECK(!coords.empty(), ()); + RoutePoints ret; + ret.reserve(coords.size()); + for (auto const & ll : coords) + ret.emplace_back(MercatorBounds::FromLatLon(ll)); + + return ret; +} +} // namespace routing_quality diff --git a/routing/routing_quality/utils.hpp b/routing/routing_quality/utils.hpp new file mode 100644 index 0000000000..acda4e5a6c --- /dev/null +++ b/routing/routing_quality/utils.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "routing/base/followed_polyline.hpp" +#include "routing/vehicle_mask.hpp" + +#include "geometry/latlon.hpp" +#include "geometry/point2d.hpp" + +#include + +namespace routing_quality +{ +using Coordinates = std::vector; + +struct RouteParams +{ + /// Waypoints which the route passes through. + Coordinates m_waypoints; + routing::VehicleType m_type = routing::VehicleType::Car; +}; + +using RoutePoints = std::vector; + +/// Builds the route based on |params| and returns its polyline. +routing::FollowedPolyline GetRouteFollowedPolyline(RouteParams && params); +RoutePoints FromLatLon(Coordinates const & coords); +} // namespace routing_quality diff --git a/routing/routing_quality/waypoints.cpp b/routing/routing_quality/waypoints.cpp new file mode 100644 index 0000000000..7744838759 --- /dev/null +++ b/routing/routing_quality/waypoints.cpp @@ -0,0 +1,62 @@ +#include "routing/routing_quality/waypoints.hpp" +#include "routing/routing_quality/utils.hpp" + +#include "geometry/mercator.hpp" +#include "geometry/point2d.hpp" + +#include "base/assert.hpp" +#include "base/logging.hpp" + +#include +#include + +using namespace std; + +namespace routing_quality +{ +namespace metrics +{ +Similarity CompareByNumberOfMatchedWaypoints(routing::FollowedPolyline && polyline, ReferenceRoutes && candidates) +{ + auto constexpr kMaxDistanceFromRouteM = 15; + Similarity bestResult = 0.0; + for (size_t j = 0; j < candidates.size(); ++j) + { + routing::FollowedPolyline current = polyline; + auto const & candidate = candidates[j]; + auto const & waypoints = candidate.m_waypoints; + 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 = MercatorBounds::MetresToXY(ll.lon, ll.lat, kMaxDistanceFromRouteM /* metresR */); + auto const iter = current.UpdateProjection(rect); + if (iter.IsValid()) + { + auto const distFromRouteM = MercatorBounds::DistanceOnEarth(iter.m_pt, MercatorBounds::FromLatLon(ll)); + if (distFromRouteM <= kMaxDistanceFromRouteM) + continue; + } + + LOG(LINFO, ("Can't find point", ll, "with index", i)); + ++numberOfErrors; + } + + CHECK_LESS_OR_EQUAL(numberOfErrors, size, ()); + auto const result = ((size - numberOfErrors) / static_cast(size)) * candidate.m_factor; + LOG(LINFO, ("Matching result", result, "for route with index", j)); + bestResult = max(bestResult, result); + } + + LOG(LINFO, ("Best result", bestResult)); + return bestResult; +} +} // namespace metrics + +Similarity CheckWaypoints(RouteParams && params, ReferenceRoutes && candidates) +{ + return metrics::CompareByNumberOfMatchedWaypoints(GetRouteFollowedPolyline(move(params)), move(candidates)); +} +} // namespace routing_quality diff --git a/routing/routing_quality/waypoints.hpp b/routing/routing_quality/waypoints.hpp new file mode 100644 index 0000000000..f97a1b3c7b --- /dev/null +++ b/routing/routing_quality/waypoints.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include "routing/routing_quality/utils.hpp" +#include "routing/vehicle_mask.hpp" + +#include "geometry/latlon.hpp" + +#include + +namespace routing_quality +{ +struct RouteParams; + +struct ReferenceRoute +{ + /// Waypoints which the route passes through. + Coordinates m_waypoints; + /// Value in range (0.0; 1.0] which indicates how desirable the route is. + double m_factor = 1.0; +}; + +/// There can be more than one reference route. +using ReferenceRoutes = std::vector; + +using Similarity = double; + +/// Checks how many reference waypoints the route contains. +/// Returns normalized value in range [0.0; 1.0]. +Similarity CheckWaypoints(RouteParams && params, ReferenceRoutes && candidates); +} // namespace routing_quality