diff --git a/routing/CMakeLists.txt b/routing/CMakeLists.txt index 2087034250..ac3d06ce66 100644 --- a/routing/CMakeLists.txt +++ b/routing/CMakeLists.txt @@ -36,7 +36,10 @@ set( cross_mwm_graph.hpp cross_mwm_ids.hpp cross_mwm_index_graph.hpp + directions_engine.cpp directions_engine.hpp + directions_engine_helpers.cpp + directions_engine_helpers.hpp edge_estimator.cpp edge_estimator.hpp fake_edges_container.hpp diff --git a/routing/car_directions.cpp b/routing/car_directions.cpp index 896c644c72..8891e27bd2 100644 --- a/routing/car_directions.cpp +++ b/routing/car_directions.cpp @@ -4,165 +4,22 @@ #include "routing/road_point.hpp" #include "routing/router_delegate.hpp" #include "routing/routing_exceptions.hpp" -#include "routing/routing_helpers.hpp" #include "routing/routing_result_graph.hpp" #include "routing/turns.hpp" #include "routing/turns_generator.hpp" -#include "traffic/traffic_info.hpp" - -#include "routing_common/car_model.hpp" - -#include "indexer/ftypes_matcher.hpp" - -#include "coding/string_utf8_multilang.hpp" - -#include "geometry/mercator.hpp" -#include "geometry/point2d.hpp" +#include "base/assert.hpp" #include -#include -#include -#include - -namespace -{ -using namespace routing; -using namespace routing::turns; -using namespace std; -using namespace traffic; - -class RoutingResult : public IRoutingResult -{ -public: - RoutingResult(IRoadGraph::EdgeVector const & routeEdges, - CarDirectionsEngine::AdjacentEdgesMap const & adjacentEdges, - TUnpackedPathSegments const & pathSegments) - : m_routeEdges(routeEdges) - , m_adjacentEdges(adjacentEdges) - , m_pathSegments(pathSegments) - , m_routeLength(0) - { - for (auto const & edge : routeEdges) - { - m_routeLength += mercator::DistanceOnEarth(edge.GetStartJunction().GetPoint(), - edge.GetEndJunction().GetPoint()); - } - } - - // turns::IRoutingResult overrides: - TUnpackedPathSegments const & GetSegments() const override { return m_pathSegments; } - - void GetPossibleTurns(SegmentRange const & segmentRange, m2::PointD const & junctionPoint, - size_t & ingoingCount, TurnCandidates & outgoingTurns) const override - { - CHECK(!segmentRange.IsEmpty(), ("SegmentRange presents a fake feature.", - "junctionPoint:", mercator::ToLatLon(junctionPoint))); - - ingoingCount = 0; - outgoingTurns.candidates.clear(); - - auto const adjacentEdges = m_adjacentEdges.find(segmentRange); - if (adjacentEdges == m_adjacentEdges.cend()) - { - ASSERT(false, ()); - return; - } - - ingoingCount = adjacentEdges->second.m_ingoingTurnsCount; - outgoingTurns = adjacentEdges->second.m_outgoingTurns; - } - - double GetPathLength() const override { return m_routeLength; } - - geometry::PointWithAltitude GetStartPoint() const override - { - CHECK(!m_routeEdges.empty(), ()); - return m_routeEdges.front().GetStartJunction(); - } - - geometry::PointWithAltitude GetEndPoint() const override - { - CHECK(!m_routeEdges.empty(), ()); - return m_routeEdges.back().GetEndJunction(); - } - -private: - IRoadGraph::EdgeVector const & m_routeEdges; - CarDirectionsEngine::AdjacentEdgesMap const & m_adjacentEdges; - TUnpackedPathSegments const & m_pathSegments; - double m_routeLength; -}; - -/// \brief This method should be called for an internal junction of the route with corresponding -/// |ingoingEdges|, |outgoingEdges|, |ingoingRouteEdge| and |outgoingRouteEdge|. -/// \returns false if the junction is an internal point of feature segment and can be considered as -/// a part of LoadedPathSegment and returns true if the junction should be considered as a beginning -/// of a new LoadedPathSegment. -bool IsJoint(IRoadGraph::EdgeVector const & ingoingEdges, - IRoadGraph::EdgeVector const & outgoingEdges, - Edge const & ingoingRouteEdge, - Edge const & outgoingRouteEdge, - bool isCurrJunctionFinish, - bool isInEdgeReal) -{ - // When feature id is changed at a junction this junction should be considered as a joint. - // - // If a feature id is not changed at a junction but the junction has some ingoing or outgoing edges with - // different feature ids, the junction should be considered as a joint. - // - // If a feature id is not changed at a junction and all ingoing and outgoing edges of the junction has - // the same feature id, the junction still may be considered as a joint. - // It happens in case of self intersected features. For example: - // *--Seg3--* - // | | - // Seg4 Seg2 - // | | - // *--Seg0--*--Seg1--* - // The common point of segments 0, 1 and 4 should be considered as a joint. - if (!isInEdgeReal) - return true; - - if (isCurrJunctionFinish) - return true; - - if (ingoingRouteEdge.GetFeatureId() != outgoingRouteEdge.GetFeatureId()) - return true; - - FeatureID const & featureId = ingoingRouteEdge.GetFeatureId(); - uint32_t const segOut = outgoingRouteEdge.GetSegId(); - for (Edge const & e : ingoingEdges) - { - if (e.GetFeatureId() != featureId || abs(static_cast(segOut - e.GetSegId())) != 1) - return true; - } - - uint32_t const segIn = ingoingRouteEdge.GetSegId(); - for (Edge const & e : outgoingEdges) - { - // It's necessary to compare segments for cases when |featureId| is a loop. - if (e.GetFeatureId() != featureId || abs(static_cast(segIn - e.GetSegId())) != 1) - return true; - } - return false; -} -} // namespace namespace routing { -// CarDirectionsEngine::AdjacentEdges --------------------------------------------------------- -bool CarDirectionsEngine::AdjacentEdges::IsAlmostEqual(AdjacentEdges const & rhs) const -{ - return m_outgoingTurns.IsAlmostEqual(rhs.m_outgoingTurns) && - m_ingoingTurnsCount == rhs.m_ingoingTurnsCount; -} +using namespace std; -// CarDirectionsEngine ------------------------------------------------------------------------ CarDirectionsEngine::CarDirectionsEngine(DataSource const & dataSource, shared_ptr numMwmIds) - : m_dataSource(dataSource), m_numMwmIds(numMwmIds) + : IDirectionsEngine(dataSource, move(numMwmIds)) { - CHECK(m_numMwmIds, ()); } bool CarDirectionsEngine::Generate(IndexRoadGraph const & graph, @@ -200,7 +57,7 @@ bool CarDirectionsEngine::Generate(IndexRoadGraph const & graph, if (cancellable.IsCancelled()) return false; - ::RoutingResult resultGraph(routeEdges, m_adjacentEdges, m_pathSegments); + RoutingEngineResult resultGraph(routeEdges, m_adjacentEdges, m_pathSegments); auto const res = MakeTurnAnnotation(resultGraph, *m_numMwmIds, cancellable, routeGeometry, turns, streetNames, segments); @@ -220,194 +77,4 @@ bool CarDirectionsEngine::Generate(IndexRoadGraph const & graph, CHECK_EQUAL(segments.size(), routeEdges.size(), ()); return true; } - -void CarDirectionsEngine::Clear() -{ - m_adjacentEdges.clear(); - m_pathSegments.clear(); - m_loader.reset(); -} - -FeaturesLoaderGuard & CarDirectionsEngine::GetLoader(MwmSet::MwmId const & id) -{ - if (!m_loader || id != m_loader->GetId()) - m_loader = make_unique(m_dataSource, id); - return *m_loader; -} - -void CarDirectionsEngine::LoadPathAttributes(FeatureID const & featureId, LoadedPathSegment & pathSegment) -{ - if (!featureId.IsValid()) - return; - - if (FakeFeatureIds::IsGuidesFeature(featureId.m_index)) - return; - - auto ft = GetLoader(featureId.m_mwmId).GetFeatureByIndex(featureId.m_index); - if (!ft) - return; - - auto const highwayClass = ftypes::GetHighwayClass(feature::TypesHolder(*ft)); - ASSERT_NOT_EQUAL(highwayClass, ftypes::HighwayClass::Error, ()); - ASSERT_NOT_EQUAL(highwayClass, ftypes::HighwayClass::Undefined, ()); - - pathSegment.m_highwayClass = highwayClass; - pathSegment.m_isLink = ftypes::IsLinkChecker::Instance()(*ft); - ft->GetName(StringUtf8Multilang::kDefaultCode, pathSegment.m_name); - pathSegment.m_onRoundabout = ftypes::IsRoundAboutChecker::Instance()(*ft); -} - -void CarDirectionsEngine::GetSegmentRangeAndAdjacentEdges( - IRoadGraph::EdgeVector const & outgoingEdges, Edge const & inEdge, uint32_t startSegId, - uint32_t endSegId, SegmentRange & segmentRange, TurnCandidates & outgoingTurns) -{ - outgoingTurns.isCandidatesAngleValid = true; - outgoingTurns.candidates.reserve(outgoingEdges.size()); - segmentRange = SegmentRange(inEdge.GetFeatureId(), startSegId, endSegId, inEdge.IsForward(), - inEdge.GetStartPoint(), inEdge.GetEndPoint()); - CHECK(segmentRange.IsCorrect(), ()); - m2::PointD const & ingoingPoint = inEdge.GetStartJunction().GetPoint(); - m2::PointD const & junctionPoint = inEdge.GetEndJunction().GetPoint(); - - for (auto const & edge : outgoingEdges) - { - if (edge.IsFake()) - continue; - - auto const & outFeatureId = edge.GetFeatureId(); - if (FakeFeatureIds::IsGuidesFeature(outFeatureId.m_index)) - continue; - - auto ft = GetLoader(outFeatureId.m_mwmId).GetFeatureByIndex(outFeatureId.m_index); - if (!ft) - continue; - - auto const highwayClass = ftypes::GetHighwayClass(feature::TypesHolder(*ft)); - ASSERT_NOT_EQUAL( - highwayClass, ftypes::HighwayClass::Error, - (mercator::ToLatLon(edge.GetStartPoint()), mercator::ToLatLon(edge.GetEndPoint()))); - ASSERT_NOT_EQUAL( - highwayClass, ftypes::HighwayClass::Undefined, - (mercator::ToLatLon(edge.GetStartPoint()), mercator::ToLatLon(edge.GetEndPoint()))); - - bool const isLink = ftypes::IsLinkChecker::Instance()(*ft); - - double angle = 0; - - if (inEdge.GetFeatureId().m_mwmId == edge.GetFeatureId().m_mwmId) - { - ASSERT_LESS(mercator::DistanceOnEarth(junctionPoint, edge.GetStartJunction().GetPoint()), - turns::kFeaturesNearTurnMeters, ()); - m2::PointD const & outgoingPoint = edge.GetEndJunction().GetPoint(); - angle = base::RadToDeg(turns::PiMinusTwoVectorsAngle(junctionPoint, ingoingPoint, outgoingPoint)); - } - else - { - // Note. In case of crossing mwm border - // (inEdge.GetFeatureId().m_mwmId != edge.GetFeatureId().m_mwmId) - // twins of inEdge.GetFeatureId() are considered as outgoing features. - // In this case that turn candidate angle is invalid and - // should not be used for turn generation. - outgoingTurns.isCandidatesAngleValid = false; - } - outgoingTurns.candidates.emplace_back(angle, ConvertEdgeToSegment(*m_numMwmIds, edge), - highwayClass, isLink); - } - - if (outgoingTurns.isCandidatesAngleValid) - sort(outgoingTurns.candidates.begin(), outgoingTurns.candidates.end(), base::LessBy(&TurnCandidate::m_angle)); -} - -void CarDirectionsEngine::GetEdges(IndexRoadGraph const & graph, - geometry::PointWithAltitude const & currJunction, - bool isCurrJunctionFinish, IRoadGraph::EdgeVector & outgoing, - IRoadGraph::EdgeVector & ingoing) -{ - // Note. If |currJunction| is a finish the outgoing edges - // from finish are not important for turn generation. - if (!isCurrJunctionFinish) - graph.GetOutgoingEdges(currJunction, outgoing); - - graph.GetIngoingEdges(currJunction, ingoing); -} - -void CarDirectionsEngine::FillPathSegmentsAndAdjacentEdgesMap( - IndexRoadGraph const & graph, vector const & path, - IRoadGraph::EdgeVector const & routeEdges, base::Cancellable const & cancellable) -{ - size_t const pathSize = path.size(); - CHECK_GREATER(pathSize, 1, ()); - CHECK_EQUAL(routeEdges.size() + 1, pathSize, ()); - // Filling |m_adjacentEdges|. - auto constexpr kInvalidSegId = numeric_limits::max(); - // |startSegId| is a value to keep start segment id of a new instance of LoadedPathSegment. - uint32_t startSegId = kInvalidSegId; - vector prevJunctions; - vector prevSegments; - for (size_t i = 1; i < pathSize; ++i) - { - if (cancellable.IsCancelled()) - return; - - geometry::PointWithAltitude const & prevJunction = path[i - 1]; - geometry::PointWithAltitude const & currJunction = path[i]; - - IRoadGraph::EdgeVector outgoingEdges; - IRoadGraph::EdgeVector ingoingEdges; - bool const isCurrJunctionFinish = (i + 1 == pathSize); - GetEdges(graph, currJunction, isCurrJunctionFinish, outgoingEdges, ingoingEdges); - - Edge const & inEdge = routeEdges[i - 1]; - // Note. |inFeatureId| may be invalid in case of adding fake features. - // It happens for example near starts and a finishes. - FeatureID const & inFeatureId = inEdge.GetFeatureId(); - uint32_t const inSegId = inEdge.GetSegId(); - - if (startSegId == kInvalidSegId) - startSegId = inSegId; - - prevJunctions.push_back(prevJunction); - prevSegments.push_back(ConvertEdgeToSegment(*m_numMwmIds, inEdge)); - - if (!IsJoint(ingoingEdges, outgoingEdges, inEdge, routeEdges[i], isCurrJunctionFinish, - inFeatureId.IsValid())) - { - continue; - } - - CHECK_EQUAL(prevJunctions.size(), - static_cast(abs(static_cast(inSegId - startSegId)) + 1), ()); - - prevJunctions.push_back(currJunction); - - AdjacentEdges adjacentEdges(ingoingEdges.size()); - SegmentRange segmentRange; - GetSegmentRangeAndAdjacentEdges(outgoingEdges, inEdge, startSegId, inSegId, segmentRange, - adjacentEdges.m_outgoingTurns); - - size_t const prevJunctionSize = prevJunctions.size(); - LoadedPathSegment pathSegment; - LoadPathAttributes(segmentRange.GetFeature(), pathSegment); - pathSegment.m_segmentRange = segmentRange; - pathSegment.m_path = move(prevJunctions); - // @TODO(bykoianko) |pathSegment.m_weight| should be filled here. - - // |prevSegments| contains segments which corresponds to road edges between joints. In case of a fake edge - // a fake segment is created. - CHECK_EQUAL(prevSegments.size() + 1, prevJunctionSize, ()); - pathSegment.m_segments = move(prevSegments); - - if (!segmentRange.IsEmpty()) - { - auto const it = m_adjacentEdges.find(segmentRange); - m_adjacentEdges.insert(it, make_pair(segmentRange, move(adjacentEdges))); - } - - m_pathSegments.push_back(move(pathSegment)); - - prevJunctions.clear(); - prevSegments.clear(); - startSegId = kInvalidSegId; - } -} } // namespace routing diff --git a/routing/car_directions.hpp b/routing/car_directions.hpp index d590fc8721..0f1fda5a41 100644 --- a/routing/car_directions.hpp +++ b/routing/car_directions.hpp @@ -1,6 +1,7 @@ #pragma once #include "routing/directions_engine.hpp" +#include "routing/directions_engine_helpers.hpp" #include "routing/index_road_graph.hpp" #include "routing/loaded_path_segment.hpp" #include "routing/segment.hpp" @@ -21,17 +22,6 @@ namespace routing class CarDirectionsEngine : public IDirectionsEngine { public: - struct AdjacentEdges - { - explicit AdjacentEdges(size_t ingoingTurnsCount = 0) : m_ingoingTurnsCount(ingoingTurnsCount) {} - bool IsAlmostEqual(AdjacentEdges const & rhs) const; - - turns::TurnCandidates m_outgoingTurns; - size_t m_ingoingTurnsCount; - }; - - using AdjacentEdgesMap = std::map; - CarDirectionsEngine(DataSource const & dataSource, std::shared_ptr numMwmIds); // IDirectionsEngine override: @@ -40,30 +30,5 @@ public: Route::TStreets & streetNames, std::vector & routeGeometry, std::vector & segments) override; - void Clear() override; - -private: - FeaturesLoaderGuard & GetLoader(MwmSet::MwmId const & id); - void LoadPathAttributes(FeatureID const & featureId, LoadedPathSegment & pathSegment); - void GetSegmentRangeAndAdjacentEdges(IRoadGraph::EdgeVector const & outgoingEdges, - Edge const & inEdge, uint32_t startSegId, uint32_t endSegId, - SegmentRange & segmentRange, - turns::TurnCandidates & outgoingTurns); - /// \brief The method gathers sequence of segments according to IsJoint() method - /// and fills |m_adjacentEdges| and |m_pathSegments|. - void FillPathSegmentsAndAdjacentEdgesMap(IndexRoadGraph const & graph, - std::vector const & path, - IRoadGraph::EdgeVector const & routeEdges, - base::Cancellable const & cancellable); - - void GetEdges(IndexRoadGraph const & graph, geometry::PointWithAltitude const & currJunction, - bool isCurrJunctionFinish, IRoadGraph::EdgeVector & outgoing, - IRoadGraph::EdgeVector & ingoing); - - AdjacentEdgesMap m_adjacentEdges; - TUnpackedPathSegments m_pathSegments; - DataSource const & m_dataSource; - std::shared_ptr m_numMwmIds; - std::unique_ptr m_loader; }; } // namespace routing diff --git a/routing/directions_engine.cpp b/routing/directions_engine.cpp new file mode 100644 index 0000000000..cb22b65309 --- /dev/null +++ b/routing/directions_engine.cpp @@ -0,0 +1,217 @@ +#include "routing/directions_engine.hpp" + +#include "routing/routing_helpers.hpp" + +#include "traffic/traffic_info.hpp" + +#include "routing_common/car_model.hpp" + +#include "indexer/ftypes_matcher.hpp" + +#include "coding/string_utf8_multilang.hpp" + +#include "geometry/mercator.hpp" +#include "geometry/point2d.hpp" + +#include +#include + +namespace routing +{ +using namespace routing::turns; +using namespace std; +using namespace traffic; + +void IDirectionsEngine::Clear() +{ + m_adjacentEdges.clear(); + m_pathSegments.clear(); + m_loader.reset(); +} + +FeaturesLoaderGuard & IDirectionsEngine::GetLoader(MwmSet::MwmId const & id) +{ + if (!m_loader || id != m_loader->GetId()) + m_loader = make_unique(m_dataSource, id); + return *m_loader; +} + +void IDirectionsEngine::LoadPathAttributes(FeatureID const & featureId, + LoadedPathSegment & pathSegment) +{ + if (!featureId.IsValid()) + return; + + if (FakeFeatureIds::IsGuidesFeature(featureId.m_index)) + return; + + auto ft = GetLoader(featureId.m_mwmId).GetFeatureByIndex(featureId.m_index); + if (!ft) + return; + + auto const highwayClass = ftypes::GetHighwayClass(feature::TypesHolder(*ft)); + ASSERT_NOT_EQUAL(highwayClass, ftypes::HighwayClass::Error, ()); + ASSERT_NOT_EQUAL(highwayClass, ftypes::HighwayClass::Undefined, ()); + + pathSegment.m_highwayClass = highwayClass; + pathSegment.m_isLink = ftypes::IsLinkChecker::Instance()(*ft); + ft->GetName(StringUtf8Multilang::kDefaultCode, pathSegment.m_name); + pathSegment.m_onRoundabout = ftypes::IsRoundAboutChecker::Instance()(*ft); +} + +void IDirectionsEngine::GetSegmentRangeAndAdjacentEdges( + IRoadGraph::EdgeVector const & outgoingEdges, Edge const & inEdge, uint32_t startSegId, + uint32_t endSegId, SegmentRange & segmentRange, TurnCandidates & outgoingTurns) +{ + outgoingTurns.isCandidatesAngleValid = true; + outgoingTurns.candidates.reserve(outgoingEdges.size()); + segmentRange = SegmentRange(inEdge.GetFeatureId(), startSegId, endSegId, inEdge.IsForward(), + inEdge.GetStartPoint(), inEdge.GetEndPoint()); + CHECK(segmentRange.IsCorrect(), ()); + m2::PointD const & ingoingPoint = inEdge.GetStartJunction().GetPoint(); + m2::PointD const & junctionPoint = inEdge.GetEndJunction().GetPoint(); + + for (auto const & edge : outgoingEdges) + { + if (edge.IsFake()) + continue; + + auto const & outFeatureId = edge.GetFeatureId(); + if (FakeFeatureIds::IsGuidesFeature(outFeatureId.m_index)) + continue; + + auto ft = GetLoader(outFeatureId.m_mwmId).GetFeatureByIndex(outFeatureId.m_index); + if (!ft) + continue; + + auto const highwayClass = ftypes::GetHighwayClass(feature::TypesHolder(*ft)); + ASSERT_NOT_EQUAL( + highwayClass, ftypes::HighwayClass::Error, + (mercator::ToLatLon(edge.GetStartPoint()), mercator::ToLatLon(edge.GetEndPoint()))); + ASSERT_NOT_EQUAL( + highwayClass, ftypes::HighwayClass::Undefined, + (mercator::ToLatLon(edge.GetStartPoint()), mercator::ToLatLon(edge.GetEndPoint()))); + + bool const isLink = ftypes::IsLinkChecker::Instance()(*ft); + + double angle = 0; + + if (inEdge.GetFeatureId().m_mwmId == edge.GetFeatureId().m_mwmId) + { + ASSERT_LESS(mercator::DistanceOnEarth(junctionPoint, edge.GetStartJunction().GetPoint()), + turns::kFeaturesNearTurnMeters, ()); + m2::PointD const & outgoingPoint = edge.GetEndJunction().GetPoint(); + angle = + base::RadToDeg(turns::PiMinusTwoVectorsAngle(junctionPoint, ingoingPoint, outgoingPoint)); + } + else + { + // Note. In case of crossing mwm border + // (inEdge.GetFeatureId().m_mwmId != edge.GetFeatureId().m_mwmId) + // twins of inEdge.GetFeatureId() are considered as outgoing features. + // In this case that turn candidate angle is invalid and + // should not be used for turn generation. + outgoingTurns.isCandidatesAngleValid = false; + } + outgoingTurns.candidates.emplace_back(angle, ConvertEdgeToSegment(*m_numMwmIds, edge), + highwayClass, isLink); + } + + if (outgoingTurns.isCandidatesAngleValid) + sort(outgoingTurns.candidates.begin(), outgoingTurns.candidates.end(), + base::LessBy(&TurnCandidate::m_angle)); +} + +void IDirectionsEngine::GetEdges(IndexRoadGraph const & graph, + geometry::PointWithAltitude const & currJunction, + bool isCurrJunctionFinish, IRoadGraph::EdgeVector & outgoing, + IRoadGraph::EdgeVector & ingoing) +{ + // Note. If |currJunction| is a finish the outgoing edges + // from finish are not important for turn generation. + if (!isCurrJunctionFinish) + graph.GetOutgoingEdges(currJunction, outgoing); + + graph.GetIngoingEdges(currJunction, ingoing); +} + +void IDirectionsEngine::FillPathSegmentsAndAdjacentEdgesMap( + IndexRoadGraph const & graph, vector const & path, + IRoadGraph::EdgeVector const & routeEdges, base::Cancellable const & cancellable) +{ + size_t const pathSize = path.size(); + CHECK_GREATER(pathSize, 1, ()); + CHECK_EQUAL(routeEdges.size() + 1, pathSize, ()); + // Filling |m_adjacentEdges|. + auto constexpr kInvalidSegId = numeric_limits::max(); + // |startSegId| is a value to keep start segment id of a new instance of LoadedPathSegment. + uint32_t startSegId = kInvalidSegId; + vector prevJunctions; + vector prevSegments; + for (size_t i = 1; i < pathSize; ++i) + { + if (cancellable.IsCancelled()) + return; + + geometry::PointWithAltitude const & prevJunction = path[i - 1]; + geometry::PointWithAltitude const & currJunction = path[i]; + + IRoadGraph::EdgeVector outgoingEdges; + IRoadGraph::EdgeVector ingoingEdges; + bool const isCurrJunctionFinish = (i + 1 == pathSize); + GetEdges(graph, currJunction, isCurrJunctionFinish, outgoingEdges, ingoingEdges); + + Edge const & inEdge = routeEdges[i - 1]; + // Note. |inFeatureId| may be invalid in case of adding fake features. + // It happens for example near starts and a finishes. + FeatureID const & inFeatureId = inEdge.GetFeatureId(); + uint32_t const inSegId = inEdge.GetSegId(); + + if (startSegId == kInvalidSegId) + startSegId = inSegId; + + prevJunctions.push_back(prevJunction); + prevSegments.push_back(ConvertEdgeToSegment(*m_numMwmIds, inEdge)); + + if (!IsJoint(ingoingEdges, outgoingEdges, inEdge, routeEdges[i], isCurrJunctionFinish, + inFeatureId.IsValid())) + { + continue; + } + + CHECK_EQUAL(prevJunctions.size(), + static_cast(abs(static_cast(inSegId - startSegId)) + 1), ()); + + prevJunctions.push_back(currJunction); + + AdjacentEdges adjacentEdges(ingoingEdges.size()); + SegmentRange segmentRange; + GetSegmentRangeAndAdjacentEdges(outgoingEdges, inEdge, startSegId, inSegId, segmentRange, + adjacentEdges.m_outgoingTurns); + + size_t const prevJunctionSize = prevJunctions.size(); + LoadedPathSegment pathSegment; + LoadPathAttributes(segmentRange.GetFeature(), pathSegment); + pathSegment.m_segmentRange = segmentRange; + pathSegment.m_path = move(prevJunctions); + // @TODO(bykoianko) |pathSegment.m_weight| should be filled here. + + // |prevSegments| contains segments which corresponds to road edges between joints. In case of a + // fake edge a fake segment is created. + CHECK_EQUAL(prevSegments.size() + 1, prevJunctionSize, ()); + pathSegment.m_segments = move(prevSegments); + + if (!segmentRange.IsEmpty()) + { + auto const it = m_adjacentEdges.find(segmentRange); + m_adjacentEdges.insert(it, make_pair(segmentRange, move(adjacentEdges))); + } + + m_pathSegments.push_back(move(pathSegment)); + + prevJunctions.clear(); + prevSegments.clear(); + startSegId = kInvalidSegId; + } +} +} // namespace routing diff --git a/routing/directions_engine.hpp b/routing/directions_engine.hpp index 720442c541..24f46f4204 100644 --- a/routing/directions_engine.hpp +++ b/routing/directions_engine.hpp @@ -1,5 +1,6 @@ #pragma once +#include "routing/directions_engine_helpers.hpp" #include "routing/index_road_graph.hpp" #include "routing/road_point.hpp" #include "routing/route.hpp" @@ -7,10 +8,14 @@ #include "traffic/traffic_info.hpp" +#include "indexer/data_source.hpp" + #include "geometry/point_with_altitude.hpp" +#include "base/assert.hpp" #include "base/cancellable.hpp" +#include #include namespace routing @@ -18,6 +23,12 @@ namespace routing class IDirectionsEngine { public: + IDirectionsEngine(DataSource const & dataSource, std::shared_ptr numMwmIds) + : m_dataSource(dataSource), m_numMwmIds(numMwmIds) + { + CHECK(m_numMwmIds, ()); + } + virtual ~IDirectionsEngine() = default; // @TODO(bykoianko) Method Generate() should fill @@ -31,6 +42,31 @@ public: Route::TStreets & streetNames, std::vector & routeGeometry, std::vector & segments) = 0; - virtual void Clear() = 0; + void Clear(); + +protected: + FeaturesLoaderGuard & GetLoader(MwmSet::MwmId const & id); + void LoadPathAttributes(FeatureID const & featureId, LoadedPathSegment & pathSegment); + void GetSegmentRangeAndAdjacentEdges(IRoadGraph::EdgeVector const & outgoingEdges, + Edge const & inEdge, uint32_t startSegId, uint32_t endSegId, + SegmentRange & segmentRange, + turns::TurnCandidates & outgoingTurns); + /// \brief The method gathers sequence of segments according to IsJoint() method + /// and fills |m_adjacentEdges| and |m_pathSegments|. + void FillPathSegmentsAndAdjacentEdgesMap(IndexRoadGraph const & graph, + std::vector const & path, + IRoadGraph::EdgeVector const & routeEdges, + base::Cancellable const & cancellable); + + void GetEdges(IndexRoadGraph const & graph, geometry::PointWithAltitude const & currJunction, + bool isCurrJunctionFinish, IRoadGraph::EdgeVector & outgoing, + IRoadGraph::EdgeVector & ingoing); + + AdjacentEdgesMap m_adjacentEdges; + TUnpackedPathSegments m_pathSegments; + + DataSource const & m_dataSource; + std::shared_ptr m_numMwmIds; + std::unique_ptr m_loader; }; } // namespace routing diff --git a/routing/directions_engine_helpers.cpp b/routing/directions_engine_helpers.cpp new file mode 100644 index 0000000000..aa606df005 --- /dev/null +++ b/routing/directions_engine_helpers.cpp @@ -0,0 +1,110 @@ +#include "routing/directions_engine_helpers.hpp" + +#include "geometry/mercator.hpp" + +namespace routing +{ +bool AdjacentEdges::IsAlmostEqual(AdjacentEdges const & rhs) const +{ + return m_outgoingTurns.IsAlmostEqual(rhs.m_outgoingTurns) && + m_ingoingTurnsCount == rhs.m_ingoingTurnsCount; +} + +RoutingEngineResult::RoutingEngineResult(IRoadGraph::EdgeVector const & routeEdges, + AdjacentEdgesMap const & adjacentEdges, + TUnpackedPathSegments const & pathSegments) + : m_routeEdges(routeEdges) + , m_adjacentEdges(adjacentEdges) + , m_pathSegments(pathSegments) + , m_routeLength(0) +{ + for (auto const & edge : routeEdges) + { + m_routeLength += mercator::DistanceOnEarth(edge.GetStartJunction().GetPoint(), + edge.GetEndJunction().GetPoint()); + } +} + +void RoutingEngineResult::GetPossibleTurns(SegmentRange const & segmentRange, + m2::PointD const & junctionPoint, size_t & ingoingCount, + turns::TurnCandidates & outgoingTurns) const +{ + CHECK(!segmentRange.IsEmpty(), ("SegmentRange presents a fake feature.", + "junctionPoint:", mercator::ToLatLon(junctionPoint))); + + ingoingCount = 0; + outgoingTurns.candidates.clear(); + + auto const adjacentEdges = m_adjacentEdges.find(segmentRange); + if (adjacentEdges == m_adjacentEdges.cend()) + { + ASSERT(false, (segmentRange)); + return; + } + + ingoingCount = adjacentEdges->second.m_ingoingTurnsCount; + outgoingTurns = adjacentEdges->second.m_outgoingTurns; +} + +geometry::PointWithAltitude RoutingEngineResult::GetStartPoint() const +{ + CHECK(!m_routeEdges.empty(), ()); + return m_routeEdges.front().GetStartJunction(); +} + +geometry::PointWithAltitude RoutingEngineResult::GetEndPoint() const +{ + CHECK(!m_routeEdges.empty(), ()); + return m_routeEdges.back().GetEndJunction(); +} + +/// \brief This method should be called for an internal junction of the route with corresponding +/// |ingoingEdges|, |outgoingEdges|, |ingoingRouteEdge| and |outgoingRouteEdge|. +/// \returns false if the junction is an internal point of feature segment and can be considered as +/// a part of LoadedPathSegment and returns true if the junction should be considered as a beginning +/// of a new LoadedPathSegment. +bool IsJoint(IRoadGraph::EdgeVector const & ingoingEdges, + IRoadGraph::EdgeVector const & outgoingEdges, Edge const & ingoingRouteEdge, + Edge const & outgoingRouteEdge, bool isCurrJunctionFinish, bool isInEdgeReal) +{ + // When feature id is changed at a junction this junction should be considered as a joint. + // + // If a feature id is not changed at a junction but the junction has some ingoing or outgoing + // edges with different feature ids, the junction should be considered as a joint. + // + // If a feature id is not changed at a junction and all ingoing and outgoing edges of the junction + // has the same feature id, the junction still may be considered as a joint. It happens in case of + // self intersected features. For example: + // *--Seg3--* + // | | + // Seg4 Seg2 + // | | + // *--Seg0--*--Seg1--* + // The common point of segments 0, 1 and 4 should be considered as a joint. + if (!isInEdgeReal) + return true; + + if (isCurrJunctionFinish) + return true; + + if (ingoingRouteEdge.GetFeatureId() != outgoingRouteEdge.GetFeatureId()) + return true; + + FeatureID const & featureId = ingoingRouteEdge.GetFeatureId(); + uint32_t const segOut = outgoingRouteEdge.GetSegId(); + for (Edge const & e : ingoingEdges) + { + if (e.GetFeatureId() != featureId || abs(static_cast(segOut - e.GetSegId())) != 1) + return true; + } + + uint32_t const segIn = ingoingRouteEdge.GetSegId(); + for (Edge const & e : outgoingEdges) + { + // It's necessary to compare segments for cases when |featureId| is a loop. + if (e.GetFeatureId() != featureId || abs(static_cast(segIn - e.GetSegId())) != 1) + return true; + } + return false; +} +} // namespace routing diff --git a/routing/directions_engine_helpers.hpp b/routing/directions_engine_helpers.hpp new file mode 100644 index 0000000000..e9818c8d5a --- /dev/null +++ b/routing/directions_engine_helpers.hpp @@ -0,0 +1,57 @@ +#pragma once + +#include "routing/loaded_path_segment.hpp" +#include "routing/road_graph.hpp" +#include "routing/routing_result_graph.hpp" +#include "routing/turn_candidate.hpp" +#include "routing/turns.hpp" + +#include "geometry/point2d.hpp" + +namespace routing +{ +struct AdjacentEdges +{ + explicit AdjacentEdges(size_t ingoingTurnsCount = 0) : m_ingoingTurnsCount(ingoingTurnsCount) {} + bool IsAlmostEqual(AdjacentEdges const & rhs) const; + + turns::TurnCandidates m_outgoingTurns; + size_t m_ingoingTurnsCount; +}; + +using AdjacentEdgesMap = std::map; + +class RoutingEngineResult : public turns::IRoutingResult +{ +public: + RoutingEngineResult(IRoadGraph::EdgeVector const & routeEdges, + AdjacentEdgesMap const & adjacentEdges, + TUnpackedPathSegments const & pathSegments); + + // turns::IRoutingResult overrides: + TUnpackedPathSegments const & GetSegments() const override { return m_pathSegments; } + + void GetPossibleTurns(SegmentRange const & segmentRange, m2::PointD const & junctionPoint, + size_t & ingoingCount, + turns::TurnCandidates & outgoingTurns) const override; + + double GetPathLength() const override { return m_routeLength; } + geometry::PointWithAltitude GetStartPoint() const override; + geometry::PointWithAltitude GetEndPoint() const override; + +private: + IRoadGraph::EdgeVector const & m_routeEdges; + AdjacentEdgesMap const & m_adjacentEdges; + TUnpackedPathSegments const & m_pathSegments; + double m_routeLength; +}; + +/// \brief This method should be called for an internal junction of the route with corresponding +/// |ingoingEdges|, |outgoingEdges|, |ingoingRouteEdge| and |outgoingRouteEdge|. +/// \returns false if the junction is an internal point of feature segment and can be considered as +/// a part of LoadedPathSegment and returns true if the junction should be considered as a beginning +/// of a new LoadedPathSegment. +bool IsJoint(IRoadGraph::EdgeVector const & ingoingEdges, + IRoadGraph::EdgeVector const & outgoingEdges, Edge const & ingoingRouteEdge, + Edge const & outgoingRouteEdge, bool isCurrJunctionFinish, bool isInEdgeReal); +} // namespace routing diff --git a/routing/index_router.cpp b/routing/index_router.cpp index 90610f5c7a..30e5b4365f 100644 --- a/routing/index_router.cpp +++ b/routing/index_router.cpp @@ -119,7 +119,7 @@ unique_ptr CreateDirectionsEngine(VehicleType vehicleType, switch (vehicleType) { case VehicleType::Pedestrian: - case VehicleType::Transit: return make_unique(numMwmIds); + case VehicleType::Transit: return make_unique(dataSource, numMwmIds); case VehicleType::Bicycle: // @TODO Bicycle turn generation engine is used now. It's ok for the time being. // But later a special car turn generation engine should be implemented. diff --git a/routing/pedestrian_directions.cpp b/routing/pedestrian_directions.cpp index dce69d0025..6a11fd5b77 100644 --- a/routing/pedestrian_directions.cpp +++ b/routing/pedestrian_directions.cpp @@ -1,11 +1,7 @@ #include "routing/pedestrian_directions.hpp" #include "routing/road_graph.hpp" -#include "routing/routing_helpers.hpp" - -#include "indexer/classificator.hpp" -#include "indexer/feature.hpp" -#include "indexer/ftypes_matcher.hpp" +#include "routing/turns_generator.hpp" #include "base/assert.hpp" #include "base/logging.hpp" @@ -14,28 +10,11 @@ using namespace std; -namespace -{ -bool HasType(uint32_t type, feature::TypesHolder const & types) -{ - for (uint32_t t : types) - { - t = ftypes::BaseChecker::PrepareToMatch(t, 2); - if (type == t) - return true; - } - return false; -} -} // namespace - namespace routing { - -PedestrianDirectionsEngine::PedestrianDirectionsEngine(shared_ptr numMwmIds) - : m_typeSteps(classif().GetTypeByPath({"highway", "steps"})) - , m_typeLiftGate(classif().GetTypeByPath({"barrier", "lift_gate"})) - , m_typeGate(classif().GetTypeByPath({"barrier", "gate"})) - , m_numMwmIds(move(numMwmIds)) +PedestrianDirectionsEngine::PedestrianDirectionsEngine(DataSource const & dataSource, + shared_ptr numMwmIds) + : IDirectionsEngine(dataSource, move(numMwmIds)) { } @@ -46,60 +25,47 @@ bool PedestrianDirectionsEngine::Generate(IndexRoadGraph const & graph, vector & routeGeometry, vector & segments) { + CHECK(m_numMwmIds, ()); + + m_adjacentEdges.clear(); + m_pathSegments.clear(); turns.clear(); streetNames.clear(); segments.clear(); - routeGeometry = path; + routeGeometry.clear(); - // Note. According to Route::IsValid() method route of zero or one point is invalid. if (path.size() <= 1) return false; + size_t const pathSize = path.size(); + vector routeEdges; graph.GetRouteEdges(routeEdges); - CalculateTurns(graph, routeEdges, turns, cancellable); + if (routeEdges.empty()) + return false; + + if (cancellable.IsCancelled()) + return false; + + FillPathSegmentsAndAdjacentEdgesMap(graph, path, routeEdges, cancellable); + + if (cancellable.IsCancelled()) + return false; + + RoutingEngineResult resultGraph(routeEdges, m_adjacentEdges, m_pathSegments); + auto const res = MakeTurnAnnotationPedestrian(resultGraph, *m_numMwmIds, cancellable, + routeGeometry, turns, streetNames, segments); + + if (res != RouterResultCode::NoError) + return false; + + CHECK_EQUAL( + routeGeometry.size(), pathSize, + ("routeGeometry and path have different sizes. routeGeometry size:", routeGeometry.size(), + "path size:", pathSize, "segments size:", segments.size(), "routeEdges size:", + routeEdges.size(), "resultGraph.GetSegments() size:", resultGraph.GetSegments().size())); - graph.GetRouteSegments(segments); return true; } - -void PedestrianDirectionsEngine::CalculateTurns(IndexRoadGraph const & graph, - vector const & routeEdges, - Route::TTurns & turns, - base::Cancellable const & cancellable) const -{ - for (size_t i = 0; i < routeEdges.size(); ++i) - { - if (cancellable.IsCancelled()) - return; - - Edge const & edge = routeEdges[i]; - - feature::TypesHolder types; - graph.GetEdgeTypes(edge, types); - - if (HasType(m_typeSteps, types)) - { - if (edge.IsForward()) - turns.emplace_back(i, turns::PedestrianDirection::Upstairs); - else - turns.emplace_back(i, turns::PedestrianDirection::Downstairs); - } - else - { - graph.GetJunctionTypes(edge.GetStartJunction(), types); - - // @TODO(bykoianko) Turn types Gate and LiftGate should be removed. - if (HasType(m_typeLiftGate, types)) - turns.emplace_back(i, turns::PedestrianDirection::LiftGate); - else if (HasType(m_typeGate, types)) - turns.emplace_back(i, turns::PedestrianDirection::Gate); - } - } - - // direction "arrival" - // (index of last junction is the same as number of edges) - turns.emplace_back(routeEdges.size(), turns::PedestrianDirection::ReachedYourDestination); -} } // namespace routing diff --git a/routing/pedestrian_directions.hpp b/routing/pedestrian_directions.hpp index 4a0a114cd4..fd88c82afb 100644 --- a/routing/pedestrian_directions.hpp +++ b/routing/pedestrian_directions.hpp @@ -1,6 +1,10 @@ #pragma once #include "routing/directions_engine.hpp" +#include "routing/directions_engine_helpers.hpp" +#include "routing/loaded_path_segment.hpp" +#include "routing/routing_result_graph.hpp" +#include "routing/turn_candidate.hpp" #include "routing_common/num_mwm_id.hpp" @@ -11,11 +15,10 @@ namespace routing { - class PedestrianDirectionsEngine : public IDirectionsEngine { public: - PedestrianDirectionsEngine(std::shared_ptr numMwmIds); + PedestrianDirectionsEngine(DataSource const & dataSource, std::shared_ptr numMwmIds); // IDirectionsEngine override: bool Generate(IndexRoadGraph const & graph, std::vector const & path, @@ -23,16 +26,5 @@ public: Route::TStreets & streetNames, std::vector & routeGeometry, std::vector & segments) override; - void Clear() override {} - -private: - void CalculateTurns(IndexRoadGraph const & graph, std::vector const & routeEdges, - Route::TTurns & turnsDir, base::Cancellable const & cancellable) const; - - uint32_t const m_typeSteps; - uint32_t const m_typeLiftGate; - uint32_t const m_typeGate; - std::shared_ptr const m_numMwmIds; }; - } // namespace routing diff --git a/routing/route.cpp b/routing/route.cpp index 58e207158a..9a3483d5ee 100644 --- a/routing/route.cpp +++ b/routing/route.cpp @@ -202,7 +202,8 @@ bool Route::GetNextTurn(double & distanceToTurnMeters, TurnItem & nextTurn) cons // |curIdx| + 1 - 1 is an index of segment to start look for the closest turn. GetClosestTurn(curIdx, curTurn); CHECK_LESS(curIdx, curTurn.m_index, ()); - if (curTurn.m_turn == CarDirection::ReachedYourDestination) + if (curTurn.m_turn == CarDirection::ReachedYourDestination || + curTurn.m_pedestrianTurn == PedestrianDirection::ReachedYourDestination) { nextTurn = TurnItem(); return false; diff --git a/routing/turns.cpp b/routing/turns.cpp index e945ccf2c5..36629c8506 100644 --- a/routing/turns.cpp +++ b/routing/turns.cpp @@ -361,10 +361,9 @@ string DebugPrint(PedestrianDirection const l) switch (l) { case PedestrianDirection::None: return "None"; - case PedestrianDirection::Upstairs: return "Upstairs"; - case PedestrianDirection::Downstairs: return "Downstairs"; - case PedestrianDirection::LiftGate: return "LiftGate"; - case PedestrianDirection::Gate: return "Gate"; + case PedestrianDirection::GoStraight: return "GoStraight"; + case PedestrianDirection::TurnRight: return "TurnRight"; + case PedestrianDirection::TurnLeft: return "TurnLeft"; case PedestrianDirection::ReachedYourDestination: return "ReachedYourDestination"; case PedestrianDirection::Count: // PedestrianDirection::Count should be never used in the code, print it as unknown value diff --git a/routing/turns.hpp b/routing/turns.hpp index 551c97273a..47a0b8e910 100644 --- a/routing/turns.hpp +++ b/routing/turns.hpp @@ -112,12 +112,11 @@ std::string DebugPrint(CarDirection const l); enum class PedestrianDirection { None = 0, - Upstairs, - Downstairs, - LiftGate, - Gate, + GoStraight, + TurnRight, + TurnLeft, ReachedYourDestination, - Count /**< This value is used for internals only. */ + Count /**< This value is used for internals only. */ }; std::string DebugPrint(PedestrianDirection const l); @@ -189,7 +188,7 @@ struct TurnItem } uint32_t m_index; /*!< Index of point on route polyline (number of segment + 1). */ - CarDirection m_turn; /*!< The turn instruction of the TurnItem */ + CarDirection m_turn = CarDirection::None; /*!< The turn instruction of the TurnItem */ std::vector m_lanes; /*!< Lane information on the edge before the turn. */ uint32_t m_exitNum; /*!< Number of exit on roundabout. */ std::string m_sourceName; /*!< Name of the street which the ingoing edge belongs to */ @@ -203,7 +202,7 @@ struct TurnItem * \brief m_pedestrianTurn is type of corresponding direction for a pedestrian, or None * if there is no pedestrian specific direction */ - PedestrianDirection m_pedestrianTurn; + PedestrianDirection m_pedestrianTurn = PedestrianDirection::None; }; std::string DebugPrint(TurnItem const & turnItem); diff --git a/routing/turns_generator.cpp b/routing/turns_generator.cpp index 22bed35503..0f9cb6da28 100644 --- a/routing/turns_generator.cpp +++ b/routing/turns_generator.cpp @@ -310,17 +310,17 @@ bool FixupLaneSet(CarDirection turn, vector & lanes, * These angles should be measured in degrees and should belong to the range [-180; 180]. * The second paramer (angle) shall belong to the range [-180; 180] and is measured in degrees. */ -CarDirection FindDirectionByAngle(vector> const & lowerBounds, - double angle) +template +T FindDirectionByAngle(vector> const & lowerBounds, double angle) { ASSERT_GREATER_OR_EQUAL(angle, -180., (angle)); ASSERT_LESS_OR_EQUAL(angle, 180., (angle)); ASSERT(!lowerBounds.empty(), ()); ASSERT(is_sorted(lowerBounds.cbegin(), lowerBounds.cend(), - [](pair const & p1, pair const & p2) - { - return p1.first > p2.first; - }), ()); + [](pair const & p1, pair const & p2) { + return p1.first > p2.first; + }), + ()); for (auto const & lower : lowerBounds) { @@ -329,7 +329,7 @@ CarDirection FindDirectionByAngle(vector> const & low } ASSERT(false, ("The angle is not covered by the table. angle = ", angle)); - return CarDirection::None; + return T::None; } RoutePointIndex GetFirstOutgoingPointIndex(size_t outgoingSegmentIndex) @@ -526,6 +526,18 @@ void GoStraightCorrection(TurnCandidate const & notRouteCandidate, CarDirection turn.m_turn = turnToSet; } + +// Returns distance in meters between |junctions[start]| and |junctions[end]|. +double CalcRouteDistanceM(vector const & junctions, uint32_t start, + uint32_t end) +{ + double res = 0.0; + + for (uint32_t i = start + 1; i < end; ++i) + res += mercator::DistanceOnEarth(junctions[i - 1].GetPoint(), junctions[i].GetPoint()); + + return res; +} } // namespace namespace routing @@ -574,7 +586,7 @@ RouterResultCode MakeTurnAnnotation(IRoutingResult const & result, NumMwmIds con Route::TTurns & turnsDir, Route::TStreets & streets, vector & segments) { - LOG(LDEBUG, ("Shortest th length:", result.GetPathLength())); + LOG(LDEBUG, ("Shortest path length:", result.GetPathLength())); if (cancellable.IsCancelled()) return RouterResultCode::Cancelled; @@ -660,6 +672,84 @@ RouterResultCode MakeTurnAnnotation(IRoutingResult const & result, NumMwmIds con return RouterResultCode::NoError; } +RouterResultCode MakeTurnAnnotationPedestrian(IRoutingResult const & result, + NumMwmIds const & numMwmIds, + base::Cancellable const & cancellable, + vector & junctions, + Route::TTurns & turnsDir, Route::TStreets & streets, + vector & segments) +{ + LOG(LDEBUG, ("Shortest path length:", result.GetPathLength())); + + if (cancellable.IsCancelled()) + return RouterResultCode::Cancelled; + + size_t skipTurnSegments = 0; + auto const & loadedSegments = result.GetSegments(); + segments.reserve(loadedSegments.size()); + + for (auto loadedSegmentIt = loadedSegments.cbegin(); loadedSegmentIt != loadedSegments.cend(); + ++loadedSegmentIt) + { + CHECK(loadedSegmentIt->IsValid(), ()); + + // Street names contain empty names too for avoiding of freezing of old street name while + // moving along unnamed street. + streets.emplace_back(max(junctions.size(), static_cast(1)) - 1, + loadedSegmentIt->m_name); + + // Turns information. + if (!junctions.empty() && skipTurnSegments == 0) + { + auto const outgoingSegmentDist = distance(loadedSegments.begin(), loadedSegmentIt); + CHECK_GREATER(outgoingSegmentDist, 0, ()); + + auto const outgoingSegmentIndex = static_cast(outgoingSegmentDist); + + TurnItem turnItem; + turnItem.m_index = static_cast(junctions.size() - 1); + GetTurnDirectionPedestrian(result, outgoingSegmentIndex, numMwmIds, turnItem); + + if (turnItem.m_pedestrianTurn != PedestrianDirection::None) + turnsDir.push_back(move(turnItem)); + } + + if (skipTurnSegments > 0) + --skipTurnSegments; + + CHECK_GREATER_OR_EQUAL(loadedSegmentIt->m_path.size(), 2, ()); + + junctions.insert(junctions.end(), + loadedSegmentIt == loadedSegments.cbegin() + ? loadedSegmentIt->m_path.cbegin() + : loadedSegmentIt->m_path.cbegin() + 1, + loadedSegmentIt->m_path.cend()); + + segments.insert(segments.end(), loadedSegmentIt->m_segments.cbegin(), + loadedSegmentIt->m_segments.cend()); + } + + if (junctions.size() == 1) + junctions.push_back(junctions.front()); + + if (junctions.size() < 2) + return RouterResultCode::RouteNotFound; + + junctions.front() = result.GetStartPoint(); + junctions.back() = result.GetEndPoint(); + + turnsDir.emplace_back(TurnItem(base::asserted_cast(junctions.size()) - 1, + PedestrianDirection::ReachedYourDestination)); + + FixupTurnsPedestrian(junctions, turnsDir); + +#ifdef DEBUG + for (auto t : turnsDir) + LOG(LDEBUG, (t.m_pedestrianTurn, ":", t.m_index, t.m_sourceName, "-", t.m_targetName)); +#endif + return RouterResultCode::NoError; +} + double CalculateMercatorDistanceAlongPath(uint32_t startPointIndex, uint32_t endPointIndex, vector const & points) { @@ -685,14 +775,6 @@ void FixupTurns(vector const & junctions, Route::TT // of the enter to the roundabout. If not, roundabout is equal to nullptr. TurnItem * roundabout = nullptr; - auto routeDistanceMeters = [&junctions](uint32_t start, uint32_t end) - { - double res = 0.0; - for (uint32_t i = start + 1; i < end; ++i) - res += mercator::DistanceOnEarth(junctions[i - 1].GetPoint(), junctions[i].GetPoint()); - return res; - }; - for (size_t idx = 0; idx < turnsDir.size(); ) { TurnItem & t = turnsDir[idx]; @@ -727,7 +809,8 @@ void FixupTurns(vector const & junctions, Route::TT // and the current turn (idx). if (idx > 0 && IsStayOnRoad(turnsDir[idx - 1].m_turn) && IsLeftOrRightTurn(turnsDir[idx].m_turn) && - routeDistanceMeters(turnsDir[idx - 1].m_index, turnsDir[idx].m_index) < kMergeDistMeters) + CalcRouteDistanceM(junctions, turnsDir[idx - 1].m_index, turnsDir[idx].m_index) < + kMergeDistMeters) { turnsDir.erase(turnsDir.begin() + idx - 1); continue; @@ -736,7 +819,31 @@ void FixupTurns(vector const & junctions, Route::TT ++idx; } SelectRecommendedLanes(turnsDir); - return; +} + +void FixupTurnsPedestrian(vector const & junctions, + Route::TTurns & turnsDir) +{ + double const kMergeDistMeters = 15.0; + + for (size_t idx = 0; idx < turnsDir.size();) + { + bool const prevStepNoTurn = + idx > 0 && turnsDir[idx - 1].m_pedestrianTurn == PedestrianDirection::GoStraight; + bool const needToTurn = turnsDir[idx].m_pedestrianTurn == PedestrianDirection::TurnLeft || + turnsDir[idx].m_pedestrianTurn == PedestrianDirection::TurnRight; + + // Merging turns which are closer to each other under some circumstance. + if (prevStepNoTurn && needToTurn && + CalcRouteDistanceM(junctions, turnsDir[idx - 1].m_index, turnsDir[idx].m_index) < + kMergeDistMeters) + { + turnsDir.erase(turnsDir.begin() + idx - 1); + continue; + } + + ++idx; + } } void SelectRecommendedLanes(Route::TTurns & turnsDir) @@ -841,7 +948,17 @@ CarDirection IntermediateDirection(const double angle) return FindDirectionByAngle(kLowerBounds, angle); } -/// \returns true iff one of the turn candidates goes along the ingoing route segment. +PedestrianDirection IntermediateDirectionPedestrian(const double angle) +{ + static vector> const kLowerBounds = { + {10.0, PedestrianDirection::TurnRight}, + {-10.0, PedestrianDirection::GoStraight}, + {-180.0, PedestrianDirection::TurnLeft}}; + + return FindDirectionByAngle(kLowerBounds, angle); +} + +/// \returns true if one of the turn candidates goes along the ingoing route segment. bool OneOfTurnCandidatesGoesAlongIngoingSegment(NumMwmIds const & numMwmIds, TurnCandidates const & turnCandidates, TurnInfo const & turnInfo) @@ -1030,10 +1147,61 @@ void GetTurnDirection(IRoutingResult const & result, size_t outgoingSegmentIndex // Note. It's possible that |firstOutgoingSeg| is not contained in |nodes.candidates|. // It may happened if |firstOutgoingSeg| and candidates in |nodes.candidates| are // from different mwms. - } } +void GetTurnDirectionPedestrian(IRoutingResult const & result, size_t outgoingSegmentIndex, + NumMwmIds const & numMwmIds, TurnItem & turn) +{ + auto const & segments = result.GetSegments(); + CHECK_LESS(outgoingSegmentIndex, segments.size(), ()); + CHECK_GREATER(outgoingSegmentIndex, 0, ()); + + TurnInfo turnInfo(segments[outgoingSegmentIndex - 1], segments[outgoingSegmentIndex]); + if (!turnInfo.IsSegmentsValid() || turnInfo.m_ingoing.m_segmentRange.IsEmpty()) + return; + + ASSERT(!turnInfo.m_ingoing.m_path.empty(), ()); + ASSERT(!turnInfo.m_outgoing.m_path.empty(), ()); + ASSERT_LESS(mercator::DistanceOnEarth(turnInfo.m_ingoing.m_path.back().GetPoint(), + turnInfo.m_outgoing.m_path.front().GetPoint()), + kFeaturesNearTurnMeters, ()); + + m2::PointD const junctionPoint = turnInfo.m_ingoing.m_path.back().GetPoint(); + m2::PointD const ingoingPoint = + GetPointForTurn(result, outgoingSegmentIndex, numMwmIds, kMaxIngoingPointsCount, + kMinIngoingDistMeters, false /* forward */); + m2::PointD const outgoingPoint = + GetPointForTurn(result, outgoingSegmentIndex, numMwmIds, kMaxOutgoingPointsCount, + kMinOutgoingDistMeters, true /* forward */); + + double const turnAngle = + base::RadToDeg(PiMinusTwoVectorsAngle(junctionPoint, ingoingPoint, outgoingPoint)); + + turn.m_sourceName = turnInfo.m_ingoing.m_name; + turn.m_targetName = turnInfo.m_outgoing.m_name; + turn.m_pedestrianTurn = PedestrianDirection::None; + + ASSERT_GREATER(turnInfo.m_ingoing.m_path.size(), 1, ()); + TurnCandidates nodes; + size_t ingoingCount = 0; + result.GetPossibleTurns(turnInfo.m_ingoing.m_segmentRange, junctionPoint, ingoingCount, nodes); + if (nodes.isCandidatesAngleValid) + { + ASSERT(is_sorted(nodes.candidates.begin(), nodes.candidates.end(), + base::LessBy(&TurnCandidate::m_angle)), + ("Turn candidates should be sorted by its angle field.")); + } + + if (nodes.candidates.size() == 0) + return; + + turn.m_pedestrianTurn = IntermediateDirectionPedestrian(turnAngle); + + if (turn.m_pedestrianTurn == PedestrianDirection::GoStraight) + turn.m_pedestrianTurn = PedestrianDirection::None; +} + size_t CheckUTurnOnRoute(IRoutingResult const & result, size_t outgoingSegmentIndex, NumMwmIds const & numMwmIds, TurnItem & turn) { diff --git a/routing/turns_generator.hpp b/routing/turns_generator.hpp index 0ced1ef26a..581bccf048 100644 --- a/routing/turns_generator.hpp +++ b/routing/turns_generator.hpp @@ -101,6 +101,13 @@ RouterResultCode MakeTurnAnnotation(IRoutingResult const & result, NumMwmIds con Route::TTurns & turnsDir, Route::TStreets & streets, std::vector & segments); +RouterResultCode MakeTurnAnnotationPedestrian(IRoutingResult const & result, + NumMwmIds const & numMwmIds, + base::Cancellable const & cancellable, + std::vector & points, + Route::TTurns & turnsDir, Route::TStreets & streets, + std::vector & segments); + // Returns the distance in meractor units for the path of points for the range [startPointIndex, endPointIndex]. double CalculateMercatorDistanceAlongPath(uint32_t startPointIndex, uint32_t endPointIndex, std::vector const & points); @@ -110,6 +117,9 @@ double CalculateMercatorDistanceAlongPath(uint32_t startPointIndex, uint32_t end */ void SelectRecommendedLanes(Route::TTurns & turnsDir); void FixupTurns(std::vector const & points, Route::TTurns & turnsDir); +void FixupTurnsPedestrian(std::vector const & junctions, + Route::TTurns & turnsDir); + inline size_t GetFirstSegmentPointIndex(std::pair const & p) { return p.first; } CarDirection InvertDirection(CarDirection dir); @@ -166,6 +176,8 @@ CarDirection GetRoundaboutDirection(bool isIngoingEdgeRoundabout, bool isOutgoin */ void GetTurnDirection(IRoutingResult const & result, size_t outgoingSegmentIndex, NumMwmIds const & numMwmIds, TurnItem & turn); +void GetTurnDirectionPedestrian(IRoutingResult const & result, size_t outgoingSegmentIndex, + NumMwmIds const & numMwmIds, TurnItem & turn); /*! * \brief Finds an U-turn that starts from master segment and returns how many segments it lasts. diff --git a/routing/turns_notification_manager.cpp b/routing/turns_notification_manager.cpp index 5bf577fc87..3a5ff8d7c1 100644 --- a/routing/turns_notification_manager.cpp +++ b/routing/turns_notification_manager.cpp @@ -68,11 +68,18 @@ NotificationManager NotificationManager::CreateNotificationManagerForTesting( } string NotificationManager::GenerateTurnText(uint32_t distanceUnits, uint8_t exitNum, - bool useThenInsteadOfDistance, CarDirection turnDir, + bool useThenInsteadOfDistance, TurnItem const & turn, measurement_utils::Units lengthUnits) const { - Notification const notification(distanceUnits, exitNum, useThenInsteadOfDistance, turnDir, - lengthUnits); + if (turn.m_turn != CarDirection::None && turn.m_pedestrianTurn == PedestrianDirection::None) + { + Notification const notification(distanceUnits, exitNum, useThenInsteadOfDistance, turn.m_turn, + lengthUnits); + return m_getTtsText.GetTurnNotification(notification); + } + + Notification const notification(distanceUnits, exitNum, useThenInsteadOfDistance, + turn.m_pedestrianTurn, lengthUnits); return m_getTtsText.GetTurnNotification(notification); } @@ -110,7 +117,7 @@ void NotificationManager::GenerateTurnNotifications(vector const & } string secondNotification = GenerateTurnText( 0 /* distanceUnits is not used because of "Then" is used */, secondTurn.m_turnItem.m_exitNum, - true, secondTurn.m_turnItem.m_turn, m_settings.GetLengthUnits()); + true, secondTurn.m_turnItem, m_settings.GetLengthUnits()); if (secondNotification.empty()) return; turnNotifications.emplace_back(move(secondNotification)); @@ -161,7 +168,7 @@ string NotificationManager::GenerateFirstTurnSound(TurnItem const & turn, m_settings.RoundByPresetSoundedDistancesUnits(distToPronounceUnits); m_nextTurnNotificationProgress = PronouncedNotification::First; return GenerateTurnText(roundedDistToPronounceUnits, turn.m_exitNum, - false /* useThenInsteadOfDistance */, turn.m_turn, + false /* useThenInsteadOfDistance */, turn, m_settings.GetLengthUnits()); } } @@ -182,7 +189,7 @@ string NotificationManager::GenerateFirstTurnSound(TurnItem const & turn, m_nextTurnNotificationProgress = PronouncedNotification::Second; FastForwardFirstTurnNotification(); return GenerateTurnText(0 /* distanceUnits */, turn.m_exitNum, - false /* useThenInsteadOfDistance */, turn.m_turn, + false /* useThenInsteadOfDistance */, turn, m_settings.GetLengthUnits()); } return string(); diff --git a/routing/turns_notification_manager.hpp b/routing/turns_notification_manager.hpp index 7e8a1f9326..fd7010aed3 100644 --- a/routing/turns_notification_manager.hpp +++ b/routing/turns_notification_manager.hpp @@ -88,8 +88,9 @@ public: CarDirection GetSecondTurnNotification() const { return m_secondTurnNotification; } private: - std::string GenerateTurnText(uint32_t distanceUnits, uint8_t exitNum, bool useThenInsteadOfDistance, - CarDirection turnDir, measurement_utils::Units lengthUnits) const; + std::string GenerateTurnText(uint32_t distanceUnits, uint8_t exitNum, + bool useThenInsteadOfDistance, TurnItem const & turn, + measurement_utils::Units lengthUnits) const; /// Generates turn sound notification for the nearest to the current position turn. std::string GenerateFirstTurnSound(TurnItem const & turn, double distanceToTurnMeters); @@ -147,7 +148,7 @@ private: /// m_secondTurnNotification is a direction of the turn after the closest one /// if an end user shall be informed about it. If not, m_secondTurnNotification == /// TurnDirection::NoTurn - CarDirection m_secondTurnNotification; + CarDirection m_secondTurnNotification = CarDirection::None; /// m_secondTurnNotificationIndex is an index of the closest turn on the route polyline /// where m_secondTurnNotification was set to true last time for a turn. diff --git a/routing/turns_sound_settings.cpp b/routing/turns_sound_settings.cpp index 90a9941840..ec17b49e8a 100644 --- a/routing/turns_sound_settings.cpp +++ b/routing/turns_sound_settings.cpp @@ -113,6 +113,7 @@ string DebugPrint(Notification const & notification) << ", m_exitNum == " << notification.m_exitNum << ", m_useThenInsteadOfDistance == " << notification.m_useThenInsteadOfDistance << ", m_turnDir == " << DebugPrint(notification.m_turnDir) + << ", m_turnDirPedestrian == " << DebugPrint(notification.m_turnDirPedestrian) << ", m_lengthUnits == " << DebugPrint(notification.m_lengthUnits) << " ]" << endl; return out.str(); } diff --git a/routing/turns_sound_settings.hpp b/routing/turns_sound_settings.hpp index c05071decc..c2b9a32a53 100644 --- a/routing/turns_sound_settings.hpp +++ b/routing/turns_sound_settings.hpp @@ -136,7 +136,8 @@ struct Notification /// if m_useThenInsteadOfDistance == true the m_distanceUnits is ignored. /// The word "Then" shall be pronounced intead of the distance. bool m_useThenInsteadOfDistance; - CarDirection m_turnDir; + CarDirection m_turnDir = CarDirection::None; + PedestrianDirection m_turnDirPedestrian = PedestrianDirection::None; measurement_utils::Units m_lengthUnits; Notification(uint32_t distanceUnits, uint8_t exitNum, bool useThenInsteadOfDistance, @@ -148,11 +149,28 @@ struct Notification , m_lengthUnits(lengthUnits) { } + + Notification(uint32_t distanceUnits, uint8_t exitNum, bool useThenInsteadOfDistance, + PedestrianDirection turnDirPedestrian, measurement_utils::Units lengthUnits) + : m_distanceUnits(distanceUnits) + , m_exitNum(exitNum) + , m_useThenInsteadOfDistance(useThenInsteadOfDistance) + , m_turnDirPedestrian(turnDirPedestrian) + , m_lengthUnits(lengthUnits) + { + } + bool operator==(Notification const & rhv) const { return m_distanceUnits == rhv.m_distanceUnits && m_exitNum == rhv.m_exitNum && m_useThenInsteadOfDistance == rhv.m_useThenInsteadOfDistance && - m_turnDir == rhv.m_turnDir && m_lengthUnits == rhv.m_lengthUnits; + m_turnDir == rhv.m_turnDir && m_turnDirPedestrian == rhv.m_turnDirPedestrian && + m_lengthUnits == rhv.m_lengthUnits; + } + + bool IsPedestrianNotification() const + { + return m_turnDir == CarDirection::None && m_turnDirPedestrian != PedestrianDirection::None; } };