diff --git a/routing/features_road_graph.cpp b/routing/features_road_graph.cpp index 7ee87240c6..e66f462505 100644 --- a/routing/features_road_graph.cpp +++ b/routing/features_road_graph.cpp @@ -199,6 +199,25 @@ void FeaturesRoadGraph::FindClosestEdges(m2::RectD const & rect, uint32_t count, finder.MakeResult(vicinities, count); } +vector> FeaturesRoadGraph::FindRoads( + m2::RectD const & rect, IsGoodFeatureFn const & isGoodFeature) const +{ + vector> roads; + auto const f = [&roads, &isGoodFeature, this](FeatureType & ft) { + if (!m_vehicleModel.IsRoad(ft)) + return; + + FeatureID const featureId = ft.GetID(); + if (isGoodFeature && !isGoodFeature(featureId)) + return; + + roads.emplace_back(featureId, GetCachedRoadInfo(featureId, ft, kInvalidSpeedKMPH)); + }; + + m_dataSource.ForEachInRect(f, rect, GetStreetReadScale()); + return roads; +} + void FeaturesRoadGraph::GetFeatureTypes(FeatureID const & featureId, feature::TypesHolder & types) const { FeaturesLoaderGuard loader(m_dataSource, featureId.m_mwmId); diff --git a/routing/features_road_graph.hpp b/routing/features_road_graph.hpp index 931726a5c4..3ae90ad931 100644 --- a/routing/features_road_graph.hpp +++ b/routing/features_road_graph.hpp @@ -82,6 +82,8 @@ public: ICrossEdgesLoader & edgesLoader) const override; void FindClosestEdges(m2::RectD const & rect, uint32_t count, IsGoodFeatureFn const & isGoodFeature, std::vector> & vicinities) const override; + std::vector> FindRoads( + m2::RectD const & rect, IsGoodFeatureFn const & isGoodFeature) const override; void GetFeatureTypes(FeatureID const & featureId, feature::TypesHolder & types) const override; void GetJunctionTypes(Junction const & junction, feature::TypesHolder & types) const override; IRoadGraph::Mode GetMode() const override; diff --git a/routing/index_router.cpp b/routing/index_router.cpp index ada85745c3..eb756e3549 100644 --- a/routing/index_router.cpp +++ b/routing/index_router.cpp @@ -11,6 +11,7 @@ #include "routing/index_graph_starter.hpp" #include "routing/index_road_graph.hpp" #include "routing/leaps_postprocessor.hpp" +#include "routing/nearest_edge_finder.hpp" #include "routing/pedestrian_directions.hpp" #include "routing/restriction_loader.hpp" #include "routing/route.hpp" @@ -196,7 +197,8 @@ bool IsTheSameSegments(m2::PointD const & seg1From, m2::PointD const & seg1To, return (seg1From == seg2From && seg1To == seg2To) || (seg1From == seg2To && seg1To == seg2From); } -bool IsDeadEnd(Segment const & segment, bool isOutgoing, WorldGraph & worldGraph) +bool IsDeadEnd(Segment const & segment, bool isOutgoing, WorldGraph & worldGraph, + set & visitedSegments) { // Maximum size (in Segment) of an island in road graph which may be found by the method. size_t constexpr kDeadEndTestLimit = 250; @@ -212,7 +214,7 @@ bool IsDeadEnd(Segment const & segment, bool isOutgoing, WorldGraph & worldGraph graph.GetEdgeList(u, isOutgoing, edges); }; - return !CheckGraphConnectivity(segment, kDeadEndTestLimit, worldGraph, + return !CheckGraphConnectivity(segment, kDeadEndTestLimit, worldGraph, visitedSegments, getVertexByEdgeFn, getOutgoingEdgesFn); } } // namespace @@ -816,14 +818,45 @@ unique_ptr IndexRouter::MakeWorldGraph() move(transitGraphLoader), m_estimator); } +void IndexRouter::EraseIfDeadEnd(WorldGraph & worldGraph, + vector> & roads) const +{ + // |deadEnds| cache is necessary to minimize number of calls a time consumption IsDeadEnd() method. + set deadEnds; + base::EraseIf(roads, [&deadEnds, &worldGraph, this](pair const & r) { + auto const & ft = r.first; + auto const & road = r.second; + CHECK_GREATER_OR_EQUAL(road.m_junctions.size(), 2, ()); + + // Note. Checking if an edge goes to a dead end is a time consumption process. + // So the number of checked edges should be minimized as possible. + // Below a heuristic is used. If a first segment of a feature is forward direction is a dead end + // all segments of the feature is considered as dead ends. + auto const segment = GetSegmentByEdge(Edge::MakeReal(ft, true /* forward */, 0 /* segment id */, + road.m_junctions[0], road.m_junctions[1])); + if (deadEnds.count(segment) != 0) + return true; + + set visitedSegments; + if (!IsDeadEnd(segment, true /* isOutgoing */, worldGraph, visitedSegments)) + return false; + + auto const beginIt = std::make_move_iterator(visitedSegments.begin()); + auto const endIt = std::make_move_iterator(visitedSegments.end()); + deadEnds.insert(beginIt, endIt); + return true; + }); +} + bool IndexRouter::IsFencedOff(m2::PointD const & point, pair const & edgeProjection, - vector const & fences) const + vector> const & fences) const { auto const & edge = edgeProjection.first; auto const & projPoint = edgeProjection.second.GetPoint(); - for (auto const & featureGeom : fences) + for (auto const & fence : fences) { + auto const & featureGeom = fence.second.m_junctions; for (size_t i = 1; i < featureGeom.size(); ++i) { auto const & fencePointFrom = featureGeom[i - 1]; @@ -834,6 +867,11 @@ bool IndexRouter::IsFencedOff(m2::PointD const & point, pair con continue; } + // If two segment are connected with its ends it's also considered as an + // intersection according to m2::SegmentsIntersect(). On the other hand + // it's possible that |projPoint| is an end point of |edge| and |edge| + // is connected with other edges. To prevent fencing off such edges with their + // neighboring edges the condition !m2::IsPointOnSegment() is added. if (m2::SegmentsIntersect(point, projPoint, fencePointFrom.GetPoint(), fencePointTo.GetPoint()) && !m2::IsPointOnSegment(projPoint, fencePointFrom.GetPoint(), @@ -846,36 +884,19 @@ bool IndexRouter::IsFencedOff(m2::PointD const & point, pair con return false; } -void IndexRouter::FetchRoadInfo(m2::RectD const & rect, WorldGraph & worldGraph, - vector & roadGeom, - set & deadEnds) const +void IndexRouter::RoadsToNearestEdges(m2::PointD const & point, + vector> const & roads, + uint32_t count, vector> & edgeProj) const { - auto const roadFetcher = [this, &worldGraph, &roadGeom, &deadEnds](FeatureType & ft) { - ft.ParseGeometry(FeatureType::BEST_GEOMETRY); - if (!m_roadGraph.IsRoad(ft)) - return; + NearestEdgeFinder finder(point); + for (auto const & r : roads) + { + auto const & fid = r.first; + auto const & roadInfo = r.second; + finder.AddInformationSource(fid, roadInfo.m_junctions, roadInfo.m_bidirectional); + } - auto const info = ft.GetID().m_mwmId.GetInfo(); - CHECK(info, ()); - if (!m_numMwmIds->ContainsFile(info->GetLocalFile().GetCountryFile())) - return; - - CHECK_GREATER_OR_EQUAL(ft.GetPointsCount(), 2, ()); - // Note. Checking if an edge goes to a dead end is a time consumption process. - // So the number of checked edges should be minimized as possible. - // Below a heuristic is used. If a first segment of a feature is forward direction is a dead end - // all segments of the feature is considered as dead ends. - if (IsDeadEnd(Edge::MakeReal(ft.GetID(), true /* forward */, 0 /* segment id */, - Junction(ft.GetPoint(0), 0), Junction(ft.GetPoint(1), 0)), true /* isOutgoing */, worldGraph)) - { - deadEnds.emplace(ft.GetID()); - return; - } - - roadGeom.emplace_back(m_roadGraph.GetRoadGeom(ft)); - }; - - m_dataSource.ForEachInRect(roadFetcher, rect, scales::GetUpperScale()); + finder.MakeResult(edgeProj, count); } Segment IndexRouter::GetSegmentByEdge(Edge const & edge) const @@ -886,11 +907,6 @@ Segment IndexRouter::GetSegmentByEdge(Edge const & edge) const return Segment(numMwmId, edge.GetFeatureId().m_index, edge.GetSegId(), edge.IsForward()); } -bool IndexRouter::IsDeadEnd(Edge const & edge, bool isOutgoing, WorldGraph & worldGraph) const -{ - return ::IsDeadEnd(GetSegmentByEdge(edge), isOutgoing, worldGraph); -} - bool IndexRouter::FindClosestCodirectionalEdge(m2::PointD const & point, m2::PointD const & direction, vector> const & candidates, Edge & closestCodirectionalEdge) const @@ -962,29 +978,40 @@ bool IndexRouter::FindBestEdges(m2::PointD const & point, MYTHROW(MwmIsNotAliveException, ("Can't get mwm handle for", pointCountryFile)); auto const rect = MercatorBounds::RectByCenterXYAndSizeInMeters(point, closestEdgesRadiusM); - vector roadGeom; - set deadEnds; - FetchRoadInfo(rect, worldGraph, roadGeom, deadEnds); - - auto const isGoodFeature = [this, &deadEnds](FeatureID const & fid) { + auto const isGood = [this](FeatureID const & fid) { auto const & info = fid.m_mwmId.GetInfo(); - if (!m_numMwmIds->ContainsFile(info->GetLocalFile().GetCountryFile())) - return false; - - return deadEnds.count(fid) == 0; + return m_numMwmIds->ContainsFile(info->GetLocalFile().GetCountryFile()); }; + auto closestRoads = m_roadGraph.FindRoads(rect, isGood); - // @TODO(bykoianko) The geometry is parsed twice. Once in |FetchRoadInfo()| and once in - // |m_roadGraph.FindClosestEdges()|. Consider gathering them to parse geometry only once. + // Removing all dead ends from |closestRoads|. Then some candidates will be taken from |closestRoads|. + // It's necessary to call for all |closestRoads| before IsFencedOff(). If to remove all fenced off + // by other features from |point| candidates at first, only dead ends candidates may be left. + // And then the dead end candidates will be removed as well as dead ends. + EraseIfDeadEnd(worldGraph, closestRoads); + + // Sorting from the closest features to the further ones. The idea is the closer + // a feature to a |point| the more chances that it crosses the segment + // |point|, projections of |point| on feature edges. It confirmed with benchmarks. + sort(closestRoads.begin(), closestRoads.end(), + [&point](pair const & lhs, + pair const & rhs) { + CHECK(!lhs.second.m_junctions.empty(), ()); + return + point.SquaredLength(lhs.second.m_junctions[0].GetPoint()) < + point.SquaredLength(rhs.second.m_junctions[0].GetPoint()); + }); + + // Getting |kMaxRoadCandidates| closest edges from |closestRoads|. vector> candidates; - m_roadGraph.FindClosestEdges(rect, kMaxRoadCandidates, isGoodFeature, candidates); + RoadsToNearestEdges(point, closestRoads, kMaxRoadCandidates, candidates); // Removing all candidates which are fenced off by the road graph from |point|. // It's better to perform this step after |candidates| are found: // * by performance reasons // * the closest edge(s) is not separated from |point| by other edges. - base::EraseIf(candidates, [&point, &roadGeom, this](pair const & candidate) { - return IsFencedOff(point, candidate, roadGeom); + base::EraseIf(candidates, [&point, &closestRoads, this](pair const & candidate) { + return IsFencedOff(point, candidate, closestRoads); }); if (candidates.empty()) diff --git a/routing/index_router.hpp b/routing/index_router.hpp index c3b599a0bb..c1ff865a8c 100644 --- a/routing/index_router.hpp +++ b/routing/index_router.hpp @@ -105,24 +105,21 @@ private: std::unique_ptr MakeWorldGraph(); + /// \brief Removes all roads from |roads| which goes to dead ends. + void EraseIfDeadEnd(WorldGraph & worldGraph, + std::vector> & roads) const; + /// \returns true if a segment (|point|, |edgeProjection.second|) crosses one of segments /// in |fences| except for a one which has the same geometry with |edgeProjection.first|. bool IsFencedOff(m2::PointD const & point, std::pair const & edgeProjection, - std::vector const & fences) const; + std::vector> const & fences) const; - /// \brief Fills |roadGeom| geometry of not dead-end road features which lie in |rect|, - /// and |deadEnds| with FeatureIDs which goes to dead ends. - /// \note Some additional road features which lie near |rect| may be added to |roadGeom| - /// and to |deadEnds|. - void FetchRoadInfo(m2::RectD const & rect, WorldGraph & worldGraph, - vector & roadGeom, - std::set & deadEnds) const; + void RoadsToNearestEdges(m2::PointD const & point, + std::vector> const & roads, + uint32_t count, std::vector> & edgeProj) const; Segment GetSegmentByEdge(Edge const & edge) const; - /// \returns true if it's impossible to go from |edge| far enough. - bool IsDeadEnd(Edge const & edge, bool isOutgoing, WorldGraph & worldGraph) const; - /// \brief Fills |closestCodirectionalEdge| with a codirectional edge which is closet to /// |point| and returns true if there's any. If not returns false. bool FindClosestCodirectionalEdge(m2::PointD const & point, m2::PointD const & direction, diff --git a/routing/road_graph.cpp b/routing/road_graph.cpp index 74d8272de2..2e7e6c2cc6 100644 --- a/routing/road_graph.cpp +++ b/routing/road_graph.cpp @@ -304,6 +304,12 @@ double IRoadGraph::GetSpeedKMpH(Edge const & edge, SpeedParams const & speedPara return speedKMpH; } +vector> IRoadGraph::FindRoads( + m2::RectD const & rect, IsGoodFeatureFn const & isGoodFeature) const +{ + return {}; +} + void IRoadGraph::GetEdgeTypes(Edge const & edge, feature::TypesHolder & types) const { if (edge.IsFake()) diff --git a/routing/road_graph.hpp b/routing/road_graph.hpp index 4464819a68..8f43598b83 100644 --- a/routing/road_graph.hpp +++ b/routing/road_graph.hpp @@ -17,6 +17,7 @@ #include #include #include +#include #include namespace routing @@ -314,6 +315,13 @@ public: IsGoodFeatureFn const & isGoodFeature, std::vector> & vicinities) const {}; + /// \returns Vector of pairs FeatureID and corresponing RoadInfo for road features + /// lying in |rect|. + /// \note |RoadInfo::m_speedKMPH| is set to |kInvalidSpeedKMPH|. + /// \note Some roads returned by this method my lie outside |rect| but close to it. + virtual std::vector> FindRoads( + m2::RectD const & rect, IsGoodFeatureFn const & isGoodFeature) const; + /// @return Types for the specified feature virtual void GetFeatureTypes(FeatureID const & featureId, feature::TypesHolder & types) const = 0; diff --git a/routing/routing_helpers.hpp b/routing/routing_helpers.hpp index 72e6e311f8..a569b28b70 100644 --- a/routing/routing_helpers.hpp +++ b/routing/routing_helpers.hpp @@ -56,12 +56,13 @@ Segment ConvertEdgeToSegment(NumMwmIds const & numMwmIds, Edge const & edge); /// world graph. template bool CheckGraphConnectivity(typename Graph::Vertex const & start, size_t limit, Graph & graph, - GetVertexByEdgeFn && getVertexByEdgeFn, GetOutgoingEdgesFn && getOutgoingEdgesFn) + std::set & marked, + GetVertexByEdgeFn && getVertexByEdgeFn, + GetOutgoingEdgesFn && getOutgoingEdgesFn) { std::queue q; q.push(start); - std::set marked; marked.insert(start); std::vector edges;