From 911e9cb9b8ec6d0377f986cea7a64a23bb955808 Mon Sep 17 00:00:00 2001 From: Vladimir Byko-Ianko Date: Tue, 19 May 2015 13:44:57 +0300 Subject: [PATCH] The turn functionality is moved to the turn_generator unit. --- indexer/ftypes_matcher.hpp | 3 +- integration_tests/osrm_route_test.cpp | 26 +- integration_tests/osrm_test_tools.cpp | 8 + integration_tests/osrm_test_tools.hpp | 2 + routing/osrm_router.cpp | 370 ++--------- routing/osrm_router.hpp | 34 +- routing/route.cpp | 1 + .../routing_tests/turns_generator_test.cpp | 2 +- routing/turns.cpp | 4 +- routing/turns.hpp | 44 +- routing/turns_generator.cpp | 580 +++++++++++++++--- routing/turns_generator.hpp | 77 ++- 12 files changed, 644 insertions(+), 507 deletions(-) diff --git a/indexer/ftypes_matcher.hpp b/indexer/ftypes_matcher.hpp index ac3a918a47..89e8f09a0f 100644 --- a/indexer/ftypes_matcher.hpp +++ b/indexer/ftypes_matcher.hpp @@ -141,7 +141,8 @@ bool IsTypeConformed(uint32_t type, vector const & path); // The enum values follow from the biggest roads (Trunk) to the smallest ones (Service). enum class HighwayClass { - None = 0, + Undefined = 0, // There has not been any attempt of calculating HighwayClass. + None, // There was an attempt of calculating HighwayClass but it was not successful. Trunk, Primary, Secondary, diff --git a/integration_tests/osrm_route_test.cpp b/integration_tests/osrm_route_test.cpp index e371e79bf4..40ce082ea6 100644 --- a/integration_tests/osrm_route_test.cpp +++ b/integration_tests/osrm_route_test.cpp @@ -133,4 +133,28 @@ namespace integration::GetAllMaps(), MercatorBounds::FromLatLon(45.36078, 36.60866), {0., 0.}, MercatorBounds::FromLatLon(45.38053, 36.73226), 15600.); } -} + + UNIT_TEST(RussiaSmolenskRussiaMoscowTimeTest) + { + TRouteResult const routeResult = integration::CalculateRoute( + integration::GetAllMaps(), {32.05489, 65.78463}, {0., 0.}, {37.60169, 67.45807}); + + Route const & route = *routeResult.first; + OsrmRouter::ResultCode const result = routeResult.second; + TEST_EQUAL(result, OsrmRouter::NoError, ()); + + integration::TestRouteTime(route, 20237.); + } + + UNIT_TEST(RussiaMoscowLenigradskiy39GeroevPanfilovtsev22TimeTest) + { + TRouteResult const routeResult = integration::CalculateRoute( + integration::GetAllMaps(), {37.53804, 67.53647}, {0., 0.}, {37.40990, 67.64474}); + + Route const & route = *routeResult.first; + OsrmRouter::ResultCode const result = routeResult.second; + TEST_EQUAL(result, OsrmRouter::NoError, ()); + + integration::TestRouteTime(route, 1092.); + } +} // namespace diff --git a/integration_tests/osrm_test_tools.cpp b/integration_tests/osrm_test_tools.cpp index 4b3a4d5de3..3aa68d86f3 100644 --- a/integration_tests/osrm_test_tools.cpp +++ b/integration_tests/osrm_test_tools.cpp @@ -164,6 +164,14 @@ namespace integration TEST_GREATER_OR_EQUAL(routeLength + delta, expectedRouteLength, ("Route length test failed. Expected:", expectedRouteLength, "have:", routeLength)); } + void TestRouteTime(Route const & route, double expectedRouteTimeSeconds, double relativeError) + { + double const delta = expectedRouteTimeSeconds * relativeError; + double const routeTime = route.GetAllTime(); + TEST_LESS_OR_EQUAL(routeTime - delta, expectedRouteTimeSeconds, ()); + TEST_GREATER_OR_EQUAL(routeTime + delta, expectedRouteTimeSeconds, ()); + } + void CalculateRouteAndTestRouteLength(OsrmRouterComponents const & routerComponents, m2::PointD const & startPoint, m2::PointD const & startDirection, diff --git a/integration_tests/osrm_test_tools.hpp b/integration_tests/osrm_test_tools.hpp index c946305856..f874f101e3 100644 --- a/integration_tests/osrm_test_tools.hpp +++ b/integration_tests/osrm_test_tools.hpp @@ -60,6 +60,8 @@ namespace integration /// && expectedRouteLength + expectedRouteLength * relativeError >= route->GetDistance() void TestRouteLength(Route const & route, double expectedRouteLength, double relativeError = 0.01); + void TestRouteTime(Route const & route, double expectedRouteTimeSeconds, + double relativeError = 0.01); void CalculateRouteAndTestRouteLength(OsrmRouterComponents const & routerComponents, m2::PointD const & startPoint, m2::PointD const & startDirection, diff --git a/routing/osrm_router.cpp b/routing/osrm_router.cpp index 78d9fc37e8..bab0b403d9 100644 --- a/routing/osrm_router.cpp +++ b/routing/osrm_router.cpp @@ -14,7 +14,6 @@ #include "indexer/mercator.hpp" #include "indexer/index.hpp" #include "indexer/scales.hpp" -#include "indexer/search_string_utils.hpp" #include "coding/reader_wrapper.hpp" @@ -41,7 +40,6 @@ namespace routing { size_t constexpr kMaxNodeCandidatesCount = 10; double constexpr kFeatureFindingRectSideRadiusMeters = 1000.0; -double constexpr kTimeOverhead = 1.; double constexpr kFeaturesNearTurnM = 3.0; // TODO (ldragunov) Switch all RawRouteData and incapsulate to own omim types. @@ -382,12 +380,6 @@ public: res.end()); } }; - -size_t GetLastSegmentPointIndex(pair const & p) -{ - ASSERT_GREATER(p.second, 0, ()); - return p.second - 1; -} } // namespace OsrmRouter::OsrmRouter(Index const * index, TCountryFileFn const & fn, @@ -535,9 +527,8 @@ OsrmRouter::ResultCode OsrmRouter::CalculateRoute(m2::PointD const & startPoint, { if (finalPoint != m_CachedTargetPoint) { - ResultCode const code = - FindPhantomNodes(finalPoint, m2::PointD::Zero(), - m_CachedTargetTask, kMaxNodeCandidatesCount, targetMapping); + ResultCode const code = FindPhantomNodes(finalPoint, m2::PointD::Zero(), + m_CachedTargetTask, kMaxNodeCandidatesCount, targetMapping); if (code != NoError) return code; m_CachedTargetPoint = finalPoint; @@ -608,32 +599,31 @@ OsrmRouter::ResultCode OsrmRouter::CalculateRoute(m2::PointD const & startPoint, } } -m2::PointD OsrmRouter::GetPointForTurnAngle(OsrmMappingTypes::FtSeg const & seg, - FeatureType const & ft, m2::PointD const & turnPnt, - size_t (*GetPndInd)(const size_t, const size_t, const size_t)) const +IRouter::ResultCode OsrmRouter::FindPhantomNodes(m2::PointD const & point, + m2::PointD const & direction, + TFeatureGraphNodeVec & res, size_t maxCount, + TRoutingMappingPtr const & mapping) { - const size_t maxPntsNum = 7; - const double maxDistMeter = 300.f; - double curDist = 0.f; - m2::PointD pnt = turnPnt, nextPnt; + Point2PhantomNode getter(mapping->m_segMapping, m_pIndex, direction); + getter.SetPoint(point); - const size_t segDist = abs(seg.m_pointEnd - seg.m_pointStart); - ASSERT_LESS(segDist, ft.GetPointsCount(), ()); - const size_t usedFtPntNum = min(maxPntsNum, segDist); + m_pIndex->ForEachInRectForMWM(getter, MercatorBounds::RectByCenterXYAndSizeInMeters( + point, kFeatureFindingRectSideRadiusMeters), + scales::GetUpperScale(), mapping->GetMwmId()); - for (size_t i = 1; i <= usedFtPntNum; ++i) - { - nextPnt = ft.GetPoint(GetPndInd(seg.m_pointStart, seg.m_pointEnd, i)); - curDist += MercatorBounds::DistanceOnEarth(pnt, nextPnt); - if (curDist > maxDistMeter) - { - return nextPnt; - } - pnt = nextPnt; - } - return nextPnt; + if (!getter.HasCandidates()) + return RouteNotFound; + + getter.MakeResult(res, maxCount, mapping->GetName()); + return NoError; } +// @todo(vbykoianko) This method shall to be refactored. It shall be split into several +// methods. All the functionality shall be moved to the turns_generator unit. + +// @todo(vbykoianko) For the time being MakeTurnAnnotation generates the turn annotation +// and the route polyline at the same time. It is better to generate it separately +// to be able to use the route without turn annotation. OsrmRouter::ResultCode OsrmRouter::MakeTurnAnnotation(RawRoutingResult const & routingResult, TRoutingMappingPtr const & mapping, vector & points, @@ -641,9 +631,11 @@ OsrmRouter::ResultCode OsrmRouter::MakeTurnAnnotation(RawRoutingResult const & r Route::TimesT & times, turns::TurnsGeomT & turnsGeom) { - typedef OsrmMappingTypes::FtSeg SegT; - SegT const & segBegin = routingResult.sourceEdge.segment; - SegT const & segEnd = routingResult.targetEdge.segment; + ASSERT(mapping, ()); + + typedef OsrmMappingTypes::FtSeg TSeg; + TSeg const & segBegin = routingResult.sourceEdge.segment; + TSeg const & segEnd = routingResult.targetEdge.segment; double estimateTime = 0; @@ -654,7 +646,9 @@ OsrmRouter::ResultCode OsrmRouter::MakeTurnAnnotation(RawRoutingResult const & r #ifdef _DEBUG size_t lastIdx = 0; #endif + for (auto const & segment : routingResult.unpackedPathSegments) + //for (auto i : osrm::irange(0, routingResult.m_routePath.unpacked_path_segments.size())) { INTERRUPT_WHEN_CANCELLED(); @@ -669,33 +663,40 @@ OsrmRouter::ResultCode OsrmRouter::MakeTurnAnnotation(RawRoutingResult const & r TurnItem t; t.m_index = points.size() - 1; - GetTurnDirection(segment[j - 1], - segment[j], mapping, t); - if (t.m_turn != turns::TurnDirection::NoTurn) - { - // adding lane info - t.m_lanes = turns::GetLanesInfo(segment[j - 1].node, *mapping, - GetLastSegmentPointIndex, *m_pIndex); - turnsDir.push_back(move(t)); - } + turns::TurnInfo turnInfo(*mapping, + segment[j - 1].node, + segment[j].node); + turns::GetTurnDirection(*m_pIndex, turnInfo, t); + + // ETA information. + // Osrm multiples seconds to 10, so we need to divide it back. + double const nodeTimeSeconds = path_data.segmentWeight / 10.0; - // osrm multiple seconds to 10, so we need to divide it back - double const sTime = kTimeOverhead * path_data.segmentWeight / 10.0; #ifdef _DEBUG double dist = 0.0; for (size_t l = lastIdx + 1; l < points.size(); ++l) dist += MercatorBounds::DistanceOnEarth(points[l - 1], points[l]); - LOG(LDEBUG, ("Speed:", 3.6 * dist / sTime, "kmph; Dist:", dist, "Time:", sTime, "s", lastIdx, "e", points.size())); + LOG(LDEBUG, ("Speed:", 3.6 * dist / nodeTimeSeconds, "kmph; Dist:", dist, "Time:", + nodeTimeSeconds, "s", lastIdx, "e", points.size())); lastIdx = points.size(); #endif - estimateTime += sTime; + estimateTime += nodeTimeSeconds; times.push_back(Route::TimeItemT(points.size(), estimateTime)); + + // Lane information. + if (t.m_turn != turns::TurnDirection::NoTurn) + { + t.m_lanes = + turns::GetLanesInfo(segment[j - 1].node, + *mapping, turns::GetLastSegmentPointIndex, *m_pIndex); + turnsDir.push_back(move(t)); + } } - buffer_vector buffer; + buffer_vector buffer; mapping->m_segMapping.ForEachFtSeg(path_data.node, MakeBackInsertFunctor(buffer)); - auto FindIntersectingSeg = [&buffer] (SegT const & seg) -> size_t + auto FindIntersectingSeg = [&buffer] (TSeg const & seg) -> size_t { ASSERT(seg.IsValid(), ()); auto const it = find_if(buffer.begin(), buffer.end(), [&seg] (OsrmMappingTypes::FtSeg const & s) @@ -726,7 +727,7 @@ OsrmRouter::ResultCode OsrmRouter::MakeTurnAnnotation(RawRoutingResult const & r for (size_t k = startK; k < endK; ++k) { - SegT const & seg = buffer[k]; + TSeg const & seg = buffer[k]; FeatureType ft; Index::FeaturesLoaderGuard loader(*m_pIndex, mapping->GetMwmId()); @@ -804,269 +805,4 @@ OsrmRouter::ResultCode OsrmRouter::MakeTurnAnnotation(RawRoutingResult const & r LOG(LDEBUG, ("Estimate time:", estimateTime, "s")); return OsrmRouter::NoError; } - -NodeID OsrmRouter::GetTurnTargetNode(NodeID src, NodeID trg, QueryEdge::EdgeData const & edgeData, TRoutingMappingPtr const & routingMapping) -{ - ASSERT_NOT_EQUAL(src, SPECIAL_NODEID, ()); - ASSERT_NOT_EQUAL(trg, SPECIAL_NODEID, ()); - if (!edgeData.shortcut) - return trg; - - ASSERT_LESS(edgeData.id, routingMapping->m_dataFacade.GetNumberOfNodes(), ()); - EdgeID edge = SPECIAL_EDGEID; - QueryEdge::EdgeData d; - for (EdgeID e : routingMapping->m_dataFacade.GetAdjacentEdgeRange(edgeData.id)) - { - if (routingMapping->m_dataFacade.GetTarget(e) == src ) - { - d = routingMapping->m_dataFacade.GetEdgeData(e, edgeData.id); - if (d.backward) - { - edge = e; - break; - } - } - } - - if (edge == SPECIAL_EDGEID) - { - for (EdgeID e : routingMapping->m_dataFacade.GetAdjacentEdgeRange(src)) - { - if (routingMapping->m_dataFacade.GetTarget(e) == edgeData.id) - { - d = routingMapping->m_dataFacade.GetEdgeData(e, src); - if (d.forward) - { - edge = e; - break; - } - } - } - } - ASSERT_NOT_EQUAL(edge, SPECIAL_EDGEID, ()); - - if (d.shortcut) - return GetTurnTargetNode(src, edgeData.id, d, routingMapping); - - return edgeData.id; -} - -void OsrmRouter::GetPossibleTurns(NodeID node, m2::PointD const & p1, m2::PointD const & p, - TRoutingMappingPtr const & routingMapping, - turns::TTurnCandidates & candidates) -{ - ASSERT(routingMapping.get(), ()); - for (EdgeID e : routingMapping->m_dataFacade.GetAdjacentEdgeRange(node)) - { - QueryEdge::EdgeData const data = routingMapping->m_dataFacade.GetEdgeData(e, node); - if (!data.forward) - continue; - - NodeID trg = GetTurnTargetNode(node, routingMapping->m_dataFacade.GetTarget(e), data, routingMapping); - ASSERT_NOT_EQUAL(trg, SPECIAL_NODEID, ()); - - auto const range = routingMapping->m_segMapping.GetSegmentsRange(trg); - OsrmMappingTypes::FtSeg seg; - routingMapping->m_segMapping.GetSegmentByIndex(range.first, seg); - if (!seg.IsValid()) - continue; - - FeatureType ft; - Index::FeaturesLoaderGuard loader(*m_pIndex, routingMapping->GetMwmId()); - loader.GetFeature(seg.m_fid, ft); - ft.ParseGeometry(FeatureType::BEST_GEOMETRY); - - m2::PointD const p2 = ft.GetPoint(seg.m_pointStart < seg.m_pointEnd ? seg.m_pointStart + 1 : seg.m_pointStart - 1); - ASSERT_LESS(MercatorBounds::DistanceOnEarth(p, ft.GetPoint(seg.m_pointStart)), - kFeaturesNearTurnM, ()); - - double const a = my::RadToDeg(ang::TwoVectorsAngle(p, p1, p2)); - - candidates.emplace_back(a, trg); - } - - sort(candidates.begin(), candidates.end(), - [](turns::TurnCandidate const & t1, turns::TurnCandidate const & t2) - { - return t1.m_node < t2.m_node; - }); - - auto const last = unique(candidates.begin(), candidates.end(), - [](turns::TurnCandidate const & t1, turns::TurnCandidate const & t2) - { - return t1.m_node == t2.m_node; - }); - candidates.erase(last, candidates.end()); - - sort(candidates.begin(), candidates.end(), - [](turns::TurnCandidate const & t1, turns::TurnCandidate const & t2) - { - return t1.m_angle < t2.m_angle; - }); -} - -void OsrmRouter::GetTurnGeometry(m2::PointD const & junctionPoint, m2::PointD const & ingoingPoint, - GeomTurnCandidateT & candidates, - TRoutingMappingPtr const & mapping) const -{ - ASSERT(mapping.get(), ()); - Point2Geometry getter(junctionPoint, ingoingPoint, candidates); - m_pIndex->ForEachInRectForMWM( - getter, MercatorBounds::RectByCenterXYAndSizeInMeters(junctionPoint, kFeaturesNearTurnM), - scales::GetUpperScale(), mapping->GetMwmId()); -} - -size_t OsrmRouter::NumberOfIngoingAndOutgoingSegments(m2::PointD const & junctionPoint, - m2::PointD const & ingoingPointOneSegment, - TRoutingMappingPtr const & mapping) const -{ - ASSERT(mapping.get(), ()); - - GeomTurnCandidateT geoNodes; - GetTurnGeometry(junctionPoint, ingoingPointOneSegment, geoNodes, mapping); - return geoNodes.size(); -} - -// @todo(vbykoianko) Move this method and all dependencies to turns_generator.cpp -void OsrmRouter::GetTurnDirection(RawPathData const & node1, RawPathData const & node2, - TRoutingMappingPtr const & routingMapping, TurnItem & turn) -{ - ASSERT(routingMapping.get(), ()); - OsrmMappingTypes::FtSeg const seg1 = - turns::GetSegment(node1.node, *routingMapping, GetLastSegmentPointIndex); - OsrmMappingTypes::FtSeg const seg2 = - turns::GetSegment(node2.node, *routingMapping, turns::GetFirstSegmentPointIndex); - - if (!seg1.IsValid() || !seg2.IsValid()) - { - LOG(LWARNING, ("Some turns can't load geometry")); - turn.m_turn = turns::TurnDirection::NoTurn; - return; - } - - FeatureType ft1, ft2; - Index::FeaturesLoaderGuard loader1(*m_pIndex, routingMapping->GetMwmId()); - Index::FeaturesLoaderGuard loader2(*m_pIndex, routingMapping->GetMwmId()); - loader1.GetFeature(seg1.m_fid, ft1); - loader2.GetFeature(seg2.m_fid, ft2); - - ft1.ParseGeometry(FeatureType::BEST_GEOMETRY); - ft2.ParseGeometry(FeatureType::BEST_GEOMETRY); - - ASSERT_LESS(MercatorBounds::DistanceOnEarth(ft1.GetPoint(seg1.m_pointEnd), - ft2.GetPoint(seg2.m_pointStart)), - kFeaturesNearTurnM, ()); - - m2::PointD const p = ft1.GetPoint(seg1.m_pointEnd); - m2::PointD const p1 = GetPointForTurnAngle(seg1, ft1, p, - [](const size_t start, const size_t end, const size_t i) - { - return end > start ? end - i : end + i; - }); - m2::PointD const p2 = GetPointForTurnAngle(seg2, ft2, p, - [](const size_t start, const size_t end, const size_t i) - { - return end > start ? start + i : start - i; - }); - double const a = my::RadToDeg(ang::TwoVectorsAngle(p, p1, p2)); - - m2::PointD const p1OneSeg = ft1.GetPoint(seg1.m_pointStart < seg1.m_pointEnd ? seg1.m_pointEnd - 1 : seg1.m_pointEnd + 1); - turns::TTurnCandidates nodes; - GetPossibleTurns(node1.node, p1OneSeg, p, routingMapping, nodes); - - turn.m_turn = turns::TurnDirection::NoTurn; - size_t const nodesSz = nodes.size(); - bool const hasMultiTurns = (nodesSz >= 2); - - if (nodesSz == 0) - { - return; - } - - if (nodes.front().m_node == node2.node) - turn.m_turn = turns::MostRightDirection(a); - else if (nodes.back().m_node == node2.node) - turn.m_turn = turns::MostLeftDirection(a); - else - turn.m_turn = turns::IntermediateDirection(a); - - bool const isIngoingEdgeRoundabout = ftypes::IsRoundAboutChecker::Instance()(ft1); - bool const isOutgoingEdgeRoundabout = ftypes::IsRoundAboutChecker::Instance()(ft2); - - if (isIngoingEdgeRoundabout || isOutgoingEdgeRoundabout) - { - turn.m_turn = turns::GetRoundaboutDirection(isIngoingEdgeRoundabout, isOutgoingEdgeRoundabout, - hasMultiTurns); - return; - } - - turn.m_keepAnyway = (!ftypes::IsLinkChecker::Instance()(ft1) - && ftypes::IsLinkChecker::Instance()(ft2)); - - // get names - string name1, name2; - { - ft1.GetName(FeatureType::DEFAULT_LANG, turn.m_sourceName); - ft2.GetName(FeatureType::DEFAULT_LANG, turn.m_targetName); - - search::GetStreetNameAsKey(turn.m_sourceName, name1); - search::GetStreetNameAsKey(turn.m_targetName, name2); - } - - ftypes::HighwayClass const highwayClass1 = ftypes::GetHighwayClass(ft1); - ftypes::HighwayClass const highwayClass2 = ftypes::GetHighwayClass(ft2); - if (!turn.m_keepAnyway && - !turns::HighwayClassFilter(highwayClass1, highwayClass2, node2.node, turn.m_turn, nodes, - *routingMapping, *m_pIndex)) - { - turn.m_turn = turns::TurnDirection::NoTurn; - return; - } - - bool const isGoStraightOrSlightTurn = - turns::IsGoStraightOrSlightTurn(turns::IntermediateDirection(my::RadToDeg(ang::TwoVectorsAngle(p,p1OneSeg, p2)))); - // The code below is resposible for cases when there is only one way to leave the junction. - // Such junction has to be kept as a turn when - // * it's not a slight turn and it has ingoing edges (one or more); - // * it's an entrance to a roundabout; - if (!hasMultiTurns && - (isGoStraightOrSlightTurn || NumberOfIngoingAndOutgoingSegments(p, p1OneSeg, routingMapping) <= 2) && - !turns::CheckRoundaboutEntrance(isIngoingEdgeRoundabout, isOutgoingEdgeRoundabout)) - { - turn.m_turn = turns::TurnDirection::NoTurn; - return; - } - - if (turn.m_turn == turns::TurnDirection::GoStraight) - { - if (!hasMultiTurns) - turn.m_turn = turns::TurnDirection::NoTurn; - - return; - } - - // @todo(vbykoianko) Checking if it's a uturn or not shall be moved to FindDirectionByAngle. - if (turn.m_turn == turns::TurnDirection::NoTurn) - turn.m_turn = turns::TurnDirection::UTurn; -} - -IRouter::ResultCode OsrmRouter::FindPhantomNodes(m2::PointD const & point, - m2::PointD const & direction, - TFeatureGraphNodeVec & res, size_t maxCount, - TRoutingMappingPtr const & mapping) -{ - Point2PhantomNode getter(mapping->m_segMapping, m_pIndex, direction); - getter.SetPoint(point); - - m_pIndex->ForEachInRectForMWM(getter, MercatorBounds::RectByCenterXYAndSizeInMeters( - point, kFeatureFindingRectSideRadiusMeters), - scales::GetUpperScale(), mapping->GetMwmId()); - - if (!getter.HasCandidates()) - return RouteNotFound; - - getter.MakeResult(res, maxCount, mapping->GetName()); - return NoError; -} - -} +} // namespace routing diff --git a/routing/osrm_router.hpp b/routing/osrm_router.hpp index b100c485ef..91f0db2ad7 100644 --- a/routing/osrm_router.hpp +++ b/routing/osrm_router.hpp @@ -39,6 +39,7 @@ class OsrmRouter : public IRouter { public: typedef vector GeomTurnCandidateT; + typedef vector NodeIdVectorT; OsrmRouter(Index const * index, TCountryFileFn const & fn, TRoutingVisualizerFn routingVisualization = nullptr); @@ -101,39 +102,6 @@ private: */ ResultCode MakeRouteFromCrossesPath(TCheckedPath const & path, Route & route); - NodeID GetTurnTargetNode(NodeID src, NodeID trg, QueryEdge::EdgeData const & edgeData, TRoutingMappingPtr const & routingMapping); - void GetPossibleTurns(NodeID node, m2::PointD const & p1, m2::PointD const & p, - TRoutingMappingPtr const & routingMapping, - turns::TTurnCandidates & candidates); - void GetTurnDirection(RawPathData const & node1, RawPathData const & node2, - TRoutingMappingPtr const & routingMapping, TurnItem & turn); - m2::PointD GetPointForTurnAngle(OsrmMappingTypes::FtSeg const & seg, - FeatureType const & ft, m2::PointD const & turnPnt, - size_t (*GetPndInd)(const size_t, const size_t, const size_t)) const; - /*! - * \param junctionPoint is a point of the junction. - * \param ingoingPointOneSegment is a point one segment before the junction along the route. - * \param mapping is a route mapping. - * \return number of all the segments which joins junctionPoint. That means - * the number of ingoing segments plus the number of outgoing segments. - * \warning NumberOfIngoingAndOutgoingSegments should be used carefully because - * it's a time-consuming function. - * \warning In multilevel crossroads there is an insignificant possibility that the returned value - * contains redundant segments of roads of different levels. - */ - size_t NumberOfIngoingAndOutgoingSegments(m2::PointD const & junctionPoint, - m2::PointD const & ingoingPointOneSegment, - TRoutingMappingPtr const & mapping) const; - /*! - * \brief GetTurnGeometry looks for all the road network edges near ingoingPoint. - * GetTurnGeometry fills candidates with angles of all the incoming and outgoint segments. - * \warning GetTurnGeometry should be used carefully because it's a time-consuming function. - * \warning In multilevel crossroads there is an insignificant possibility that candidates - * is filled with redundant segments of roads of different levels. - */ - void GetTurnGeometry(m2::PointD const & junctionPoint, m2::PointD const & ingoingPoint, - GeomTurnCandidateT & candidates, TRoutingMappingPtr const & mapping) const; - Index const * m_pIndex; TFeatureGraphNodeVec m_CachedTargetTask; diff --git a/routing/route.cpp b/routing/route.cpp index eca1f0f0e0..1eea5e7d2b 100644 --- a/routing/route.cpp +++ b/routing/route.cpp @@ -87,6 +87,7 @@ double Route::GetCurrentDistanceFromBegin() const double Route::GetCurrentDistanceToEnd() const { ASSERT(m_current.IsValid(), ()); + ASSERT_LESS(m_current.m_ind, m_segDistance.size(), ()); return (m_segDistance.back() - m_segDistance[m_current.m_ind] + MercatorBounds::DistanceOnEarth(m_current.m_pt, m_poly.GetPoint(m_current.m_ind + 1))); diff --git a/routing/routing_tests/turns_generator_test.cpp b/routing/routing_tests/turns_generator_test.cpp index 5e0bd1b40c..fdc586402b 100644 --- a/routing/routing_tests/turns_generator_test.cpp +++ b/routing/routing_tests/turns_generator_test.cpp @@ -298,7 +298,7 @@ UNIT_TEST(TestGetRoundaboutDirection) { // The signature of GetRoundaboutDirection function is // GetRoundaboutDirection(bool isIngoingEdgeRoundabout, bool isOutgoingEdgeRoundabout, bool - // isJunctionOfSeveralTurns) + // isMultiTurnJunction) TEST_EQUAL(GetRoundaboutDirection(true, true, true), TurnDirection::StayOnRoundAbout, ()); TEST_EQUAL(GetRoundaboutDirection(true, true, false), TurnDirection::NoTurn, ()); TEST_EQUAL(GetRoundaboutDirection(false, true, false), TurnDirection::EnterRoundAbout, ()); diff --git a/routing/turns.cpp b/routing/turns.cpp index c8df840936..271ade6865 100644 --- a/routing/turns.cpp +++ b/routing/turns.cpp @@ -8,8 +8,8 @@ namespace { using namespace routing::turns; -// The order is important. Starting with the most frequent tokens according to -// taginfo.openstreetmap.org to minimize the number of comparisons in ParseSingleLane(). +/// The order is important. Starting with the most frequent tokens according to +/// taginfo.openstreetmap.org we minimize the number of the comparisons in ParseSingleLane(). array, static_cast(LaneWay::Count)> const g_laneWayNames = { {{LaneWay::Through, "through"}, {LaneWay::Left, "left"}, diff --git a/routing/turns.hpp b/routing/turns.hpp index 77db7774ad..04f899bc11 100644 --- a/routing/turns.hpp +++ b/routing/turns.hpp @@ -5,25 +5,24 @@ #include "3party/osrm/osrm-backend/typedefs.h" #include "std/initializer_list.hpp" -#include "std/iostream.hpp" #include "std/string.hpp" #include "std/vector.hpp" namespace routing { - namespace turns { -// @todo(vbykoianko) It's a good idea to gather all the turns information into one entity. -// For the time being several separate entities reflect turn information. Like Route::TurnsT or -// turns::TurnsGeomT +/// @todo(vbykoianko) It's a good idea to gather all the turns information into one entity. +/// For the time being several separate entities reflect the turn information. Like Route::TurnsT or +/// turns::TurnsGeomT -// Do not change the order of right and left turns -// TurnRight(TurnLeft) must have a minimal value -// TurnSlightRight(TurnSlightLeft) must have a maximum value -// to make check TurnRight <= turn <= TurnSlightRight work -// -// TurnDirection array in cpp file must be synchronized with state of TurnDirection enum in java. +/*! + * \warning The order of values below shall not be changed. + * TurnRight(TurnLeft) must have a minimal value and + * TurnSlightRight(TurnSlightLeft) must have a maximum value + * \warning The values of TurnDirection shall be synchronized with values of TurnDirection enum in + * java. + */ enum class TurnDirection { NoTurn = 0, @@ -47,12 +46,14 @@ enum class TurnDirection StartAtEndOfStreet, ReachedYourDestination, - Count // This value is used for internals only. + Count /// This value is used for internals only. }; string DebugPrint(TurnDirection const l); -// LaneWay array in cpp file must be synchronized with state of LaneWay enum in java. +/*! + * \warning The values of LaneWay shall be synchronized with values of LaneWay enum in java. + */ enum class LaneWay { None = 0, @@ -66,7 +67,7 @@ enum class LaneWay SlightRight, Right, SharpRight, - Count // This value is used for internals only. + Count /// This value is used for internals only. }; string DebugPrint(LaneWay const l); @@ -102,15 +103,6 @@ struct SingleLaneInfo string DebugPrint(SingleLaneInfo const & singleLaneInfo); -struct TurnCandidate -{ - double m_angle; - NodeID m_node; - - TurnCandidate(double a, NodeID n) : m_angle(a), m_node(n) {} -}; -typedef vector TTurnCandidates; - string const GetTurnString(TurnDirection turn); bool IsLeftTurn(TurnDirection t); @@ -148,7 +140,5 @@ bool IsLaneWayConformedTurnDirectionApproximately(LaneWay l, TurnDirection t); bool ParseLanes(string lanesString, vector & lanes); void SplitLanes(string const & lanesString, char delimiter, vector & lanes); bool ParseSingleLane(string const & laneString, char delimiter, TSingleLane & lane); - -} -} - +} // namespace turns +} // namespace routing diff --git a/routing/turns_generator.cpp b/routing/turns_generator.cpp index bf1a430023..0b83206f4a 100644 --- a/routing/turns_generator.cpp +++ b/routing/turns_generator.cpp @@ -1,24 +1,159 @@ #include "routing/routing_mapping.h" #include "routing/turns_generator.hpp" +#include "routing/vehicle_model.hpp" + +#include "search/house_detector.hpp" #include "indexer/ftypes_matcher.hpp" +#include "indexer/scales.hpp" + +#include "geometry/angles.hpp" #include "3party/osrm/osrm-backend/data_structures/internal_route_result.hpp" #include "std/numeric.hpp" #include "std/string.hpp" +using namespace routing; +using namespace routing::turns; + namespace { -using namespace routing::turns; +double const kFeaturesNearTurnMeters = 3.0; + +typedef vector TGeomTurnCandidate; + +struct TurnCandidate +{ + double angle; + NodeID node; + + TurnCandidate(double a, NodeID n) : angle(a), node(n) {} +}; +typedef vector TTurnCandidates; + +class Point2Geometry : private noncopyable +{ + m2::PointD m_junctionPoint, m_ingoingPoint; + TGeomTurnCandidate & m_candidates; + +public: + Point2Geometry(m2::PointD const & junctionPoint, m2::PointD const & ingoingPoint, + TGeomTurnCandidate & candidates) + : m_junctionPoint(junctionPoint), m_ingoingPoint(ingoingPoint), m_candidates(candidates) + { + } + + void operator()(FeatureType const & ft) + { + static CarModel const carModel; + if (ft.GetFeatureType() != feature::GEOM_LINE || !carModel.IsRoad(ft)) + return; + ft.ParseGeometry(FeatureType::BEST_GEOMETRY); + size_t const count = ft.GetPointsCount(); + ASSERT_GREATER(count, 1, ()); + + for (size_t i = 0; i < count; ++i) + { + if (MercatorBounds::DistanceOnEarth(m_junctionPoint, ft.GetPoint(i)) < + kFeaturesNearTurnMeters) + { + if (i > 0) + m_candidates.push_back(my::RadToDeg( + ang::TwoVectorsAngle(m_junctionPoint, m_ingoingPoint, ft.GetPoint(i - 1)))); + if (i < count - 1) + m_candidates.push_back(my::RadToDeg( + ang::TwoVectorsAngle(m_junctionPoint, m_ingoingPoint, ft.GetPoint(i + 1)))); + return; + } + } + } +}; + +size_t GetFirstSegmentPointIndex(pair const & p) { return p.first; } + +OsrmMappingTypes::FtSeg GetSegment(NodeID node, RoutingMapping const & routingMapping, + TGetIndexFunction GetIndex) +{ + auto const segmentsRange = routingMapping.m_segMapping.GetSegmentsRange(node); + OsrmMappingTypes::FtSeg seg; + routingMapping.m_segMapping.GetSegmentByIndex(GetIndex(segmentsRange), seg); + return seg; +} + +ftypes::HighwayClass GetOutgoingHighwayClass(NodeID outgoingNode, + RoutingMapping const & routingMapping, + Index const & index) +{ + OsrmMappingTypes::FtSeg const seg = + GetSegment(outgoingNode, routingMapping, GetFirstSegmentPointIndex); + if (!seg.IsValid()) + return ftypes::HighwayClass::None; + Index::FeaturesLoaderGuard loader(index, routingMapping.GetMwmId()); + FeatureType ft; + loader.GetFeature(seg.m_fid, ft); + return ftypes::GetHighwayClass(ft); +} + +/*! + * \brief Returns false when + * - the route leads from one big road to another one; + * - and the other possible turns lead to small roads; + * - and the turn is GoStraight or TurnSlight*. + */ +bool HighwayClassFilter(ftypes::HighwayClass ingoingClass, ftypes::HighwayClass outgoingClass, + NodeID outgoingNode, TurnDirection turn, + TTurnCandidates const & possibleTurns, + RoutingMapping const & routingMapping, Index const & index) +{ + if (!IsGoStraightOrSlightTurn(turn)) + return true; /// The road significantly changes its direction here. So this turn shall be kept. + + /// There's only one exit from this junction. NodeID of the exit is outgoingNode. + if (possibleTurns.size() == 1) + return true; + + ftypes::HighwayClass maxClassForPossibleTurns = ftypes::HighwayClass::None; + for (auto const & t : possibleTurns) + { + if (t.node == outgoingNode) + continue; + ftypes::HighwayClass const highwayClass = + GetOutgoingHighwayClass(t.node, routingMapping, index); + if (static_cast(highwayClass) > static_cast(maxClassForPossibleTurns)) + maxClassForPossibleTurns = highwayClass; + } + if (maxClassForPossibleTurns == ftypes::HighwayClass::None) + { + ASSERT(false, ("One of possible turns follows along an undefined HighwayClass.")); + return true; + } + + ftypes::HighwayClass const minClassForTheRoute = static_cast( + min(static_cast(ingoingClass), static_cast(outgoingClass))); + if (minClassForTheRoute == ftypes::HighwayClass::None) + { + ASSERT(false, ("The route contains undefined HighwayClass.")); + return false; + } + + int const maxHighwayClassDiffToKeepTheTurn = 2; + if (static_cast(maxClassForPossibleTurns) - static_cast(minClassForTheRoute) >= + maxHighwayClassDiffToKeepTheTurn) + { + /// The turn shall be removed if the route goes near small roads without changing the direction. + return false; + } + return true; +} bool FixupLaneSet(TurnDirection turn, vector & lanes, function checker) { bool isLaneConformed = false; - // There are two hidden nested loops below (transform and find_if). - // But the number of calls of the body of inner one (lambda in find_if) is relatively small. - // Less than 10 in most cases. + /// There are two nested loops below. (There is a for-loop in checker.) + /// But the number of calls of the body of inner one (in checker) is relatively small. + /// Less than 10 in most cases. for (auto & singleLane : lanes) { for (LaneWay laneWay : singleLane.m_lane) @@ -34,11 +169,13 @@ bool FixupLaneSet(TurnDirection turn, vector & lanes, return isLaneConformed; } -// Converts a turn angle (double) into a turn direction (TurnDirection). -// upperBounds is a table of pairs: an angle and a direction. -// upperBounds shall be sorted by the first parameter (angle) from small angles to big angles. -// These angles should be measured in degrees and should belong to the range [0; 360]. -// The second paramer (angle) shall be greater than or equal to zero and is measured in degrees. +/*! + * \brief Converts a turn angle (double) into a turn direction (TurnDirection). + * \note upperBounds is a table of pairs: an angle and a direction. + * upperBounds shall be sorted by the first parameter (angle) from small angles to big angles. + * These angles should be measured in degrees and should belong to the range [0; 360]. + * The second paramer (angle) shall be greater than or equal to zero and is measured in degrees. + */ TurnDirection FindDirectionByAngle(vector> const & upperBounds, double angle) { @@ -60,21 +197,200 @@ TurnDirection FindDirectionByAngle(vector> const & u ASSERT(false, ("The angle is not covered by the table. angle = ", angle)); return TurnDirection::NoTurn; } + +m2::PointD GetPointForTurnAngle(OsrmMappingTypes::FtSeg const & seg, FeatureType const & ft, + m2::PointD const & turnPnt, + size_t (*GetPndInd)(const size_t, const size_t, const size_t)) +{ + size_t const kMaxPointsCount = 7; + double const kMaxDistMeters = 300.f; + double curDist = 0.f; + m2::PointD pnt = turnPnt, nextPnt; + + size_t const segDist = abs(seg.m_pointEnd - seg.m_pointStart); + ASSERT_LESS(segDist, ft.GetPointsCount(), ()); + size_t const usedFtPntNum = min(kMaxPointsCount, segDist); + + for (size_t i = 1; i <= usedFtPntNum; ++i) + { + nextPnt = ft.GetPoint(GetPndInd(seg.m_pointStart, seg.m_pointEnd, i)); + curDist += MercatorBounds::DistanceOnEarth(pnt, nextPnt); + if (curDist > kMaxDistMeters) + return nextPnt; + pnt = nextPnt; + } + return nextPnt; +} + +/*! + * \brief GetTurnGeometry looks for all the road network edges near ingoingPoint. + * GetTurnGeometry fills candidates with angles of all the incoming and outgoint segments. + * \warning GetTurnGeometry should be used carefully because it's a time-consuming function. + * \warning In multilevel crossroads there is an insignificant possibility that candidates + * is filled with redundant segments of roads of different levels. + */ +void GetTurnGeometry(m2::PointD const & junctionPoint, m2::PointD const & ingoingPoint, + TGeomTurnCandidate & candidates, RoutingMapping const & mapping, + Index const & index) +{ + Point2Geometry getter(junctionPoint, ingoingPoint, candidates); + index.ForEachInRectForMWM( + getter, MercatorBounds::RectByCenterXYAndSizeInMeters(junctionPoint, kFeaturesNearTurnMeters), + scales::GetUpperScale(), mapping.GetMwmId()); +} + +/*! + * \param junctionPoint is a point of the junction. + * \param ingoingPointOneSegment is a point one segment before the junction along the route. + * \param mapping is a route mapping. + * \return number of all the segments which joins junctionPoint. That means + * the number of ingoing segments plus the number of outgoing segments. + * \warning NumberOfIngoingAndOutgoingSegments should be used carefully because + * it's a time-consuming function. + * \warning In multilevel crossroads there is an insignificant possibility that the returned value + * contains redundant segments of roads of different levels. + */ +size_t NumberOfIngoingAndOutgoingSegments(m2::PointD const & junctionPoint, + m2::PointD const & ingoingPointOneSegment, + RoutingMapping const & mapping, Index const & index) +{ + TGeomTurnCandidate geoNodes; + GetTurnGeometry(junctionPoint, ingoingPointOneSegment, geoNodes, mapping, index); + return geoNodes.size(); +} + +NodeID GetTurnTargetNode(NodeID src, NodeID trg, QueryEdge::EdgeData const & edgeData, + RoutingMapping & routingMapping) +{ + ASSERT_NOT_EQUAL(src, SPECIAL_NODEID, ()); + ASSERT_NOT_EQUAL(trg, SPECIAL_NODEID, ()); + if (!edgeData.shortcut) + return trg; + + ASSERT_LESS(edgeData.id, routingMapping.m_dataFacade.GetNumberOfNodes(), ()); + EdgeID edge = SPECIAL_EDGEID; + QueryEdge::EdgeData d; + for (EdgeID e : routingMapping.m_dataFacade.GetAdjacentEdgeRange(edgeData.id)) + { + if (routingMapping.m_dataFacade.GetTarget(e) == src) + { + d = routingMapping.m_dataFacade.GetEdgeData(e, edgeData.id); + if (d.backward) + { + edge = e; + break; + } + } + } + + if (edge == SPECIAL_EDGEID) + { + for (EdgeID e : routingMapping.m_dataFacade.GetAdjacentEdgeRange(src)) + { + if (routingMapping.m_dataFacade.GetTarget(e) == edgeData.id) + { + d = routingMapping.m_dataFacade.GetEdgeData(e, src); + if (d.forward) + { + edge = e; + break; + } + } + } + } + ASSERT_NOT_EQUAL(edge, SPECIAL_EDGEID, ()); + + if (d.shortcut) + return GetTurnTargetNode(src, edgeData.id, d, routingMapping); + + return edgeData.id; +} + +void GetPossibleTurns(Index const & index, NodeID node, m2::PointD const & ingoingPoint, + m2::PointD const & junctionPoint, RoutingMapping & routingMapping, + TTurnCandidates & candidates) +{ + for (EdgeID e : routingMapping.m_dataFacade.GetAdjacentEdgeRange(node)) + { + QueryEdge::EdgeData const data = routingMapping.m_dataFacade.GetEdgeData(e, node); + if (!data.forward) + continue; + + NodeID const trg = + GetTurnTargetNode(node, routingMapping.m_dataFacade.GetTarget(e), data, routingMapping); + ASSERT_NOT_EQUAL(trg, SPECIAL_NODEID, ()); + + auto const range = routingMapping.m_segMapping.GetSegmentsRange(trg); + OsrmMappingTypes::FtSeg seg; + routingMapping.m_segMapping.GetSegmentByIndex(range.first, seg); + if (!seg.IsValid()) + continue; + + FeatureType ft; + Index::FeaturesLoaderGuard loader(index, routingMapping.GetMwmId()); + loader.GetFeature(seg.m_fid, ft); + ft.ParseGeometry(FeatureType::BEST_GEOMETRY); + + m2::PointD const outgoingPoint = ft.GetPoint(seg.m_pointStart < seg.m_pointEnd ? seg.m_pointStart + 1 + : seg.m_pointStart - 1); + ASSERT_LESS(MercatorBounds::DistanceOnEarth(junctionPoint, ft.GetPoint(seg.m_pointStart)), + kFeaturesNearTurnMeters, ()); + + double const a = my::RadToDeg(ang::TwoVectorsAngle(junctionPoint, ingoingPoint, outgoingPoint)); + + candidates.emplace_back(a, trg); + } + + sort(candidates.begin(), candidates.end(), [](TurnCandidate const & t1, TurnCandidate const & t2) + { + return t1.node < t2.node; + }); + + auto const last = unique(candidates.begin(), candidates.end(), + [](TurnCandidate const & t1, TurnCandidate const & t2) + { + return t1.node == t2.node; + }); + candidates.erase(last, candidates.end()); + + sort(candidates.begin(), candidates.end(), [](TurnCandidate const & t1, TurnCandidate const & t2) + { + return t1.angle < t2.angle; + }); +} } // namespace namespace routing { namespace turns { -size_t GetFirstSegmentPointIndex(pair const & p) { return p.first; } - -OsrmMappingTypes::FtSeg GetSegment(NodeID node, RoutingMapping const & routingMapping, - TGetIndexFunction GetIndex) +TurnInfo::TurnInfo(RoutingMapping & routeMapping, NodeID ingoingNodeID, NodeID outgoingNodeID) + : m_routeMapping(routeMapping), + m_ingoingNodeID(ingoingNodeID), + m_ingoingHighwayClass(ftypes::HighwayClass::Undefined), + m_outgoingNodeID(outgoingNodeID), + m_outgoingHighwayClass(ftypes::HighwayClass::Undefined) { - auto const segmentsRange = routingMapping.m_segMapping.GetSegmentsRange(node); - OsrmMappingTypes::FtSeg seg; - routingMapping.m_segMapping.GetSegmentByIndex(GetIndex(segmentsRange), seg); - return seg; + m_ingoingSegment = GetSegment(m_ingoingNodeID, m_routeMapping, GetLastSegmentPointIndex); + m_outgoingSegment = GetSegment(m_outgoingNodeID, m_routeMapping, GetFirstSegmentPointIndex); +} + +TurnInfo::~TurnInfo() {} + +bool TurnInfo::IsSegmentsValid() const +{ + if (!m_ingoingSegment.IsValid() || !m_outgoingSegment.IsValid()) + { + LOG(LWARNING, ("Some turns can't load the geometry.")); + return false; + } + return true; +} + +size_t GetLastSegmentPointIndex(pair const & p) +{ + ASSERT_GREATER(p.second, 0, ()); + return p.second - 1; } vector GetLanesInfo(NodeID node, RoutingMapping const & routingMapping, @@ -146,13 +462,13 @@ void CalculateTurnGeometry(vector const & points, Route::TurnsT cons void FixupTurns(vector const & points, Route::TurnsT & turnsDir) { double const kMergeDistMeters = 30.0; - // For turns that are not EnterRoundAbout exitNum is always equal to zero. - // If a turn is EnterRoundAbout exitNum is a number of turns between two points: - // (1) the route enters to the roundabout; - // (2) the route leaves the roundabout; + /// For turns that are not EnterRoundAbout exitNum is always equal to zero. + /// If a turn is EnterRoundAbout exitNum is a number of turns between two points: + /// (1) the route enters to the roundabout; + /// (2) the route leaves the roundabout; uint32_t exitNum = 0; - // If a roundabout is worked up the roundabout value points to the turn - // of the enter to the roundabout. If not, roundabout is equal to nullptr. + /// If a roundabout is worked up the roundabout value points to the turn + /// of the enter to the roundabout. If not, roundabout is equal to nullptr. TurnItem * roundabout = nullptr; auto routeDistanceMeters = [&points](uint32_t start, uint32_t end) @@ -190,10 +506,10 @@ void FixupTurns(vector const & points, Route::TurnsT & turnsDir) exitNum = 0; } - // Merging turns which are closed to each other under some circumstance. - // distance(turnsDir[idx - 1].m_index, turnsDir[idx].m_index) < kMergeDistMeters - // means the distance in meters between the former turn (idx - 1) - // and the current turn (idx). + /// Merging turns which are closed to each other under some circumstance. + /// distance(turnsDir[idx - 1].m_index, turnsDir[idx].m_index) < kMergeDistMeters + /// means the distance in meters between the former turn (idx - 1) + /// 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) @@ -202,15 +518,17 @@ void FixupTurns(vector const & points, Route::TurnsT & turnsDir) continue; } - // @todo(vbykoianko) The sieve below is made for filtering unnecessary turns on Moscow's MKAD - // and roads like it. It's a quick fix but it's possible to do better. - // The better solution is to remove all "slight" turns if the route goes form one not-link road - // to another not-link road and other possible turns are links. But it's not possible to - // implement it quickly. To do that you need to calculate FeatureType for most possible turns. - // But it is already made once in HighwayClassFilter(GetOutgoingHighwayClass). - // So it's a good idea to keep FeatureType for outgoing turns in TTurnCandidates - // (if they have been calculated). For the time being I decided to postpone the implementation - // of the feature but it is worth implementing it in the future. + /// @todo(vbykoianko) The sieve below is made for filtering unnecessary turns on Moscow's MKAD + /// and roads like it. It's a quick fix but it's possible to do better. + /// The better solution is to remove all "slight" turns if the route goes form one not-link road + /// to another not-link road and other possible turns are links. But it's not possible to + /// implement it quickly. To do that you need to calculate FeatureType for most possible turns. + /// But it is already made once in HighwayClassFilter(GetOutgoingHighwayClass). + /// So it's a good idea to keep FeatureType for outgoing turns in TTurnCandidates + /// (if they have been calculated). For the time being I decided to postpone the implementation + /// of the feature but it is worth implementing it in the future. + /// of the feature but it worth implementing it in the future. + /// To implement the new sieve (the better solution) use TurnInfo structure. if (!t.m_keepAnyway && IsGoStraightOrSlightTurn(t.m_turn) && !t.m_sourceName.empty() && strings::AlmostEqual(t.m_sourceName, t.m_targetName, 2 /* mismatched symbols count */)) { @@ -232,87 +550,27 @@ void SelectRecommendedLanes(Route::TurnsT & turnsDir) if (lanes.empty()) continue; TurnDirection const turn = t.m_turn; - // Checking if threre are elements in lanes which correspond with the turn exactly. - // If so fixing up all the elements in lanes which correspond with the turn. + /// Checking if threre are elements in lanes which correspond with the turn exactly. + /// If so fixing up all the elements in lanes which correspond with the turn. if (FixupLaneSet(turn, lanes, &IsLaneWayConformedTurnDirection)) continue; - // If not checking if there are elements in lanes which corresponds with the turn - // approximately. If so fixing up all these elements. + /// If not checking if there are elements in lanes which corresponds with the turn + /// approximately. If so fixing up all these elements. FixupLaneSet(turn, lanes, &IsLaneWayConformedTurnDirectionApproximately); } } -ftypes::HighwayClass GetOutgoingHighwayClass(NodeID outgoingNode, - RoutingMapping const & routingMapping, - Index const & index) -{ - OsrmMappingTypes::FtSeg const seg = - GetSegment(outgoingNode, routingMapping, GetFirstSegmentPointIndex); - if (!seg.IsValid()) - return ftypes::HighwayClass::None; - Index::FeaturesLoaderGuard loader(index, routingMapping.GetMwmId()); - FeatureType ft; - loader.GetFeature(seg.m_fid, ft); - return ftypes::GetHighwayClass(ft); -} - -bool HighwayClassFilter(ftypes::HighwayClass ingoingClass, ftypes::HighwayClass outgoingClass, - NodeID outgoingNode, TurnDirection turn, - TTurnCandidates const & possibleTurns, - RoutingMapping const & routingMapping, Index const & index) -{ - if (!IsGoStraightOrSlightTurn(turn)) - return true; // The road significantly changes its direction here. So this turn shall be kept. - - // There's only one exit from this junction. NodeID of the exit is outgoingNode. - if (possibleTurns.size() == 1) - return true; - - ftypes::HighwayClass maxClassForPossibleTurns = ftypes::HighwayClass::None; - for (auto const & t : possibleTurns) - { - if (t.m_node == outgoingNode) - continue; - ftypes::HighwayClass const highwayClass = - GetOutgoingHighwayClass(t.m_node, routingMapping, index); - if (static_cast(highwayClass) > static_cast(maxClassForPossibleTurns)) - maxClassForPossibleTurns = highwayClass; - } - if (maxClassForPossibleTurns == ftypes::HighwayClass::None) - { - ASSERT(false, ("One of possible turns follows along an undefined HighwayClass.")); - return true; - } - - ftypes::HighwayClass const minClassForTheRoute = static_cast( - min(static_cast(ingoingClass), static_cast(outgoingClass))); - if (minClassForTheRoute == ftypes::HighwayClass::None) - { - ASSERT(false, ("The route contains undefined HighwayClass.")); - return false; - } - - int const maxHighwayClassDiffToKeepTheTurn = 2; - if (static_cast(maxClassForPossibleTurns) - static_cast(minClassForTheRoute) >= - maxHighwayClassDiffToKeepTheTurn) - { - // The turn shall be removed if the route goes near small roads without changing the direction. - return false; - } - return true; -} - bool CheckRoundaboutEntrance(bool isIngoingEdgeRoundabout, bool isOutgoingEdgeRoundabout) { return !isIngoingEdgeRoundabout && isOutgoingEdgeRoundabout; } TurnDirection GetRoundaboutDirection(bool isIngoingEdgeRoundabout, bool isOutgoingEdgeRoundabout, - bool isJunctionOfSeveralTurns) + bool isMultiTurnJunction) { if (isIngoingEdgeRoundabout && isOutgoingEdgeRoundabout) { - if (isJunctionOfSeveralTurns) + if (isMultiTurnJunction) return TurnDirection::StayOnRoundAbout; return TurnDirection::NoTurn; } @@ -383,5 +641,123 @@ TurnDirection IntermediateDirection(const double angle) return FindDirectionByAngle(kUpperBounds, angle); } + +void GetTurnDirection(Index const & index, TurnInfo & turnInfo, TurnItem & turn) +{ + if (!turnInfo.IsSegmentsValid()) + return; + + /// ingoingFeature and outgoingFeature can be used only within the scope. + FeatureType ingoingFeature, outgoingFeature; + Index::FeaturesLoaderGuard ingoingLoader(index, turnInfo.m_routeMapping.GetMwmId()); + Index::FeaturesLoaderGuard outgoingLoader(index, turnInfo.m_routeMapping.GetMwmId()); + ingoingLoader.GetFeature(turnInfo.m_ingoingSegment.m_fid, ingoingFeature); + outgoingLoader.GetFeature(turnInfo.m_outgoingSegment.m_fid, outgoingFeature); + + ingoingFeature.ParseGeometry(FeatureType::BEST_GEOMETRY); + outgoingFeature.ParseGeometry(FeatureType::BEST_GEOMETRY); + + ASSERT_LESS(MercatorBounds::DistanceOnEarth( + ingoingFeature.GetPoint(turnInfo.m_ingoingSegment.m_pointEnd), + outgoingFeature.GetPoint(turnInfo.m_outgoingSegment.m_pointStart)), + kFeaturesNearTurnMeters, ()); + + m2::PointD const junctionPoint = ingoingFeature.GetPoint(turnInfo.m_ingoingSegment.m_pointEnd); + m2::PointD const ingoingPoint = + GetPointForTurnAngle(turnInfo.m_ingoingSegment, ingoingFeature, junctionPoint, + [](const size_t start, const size_t end, const size_t i) + { + return end > start ? end - i : end + i; + }); + m2::PointD const outgoingPoint = + GetPointForTurnAngle(turnInfo.m_outgoingSegment, outgoingFeature, junctionPoint, + [](const size_t start, const size_t end, const size_t i) + { + return end > start ? start + i : start - i; + }); + double const a = my::RadToDeg(ang::TwoVectorsAngle(junctionPoint, ingoingPoint, outgoingPoint)); + + m2::PointD const ingoingPointOneSegment = ingoingFeature.GetPoint( + turnInfo.m_ingoingSegment.m_pointStart < turnInfo.m_ingoingSegment.m_pointEnd + ? turnInfo.m_ingoingSegment.m_pointEnd - 1 + : turnInfo.m_ingoingSegment.m_pointEnd + 1); + TTurnCandidates nodes; + GetPossibleTurns(index, turnInfo.m_ingoingNodeID, ingoingPointOneSegment, junctionPoint, + turnInfo.m_routeMapping, nodes); + + turn.m_turn = TurnDirection::NoTurn; + size_t const nodesSize = nodes.size(); + bool const hasMultiTurns = (nodesSize >= 2); + + if (nodesSize == 0) + return; + + if (nodes.front().node == turnInfo.m_outgoingNodeID) + turn.m_turn = MostRightDirection(a); + else if (nodes.back().node == turnInfo.m_outgoingNodeID) + turn.m_turn = MostLeftDirection(a); + else + turn.m_turn = IntermediateDirection(a); + + bool const isIngoingEdgeRoundabout = ftypes::IsRoundAboutChecker::Instance()(ingoingFeature); + bool const isOutgoingEdgeRoundabout = ftypes::IsRoundAboutChecker::Instance()(outgoingFeature); + + if (isIngoingEdgeRoundabout || isOutgoingEdgeRoundabout) + { + turn.m_turn = + GetRoundaboutDirection(isIngoingEdgeRoundabout, isOutgoingEdgeRoundabout, hasMultiTurns); + return; + } + + turn.m_keepAnyway = (!ftypes::IsLinkChecker::Instance()(ingoingFeature) && + ftypes::IsLinkChecker::Instance()(outgoingFeature)); + + // get names + string name1, name2; + { + ingoingFeature.GetName(FeatureType::DEFAULT_LANG, turn.m_sourceName); + outgoingFeature.GetName(FeatureType::DEFAULT_LANG, turn.m_targetName); + + search::GetStreetNameAsKey(turn.m_sourceName, name1); + search::GetStreetNameAsKey(turn.m_targetName, name2); + } + + turnInfo.m_ingoingHighwayClass = ftypes::GetHighwayClass(ingoingFeature); + turnInfo.m_outgoingHighwayClass = ftypes::GetHighwayClass(outgoingFeature); + if (!turn.m_keepAnyway && + !HighwayClassFilter(turnInfo.m_ingoingHighwayClass, turnInfo.m_outgoingHighwayClass, + turnInfo.m_outgoingNodeID, turn.m_turn, nodes, turnInfo.m_routeMapping, index)) + { + turn.m_turn = TurnDirection::NoTurn; + return; + } + + bool const isGoStraightOrSlightTurn = IsGoStraightOrSlightTurn(IntermediateDirection( + my::RadToDeg(ang::TwoVectorsAngle(junctionPoint, ingoingPointOneSegment, outgoingPoint)))); + /// The code below is resposible for cases when there is only one way to leave the junction. + /// Such junction has to be kept as a turn when: + /// * it's not a slight turn and it has ingoing edges (one or more); + /// * it's an entrance to a roundabout; + if (!hasMultiTurns && (isGoStraightOrSlightTurn || + NumberOfIngoingAndOutgoingSegments(junctionPoint, ingoingPointOneSegment, + turnInfo.m_routeMapping, index) <= 2) && + !CheckRoundaboutEntrance(isIngoingEdgeRoundabout, isOutgoingEdgeRoundabout)) + { + turn.m_turn = TurnDirection::NoTurn; + return; + } + + if (turn.m_turn == TurnDirection::GoStraight) + { + if (!hasMultiTurns) + turn.m_turn = TurnDirection::NoTurn; + + return; + } + + /// @todo(vbykoianko) Checking if it's a uturn or not shall be moved to FindDirectionByAngle. + if (turn.m_turn == TurnDirection::NoTurn) + turn.m_turn = TurnDirection::UTurn; +} } // namespace turns } // namespace routing diff --git a/routing/turns_generator.hpp b/routing/turns_generator.hpp index edb8c7131e..3d29c1204f 100644 --- a/routing/turns_generator.hpp +++ b/routing/turns_generator.hpp @@ -5,8 +5,6 @@ #include "routing/route.hpp" #include "routing/turns.hpp" -#include "indexer/ftypes_matcher.hpp" - #include "std/function.hpp" #include "std/utility.hpp" #include "std/vector.hpp" @@ -14,48 +12,81 @@ struct PathData; class Index; +namespace ftypes +{ +enum class HighwayClass; +} + namespace routing { struct RoutingMapping; namespace turns { -// Returns a segment index by STL-like range [s, e) of segments indices for the passed node. +/*! + * \brief Returns a segment index by STL-like range [s, e) of segments indices for the passed node. + */ typedef function)> TGetIndexFunction; -size_t GetFirstSegmentPointIndex(pair const & p); -OsrmMappingTypes::FtSeg GetSegment(NodeID node, RoutingMapping const & routingMapping, - TGetIndexFunction GetIndex); +/*! + * \brief The TurnInfo struct is an accumulator for all junction information. + * During a junction (a turn) analysis a different subsets of fields in the structure + * may be calculated. The main purpose of TurnInfo is to prevent recalculation the same fields. + * The idea is if a field is required to check whether a field has been calculated. + * If yes, just use it. If not, the field should be calculated, kept in the structure + * and used. + */ +struct TurnInfo +{ + RoutingMapping & m_routeMapping; + + NodeID m_ingoingNodeID; + OsrmMappingTypes::FtSeg m_ingoingSegment; + ftypes::HighwayClass m_ingoingHighwayClass; + + NodeID m_outgoingNodeID; + OsrmMappingTypes::FtSeg m_outgoingSegment; + ftypes::HighwayClass m_outgoingHighwayClass; + + TurnInfo(RoutingMapping & routeMapping, NodeID ingoingNodeID, NodeID outgoingNodeID); + ~TurnInfo(); + + bool IsSegmentsValid() const; +}; + +size_t GetLastSegmentPointIndex(pair const & p); vector GetLanesInfo(NodeID node, RoutingMapping const & routingMapping, TGetIndexFunction GetIndex, Index const & index); -// Returns geometry for all the turns. That means that for every turn CalculateTurnGeometry calculates -// a sequence of points. + +/*! + * \brief Returns geometry for all the turns. It means that for every turn CalculateTurnGeometry + * calculates a sequence of points. + */ void CalculateTurnGeometry(vector const & points, Route::TurnsT const & turnsDir, TurnsGeomT & turnsGeom); -// Selects lanes which are recommended for an end user. +/*! + * \brief Selects lanes which are recommended for an end user. + */ void SelectRecommendedLanes(Route::TurnsT & turnsDir); void FixupTurns(vector const & points, Route::TurnsT & turnsDir); -ftypes::HighwayClass GetOutgoingHighwayClass(NodeID node, RoutingMapping const & routingMapping, - Index const & index); + TurnDirection InvertDirection(TurnDirection dir); TurnDirection MostRightDirection(double angle); TurnDirection MostLeftDirection(double angle); TurnDirection IntermediateDirection(double angle); -// Returns true if the route enters a roundabout. -// That means isIngoingEdgeRoundabout is false and isOutgoingEdgeRoundabout is true. +/*! + * \return Returns true if the route enters a roundabout. + * That means isIngoingEdgeRoundabout is false and isOutgoingEdgeRoundabout is true. + */ bool CheckRoundaboutEntrance(bool isIngoingEdgeRoundabout, bool isOutgoingEdgeRoundabout); -// Returns a turn instruction if an ingoing edge or (and) outgoing edge belongs to a roundabout. +/*! + * \return Returns a turn instruction if an ingoing edge or (and) outgoing edge belongs to a roundabout. + */ TurnDirection GetRoundaboutDirection(bool isIngoingEdgeRoundabout, bool isOutgoingEdgeRoundabout, - bool isJunctionOfSeveralTurns); + bool isMultiTurnJunction); + +void GetTurnDirection(Index const & index, turns::TurnInfo & turnInfo, TurnItem & turn); -// Returns false when -// * the route leads from one big road to another one; -// * and the other possible turns lead to small roads; -// * and the turn is GoStraight or TurnSlight*. -bool HighwayClassFilter(ftypes::HighwayClass ingoingClass, ftypes::HighwayClass outgoingClass, - NodeID outgoingNode, TurnDirection turn, - TTurnCandidates const & possibleTurns, - RoutingMapping const & routingMapping, Index const & index); } // namespace routing } // namespace turns