From 81ed08055f4d20ee7540d60892fd7c9d2c2c681b Mon Sep 17 00:00:00 2001 From: Vladimir Byko-Ianko Date: Wed, 11 Jan 2017 16:16:29 +0300 Subject: [PATCH] Implementing restrictions and uturns graph where vertixs are edges and tests on it. --- .../restriction_collector_test.cpp | 2 +- .../generator_tests/restriction_test.cpp | 4 +- generator/restriction_collector.hpp | 2 +- generator/restriction_generator.cpp | 4 +- generator/restriction_writer.cpp | 2 +- routing/edge_estimator.cpp | 9 + routing/edge_estimator.hpp | 1 + routing/index_graph.cpp | 119 ++- routing/index_graph.hpp | 6 + routing/index_graph_starter.cpp | 10 + routing/index_graph_starter.hpp | 12 +- routing/restriction_loader.cpp | 2 +- routing/restriction_loader.hpp | 7 +- ...ion.cpp => restrictions_serialization.cpp} | 7 +- ...ion.hpp => restrictions_serialization.hpp} | 40 +- routing/routing.pro | 4 +- .../cumulative_restriction_test.cpp | 283 ++++++ routing/routing_tests/index_graph_test.cpp | 81 +- routing/routing_tests/index_graph_tools.cpp | 68 +- routing/routing_tests/index_graph_tools.hpp | 44 +- routing/routing_tests/restriction_test.cpp | 824 ++++++++++++++++++ routing/routing_tests/routing_tests.pro | 2 + routing/single_mwm_router.cpp | 5 + 23 files changed, 1470 insertions(+), 68 deletions(-) rename routing/{routing_serialization.cpp => restrictions_serialization.cpp} (86%) rename routing/{routing_serialization.hpp => restrictions_serialization.hpp} (82%) create mode 100644 routing/routing_tests/cumulative_restriction_test.cpp create mode 100644 routing/routing_tests/restriction_test.cpp diff --git a/generator/generator_tests/restriction_collector_test.cpp b/generator/generator_tests/restriction_collector_test.cpp index be561e4867..7dd9556b32 100644 --- a/generator/generator_tests/restriction_collector_test.cpp +++ b/generator/generator_tests/restriction_collector_test.cpp @@ -5,7 +5,7 @@ #include "generator/osm_id.hpp" #include "generator/restriction_collector.hpp" -#include "routing/routing_serialization.hpp" +#include "routing/restrictions_serialization.hpp" #include "platform/platform_tests_support/scoped_dir.hpp" #include "platform/platform_tests_support/scoped_file.hpp" diff --git a/generator/generator_tests/restriction_test.cpp b/generator/generator_tests/restriction_test.cpp index d0e4d2d03a..34b3f7fabf 100644 --- a/generator/generator_tests/restriction_test.cpp +++ b/generator/generator_tests/restriction_test.cpp @@ -83,10 +83,10 @@ void TestRestrictionBuilding(string const & restrictionContent, string const & m MwmSet::MwmHandle mwmHandle = index.GetMwmHandleById(regResult.first); TEST(mwmHandle.IsAlive(), ()); - RestrictionLoader const restrictionLoader(*mwmHandle.GetValue()); + RestrictionLoader restrictionLoader(*mwmHandle.GetValue()); RestrictionCollector const restrictionCollector(restrictionFullPath, mappingFullPath); - TEST_EQUAL(restrictionLoader.GetRestrictions(), restrictionCollector.GetRestrictions(), ()); + TEST_EQUAL(restrictionLoader.StealRestrictions(), restrictionCollector.GetRestrictions(), ()); } UNIT_TEST(RestrictionGenerationTest_NoRestriction) diff --git a/generator/restriction_collector.hpp b/generator/restriction_collector.hpp index 432acdc159..e9df69a428 100644 --- a/generator/restriction_collector.hpp +++ b/generator/restriction_collector.hpp @@ -1,6 +1,6 @@ #pragma once -#include "routing/routing_serialization.hpp" +#include "routing/restrictions_serialization.hpp" #include "std/functional.hpp" #include "std/limits.hpp" diff --git a/generator/restriction_generator.cpp b/generator/restriction_generator.cpp index 6613e12cd3..5ab6d9b395 100644 --- a/generator/restriction_generator.cpp +++ b/generator/restriction_generator.cpp @@ -33,9 +33,11 @@ bool BuildRoadRestrictions(string const & mwmPath, string const & restrictionPat auto const firstOnlyIt = lower_bound(restrictions.cbegin(), restrictions.cend(), Restriction(Restriction::Type::Only, {} /* links */), my::LessBy(&Restriction::m_type)); - RoutingHeader header; + + RestrictionHeader header; header.m_noRestrictionCount = base::checked_cast(distance(restrictions.cbegin(), firstOnlyIt)); header.m_onlyRestrictionCount = base::checked_cast(restrictions.size() - header.m_noRestrictionCount); + LOG(LINFO, ("Header info. There are", header.m_noRestrictionCount, "of type No restrictions and", header.m_onlyRestrictionCount, "of type Only restrictions")); diff --git a/generator/restriction_writer.cpp b/generator/restriction_writer.cpp index 604bc09e1e..97aee60ada 100644 --- a/generator/restriction_writer.cpp +++ b/generator/restriction_writer.cpp @@ -4,7 +4,7 @@ #include "generator/osm_id.hpp" #include "generator/restriction_collector.hpp" -#include "routing/routing_serialization.hpp" +#include "routing/restrictions_serialization.hpp" #include "base/logging.hpp" diff --git a/routing/edge_estimator.cpp b/routing/edge_estimator.cpp index 354b1283d1..31bbe0107a 100644 --- a/routing/edge_estimator.cpp +++ b/routing/edge_estimator.cpp @@ -43,6 +43,7 @@ public: void Finish() override; double CalcSegmentWeight(Segment const & segment, RoadGeometry const & road) const override; double CalcHeuristic(m2::PointD const & from, m2::PointD const & to) const override; + double GetUTurnPenalty() const override; private: TrafficCache const & m_trafficCache; @@ -101,6 +102,14 @@ double CarEdgeEstimator::CalcHeuristic(m2::PointD const & from, m2::PointD const { return TimeBetweenSec(from, to, m_maxSpeedMPS); } + +double CarEdgeEstimator::GetUTurnPenalty() const +{ + // Adds 2 minutes penalty for U-turn. The value is quite arbitrary + // and needs to be properly selected after a number of real-world + // experiments. + return 2 * 60; // seconds +} } // namespace namespace routing diff --git a/routing/edge_estimator.hpp b/routing/edge_estimator.hpp index 1b64e84fa0..8c4f59f1e8 100644 --- a/routing/edge_estimator.hpp +++ b/routing/edge_estimator.hpp @@ -24,6 +24,7 @@ public: virtual void Finish() = 0; virtual double CalcSegmentWeight(Segment const & segment, RoadGeometry const & road) const = 0; virtual double CalcHeuristic(m2::PointD const & from, m2::PointD const & to) const = 0; + virtual double GetUTurnPenalty() const = 0; static shared_ptr CreateForCar(IVehicleModel const & vehicleModel, traffic::TrafficCache const & trafficCache); diff --git a/routing/index_graph.cpp b/routing/index_graph.cpp index cba0dd3b56..f660d3940f 100644 --- a/routing/index_graph.cpp +++ b/routing/index_graph.cpp @@ -1,9 +1,94 @@ -#include "index_graph.hpp" +#include "routing/index_graph.hpp" + +#include "routing/restrictions_serialization.hpp" #include "base/assert.hpp" #include "base/exception.hpp" -#include "std/limits.hpp" +#include +#include + +namespace +{ +using namespace routing; +using namespace std; + +bool IsRestricted(RestrictionVec const & restrictions, vector const & edges, + Segment const & u, Segment const & v, bool isOutgoing) +{ + uint32_t const featureIdFrom = isOutgoing ? u.GetFeatureId() : v.GetFeatureId(); + uint32_t const featureIdTo = isOutgoing ? v.GetFeatureId() : u.GetFeatureId(); + + // Looking for at least one restriction of type No from |featureIdFrom| to |featureIdTo|. + if (binary_search(restrictions.cbegin(), restrictions.cend(), + Restriction(Restriction::Type::No, {featureIdFrom, featureIdTo}))) + { + if (featureIdFrom != featureIdTo) + return true; + + // @TODO(bykoianko) According to current code if a feature id is marked as a feature with + // restrictricted U-turn it's restricted to make a U-turn on the both ends of the feature. + // Generally speaking it's wrong. In osm there's information about the end of the feature + // where the U-turn is restricted. It's necessary to pass the data to mwm and to use it here. + // Please see test LineGraph_RestrictionF1F1No for details. + return u.GetSegmentIdx() == v.GetSegmentIdx() && u.IsForward() != v.IsForward(); + } + + // Taking into account that number of restrictions of type Only starting and + // ending with the same feature id is relevantly small it's possible to ignore such cases. + if (featureIdFrom == featureIdTo) + return false; + + // Looking for a range of restrictins of type Only starting from |featureIdFrom| + // and finishing with any feature. + auto const range = equal_range(restrictions.cbegin(), restrictions.cend(), + Restriction(Restriction::Type::Only, {featureIdFrom}), + [](Restriction const & r1, Restriction const & r2) { + CHECK(!r1.m_featureIds.empty(), ()); + CHECK(!r2.m_featureIds.empty(), ()); + if (r1.m_type != r2.m_type) + return r1.m_type < r2.m_type; + return r1.m_featureIds[0] < r2.m_featureIds[0]; + }); + auto const lower = range.first; + auto const upper = range.second; + + // Checking if there's at least one restrictions of type Only from |featureIdFrom| and + // ending with |featureIdTo|. If yes, returns false. + if (lower != upper && lower->m_featureIds[1] == featureIdTo) + return false; + + // Checking if there's at least one Only starting from |featureIdFrom|. + // If not, returns false. + if (lower == restrictions.cend() || lower->m_type != Restriction::Type::Only || + lower->m_featureIds[0] != featureIdFrom) + { + return false; + } + + // Note. At this point it's clear that there is an item in |restrictions| with type Only + // and starting with |featureIdFrom|. So there are two possibilities: + // * |edges| contains |it->m_featureIds[1]| => the end of |featureIdFrom| which implies in + // |restrictions| is considered. + // * |edges| does not contain |it->m_featureIds[1]| => either the other end or an intermediate + // point of |featureIdFrom| is considered. + // See test FGraph_RestrictionF0F2Only for details. + CHECK_EQUAL(lower->m_featureIds.size(), 2, ("Only two link restrictions are support.")); + for (SegmentEdge const & e : edges) + { + if (e.GetTarget().GetFeatureId() == lower->m_featureIds[isOutgoing ? 1 /* to */ : 0 /* from */]) + return true; + } + return false; +} + +bool IsUTurn(Segment const & u, Segment const & v) +{ + return u.GetFeatureId() == v.GetFeatureId() + && u.GetSegmentIdx() == v.GetSegmentIdx() + && u.IsForward() != v.IsForward(); +} +} // namespace namespace routing { @@ -28,6 +113,16 @@ void IndexGraph::GetEdgeList(Segment const & segment, bool isOutgoing, vector filteredEdges; + filteredEdges.reserve(edges.size()); + for (SegmentEdge const & e : edges) + { + if (!IsRestricted(m_restrictions, edges, segment, e.GetTarget(), isOutgoing)) + filteredEdges.push_back(e); + } + edges.swap(filteredEdges); } void IndexGraph::Build(uint32_t numJoints) { m_jointIndex.Build(m_roadIndex, numJoints); } @@ -39,6 +134,12 @@ void IndexGraph::Import(vector const & joints) Build(static_cast(joints.size())); } +void IndexGraph::SetRestrictions(RestrictionVec && restrictions) +{ + ASSERT(is_sorted(restrictions.cbegin(), restrictions.cend()), ()); + m_restrictions = move(restrictions); +} + double IndexGraph::CalcSegmentWeight(Segment const & segment) { return m_estimator->CalcSegmentWeight(segment, m_geometry.GetRoad(segment.GetFeatureId())); @@ -71,8 +172,7 @@ void IndexGraph::GetNeighboringEdge(Segment const & from, Segment const & to, bo vector & edges) { RoadPoint const rp = from.GetRoadPoint(isOutgoing); - if (from.GetFeatureId() == to.GetFeatureId() && from.GetSegmentIdx() == to.GetSegmentIdx() - && from.IsForward() != to.IsForward() + if (IsUTurn(from, to) && m_roadIndex.GetJointId(rp) == Joint::kInvalidId && rp.GetPointId() != 0 && rp.GetPointId() + 1 != m_geometry.GetRoad(from.GetFeatureId()).GetPointsCount()) @@ -80,7 +180,16 @@ void IndexGraph::GetNeighboringEdge(Segment const & from, Segment const & to, bo return; } - double const weight = CalcSegmentWeight(isOutgoing ? to : from); + double const weight = CalcSegmentWeight(isOutgoing ? to : from) + + GetPenalties(isOutgoing ? from : to, isOutgoing ? to : from); edges.emplace_back(to, weight); } + +double IndexGraph::GetPenalties(Segment const & u, Segment const & v) +{ + if (IsUTurn(u, v)) + return GetEstimator().GetUTurnPenalty(); + + return 0.0; +} } // namespace routing diff --git a/routing/index_graph.hpp b/routing/index_graph.hpp index e9b441dc02..36c4d87e1b 100644 --- a/routing/index_graph.hpp +++ b/routing/index_graph.hpp @@ -4,6 +4,7 @@ #include "routing/geometry.hpp" #include "routing/joint.hpp" #include "routing/joint_index.hpp" +#include "routing/restrictions_serialization.hpp" #include "routing/road_index.hpp" #include "routing/road_point.hpp" #include "routing/segment.hpp" @@ -40,6 +41,8 @@ public: void Build(uint32_t numJoints); void Import(vector const & joints); + void SetRestrictions(RestrictionVec && restrictions); + void PushFromSerializer(Joint::Id jointId, RoadPoint const & rp) { m_roadIndex.PushFromSerializer(jointId, rp); @@ -57,10 +60,13 @@ private: vector & edges); void GetNeighboringEdge(Segment const & from, Segment const & to, bool isOutgoing, vector & edges); + double GetPenalties(Segment const & u, Segment const & v); Geometry m_geometry; shared_ptr m_estimator; RoadIndex m_roadIndex; JointIndex m_jointIndex; + + RestrictionVec m_restrictions; }; } // namespace routing diff --git a/routing/index_graph_starter.cpp b/routing/index_graph_starter.cpp index 0ba068c559..58e9d63053 100644 --- a/routing/index_graph_starter.cpp +++ b/routing/index_graph_starter.cpp @@ -47,6 +47,16 @@ m2::PointD const & IndexGraphStarter::GetRoutePoint(vector const & segm return GetPoint(segments[pointIndex], true /* front */); } +void IndexGraphStarter::GetOutgoingEdgesList(TVertexType const & segment, vector & edges) +{ + GetEdgesList(segment, true /* isOutgoing */, edges); +} + +void IndexGraphStarter::GetIngoingEdgesList(TVertexType const & segment, vector & edges) +{ + GetEdgesList(segment, false /* isOutgoing */, edges); +} + void IndexGraphStarter::GetEdgesList(Segment const & segment, bool isOutgoing, vector & edges) { diff --git a/routing/index_graph_starter.hpp b/routing/index_graph_starter.hpp index 4ad31a298a..cf5d415108 100644 --- a/routing/index_graph_starter.hpp +++ b/routing/index_graph_starter.hpp @@ -52,16 +52,8 @@ public: m2::PointD const & GetRoutePoint(vector const & route, size_t pointIndex); void GetEdgesList(Segment const & segment, bool isOutgoing, vector & edges); - - void GetOutgoingEdgesList(TVertexType const & segment, vector & edges) - { - GetEdgesList(segment, true /* isOutgoing */, edges); - } - - void GetIngoingEdgesList(TVertexType const & segment, vector & edges) - { - GetEdgesList(segment, false /* isOutgoing */, edges); - } + void GetOutgoingEdgesList(TVertexType const & segment, vector & edges); + void GetIngoingEdgesList(TVertexType const & segment, vector & edges); double HeuristicCostEstimate(TVertexType const & from, TVertexType const & to) { diff --git a/routing/restriction_loader.cpp b/routing/restriction_loader.cpp index caba4ea903..d2208e87eb 100644 --- a/routing/restriction_loader.cpp +++ b/routing/restriction_loader.cpp @@ -1,5 +1,5 @@ #include "routing/restriction_loader.hpp" -#include "routing/routing_serialization.hpp" +#include "routing/restrictions_serialization.hpp" namespace routing { diff --git a/routing/restriction_loader.hpp b/routing/restriction_loader.hpp index 5cb2a8987a..6e58134225 100644 --- a/routing/restriction_loader.hpp +++ b/routing/restriction_loader.hpp @@ -1,6 +1,6 @@ #pragma once -#include "routing/routing_serialization.hpp" +#include "routing/restrictions_serialization.hpp" #include "indexer/index.hpp" @@ -17,11 +17,10 @@ public: explicit RestrictionLoader(MwmValue const & mwmValue); bool HasRestrictions() const { return !m_restrictions.empty(); } - RestrictionVec const & GetRestrictions() const { return m_restrictions; } - + RestrictionVec && StealRestrictions() { return move(m_restrictions); } private: unique_ptr m_reader; - RoutingHeader m_header; + RestrictionHeader m_header; RestrictionVec m_restrictions; string const m_countryFileName; }; diff --git a/routing/routing_serialization.cpp b/routing/restrictions_serialization.cpp similarity index 86% rename from routing/routing_serialization.cpp rename to routing/restrictions_serialization.cpp index a64aa441ad..1a72524b68 100644 --- a/routing/routing_serialization.cpp +++ b/routing/restrictions_serialization.cpp @@ -1,4 +1,4 @@ -#include "routing/routing_serialization.hpp" +#include "routing/restrictions_serialization.hpp" #include "std/sstream.hpp" @@ -15,8 +15,8 @@ uint32_t const Restriction::kInvalidFeatureId = numeric_limits::max(); bool Restriction::IsValid() const { - return !m_featureIds.empty() && find(begin(m_featureIds), end(m_featureIds), kInvalidFeatureId) - == end(m_featureIds); + return !m_featureIds.empty() && + find(begin(m_featureIds), end(m_featureIds), kInvalidFeatureId) == end(m_featureIds); } bool Restriction::operator==(Restriction const & restriction) const @@ -42,7 +42,6 @@ string ToString(Restriction::Type const & type) } string DebugPrint(Restriction::Type const & type) { return ToString(type); } - string DebugPrint(Restriction const & restriction) { ostringstream out; diff --git a/routing/routing_serialization.hpp b/routing/restrictions_serialization.hpp similarity index 82% rename from routing/routing_serialization.hpp rename to routing/restrictions_serialization.hpp index 8e1b747a7c..f0a75cb7df 100644 --- a/routing/routing_serialization.hpp +++ b/routing/restrictions_serialization.hpp @@ -37,7 +37,6 @@ struct Restriction }; Restriction(Type type, vector const & links) : m_featureIds(links), m_type(type) {} - bool IsValid() const; bool operator==(Restriction const & restriction) const; bool operator<(Restriction const & restriction) const; @@ -53,10 +52,9 @@ string ToString(Restriction::Type const & type); string DebugPrint(Restriction::Type const & type); string DebugPrint(Restriction const & restriction); -struct RoutingHeader +struct RestrictionHeader { - RoutingHeader() { Reset(); } - + RestrictionHeader() { Reset(); } template void Serialize(Sink & sink) const { @@ -89,13 +87,13 @@ struct RoutingHeader uint32_t m_onlyRestrictionCount; }; -static_assert(sizeof(RoutingHeader) == 12, "Wrong header size of routing section."); +static_assert(sizeof(RestrictionHeader) == 12, "Wrong header size of routing section."); class RestrictionSerializer { public: template - static void Serialize(RoutingHeader const & header, + static void Serialize(RestrictionHeader const & header, routing::RestrictionVec::const_iterator begin, routing::RestrictionVec::const_iterator end, Sink & sink) { @@ -105,11 +103,11 @@ public: } template - static void Deserialize(RoutingHeader const & header, routing::RestrictionVec & restrictions, + static void Deserialize(RestrictionHeader const & header, routing::RestrictionVec & restrictions, Source & src) { - DeserializeSingleType(routing::Restriction::Type::No, header.m_noRestrictionCount, - restrictions, src); + DeserializeSingleType(routing::Restriction::Type::No, header.m_noRestrictionCount, restrictions, + src); DeserializeSingleType(routing::Restriction::Type::Only, header.m_onlyRestrictionCount, restrictions, src); } @@ -139,17 +137,20 @@ private: routing::Restriction const & restriction = *begin; CHECK(restriction.IsValid(), ()); - CHECK_LESS(1, restriction.m_featureIds.size(), ("No sense in zero or one link restrictions.")); + CHECK_LESS(1, restriction.m_featureIds.size(), + ("No sense in zero or one link restrictions.")); - coding::DeltaCoder::Encode(bits, restriction.m_featureIds.size() - 1 /* number of link is two or more */); + coding::DeltaCoder::Encode( + bits, restriction.m_featureIds.size() - 1 /* number of link is two or more */); CHECK_LESS_OR_EQUAL(prevFirstLinkFeatureId, restriction.m_featureIds[0], ()); - coding::DeltaCoder::Encode(bits, - restriction.m_featureIds[0] - prevFirstLinkFeatureId + 1 /* making it greater than zero */); + coding::DeltaCoder::Encode(bits, restriction.m_featureIds[0] - prevFirstLinkFeatureId + + 1 /* making it greater than zero */); for (size_t i = 1; i < restriction.m_featureIds.size(); ++i) { - uint32_t const delta = bits::ZigZagEncode(static_cast(restriction.m_featureIds[i]) - - static_cast(restriction.m_featureIds[i - 1])); + uint32_t const delta = + bits::ZigZagEncode(static_cast(restriction.m_featureIds[i]) - + static_cast(restriction.m_featureIds[i - 1])); coding::DeltaCoder::Encode(bits, delta + 1 /* making it greater than zero */); } prevFirstLinkFeatureId = restriction.m_featureIds[0]; @@ -172,7 +173,7 @@ private: } size_t const numLinks = biasedLinkNumber + 1 /* number of link is two or more */; - routing::Restriction restriction(type, {} /* links */); + routing::Restriction restriction(type, {} /* links */); restriction.m_featureIds.resize(numLinks); auto const biasedFirstFeatureId = coding::DeltaCoder::Decode(bits); if (biasedFirstFeatureId == 0) @@ -189,9 +190,10 @@ private: LOG(LERROR, ("Decoded link restriction feature id delta is zero.")); return false; } - auto const delta = biasedDelta - 1; - restriction.m_featureIds[i] = static_cast( - bits::ZigZagDecode(delta) + restriction.m_featureIds[i - 1]); + + uint32_t const delta = biasedDelta - 1; + restriction.m_featureIds[i] = + static_cast(bits::ZigZagDecode(delta) + restriction.m_featureIds[i - 1]); } prevFirstLinkFeatureId = restriction.m_featureIds[0]; diff --git a/routing/routing.pro b/routing/routing.pro index 1635233426..44426f226b 100644 --- a/routing/routing.pro +++ b/routing/routing.pro @@ -42,6 +42,7 @@ SOURCES += \ pedestrian_directions.cpp \ pedestrian_model.cpp \ restriction_loader.cpp \ + restrictions_serialization.cpp \ road_graph.cpp \ road_graph_router.cpp \ road_index.cpp \ @@ -51,7 +52,6 @@ SOURCES += \ routing_algorithm.cpp \ routing_helpers.cpp \ routing_mapping.cpp \ - routing_serialization.cpp \ routing_session.cpp \ single_mwm_router.cpp \ speed_camera.cpp \ @@ -96,6 +96,7 @@ HEADERS += \ pedestrian_directions.hpp \ pedestrian_model.hpp \ restriction_loader.hpp \ + restrictions_serialization.hpp \ road_graph.hpp \ road_graph_router.hpp \ road_index.hpp \ @@ -109,7 +110,6 @@ HEADERS += \ routing_helpers.hpp \ routing_mapping.hpp \ routing_result_graph.hpp \ - routing_serialization.hpp \ routing_session.hpp \ routing_settings.hpp \ segment.hpp \ diff --git a/routing/routing_tests/cumulative_restriction_test.cpp b/routing/routing_tests/cumulative_restriction_test.cpp new file mode 100644 index 0000000000..ad5d4fe06b --- /dev/null +++ b/routing/routing_tests/cumulative_restriction_test.cpp @@ -0,0 +1,283 @@ +#include "testing/testing.hpp" + +#include "routing/routing_tests/index_graph_tools.hpp" + +#include "routing/car_model.hpp" +#include "routing/geometry.hpp" + +#include "traffic/traffic_cache.hpp" + +#include "geometry/point2d.hpp" + +#include +#include + +namespace +{ +using namespace routing; +using namespace routing_test; + +// Finish +// 3 * +// ^ +// F5 +// | +// 2 * * +// ↖ ↗ ↖ +// F2 F3 F4 +// ↖ ↗ ↖ +// 1 * * +// ↗ ↖ +// F0 F1 +// ↗ ↖ +// 0 * * +// 0 1 2 3 +// Start +// Note. This graph contains of 6 one segment directed features. +unique_ptr BuildXYGraph() +{ + unique_ptr loader = make_unique(); + loader->AddRoad(0 /* featureId */, true /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{0.0, 0.0}, {1.0, 1.0}})); + loader->AddRoad(1 /* featureId */, true /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{2.0, 0.0}, {1.0, 1.0}})); + loader->AddRoad(2 /* featureId */, true /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{1.0, 1.0}, {0.0, 2.0}})); + loader->AddRoad(3 /* featureId */, true /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{1.0, 1.0}, {2.0, 2.0}})); + loader->AddRoad(4 /* featureId */, true /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{3.0, 1.0}, {2.0, 2.0}})); + loader->AddRoad(5 /* featureId */, true /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{2.0, 2.0}, {2.0, 3.0}})); + + vector const joints = { + MakeJoint({{0 /* feature id */, 0 /* point id */}}), /* joint at point (0, 0) */ + MakeJoint({{1, 0}}), /* joint at point (2, 0) */ + MakeJoint({{0, 1}, {1, 1}, {2, 0}, {3, 0}}), /* joint at point (1, 1) */ + MakeJoint({{2, 1}}), /* joint at point (0, 2) */ + MakeJoint({{3, 1}, {4, 1}, {5, 0}}), /* joint at point (2, 2) */ + MakeJoint({{4, 0}}), /* joint at point (3, 1) */ + MakeJoint({{5, 1}}), /* joint at point (2, 3) */ + }; + + traffic::TrafficCache const trafficCache; + unique_ptr graph = + make_unique(move(loader), CreateEstimator(trafficCache)); + graph->Import(joints); + return graph; +} + +// Route through XY graph without any restrictions. +UNIT_TEST(XYGraph) +{ + unique_ptr graph = BuildXYGraph(); + IndexGraphStarter starter(*graph, + IndexGraphStarter::FakeVertex(1, 0, m2::PointD(2, 0)) /* start */, + IndexGraphStarter::FakeVertex(5, 0, m2::PointD(2, 3)) /* finish */); + vector const expectedGeom = {{2 /* x */, 0 /* y */}, {1, 1}, {2, 2}, {2, 3}}; + TestRouteGeometry(starter, AStarAlgorithm::Result::OK, expectedGeom); +} + +// Route through XY graph with one restriciton (type only) from F1 to F3. +UNIT_CLASS_TEST(RestrictionTest, XYGraph_RestrictionF1F3Only) +{ + Init(BuildXYGraph()); + RestrictionVec restrictions = { + {Restriction::Type::Only, {1 /* feature from */, 3 /* feature to */}}}; + + vector const expectedGeom = {{2 /* x */, 0 /* y */}, {1, 1}, {2, 2}, {2, 3}}; + TestRestrictions(expectedGeom, AStarAlgorithm::Result::OK, + routing::IndexGraphStarter::FakeVertex(1, 0, m2::PointD(2, 0)) /* start */, + routing::IndexGraphStarter::FakeVertex(5, 0, m2::PointD(2, 3)) /* finish */, + move(restrictions), *this); +} + +// Route through XY graph with one restriciton (type only) from F3 to F5. +UNIT_CLASS_TEST(RestrictionTest, XYGraph_RestrictionF3F5Only) +{ + Init(BuildXYGraph()); + RestrictionVec restrictions = { + {Restriction::Type::Only, {3 /* feature from */, 5 /* feature to */}}}; + + vector const expectedGeom = {{2 /* x */, 0 /* y */}, {1, 1}, {2, 2}, {2, 3}}; + TestRestrictions(expectedGeom, AStarAlgorithm::Result::OK, + routing::IndexGraphStarter::FakeVertex(1, 0, m2::PointD(2, 0)) /* start */, + routing::IndexGraphStarter::FakeVertex(5, 0, m2::PointD(2, 3)) /* finish */, + move(restrictions), *this); +} + +// Cumulative case. Route through XY graph with two restricitons (type only) applying +// in all possible orders. +UNIT_CLASS_TEST(RestrictionTest, XYGraph_PermutationsF3F5OnlyF1F3Only) +{ + Init(BuildXYGraph()); + RestrictionVec restrictions = { + {Restriction::Type::Only, {1 /* feature from */, 3 /* feature to */}}, + {Restriction::Type::Only, {3 /* feature from */, 5 /* feature to */}}}; + + vector const expectedGeom = {{2 /* x */, 0 /* y */}, {1, 1}, {2, 2}, {2, 3}}; + TestRestrictions(expectedGeom, AStarAlgorithm::Result::OK, + routing::IndexGraphStarter::FakeVertex(1, 0, m2::PointD(2, 0)) /* start */, + routing::IndexGraphStarter::FakeVertex(5, 0, m2::PointD(2, 3)) /* finish */, + move(restrictions), *this); +} + +// Cumulative case. Route through XY graph with two restricitons (type only and type no) applying +// in all possible orders. +UNIT_CLASS_TEST(RestrictionTest, XYGraph_PermutationsF3F5OnlyAndF0F2No) +{ + Init(BuildXYGraph()); + + RestrictionVec restrictions = { + {Restriction::Type::No, {1 /* feature from */, 2 /* feature to */}}, + {Restriction::Type::Only, {3 /* feature from */, 5 /* feature to */}}}; + vector const expectedGeom = {{2 /* x */, 0 /* y */}, {1, 1}, {2, 2}, {2, 3}}; + TestRestrictions(expectedGeom, AStarAlgorithm::Result::OK, + routing::IndexGraphStarter::FakeVertex(1, 0, m2::PointD(2, 0)) /* start */, + routing::IndexGraphStarter::FakeVertex(5, 0, m2::PointD(2, 3)) /* finish */, + move(restrictions), *this); +} + +// Cumulative case. Trying to build route through XY graph with two restricitons applying +// according to the order. First from F3 to F5 (type only) +// and then and from F1 to F3 (type no). +UNIT_CLASS_TEST(RestrictionTest, XYGraph_RestrictionF3F5OnlyAndF1F3No) +{ + Init(BuildXYGraph()); + RestrictionVec restrictions = { + {Restriction::Type::No, {1 /* feature from */, 3 /* feature to */}}, + {Restriction::Type::Only, {3 /* feature from */, 5 /* feature to */}}}; + + TestRestrictions({} /* expectedGeom */, AStarAlgorithm::Result::NoPath, + routing::IndexGraphStarter::FakeVertex(1, 0, m2::PointD(2, 0)) /* start */, + routing::IndexGraphStarter::FakeVertex(5, 0, m2::PointD(2, 3)) /* finish */, + move(restrictions), *this); +} + +// Finish +// 3 * * +// ↖ ↗ +// F5 F6 +// ↖ ↗ +// 2 * * +// ↖ ↗ ↖ +// F2 F3 F4 +// ↖ ↗ ↖ +// 1 * * +// ↗ ↖ ^ +// F0 F1 F8 +// ↗ ↖ | +// 0 * *--F7--->* +// ^ +// F9 +// | +//-1 * +// 0 1 2 3 +// Start +// Note. This graph contains of 9 one segment directed features. +unique_ptr BuildXXGraph() +{ + unique_ptr loader = make_unique(); + loader->AddRoad(0 /* featureId */, true /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{0.0, 0.0}, {1.0, 1.0}})); + loader->AddRoad(1 /* featureId */, true /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{2.0, 0.0}, {1.0, 1.0}})); + loader->AddRoad(2 /* featureId */, true /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{1.0, 1.0}, {0.0, 2.0}})); + loader->AddRoad(3 /* featureId */, true /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{1.0, 1.0}, {2.0, 2.0}})); + loader->AddRoad(4 /* featureId */, true /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{3.0, 1.0}, {2.0, 2.0}})); + loader->AddRoad(5 /* featureId */, true /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{2.0, 2.0}, {1.0, 3.0}})); + loader->AddRoad(6 /* featureId */, true /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{2.0, 2.0}, {3.0, 3.0}})); + loader->AddRoad(7 /* featureId */, true /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{2.0, 0.0}, {3.0, 0.0}})); + loader->AddRoad(8 /* featureId */, true /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{3.0, 0.0}, {3.0, 1.0}})); + loader->AddRoad(9 /* featureId */, true /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{2.0, -1.0}, {2.0, 0.0}})); + + vector const joints = { + MakeJoint({{0 /* feature id */, 0 /* point id */}}), /* joint at point (0, 0) */ + MakeJoint({{1, 0}, {7, 0}, {9, 1}}), /* joint at point (2, 0) */ + MakeJoint({{0, 1}, {1, 1}, {2, 0}, {3, 0}}), /* joint at point (1, 1) */ + MakeJoint({{2, 1}}), /* joint at point (0, 2) */ + MakeJoint({{3, 1}, {4, 1}, {5, 0}, {6, 0}}), /* joint at point (2, 2) */ + MakeJoint({{4, 0}, {8, 1}}), /* joint at point (3, 1) */ + MakeJoint({{5, 1}}), /* joint at point (1, 3) */ + MakeJoint({{6, 1}}), /* joint at point (3, 3) */ + MakeJoint({{7, 1}, {8, 0}}), /* joint at point (3, 0) */ + MakeJoint({{9, 0}}), /* joint at point (2, -1) */ + }; + + traffic::TrafficCache const trafficCache; + unique_ptr graph = + make_unique(move(loader), CreateEstimator(trafficCache)); + graph->Import(joints); + return graph; +} + +// Route through XY graph without any restrictions. +UNIT_CLASS_TEST(RestrictionTest, XXGraph) +{ + Init(BuildXXGraph()); + RestrictionVec restrictions = {}; + vector const expectedGeom = {{2 /* x */, -1 /* y */}, {2, 0}, {1, 1}, {2, 2}, {3, 3}}; + TestRestrictions(expectedGeom, AStarAlgorithm::Result::OK, + routing::IndexGraphStarter::FakeVertex(9, 0, m2::PointD(2, -1)), /* start */ + routing::IndexGraphStarter::FakeVertex(6, 0, m2::PointD(3, 3)), /* finish */ + move(restrictions), *this); +} + +// Cumulative case. Route through XX graph with two restricitons (type only) applying +// in all possible orders. +UNIT_CLASS_TEST(RestrictionTest, XXGraph_PermutationsF1F3OnlyAndF3F6Only) +{ + Init(BuildXXGraph()); + RestrictionVec restrictions = { + {Restriction::Type::Only, {1 /* feature from */, 3 /* feature to */}}, + {Restriction::Type::Only, {3 /* feature from */, 6 /* feature to */}}}; + + vector const expectedGeom = {{2 /* x */, -1 /* y */}, {2, 0}, {1, 1}, {2, 2}, {3, 3}}; + TestRestrictions(expectedGeom, AStarAlgorithm::Result::OK, + routing::IndexGraphStarter::FakeVertex(9, 0, m2::PointD(2, -1)), /* start */ + routing::IndexGraphStarter::FakeVertex(6, 0, m2::PointD(3, 3)), /* finish */ + move(restrictions), *this); +} + +// Route through XX graph with one restriciton (type no) from F1 to F3. +UNIT_CLASS_TEST(RestrictionTest, XXGraph_RestrictionF1F3No) +{ + Init(BuildXXGraph()); + RestrictionVec restrictions = { + {Restriction::Type::No, {1 /* feature from */, 3 /* feature to */}}}; + vector const expectedGeom = { + {2 /* x */, -1 /* y */}, {2, 0}, {3, 0}, {3, 1}, {2, 2}, {3, 3}}; + + TestRestrictions(expectedGeom, AStarAlgorithm::Result::OK, + routing::IndexGraphStarter::FakeVertex(9, 0, m2::PointD(2, -1)), /* start */ + routing::IndexGraphStarter::FakeVertex(6, 0, m2::PointD(3, 3)), /* finish */ + move(restrictions), *this); +} + +// Cumulative case. Route through XX graph with four restricitons of different types applying +// in all possible orders. +UNIT_CLASS_TEST(RestrictionTest, XXGraph_PermutationsF1F3NoF7F8OnlyF8F4OnlyF4F6Only) +{ + Init(BuildXXGraph()); + RestrictionVec restrictions = { + {Restriction::Type::No, {1 /* feature from */, 3 /* feature to */}}, + {Restriction::Type::Only, {4 /* feature from */, 6 /* feature to */}}, + {Restriction::Type::Only, {7 /* feature from */, 8 /* feature to */}}, + {Restriction::Type::Only, {8 /* feature from */, 4 /* feature to */}}}; + + vector const expectedGeom = { + {2 /* x */, -1 /* y */}, {2, 0}, {3, 0}, {3, 1}, {2, 2}, {3, 3}}; + TestRestrictions(expectedGeom, AStarAlgorithm::Result::OK, + routing::IndexGraphStarter::FakeVertex(9, 0, m2::PointD(2, -1)), /* start */ + routing::IndexGraphStarter::FakeVertex(6, 0, m2::PointD(3, 3)), /* finish */ + move(restrictions), *this); +} +} // namespace diff --git a/routing/routing_tests/index_graph_test.cpp b/routing/routing_tests/index_graph_test.cpp index 2e690f5219..d76872de60 100644 --- a/routing/routing_tests/index_graph_test.cpp +++ b/routing/routing_tests/index_graph_test.cpp @@ -31,12 +31,10 @@ void TestRoute(IndexGraph & graph, IndexGraphStarter::FakeVertex const & start, IndexGraphStarter::FakeVertex const & finish, size_t expectedLength, vector const * expectedRoute = nullptr) { - LOG(LINFO, ("Test route", start.GetFeatureId(), ",", start.GetSegmentIdx(), "=>", - finish.GetFeatureId(), ",", finish.GetSegmentIdx())); - IndexGraphStarter starter(graph, start, finish); vector route; - auto const resultCode = CalculateRoute(starter, route); + double timeSec; + auto const resultCode = CalculateRoute(starter, route, timeSec); TEST_EQUAL(resultCode, AStarAlgorithm::Result::OK, ()); TEST_GREATER(route.size(), 2, ()); @@ -386,4 +384,79 @@ UNIT_TEST(SerializeSimpleGraph) TEST_EQUAL(graph.GetJointId({2, 1}), Joint::kInvalidId, ()); } } + +// Finish +// 0.0004 * +// ^ +// | +// F2 +// | +// | +// 0.0003 *---------* +// | | +// | | +// | | +// | | +// | | +// 0.0002 * * +// | | +// | | +// 0.00015 * F0 * +// \ / +// \ / +// 0.0001 *---F0-*----* +// ^ +// | +// F1 +// | +// | +// 0 * Start +// 0 0.0001 0.0002 +// F0 is a two-way feature with a loop and F1 and F2 are an one-way one-segment features. +unique_ptr BuildLoopGraph() +{ + unique_ptr loader = make_unique(); + loader->AddRoad(0 /* feature id */, false /* one way */, 100.0 /* speed */, + RoadGeometry::Points({{0.0002, 0.0001}, + {0.00015, 0.0001}, + {0.0001, 0.0001}, + {0.00015, 0.00015}, + {0.00015, 0.0002}, + {0.00015, 0.0003}, + {0.00005, 0.0003}, + {0.00005, 0.0002}, + {0.00005, 0.00015}, + {0.0001, 0.0001}})); + loader->AddRoad(1 /* feature id */, true /* one way */, 100.0 /* speed */, + RoadGeometry::Points({{0.0002, 0.0}, {0.0002, 0.0001}})); + loader->AddRoad(2 /* feature id */, true /* one way */, 100.0 /* speed */, + RoadGeometry::Points({{0.00005, 0.0003}, {0.00005, 0.0004}})); + + vector const joints = { + MakeJoint({{0 /* feature id */, 2 /* point id */}, {0, 9}}), /* joint at point (0.0002, 0) */ + MakeJoint({{1, 1}, {0, 0}}), /* joint at point (0.0002, 0.0001) */ + MakeJoint({{0, 6}, {2, 0}}), /* joint at point (0.00005, 0.0003) */ + MakeJoint({{2, 1}}), /* joint at point (0.00005, 0.0004) */ + }; + + traffic::TrafficCache const trafficCache; + unique_ptr graph = + make_unique(move(loader), CreateEstimator(trafficCache)); + graph->Import(joints); + return graph; +} + +// This test checks that the route from Start to Finish doesn't make an extra loop in F0. +// If it was so the route time had been much more. +UNIT_CLASS_TEST(RestrictionTest, LoopGraph) +{ + Init(BuildLoopGraph()); + SetStarter( + routing::IndexGraphStarter::FakeVertex(1, 0 /* seg id */, m2::PointD(0.0002, 0)) /* start */, + routing::IndexGraphStarter::FakeVertex(2, 0 /* seg id */, + m2::PointD(0.00005, 0.0004)) /* finish */); + + double constexpr kExpectedRouteTimeSec = 3.48; + TestRouteTime(*m_starter, AStarAlgorithm::Result::OK, kExpectedRouteTimeSec); +} } // namespace routing_test diff --git a/routing/routing_tests/index_graph_tools.cpp b/routing/routing_tests/index_graph_tools.cpp index 3859f2c417..f8fad8ef2c 100644 --- a/routing/routing_tests/index_graph_tools.cpp +++ b/routing/routing_tests/index_graph_tools.cpp @@ -41,13 +41,16 @@ shared_ptr CreateEstimator(traffic::TrafficCache const & trafficC } AStarAlgorithm::Result CalculateRoute(IndexGraphStarter & starter, - vector & roadPoints) + vector & roadPoints, + double & timeSec) { AStarAlgorithm algorithm; RoutingResult routingResult; auto const resultCode = algorithm.FindPathBidirectional( starter, starter.GetStart(), starter.GetFinish(), routingResult, {}, {}); + timeSec = routingResult.distance; + roadPoints = routingResult.path; return resultCode; } @@ -56,12 +59,63 @@ void TestRouteGeometry(IndexGraphStarter & starter, AStarAlgorithm::Result expectedRouteResult, vector const & expectedRouteGeom) { - vector route; - auto const resultCode = CalculateRoute(starter, route); - TEST_EQUAL(resultCode, expectedRouteResult, ()); - TEST_EQUAL(IndexGraphStarter::GetRouteNumPoints(route), expectedRouteGeom.size(), ()); + vector routeSegs; + double timeSec = 0.0; + auto const resultCode = CalculateRoute(starter, routeSegs, timeSec); - for (size_t i = 0; i < IndexGraphStarter::GetRouteNumPoints(route); ++i) - TEST_EQUAL(expectedRouteGeom[i], starter.GetRoutePoint(route, i), ()); + if (AStarAlgorithm::Result::NoPath == expectedRouteResult && + expectedRouteGeom == vector()) + { + // The route goes through a restriction. So there's no choice for building route + // except for going through restriction. So no path. + return; + } + + TEST_EQUAL(resultCode, expectedRouteResult, ()); + if (resultCode != AStarAlgorithm::Result::OK) + return; + + CHECK(!routeSegs.empty(), ()); + vector geom; + + auto const pushPoint = [&geom](m2::PointD const & point) { + if (geom.empty() || geom.back() != point) + geom.push_back(point); + }; + + for (size_t i = 0; i < routeSegs.size(); ++i) + { + m2::PointD const & pnt = starter.GetPoint(routeSegs[i], false /* front */); + // Note. In case of A* router all internal points of route are duplicated. + // So it's necessary to exclude the duplicates. + pushPoint(pnt); + } + + pushPoint(starter.GetPoint(routeSegs.back(), false /* front */)); + TEST_EQUAL(geom, expectedRouteGeom, ()); +} + +void TestRouteTime(IndexGraphStarter & starter, + AStarAlgorithm::Result expectedRouteResult, + double expectedTime) +{ + vector routeSegs; + double timeSec = 0.0; + auto const resultCode = CalculateRoute(starter, routeSegs, timeSec); + + TEST_EQUAL(resultCode, expectedRouteResult, ()); + double const kEpsilon = 1e-5; + TEST(my::AlmostEqualAbs(timeSec, expectedTime, kEpsilon), ()); +} + +void TestRestrictions(vector const & expectedRouteGeom, + AStarAlgorithm::Result expectedRouteResult, + routing::IndexGraphStarter::FakeVertex const & start, + routing::IndexGraphStarter::FakeVertex const & finish, + RestrictionVec && restrictions, RestrictionTest & restrictionTest) +{ + restrictionTest.SetRestrictions(move(restrictions)); + restrictionTest.SetStarter(start, finish); + TestRouteGeometry(*restrictionTest.m_starter, expectedRouteResult, expectedRouteGeom); } } // namespace routing_test diff --git a/routing/routing_tests/index_graph_tools.hpp b/routing/routing_tests/index_graph_tools.hpp index 4c5494b1b3..e03cdc8aa6 100644 --- a/routing/routing_tests/index_graph_tools.hpp +++ b/routing/routing_tests/index_graph_tools.hpp @@ -3,6 +3,7 @@ #include "routing/edge_estimator.hpp" #include "routing/index_graph.hpp" #include "routing/index_graph_starter.hpp" +#include "routing/restrictions_serialization.hpp" #include "routing/road_point.hpp" #include "routing/segment.hpp" @@ -10,6 +11,8 @@ #include "traffic/traffic_info.hpp" +#include "indexer/classificator_loader.hpp" + #include "geometry/point2d.hpp" #include "std/algorithm.hpp" @@ -20,6 +23,27 @@ namespace routing_test { +using namespace routing; + +struct RestrictionTest +{ + RestrictionTest() { classificator::Load(); } + void Init(unique_ptr graph) { m_graph = move(graph); } + void SetStarter(IndexGraphStarter::FakeVertex const & start, + IndexGraphStarter::FakeVertex const & finish) + { + m_starter = make_unique(*m_graph, start, finish); + } + + void SetRestrictions(RestrictionVec && restrictions) + { + m_graph->SetRestrictions(move(restrictions)); + } + + unique_ptr m_graph; + unique_ptr m_starter; +}; + class TestGeometryLoader final : public routing::GeometryLoader { public: @@ -38,15 +62,23 @@ routing::Joint MakeJoint(vector const & points); shared_ptr CreateEstimator(traffic::TrafficCache const & trafficCache); routing::AStarAlgorithm::Result CalculateRoute( - routing::IndexGraphStarter & starter, vector & roadPoints); - -void TestRouteSegments( - routing::IndexGraphStarter & starter, - routing::AStarAlgorithm::Result expectedRouteResult, - vector const & expectedRoute); + routing::IndexGraphStarter & starter, vector & roadPoints, double & timeSec); void TestRouteGeometry( routing::IndexGraphStarter & starter, routing::AStarAlgorithm::Result expectedRouteResult, vector const & expectedRouteGeom); + +void TestRouteTime(IndexGraphStarter & starter, + AStarAlgorithm::Result expectedRouteResult, + double expectedTime); + +/// \brief Applies |restrictions| to graph in |restrictionTest| and +/// tests the resulting route. +/// \note restrictionTest should have a valid |restrictionTest.m_graph|. +void TestRestrictions(vector const & expectedRouteGeom, + AStarAlgorithm::Result expectedRouteResult, + routing::IndexGraphStarter::FakeVertex const & start, + routing::IndexGraphStarter::FakeVertex const & finish, + RestrictionVec && restrictions, RestrictionTest & restrictionTest); } // namespace routing_test diff --git a/routing/routing_tests/restriction_test.cpp b/routing/routing_tests/restriction_test.cpp new file mode 100644 index 0000000000..0f38395103 --- /dev/null +++ b/routing/routing_tests/restriction_test.cpp @@ -0,0 +1,824 @@ +#include "testing/testing.hpp" + +#include "routing/routing_tests/index_graph_tools.hpp" + +#include "routing/car_model.hpp" +#include "routing/geometry.hpp" + +#include "indexer/classificator_loader.hpp" + +#include "geometry/point2d.hpp" + +#include +#include + +namespace routing_test +{ +using namespace routing; + +// Finish +// * +// ^ +// | +// F7 +// | +// * +// ^ +// | +// F6 +// | +// Start * -- F0 --> * -- F1 --> * <-- F2 --> * -- F3 --> * +// | ^ +// | | +// F4 F5 +// | | +// ⌄ | +// * +unique_ptr BuildCrossGraph() +{ + unique_ptr loader = make_unique(); + loader->AddRoad(0 /* featureId */, true /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{-1.0, 0.0}, {0.0, 0.0}})); + loader->AddRoad(1 /* featureId */, true /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{0.0, 0.0}, {1.0, 0.0}})); + loader->AddRoad(2 /* featureId */, false /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{1.0, 0.0}, {1.9999, 0.0}})); + loader->AddRoad(3 /* featureId */, true /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{1.9999, 0.0}, {3.0, 0.0}})); + loader->AddRoad(4 /* featureId */, true /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{1.0, 0.0}, {1.0, -1.0}})); + loader->AddRoad(5 /* featureId */, true /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{1.0, -1.0}, {1.0, 0.0}})); + loader->AddRoad(6 /* featureId */, true /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{1.0, 0.0}, {1.0, 1.0}})); + loader->AddRoad(7 /* featureId */, true /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{1.0, 1.0}, {1.0, 2.0}})); + + vector const joints = { + MakeJoint({{0, 1}, {1, 0}}), MakeJoint({{1, 1}, {2, 0}, {4, 0}, {5, 1}, {6, 0}}), + MakeJoint({{2, 1}, {3, 0}}), MakeJoint({{4, 1}, {5, 0}}), MakeJoint({{6, 1}, {7, 0}})}; + + traffic::TrafficCache const trafficCache; + unique_ptr graph = + make_unique(move(loader), CreateEstimator(trafficCache)); + graph->Import(joints); + return graph; +} + +UNIT_CLASS_TEST(RestrictionTest, CrossGraph_NoUTurn) +{ + Init(BuildCrossGraph()); + SetStarter(routing::IndexGraphStarter::FakeVertex(0, 0, m2::PointD(-1, 0)) /* start */, + routing::IndexGraphStarter::FakeVertex(7, 0, m2::PointD(1, 2)) /* finish */); + + vector const expectedGeom = { + {-1.0 /* x */, 0.0 /* y */}, {0.0, 0.0}, {1.0, 0.0}, {1.0, 1.0}, {1.0, 2.0}}; + TestRouteGeometry(*m_starter, AStarAlgorithm::Result::OK, expectedGeom); +} + +UNIT_CLASS_TEST(RestrictionTest, CrossGraph_UTurn) +{ + Init(BuildCrossGraph()); + SetStarter(routing::IndexGraphStarter::FakeVertex(0, 0, m2::PointD(-1, 0)) /* start */, + routing::IndexGraphStarter::FakeVertex(7, 0, m2::PointD(1, 2)) /* finish */); + + RestrictionVec restrictions = { + {Restriction::Type::No, {1 /* feature from */, 6 /* feature to */}}}; + vector const expectedGeom = {{-1.0, 0.0}, {0.0, 0.0}, {1.0, 0.0}, {1.0, -1.0}, + {1.0, 0.0}, {1.0, 1.0}, {1.0, 2.0}}; + + TestRestrictions(expectedGeom, AStarAlgorithm::Result::OK, + routing::IndexGraphStarter::FakeVertex(0, 0, m2::PointD(-1, 0)), /* start */ + routing::IndexGraphStarter::FakeVertex(7, 0, m2::PointD(1, 2)), /* finish */ + move(restrictions), *this); +} + +// Finish +// 3 * +// ^ +// | +// F4 +// | +// 2 * +// ^ ↖ +// | F1 +// | ↖ +// 1 | * +// F0 ↖ +// | F2 +// | ↖ +// 0 *<--F3---<--F3---*<--F5--* Start +// 0 1 2 3 +// Note. F0, F1 and F2 are one segment features. F3 is a two segments feature. +unique_ptr BuildTriangularGraph() +{ + unique_ptr loader = make_unique(); + loader->AddRoad(0 /* featureId */, true /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{0.0, 0.0}, {0.0, 2.0}})); + loader->AddRoad(1 /* featureId */, true /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{1.0, 1.0}, {0.0, 2.0}})); + loader->AddRoad(2 /* featureId */, true /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{2.0, 0.0}, {1.0, 1.0}})); + loader->AddRoad(3 /* featureId */, true /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{2.0, 0.0}, {1.0, 0.0}, {0.0, 0.0}})); + loader->AddRoad(4 /* featureId */, true /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{0.0, 2.0}, {0.0, 3.0}})); + loader->AddRoad(5 /* featureId */, true /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{3.0, 0.0}, {2.0, 0.0}})); + + vector const joints = { + MakeJoint({{2, 0}, {3, 0}, {5, 1}}), /* joint at point (2, 0) */ + MakeJoint({{3, 2}, {0, 0}}), /* joint at point (0, 0) */ + MakeJoint({{2, 1}, {1, 0}}), /* joint at point (1, 1) */ + MakeJoint({{0, 1}, {1, 1}, {4, 0}}), /* joint at point (0, 2) */ + MakeJoint({{5, 0}}), /* joint at point (3, 0) */ + MakeJoint({{4, 1}}) /* joint at point (0, 3) */ + }; + + traffic::TrafficCache const trafficCache; + unique_ptr graph = + make_unique(move(loader), CreateEstimator(trafficCache)); + graph->Import(joints); + return graph; +} + +// Route through triangular graph without any restrictions. +UNIT_CLASS_TEST(RestrictionTest, TriangularGraph) +{ + Init(BuildTriangularGraph()); + + vector const expectedGeom = {{3 /* x */, 0 /* y */}, {2, 0}, {1, 1}, {0, 2}, {0, 3}}; + + TestRestrictions(expectedGeom, AStarAlgorithm::Result::OK, + routing::IndexGraphStarter::FakeVertex(5, 0, m2::PointD(3, 0)) /* start */, + routing::IndexGraphStarter::FakeVertex(4, 0, m2::PointD(0, 3)) /* finish */, {}, + *this); +} + +// Route through triangular graph with restriction type no from feature 2 to feature 1. +UNIT_CLASS_TEST(RestrictionTest, TriangularGraph_RestrictionNoF2F1) +{ + Init(BuildTriangularGraph()); + RestrictionVec restrictions = { + {Restriction::Type::No, {2 /* feature from */, 1 /* feature to */}}}; + vector const expectedGeom = { + {3 /* x */, 0 /* y */}, {2, 0}, {1, 0}, {0, 0}, {0, 2}, {0, 3}}; + + TestRestrictions(expectedGeom, AStarAlgorithm::Result::OK, + routing::IndexGraphStarter::FakeVertex(5, 0, m2::PointD(3, 0)) /* start */, + routing::IndexGraphStarter::FakeVertex(4, 0, m2::PointD(0, 3)) /* finish */, + move(restrictions), *this); +} + +UNIT_CLASS_TEST(RestrictionTest, TriangularGraph_RestrictionNoF5F2) +{ + Init(BuildTriangularGraph()); + RestrictionVec restrictions = { + {Restriction::Type::No, {5 /* feature from */, 2 /* feature to */}}}; + vector const expectedGeom = { + {3 /* x */, 0 /* y */}, {2, 0}, {1, 0}, {0, 0}, {0, 2}, {0, 3}}; + + TestRestrictions(expectedGeom, AStarAlgorithm::Result::OK, + routing::IndexGraphStarter::FakeVertex(5, 0, m2::PointD(3, 0)) /* start */, + routing::IndexGraphStarter::FakeVertex(4, 0, m2::PointD(0, 3)) /* finish */, + move(restrictions), *this); +} + +UNIT_CLASS_TEST(RestrictionTest, TriangularGraph_RestrictionOnlyF5F3) +{ + Init(BuildTriangularGraph()); + RestrictionVec restrictions = { + {Restriction::Type::Only, {5 /* feature from */, 3 /* feature to */}}}; + vector const expectedGeom = { + {3 /* x */, 0 /* y */}, {2, 0}, {1, 0}, {0, 0}, {0, 2}, {0, 3}}; + + TestRestrictions(expectedGeom, AStarAlgorithm::Result::OK, + routing::IndexGraphStarter::FakeVertex(5, 0, m2::PointD(3, 0)) /* start */, + routing::IndexGraphStarter::FakeVertex(4, 0, m2::PointD(0, 3)) /* finish */, + move(restrictions), *this); +} + +UNIT_CLASS_TEST(RestrictionTest, TriangularGraph_RestrictionNoF5F2RestrictionOnlyF5F3) +{ + Init(BuildTriangularGraph()); + RestrictionVec restrictions = { + {Restriction::Type::No, {5 /* feature from */, 2 /* feature to */}}, + {Restriction::Type::Only, {5 /* feature from */, 3 /* feature to */}}}; + vector const expectedGeom = { + {3 /* x */, 0 /* y */}, {2, 0}, {1, 0}, {0, 0}, {0, 2}, {0, 3}}; + + TestRestrictions(expectedGeom, AStarAlgorithm::Result::OK, + routing::IndexGraphStarter::FakeVertex(5, 0, m2::PointD(3, 0)) /* start */, + routing::IndexGraphStarter::FakeVertex(4, 0, m2::PointD(0, 3)) /* finish */, + move(restrictions), *this); +} + +// Finish +// 3 * +// | +// F4 +// | +// 2 * +// | \ +// F0 F2 +// | \ +// 1 * * +// | \ +// F0 F2 +// | \ +// 0 *---F1--*--F1--*--F3---* Start +// 0 1 2 3 +// Note. All features are two setments and two-way. +unique_ptr BuildTwowayCornerGraph() +{ + unique_ptr loader = make_unique(); + loader->AddRoad(0 /* feature id */, false /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{0.0, 0.0}, {0.0, 1.0}, {0.0, 2.0}})); + loader->AddRoad(1 /* feature id */, false /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{2.0, 0.0}, {1.0, 0.0}, {0.0, 0.0}})); + loader->AddRoad(2 /* feature id */, false /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{2.0, 0.0}, {1.0, 1.0}, {0.0, 2.0}})); + loader->AddRoad(3 /* feature id */, false /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{3.0, 0.0}, {2.0, 0.0}})); + loader->AddRoad(4 /* feature id */, false /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{0.0, 2.0}, {0.0, 3.0}})); + + vector const joints = { + MakeJoint({{1 /* feature id */, 2 /* point id */}, {0, 0}}) + /* joint at point (0, 0) */, + MakeJoint({{1, 0}, {2, 0}, {3, 1}}), /* joint at point (2, 0) */ + MakeJoint({{2, 2}, {0, 2}, {4, 0}}), /* joint at point (0, 2) */ + MakeJoint({{4, 1}}), /* joint at point (0, 3) */ + MakeJoint({{3, 0}}), /* joint at point (3, 0) */ + }; + traffic::TrafficCache const trafficCache; + unique_ptr graph = + make_unique(move(loader), CreateEstimator(trafficCache)); + graph->Import(joints); + return graph; +} + +UNIT_CLASS_TEST(RestrictionTest, TwowayCornerGraph) +{ + Init(BuildTwowayCornerGraph()); + vector const expectedGeom = {{3 /* x */, 0 /* y */}, {2, 0}, {1, 1}, {0, 2}, {0, 3}}; + + TestRestrictions(expectedGeom, AStarAlgorithm::Result::OK, + routing::IndexGraphStarter::FakeVertex(3, 0, m2::PointD(3, 0)) /* start */, + routing::IndexGraphStarter::FakeVertex(4, 0, m2::PointD(0, 3)) /* finish */, {}, + *this); +} + +UNIT_CLASS_TEST(RestrictionTest, TwowayCornerGraph_RestrictionF3F2No) +{ + Init(BuildTwowayCornerGraph()); + RestrictionVec restrictions = { + {Restriction::Type::No, {3 /* feature from */, 2 /* feature to */}}}; + vector const expectedGeom = { + {3 /* x */, 0 /* y */}, {2, 0}, {1, 0}, {0, 0}, {0, 1}, {0, 2}, {0, 3}}; + + TestRestrictions(expectedGeom, AStarAlgorithm::Result::OK, + routing::IndexGraphStarter::FakeVertex(3, 0, m2::PointD(3, 0)) /* start */, + routing::IndexGraphStarter::FakeVertex(4, 0, m2::PointD(0, 3)) /* finish */, + move(restrictions), *this); +} + +UNIT_CLASS_TEST(RestrictionTest, TwowayCornerGraph_RestrictionF3F1Only) +{ + Init(BuildTwowayCornerGraph()); + RestrictionVec restrictions = { + {Restriction::Type::Only, {3 /* feature from */, 1 /* feature to */}}}; + vector const expectedGeom = { + {3 /* x */, 0 /* y */}, {2, 0}, {1, 0}, {0, 0}, {0, 1}, {0, 2}, {0, 3}}; + + TestRestrictions(expectedGeom, AStarAlgorithm::Result::OK, + routing::IndexGraphStarter::FakeVertex(3, 0, m2::PointD(3, 0)) /* start */, + routing::IndexGraphStarter::FakeVertex(4, 0, m2::PointD(0, 3)) /* finish */, + move(restrictions), *this); +} + +// Finish +// 3 * +// ^ +// | +// F11 +// | +// 2 *<---F5----*<---F6---* +// ^ ↖ ^ ↖ ^ +// | F7 | F8 | +// | ↖ F1 ↖ F2 +// | ↖ | ↖ | +// 1 F0 * * +// | ^ ↖ ^ +// | F1 F9 F2 +// | | ↖ | +// 0 *<----F4---*<---F3----*<--F10---* Start +// 0 1 2 3 +// Note. F1 and F2 are two segments features. The others are one segment ones. +unique_ptr BuildTwoSquaresGraph() +{ + unique_ptr loader = make_unique(); + loader->AddRoad(0 /* feature id */, true /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{0.0, 0.0}, {0.0, 2.0}})); + loader->AddRoad(1 /* feature id */, true /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{1.0, 0.0}, {1.0, 1.0}, {1.0, 2.0}})); + loader->AddRoad(2 /* feature id */, true /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{2.0, 0.0}, {2.0, 1.0}, {2.0, 2.0}})); + loader->AddRoad(3 /* feature id */, true /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{2.0, 0.0}, {1.0, 0.0}})); + loader->AddRoad(4 /* feature id */, true /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{1.0, 0.0}, {0.0, 0.0}})); + loader->AddRoad(5 /* feature id */, true /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{1.0, 2.0}, {0.0, 2.0}})); + loader->AddRoad(6 /* feature id */, true /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{2.0, 2.0}, {1.0, 2.0}})); + loader->AddRoad(7 /* feature id */, true /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{1.0, 1.0}, {0.0, 2.0}})); + loader->AddRoad(8 /* feature id */, true /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{2.0, 1.0}, {1.0, 2.0}})); + loader->AddRoad(9 /* feature id */, true /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{2.0, 0.0}, {1.0, 1.0}})); + loader->AddRoad(10 /* feature id */, true /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{3.0, 0.0}, {2.0, 0.0}})); + loader->AddRoad(11 /* feature id */, true /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{0.0, 2.0}, {0.0, 3.0}})); + + vector const joints = { + MakeJoint({{4 /* featureId */, 1 /* pointId */}, {0, 0}}), /* joint at point (0, 0) */ + MakeJoint({{0, 1}, {5, 1}, {7, 1}, {11, 0}}), /* joint at point (0, 2) */ + MakeJoint({{4, 0}, {1, 0}, {3, 1}}), /* joint at point (1, 0) */ + MakeJoint({{5, 0}, {1, 2}, {6, 1}, {8, 1}}), /* joint at point (1, 2) */ + MakeJoint({{3, 0}, {2, 0}, {9, 0}, {10, 1}}), /* joint at point (2, 0) */ + MakeJoint({{2, 2}, {6, 0}}), /* joint at point (2, 2) */ + MakeJoint({{1, 1}, {9, 1}, {7, 0}}), /* joint at point (1, 1) */ + MakeJoint({{2, 1}, {8, 0}}), /* joint at point (2, 1) */ + MakeJoint({{10, 0}}), /* joint at point (3, 0) */ + MakeJoint({{11, 1}}), /* joint at point (0, 3) */ + }; + + traffic::TrafficCache const trafficCache; + unique_ptr graph = + make_unique(move(loader), CreateEstimator(trafficCache)); + graph->Import(joints); + return graph; +} + +UNIT_CLASS_TEST(RestrictionTest, TwoSquaresGraph) +{ + Init(BuildTwoSquaresGraph()); + vector const expectedGeom = {{3 /* x */, 0 /* y */}, {2, 0}, {1, 1}, {0, 2}, {0, 3}}; + + TestRestrictions(expectedGeom, AStarAlgorithm::Result::OK, + routing::IndexGraphStarter::FakeVertex(10, 0, m2::PointD(3, 0)) /* start */, + routing::IndexGraphStarter::FakeVertex(11, 0, m2::PointD(0, 3)) /* finish */, {}, + *this); +} + +UNIT_CLASS_TEST(RestrictionTest, TwoSquaresGraph_RestrictionF10F3Only) +{ + Init(BuildTwoSquaresGraph()); + RestrictionVec restrictions = { + {Restriction::Type::Only, {10 /* feature from */, 3 /* feature to */}}}; + + vector const expectedGeom = { + {3 /* x */, 0 /* y */}, {2, 0}, {1, 0}, {1, 1}, {0, 2}, {0, 3}}; + + TestRestrictions(expectedGeom, AStarAlgorithm::Result::OK, + routing::IndexGraphStarter::FakeVertex(10, 0, m2::PointD(3, 0)) /* start */, + routing::IndexGraphStarter::FakeVertex(11, 0, m2::PointD(0, 3)) /* finish */, + move(restrictions), *this); +} + +UNIT_CLASS_TEST(RestrictionTest, TwoSquaresGraph_RestrictionF10F3OnlyF3F4Only) +{ + Init(BuildTwoSquaresGraph()); + RestrictionVec restrictions = { + {Restriction::Type::Only, {3 /* feature from */, 4 /* feature to */}}, + {Restriction::Type::Only, {10 /* feature from */, 3 /* feature to */}}}; + + vector const expectedGeom = { + {3 /* x */, 0 /* y */}, {2, 0}, {1, 0}, {0, 0}, {0, 2}, {0, 3}}; + + TestRestrictions(expectedGeom, AStarAlgorithm::Result::OK, + routing::IndexGraphStarter::FakeVertex(10, 0, m2::PointD(3, 0)) /* start */, + routing::IndexGraphStarter::FakeVertex(11, 0, m2::PointD(0, 3)) /* finish */, + move(restrictions), *this); +} + +UNIT_CLASS_TEST(RestrictionTest, TwoSquaresGraph_RestrictionF2F8NoRestrictionF9F1Only) +{ + Init(BuildTwoSquaresGraph()); + RestrictionVec restrictions = { + {Restriction::Type::No, {2 /* feature from */, 8 /* feature to */}}, + {Restriction::Type::Only, {9 /* feature from */, 1 /* feature to */}}}; + + vector const expectedGeom = { + {3 /* x */, 0 /* y */}, {2, 0}, {1, 1}, {1, 2}, {0, 2}, {0, 3}}; + + TestRestrictions(expectedGeom, AStarAlgorithm::Result::OK, + routing::IndexGraphStarter::FakeVertex(10, 0, m2::PointD(3, 0)) /* start */, + routing::IndexGraphStarter::FakeVertex(11, 0, m2::PointD(0, 3)) /* finish */, + move(restrictions), *this); +} + +// 2 * +// | +// F6 +// |Finish +// 1 *-F4-*-F5-* +// | | +// F2 F3 +// | | +// 0 *---F1----*---F0---* Start +// 0 1 2 +// Note 1. All features are two-way. (It's possible to move along any direction of the features.) +// Note 2. Any feature contains of one segment. +unique_ptr BuildFlagGraph() +{ + unique_ptr loader = make_unique(); + loader->AddRoad(0 /* feature id */, false /* one way */, 1.0 /* speed */, + RoadGeometry::Points({{2.0, 0.0}, {1.0, 0.0}})); + loader->AddRoad(1 /* feature id */, false /* one way */, 1.0 /* speed */, + RoadGeometry::Points({{1.0, 0.0}, {0.0, 0.0}})); + loader->AddRoad(2 /* feature id */, false /* one way */, 1.0 /* speed */, + RoadGeometry::Points({{0.0, 0.0}, {0.0, 1.0}})); + loader->AddRoad(3 /* feature id */, false /* one way */, 1.0 /* speed */, + RoadGeometry::Points({{1.0, 0.0}, {1.0, 1.0}})); + loader->AddRoad(4 /* feature id */, false /* one way */, 1.0 /* speed */, + RoadGeometry::Points({{0.0, 1.0}, {0.5, 1.0}})); + loader->AddRoad(5 /* feature id */, false /* one way */, 1.0 /* speed */, + RoadGeometry::Points({{0.5, 1.0}, {1.0, 1.0}})); + loader->AddRoad(6 /* feature id */, false /* one way */, 1.0 /* speed */, + RoadGeometry::Points({{0.5, 1.0}, {0.5, 2.0}})); + + vector const joints = { + MakeJoint({{1 /* feature id */, 1 /* point id */}, {2, 0}}), /* joint at point (0, 0) */ + MakeJoint({{2, 1}, {4, 0}}), /* joint at point (0, 1) */ + MakeJoint({{4, 1}, {5, 0}, {6, 0}}), /* joint at point (0.5, 1) */ + MakeJoint({{1, 0}, {3, 0}, {0, 1}}), /* joint at point (1, 0) */ + MakeJoint({{3, 1}, {5, 1}}), /* joint at point (1, 1) */ + MakeJoint({{6, 1}}), /* joint at point (0.5, 2) */ + MakeJoint({{0, 0}}), /* joint at point (2, 0) */ + }; + + traffic::TrafficCache const trafficCache; + unique_ptr graph = + make_unique(move(loader), CreateEstimator(trafficCache)); + graph->Import(joints); + return graph; +} + +// Route through flag graph without any restrictions. +UNIT_TEST(FlagGraph) +{ + unique_ptr graph = BuildFlagGraph(); + IndexGraphStarter starter( + *graph, routing::IndexGraphStarter::FakeVertex(0, 0, m2::PointD(2, 0)) /* start */, + routing::IndexGraphStarter::FakeVertex(6, 0, m2::PointD(0.5, 1)) /* finish */); + vector const expectedGeom = {{2 /* x */, 0 /* y */}, {1, 0}, {1, 1}, {0.5, 1}}; + TestRouteGeometry(starter, AStarAlgorithm::Result::OK, expectedGeom); +} + +// Route through flag graph with one restriciton (type no) from F0 to F3. +UNIT_CLASS_TEST(RestrictionTest, FlagGraph_RestrictionF0F3No) +{ + Init(BuildFlagGraph()); + RestrictionVec restrictions = { + {Restriction::Type::No, {0 /* feature from */, 3 /* feature to */}}}; + vector const expectedGeom = { + {2 /* x */, 0 /* y */}, {1, 0}, {0, 0}, {0, 1}, {0.5, 1}}; + + TestRestrictions(expectedGeom, AStarAlgorithm::Result::OK, + routing::IndexGraphStarter::FakeVertex(0, 0, m2::PointD(2, 0)) /* start */, + routing::IndexGraphStarter::FakeVertex(6, 0, m2::PointD(0.5, 1)) /* finish */, + move(restrictions), *this); +} + +// Route through flag graph with one restriciton (type only) from F0 to F1. +UNIT_CLASS_TEST(RestrictionTest, FlagGraph_RestrictionF0F1Only) +{ + Init(BuildFlagGraph()); + RestrictionVec restrictions = { + {Restriction::Type::No, {0 /* feature from */, 1 /* feature to */}}}; + vector const expectedGeom = {{2 /* x */, 0 /* y */}, {1, 0}, {1, 1}, {0.5, 1}}; + + TestRestrictions(expectedGeom, AStarAlgorithm::Result::OK, + routing::IndexGraphStarter::FakeVertex(0, 0, m2::PointD(2, 0)) /* start */, + routing::IndexGraphStarter::FakeVertex(6, 0, m2::PointD(0.5, 1)) /* finish */, + move(restrictions), *this); +} + +UNIT_CLASS_TEST(RestrictionTest, FlagGraph_PermutationsF1F3NoF7F8OnlyF8F4OnlyF4F6Only) +{ + Init(BuildFlagGraph()); + RestrictionVec restrictions = { + {Restriction::Type::No, {0 /* feature from */, 3 /* feature to */}}, + {Restriction::Type::Only, {0 /* feature from */, 1 /* feature to */}}, + {Restriction::Type::Only, {1 /* feature from */, 2 /* feature to */}}}; + + vector const expectedGeom = { + {2 /* x */, 0 /* y */}, {1, 0}, {0, 0}, {0, 1}, {0.5, 1}}; + TestRestrictions(expectedGeom, AStarAlgorithm::Result::OK, + routing::IndexGraphStarter::FakeVertex(0, 0, m2::PointD(2, 0)) /* start */, + routing::IndexGraphStarter::FakeVertex(6, 0, m2::PointD(0.5, 1)) /* finish */, + move(restrictions), *this); +} + +// 1 *-F4-*-F5-*---F6---* Finish +// | | +// F2 F3 +// | | +// 0 *---F1----*---F0---* Start +// 0 1 2 +// Note 1. All features except for F7 are two-way. +// Note 2. Any feature contains of one segment. +unique_ptr BuildPosterGraph() +{ + unique_ptr loader = make_unique(); + loader->AddRoad(0 /* feature id */, false /* one way */, 1.0 /* speed */, + RoadGeometry::Points({{2.0, 0.0}, {1.0, 0.0}})); + loader->AddRoad(1 /* feature id */, false /* one way */, 1.0 /* speed */, + RoadGeometry::Points({{1.0, 0.0}, {0.0, 0.0}})); + loader->AddRoad(2 /* feature id */, false /* one way */, 1.0 /* speed */, + RoadGeometry::Points({{0.0, 0.0}, {0.0, 1.0}})); + loader->AddRoad(3 /* feature id */, false /* one way */, 1.0 /* speed */, + RoadGeometry::Points({{1.0, 0.0}, {1.0, 1.0}})); + loader->AddRoad(4 /* feature id */, false /* one way */, 1.0 /* speed */, + RoadGeometry::Points({{0.0, 1.0}, {0.5, 1.0}})); + loader->AddRoad(5 /* feature id */, false /* one way */, 1.0 /* speed */, + RoadGeometry::Points({{0.5, 1.0}, {1.0, 1.0}})); + loader->AddRoad(6 /* feature id */, false /* one way */, 1.0 /* speed */, + RoadGeometry::Points({{1.0, 1.0}, {2.0, 1.0}})); + + vector const joints = { + MakeJoint({{1 /* feature id */, 1 /* point id */}, {2, 0}}), /* joint at point (0, 0) */ + MakeJoint({{2, 1}, {4, 0}}), /* joint at point (0, 1) */ + MakeJoint({{4, 1}, {5, 0}}), /* joint at point (0.5, 1) */ + MakeJoint({{1, 0}, {3, 0}, {0, 1}}), /* joint at point (1, 0) */ + MakeJoint({{3, 1}, {5, 1}, {6, 0}}), /* joint at point (1, 1) */ + MakeJoint({{0, 0}}), /* joint at point (2, 0) */ + MakeJoint({{6, 1}}), /* joint at point (2, 1) */ + }; + + traffic::TrafficCache const trafficCache; + unique_ptr graph = + make_unique(move(loader), CreateEstimator(trafficCache)); + graph->Import(joints); + + return graph; +} + +// Route through poster graph without any restrictions. +UNIT_TEST(PosterGraph) +{ + unique_ptr graph = BuildPosterGraph(); + IndexGraphStarter starter( + *graph, routing::IndexGraphStarter::FakeVertex(0, 0, m2::PointD(2, 0)) /* start */, + routing::IndexGraphStarter::FakeVertex(6, 0, m2::PointD(2, 1)) /* finish */); + vector const expectedGeom = {{2 /* x */, 0 /* y */}, {1, 0}, {1, 1}, {2, 1}}; + + TestRouteGeometry(starter, AStarAlgorithm::Result::OK, expectedGeom); +} + +// Route through poster graph with restrictions F0-F3 (type no). +UNIT_CLASS_TEST(RestrictionTest, PosterGraph_RestrictionF0F3No) +{ + Init(BuildPosterGraph()); + RestrictionVec restrictions = { + {Restriction::Type::No, {0 /* feature from */, 3 /* feature to */}}}; + vector const expectedGeom = { + {2 /* x */, 0 /* y */}, {1, 0}, {0, 0}, {0, 1}, {0.5, 1}, {1, 1}, {2, 1}}; + + TestRestrictions(expectedGeom, AStarAlgorithm::Result::OK, + routing::IndexGraphStarter::FakeVertex(0, 0, m2::PointD(2, 0)), /* start */ + routing::IndexGraphStarter::FakeVertex(6, 0, m2::PointD(2, 1)), /* finish */ + move(restrictions), *this); +} + +// Route through poster graph with restrictions F0-F1 (type only). +UNIT_CLASS_TEST(RestrictionTest, PosterGraph_RestrictionF0F1Only) +{ + Init(BuildPosterGraph()); + + RestrictionVec restrictions = { + {Restriction::Type::Only, {0 /* feature from */, 1 /* feature to */}}}; + vector const expectedGeom = { + {2 /* x */, 0 /* y */}, {1, 0}, {0, 0}, {0, 1}, {0.5, 1}, {1, 1}, {2, 1}}; + TestRestrictions(expectedGeom, AStarAlgorithm::Result::OK, + routing::IndexGraphStarter::FakeVertex(0, 0, m2::PointD(2, 0)), /* start */ + routing::IndexGraphStarter::FakeVertex(6, 0, m2::PointD(2, 1)), /* finish */ + move(restrictions), *this); +} + +// 1 *--F1-->* +// ↗ ↘ +// F1 F1 +// ↗ ↘ +// 0 Start *---F3--->*---F0--->-------F0----->*---F2-->* Finish +// -1 0 1 2 3 4 +// Note. F0 is a two segments feature. F1 is a three segment one. F2 and F3 are one segment +// features. +unique_ptr BuildTwoWayGraph() +{ + unique_ptr loader = make_unique(); + loader->AddRoad(0 /* feature id */, true /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{0.0, 0.0}, {1.0, 0.0}, {3.0, 0}})); + loader->AddRoad(1 /* feature id */, true /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{0.0, 0.0}, {1.0, 1.0}, {2.0, 1.0}, {3.0, 0.0}})); + loader->AddRoad(2 /* feature id */, true /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{3.0, 0.0}, {4.0, 0.0}})); + loader->AddRoad(3 /* feature id */, true /* oneWay */, 1.0 /* speed */, + RoadGeometry::Points({{-1.0, 0.0}, {0.0, 0.0}})); + + vector const joints = { + MakeJoint( + {{0 /* feature id */, 0 /* point id */}, {1, 0}, {3, 1}}), /* joint at point (0, 0) */ + MakeJoint( + {{0 /* feature id */, 2 /* point id */}, {1, 3}, {2, 0}}), /* joint at point (3, 0) */ + MakeJoint({{3 /* feature id */, 0 /* point id */}}), /* joint at point (-1, 0) */ + MakeJoint({{2 /* feature id */, 1 /* point id */}}), /* joint at point (4, 0) */ + }; + + traffic::TrafficCache const trafficCache; + unique_ptr graph = + make_unique(move(loader), CreateEstimator(trafficCache)); + graph->Import(joints); + return graph; +} + +UNIT_TEST(TwoWayGraph) +{ + unique_ptr graph = BuildTwoWayGraph(); + IndexGraphStarter starter( + *graph, routing::IndexGraphStarter::FakeVertex(3, 0, m2::PointD(-1, 0)) /* start */, + routing::IndexGraphStarter::FakeVertex(2, 0, m2::PointD(4, 0)) /* finish */); + vector const expectedGeom = {{-1 /* x */, 0 /* y */}, {0, 0}, {1, 0}, {3, 0}, {4, 0}}; + + TestRouteGeometry(starter, AStarAlgorithm::Result::OK, expectedGeom); +} + +// 1 *---F4----* +// | | +// F2 F3 +// | | +// 0 *<--F5---*<--F1----*<--F0---* Start +// Finish +// 0 1 2 3 +// Note 1. F0, F1 and F5 are one-way features. F3, F2 and F4 are two-way features. +// Note 2. Any feature contains of one segment. +unique_ptr BuildSquaresGraph() +{ + unique_ptr loader = make_unique(); + loader->AddRoad(0 /* feature id */, true /* one way */, 1.0 /* speed */, + RoadGeometry::Points({{3.0, 0.0}, {2.0, 0.0}})); + loader->AddRoad(1 /* feature id */, true /* one way */, 1.0 /* speed */, + RoadGeometry::Points({{2.0, 0.0}, {1.0, 0.0}})); + loader->AddRoad(2 /* feature id */, false /* one way */, 1.0 /* speed */, + RoadGeometry::Points({{1.0, 0.0}, {1.0, 1.0}})); + loader->AddRoad(3 /* feature id */, false /* one way */, 1.0 /* speed */, + RoadGeometry::Points({{2.0, 0.0}, {2.0, 1.0}})); + loader->AddRoad(4 /* feature id */, false /* one way */, 1.0 /* speed */, + RoadGeometry::Points({{2.0, 1.0}, {1.0, 1.0}})); + loader->AddRoad(5 /* feature id */, true /* one way */, 1.0 /* speed */, + RoadGeometry::Points({{1.0, 0.0}, {0.0, 0.0}})); + + vector const joints = { + MakeJoint({{0 /* feature id */, 0 /* point id */}}), /* joint at point (3, 0) */ + MakeJoint({{0, 1}, {3, 0}, {1, 0}}), /* joint at point (2, 0) */ + MakeJoint({{3, 1}, {4, 0}}), /* joint at point (2, 1) */ + MakeJoint({{2, 1}, {4, 1}}), /* joint at point (1, 1) */ + MakeJoint({{1, 1}, {2, 0}, {5, 0}}), /* joint at point (1, 0) */ + MakeJoint({{5, 1}}) /* joint at point (0, 0) */ + }; + + traffic::TrafficCache const trafficCache; + unique_ptr graph = + make_unique(move(loader), CreateEstimator(trafficCache)); + graph->Import(joints); + return graph; +} + +UNIT_TEST(SquaresGraph) +{ + unique_ptr graph = BuildSquaresGraph(); + IndexGraphStarter starter( + *graph, routing::IndexGraphStarter::FakeVertex(0, 0, m2::PointD(3, 0)) /* start */, + routing::IndexGraphStarter::FakeVertex(5, 0, m2::PointD(0, 0)) /* finish */); + vector const expectedGeom = {{3 /* x */, 0 /* y */}, {2, 0}, {1, 0}, {0, 0}}; + TestRouteGeometry(starter, AStarAlgorithm::Result::OK, expectedGeom); +} + +// It's a test on correct working in case when because of adding restrictions +// start and finish could be match on blocked, moved or copied edges. +// See IndexGraphStarter constructor for a detailed description. +UNIT_CLASS_TEST(RestrictionTest, SquaresGraph_RestrictionF0F1OnlyF1F5Only) +{ + Init(BuildSquaresGraph()); + RestrictionVec restrictions = { + {Restriction::Type::Only, {0 /* feature from */, 1 /* feature to */}}, + {Restriction::Type::Only, {1 /* feature from */, 5 /* feature to */}}}; + vector const expectedGeom = {{3 /* x */, 0 /* y */}, {2, 0}, {1, 0}, {0, 0}}; + + TestRestrictions(expectedGeom, AStarAlgorithm::Result::OK, + routing::IndexGraphStarter::FakeVertex(0, 0, m2::PointD(3, 0)) /* start */, + routing::IndexGraphStarter::FakeVertex(5, 0, m2::PointD(0, 0)) /* finish */, + move(restrictions), *this); +} + +// 0 Start *--F0--->*---F1---*---F1---*---F1---*---F2-->* Finish +// 0 1 2 3 4 5 +// Note. F0 and F2 are one segment one-way features. F1 is a 3 segment two-way feature. +unique_ptr BuildLineGraph() +{ + unique_ptr loader = make_unique(); + loader->AddRoad(0 /* feature id */, true /* one way */, 1.0 /* speed */, + RoadGeometry::Points({{0.0, 0.0}, {0.0, 1.0}})); + loader->AddRoad(1 /* feature id */, false /* one way */, 1.0 /* speed */, + RoadGeometry::Points({{1.0, 0.0}, {2.0, 0.0}, {3.0, 0.0}, {3.0, 0.0}})); + loader->AddRoad(2 /* feature id */, true /* one way */, 1.0 /* speed */, + RoadGeometry::Points({{4.0, 0.0}, {5.0, 0.0}})); + + vector const joints = { + MakeJoint({{0 /* feature id */, 0 /* point id */}}), /* joint at point (0, 0) */ + MakeJoint({{0, 1}, {1, 0}}), /* joint at point (1, 0) */ + MakeJoint({{1, 3}, {2, 0}}), /* joint at point (4, 0) */ + MakeJoint({{2, 1}}), /* joint at point (5, 0) */ + }; + + traffic::TrafficCache const trafficCache; + unique_ptr graph = + make_unique(move(loader), CreateEstimator(trafficCache)); + graph->Import(joints); + return graph; +} + +// This test checks that despite the fact uturn on F1 is prohibited (moving from F1 to F1 is +// prohibited) +// it's still possible to move from F1 to F1 in straight direction. +UNIT_CLASS_TEST(RestrictionTest, LineGraph_RestrictionF1F1No) +{ + Init(BuildLineGraph()); + RestrictionVec restrictions = { + {Restriction::Type::No, {1 /* feature from */, 1 /* feature to */}}}; + vector const expectedGeom = { + {0 /* x */, 0 /* y */}, {1, 0}, {2, 0}, {3, 0}, {4, 0}, {5, 0}}; + + TestRestrictions(expectedGeom, AStarAlgorithm::Result::OK, + routing::IndexGraphStarter::FakeVertex(0 /* feature id */, 0 /* seg id */, + m2::PointD(0, 0)) /* start */, + routing::IndexGraphStarter::FakeVertex(2, 0, m2::PointD(5, 0)) /* finish */, + move(restrictions), *this); +} + +// 2 *---F2-->* +// ^ +// F0 +// | +// 1 *---F1-->* Finish +// ^ +// F0 +// | +// 0 * +// 0 1 +// Start +unique_ptr BuildFGraph() +{ + unique_ptr loader = make_unique(); + loader->AddRoad(0 /* feature id */, true /* one way */, 1.0 /* speed */, + RoadGeometry::Points({{0.0, 0.0}, {0.0, 1.0}, {0.0, 2.0}})); + loader->AddRoad(1 /* feature id */, true /* one way */, 1.0 /* speed */, + RoadGeometry::Points({{0.0, 1.0}, {1.0, 1.0}})); + loader->AddRoad(2 /* feature id */, true /* one way */, 1.0 /* speed */, + RoadGeometry::Points({{0.0, 2.0}, {1.0, 2.0}})); + + vector const joints = { + MakeJoint({{0 /* feature id */, 0 /* point id */}}), /* joint at point (0, 0) */ + MakeJoint({{0, 1}, {1, 0}}), /* joint at point (0, 1) */ + MakeJoint({{0, 2}, {2, 0}}), /* joint at point (0, 2) */ + MakeJoint({{1, 1}}), /* joint at point (1, 1) */ + MakeJoint({{2, 1}}), /* joint at point (1, 2) */ + }; + + traffic::TrafficCache const trafficCache; + unique_ptr graph = + make_unique(move(loader), CreateEstimator(trafficCache)); + graph->Import(joints); + return graph; +} + +// This test checks that having a Only restriction from F0 to F2 it's still possible move +// from F0 to F1. +UNIT_CLASS_TEST(RestrictionTest, FGraph_RestrictionF0F2Only) +{ + Init(BuildFGraph()); + RestrictionVec restrictions = { + {Restriction::Type::Only, {0 /* feature from */, 2 /* feature to */}}}; + vector const expectedGeom = {{0 /* x */, 0 /* y */}, {0, 1}, {1, 1}}; + + TestRestrictions(expectedGeom, AStarAlgorithm::Result::OK, + routing::IndexGraphStarter::FakeVertex(0 /* feature id */, 0 /* seg id */, + m2::PointD(0, 0)) /* start */, + routing::IndexGraphStarter::FakeVertex(1, 0, m2::PointD(1, 1)) /* finish */, + move(restrictions), *this); +} +} // namespace routing_test diff --git a/routing/routing_tests/routing_tests.pro b/routing/routing_tests/routing_tests.pro index 49ee873ed0..f4c315d499 100644 --- a/routing/routing_tests/routing_tests.pro +++ b/routing/routing_tests/routing_tests.pro @@ -27,12 +27,14 @@ SOURCES += \ astar_router_test.cpp \ async_router_test.cpp \ cross_routing_tests.cpp \ + cumulative_restriction_test.cpp \ followed_polyline_test.cpp \ index_graph_test.cpp \ index_graph_tools.cpp \ nearest_edge_finder_tests.cpp \ online_cross_fetcher_test.cpp \ osrm_router_test.cpp \ + restriction_test.cpp \ road_graph_builder.cpp \ road_graph_nearest_edges_test.cpp \ route_tests.cpp \ diff --git a/routing/single_mwm_router.cpp b/routing/single_mwm_router.cpp index 288dcea1d9..190a5bcfce 100644 --- a/routing/single_mwm_router.cpp +++ b/routing/single_mwm_router.cpp @@ -10,6 +10,7 @@ #include "routing/index_graph_starter.hpp" #include "routing/index_road_graph.hpp" #include "routing/pedestrian_model.hpp" +#include "routing/restriction_loader.hpp" #include "routing/route.hpp" #include "routing/routing_helpers.hpp" #include "routing/turns_generator.hpp" @@ -190,6 +191,10 @@ bool SingleMwmRouter::LoadIndex(MwmSet::MwmId const & mwmId, string const & coun FilesContainerR::TReader reader(mwmValue->m_cont.GetReader(ROUTING_FILE_TAG)); ReaderSource src(reader); IndexGraphSerializer::Deserialize(graph, src, kCarMask); + RestrictionLoader restrictionLoader(*mwmValue); + if (restrictionLoader.HasRestrictions()) + graph.SetRestrictions(restrictionLoader.StealRestrictions()); + LOG(LINFO, (ROUTING_FILE_TAG, "section for", country, "loaded in", timer.ElapsedSeconds(), "seconds")); return true;