diff --git a/routing/index_graph.cpp b/routing/index_graph.cpp index 03532cdbb1..658bceb0c3 100644 --- a/routing/index_graph.cpp +++ b/routing/index_graph.cpp @@ -160,7 +160,7 @@ void IndexGraph::GetNeighboringEdge(Segment const & from, Segment const & to, bo if (IsRestricted(m_restrictions, from, to, isOutgoing)) return; - if (m_roadAccess.GetSegmentType(to) != RoadAccess::Type::Yes) + if (m_roadAccess.GetSegmentType(to) == RoadAccess::Type::No) return; RouteWeight const weight = CalcSegmentWeight(isOutgoing ? to : from) + @@ -172,12 +172,19 @@ RouteWeight IndexGraph::GetPenalties(Segment const & u, Segment const & v) { bool const fromPassThroughAllowed = m_geometry.GetRoad(u.GetFeatureId()).IsPassThroughAllowed(); bool const toPassThroughAllowed = m_geometry.GetRoad(v.GetFeatureId()).IsPassThroughAllowed(); + // Route crosses border of pass-through/non-pass-through area if |u| and |v| have different + // pass through restrictions. + int32_t const passThroughPenalty = fromPassThroughAllowed == toPassThroughAllowed ? 0 : 1; - uint32_t const passThroughPenalty = fromPassThroughAllowed == toPassThroughAllowed ? 0 : 1; + // We do not distinguish between RoadAccess::Type::Private and RoadAccess::Type::Destination for now. + bool const fromAccessAllowed = m_roadAccess.GetSegmentType(u) == RoadAccess::Type::Yes; + bool const toAccessAllowed = m_roadAccess.GetSegmentType(v) == RoadAccess::Type::Yes; + // Route crosses border of access=yes/access={private, destination} area if |u| and |v| have different + // access restrictions. + int32_t const accessPenalty = fromAccessAllowed == toAccessAllowed ? 0 : 1; - if (IsUTurn(u, v)) - return RouteWeight(m_estimator->GetUTurnPenalty(), passThroughPenalty, 0.0 /* transitTime */); + auto const uTurnPenalty = IsUTurn(u, v) ? m_estimator->GetUTurnPenalty() : 0.0; - return RouteWeight(0.0, passThroughPenalty, 0.0 /* transitTime */); + return RouteWeight(uTurnPenalty /* weight */, passThroughPenalty, accessPenalty, 0.0 /* transitTime */); } } // namespace routing diff --git a/routing/index_graph_starter.cpp b/routing/index_graph_starter.cpp index 4312344dbf..cbe1689cda 100644 --- a/routing/index_graph_starter.cpp +++ b/routing/index_graph_starter.cpp @@ -133,7 +133,7 @@ bool IndexGraphStarter::CheckLength(RouteWeight const & weight) { // We allow 1 pass-through/non-pass-through crossing per ending located in // non-pass-through zone to allow user to leave this zone. - int const nonPassThroughCrossAllowed = + int32_t const nonPassThroughCrossAllowed = (StartPassThroughAllowed() ? 0 : 1) + (FinishPassThroughAllowed() ? 0 : 1); return weight.GetNonPassThroughCross() <= nonPassThroughCrossAllowed && diff --git a/routing/route_weight.cpp b/routing/route_weight.cpp index 164a525cad..976e29174c 100644 --- a/routing/route_weight.cpp +++ b/routing/route_weight.cpp @@ -2,27 +2,80 @@ #include "routing/cross_mwm_connector.hpp" +#include + using namespace std; +namespace +{ +template ::value, void>::type> +bool SumWillOverflow(Number lhs, Number rhs) +{ + if (lhs > 0) + return rhs > numeric_limits::max() - lhs; + if (lhs < 0) + return rhs < numeric_limits::min() - lhs; + return false; +} +} // namespace + namespace routing { double RouteWeight::ToCrossMwmWeight() const { - if (m_nonPassThroughCross > 0) + if (m_nonPassThroughCross > 0 || m_numAccessChanges > 0) return connector::kNoRoute; return GetWeight(); } +RouteWeight RouteWeight::operator+(RouteWeight const & rhs) const +{ + ASSERT(!SumWillOverflow(m_nonPassThroughCross, rhs.m_nonPassThroughCross), + (m_nonPassThroughCross, rhs.m_nonPassThroughCross)); + ASSERT(!SumWillOverflow(m_numAccessChanges, rhs.m_numAccessChanges), + (m_numAccessChanges, rhs.m_numAccessChanges)); + return RouteWeight(m_weight + rhs.m_weight, m_nonPassThroughCross + rhs.m_nonPassThroughCross, + m_numAccessChanges + rhs.m_numAccessChanges, + m_transitTime + rhs.m_transitTime); +} + +RouteWeight RouteWeight::operator-(RouteWeight const & rhs) const +{ + ASSERT_NOT_EQUAL(m_nonPassThroughCross, std::numeric_limits::min(), ()); + ASSERT_NOT_EQUAL(m_numAccessChanges, std::numeric_limits::min(), ()); + ASSERT(!SumWillOverflow(m_nonPassThroughCross, -rhs.m_nonPassThroughCross), + (m_nonPassThroughCross, -rhs.m_nonPassThroughCross)); + ASSERT(!SumWillOverflow(m_numAccessChanges, -rhs.m_numAccessChanges), + (m_numAccessChanges, -rhs.m_numAccessChanges)); + return RouteWeight(m_weight - rhs.m_weight, m_nonPassThroughCross - rhs.m_nonPassThroughCross, + m_numAccessChanges - rhs.m_numAccessChanges, + m_transitTime - rhs.m_transitTime); +} + +RouteWeight & RouteWeight::operator+=(RouteWeight const & rhs) +{ + ASSERT(!SumWillOverflow(m_nonPassThroughCross, rhs.m_nonPassThroughCross), + (m_nonPassThroughCross, rhs.m_nonPassThroughCross)); + ASSERT(!SumWillOverflow(m_numAccessChanges, rhs.m_numAccessChanges), + (m_numAccessChanges, rhs.m_numAccessChanges)); + m_weight += rhs.m_weight; + m_nonPassThroughCross += rhs.m_nonPassThroughCross; + m_numAccessChanges += rhs.m_numAccessChanges; + m_transitTime += rhs.m_transitTime; + return *this; +} + ostream & operator<<(ostream & os, RouteWeight const & routeWeight) { - os << "(" << routeWeight.GetNonPassThroughCross() << ", " << routeWeight.GetWeight() << ", " - << routeWeight.GetTransitTime() << ")"; + os << "(" << routeWeight.GetNonPassThroughCross() << ", " << routeWeight.GetNumAccessChanges() + << ", " << routeWeight.GetWeight() << ", " << routeWeight.GetTransitTime() << ")"; return os; } RouteWeight operator*(double lhs, RouteWeight const & rhs) { - return RouteWeight(lhs * rhs.GetWeight(), rhs.GetNonPassThroughCross(), + return RouteWeight(lhs * rhs.GetWeight(), rhs.GetNonPassThroughCross(), rhs.GetNumAccessChanges(), lhs * rhs.GetTransitTime()); } } // namespace routing diff --git a/routing/route_weight.hpp b/routing/route_weight.hpp index f2c0fde21c..8cd12ec9da 100644 --- a/routing/route_weight.hpp +++ b/routing/route_weight.hpp @@ -4,6 +4,7 @@ #include "base/math.hpp" +#include #include #include @@ -16,8 +17,12 @@ public: explicit constexpr RouteWeight(double weight) : m_weight(weight) {} - constexpr RouteWeight(double weight, int nonPassThroughCross, double transitTime) - : m_weight(weight), m_nonPassThroughCross(nonPassThroughCross), m_transitTime(transitTime) + constexpr RouteWeight(double weight, int32_t nonPassThroughCross, int32_t numAccessChanges, + double transitTime) + : m_weight(weight) + , m_nonPassThroughCross(nonPassThroughCross) + , m_numAccessChanges(numAccessChanges) + , m_transitTime(transitTime) { } @@ -25,13 +30,22 @@ public: double ToCrossMwmWeight() const; double GetWeight() const { return m_weight; } - int GetNonPassThroughCross() const { return m_nonPassThroughCross; } + int32_t GetNonPassThroughCross() const { return m_nonPassThroughCross; } + int32_t GetNumAccessChanges() const { return m_numAccessChanges; } double GetTransitTime() const { return m_transitTime; } bool operator<(RouteWeight const & rhs) const { if (m_nonPassThroughCross != rhs.m_nonPassThroughCross) return m_nonPassThroughCross < rhs.m_nonPassThroughCross; + // We compare m_numAccessChanges after m_nonPassThroughCross because we can have multiple nodes + // with access tags on the way from the area with limited access and no access tags on the ways + // inside this area. So we probably need to make access restriction less strict than pass + // through restrictions e.g. allow to cross access={private, destination} and build the route + // with the least possible number of such crosses or introduce some maximal number of + // access={private, destination} crosses. + if (m_numAccessChanges != rhs.m_numAccessChanges) + return m_numAccessChanges < rhs.m_numAccessChanges; if (m_weight != rhs.m_weight) return m_weight < rhs.m_weight; // Preffer bigger transit time if total weights are same. @@ -48,43 +62,37 @@ public: bool operator<=(RouteWeight const & rhs) const { return rhs >= (*this); } - RouteWeight operator+(RouteWeight const & rhs) const - { - return RouteWeight(m_weight + rhs.m_weight, m_nonPassThroughCross + rhs.m_nonPassThroughCross, - m_transitTime + rhs.m_transitTime); - } + RouteWeight operator+(RouteWeight const & rhs) const; - RouteWeight operator-(RouteWeight const & rhs) const - { - return RouteWeight(m_weight - rhs.m_weight, m_nonPassThroughCross - rhs.m_nonPassThroughCross, - m_transitTime - rhs.m_transitTime); - } + RouteWeight operator-(RouteWeight const & rhs) const; - RouteWeight & operator+=(RouteWeight const & rhs) - { - m_weight += rhs.m_weight; - m_nonPassThroughCross += rhs.m_nonPassThroughCross; - m_transitTime += rhs.m_transitTime; - return *this; - } + RouteWeight & operator+=(RouteWeight const & rhs); RouteWeight operator-() const { - return RouteWeight(-m_weight, -m_nonPassThroughCross, -m_transitTime); + ASSERT_NOT_EQUAL(m_nonPassThroughCross, std::numeric_limits::min(), ()); + ASSERT_NOT_EQUAL(m_numAccessChanges, std::numeric_limits::min(), ()); + return RouteWeight(-m_weight, -m_nonPassThroughCross, -m_numAccessChanges, -m_transitTime); } bool IsAlmostEqualForTests(RouteWeight const & rhs, double epsilon) { return m_nonPassThroughCross == rhs.m_nonPassThroughCross && + m_numAccessChanges == rhs.m_numAccessChanges && my::AlmostEqualAbs(m_weight, rhs.m_weight, epsilon) && my::AlmostEqualAbs(m_transitTime, rhs.m_transitTime, epsilon); } private: + // Note: consider smaller types for m_nonPassThroughCross and m_numAccessChanges + // in case of adding new fields to RouteWeight to reduce RouteWeight size. + // Regular weight (seconds). double m_weight = 0.0; - // Number of pass-through/non-pass-through area border cross. - int m_nonPassThroughCross = 0; + // Number of pass-through/non-pass-through area border crosses. + int32_t m_nonPassThroughCross = 0; + // Number of access=yes/access={private,destination} area border crosses. + int32_t m_numAccessChanges = 0; // Transit time. It's already included in |m_weight| (m_transitTime <= m_weight). double m_transitTime = 0.0; }; @@ -97,21 +105,23 @@ template <> constexpr RouteWeight GetAStarWeightMax() { return RouteWeight(std::numeric_limits::max() /* weight */, - std::numeric_limits::max() /* nonPassThroughCross */, - std::numeric_limits::max() /* transitTime */); + std::numeric_limits::max() /* nonPassThroughCross */, + std::numeric_limits::max() /* numAccessChanges */, + 0 /* transitTime */); // operator< prefers bigger transit time } template <> constexpr RouteWeight GetAStarWeightZero() { - return RouteWeight(0.0 /* weight */, 0 /* nonPassThroughCross */, 0.0 /* transitTime */); + return RouteWeight(0.0 /* weight */, 0 /* nonPassThroughCross */, 0 /* numAccessChanges */, + 0.0 /* transitTime */); } template <> constexpr RouteWeight GetAStarWeightEpsilon() { return RouteWeight(GetAStarWeightEpsilon(), 0 /* nonPassThroughCross */, - 0.0 /* transitTime */); + 0 /* numAccessChanges */, 0.0 /* transitTime */); } } // namespace routing diff --git a/routing/routing_tests/index_graph_tools.cpp b/routing/routing_tests/index_graph_tools.cpp index d1481d24a1..06ba3b1ef0 100644 --- a/routing/routing_tests/index_graph_tools.cpp +++ b/routing/routing_tests/index_graph_tools.cpp @@ -122,17 +122,17 @@ void TestIndexGraphTopology::AddDirectedEdge(Vertex from, Vertex to, double weig AddDirectedEdge(m_edgeRequests, from, to, weight); } -void TestIndexGraphTopology::BlockEdge(Vertex from, Vertex to) +void TestIndexGraphTopology::SetEdgeAccess(Vertex from, Vertex to, RoadAccess::Type type) { for (auto & r : m_edgeRequests) { if (r.m_from == from && r.m_to == to) { - r.m_isBlocked = true; + r.m_accessType = type; return; } } - CHECK(false, ("Cannot block edge that is not in the graph", from, to)); + CHECK(false, ("Cannot set access for edge that is not in the graph", from, to)); } bool TestIndexGraphTopology::FindPath(Vertex start, Vertex finish, double & pathWeight, @@ -254,19 +254,15 @@ void TestIndexGraphTopology::Builder::BuildJoints() void TestIndexGraphTopology::Builder::BuildGraphFromRequests(vector const & requests) { - vector blockedFeatureIds; + map segmentTypes; for (auto const & request : requests) { BuildSegmentFromEdge(request); - if (request.m_isBlocked) - blockedFeatureIds.push_back(request.m_id); - } - - map segmentTypes; - for (auto const fid : blockedFeatureIds) - { - segmentTypes[Segment(kFakeNumMwmId, fid, 0 /* wildcard segmentIdx */, true)] = - RoadAccess::Type::No; + if (request.m_accessType != RoadAccess::Type::Yes) + { + segmentTypes[Segment(kFakeNumMwmId, request.m_id, 0 /* wildcard segmentIdx */, true)] = + request.m_accessType; + } } m_roadAccess.SetSegmentTypes(move(segmentTypes)); diff --git a/routing/routing_tests/index_graph_tools.hpp b/routing/routing_tests/index_graph_tools.hpp index 856db5b2ea..3189ef47d3 100644 --- a/routing/routing_tests/index_graph_tools.hpp +++ b/routing/routing_tests/index_graph_tools.hpp @@ -155,8 +155,8 @@ public: // and the graph itself is built only after a call to FindPath. void AddDirectedEdge(Vertex from, Vertex to, double weight); - // Blocks a previously added edge without removing it from the graph. - void BlockEdge(Vertex from, Vertex to); + // Sets access for previously added edge. + void SetEdgeAccess(Vertex from, Vertex to, RoadAccess::Type type); // Finds a path between the start and finish vertices. Returns true iff a path exists. bool FindPath(Vertex start, Vertex finish, double & pathWeight, vector & pathEdges) const; @@ -168,7 +168,7 @@ private: Vertex m_from = 0; Vertex m_to = 0; double m_weight = 0.0; - bool m_isBlocked = false; + RoadAccess::Type m_accessType = RoadAccess::Type::Yes; EdgeRequest(uint32_t id, Vertex from, Vertex to, double weight) : m_id(id), m_from(from), m_to(to), m_weight(weight) diff --git a/routing/routing_tests/road_access_test.cpp b/routing/routing_tests/road_access_test.cpp index 79dfe92845..41a1407f27 100644 --- a/routing/routing_tests/road_access_test.cpp +++ b/routing/routing_tests/road_access_test.cpp @@ -87,7 +87,7 @@ UNIT_TEST(RoadAccess_WayBlocked) double const expectedWeight = 0.0; vector const expectedEdges = {}; - graph.BlockEdge(1, 2); + graph.SetEdgeAccess(1, 2, RoadAccess::Type::No); TestTopologyGraph(graph, 0, 3, false /* pathFound */, expectedWeight, expectedEdges); } @@ -111,12 +111,32 @@ UNIT_TEST(RoadAccess_BarrierBypassing) expectedEdges = {{0, 1}, {1, 2}, {2, 5}}; TestTopologyGraph(graph, 0, 5, true /* pathFound */, expectedWeight, expectedEdges); - graph.BlockEdge(1, 2); + // Test avoid access=private while we have route with RoadAccess::Type::Yes. + graph.SetEdgeAccess(1, 2, RoadAccess::Type::Private); expectedWeight = 4.0; expectedEdges = {{0, 3}, {3, 4}, {4, 5}}; TestTopologyGraph(graph, 0, 5, true /* pathFound */, expectedWeight, expectedEdges); - graph.BlockEdge(3, 4); + // Test avoid access=destination while we have route with RoadAccess::Type::Yes. + graph.SetEdgeAccess(1, 2, RoadAccess::Type::Destination); + TestTopologyGraph(graph, 0, 5, true /* pathFound */, expectedWeight, expectedEdges); + + // Test avoid access=no while we have route with RoadAccess::Type::Yes. + graph.SetEdgeAccess(1, 2, RoadAccess::Type::No); + TestTopologyGraph(graph, 0, 5, true /* pathFound */, expectedWeight, expectedEdges); + + // Test it's possible to build the route because private usage restriction is not strict: + // we use minimal possible number of access=yes/access={private, destination} crossing + // but allow to use private/destination if there is no other way. + graph.SetEdgeAccess(3, 4, RoadAccess::Type::Private); + TestTopologyGraph(graph, 0, 5, true /* pathFound */, expectedWeight, expectedEdges); + + // Test we have the same behaviour for access=destination. + graph.SetEdgeAccess(3, 4, RoadAccess::Type::Destination); + TestTopologyGraph(graph, 0, 5, true /* pathFound */, expectedWeight, expectedEdges); + + // Test we have strict restriction for access=no and can not build route. + graph.SetEdgeAccess(3, 4, RoadAccess::Type::No); TestTopologyGraph(graph, 0, 5, false /* pathFound */, expectedWeight, expectedEdges); } } // namespace diff --git a/routing/transit_graph.cpp b/routing/transit_graph.cpp index 1479e4a28f..2cdf384795 100644 --- a/routing/transit_graph.cpp +++ b/routing/transit_graph.cpp @@ -56,13 +56,15 @@ RouteWeight TransitGraph::CalcSegmentWeight(Segment const & segment) const if (IsGate(segment)) { auto const weight = GetGate(segment).GetWeight(); - return RouteWeight(weight /* weight */, 0 /* nonPassThrougCross */, weight /* transitTime */); + return RouteWeight(weight /* weight */, 0 /* nonPassThroughCross */, 0 /* numAccessChanges */, + weight /* transitTime */); } if (IsEdge(segment)) { auto const weight = GetEdge(segment).GetWeight(); - return RouteWeight(weight /* weight */, 0 /* nonPassThrougCross */, weight /* transitTime */); + return RouteWeight(weight /* weight */, 0 /* nonPassThrougCross */, 0 /* numAccessChanges */, + weight /* transitTime */); } return RouteWeight( @@ -94,7 +96,7 @@ RouteWeight TransitGraph::GetTransferPenalty(Segment const & from, Segment const // 3. |from| is edge, |to| is edge from another line directly connected to |from|. auto const it = m_transferPenalties.find(lineIdTo); CHECK(it != m_transferPenalties.cend(), ("Segment", to, "belongs to unknown line:", lineIdTo)); - return RouteWeight(it->second /* weight */, 0 /* nonPassThrougCross */, + return RouteWeight(it->second /* weight */, 0 /* nonPassThrougCross */, 0 /* numAccessChanges */, it->second /* transitTime */); }