diff options
author | Vladimir Byko-Ianko <v.bykoianko@corp.mail.ru> | 2016-11-16 08:26:16 +0300 |
---|---|---|
committer | Vladimir Byko-Ianko <v.bykoianko@corp.mail.ru> | 2016-11-25 10:25:11 +0300 |
commit | 91ead6af9046745416b136c39561d1ba598f1b07 (patch) | |
tree | 6b8eff31b91ba709b6f943ea998256eea2181a2c | |
parent | eb1c85bce50a6d98fa92b1cec278643b26b59946 (diff) |
Generating restriction, implementaion implementation using them at runtime§:wq, applying them on index graph and tests on it.
-rw-r--r-- | routing/fseg_index.cpp | 104 | ||||
-rw-r--r-- | routing/geometry.cpp | 13 | ||||
-rw-r--r-- | routing/geometry.hpp | 18 | ||||
-rw-r--r-- | routing/index_graph.cpp | 429 | ||||
-rw-r--r-- | routing/index_graph.hpp | 124 | ||||
-rw-r--r-- | routing/index_graph_starter.cpp | 8 | ||||
-rw-r--r-- | routing/joint_index.cpp | 17 | ||||
-rw-r--r-- | routing/joint_index.hpp | 6 | ||||
-rw-r--r-- | routing/road_index.cpp | 53 | ||||
-rw-r--r-- | routing/road_index.hpp | 27 | ||||
-rw-r--r-- | routing/road_point.hpp | 23 | ||||
-rw-r--r-- | routing/routing_serialization.cpp | 4 | ||||
-rw-r--r-- | routing/routing_tests/index_graph_test.cpp | 71 | ||||
-rw-r--r-- | routing/routing_tests/index_graph_tools.cpp | 84 | ||||
-rw-r--r-- | routing/routing_tests/index_graph_tools.hpp | 46 | ||||
-rw-r--r-- | routing/routing_tests/restriction_test.cpp | 622 | ||||
-rw-r--r-- | routing/routing_tests/routing_tests.pro | 3 | ||||
-rw-r--r-- | routing/single_mwm_router.cpp | 10 |
18 files changed, 1559 insertions, 103 deletions
diff --git a/routing/fseg_index.cpp b/routing/fseg_index.cpp new file mode 100644 index 0000000000..a2ab7037eb --- /dev/null +++ b/routing/fseg_index.cpp @@ -0,0 +1,104 @@ +#include "routing/fseg_index.hpp" + +#include "base/logging.hpp" + +#include "std/utility.hpp" + +namespace routing +{ +void FSegIndex::Import(vector<Joint> const & joints) +{ + for (JointId jointId = 0; jointId < joints.size(); ++jointId) + { + Joint const & joint = joints[jointId]; + for (uint32_t i = 0; i < joint.GetSize(); ++i) + { + FSegId const & entry = joint.GetEntry(i); + RoadJointIds & roadJoints = m_roads[entry.GetFeatureId()]; + roadJoints.AddJoint(entry.GetSegId(), jointId); + if (entry.GetFeatureId() > m_maxFeatureId) + m_maxFeatureId = entry.GetFeatureId(); + } + } +} + +bool FSegIndex::GetAdjacentSegments(uint32_t featureIdFrom, uint32_t featureIdTo, + routing::FSegId & from, routing::FSegId & to, JointId & jointId) const +{ + auto const fromIt = m_roads.find(featureIdFrom); + if (fromIt == m_roads.cend()) + return false; + + auto const toIt = m_roads.find(featureIdTo); + if (toIt == m_roads.cend()) + return false; + + routing::RoadJointIds const & roadJointIdsFrom = fromIt->second; + routing::RoadJointIds const & roadJointIdsTo = toIt->second; + if (roadJointIdsFrom.GetSize() == 0 || roadJointIdsTo.GetSize() == 0) + return false; // No sence in restrictions on features without joints. + + // Note. It's important to check other variant besides a restriction from last segment + // of featureIdFrom to first segment of featureIdTo since two way features can have + // reverse point order. + if (roadJointIdsFrom.Back() == roadJointIdsTo.Front()) + { + from = routing::FSegId(featureIdFrom, roadJointIdsFrom.GetSize() - 1); + to = routing::FSegId(featureIdTo, 0); + jointId = roadJointIdsFrom.Back(); + return true; + } + + if (roadJointIdsFrom.Front() == roadJointIdsTo.Back()) + { + from = routing::FSegId(featureIdFrom, 0); + to = routing::FSegId(featureIdTo, roadJointIdsTo.GetSize() - 1); + jointId = roadJointIdsFrom.Front(); + return true; + } + + if (roadJointIdsFrom.Back() == roadJointIdsTo.Back()) + { + from = routing::FSegId(featureIdFrom, roadJointIdsFrom.GetSize() - 1); + to = routing::FSegId(featureIdTo, roadJointIdsTo.GetSize() - 1); + jointId = roadJointIdsFrom.Back(); + return true; + } + + if (roadJointIdsFrom.Front() == roadJointIdsTo.Front()) + { + from = routing::FSegId(featureIdFrom, 0); + to = routing::FSegId(featureIdTo, 0); + jointId = roadJointIdsFrom.Front(); + return true; + } + return false; // |featureIdFrom| and |featureIdTo| are not adjacent. +} + +void FSegIndex::ApplyRestrictionNo(routing::FSegId const & from, routing::FSegId const & to, JointId jointId) +{ +} + +void FSegIndex::ApplyRestrictionOnly(routing::FSegId const & from, routing::FSegId const & to, JointId jointId) +{ +} + +pair<JointId, uint32_t> FSegIndex::FindNeigbor(FSegId fseg, bool forward) const +{ + auto const it = m_roads.find(fseg.GetFeatureId()); + if (it == m_roads.cend()) + return make_pair(kInvalidJointId, 0); + + RoadJointIds const & joints = it->second; + int32_t const step = forward ? 1 : -1; + + for (uint32_t segId = fseg.GetSegId() + step; segId < joints.GetSize(); segId += step) + { + JointId const jointId = joints.GetJointId(segId); + if (jointId != kInvalidJointId) + return make_pair(jointId, segId); + } + + return make_pair(kInvalidJointId, 0); +} +} // namespace routing diff --git a/routing/geometry.cpp b/routing/geometry.cpp index 218aa0eb03..556911a4c6 100644 --- a/routing/geometry.cpp +++ b/routing/geometry.cpp @@ -6,6 +6,8 @@ #include "base/assert.hpp" +#include "std/iostream.hpp" + using namespace routing; namespace @@ -83,6 +85,17 @@ RoadGeometry const & Geometry::GetRoad(uint32_t featureId) const return road; } +string DebugPrint(RoadGeometry const & roadGeometry) +{ + stringstream str; + str << "RoadGeometry [ m_isRoad: " << roadGeometry.m_isRoad + << ", m_isOneWay: " << roadGeometry.m_isOneWay + << ", m_speed: " << roadGeometry.m_speed + << ", m_points: " << DebugPrint(roadGeometry.m_points) + << " ]"; + return str.str(); +} + unique_ptr<GeometryLoader> CreateGeometryLoader(Index const & index, MwmSet::MwmId const & mwmId, shared_ptr<IVehicleModel> vehicleModel) { diff --git a/routing/geometry.hpp b/routing/geometry.hpp index f10cb6dd78..7324f352fc 100644 --- a/routing/geometry.hpp +++ b/routing/geometry.hpp @@ -11,12 +11,15 @@ #include "std/cstdint.hpp" #include "std/shared_ptr.hpp" +#include "std/string.hpp" #include "std/unique_ptr.hpp" namespace routing { class RoadGeometry final { + friend string DebugPrint(RoadGeometry const & roadGeometry); + public: using Points = buffer_vector<m2::PointD, 32>; @@ -40,6 +43,14 @@ public: uint32_t GetPointsCount() const { return m_points.size(); } + bool operator==(RoadGeometry const & roadGeometry) + { + return m_isRoad == roadGeometry.m_isRoad + && m_isOneWay == roadGeometry.m_isOneWay + && m_speed == roadGeometry.m_speed + && m_points == roadGeometry.m_points; + } + private: bool m_isRoad = false; bool m_isOneWay = false; @@ -47,6 +58,8 @@ private: Points m_points; }; +string DebugPrint(RoadGeometry const & roadGeometry); + class GeometryLoader { public: @@ -63,11 +76,6 @@ public: RoadGeometry const & GetRoad(uint32_t featureId) const; - m2::PointD const & GetPoint(RoadPoint const & rp) const - { - return GetRoad(rp.GetFeatureId()).GetPoint(rp.GetPointId()); - } - private: // Feature id to RoadGeometry map. mutable unordered_map<uint32_t, RoadGeometry> m_roads; diff --git a/routing/index_graph.cpp b/routing/index_graph.cpp index f2c8131298..d0e45261cc 100644 --- a/routing/index_graph.cpp +++ b/routing/index_graph.cpp @@ -2,6 +2,7 @@ #include "base/assert.hpp" #include "base/exception.hpp" +#include "base/stl_helpers.hpp" namespace routing { @@ -18,15 +19,404 @@ void IndexGraph::GetEdgesList(Joint::Id jointId, bool isOutgoing, vector<JointEd }); } +m2::PointD const & IndexGraph::GetPoint(RoadPoint const & ftp) const +{ + RoadGeometry const & road = GetRoad(ftp.GetFeatureId()); + CHECK_LESS(ftp.GetPointId(), road.GetPointsCount(), ()); + return road.GetPoint(ftp.GetPointId()); +} + m2::PointD const & IndexGraph::GetPoint(Joint::Id jointId) const { - return m_geometry.GetPoint(m_jointIndex.GetPoint(jointId)); + return GetPoint(m_jointIndex.GetPoint(jointId)); +} + +double IndexGraph::GetSpeed(RoadPoint ftp) const +{ + return GetRoad(ftp.GetFeatureId()).GetSpeed(); +} + +void IndexGraph::Build(uint32_t jointNumber) +{ + m_jointIndex.Build(m_roadIndex, jointNumber); } void IndexGraph::Import(vector<Joint> const & joints) { m_roadIndex.Import(joints); - m_jointIndex.Build(m_roadIndex, joints.size()); + Build(joints.size()); +} + +void IndexGraph::GetSingleFeaturePaths(RoadPoint from, RoadPoint to, + vector<RoadPoint> & singleFeaturePath) +{ + CHECK_EQUAL(from.GetFeatureId(), to.GetFeatureId(), ()); + singleFeaturePath.clear(); + if (from == to) + { + singleFeaturePath.push_back(from); + return; + } + + int shift = to.GetPointId() > from.GetPointId() ? 1 : -1; + for (int i = from.GetPointId(); i != to.GetPointId(); i += shift) + singleFeaturePath.emplace_back(from.GetFeatureId(), i); + singleFeaturePath.emplace_back(from.GetFeatureId(), to.GetPointId()); +} + +void IndexGraph::GetConnectionPaths(Joint::Id from, Joint::Id to, + vector<vector<RoadPoint>> & connectionPaths) +{ + CHECK_NOT_EQUAL(from, Joint::kInvalidId, ()); + CHECK_NOT_EQUAL(to, Joint::kInvalidId, ()); + + vector<pair<RoadPoint, RoadPoint>> connections; + m_jointIndex.FindPointsWithCommonFeature(from, to, connections); + CHECK_LESS(0, connections.size(), ()); + + connectionPaths.clear(); + for (pair<RoadPoint, RoadPoint> const & c : connections) + { + vector<RoadPoint> roadPoints; + GetSingleFeaturePaths(c.first, c.second, roadPoints); + connectionPaths.push_back(move(roadPoints)); + } +} + +void IndexGraph::GetShortestConnectionPath(Joint::Id from, Joint::Id to, + vector<RoadPoint> & shortestConnectionPath) +{ + vector<pair<RoadPoint, RoadPoint>> connections; + m_jointIndex.FindPointsWithCommonFeature(from, to, connections); + CHECK_LESS(0, connections.size(), ()); + + // Note. The case when size of connections is zero is the most common case. If not, + // it's necessary to perform a costy calls below. + if (connections.size() == 1) + { + GetSingleFeaturePaths(connections[0].first /* from */, connections[0].second /* to */, + shortestConnectionPath); + return; + } + + double minWeight = numeric_limits<double>::max(); + pair<RoadPoint, RoadPoint> minWeightConnection; + for (pair<RoadPoint, RoadPoint> const & c : connections) + { + CHECK_EQUAL(c.first.GetFeatureId(), c.second.GetFeatureId(), ()); + RoadGeometry const & geom = GetRoad(c.first.GetFeatureId()); + if (!geom.IsRoad()) + continue; + + double weight = m_estimator->CalcEdgesWeight(geom, c.first.GetPointId() /* from */, + c.second.GetPointId() /* to */); + if (weight < minWeight) + { + minWeight = weight; + minWeightConnection = c; + } + } + + if (minWeight == numeric_limits<double>::max()) + { + MYTHROW(RootException, + ("Joint from:", from, "and joint to:", to, "are not connected a feature necessary type.")); + } + + GetSingleFeaturePaths(minWeightConnection.first /* from */, minWeightConnection.second /* to */, + shortestConnectionPath); +} + +void IndexGraph::GetOutgoingGeomEdges(vector<JointEdge> const & outgoingEdges, Joint::Id center, + vector<JointEdgeGeom> & outgoingGeomEdges) +{ + for (JointEdge const & e : outgoingEdges) + { + vector<RoadPoint> shortestConnectionPath; + GetShortestConnectionPath(center, e.GetTarget(), shortestConnectionPath); + outgoingGeomEdges.emplace_back(e.GetTarget(), shortestConnectionPath); + } +} + +void IndexGraph::CreateFakeFeatureGeometry(vector<RoadPoint> const & geometrySource, + RoadGeometry & geometry) const +{ + double averageSpeed = 0.0; + buffer_vector<m2::PointD, 32> points(geometrySource.size()); + for (size_t i = 0; i < geometrySource.size(); ++i) + { + RoadPoint const ftp = geometrySource[i]; + averageSpeed += GetSpeed(ftp) / geometrySource.size(); + points[i] = GetPoint(ftp); + } + + geometry = RoadGeometry(true /* oneWay */, averageSpeed, points); +} + +uint32_t IndexGraph::AddFakeFeature(Joint::Id from, Joint::Id to, vector<RoadPoint> const & geometrySource) +{ + CHECK_LESS(from, m_jointIndex.GetNumJoints(), ()); + CHECK_LESS(to, m_jointIndex.GetNumJoints(), ()); + CHECK_LESS(1, geometrySource.size(), ()); + + // Getting fake feature geometry. + RoadGeometry geom; + CreateFakeFeatureGeometry(geometrySource, geom); + m_fakeFeatureGeometry.insert(make_pair(m_nextFakeFeatureId, geom)); + + // Adding to indexes for fake feature: |from|->{via point}->|to|. + RoadPoint const fromFakeFtPoint(m_nextFakeFeatureId, 0 /* point id */); + RoadPoint const toFakeFtPoint(m_nextFakeFeatureId, geometrySource.size() - 1 /* point id */); + + m_roadIndex.AddJoint(fromFakeFtPoint, from); + m_roadIndex.AddJoint(toFakeFtPoint, to); + + m_jointIndex.AppendToJoint(from, fromFakeFtPoint); + m_jointIndex.AppendToJoint(to, toFakeFtPoint); + + return m_nextFakeFeatureId++; +} + +void IndexGraph::FindOneStepAsideRoadPoint(RoadPoint const & center, Joint::Id centerId, + vector<JointEdge> const & edges, + vector<Joint::Id> & oneStepAside) const +{ + oneStepAside.clear(); + m_roadIndex.ForEachJoint(center.GetFeatureId(), + [&](uint32_t /*pointId*/, Joint::Id jointId){ + for (JointEdge const & e : edges) + { + if (e.GetTarget() == jointId) + oneStepAside.push_back(jointId); + } + }); +} + +bool IndexGraph::ApplyRestrictionPrepareData(RoadPoint const & from, RoadPoint const & to, Joint::Id centerId, + vector<JointEdge> & ingoingEdges, vector<JointEdge> & outgoingEdges, + Joint::Id & fromFirstOneStepAside, Joint::Id & toFirstOneStepAside) +{ + GetEdgesList(centerId, false /* isOutgoing */, ingoingEdges); + vector<Joint::Id> fromOneStepAside; + FindOneStepAsideRoadPoint(from, centerId, ingoingEdges, fromOneStepAside); + if (fromOneStepAside.empty()) + return false; + + GetEdgesList(centerId, true /* isOutgoing */, outgoingEdges); + vector<Joint::Id> toOneStepAside; + FindOneStepAsideRoadPoint(to, centerId, outgoingEdges, toOneStepAside); + if (toOneStepAside.empty()) + return false; + + fromFirstOneStepAside = fromOneStepAside.back(); + toFirstOneStepAside = toOneStepAside.back(); + return true; +} + +void IndexGraph::ApplyRestrictionNo(RoadPoint const & from, RoadPoint const & to, + Joint::Id centerId) +{ + vector<JointEdge> ingoingEdges; + vector<JointEdge> outgoingEdges; + Joint::Id fromFirstOneStepAside = Joint::kInvalidId; + Joint::Id toFirstOneStepAside = Joint::kInvalidId; + if (!ApplyRestrictionPrepareData(from, to, centerId, ingoingEdges, outgoingEdges, + fromFirstOneStepAside, toFirstOneStepAside)) + { + return; + } + + // One ingoing edge case. + if (ingoingEdges.size() == 1) + { + DisableEdge(centerId, toFirstOneStepAside); + return; + } + + // One outgoing edge case. + if (outgoingEdges.size() == 1) + { + DisableEdge(fromFirstOneStepAside, centerId); + return; + } + + // Prohibition of moving from one segment to another in case of any number of ingoing and outgoing edges. + // The idea is to tranform the navigation graph for every non-degenerate case as it's shown below. + // At the picture below a restriction for prohibition moving from 4 to O to 3 is shown. + // So to implement it it's necessary to remove (disable) an edge 4-O and add features (edges) 4-N-1 and N-2. + // + // 1 2 3 1 2 3 + // * * * * * * + // ↖ ^ ↗ ^↖ ↗^ ↗ + // ↖ | ↗ | ↖ | ↗ + // ↖ | ↗ |↗ ↖| ↗ + // * O ==> N * * O + // ↗ ^ ↖ ^ ^ ↖ + // ↗ | ↖ | | ↖ + // ↗ | ↖ | | ↖ + // * * * * * * + // 4 5 7 4 5 7 + + outgoingEdges.erase(remove_if(outgoingEdges.begin(), outgoingEdges.end(), + [&](JointEdge const & e) + { + // Removing edge N->3 in example above. + return e.GetTarget() == toFirstOneStepAside + // Preveting form adding in loop below + // cycles |fromFirstOneStepAside|->|centerId|->|fromFirstOneStepAside|. + || e.GetTarget() == fromFirstOneStepAside + // Removing edges |centerId|->|centerId|. + || e.GetTarget() == centerId; + }), outgoingEdges.end()); + + // Getting outgoing shortest edges geometry. + // Node. |centerId| could be connected with any outgoing joint with + // mote than one edge but only the shortest one should be takenn into + // account. + my::SortUnique(outgoingEdges, my::LessBy(&JointEdge::GetTarget), + my::EqualsBy(&JointEdge::GetTarget)); + vector<JointEdgeGeom> outgoingShortestEdges; + GetOutgoingGeomEdges(outgoingEdges, centerId, outgoingShortestEdges); + + vector<RoadPoint> shortestIngoingPath; + GetShortestConnectionPath(fromFirstOneStepAside, centerId, shortestIngoingPath); + JointEdgeGeom ingoingShortestEdge(fromFirstOneStepAside, shortestIngoingPath); + + Joint::Id newJoint = Joint::kInvalidId; + for (auto it = outgoingShortestEdges.cbegin(); + it != outgoingShortestEdges.cend(); ++it) + { + if (it == outgoingShortestEdges.cbegin()) + { + // Adding the edge 4->N->1 in example above. + if (fromFirstOneStepAside == centerId || centerId == it->GetTarget()) + { + // @TODO(bykoianko) In rare cases it's posible that outgoing edges staring from |centerId| + // contain as targets |centerId|. The same thing with ingoing edges. + // The most likely it's a consequence of adding + // restrictions with type no for some bidirectional roads. It's necessary to + // investigate this case, to undestand the reasons of appearing such edges clearly, + // prevent appearing of such edges and write unit tests on it. + return; + } + CHECK(!ingoingShortestEdge.GetShortestPath().empty(), ()); + vector<RoadPoint> geometrySource(ingoingShortestEdge.GetShortestPath().cbegin(), + ingoingShortestEdge.GetShortestPath().cend() - 1); + geometrySource.insert(geometrySource.end(), it->GetShortestPath().cbegin(), + it->GetShortestPath().cend()); + + uint32_t const featureId = AddFakeFeature(fromFirstOneStepAside, it->GetTarget(), geometrySource); + newJoint = InsertJoint({featureId, + static_cast<uint32_t>(ingoingShortestEdge.GetShortestPath().size() - 1) + /* Intermediate joint of added feature */}); + } + else + { + // Adding the edge N->2 in example above. + AddFakeFeature(newJoint, it->GetTarget(), it->GetShortestPath()); + } + } + + DisableEdge(fromFirstOneStepAside, centerId); +} + +void IndexGraph::ApplyRestrictionOnly(RoadPoint const & from, RoadPoint const & to, + Joint::Id centerId) +{ + vector<JointEdge> ingoingEdges; + vector<JointEdge> outgoingEdges; + Joint::Id fromFirstOneStepAside = Joint::kInvalidId; + Joint::Id toFirstOneStepAside = Joint::kInvalidId; + if (!ApplyRestrictionPrepareData(from, to, centerId, ingoingEdges, outgoingEdges, + fromFirstOneStepAside, toFirstOneStepAside)) + { + return; + } + + // One outgoing edge case. + if (outgoingEdges.size() == 1) + { + for (auto const & e : ingoingEdges) + { + if (e.GetTarget() != fromFirstOneStepAside) + DisableEdge(e.GetTarget(), centerId); + } + return; + } + + // One ingoing edge case. + if (ingoingEdges.size() == 1) + { + for (auto const & e : outgoingEdges) + { + if (e.GetTarget() != toFirstOneStepAside) + DisableEdge(centerId, e.GetTarget()); + } + return; + } + + // It's possible to move only from one segment to another in case of any number of ingoing and outgoing edges. + // The idea is to tranform the navigation graph for every non-degenerate case as it's shown below. + // At the picture below a restriction for permission moving only from 7 to O to 3 is shown. + // So to implement it it's necessary to remove (disable) an edge 7-O and add feature (edge) 4-N-3. + // Adding N is important for a route recovery stage. (The geometry of O will be copied to N.) + // + // 1 2 3 1 2 3 + // * * * * * * + // ↖ ^ ↗ ↖ ^ ↗^ + // ↖ | ↗ ↖ | ↗ | + // ↖ | ↗ ↖| ↗ | + // * O ==> * O * N + // ↗ ^ ↖ ↗^ ^ + // ↗ | ↖ ↗ | | + // ↗ | ↖ ↗ | | + // * * * * * * + // 4 5 7 4 5 7 + + vector<RoadPoint> ingoingShortestPath; + GetShortestConnectionPath(fromFirstOneStepAside, centerId, ingoingShortestPath); + vector<RoadPoint> outgoingShortestPath; + GetShortestConnectionPath(centerId, toFirstOneStepAside, outgoingShortestPath); + CHECK_LESS(0, ingoingShortestPath.size(), ()); + vector<RoadPoint> geometrySource(ingoingShortestPath.cbegin(), ingoingShortestPath.cend() - 1); + geometrySource.insert(geometrySource.end(), outgoingShortestPath.cbegin(), outgoingShortestPath.cend()); + + AddFakeFeature(fromFirstOneStepAside, toFirstOneStepAside, geometrySource); + DisableEdge(fromFirstOneStepAside, centerId); +} + +void IndexGraph::ApplyRestrictions(RestrictionVec const & restrictions) +{ + for (Restriction const & restriction : restrictions) + { + if (restriction.m_featureIds.size() != 2) + { + LOG(LERROR, ("Only to link restriction are supported.")); + continue; + } + + RoadPoint from; + RoadPoint to; + Joint::Id jointId; + if (!m_roadIndex.GetAdjacentFtPoints(restriction.m_featureIds[0], restriction.m_featureIds[1], + from, to, jointId)) + { + continue; // Restriction is not contain adjacent features. + } + + try + { + switch (restriction.m_type) + { + case Restriction::Type::No: ApplyRestrictionNo(from, to, jointId); continue; + case Restriction::Type::Only: ApplyRestrictionOnly(from, to, jointId); continue; + } + } + catch(RootException const & e) + { + LOG(LERROR, ("Exception while applying restrictions. Message:", e.Msg())); + } + } } Joint::Id IndexGraph::InsertJoint(RoadPoint const & rp) @@ -54,33 +444,48 @@ bool IndexGraph::JointLiesOnRoad(Joint::Id jointId, uint32_t featureId) const inline void IndexGraph::GetNeighboringEdges(RoadPoint rp, bool isOutgoing, vector<JointEdge> & edges) const { - RoadGeometry const & road = m_geometry.GetRoad(rp.GetFeatureId()); + RoadGeometry const & road = GetRoad(rp.GetFeatureId()); if (!road.IsRoad()) return; bool const bidirectional = !road.IsOneWay(); if (!isOutgoing || bidirectional) - GetNeighboringEdge(road, rp, false /* forward */, edges); + GetNeighboringEdge(road, rp, false /* forward */, isOutgoing, edges); if (isOutgoing || bidirectional) - GetNeighboringEdge(road, rp, true /* forward */, edges); + GetNeighboringEdge(road, rp, true /* forward */, isOutgoing, edges); } inline void IndexGraph::GetNeighboringEdge(RoadGeometry const & road, RoadPoint rp, bool forward, - vector<JointEdge> & edges) const + bool outgoing, vector<JointEdge> & edges) const { pair<Joint::Id, uint32_t> const & neighbor = m_roadIndex.FindNeighbor(rp, forward); - if (neighbor.first != Joint::kInvalidId) - { - double const distance = m_estimator->CalcEdgesWeight(road, rp.GetPointId(), neighbor.second); - edges.push_back({neighbor.first, distance}); - } + if (neighbor.first == Joint::kInvalidId) + return; + + Joint::Id const rbJointId = m_roadIndex.GetJointId(rp); + auto const edge = outgoing ? make_pair(rbJointId, neighbor.first) + : make_pair(neighbor.first, rbJointId); + if (m_blockedEdges.find(edge) != m_blockedEdges.end()) + return; + + double const distance = m_estimator->CalcEdgesWeight(road, rp.GetPointId(), neighbor.second); + edges.push_back({neighbor.first, distance}); +} + +RoadGeometry const & IndexGraph::GetRoad(uint32_t featureId) const +{ + auto const it = m_fakeFeatureGeometry.find(featureId); + if (it != m_fakeFeatureGeometry.cend()) + return it->second; + + return m_geometry.GetRoad(featureId); } void IndexGraph::GetDirectedEdge(uint32_t featureId, uint32_t pointFrom, uint32_t pointTo, Joint::Id target, bool forward, vector<JointEdge> & edges) const { - RoadGeometry const & road = m_geometry.GetRoad(featureId); + RoadGeometry const & road = GetRoad(featureId); if (!road.IsRoad()) return; diff --git a/routing/index_graph.hpp b/routing/index_graph.hpp index 0d7f576b58..413c8fbc54 100644 --- a/routing/index_graph.hpp +++ b/routing/index_graph.hpp @@ -6,6 +6,7 @@ #include "routing/joint_index.hpp" #include "routing/road_index.hpp" #include "routing/road_point.hpp" +#include "routing/routing_serialization.hpp" #include "coding/reader.hpp" #include "coding/write_to_sink.hpp" @@ -13,6 +14,7 @@ #include "geometry/point2d.hpp" #include "std/cstdint.hpp" +#include "std/set.hpp" #include "std/shared_ptr.hpp" #include "std/unique_ptr.hpp" #include "std/utility.hpp" @@ -29,8 +31,27 @@ public: private: // Target is vertex going to for outgoing edges, vertex going from for ingoing edges. - Joint::Id const m_target; - double const m_weight; + Joint::Id m_target; + double m_weight; +}; + +class JointEdgeGeom final +{ +public: + JointEdgeGeom() = default; + JointEdgeGeom(Joint::Id target, vector<RoadPoint> & shortestPath) + : m_target(target), m_shortestPath(shortestPath) + { + } + Joint::Id GetTarget() const { return m_target; } + vector<RoadPoint> const & GetShortestPath() const { return m_shortestPath; } + +private: + // Target is vertex going to for outgoing edges, vertex going from for ingoing edges. + Joint::Id m_target = Joint::kInvalidId; + // Two joints can be connected with several path. But in case of find shortest routes + // only the shortest one should be considered. + vector<RoadPoint> m_shortestPath; }; class IndexGraph final @@ -48,11 +69,12 @@ public: m2::PointD const & GetPoint(Joint::Id jointId) const; Geometry const & GetGeometry() const { return m_geometry; } + EdgeEstimator const & GetEstimator() const { return *m_estimator; } uint32_t GetNumRoads() const { return m_roadIndex.GetSize(); } uint32_t GetNumJoints() const { return m_jointIndex.GetNumJoints(); } uint32_t GetNumPoints() const { return m_jointIndex.GetNumPoints(); } - + m2::PointD const & GetPoint(RoadPoint const & ftp) const; void Import(vector<Joint> const & joints); Joint::Id InsertJoint(RoadPoint const & rp); bool JointLiesOnRoad(Joint::Id jointId, uint32_t featureId) const; @@ -75,16 +97,108 @@ public: { uint32_t const jointsSize = ReadPrimitiveFromSource<uint32_t>(src); m_roadIndex.Deserialize(src); - m_jointIndex.Build(m_roadIndex, jointsSize); + Build(jointsSize); } + Joint::Id GetJointIdForTesting(RoadPoint const & ftp) const {return m_roadIndex.GetJointId(ftp); } + + /// \brief Disable all edges between |from| and |to| if they are different and adjacent. + /// \note Despit the fact that |from| and |to| could be connected with several edges + /// it's a rare case. In most cases |from| and |to| are connected with only one edge + /// if they are adjacent. + /// \note The method doesn't affect routing if |from| and |to| are not adjacent or + /// if one of them is equal to Joint::kInvalidId. + void DisableEdge(Joint::Id from, Joint::Id to) { m_blockedEdges.insert(make_pair(from, to)); } + + /// \brief Connects joint |from| and |to| with a fake oneway feature. Geometry for the feature + /// points is taken from |geometrySource|. If |geometrySource| contains more than + /// two points the feature is created with intermediate (not joint) point(s). + /// \returns feature id which was added. + uint32_t AddFakeFeature(Joint::Id from, Joint::Id to, vector<RoadPoint> const & geometrySource); + + /// \brief Adds restriction to navigation graph which says that it's prohibited to go from + /// |from| to |to|. + /// \note |from| and |to| could be only begining or ending feature points and they has to belong to + /// the same junction with |jointId|. That means features |from| and |to| has to be adjacent. + /// \note This method could be called only after |m_roads| have been loaded with the help of Deserialize() + /// or Import(). + /// \note This method adds fake features with ids which follows immediately after real feature ids. + void ApplyRestrictionNo(RoadPoint const & from, RoadPoint const & to, Joint::Id jointId); + + /// \brief Adds restriction to navigation graph which says that from feature |from| it's permitted only + /// to go to feature |to| all other ways starting form |form| is prohibited. + /// \note All notes which are valid for ApplyRestrictionNo() is valid for ApplyRestrictionOnly(). + void ApplyRestrictionOnly(RoadPoint const & from, RoadPoint const & to, Joint::Id jointId); + + /// \brief Add restrictions in |restrictions| to |m_ftPointIndex|. + void ApplyRestrictions(RestrictionVec const & restrictions); + + /// \brief Fills |singleFeaturePath| with points from point |from| to point |to| + /// \note |from| and |to| has to belong the same feature. + /// \note The order on points in items of |connectionPaths| is from |from| to |to|. + void GetSingleFeaturePaths(RoadPoint from, RoadPoint to, vector<RoadPoint> & singleFeaturePath); + + /// \brief Fills |connectionPaths| with all path from joint |from| to joint |to|. + /// If |from| and |to| don't belong to the same feature |connectionPaths| an exception + /// |RootException| with be raised. + /// If |from| and |to| belong to only one feature |connectionPaths| will have one item. + /// It's most common case. + /// If |from| and |to| could be connected by several feature |connectionPaths| + /// will have several items. + /// \note The order on points in items of |connectionPaths| is from |from| to |to|. + void GetConnectionPaths(Joint::Id from, Joint::Id to, vector<vector<RoadPoint>> & connectionPaths); + + /// \brief Fills |shortestConnectionPath| with shortest path from joint |from| to joint |to|. + /// \note The order on points in |shortestConnectionPath| is from |from| to |to|. + /// \note In most cases the method doesn't load geometry to find the shortest path + /// because there's only one path between |from| and |to|. + /// \note if |shortestConnectionPath| could be filled after call of the method + /// an exception of type |RootException| is raised. It could happend, + /// for example, if |from| and |to| are connected with a pedestrian road + /// but a car route is looked for. + void GetShortestConnectionPath(Joint::Id from, Joint::Id to, + vector<RoadPoint> & shortestConnectionPath); + + void GetOutgoingGeomEdges(vector<JointEdge> const & outgoingEdges, Joint::Id center, + vector<JointEdgeGeom> & outgoingGeomEdges); + + void CreateFakeFeatureGeometry(vector<RoadPoint> const & geometrySource, RoadGeometry & geometry) const; + + /// \returns RoadGeometry by a real or fake featureId. + RoadGeometry const & GetRoad(uint32_t featureId) const; + + bool IsFakeFeature(uint32_t featureId) const { return featureId >= kStartFakeFeatureIds; } + + static uint32_t const kStartFakeFeatureIds = 1024 * 1024 * 1024; + private: void GetNeighboringEdge(RoadGeometry const & road, RoadPoint rp, bool forward, - vector<JointEdge> & edges) const; + bool outgoing, vector<JointEdge> & edges) const; + void Build(uint32_t jointNumber); + + double GetSpeed(RoadPoint ftp) const; + + /// \brief Finds neghboring of |centerId| joint on feature id of |center| + /// which is contained in |edges| and fills |oneStepAside| with it. + /// \note If oneStepAside is empty no neghboring nodes were found. + /// \note Taking into account the way of setting restrictions almost always |oneStepAside| + /// will contain one or zero items. Besides the it it's posible to draw map + /// the (wrong) way |oneStepAside| will contain any number of items. + void FindOneStepAsideRoadPoint(RoadPoint const & center, Joint::Id centerId, + vector<JointEdge> const & edges, vector<Joint::Id> & oneStepAside) const; + + bool ApplyRestrictionPrepareData(RoadPoint const & from, RoadPoint const & to, Joint::Id centerId, + vector<JointEdge> & ingoingEdges, vector<JointEdge> & outgoingEdges, + Joint::Id & fromFirstOneStepAside, Joint::Id & toFirstOneStepAside); Geometry m_geometry; shared_ptr<EdgeEstimator> m_estimator; RoadIndex m_roadIndex; JointIndex m_jointIndex; + + set<pair<Joint::Id, Joint::Id>> m_blockedEdges; + uint32_t m_nextFakeFeatureId = kStartFakeFeatureIds; + // Mapping from fake feature id to fake feature geometry. + map<uint32_t, RoadGeometry> m_fakeFeatureGeometry; }; } // namespace routing diff --git a/routing/index_graph_starter.cpp b/routing/index_graph_starter.cpp index e58e63eec8..cb14ad8954 100644 --- a/routing/index_graph_starter.cpp +++ b/routing/index_graph_starter.cpp @@ -16,10 +16,10 @@ IndexGraphStarter::IndexGraphStarter(IndexGraph const & graph, RoadPoint startPo m2::PointD const & IndexGraphStarter::GetPoint(Joint::Id jointId) const { if (jointId == m_start.m_fakeId) - return m_graph.GetGeometry().GetPoint(m_start.m_point); + return m_graph.GetPoint(m_start.m_point); if (jointId == m_finish.m_fakeId) - return m_graph.GetGeometry().GetPoint(m_finish.m_point); + return m_graph.GetPoint(m_finish.m_point); return m_graph.GetPoint(jointId); } @@ -136,7 +136,7 @@ void IndexGraphStarter::FindPointsWithCommonFeature(Joint::Id jointId0, Joint::I if (rp0.GetFeatureId() != rp1.GetFeatureId()) return; - RoadGeometry const & road = m_graph.GetGeometry().GetRoad(rp0.GetFeatureId()); + RoadGeometry const & road = m_graph.GetRoad(rp0.GetFeatureId()); if (!road.IsRoad()) return; @@ -149,7 +149,7 @@ void IndexGraphStarter::FindPointsWithCommonFeature(Joint::Id jointId0, Joint::I { // CalcEdgesWeight is very expensive. // So calculate it only if second common feature found. - RoadGeometry const & prevRoad = m_graph.GetGeometry().GetRoad(result0.GetFeatureId()); + RoadGeometry const & prevRoad = m_graph.GetRoad(result0.GetFeatureId()); minWeight = m_graph.GetEstimator().CalcEdgesWeight(prevRoad, result0.GetPointId(), result1.GetPointId()); } diff --git a/routing/joint_index.cpp b/routing/joint_index.cpp index b073848d97..8a0c64acd2 100644 --- a/routing/joint_index.cpp +++ b/routing/joint_index.cpp @@ -17,6 +17,23 @@ void JointIndex::AppendToJoint(Joint::Id jointId, RoadPoint rp) m_dynamicJoints[jointId].AddPoint(rp); } +void JointIndex::FindPointsWithCommonFeature(Joint::Id jointId0, Joint::Id jointId1, + vector<pair<RoadPoint, RoadPoint>> & result) const +{ + result.clear(); + ForEachPoint(jointId0, [&](RoadPoint const & rp0) + { + ForEachPoint(jointId1, [&](RoadPoint const & rp1) + { + if (rp0.GetFeatureId() == rp1.GetFeatureId()) + result.emplace_back(rp0, rp1); + }); + }); + + if (result.empty()) + MYTHROW(RootException, ("Can't find common feature for joints", jointId0, jointId1)); +} + void JointIndex::Build(RoadIndex const & roadIndex, uint32_t numJoints) { // + 1 is protection for 'End' method from out of bounds. diff --git a/routing/joint_index.hpp b/routing/joint_index.hpp index 0e3c8c0441..cef629ef37 100644 --- a/routing/joint_index.hpp +++ b/routing/joint_index.hpp @@ -5,7 +5,7 @@ #include "routing/road_point.hpp" #include "base/assert.hpp" - +#include "std/utility.hpp" #include "std/vector.hpp" namespace routing @@ -43,6 +43,10 @@ public: } void Build(RoadIndex const & roadIndex, uint32_t numJoints); + + /// \brief fills result with paths from |jointId0| to |jointId1|. + void FindPointsWithCommonFeature(Joint::Id jointId0, Joint::Id jointId1, + vector<pair<RoadPoint, RoadPoint>> & result) const; Joint::Id InsertJoint(RoadPoint const & rp); void AppendToJoint(Joint::Id jointId, RoadPoint rp); diff --git a/routing/road_index.cpp b/routing/road_index.cpp index e6668e8a0d..fbea8cba5f 100644 --- a/routing/road_index.cpp +++ b/routing/road_index.cpp @@ -18,6 +18,59 @@ void RoadIndex::Import(vector<Joint> const & joints) } } +bool RoadIndex::GetAdjacentFtPoints(uint32_t featureIdFrom, uint32_t featureIdTo, RoadPoint & from, + RoadPoint & to, Joint::Id & jointId) const +{ + auto const fromIt = m_roads.find(featureIdFrom); + if (fromIt == m_roads.cend()) + return false; + + auto const toIt = m_roads.find(featureIdTo); + if (toIt == m_roads.cend()) + return false; + + RoadJointIds const & roadJointIdsFrom = fromIt->second; + RoadJointIds const & roadJointIdsTo = toIt->second; + if (roadJointIdsFrom.GetSize() == 0 || roadJointIdsTo.GetSize() == 0) + return false; // No sence in restrictions on features without joints. + + // Note. It's important to check other variant besides a restriction from last segment + // of featureIdFrom to first segment of featureIdTo since two way features can have + // reverse point order. + if (roadJointIdsFrom.Back() == roadJointIdsTo.Front()) + { + from = RoadPoint(featureIdFrom, roadJointIdsFrom.GetSize() - 1); + to = RoadPoint(featureIdTo, 0); + jointId = roadJointIdsFrom.Back(); + return true; + } + + if (roadJointIdsFrom.Front() == roadJointIdsTo.Back()) + { + from = RoadPoint(featureIdFrom, 0); + to = RoadPoint(featureIdTo, roadJointIdsTo.GetSize() - 1); + jointId = roadJointIdsFrom.Front(); + return true; + } + + if (roadJointIdsFrom.Back() == roadJointIdsTo.Back()) + { + from = RoadPoint(featureIdFrom, roadJointIdsFrom.GetSize() - 1); + to = RoadPoint(featureIdTo, roadJointIdsTo.GetSize() - 1); + jointId = roadJointIdsFrom.Back(); + return true; + } + + if (roadJointIdsFrom.Front() == roadJointIdsTo.Front()) + { + from = RoadPoint(featureIdFrom, 0); + to = RoadPoint(featureIdTo, 0); + jointId = roadJointIdsFrom.Front(); + return true; + } + return false; // |featureIdFrom| and |featureIdTo| are not adjacent. +} + pair<Joint::Id, uint32_t> RoadIndex::FindNeighbor(RoadPoint rp, bool forward) const { auto const it = m_roads.find(rp.GetFeatureId()); diff --git a/routing/road_index.hpp b/routing/road_index.hpp index 90bd61e70a..fd077538c3 100644 --- a/routing/road_index.hpp +++ b/routing/road_index.hpp @@ -1,6 +1,7 @@ #pragma once #include "routing/joint.hpp" +#include "routing/routing_serialization.hpp" #include "coding/reader.hpp" #include "coding/write_to_sink.hpp" @@ -74,8 +75,14 @@ public: return make_pair(Joint::kInvalidId, 0); } - template <class Sink> - void Serialize(Sink & sink) const + Joint::Id Front() const { return m_jointIds.front(); } + + Joint::Id Back() const { return m_jointIds.back(); } + + size_t GetSize() const { return m_jointIds.size(); } + + template <class TSink> + void Serialize(TSink & sink) const { WriteToSink(sink, static_cast<Joint::Id>(m_jointIds.size())); for (Joint::Id jointId : m_jointIds) @@ -106,6 +113,12 @@ class RoadIndex final public: void Import(vector<Joint> const & joints); + /// \brief if |featureIdFrom| and |featureIdTo| are adjacent and if they are connected by + /// its ends fills |from| and |to| with corresponding |from| and |to| and return true. + /// If not returns false. + bool GetAdjacentFtPoints(uint32_t featureIdFrom, uint32_t featureIdTo, + RoadPoint & from, RoadPoint & to, Joint::Id & jointId) const; + void AddJoint(RoadPoint rp, Joint::Id jointId) { m_roads[rp.GetFeatureId()].AddJoint(rp.GetPointId(), jointId); @@ -158,6 +171,16 @@ public: f(it.first, it.second); } + template <typename F> + void ForEachJoint(uint32_t featureId, F && f) const + { + auto const it = m_roads.find(featureId); + if (it == m_roads.cend()) + return; + + it->second.ForEachJoint(f); + } + private: // Map from feature id to RoadJointIds. unordered_map<uint32_t, RoadJointIds> m_roads; diff --git a/routing/road_point.hpp b/routing/road_point.hpp index 654f89c675..da4303a80c 100644 --- a/routing/road_point.hpp +++ b/routing/road_point.hpp @@ -1,6 +1,7 @@ #pragma once #include "std/cstdint.hpp" +#include "std/limits.hpp" #include "std/sstream.hpp" #include "std/string.hpp" @@ -17,25 +18,31 @@ public: RoadPoint(uint32_t featureId, uint32_t pointId) : m_featureId(featureId), m_pointId(pointId) {} - uint32_t GetFeatureId() const { return m_featureId; } - - uint32_t GetPointId() const { return m_pointId; } + bool operator==(RoadPoint const & roadPoint) const + { + return m_featureId == roadPoint.m_featureId && m_pointId == roadPoint.m_pointId; + } - bool operator==(RoadPoint const & rp) const + bool operator<(RoadPoint const & roadPoint) const { - return m_featureId == rp.m_featureId && m_pointId == rp.m_pointId; + if (m_featureId == roadPoint.m_featureId) + return m_pointId < roadPoint.m_pointId; + return m_featureId < roadPoint.m_featureId; } + uint32_t GetFeatureId() const { return m_featureId; } + + uint32_t GetPointId() const { return m_pointId; } + private: uint32_t m_featureId; uint32_t m_pointId; }; -inline string DebugPrint(RoadPoint const & rp) +inline string DebugPrint(RoadPoint const & roadPoint) { ostringstream out; - out << "rp(" - << "(" << rp.GetFeatureId() << ", " << rp.GetPointId() << ")"; + out << "RoadPoint[" << roadPoint.GetFeatureId() << ", " << roadPoint.GetPointId() << "]"; return out.str(); } } // namespace routing diff --git a/routing/routing_serialization.cpp b/routing/routing_serialization.cpp index d1e5a3a366..107ae8cd96 100644 --- a/routing/routing_serialization.cpp +++ b/routing/routing_serialization.cpp @@ -44,8 +44,8 @@ string DebugPrint(Restriction::Type const & type) { return ToString(type); } string DebugPrint(Restriction const & restriction) { ostringstream out; - out << "m_featureIds:[" << ::DebugPrint(restriction.m_featureIds) - << "] m_type:" << DebugPrint(restriction.m_type) << " "; + out << "Restriction [ m_featureIds: " << ::DebugPrint(restriction.m_featureIds) + << " m_type: " << DebugPrint(restriction.m_type) << " ]" << endl; return out.str(); } } // namespace routing diff --git a/routing/routing_tests/index_graph_test.cpp b/routing/routing_tests/index_graph_test.cpp index 3458b7cd8e..da2ff816cf 100644 --- a/routing/routing_tests/index_graph_test.cpp +++ b/routing/routing_tests/index_graph_test.cpp @@ -1,5 +1,7 @@ #include "testing/testing.hpp" +#include "routing/routing_tests/index_graph_tools.hpp" + #include "routing/base/astar_algorithm.hpp" #include "routing/car_model.hpp" #include "routing/edge_estimator.hpp" @@ -18,75 +20,20 @@ namespace { using namespace routing; - -class TestGeometryLoader final : public GeometryLoader -{ -public: - // GeometryLoader overrides: - void Load(uint32_t featureId, RoadGeometry & road) const override; - - void AddRoad(uint32_t featureId, bool oneWay, RoadGeometry::Points const & points); - -private: - unordered_map<uint32_t, RoadGeometry> m_roads; -}; - -void TestGeometryLoader::Load(uint32_t featureId, RoadGeometry & road) const -{ - auto it = m_roads.find(featureId); - if (it == m_roads.cend()) - return; - - road = it->second; -} - -void TestGeometryLoader::AddRoad(uint32_t featureId, bool oneWay, - RoadGeometry::Points const & points) -{ - auto it = m_roads.find(featureId); - if (it != m_roads.end()) - { - ASSERT(false, ("Already contains feature", featureId)); - return; - } - - m_roads[featureId] = RoadGeometry(oneWay, 1.0 /* speed */, points); -} - -Joint MakeJoint(vector<RoadPoint> const & points) -{ - Joint joint; - for (auto const & point : points) - joint.AddPoint(point); - - return joint; -} - -shared_ptr<EdgeEstimator> CreateEstimator() -{ - return EdgeEstimator::CreateForCar(*make_shared<CarModelFactory>()->GetVehicleModel()); -} +using namespace routing_test; void TestRoute(IndexGraph const & graph, RoadPoint const & start, RoadPoint const & finish, size_t expectedLength, vector<RoadPoint> const * expectedRoute = nullptr) { - LOG(LINFO, ("Test route", start.GetFeatureId(), ",", start.GetPointId(), "=>", - finish.GetFeatureId(), ",", finish.GetPointId())); - - AStarAlgorithm<IndexGraphStarter> algorithm; - RoutingResult<Joint::Id> routingResult; - + vector<RoadPoint> route; IndexGraphStarter starter(graph, start, finish); - auto const resultCode = algorithm.FindPath(starter, starter.GetStartJoint(), - starter.GetFinishJoint(), routingResult, {}, {}); - TEST_EQUAL(resultCode, AStarAlgorithm<IndexGraphStarter>::Result::OK, ()); + AStarAlgorithm<IndexGraphStarter>::Result const resultCode = CalculateRoute(starter, route); - vector<RoadPoint> roadPoints; - starter.RedressRoute(routingResult.path, roadPoints); - TEST_EQUAL(roadPoints.size(), expectedLength, ()); + TEST_EQUAL(resultCode, AStarAlgorithm<IndexGraphStarter>::Result::OK, ()); + TEST_EQUAL(route.size(), expectedLength, ()); if (expectedRoute != nullptr) - TEST_EQUAL(roadPoints, *expectedRoute, ()); + TEST_EQUAL(route, *expectedRoute, ()); } void TestEdges(IndexGraph const & graph, Joint::Id jointId, @@ -244,6 +191,7 @@ UNIT_TEST(FindPathManhattan) street.emplace_back(static_cast<double>(i), static_cast<double>(j)); avenue.emplace_back(static_cast<double>(j), static_cast<double>(i)); } + loader->AddRoad(i, false, street); loader->AddRoad(i + kCitySize, false, avenue); } @@ -256,6 +204,7 @@ UNIT_TEST(FindPathManhattan) for (uint32_t j = 0; j < kCitySize; ++j) joints.emplace_back(MakeJoint({{i, j}, {j + kCitySize, i}})); } + graph.Import(joints); for (uint32_t startY = 0; startY < kCitySize; ++startY) diff --git a/routing/routing_tests/index_graph_tools.cpp b/routing/routing_tests/index_graph_tools.cpp new file mode 100644 index 0000000000..eba158a256 --- /dev/null +++ b/routing/routing_tests/index_graph_tools.cpp @@ -0,0 +1,84 @@ +#include "routing/routing_tests/index_graph_tools.hpp" + +#include "testing/testing.hpp" + +#include "routing/car_model.hpp" + +namespace routing_test +{ +using namespace routing; + +void TestGeometryLoader::Load(uint32_t featureId, RoadGeometry & road) const +{ + auto it = m_roads.find(featureId); + if (it == m_roads.cend()) + return; + + road = it->second; +} + +void TestGeometryLoader::AddRoad(uint32_t featureId, bool oneWay, + RoadGeometry::Points const & points) +{ + auto it = m_roads.find(featureId); + if (it != m_roads.end()) + { + ASSERT(false, ("Already contains feature", featureId)); + return; + } + + m_roads[featureId] = RoadGeometry(oneWay, 1.0 /* speed */, points); +} + +Joint MakeJoint(vector<RoadPoint> const & points) +{ + Joint joint; + for (auto const & point : points) + joint.AddPoint(point); + + return joint; +} + +shared_ptr<EdgeEstimator> CreateEstimator() +{ + return EdgeEstimator::CreateForCar(*make_shared<CarModelFactory>()->GetVehicleModel()); +} + +AStarAlgorithm<IndexGraphStarter>::Result CalculateRoute(IndexGraphStarter const & starter, + vector<RoadPoint> & roadPoints) +{ + AStarAlgorithm<IndexGraphStarter> algorithm; + RoutingResult<Joint::Id> routingResult; + AStarAlgorithm<IndexGraphStarter>::Result const resultCode = algorithm.FindPath( + starter, starter.GetStartJoint(), starter.GetFinishJoint(), routingResult, {}, {}); + + starter.RedressRoute(routingResult.path, roadPoints); + return resultCode; +} + +void TestRouteSegments(IndexGraphStarter const & starter, + AStarAlgorithm<IndexGraphStarter>::Result expectedRouteResult, + vector<RoadPoint> const & expectedRoute) +{ + vector<RoadPoint> route; + AStarAlgorithm<IndexGraphStarter>::Result const resultCode = CalculateRoute(starter, route); + TEST_EQUAL(resultCode, expectedRouteResult, ()); + TEST_EQUAL(route, expectedRoute, ()); +} + +void TestRouteGeometry(IndexGraphStarter const & starter, + AStarAlgorithm<IndexGraphStarter>::Result expectedRouteResult, + vector<m2::PointD> const & expectedRouteGeom) +{ + vector<RoadPoint> route; + AStarAlgorithm<IndexGraphStarter>::Result const resultCode = CalculateRoute(starter, route); + TEST_EQUAL(resultCode, expectedRouteResult, ()); + TEST_EQUAL(route.size(), expectedRouteGeom.size(), ()); + for (size_t i = 0; i < route.size(); ++i) + { + RoadGeometry roadGeom = starter.GetGraph().GetRoad(route[i].GetFeatureId()); + CHECK_LESS(route[i].GetPointId(), roadGeom.GetPointsCount(), ()); + TEST_EQUAL(expectedRouteGeom[i], roadGeom.GetPoint(route[i].GetPointId()), ()); + } +} +} // namespace routing_test diff --git a/routing/routing_tests/index_graph_tools.hpp b/routing/routing_tests/index_graph_tools.hpp new file mode 100644 index 0000000000..7a5482430f --- /dev/null +++ b/routing/routing_tests/index_graph_tools.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include "routing/edge_estimator.hpp" +#include "routing/index_graph.hpp" +#include "routing/index_graph_starter.hpp" +#include "routing/road_point.hpp" + +#include "routing/base/astar_algorithm.hpp" + +#include "geometry/point2d.hpp" + +#include "std/algorithm.hpp" +#include "std/shared_ptr.hpp" +#include "std/unique_ptr.hpp" +#include "std/unordered_map.hpp" +#include "std/vector.hpp" + +namespace routing_test +{ +class TestGeometryLoader final : public routing::GeometryLoader +{ +public: + // GeometryLoader overrides: + void Load(uint32_t featureId, routing::RoadGeometry & road) const override; + + void AddRoad(uint32_t featureId, bool oneWay, routing::RoadGeometry::Points const & points); + +private: + unordered_map<uint32_t, routing::RoadGeometry> m_roads; +}; + +routing::Joint MakeJoint(vector<routing::RoadPoint> const & points); + +shared_ptr<routing::EdgeEstimator> CreateEstimator(); + +routing::AStarAlgorithm<routing::IndexGraphStarter>::Result CalculateRoute( + routing::IndexGraphStarter const & graph, vector<routing::RoadPoint> & roadPoints); + +void TestRouteSegments(routing::IndexGraphStarter const & starter, + routing::AStarAlgorithm<routing::IndexGraphStarter>::Result expectedRouteResult, + vector<routing::RoadPoint> const & expectedRoute); + +void TestRouteGeometry(routing::IndexGraphStarter const & starter, + routing::AStarAlgorithm<routing::IndexGraphStarter>::Result expectedRouteResult, + vector<m2::PointD> const & expectedRouteGeom); +} // 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..895481763e --- /dev/null +++ b/routing/routing_tests/restriction_test.cpp @@ -0,0 +1,622 @@ +#include "testing/testing.hpp" + +#include "routing/routing_tests/index_graph_tools.hpp" + +#include "routing/car_model.hpp" + +#include "routing/geometry.hpp" +#include "geometry/point2d.hpp" + +#include "std/unique_ptr.hpp" +#include "std/vector.hpp" + +namespace +{ +using namespace routing; +using namespace routing_test; + +void TestRoutes(vector<RoadPoint> const & starts, + vector<RoadPoint> const & finishes, + vector<vector<RoadPoint>> const & expectedRoutes, + IndexGraph & graph) +{ + CHECK_EQUAL(starts.size(), expectedRoutes.size(), ()); + CHECK_EQUAL(finishes.size(), expectedRoutes.size(), ()); + + for (size_t i = 0; i < expectedRoutes.size(); ++i) + { + IndexGraphStarter starter(graph, starts[i], finishes[i]); + TestRouteSegments(starter, AStarAlgorithm<IndexGraphStarter>::Result::OK, expectedRoutes[i]); + } +} + +void EdgeTest(Joint::Id vertex, size_t expectedIntgoingNum, size_t expectedOutgoingNum, + IndexGraph & graph) +{ + vector<IndexGraphStarter::TEdgeType> ingoing; + graph.GetEdgesList(vertex, false /* is outgoing */, ingoing); + TEST_EQUAL(ingoing.size(), expectedIntgoingNum, ()); + + vector<IndexGraphStarter::TEdgeType> outgoing; + graph.GetEdgesList(vertex, true /* is outgoing */, outgoing); + TEST_EQUAL(outgoing.size(), expectedOutgoingNum, ()); +} + +// Finish +// 2 * +// ^ ↖ +// | F1 +// | ↖ +// 1 | * +// F0 ↖ +// | F2 +// | ↖ +// 0 *<--F3---<--F3---* Start +// 0 1 2 +// Note. F0, F1 and F2 are one segment features. F3 is a two segments feature. +unique_ptr<IndexGraph> BuildTriangularGraph() +{ + auto const loader = []() + { + unique_ptr<TestGeometryLoader> loader = make_unique<TestGeometryLoader>(); + loader->AddRoad(0 /* featureId */, true /* oneWay */, buffer_vector<m2::PointD, 32>( + {{0.0, 0.0}, {0.0, 2.0}})); + loader->AddRoad(1 /* featureId */, true /* oneWay */, buffer_vector<m2::PointD, 32>( + {{1.0, 1.0}, {0.0, 2.0}})); + loader->AddRoad(2 /* featureId */, true /* oneWay */, buffer_vector<m2::PointD, 32>( + {{0.0, 2.0}, {1.0, 1.0}})); + loader->AddRoad(3 /* featureId */, true /* oneWay */, buffer_vector<m2::PointD, 32>( + {{2.0, 0.0}, {1.0, 0.0}, {0.0, 0.0}})); + return loader; + }; + + vector<Joint> const joints = {MakeJoint({{2, 0}, {3, 0}}), /* 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}}) /* joint at point (0, 2) */ + }; + + unique_ptr<IndexGraph> graph = make_unique<IndexGraph>(loader(), CreateEstimator()); + graph->Import(joints); + return graph; +} + +// Route through triangular graph without any restrictions. +UNIT_TEST(TriangularGraph) +{ + unique_ptr<IndexGraph> graph = BuildTriangularGraph(); + IndexGraphStarter starter(*graph, RoadPoint(2, 0) /* start */, RoadPoint(1, 1) /* finish */); + vector<RoadPoint> const expectedRoute = {{2 /* feature id */, 0 /* seg id */}, + {2, 1}, {1, 1}}; + TestRouteSegments(starter, AStarAlgorithm<IndexGraphStarter>::Result::OK, expectedRoute); +} + +// Route through triangular graph with feature 2 disabled. +UNIT_TEST(TriangularGraph_DisableF2) +{ + unique_ptr<IndexGraph> graph = BuildTriangularGraph(); + graph->DisableEdge(graph->GetJointIdForTesting({2 /* feature id */, 0 /* point id */}), + graph->GetJointIdForTesting({2, 1})); + + IndexGraphStarter starter(*graph, RoadPoint(2, 0) /* start */, RoadPoint(1, 1) /* finish */); + vector<RoadPoint> const expectedRouteOneEdgeRemoved = {{3 /* feature id */, 0 /* seg id */}, + {3, 1}, {3, 2}, {0, 1}}; + TestRouteSegments(starter, AStarAlgorithm<IndexGraphStarter>::Result::OK, + expectedRouteOneEdgeRemoved); +} + +// Route through triangular graph with restriction type no from feature 2 to feature 1. +UNIT_TEST(TriangularGraph_RestrictionNoF2F1) +{ + unique_ptr<IndexGraph> graph = BuildTriangularGraph(); + graph->ApplyRestrictionNo({2 /* feature id */, 1 /* seg id */}, {1, 0}, + graph->GetJointIdForTesting({1, 0})); + + IndexGraphStarter starter(*graph, RoadPoint(2, 0) /* start */, RoadPoint(1, 1) /* finish */); + vector<RoadPoint> const expectedRouteRestrictionF2F1No = {{3 /* feature id */, 0 /* seg id */}, + {3, 1}, {3, 2}, {0, 1}}; + TestRouteSegments(starter, AStarAlgorithm<IndexGraphStarter>::Result::OK, + expectedRouteRestrictionF2F1No); +} + +// Finish +// 2 * +// ^ ↖ +// | ↖ +// | ↖ +// 1 | Fake adding one link feature +// F0 ↖ +// | ↖ +// | ↖ +// 0 *<--F1---<--F1--* Start +// 0 1 2 +// Note. F1 is a two setments feature. The others are one setment ones. +unique_ptr<IndexGraph> BuildCornerGraph() +{ + auto const loader = []() + { + unique_ptr<TestGeometryLoader> loader = make_unique<TestGeometryLoader>(); + loader->AddRoad(0 /* feature id */, true /* oneWay */, buffer_vector<m2::PointD, 32>( + {{0.0, 0.0}, {0.0, 2.0}})); + loader->AddRoad(1 /* feature id */, true /* oneWay */, buffer_vector<m2::PointD, 32>( + {{2.0, 0.0}, {1.0, 0.0}, {0.0, 0.0}})); + return loader; + }; + + vector<Joint> const joints = + {MakeJoint({{1 /* feature id */, 2 /* point id */}, {0, 0}}), /* joint at point (0, 0) */}; + + unique_ptr<IndexGraph> graph = make_unique<IndexGraph>(loader(), CreateEstimator()); + graph->Import(joints); + graph->InsertJoint({1 /* feature id */, 0 /* point id */}); // Start joint. + graph->InsertJoint({0 /* feature id */, 1 /* point id */}); // Finish joint. + return graph; +} + +// Route through corner graph without any restrictions. +UNIT_TEST(CornerGraph) +{ + unique_ptr<IndexGraph> graph = BuildCornerGraph(); + + // Route along F1 and F0. + IndexGraphStarter starter(*graph, RoadPoint(1, 0) /* start */, RoadPoint(0, 1) /* finish */); + vector<RoadPoint> const expectedRoute = {{1 /* feature id */, 0 /* point id */}, + {1, 1}, {1, 2}, {0, 1}}; + TestRouteSegments(starter, AStarAlgorithm<IndexGraphStarter>::Result::OK, expectedRoute); +} + +// Generate geometry based on feature 1 of corner graph. +UNIT_TEST(CornerGraph_CreateFakeFeature1Geometry) +{ + unique_ptr<IndexGraph> graph = BuildCornerGraph(); + + vector<vector<RoadPoint>> oneEdgeConnectionPaths; + graph->GetConnectionPaths(graph->GetJointIdForTesting({1 /* feature id */, 0 /* point id */}), + graph->GetJointIdForTesting({1, 2}), oneEdgeConnectionPaths); + TEST_EQUAL(oneEdgeConnectionPaths.size(), 1, ()); + + vector<RoadPoint> const expectedDirectOrder = {{1, 0}, {1, 1}, {1, 2}}; + TEST_EQUAL(oneEdgeConnectionPaths[0], expectedDirectOrder, ()); + RoadGeometry geometryDirect; + graph->CreateFakeFeatureGeometry(oneEdgeConnectionPaths[0], geometryDirect); + RoadGeometry expectedGeomentryDirect(true /* one way */, 1.0 /* speed */, + buffer_vector<m2::PointD, 32>({{2.0, 0.0}, {1.0, 0.0}, + {0.0, 0.0}})); + TEST_EQUAL(geometryDirect, expectedGeomentryDirect, ()); +} + +// Generate geometry based on reversed feature 1 of corner graph. +UNIT_TEST(CornerGraph_CreateFakeReversedFeature1Geometry) +{ + unique_ptr<IndexGraph> graph = BuildCornerGraph(); + + vector<vector<RoadPoint>> oneEdgeConnectionPaths; + graph->GetConnectionPaths(graph->GetJointIdForTesting({1 /* feature id */, 2 /* point id */}), + graph->GetJointIdForTesting({1, 0}), oneEdgeConnectionPaths); + TEST_EQUAL(oneEdgeConnectionPaths.size(), 1, ()); + + vector<RoadPoint> const expectedBackOrder = {{1, 2}, {1, 1}, {1, 0}}; + TEST_EQUAL(oneEdgeConnectionPaths[0], expectedBackOrder, ()); + RoadGeometry geometryBack; + graph->CreateFakeFeatureGeometry(oneEdgeConnectionPaths[0], geometryBack); + RoadGeometry expectedGeomentryBack(true /* one way */, 1.0 /* speed */, + buffer_vector<m2::PointD, 32>({{0.0, 0.0}, {1.0, 0.0}, + {2.0, 0.0}})); + TEST_EQUAL(geometryBack, expectedGeomentryBack, ()); +} + +// Route through corner graph with adding a fake edge. +UNIT_TEST(CornerGraph_AddFakeFeature) +{ + RoadPoint const kStart(1, 0); + RoadPoint const kFinish(0, 1); + unique_ptr<IndexGraph> graph = BuildCornerGraph(); + + graph->AddFakeFeature(graph->GetJointIdForTesting(kStart), graph->GetJointIdForTesting(kFinish), + {kStart, kFinish} /* geometrySource */); + + IndexGraphStarter starter(*graph, kStart, kFinish); + vector<RoadPoint> const expectedRouteByFakeFeature = {{IndexGraph::kStartFakeFeatureIds, 0 /* seg id */}, + {IndexGraph::kStartFakeFeatureIds, 1 /* seg id */}}; + TestRouteSegments(starter, AStarAlgorithm<IndexGraphStarter>::Result::OK, expectedRouteByFakeFeature); +} + +// Finish 2 Finish 1 Finish 0 +// 2 *<---F5----*<---F6---* +// ^ ↖ ^ ↖ ^ +// | Fake-1 | Fake-0 | +// | ↖ F1 ↖ F2 +// | ↖ | ↖ | +// 1 F0 * * +// | ^ ↖ ^ +// | F1 Fake-2 F2 +// | | ↖ | +// 0 *<----F4---*<---F3----* Start +// 0 1 2 +// Note. F1 and F2 are two segments features. The others are one segment ones. +unique_ptr<IndexGraph> BuildTwoSquaresGraph() +{ + auto const loader = []() + { + unique_ptr<TestGeometryLoader> loader = make_unique<TestGeometryLoader>(); + loader->AddRoad(0 /* feature id */, true /* oneWay */, buffer_vector<m2::PointD, 32>( + {{0.0, 0.0}, {0.0, 2.0}})); + loader->AddRoad(1 /* feature id */, true /* oneWay */, buffer_vector<m2::PointD, 32>( + {{1.0, 0.0}, {1.0, 1.0}, {1.0, 2.0}})); + loader->AddRoad(2 /* feature id */, true /* oneWay */, buffer_vector<m2::PointD, 32>( + {{2.0, 0.0}, {2.0, 1.0}, {2.0, 2.0}})); + loader->AddRoad(3 /* feature id */, true /* oneWay */, buffer_vector<m2::PointD, 32>( + {{2.0, 0.0}, {1.0, 0.0}})); + loader->AddRoad(4 /* feature id */, true /* oneWay */, buffer_vector<m2::PointD, 32>( + {{1.0, 0.0}, {0.0, 0.0}})); + loader->AddRoad(5 /* feature id */, true /* oneWay */, buffer_vector<m2::PointD, 32>( + {{1.0, 2.0}, {0.0, 2.0}})); + loader->AddRoad(6 /* feature id */, true /* oneWay */, buffer_vector<m2::PointD, 32>( + {{2.0, 2.0}, {1.0, 2.0}})); + return loader; + }; + + vector<Joint> const joints = { + MakeJoint({{4 /* featureId */, 1 /* pointId */}, {0, 0}}), /* joint at point (0, 0) */ + MakeJoint({{0, 1}, {5, 1}}), /* joint at point (0, 2) */ + MakeJoint({{4, 0}, {1, 0}, {3, 1}}), /* joint at point (1, 0) */ + MakeJoint({{5, 0}, {1, 2}, {6, 1}}), /* joint at point (1, 2) */ + MakeJoint({{3, 0}, {2, 0}}), /* joint at point (2, 0) */ + MakeJoint({{2, 2}, {6, 0}}), /* joint at point (2, 2) */ + }; + + unique_ptr<IndexGraph> graph = make_unique<IndexGraph>(loader(), CreateEstimator()); + graph->Import(joints); + return graph; +} + +// Route through two squares graph without any restrictions. +UNIT_TEST(TwoSquaresGraph) +{ + unique_ptr<IndexGraph> graph = BuildTwoSquaresGraph(); + + vector<RoadPoint> const starts = {{2 /* feature id */, 0 /* point id */}, {2, 0}, {2, 0}}; + vector<RoadPoint> const finishes = {{6 /* feature id */, 0 /* point id */}, {6, 1}, {5, 1}}; + vector<RoadPoint> const expectedRoute0 = {{2 /* feature id */, 0 /* point id */}, + {2, 1}, {2, 2}}; + vector<RoadPoint> const expectedRoute1 = {{2 /* feature id */, 0 /* point id */}, + {2, 1}, {2, 2}, {6, 1}}; + vector<RoadPoint> const expectedRoute2 = {{2 /* feature id */, 0 /* point id */}, + {2, 1}, {2, 2}, {6, 1}, {5, 1}}; + + TestRoutes(starts, finishes, {expectedRoute0, expectedRoute1, expectedRoute2}, *graph); +} + +// Route through two squares graph with adding a Fake-0 edge. +UNIT_TEST(TwoSquaresGraph_AddFakeFeatureZero) +{ + unique_ptr<IndexGraph> graph = BuildTwoSquaresGraph(); + graph->AddFakeFeature(graph->InsertJoint({2 /* feature id */, 1 /* point id */}), + graph->GetJointIdForTesting({6 /* feature id */, 1 /* point id */}), + {{2, 1}, {6, 1}} /* geometrySource */); + + vector<RoadPoint> const starts = {{2 /* feature id */, 0 /* point id */}, {2, 0}, {2, 0}}; + vector<RoadPoint> const finishes = {{6 /* feature id */, 0 /* point id */}, {6, 1}, {5, 1}}; + vector<RoadPoint> const expectedRoute0 = {{2 /* feature id */, 0 /* point id */}, + {2, 1}, {2, 2}}; + vector<RoadPoint> const expectedRoute1 = {{2 /* feature id */, 0 /* point id */}, + {2, 1}, {IndexGraph::kStartFakeFeatureIds, 1}}; + vector<RoadPoint> const expectedRoute2 = {{2 /* feature id */, 0 /* point id */}, + {2, 1}, {IndexGraph::kStartFakeFeatureIds, 1}, {5, 1}}; + + TestRoutes(starts, finishes, {expectedRoute0, expectedRoute1, expectedRoute2}, *graph); +} + +// Route through two squares graph with adding a Fake-0, Fake-1 and Fake-2 edge. +UNIT_TEST(TwoSquaresGraph_AddFakeFeatureZeroOneTwo) +{ + unique_ptr<IndexGraph> graph = BuildTwoSquaresGraph(); + // Adding features: Fake 0, Fake 1 and Fake 2. + graph->AddFakeFeature(graph->InsertJoint({2 /* feature id */, 1 /* point id */}), + graph->GetJointIdForTesting({6 /* feature id */, 1 /* point id */}), + {{2, 1}, {6, 1}} /* geometrySource */); + graph->AddFakeFeature(graph->InsertJoint({1 /* feature id */, 1 /* point id */}), + graph->GetJointIdForTesting({5 /* feature id */, 1 /* point id */}), + {{1, 1}, {5, 1}} /* geometrySource */); + graph->AddFakeFeature(graph->GetJointIdForTesting({2 /* feature id */, 0 /* point id */}), + graph->GetJointIdForTesting({1 /* feature id */, 1 /* point id */}), + {{2, 0}, {1, 1}} /* geometrySource */); + + vector<RoadPoint> const starts = {{2 /* feature id */, 0 /* point id */}, {2, 0}, {2, 0}}; + vector<RoadPoint> const finishes = {{6 /* feature id */, 0 /* point id */}, {6, 1}, {5, 1}}; + vector<RoadPoint> const expectedRoute0 = {{2 /* feature id */, 0 /* point id */}, + {2, 1}, {2, 2}}; + vector<RoadPoint> const expectedRoute1 = {{2 /* feature id */, 0 /* point id */}, + {2, 1}, {IndexGraph::kStartFakeFeatureIds, 1}}; + vector<RoadPoint> const expectedRoute2 = {{IndexGraph::kStartFakeFeatureIds + 2 /* Fake 2 */, 0}, + {IndexGraph::kStartFakeFeatureIds + 2 /* Fake 2 */, 1}, + {IndexGraph::kStartFakeFeatureIds + 1 /* Fake 1 */, 1}}; + TestRoutes(starts, finishes, {expectedRoute0, expectedRoute1, expectedRoute2}, *graph); + + // Disabling Fake-2 feature. + graph->DisableEdge(graph->GetJointIdForTesting({IndexGraph::kStartFakeFeatureIds + 2 /* Fake 2 */, 0}), + graph->GetJointIdForTesting({IndexGraph::kStartFakeFeatureIds + 2 /* Fake 2 */, 1})); + vector<RoadPoint> const expectedRoute2Disable2 = {{2 /* feature id */, 0 /* point id */}, + {2, 1}, {IndexGraph::kStartFakeFeatureIds, 1}, {5, 1}}; + TestRoutes(starts, finishes, {expectedRoute0, expectedRoute1, expectedRoute2Disable2}, *graph); +} + +// 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<IndexGraph> BuildFlagGraph() +{ + auto const loader = []() + { + unique_ptr<TestGeometryLoader> loader = make_unique<TestGeometryLoader>(); + loader->AddRoad(0 /* feature id */, false /* one way */, buffer_vector<m2::PointD, 32>( + {{2.0, 0.0}, {1.0, 0.0}})); + loader->AddRoad(1 /* feature id */, false /* one way */, buffer_vector<m2::PointD, 32>( + {{1.0, 0.0}, {0.0, 0.0}})); + loader->AddRoad(2 /* feature id */, false /* one way */, buffer_vector<m2::PointD, 32>( + {{0.0, 0.0}, {0.0, 1.0}})); + loader->AddRoad(3 /* feature id */, false /* one way */, buffer_vector<m2::PointD, 32>( + {{1.0, 0.0}, {1.0, 1.0}})); + loader->AddRoad(4 /* feature id */, false /* one way */, buffer_vector<m2::PointD, 32>( + {{0.0, 1.0}, {0.5, 1.0}})); + loader->AddRoad(5 /* feature id */, false /* one way */, buffer_vector<m2::PointD, 32>( + {{0.5, 1.0}, {1.0, 1.0}})); + return loader; + }; + + vector<Joint> 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}}), /* joint at point (1, 1) */ + }; + + unique_ptr<IndexGraph> graph = make_unique<IndexGraph>(loader(), CreateEstimator()); + graph->Import(joints); + // Note. It's necessary to insert start joint because the edge F0 is used + // for creating restrictions. + graph->InsertJoint({0 /* feature id */, 0 /* point id */}); // start + return graph; +} + +// @TODO(bykoianko) It's necessary to implement put several no restriction on the same junction +// ingoing edges of the same jucntion. For example test with flag graph with two restriction +// type no from F0 to F3 and form F0 to F1. + +// Route through flag graph without any restrictions. +UNIT_TEST(FlagGraph) +{ + unique_ptr<IndexGraph> graph = BuildFlagGraph(); + IndexGraphStarter starter(*graph, RoadPoint(0, 0) /* start */, RoadPoint(5, 0) /* finish */); + vector<m2::PointD> const expectedGeom = {{2 /* x */, 0 /* y */}, + {1, 0}, {1, 1}, {0.5, 1}}; + TestRouteGeometry(starter, AStarAlgorithm<IndexGraphStarter>::Result::OK, expectedGeom); +} + +// Route through flag graph with one restriciton (type no) from F0 to F3. +UNIT_TEST(FlagGraph_RestrictionF0F3No) +{ + unique_ptr<IndexGraph> graph = BuildFlagGraph(); + Joint::Id const restictionCenterId = graph->GetJointIdForTesting({0, 1}); + + // Testing outgoing and ingoing edge number near restriction joint. + EdgeTest(restictionCenterId, 3 /* expectedIntgoingNum */, 3 /* expectedOutgoingNum */, *graph); + graph->ApplyRestrictionNo({0 /* feature id */, 1 /* point id */}, {3 /* feature id */, 0 /* point id */}, + restictionCenterId); + EdgeTest(restictionCenterId, 2 /* expectedIntgoingNum */, 3 /* expectedOutgoingNum */, *graph); + + // Testing route building after adding the restriction. + IndexGraphStarter starter(*graph, RoadPoint(0, 0) /* start */, RoadPoint(5, 0) /* finish */); + vector<m2::PointD> const expectedGeom = {{2 /* x */, 0 /* y */}, + {1, 0}, {0, 0}, {0, 1}, {0.5, 1}}; + TestRouteGeometry(starter, AStarAlgorithm<IndexGraphStarter>::Result::OK, expectedGeom); +} + +// Route through flag graph with one restriciton (type only) from F0 to F1. +UNIT_TEST(FlagGraph_RestrictionF0F1Only) +{ + unique_ptr<IndexGraph> graph = BuildFlagGraph(); + Joint::Id const restictionCenterId = graph->GetJointIdForTesting({0, 1}); + graph->ApplyRestrictionOnly({0 /* feature id */, 1 /* point id */}, {1 /* feature id */, 0 /* point id */}, + restictionCenterId); + + IndexGraphStarter starter(*graph, RoadPoint(0, 0) /* start */, RoadPoint(5, 0) /* finish */); + vector<RoadPoint> const expectedRoute = {{IndexGraph::kStartFakeFeatureIds, 0 /* point id */}, + {IndexGraph::kStartFakeFeatureIds, 1}, + {IndexGraph::kStartFakeFeatureIds, 2}, + {2 /* feature id */, 1 /* point id */}, + {4, 1}}; + TestRouteSegments(starter, AStarAlgorithm<IndexGraphStarter>::Result::OK, expectedRoute); +} + +// 1 *-F4-*-F5-*---F6---* Finish +// | | +// 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<IndexGraph> BuildPosterGraph() +{ + auto loader = []() + { + unique_ptr<TestGeometryLoader> loader = make_unique<TestGeometryLoader>(); + loader->AddRoad(0 /* feature id */, false /* one way */, buffer_vector<m2::PointD, 32>( + {{2.0, 0.0}, {1.0, 0.0}})); + loader->AddRoad(1 /* feature id */, false /* one way */, buffer_vector<m2::PointD, 32>( + {{1.0, 0.0}, {0.0, 0.0}})); + loader->AddRoad(2 /* feature id */, false /* one way */, buffer_vector<m2::PointD, 32>( + {{0.0, 0.0}, {0.0, 1.0}})); + loader->AddRoad(3 /* feature id */, false /* one way */, buffer_vector<m2::PointD, 32>( + {{1.0, 0.0}, {1.0, 1.0}})); + loader->AddRoad(4 /* feature id */, false /* one way */, buffer_vector<m2::PointD, 32>( + {{0.0, 1.0}, {0.5, 1.0}})); + loader->AddRoad(5 /* feature id */, false /* one way */, buffer_vector<m2::PointD, 32>( + {{0.5, 1.0}, {1.0, 1.0}})); + loader->AddRoad(6 /* feature id */, false /* one way */, buffer_vector<m2::PointD, 32>( + {{1.0, 1.0}, {2.0, 1.0}})); + return loader; + }; + + vector<Joint> 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) */ + }; + + unique_ptr<IndexGraph> graph = make_unique<IndexGraph>(loader(), CreateEstimator()); + graph->Import(joints); + // Note. It's necessary to insert start and finish joints because the edge F0 is used + // for creating restrictions. + graph->InsertJoint({0 /* feature id */, 0 /* point id */}); // start + graph->InsertJoint({6 /* feature id */, 1 /* point id */}); // finish + + return graph; +} + +// Route through poster graph without any restrictions. +UNIT_TEST(PosterGraph) +{ + unique_ptr<IndexGraph> graph = BuildPosterGraph(); + IndexGraphStarter starter(*graph, RoadPoint(0, 0) /* start */, RoadPoint(6, 1) /* finish */); + vector<m2::PointD> const expectedGeom = {{2 /* x */, 0 /* y */}, + {1, 0}, {1, 1}, {2, 1}}; + + TestRouteGeometry(starter, AStarAlgorithm<IndexGraphStarter>::Result::OK, expectedGeom); +} + +// Route through poster graph with restrictions F0-F3 (type no). +UNIT_TEST(PosterGraph_RestrictionF0F3No) +{ + unique_ptr<IndexGraph> graph = BuildPosterGraph(); + Joint::Id const restictionCenterId = graph->GetJointIdForTesting({0, 1}); + + graph->ApplyRestrictionNo({0 /* feature id */, 1 /* point id */}, {3 /* feature id */, 0 /* point id */}, + restictionCenterId); + + IndexGraphStarter starter(*graph, RoadPoint(0, 0) /* start */, RoadPoint(6, 1) /* finish */); + vector<m2::PointD> const expectedGeom = {{2 /* x */, 0 /* y */}, + {1, 0}, {0, 0}, {0, 1}, {0.5, 1}, {1, 1}, {2, 1}}; + TestRouteGeometry(starter, AStarAlgorithm<IndexGraphStarter>::Result::OK, expectedGeom); +} + +// Route through poster graph with restrictions F0-F1 (type only). +UNIT_TEST(PosterGraph_RestrictionF0F1Only) +{ + unique_ptr<IndexGraph> graph = BuildPosterGraph(); + Joint::Id const restictionCenterId = graph->GetJointIdForTesting({0, 1}); + + graph->ApplyRestrictionOnly({0 /* feature id */, 1 /* point id */}, {1 /* feature id */, 0 /* point id */}, + restictionCenterId); + + IndexGraphStarter starter(*graph, RoadPoint(0, 0) /* start */, RoadPoint(6, 1) /* finish */); + vector<m2::PointD> const expectedGeom = {{2 /* x */, 0 /* y */}, + {1, 0}, {0, 0}, {0, 1}, {0.5, 1}, {1, 1}, {2, 1}}; + TestRouteGeometry(starter, AStarAlgorithm<IndexGraphStarter>::Result::OK, expectedGeom); +} + +// 1 *--F1-->* +// ↗ ↘ +// F1 F1 +// ↗ ↘ +// Start 0 *---F0--->-------F0----->* Finish +// 0 1 2 3 +// Start +// Note. F0 is a two setments feature. F1 is a three segment one. +unique_ptr<IndexGraph> BuildTwoWayGraph() +{ + auto const loader = []() + { + unique_ptr<TestGeometryLoader> loader = make_unique<TestGeometryLoader>(); + loader->AddRoad(0 /* feature id */, true /* oneWay */, buffer_vector<m2::PointD, 32>( + {{0.0, 0.0}, {1.0, 0.0}, {3.0, 0}})); + loader->AddRoad(1 /* feature id */, true /* oneWay */, buffer_vector<m2::PointD, 32>( + {{0.0, 0.0}, {1.0, 1.0}, {2.0, 1.0}, {3.0, 0.0}})); + return loader; + }; + + vector<Joint> const joints = { + MakeJoint({{0 /* feature id */, 0 /* point id */}, {1, 0}}), /* joint at point (0, 0) */ + MakeJoint({{0 /* feature id */, 2 /* point id */}, {1, 3}})}; + + unique_ptr<IndexGraph> graph = make_unique<IndexGraph>(loader(), CreateEstimator()); + graph->Import(joints); + return graph; +} + +UNIT_TEST(TwoWay_GetSingleFeaturePaths) +{ + unique_ptr<IndexGraph> graph = BuildTwoWayGraph(); + vector<RoadPoint> singleFeaturePath; + + // Full feature 0. + graph->GetSingleFeaturePaths({0 /* feature id */, 0 /* point id */}, {0, 2}, singleFeaturePath); + vector<RoadPoint> const expectedF0 = {{0 /* feature id */, 0 /* point id */}, {0, 1}, {0, 2}}; + TEST_EQUAL(singleFeaturePath, expectedF0, ()); + + // Full feature 1. + graph->GetSingleFeaturePaths({1 /* feature id */, 0 /* point id */}, {1, 3}, singleFeaturePath); + vector<RoadPoint> const expectedF1 = {{1 /* feature id */, 0 /* point id */}, {1, 1}, + {1, 2}, {1, 3}}; + TEST_EQUAL(singleFeaturePath, expectedF1, ()); + + // Full feature 1 in reversed order. + graph->GetSingleFeaturePaths({1 /* feature id */, 3 /* point id */}, {1, 0}, singleFeaturePath); + vector<RoadPoint> const expectedReversedF1 = {{1 /* feature id */, 3 /* point id */}, {1, 2}, + {1, 1}, {1, 0}}; + TEST_EQUAL(singleFeaturePath, expectedReversedF1, ()); + + // Part of feature 0. + graph->GetSingleFeaturePaths({0 /* feature id */, 1 /* point id */}, {0, 2}, singleFeaturePath); + vector<RoadPoint> const expectedPartF0 = {{0 /* feature id */, 1 /* point id */}, {0, 2}}; + TEST_EQUAL(singleFeaturePath, expectedPartF0, ()); + + // Part of feature 1 in reversed order. + graph->GetSingleFeaturePaths({1 /* feature id */, 2 /* point id */}, {1, 1}, singleFeaturePath); + vector<RoadPoint> const expectedPartF1 = {{1 /* feature id */, 2 /* point id */}, {1, 1}}; + TEST_EQUAL(singleFeaturePath, expectedPartF1, ()); + + // Single point test. + graph->GetSingleFeaturePaths({0 /* feature id */, 0 /* point id */}, {0, 0}, singleFeaturePath); + vector<RoadPoint> const expectedSinglePoint = {{0 /* feature id */, 0 /* point id */}}; + TEST_EQUAL(singleFeaturePath, expectedSinglePoint, ()); +} + +UNIT_TEST(TwoWay_GetConnectionPaths) +{ + unique_ptr<IndexGraph> graph = BuildTwoWayGraph(); + vector<vector<RoadPoint>> connectionPaths; + + graph->GetConnectionPaths(graph->GetJointIdForTesting({1 /* feature id */, 0 /* point id */}), + graph->GetJointIdForTesting({0, 2}), connectionPaths); + vector<vector<RoadPoint>> const expectedConnectionPaths = { + {{0, 0}, {0, 1}, {0, 2}}, // feature 0 + {{1, 0}, {1, 1}, {1, 2}, {1, 3}}, // feature 1 + }; + sort(connectionPaths.begin(), connectionPaths.end()); + TEST_EQUAL(connectionPaths, expectedConnectionPaths, ()); +} + +UNIT_TEST(TwoWay_GetShortestConnectionPath) +{ + unique_ptr<IndexGraph> graph = BuildTwoWayGraph(); + vector<RoadPoint> shortestPath; + + graph->GetShortestConnectionPath(graph->GetJointIdForTesting({0 /* feature id */, 0 /* point id */}), + graph->GetJointIdForTesting({1, 3}), shortestPath); + vector<RoadPoint> const expectedShortestPath = { + {0, 0}, {0, 1}, {0, 2}, // feature 0 + }; + TEST_EQUAL(shortestPath, expectedShortestPath, ()); +} +} // namespace diff --git a/routing/routing_tests/routing_tests.pro b/routing/routing_tests/routing_tests.pro index 100399f067..b170fc5d4f 100644 --- a/routing/routing_tests/routing_tests.pro +++ b/routing/routing_tests/routing_tests.pro @@ -28,6 +28,7 @@ SOURCES += \ cross_routing_tests.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 \ @@ -40,6 +41,8 @@ SOURCES += \ turns_sound_test.cpp \ turns_tts_text_tests.cpp \ vehicle_model_test.cpp \ + restriction_test.cpp \ HEADERS += \ + index_graph_tools.hpp \ road_graph_builder.hpp \ diff --git a/routing/single_mwm_router.cpp b/routing/single_mwm_router.cpp index f139a1fc5a..72dd05eddc 100644 --- a/routing/single_mwm_router.cpp +++ b/routing/single_mwm_router.cpp @@ -8,6 +8,7 @@ #include "routing/index_graph.hpp" #include "routing/index_graph_starter.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" @@ -38,10 +39,9 @@ vector<Junction> ConvertToJunctions(IndexGraphStarter const & starter, vector<Junction> junctions; junctions.reserve(roadPoints.size()); - Geometry const & geometry = starter.GetGraph().GetGeometry(); // TODO: Use real altitudes for pedestrian and bicycle routing. for (RoadPoint const & point : roadPoints) - junctions.emplace_back(geometry.GetPoint(point), feature::kDefaultAltitudeMeters); + junctions.emplace_back(starter.GetGraph().GetPoint(point), feature::kDefaultAltitudeMeters); return junctions; } @@ -117,7 +117,7 @@ IRouter::ResultCode SingleMwmRouter::DoCalculateRoute(MwmSet::MwmId const & mwmI IndexGraphStarter starter(graph, start, finish); AStarProgress progress(0, 100); - progress.Initialize(graph.GetGeometry().GetPoint(start), graph.GetGeometry().GetPoint(finish)); + progress.Initialize(graph.GetPoint(start), graph.GetPoint(finish)); uint32_t drawPointsStep = 0; auto onVisitJunction = [&](Joint::Id const & from, Joint::Id const & to) { @@ -198,6 +198,10 @@ bool SingleMwmRouter::LoadIndex(MwmSet::MwmId const & mwmId, string const & coun feature::RoutingSectionHeader header; header.Deserialize(src); graph.Deserialize(src); + RestrictionLoader restrictionLoader(*mwmValue); + if (restrictionLoader.HasRestrictions()) + graph.ApplyRestrictions(restrictionLoader.GetRestrictions()); + LOG(LINFO, (ROUTING_FILE_TAG, "section for", country, "loaded in", timer.ElapsedSeconds(), "seconds")); return true; |