[Routing] Refactoring

Almost identical Generate()
from CarDirectionsEngine and PedestrianDirectionsEngine
moved to parent class.

MakeTurnAnnotation() - became method of DirectionsEngine.

All VehicleType dependent logic was incapsulated in virtual methods:
- GetTurnDirection()
- FixupTurns().

According existing functions became methods of
CarDirectionsEngine and PedestrianDirectionsEngine.

VehicleType dependent functions moved
from turns_generator to according DirectionsEngines.

Signed-off-by: Anton Makouski <anton.makouski@gmail.com>
This commit is contained in:
Anton Makouski 2022-05-13 12:41:33 +03:00
parent 083a5f264e
commit 1b998cd35b
10 changed files with 1013 additions and 1004 deletions

View file

@ -7,6 +7,9 @@
#include "routing/routing_result_graph.hpp"
#include "routing/turns.hpp"
#include "routing/turns_generator.hpp"
#include "routing/turns_generator_utils.hpp"
#include "geometry/angles.hpp"
#include "base/assert.hpp"
@ -15,6 +18,8 @@
namespace routing
{
using namespace std;
using namespace turns;
using namespace ftypes;
CarDirectionsEngine::CarDirectionsEngine(DataSource const & dataSource,
shared_ptr<NumMwmIds> numMwmIds)
@ -22,61 +27,613 @@ CarDirectionsEngine::CarDirectionsEngine(DataSource const & dataSource,
{
}
bool CarDirectionsEngine::Generate(IndexRoadGraph const & graph,
vector<geometry::PointWithAltitude> const & path,
base::Cancellable const & cancellable, Route::TTurns & turns,
Route::TStreets & streetNames,
vector<geometry::PointWithAltitude> & routeGeometry,
vector<Segment> & segments)
void CarDirectionsEngine::FixupTurns(std::vector<geometry::PointWithAltitude> const & junctions, Route::TTurns & turnsDir)
{
CHECK(m_numMwmIds, ());
FixupCarTurns(junctions, turnsDir);
m_adjacentEdges.clear();
m_pathSegments.clear();
turns.clear();
streetNames.clear();
routeGeometry.clear();
segments.clear();
#ifdef DEBUG
for (auto const & t : turnsDir)
LOG(LDEBUG, (GetTurnString(t.m_turn), ":", t.m_index, t.m_sourceName, "-",
t.m_targetName, "exit:", t.m_exitNum));
#endif
}
size_t const pathSize = path.size();
// Note. According to Route::IsValid() method route of zero or one point is invalid.
if (pathSize <= 1)
void FixupCarTurns(std::vector<geometry::PointWithAltitude> const & junctions, Route::TTurns & turnsDir)
{
uint32_t turn_index = static_cast<uint32_t>(junctions.size() - 1);
turnsDir.emplace_back(TurnItem(turn_index, CarDirection::ReachedYourDestination));
double const kMergeDistMeters = 15.0;
// For turns that are not EnterRoundAbout/ExitRoundAbout exitNum is always equal to zero.
// If a turn is EnterRoundAbout exitNum is a number of turns between two junctions:
// (1) the route enters to the roundabout;
// (2) the route leaves the roundabout;
uint32_t exitNum = 0;
TurnItem * currentEnterRoundAbout = nullptr;
for (size_t idx = 0; idx < turnsDir.size(); )
{
TurnItem & t = turnsDir[idx];
if (currentEnterRoundAbout && t.m_turn != CarDirection::StayOnRoundAbout && t.m_turn != CarDirection::LeaveRoundAbout)
{
ASSERT(false, ("Only StayOnRoundAbout or LeaveRoundAbout are expected after EnterRoundAbout."));
exitNum = 0;
currentEnterRoundAbout = nullptr;
}
else if (t.m_turn == CarDirection::EnterRoundAbout)
{
ASSERT(!currentEnterRoundAbout, ("It's not expected to find new EnterRoundAbout until previous EnterRoundAbout was leaved."));
currentEnterRoundAbout = &t;
ASSERT(exitNum == 0, ("exitNum is reset at start and after LeaveRoundAbout."));
exitNum = 0;
}
else if (t.m_turn == CarDirection::StayOnRoundAbout)
{
++exitNum;
turnsDir.erase(turnsDir.begin() + idx);
continue;
}
else if (t.m_turn == CarDirection::LeaveRoundAbout)
{
// It's possible for car to be on roundabout without entering it
// if route calculation started at roundabout (e.g. if user made full turn on roundabout).
if (currentEnterRoundAbout)
currentEnterRoundAbout->m_exitNum = exitNum + 1;
t.m_exitNum = exitNum + 1; // For LeaveRoundAbout turn.
currentEnterRoundAbout = nullptr;
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).
if (idx > 0 && IsStayOnRoad(turnsDir[idx - 1].m_turn) &&
IsLeftOrRightTurn(turnsDir[idx].m_turn) &&
CalcRouteDistanceM(junctions, turnsDir[idx - 1].m_index, turnsDir[idx].m_index) <
kMergeDistMeters)
{
turnsDir.erase(turnsDir.begin() + idx - 1);
continue;
}
++idx;
}
SelectRecommendedLanes(turnsDir);
}
void GetTurnDirectionBasic(IRoutingResult const & result, size_t const outgoingSegmentIndex,
NumMwmIds const & numMwmIds, RoutingSettings const & vehicleSettings,
TurnItem & turn);
size_t CarDirectionsEngine::GetTurnDirection(IRoutingResult const & result, size_t const outgoingSegmentIndex,
NumMwmIds const & numMwmIds,
RoutingSettings const & vehicleSettings, TurnItem & turnItem)
{
size_t skipTurnSegments = CheckUTurnOnRoute(result, outgoingSegmentIndex, numMwmIds, vehicleSettings, turnItem);
if (turnItem.m_turn == CarDirection::None)
GetTurnDirectionBasic(result, outgoingSegmentIndex, numMwmIds, vehicleSettings, turnItem);
// Lane information.
if (turnItem.m_turn != CarDirection::None)
{
auto const & loadedSegments = result.GetSegments();
auto const & ingoingSegment = loadedSegments[outgoingSegmentIndex - 1];
turnItem.m_lanes = ingoingSegment.m_lanes;
}
return skipTurnSegments;
}
/*!
* \brief Calculates a turn instruction if the ingoing edge or (and) the outgoing edge belongs to a
* roundabout.
* \return Returns one of the following results:
* - TurnDirection::EnterRoundAbout if the ingoing edge does not belong to a roundabout
* and the outgoing edge belongs to a roundabout.
* - TurnDirection::StayOnRoundAbout if the ingoing edge and the outgoing edge belong to a
* roundabout
* and there is a reasonalbe way to leave the junction besides the outgoing edge.
* This function does not return TurnDirection::StayOnRoundAbout for small ways to leave the
* roundabout.
* - TurnDirection::NoTurn if the ingoing edge and the outgoing edge belong to a roundabout
* (a) and there is a single way (outgoing edge) to leave the junction.
* (b) and there is a way(s) besides outgoing edge to leave the junction (the roundabout)
* but it is (they are) relevantly small.
*/
CarDirection GetRoundaboutDirectionBasic(bool isIngoingEdgeRoundabout, bool isOutgoingEdgeRoundabout,
bool isMultiTurnJunction, bool keepTurnByHighwayClass)
{
if (isIngoingEdgeRoundabout && isOutgoingEdgeRoundabout)
{
if (isMultiTurnJunction)
return keepTurnByHighwayClass ? CarDirection::StayOnRoundAbout : CarDirection::None;
return CarDirection::None;
}
if (!isIngoingEdgeRoundabout && isOutgoingEdgeRoundabout)
return CarDirection::EnterRoundAbout;
if (isIngoingEdgeRoundabout && !isOutgoingEdgeRoundabout)
return CarDirection::LeaveRoundAbout;
ASSERT(false, ());
return CarDirection::None;
}
// Min difference of route and alternative turn abs angles in degrees
// to ignore alternative turn (when route direction is GoStraight).
double constexpr kMinAbsAngleDiffForStraightRoute = 25.0;
// Min difference of route and alternative turn abs angles in degrees
// to ignore alternative turn (when route direction is bigger and GoStraight).
double constexpr kMinAbsAngleDiffForBigStraightRoute = 5;
// Min difference of route and alternative turn abs angles in degrees
// to ignore this alternative candidate (when alternative road is the same or smaller).
double constexpr kMinAbsAngleDiffForSameOrSmallerRoad = 35.0;
/*!
* \brief Returns false when other possible turns leads to service roads;
*/
bool KeepRoundaboutTurnByHighwayClass(TurnCandidates const & possibleTurns,
TurnInfo const & turnInfo, NumMwmIds const & numMwmIds)
{
Segment firstOutgoingSegment;
bool const validFirstOutgoingSeg = turnInfo.m_outgoing->m_segmentRange.GetFirstSegment(numMwmIds, firstOutgoingSegment);
for (auto const & t : possibleTurns.candidates)
{
if (!validFirstOutgoingSeg || t.m_segment == firstOutgoingSegment)
continue;
if (t.m_highwayClass != HighwayClass::Service)
return true;
}
return false;
}
CarDirection GetRoundaboutDirection(TurnInfo const & turnInfo, TurnCandidates const & nodes, NumMwmIds const & numMwmIds)
{
bool const keepTurnByHighwayClass = KeepRoundaboutTurnByHighwayClass(nodes, turnInfo, numMwmIds);
return GetRoundaboutDirectionBasic(turnInfo.m_ingoing->m_onRoundabout, turnInfo.m_outgoing->m_onRoundabout,
nodes.candidates.size() > 1, keepTurnByHighwayClass);
}
/// \brief Return |CarDirection::ExitHighwayToRight| or |CarDirection::ExitHighwayToLeft|
/// or return |CarDirection::None| if no exit is detected.
/// \note The function makes a decision about turn based on geometry of the route and turn
/// candidates, so it works correctly for both left and right hand traffic.
CarDirection TryToGetExitDirection(TurnCandidates const & possibleTurns, TurnInfo const & turnInfo,
Segment const & firstOutgoingSeg, CarDirection const intermediateDirection)
{
if (!possibleTurns.isCandidatesAngleValid)
return CarDirection::None;
if (!IsHighway(turnInfo.m_ingoing->m_highwayClass, turnInfo.m_ingoing->m_isLink))
return CarDirection::None;
if (!turnInfo.m_outgoing->m_isLink && !(IsSmallRoad(turnInfo.m_outgoing->m_highwayClass) && IsGoStraightOrSlightTurn(intermediateDirection)))
return CarDirection::None;
// At this point it is known that the route goes form a highway to a link road or to a small road
// which has a slight angle with the highway.
// Considering cases when the route goes from a highway to a link or a small road.
// Checking all turn candidates (sorted by their angles) and looking for the road which is a
// continuation of the ingoing segment. If the continuation is on the right hand of the route
// it's an exit to the left. If the continuation is on the left hand of the route
// it's an exit to the right.
// Note. The angle which is used for sorting turn candidates in |possibleTurns.candidates|
// is a counterclockwise angle between the ingoing route edge and corresponding candidate.
// For left turns the angle is less than zero and for right ones it is more than zero.
bool isCandidateBeforeOutgoing = true;
bool isHighwayCandidateBeforeOutgoing = true;
size_t highwayCandidateNumber = 0;
for (auto const & c : possibleTurns.candidates)
{
if (c.m_segment == firstOutgoingSeg)
{
isCandidateBeforeOutgoing = false;
continue;
}
if (IsHighway(c.m_highwayClass, c.m_isLink))
{
++highwayCandidateNumber;
if (highwayCandidateNumber >= 2)
return CarDirection::None; // There are two or more highway candidates from the junction.
isHighwayCandidateBeforeOutgoing = isCandidateBeforeOutgoing;
}
}
if (highwayCandidateNumber == 1)
{
return isHighwayCandidateBeforeOutgoing ? CarDirection::ExitHighwayToRight
: CarDirection::ExitHighwayToLeft;
}
return CarDirection::None;
}
/// * \returns true when
/// * - the route leads from one big road to another one;
/// * - and the other possible turns lead to small roads or these turns too sharp.
/// * Returns false otherwise.
/// \param routeDirection is route direction.
/// \param routeAngle is route angle.
/// \param turnCandidates is all possible ways out from a junction except uturn.
/// \param turnInfo is information about ingoing and outgoing segments of the route.
bool CanDiscardTurnByHighwayClassOrAngles(CarDirection const routeDirection,
double const routeAngle,
std::vector<TurnCandidate> const & turnCandidates,
TurnInfo const & turnInfo,
NumMwmIds const & numMwmIds)
{
if (!IsGoStraightOrSlightTurn(routeDirection))
return false;
IRoadGraph::EdgeVector routeEdges;
graph.GetRouteEdges(routeEdges);
if (turnCandidates.size() < 2)
return true;
if (routeEdges.empty())
HighwayClass outgoingRouteRoadClass = turnInfo.m_outgoing->m_highwayClass;
HighwayClass ingoingRouteRoadClass = turnInfo.m_ingoing->m_highwayClass;
// The turn should be kept if there's no any information about feature id of outgoing segment
// just to be on the safe side. It may happen in case of outgoing segment is a finish segment.
Segment firstOutgoingSegment;
if (!turnInfo.m_outgoing->m_segmentRange.GetFirstSegment(numMwmIds, firstOutgoingSegment))
return false;
if (cancellable.IsCancelled())
for (auto const & t : turnCandidates)
{
// Let's consider all outgoing segments except for route outgoing segment.
if (t.m_segment == firstOutgoingSegment)
continue;
// If route goes from non-link to link and there is not too sharp candidate then turn can't be discarded.
if (!turnInfo.m_ingoing->m_isLink && turnInfo.m_outgoing->m_isLink && abs(t.m_angle) < abs(routeAngle) + 70)
return false;
HighwayClass candidateRoadClass = t.m_highwayClass;
// If outgoing route road is significantly larger than candidate, the candidate can be ignored.
if (CalcDiffRoadClasses(outgoingRouteRoadClass, candidateRoadClass) <= kMinHighwayClassDiff)
continue;
// If outgoing route's road is significantly larger than candidate's service road, the candidate can be ignored.
if (CalcDiffRoadClasses(outgoingRouteRoadClass, candidateRoadClass) <= kMinHighwayClassDiffForService && candidateRoadClass == HighwayClass::Service)
continue;
// If igngoing and outgoing edges are not links
// and outgoing route road is the same or large than ingoing
// then candidate-link can be ignored.
if (!turnInfo.m_ingoing->m_isLink && !turnInfo.m_outgoing->m_isLink &&
CalcDiffRoadClasses(outgoingRouteRoadClass, ingoingRouteRoadClass) <= 0 &&
t.m_isLink)
continue;
// If alternative cadidate's road size is the same or smaller
// and it's angle is sharp enough compared to the route it can be ignored.
if (CalcDiffRoadClasses(outgoingRouteRoadClass, candidateRoadClass) <= 0 &&
abs(t.m_angle) > abs(routeAngle) + kMinAbsAngleDiffForSameOrSmallerRoad)
continue;
if (routeDirection == CarDirection::GoStraight)
{
// If alternative cadidate's road size is smaller
// and it's angle is not very close to the route's one - it can be ignored.
if (CalcDiffRoadClasses(outgoingRouteRoadClass, candidateRoadClass) < 0 &&
abs(t.m_angle) > abs(routeAngle) + kMinAbsAngleDiffForBigStraightRoute)
continue;
// If outgoing route road is the same or large than ingoing
// and candidate's angle is sharp enough compared to the route it can be ignored.
if (CalcDiffRoadClasses(outgoingRouteRoadClass, ingoingRouteRoadClass) <= 0 &&
abs(t.m_angle) > abs(routeAngle) + kMinAbsAngleDiffForStraightRoute)
continue;
}
return false;
FillPathSegmentsAndAdjacentEdgesMap(graph, path, routeEdges, cancellable);
if (cancellable.IsCancelled())
return false;
RoutingEngineResult resultGraph(routeEdges, m_adjacentEdges, m_pathSegments);
CHECK_NOT_EQUAL(m_vehicleType, VehicleType::Count, (m_vehicleType));
auto const res = MakeTurnAnnotation(resultGraph, *m_numMwmIds, m_vehicleType, 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()));
// In case of bicycle routing |m_pathSegments| may have an empty
// |LoadedPathSegment::m_segments| fields. In that case |segments| is empty
// so size of |segments| is not equal to size of |routeEdges|.
if (!segments.empty())
CHECK_EQUAL(segments.size(), routeEdges.size(), ());
}
return true;
}
/*!
* \brief Corrects |turn.m_turn| if |turn.m_turn == CarDirection::GoStraight|.
* If the other way is not sharp enough, turn.m_turn is set to |turnToSet|.
*/
void CorrectGoStraight(TurnCandidate const & notRouteCandidate, double const routeAngle, CarDirection const & turnToSet,
TurnItem & turn)
{
if (turn.m_turn != CarDirection::GoStraight)
return;
// Correct turn if alternative cadidate's angle is not sharp enough compared to the route.
if (abs(notRouteCandidate.m_angle) < abs(routeAngle) + kMinAbsAngleDiffForStraightRoute)
{
LOG(LDEBUG, ("Turn: ", turn.m_index, " GoStraight correction."));
turn.m_turn = turnToSet;
}
}
// If the route goes along the rightmost or the leftmost way among available ones:
// 1. RightmostDirection or LeftmostDirection is selected
// 2. If the turn direction is |CarDirection::GoStraight|
// and there's another not sharp enough turn
// GoStraight is corrected to TurnSlightRight/TurnSlightLeft
// to avoid ambiguity for GoStraight direction: 2 or more almost straight turns.
void CorrectRightmostAndLeftmost(std::vector<TurnCandidate> const & turnCandidates,
Segment const & firstOutgoingSeg, double const turnAngle,
TurnItem & turn)
{
// turnCandidates are sorted by angle from leftmost to rightmost.
// Normally no duplicates should be found. But if they are present we can't identify the leftmost/rightmost by order.
if (adjacent_find(turnCandidates.begin(), turnCandidates.end(), base::EqualsBy(&TurnCandidate::m_angle)) != turnCandidates.end())
{
LOG(LWARNING, ("nodes.candidates are not expected to have same m_angle."));
return;
}
double constexpr kMaxAbsAngleConsideredLeftOrRightMost = 90;
// Go from left to right to findout if the route goes through the leftmost candidate and fixes can be applied.
// Other candidates which are sharper than kMaxAbsAngleConsideredLeftOrRightMost are ignored.
for (auto candidate = turnCandidates.begin(); candidate != turnCandidates.end(); ++candidate)
{
if (candidate->m_segment == firstOutgoingSeg && candidate + 1 != turnCandidates.end())
{
// The route goes along the leftmost candidate.
turn.m_turn = LeftmostDirection(turnAngle);
if (IntermediateDirection(turnAngle) != turn.m_turn)
LOG(LDEBUG, ("Turn: ", turn.m_index, " LeftmostDirection correction."));
// Compare with the next candidate to the right.
CorrectGoStraight(*(candidate + 1), candidate->m_angle, CarDirection::TurnSlightLeft, turn);
break;
}
// Check if this candidate is considered as leftmost as not too sharp.
// If yes - this candidate is leftmost, not route's one.
if (candidate->m_angle > -kMaxAbsAngleConsideredLeftOrRightMost)
break;
}
// Go from right to left to findout if the route goes through the rightmost candidate anf fixes can be applied.
// Other candidates which are sharper than kMaxAbsAngleConsideredLeftOrRightMost are ignored.
for (auto candidate = turnCandidates.rbegin(); candidate != turnCandidates.rend(); ++candidate)
{
if (candidate->m_segment == firstOutgoingSeg && candidate + 1 != turnCandidates.rend())
{
// The route goes along the rightmost candidate.
turn.m_turn = RightmostDirection(turnAngle);
if (IntermediateDirection(turnAngle) != turn.m_turn)
LOG(LDEBUG, ("Turn: ", turn.m_index, " RighmostDirection correction."));
// Compare with the next candidate to the left.
CorrectGoStraight(*(candidate + 1), candidate->m_angle, CarDirection::TurnSlightRight, turn);
break;
}
// Check if this candidate is considered as rightmost as not too sharp.
// If yes - this candidate is rightmost, not route's one.
if (candidate->m_angle < kMaxAbsAngleConsideredLeftOrRightMost)
break;
};
}
void GetTurnDirectionBasic(IRoutingResult const & result, size_t const outgoingSegmentIndex,
NumMwmIds const & numMwmIds, RoutingSettings const & vehicleSettings,
TurnItem & turn)
{
TurnInfo turnInfo;
if (!GetTurnInfo(result, outgoingSegmentIndex, vehicleSettings, turnInfo))
return;
turn.m_sourceName = turnInfo.m_ingoing->m_name;
turn.m_targetName = turnInfo.m_outgoing->m_name;
turn.m_turn = CarDirection::None;
ASSERT_GREATER(turnInfo.m_ingoing->m_path.size(), 1, ());
TurnCandidates nodes;
size_t ingoingCount;
m2::PointD const junctionPoint = turnInfo.m_ingoing->m_path.back().GetPoint();
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;
// No turns are needed on transported road.
if (turnInfo.m_ingoing->m_highwayClass == HighwayClass::Transported && turnInfo.m_outgoing->m_highwayClass == HighwayClass::Transported)
return;
Segment firstOutgoingSeg;
bool const isFirstOutgoingSegValid = turnInfo.m_outgoing->m_segmentRange.GetFirstSegment(numMwmIds, firstOutgoingSeg);
if (!isFirstOutgoingSegValid)
return;
CorrectCandidatesSegmentByOutgoing(turnInfo, firstOutgoingSeg, nodes.candidates);
RemoveUTurnCandidate(turnInfo, numMwmIds, nodes.candidates);
auto const & turnCandidates = nodes.candidates;
// Check for enter or exit to/from roundabout.
if (turnInfo.m_ingoing->m_onRoundabout || turnInfo.m_outgoing->m_onRoundabout)
{
turn.m_turn = GetRoundaboutDirection(turnInfo, nodes, numMwmIds);
return;
}
double const turnAngle = CalcTurnAngle(result, outgoingSegmentIndex, numMwmIds, vehicleSettings);
CarDirection const intermediateDirection = IntermediateDirection(turnAngle);
// Checking for exits from highways.
turn.m_turn = TryToGetExitDirection(nodes, turnInfo, firstOutgoingSeg, intermediateDirection);
if (turn.m_turn != CarDirection::None)
return;
if (turnCandidates.size() == 1)
{
ASSERT(turnCandidates.front().m_segment == firstOutgoingSeg, ());
if (IsGoStraightOrSlightTurn(intermediateDirection))
return;
// IngoingCount includes ingoing segment and reversed outgoing (if it is not one-way).
// If any other one is present, turn (not straight or slight) is kept to prevent user from going to oneway alternative.
/// @todo Min abs angle of ingoing ones should be considered. If it's much bigger than route angle - ignore ingoing ones.
/// Now this data is not available from IRoutingResult::GetPossibleTurns().
if (ingoingCount <= 1 + size_t(!turnInfo.m_outgoing->m_isOneWay))
return;
}
// This angle is calculated using only 1 segment back and forward, not like turnAngle.
double turnOneSegmentAngle = CalcOneSegmentTurnAngle(turnInfo);
// To not discard some disputable turns let's use max by modulus from turnOneSegmentAngle and turnAngle.
// It's natural since angles of turnCandidates are calculated in IRoutingResult::GetPossibleTurns()
// according to CalcOneSegmentTurnAngle logic. And to be safe turnAngle is used too.
double turnAngleToCompare = turnAngle;
if (turnOneSegmentAngle <= 0 && turnAngle <= 0)
turnAngleToCompare = min(turnOneSegmentAngle, turnAngle);
else if (turnOneSegmentAngle >= 0 && turnAngle >= 0)
turnAngleToCompare = max(turnOneSegmentAngle, turnAngle);
else if (abs(turnOneSegmentAngle) > 10)
LOG(LWARNING, ("Significant angles are expected to have the same sign."));
if (CanDiscardTurnByHighwayClassOrAngles(intermediateDirection, turnAngleToCompare, turnCandidates, turnInfo, numMwmIds))
return;
turn.m_turn = intermediateDirection;
if (turnCandidates.size() >= 2)
CorrectRightmostAndLeftmost(turnCandidates, firstOutgoingSeg, turnAngle, turn);
}
size_t CheckUTurnOnRoute(IRoutingResult const & result, size_t const outgoingSegmentIndex,
NumMwmIds const & numMwmIds, RoutingSettings const & vehicleSettings,
TurnItem & turn)
{
size_t constexpr kUTurnLookAhead = 3;
double constexpr kUTurnHeadingSensitivity = math::pi / 10.0;
auto const & segments = result.GetSegments();
// In this function we process the turn between the previous and the current
// segments. So we need a shift to get the previous segment.
ASSERT_GREATER(segments.size(), 1, ());
ASSERT_GREATER(outgoingSegmentIndex, 0, ());
ASSERT_GREATER(segments.size(), outgoingSegmentIndex, ());
auto const & masterSegment = segments[outgoingSegmentIndex - 1];
if (masterSegment.m_path.size() < 2)
return 0;
// Roundabout is not the UTurn.
if (masterSegment.m_onRoundabout)
return 0;
for (size_t i = 0; i < kUTurnLookAhead && i + outgoingSegmentIndex < segments.size(); ++i)
{
auto const & checkedSegment = segments[outgoingSegmentIndex + i];
if (checkedSegment.m_path.size() < 2)
return 0;
if (checkedSegment.m_name == masterSegment.m_name &&
checkedSegment.m_highwayClass == masterSegment.m_highwayClass &&
checkedSegment.m_isLink == masterSegment.m_isLink && !checkedSegment.m_onRoundabout)
{
auto const & path = masterSegment.m_path;
auto const & pointBeforeTurn = path[path.size() - 2];
auto const & turnPoint = path[path.size() - 1];
auto const & pointAfterTurn = checkedSegment.m_path[1];
// Same segment UTurn case.
if (i == 0)
{
// TODO Fix direction calculation.
// Warning! We can not determine UTurn direction in single edge case. So we use UTurnLeft.
// We decided to add driving rules (left-right sided driving) to mwm header.
if (pointBeforeTurn == pointAfterTurn && turnPoint != pointBeforeTurn)
{
turn.m_turn = CarDirection::UTurnLeft;
return 1;
}
// Wide UTurn must have link in it's middle.
return 0;
}
// Avoid the UTurn on unnamed roads inside the rectangle based distinct.
if (checkedSegment.m_name.empty())
return 0;
// Avoid returning to the same edge after uturn somewere else.
if (pointBeforeTurn == pointAfterTurn)
return 0;
m2::PointD const v1 = turnPoint.GetPoint() - pointBeforeTurn.GetPoint();
m2::PointD const v2 = pointAfterTurn.GetPoint() - checkedSegment.m_path[0].GetPoint();
auto angle = ang::TwoVectorsAngle(m2::PointD::Zero(), v1, v2);
if (!base::AlmostEqualAbs(angle, math::pi, kUTurnHeadingSensitivity))
return 0;
// Determine turn direction.
m2::PointD const junctionPoint = masterSegment.m_path.back().GetPoint();
m2::PointD const ingoingPoint = GetPointForTurn(
result, outgoingSegmentIndex, numMwmIds, vehicleSettings.m_maxIngoingPointsCount,
vehicleSettings.m_minIngoingDistMeters, false /* forward */);
m2::PointD const outgoingPoint = GetPointForTurn(
result, outgoingSegmentIndex, numMwmIds, vehicleSettings.m_maxOutgoingPointsCount,
vehicleSettings.m_minOutgoingDistMeters, true /* forward */);
if (PiMinusTwoVectorsAngle(junctionPoint, ingoingPoint, outgoingPoint) < 0)
turn.m_turn = CarDirection::UTurnLeft;
else
turn.m_turn = CarDirection::UTurnRight;
return i + 1;
}
}
return 0;
}
bool FixupLaneSet(CarDirection turn, std::vector<SingleLaneInfo> & lanes,
function<bool(LaneWay l, CarDirection t)> checker)
{
bool isLaneConformed = false;
// 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)
{
if (checker(laneWay, turn))
{
singleLane.m_isRecommended = true;
isLaneConformed = true;
break;
}
}
}
return isLaneConformed;
}
void SelectRecommendedLanes(Route::TTurns & turnsDir)
{
for (auto & t : turnsDir)
{
std::vector<SingleLaneInfo> & lanes = t.m_lanes;
if (lanes.empty())
continue;
CarDirection 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.
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.
FixupLaneSet(turn, lanes, &IsLaneWayConformedTurnDirectionApproximately);
}
}
} // namespace routing

View file

@ -16,16 +16,54 @@
namespace routing
{
class CarDirectionsEngine : public DirectionsEngine
{
public:
CarDirectionsEngine(DataSource const & dataSource, std::shared_ptr<NumMwmIds> numMwmIds);
// DirectionsEngine override:
bool Generate(IndexRoadGraph const & graph, std::vector<geometry::PointWithAltitude> const & path,
base::Cancellable const & cancellable, Route::TTurns & turns,
Route::TStreets & streetNames,
std::vector<geometry::PointWithAltitude> & routeGeometry,
std::vector<Segment> & segments) override;
protected:
virtual size_t GetTurnDirection(turns::IRoutingResult const & result, size_t const outgoingSegmentIndex,
NumMwmIds const & numMwmIds,
RoutingSettings const & vehicleSettings, turns::TurnItem & turn);
virtual void FixupTurns(std::vector<geometry::PointWithAltitude> const & junctions,
Route::TTurns & turnsDir);
};
/*!
* \brief Selects lanes which are recommended for an end user.
*/
void SelectRecommendedLanes(Route::TTurns & turnsDir);
void FixupCarTurns(std::vector<geometry::PointWithAltitude> const & junctions, Route::TTurns & turnsDir);
/*!
* \brief Finds an U-turn that starts from master segment and returns how many segments it lasts.
* \returns an index in |segments| that has the opposite direction with master segment
* (|segments[currentSegment - 1]|) and 0 if there is no UTurn.
* \warning |currentSegment| must be greater than 0.
*/
size_t CheckUTurnOnRoute(turns::IRoutingResult const & result, size_t const outgoingSegmentIndex,
NumMwmIds const & numMwmIds, RoutingSettings const & vehicleSettings,
turns::TurnItem & turn);
/*!
* \brief Calculates a turn instruction if the ingoing edge or (and) the outgoing edge belongs to a
* roundabout.
* \return Returns one of the following results:
* - TurnDirection::EnterRoundAbout if the ingoing edge does not belong to a roundabout
* and the outgoing edge belongs to a roundabout.
* - TurnDirection::StayOnRoundAbout if the ingoing edge and the outgoing edge belong to a
* roundabout
* and there is a reasonalbe way to leave the junction besides the outgoing edge.
* This function does not return TurnDirection::StayOnRoundAbout for small ways to leave the
* roundabout.
* - TurnDirection::NoTurn if the ingoing edge and the outgoing edge belong to a roundabout
* (a) and there is a single way (outgoing edge) to leave the junction.
* (b) and there is a way(s) besides outgoing edge to leave the junction (the roundabout)
* but it is (they are) relevantly small.
*/
turns::CarDirection GetRoundaboutDirectionBasic(bool isIngoingEdgeRoundabout, bool isOutgoingEdgeRoundabout,
bool isMultiTurnJunction, bool keepTurnByHighwayClass);
} // namespace routing

View file

@ -1,6 +1,7 @@
#include "routing/directions_engine.hpp"
#include "routing/fake_feature_ids.hpp"
#include "routing/routing_helpers.hpp"
#include "routing/routing_callbacks.hpp"
#include "traffic/traffic_info.hpp"
@ -229,4 +230,182 @@ void DirectionsEngine::FillPathSegmentsAndAdjacentEdgesMap(
startSegId = kInvalidSegId;
}
}
bool DirectionsEngine::Generate(IndexRoadGraph const & graph,
vector<geometry::PointWithAltitude> const & path,
base::Cancellable const & cancellable, Route::TTurns & turns,
Route::TStreets & streetNames,
vector<geometry::PointWithAltitude> & routeGeometry,
vector<Segment> & segments)
{
CHECK(m_numMwmIds, ());
m_adjacentEdges.clear();
m_pathSegments.clear();
turns.clear();
streetNames.clear();
segments.clear();
IndexRoadGraph::EdgeVector routeEdges;
CHECK_NOT_EQUAL(m_vehicleType, VehicleType::Count, (m_vehicleType));
if (m_vehicleType == VehicleType::Transit)
{
routeGeometry = path;
graph.GetRouteSegments(segments);
graph.GetRouteEdges(routeEdges);
turns.emplace_back(routeEdges.size(), turns::PedestrianDirection::ReachedYourDestination);
return true;
}
routeGeometry.clear();
if (path.size() <= 1)
return false;
graph.GetRouteEdges(routeEdges);
if (routeEdges.empty())
return false;
if (cancellable.IsCancelled())
return false;
FillPathSegmentsAndAdjacentEdgesMap(graph, path, routeEdges, cancellable);
if (cancellable.IsCancelled())
return false;
auto const res = MakeTurnAnnotation(routeEdges, cancellable,
routeGeometry, turns, streetNames, segments);
if (res != RouterResultCode::NoError)
return false;
// In case of bicycle routing |m_pathSegments| may have an empty
// |LoadedPathSegment::m_segments| fields. In that case |segments| is empty
// so size of |segments| is not equal to size of |routeEdges|.
if (!segments.empty())
CHECK_EQUAL(segments.size(), routeEdges.size(), ());
return true;
}
/*!
* \brief Compute turn and time estimation structs for the abstract route result.
* \param result abstract routing result to annotate.
* \param delegate Routing callbacks delegate.
* \param points Storage for unpacked points of the path.
* \param turnsDir output turns annotation storage.
* \param streets output street names along the path.
* \param segments route segments.
* \return routing operation result code.
*/
// Normally loadedSegments structure is:
// - Start point. Fake loop LoadedPathSegment with 1 segment of zero length.
// - Straight jump from start point to the beginning of real route. LoadedPathSegment with 1 segment.
// - Real route. N LoadedPathSegments, each with arbitrary amount of segments. N >= 1.
// - Straight jump from the end of real route to finish point. LoadedPathSegment with 1 segment.
// - Finish point. Fake loop LoadedPathSegment with 1 segment of zero length.
// So minimal amount of segments is 5.
// Resulting structure of turnsDir:
// No Turn for 0th segment (no ingoing). m_index == 0.
// No Turn for 1st segment (ingoing fake loop) - at start point. m_index == 1.
// No Turn for 2nd (ingoing == jump) - at beginning of real route. m_index == 2.
// Possible turn for next N-1 segments. m_index >= 3.
// No Turn for (2 + N + 1)th segment (outgoing jump) - at finish point. m_index = 3 + M.
// No Turn for (2 + N + 2)th segment (outgoing fake loop) - at finish point. m_index == 4 + M.
// Added ReachedYourDestination - at finish point. m_index == 4 + M.
// Where M - total amount of all segments from all LoadedPathSegments (size of |segments|).
// So minimum m_index of ReachedYourDestination is 5 (real route with single segment),
// and minimal |turnsDir| is - single ReachedYourDestination with m_index == 5.
RouterResultCode DirectionsEngine::MakeTurnAnnotation(IndexRoadGraph::EdgeVector const & routeEdges,
base::Cancellable const & cancellable,
std::vector<geometry::PointWithAltitude> & junctions,
Route::TTurns & turnsDir, Route::TStreets & streets,
std::vector<Segment> & segments)
{
RoutingEngineResult result(routeEdges, m_adjacentEdges, m_pathSegments);
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());
RoutingSettings const vehicleSettings = GetRoutingSettings(m_vehicleType);
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<size_t>(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<size_t>(outgoingSegmentDist);
TurnItem turnItem;
turnItem.m_index = static_cast<uint32_t>(junctions.size() - 1);
skipTurnSegments = GetTurnDirection(result, outgoingSegmentIndex, *m_numMwmIds, vehicleSettings, turnItem);
if (!turnItem.IsTurnNone())
turnsDir.push_back(move(turnItem));
}
if (skipTurnSegments > 0)
--skipTurnSegments;
// Path geometry.
CHECK_GREATER_OR_EQUAL(loadedSegmentIt->m_path.size(), 2, ());
// Note. Every LoadedPathSegment in TUnpackedPathSegments contains LoadedPathSegment::m_path
// of several Junctions. Last PointWithAltitude in a LoadedPathSegment::m_path is equal to first
// junction in next LoadedPathSegment::m_path in vector TUnpackedPathSegments:
// *---*---*---*---* *---* *---*---*---*
// *---*---* *---*---*---*
// To prevent having repetitions in |junctions| list it's necessary to take the first point only
// from the first item of |loadedSegments|. The beginning should be ignored for the rest
// |m_path|.
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());
}
// Path found. Points will be replaced by start and end edges junctions.
if (junctions.size() == 1)
junctions.push_back(junctions.front());
if (junctions.size() < 2)
return RouterResultCode::RouteNotFound;
junctions.front() = result.GetStartPoint();
junctions.back() = result.GetEndPoint();
FixupTurns(junctions, turnsDir);
/*
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()));
*/
return RouterResultCode::NoError;
}
} // namespace routing

View file

@ -19,6 +19,15 @@
namespace routing
{
namespace turns
{
class IRoutingResult;
struct TurnItem;
}
enum class RouterResultCode;
class DirectionsEngine
{
public:
@ -35,17 +44,28 @@ public:
/// \brief Generates all args which are passed by reference.
/// \param path is points of the route. It should not be empty.
/// \returns true if fields passed by reference are filled correctly and false otherwise.
virtual bool Generate(IndexRoadGraph const & graph,
std::vector<geometry::PointWithAltitude> const & path,
base::Cancellable const & cancellable, Route::TTurns & turns,
Route::TStreets & streetNames,
std::vector<geometry::PointWithAltitude> & routeGeometry,
std::vector<Segment> & segments) = 0;
bool Generate(IndexRoadGraph const & graph,
std::vector<geometry::PointWithAltitude> const & path,
base::Cancellable const & cancellable, Route::TTurns & turns,
Route::TStreets & streetNames,
std::vector<geometry::PointWithAltitude> & routeGeometry,
std::vector<Segment> & segments);
void Clear();
void SetVehicleType(VehicleType const & vehicleType) { m_vehicleType = vehicleType; }
protected:
/*!
* \brief GetTurnDirection makes a primary decision about turns on the route.
* \param outgoingSegmentIndex index of an outgoing segments in vector result.GetSegments().
* \param turn is used for keeping the result of turn calculation.
*/
virtual size_t GetTurnDirection(turns::IRoutingResult const & result, size_t const outgoingSegmentIndex,
NumMwmIds const & numMwmIds,
RoutingSettings const & vehicleSettings, turns::TurnItem & turn) = 0;
virtual void FixupTurns(std::vector<geometry::PointWithAltitude> const & junctions,
Route::TTurns & turnsDir) = 0;
FeaturesLoaderGuard & GetLoader(MwmSet::MwmId const & id);
void LoadPathAttributes(FeatureID const & featureId, LoadedPathSegment & pathSegment);
void GetSegmentRangeAndAdjacentEdges(IRoadGraph::EdgeListT const & outgoingEdges,
@ -70,5 +90,12 @@ protected:
std::shared_ptr<NumMwmIds> m_numMwmIds;
std::unique_ptr<FeaturesLoaderGuard> m_loader;
VehicleType m_vehicleType = VehicleType::Count;
private:
RouterResultCode MakeTurnAnnotation(IndexRoadGraph::EdgeVector const & routeEdges,
base::Cancellable const & cancellable,
std::vector<geometry::PointWithAltitude> & junctions,
Route::TTurns & turnsDir, Route::TStreets & streets,
std::vector<Segment> & segments);
};
} // namespace routing

View file

@ -2,6 +2,8 @@
#include "routing/road_graph.hpp"
#include "routing/turns_generator.hpp"
#include "routing/turns.hpp"
#include "routing/turns_generator_utils.hpp"
#include "base/assert.hpp"
#include "base/logging.hpp"
@ -18,68 +20,93 @@ PedestrianDirectionsEngine::PedestrianDirectionsEngine(DataSource const & dataSo
{
}
bool PedestrianDirectionsEngine::Generate(IndexRoadGraph const & graph,
vector<geometry::PointWithAltitude> const & path,
base::Cancellable const & cancellable,
Route::TTurns & turns, Route::TStreets & streetNames,
vector<geometry::PointWithAltitude> & routeGeometry,
vector<Segment> & segments)
using namespace turns;
// Angles in degrees for finding route segments with no actual forks.
double constexpr kMaxForwardAngleCandidates = 95.0;
double constexpr kMaxForwardAngleActual = 60.0;
size_t PedestrianDirectionsEngine::GetTurnDirection(IRoutingResult const & result, size_t const outgoingSegmentIndex,
NumMwmIds const & numMwmIds,
RoutingSettings const & vehicleSettings, TurnItem & turn)
{
CHECK(m_numMwmIds, ());
TurnInfo turnInfo;
if (!GetTurnInfo(result, outgoingSegmentIndex, vehicleSettings, turnInfo))
return 0;
m_adjacentEdges.clear();
m_pathSegments.clear();
turns.clear();
streetNames.clear();
segments.clear();
double const turnAngle = CalcTurnAngle(result, outgoingSegmentIndex, numMwmIds, vehicleSettings);
IndexRoadGraph::EdgeVector routeEdges;
turn.m_sourceName = turnInfo.m_ingoing->m_name;
turn.m_targetName = turnInfo.m_outgoing->m_name;
turn.m_pedestrianTurn = PedestrianDirection::None;
CHECK_NOT_EQUAL(m_vehicleType, VehicleType::Count, (m_vehicleType));
ASSERT_GREATER(turnInfo.m_ingoing->m_path.size(), 1, ());
if (m_vehicleType == VehicleType::Transit)
TurnCandidates nodes;
size_t ingoingCount = 0;
m2::PointD const junctionPoint = turnInfo.m_ingoing->m_path.back().GetPoint();
result.GetPossibleTurns(turnInfo.m_ingoing->m_segmentRange, junctionPoint, ingoingCount, nodes);
if (nodes.isCandidatesAngleValid)
{
routeGeometry = path;
graph.GetRouteSegments(segments);
graph.GetRouteEdges(routeEdges);
turns.emplace_back(routeEdges.size(), turns::PedestrianDirection::ReachedYourDestination);
return true;
ASSERT(is_sorted(nodes.candidates.begin(), nodes.candidates.end(), base::LessBy(&TurnCandidate::m_angle)),
("Turn candidates should be sorted by its angle field."));
}
routeGeometry.clear();
if (nodes.candidates.size() == 0)
return 0;
if (path.size() <= 1)
return false;
turn.m_pedestrianTurn = IntermediateDirectionPedestrian(turnAngle);
size_t const pathSize = path.size();
if (turn.m_pedestrianTurn == PedestrianDirection::GoStraight)
{
turn.m_pedestrianTurn = PedestrianDirection::None;
return 0;
}
graph.GetRouteEdges(routeEdges);
RemoveUTurnCandidate(turnInfo, numMwmIds, nodes.candidates);
if (routeEdges.empty())
return false;
// If there is no fork on the road we don't need to generate any turn. It is pointless because
// there is no possibility of leaving the route.
if (nodes.candidates.size() <= 1)
turn.m_pedestrianTurn = PedestrianDirection::None;
if (std::fabs(CalcOneSegmentTurnAngle(turnInfo)) < kMaxForwardAngleActual && HasSingleForwardTurn(nodes, kMaxForwardAngleCandidates))
turn.m_pedestrianTurn = PedestrianDirection::None;
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 =
MakeTurnAnnotation(resultGraph, *m_numMwmIds, m_vehicleType, 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()));
return true;
return 0;
}
void PedestrianDirectionsEngine::FixupTurns(std::vector<geometry::PointWithAltitude> const & junctions,
Route::TTurns & turnsDir)
{
uint32_t turn_index = static_cast<uint32_t>(junctions.size() - 1);
turnsDir.emplace_back(TurnItem(turn_index, PedestrianDirection::ReachedYourDestination));
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;
}
#ifdef DEBUG
for (auto const & t : turnsDir)
LOG(LDEBUG, (GetTurnString(t.m_turn), ":", t.m_index, t.m_sourceName, "-",
t.m_targetName, "exit:", t.m_exitNum));
#endif
}
} // namespace routing

View file

@ -20,11 +20,11 @@ class PedestrianDirectionsEngine : public DirectionsEngine
public:
PedestrianDirectionsEngine(DataSource const & dataSource, std::shared_ptr<NumMwmIds> numMwmIds);
// DirectionsEngine override:
bool Generate(IndexRoadGraph const & graph, std::vector<geometry::PointWithAltitude> const & path,
base::Cancellable const & cancellable, Route::TTurns & turns,
Route::TStreets & streetNames,
std::vector<geometry::PointWithAltitude> & routeGeometry,
std::vector<Segment> & segments) override;
protected:
virtual size_t GetTurnDirection(IRoutingResult const & result, size_t const outgoingSegmentIndex,
NumMwmIds const & numMwmIds,
RoutingSettings const & vehicleSettings, TurnItem & turn);
virtual void FixupTurns(std::vector<geometry::PointWithAltitude> const & junctions,
Route::TTurns & turnsDir);
};
} // namespace routing

View file

@ -6,6 +6,7 @@
#include "routing/turns.hpp"
#include "routing/turns_generator_utils.hpp"
#include "routing/turns_generator.hpp"
#include "routing/car_directions.hpp"
#include "indexer/ftypes_matcher.hpp"
@ -178,10 +179,9 @@ UNIT_TEST(TestFixupTurns)
// is used for initialization of vector<TurnItem> below.
Route::TTurns turnsDir1 = {{0, CarDirection::EnterRoundAbout},
{1, CarDirection::StayOnRoundAbout},
{2, CarDirection::LeaveRoundAbout},
{3, CarDirection::ReachedYourDestination}};
{2, CarDirection::LeaveRoundAbout}};
FixupTurns(pointsMerc1, turnsDir1);
FixupCarTurns(pointsMerc1, turnsDir1);
Route::TTurns const expectedTurnDir1 = {{0, CarDirection::EnterRoundAbout, 2},
{2, CarDirection::LeaveRoundAbout, 2},
{3, CarDirection::ReachedYourDestination}};
@ -194,10 +194,9 @@ UNIT_TEST(TestFixupTurns)
{{kSquareNearZero.maxX(), kSquareNearZero.maxY()}, geometry::kDefaultAltitudeMeters},
};
Route::TTurns turnsDir2 = {{0, CarDirection::GoStraight},
{1, CarDirection::TurnLeft},
{2, CarDirection::ReachedYourDestination}};
{1, CarDirection::TurnLeft}};
FixupTurns(pointsMerc2, turnsDir2);
FixupCarTurns(pointsMerc2, turnsDir2);
Route::TTurns const expectedTurnDir2 = {{1, CarDirection::TurnLeft},
{2, CarDirection::ReachedYourDestination}};
TEST_EQUAL(turnsDir2, expectedTurnDir2, ());
@ -208,10 +207,9 @@ UNIT_TEST(TestFixupTurns)
{{kSquareNearZero.minX(), kSquareNearZero.maxY()}, geometry::kDefaultAltitudeMeters},
{{kSquareNearZero.maxX(), kSquareNearZero.maxY()}, geometry::kDefaultAltitudeMeters},
};
Route::TTurns turnsDir3 = {{1, CarDirection::TurnRight},
{2, CarDirection::ReachedYourDestination}};
Route::TTurns turnsDir3 = {{1, CarDirection::TurnRight}};
FixupTurns(pointsMerc3, turnsDir3);
FixupCarTurns(pointsMerc3, turnsDir3);
Route::TTurns const expectedTurnDir3 = {{1, CarDirection::TurnRight},
{2, CarDirection::ReachedYourDestination}};
TEST_EQUAL(turnsDir3, expectedTurnDir3, ());
@ -283,12 +281,12 @@ UNIT_TEST(TestGetRoundaboutDirection)
// The signature of GetRoundaboutDirection function is
// GetRoundaboutDirection(bool isIngoingEdgeRoundabout, bool isOutgoingEdgeRoundabout,
// bool isMultiTurnJunction, bool keepTurnByHighwayClass)
TEST_EQUAL(GetRoundaboutDirection(true, true, true, true), CarDirection::StayOnRoundAbout, ());
TEST_EQUAL(GetRoundaboutDirection(true, true, true, false), CarDirection::None, ());
TEST_EQUAL(GetRoundaboutDirection(true, true, false, true), CarDirection::None, ());
TEST_EQUAL(GetRoundaboutDirection(true, true, false, false), CarDirection::None, ());
TEST_EQUAL(GetRoundaboutDirection(false, true, false, true), CarDirection::EnterRoundAbout, ());
TEST_EQUAL(GetRoundaboutDirection(true, false, false, false), CarDirection::LeaveRoundAbout, ());
TEST_EQUAL(GetRoundaboutDirectionBasic(true, true, true, true), CarDirection::StayOnRoundAbout, ());
TEST_EQUAL(GetRoundaboutDirectionBasic(true, true, true, false), CarDirection::None, ());
TEST_EQUAL(GetRoundaboutDirectionBasic(true, true, false, true), CarDirection::None, ());
TEST_EQUAL(GetRoundaboutDirectionBasic(true, true, false, false), CarDirection::None, ());
TEST_EQUAL(GetRoundaboutDirectionBasic(false, true, false, true), CarDirection::EnterRoundAbout, ());
TEST_EQUAL(GetRoundaboutDirectionBasic(true, false, false, false), CarDirection::LeaveRoundAbout, ());
}
UNIT_TEST(TestInvertDirection)

View file

@ -6,8 +6,6 @@
#include "indexer/ftypes_matcher.hpp"
#include "geometry/angles.hpp"
#include "base/checked_cast.hpp"
#include "base/stl_helpers.hpp"
@ -22,85 +20,6 @@ namespace turns
using namespace std;
using namespace ftypes;
// Angles in degrees for finding route segments with no actual forks.
double constexpr kMaxForwardAngleCandidates = 95.0;
double constexpr kMaxForwardAngleActual = 60.0;
// Min difference of route and alternative turn abs angles in degrees
// to ignore alternative turn (when route direction is GoStraight).
double constexpr kMinAbsAngleDiffForStraightRoute = 25.0;
// Min difference of route and alternative turn abs angles in degrees
// to ignore alternative turn (when route direction is bigger and GoStraight).
double constexpr kMinAbsAngleDiffForBigStraightRoute = 5;
// Min difference of route and alternative turn abs angles in degrees
// to ignore this alternative candidate (when alternative road is the same or smaller).
double constexpr kMinAbsAngleDiffForSameOrSmallerRoad = 35.0;
// Min difference between HighwayClasses of the route segment and alternative turn segment
// to ignore this alternative candidate.
int constexpr kMinHighwayClassDiff = -2;
// Min difference between HighwayClasses of the route segment and alternative turn segment
// to ignore this alternative candidate (when alternative road is service).
int constexpr kMinHighwayClassDiffForService = -1;
/// \brief Return |CarDirection::ExitHighwayToRight| or |CarDirection::ExitHighwayToLeft|
/// or return |CarDirection::None| if no exit is detected.
/// \note The function makes a decision about turn based on geometry of the route and turn
/// candidates, so it works correctly for both left and right hand traffic.
CarDirection TryToGetExitDirection(TurnCandidates const & possibleTurns, TurnInfo const & turnInfo,
Segment const & firstOutgoingSeg, CarDirection const intermediateDirection)
{
if (!possibleTurns.isCandidatesAngleValid)
return CarDirection::None;
if (!IsHighway(turnInfo.m_ingoing->m_highwayClass, turnInfo.m_ingoing->m_isLink))
return CarDirection::None;
if (!turnInfo.m_outgoing->m_isLink && !(IsSmallRoad(turnInfo.m_outgoing->m_highwayClass) && IsGoStraightOrSlightTurn(intermediateDirection)))
return CarDirection::None;
// At this point it is known that the route goes form a highway to a link road or to a small road
// which has a slight angle with the highway.
// Considering cases when the route goes from a highway to a link or a small road.
// Checking all turn candidates (sorted by their angles) and looking for the road which is a
// continuation of the ingoing segment. If the continuation is on the right hand of the route
// it's an exit to the left. If the continuation is on the left hand of the route
// it's an exit to the right.
// Note. The angle which is used for sorting turn candidates in |possibleTurns.candidates|
// is a counterclockwise angle between the ingoing route edge and corresponding candidate.
// For left turns the angle is less than zero and for right ones it is more than zero.
bool isCandidateBeforeOutgoing = true;
bool isHighwayCandidateBeforeOutgoing = true;
size_t highwayCandidateNumber = 0;
for (auto const & c : possibleTurns.candidates)
{
if (c.m_segment == firstOutgoingSeg)
{
isCandidateBeforeOutgoing = false;
continue;
}
if (IsHighway(c.m_highwayClass, c.m_isLink))
{
++highwayCandidateNumber;
if (highwayCandidateNumber >= 2)
return CarDirection::None; // There are two or more highway candidates from the junction.
isHighwayCandidateBeforeOutgoing = isCandidateBeforeOutgoing;
}
}
if (highwayCandidateNumber == 1)
{
return isHighwayCandidateBeforeOutgoing ? CarDirection::ExitHighwayToRight
: CarDirection::ExitHighwayToLeft;
}
return CarDirection::None;
}
/// * \returns true when
/// * - the route leads from one big road to another one;
/// * - and the other possible turns lead to small roads;
@ -144,153 +63,6 @@ bool CanDiscardTurnByHighwayClass(std::vector<TurnCandidate> const & turnCandida
return true;
}
/// * \returns true when
/// * - the route leads from one big road to another one;
/// * - and the other possible turns lead to small roads or these turns too sharp.
/// * Returns false otherwise.
/// \param routeDirection is route direction.
/// \param routeAngle is route angle.
/// \param turnCandidates is all possible ways out from a junction except uturn.
/// \param turnInfo is information about ingoing and outgoing segments of the route.
bool CanDiscardTurnByHighwayClassOrAngles(CarDirection const routeDirection,
double const routeAngle,
std::vector<TurnCandidate> const & turnCandidates,
TurnInfo const & turnInfo,
NumMwmIds const & numMwmIds)
{
if (!IsGoStraightOrSlightTurn(routeDirection))
return false;
if (turnCandidates.size() < 2)
return true;
HighwayClass outgoingRouteRoadClass = turnInfo.m_outgoing->m_highwayClass;
HighwayClass ingoingRouteRoadClass = turnInfo.m_ingoing->m_highwayClass;
// The turn should be kept if there's no any information about feature id of outgoing segment
// just to be on the safe side. It may happen in case of outgoing segment is a finish segment.
Segment firstOutgoingSegment;
if (!turnInfo.m_outgoing->m_segmentRange.GetFirstSegment(numMwmIds, firstOutgoingSegment))
return false;
for (auto const & t : turnCandidates)
{
// Let's consider all outgoing segments except for route outgoing segment.
if (t.m_segment == firstOutgoingSegment)
continue;
// If route goes from non-link to link and there is not too sharp candidate then turn can't be discarded.
if (!turnInfo.m_ingoing->m_isLink && turnInfo.m_outgoing->m_isLink && abs(t.m_angle) < abs(routeAngle) + 70)
return false;
HighwayClass candidateRoadClass = t.m_highwayClass;
// If outgoing route road is significantly larger than candidate, the candidate can be ignored.
if (CalcDiffRoadClasses(outgoingRouteRoadClass, candidateRoadClass) <= kMinHighwayClassDiff)
continue;
// If outgoing route's road is significantly larger than candidate's service road, the candidate can be ignored.
if (CalcDiffRoadClasses(outgoingRouteRoadClass, candidateRoadClass) <= kMinHighwayClassDiffForService && candidateRoadClass == HighwayClass::Service)
continue;
// If igngoing and outgoing edges are not links
// and outgoing route road is the same or large than ingoing
// then candidate-link can be ignored.
if (!turnInfo.m_ingoing->m_isLink && !turnInfo.m_outgoing->m_isLink &&
CalcDiffRoadClasses(outgoingRouteRoadClass, ingoingRouteRoadClass) <= 0 &&
t.m_isLink)
continue;
// If alternative cadidate's road size is the same or smaller
// and it's angle is sharp enough compared to the route it can be ignored.
if (CalcDiffRoadClasses(outgoingRouteRoadClass, candidateRoadClass) <= 0 &&
abs(t.m_angle) > abs(routeAngle) + kMinAbsAngleDiffForSameOrSmallerRoad)
continue;
if (routeDirection == CarDirection::GoStraight)
{
// If alternative cadidate's road size is smaller
// and it's angle is not very close to the route's one - it can be ignored.
if (CalcDiffRoadClasses(outgoingRouteRoadClass, candidateRoadClass) < 0 &&
abs(t.m_angle) > abs(routeAngle) + kMinAbsAngleDiffForBigStraightRoute)
continue;
// If outgoing route road is the same or large than ingoing
// and candidate's angle is sharp enough compared to the route it can be ignored.
if (CalcDiffRoadClasses(outgoingRouteRoadClass, ingoingRouteRoadClass) <= 0 &&
abs(t.m_angle) > abs(routeAngle) + kMinAbsAngleDiffForStraightRoute)
continue;
}
return false;
}
return true;
}
/*!
* \brief Returns false when other possible turns leads to service roads;
*/
bool KeepRoundaboutTurnByHighwayClass(TurnCandidates const & possibleTurns,
TurnInfo const & turnInfo, NumMwmIds const & numMwmIds)
{
Segment firstOutgoingSegment;
bool const validFirstOutgoingSeg = turnInfo.m_outgoing->m_segmentRange.GetFirstSegment(numMwmIds, firstOutgoingSegment);
for (auto const & t : possibleTurns.candidates)
{
if (!validFirstOutgoingSeg || t.m_segment == firstOutgoingSegment)
continue;
if (t.m_highwayClass != HighwayClass::Service)
return true;
}
return false;
}
bool FixupLaneSet(CarDirection turn, std::vector<SingleLaneInfo> & lanes,
function<bool(LaneWay l, CarDirection t)> checker)
{
bool isLaneConformed = false;
// 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)
{
if (checker(laneWay, turn))
{
singleLane.m_isRecommended = true;
isLaneConformed = true;
break;
}
}
}
return isLaneConformed;
}
/*!
* \brief Returns ingoing point or outgoing point for turns.
* These points belong to the route but they often are not neighbor of junction point.
* To calculate the resulting point the function implements the following steps:
* - going from junction point along route path according to the direction which is set in GetPointIndex().
* - until one of following conditions is fulfilled:
* - more than |maxPointsCount| points are passed (returns the maxPointsCount-th point);
* - the length of passed parts of segment exceeds maxDistMeters;
* (returns the next point after the event)
* - an important bifurcation point is reached in case of outgoing point is looked up (forward == true).
* \param result information about the route. |result.GetSegments()| is composed of LoadedPathSegment.
* Each LoadedPathSegment is composed of several Segments. The sequence of Segments belongs to
* single feature and does not split by other features.
* \param outgoingSegmentIndex index in |segments|. Junction point noticed above is the first point
* of |outgoingSegmentIndex| segment in |result.GetSegments()|.
* \param maxPointsCount maximum number between the returned point and junction point.
* \param maxDistMeters maximum distance between the returned point and junction point.
* \param forward is direction of moving along the route to calculate the returned point.
* If forward == true the direction is to route finish. If forward == false the direction is to route start.
* \return an ingoing or outgoing point for a turn calculation.
*/
m2::PointD GetPointForTurn(IRoutingResult const & result, size_t outgoingSegmentIndex,
NumMwmIds const & numMwmIds, size_t const maxPointsCount,
double const maxDistMeters, bool const forward)
@ -431,24 +203,6 @@ bool GetPrevInSegmentRoutePoint(IRoutingResult const & result, RoutePointIndex c
return true;
}
/*!
* \brief Corrects |turn.m_turn| if |turn.m_turn == CarDirection::GoStraight|.
* If the other way is not sharp enough, turn.m_turn is set to |turnToSet|.
*/
void CorrectGoStraight(TurnCandidate const & notRouteCandidate, double const routeAngle, CarDirection const & turnToSet,
TurnItem & turn)
{
if (turn.m_turn != CarDirection::GoStraight)
return;
// Correct turn if alternative cadidate's angle is not sharp enough compared to the route.
if (abs(notRouteCandidate.m_angle) < abs(routeAngle) + kMinAbsAngleDiffForStraightRoute)
{
LOG(LDEBUG, ("Turn: ", turn.m_index, " GoStraight correction."));
turn.m_turn = turnToSet;
}
}
bool GetNextRoutePointIndex(IRoutingResult const & result, RoutePointIndex const & index,
NumMwmIds const & numMwmIds, bool const forward,
RoutePointIndex & nextIndex)
@ -469,275 +223,6 @@ bool GetNextRoutePointIndex(IRoutingResult const & result, RoutePointIndex const
return true;
}
// Normally loadedSegments structure is:
// - Start point. Fake loop LoadedPathSegment with 1 segment of zero length.
// - Straight jump from start point to the beginning of real route. LoadedPathSegment with 1 segment.
// - Real route. N LoadedPathSegments, each with arbitrary amount of segments. N >= 1.
// - Straight jump from the end of real route to finish point. LoadedPathSegment with 1 segment.
// - Finish point. Fake loop LoadedPathSegment with 1 segment of zero length.
// So minimal amount of segments is 5.
// Resulting structure of turnsDir:
// No Turn for 0th segment (no ingoing). m_index == 0.
// No Turn for 1st segment (ingoing fake loop) - at start point. m_index == 1.
// No Turn for 2nd (ingoing == jump) - at beginning of real route. m_index == 2.
// Possible turn for next N-1 segments. m_index >= 3.
// No Turn for (2 + N + 1)th segment (outgoing jump) - at finish point. m_index = 3 + M.
// No Turn for (2 + N + 2)th segment (outgoing fake loop) - at finish point. m_index == 4 + M.
// Added ReachedYourDestination - at finish point. m_index == 4 + M.
// Where M - total amount of all segments from all LoadedPathSegments (size of |segments|).
// So minimum m_index of ReachedYourDestination is 5 (real route with single segment),
// and minimal |turnsDir| is - single ReachedYourDestination with m_index == 5.
RouterResultCode MakeTurnAnnotation(IRoutingResult const & result, NumMwmIds const & numMwmIds,
VehicleType const & vehicleType,
base::Cancellable const & cancellable,
std::vector<geometry::PointWithAltitude> & junctions,
Route::TTurns & turnsDir, Route::TStreets & streets,
std::vector<Segment> & 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());
RoutingSettings const vehicleSettings = GetRoutingSettings(vehicleType);
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<size_t>(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<size_t>(outgoingSegmentDist);
TurnItem turnItem;
turnItem.m_index = static_cast<uint32_t>(junctions.size() - 1);
if (vehicleType == VehicleType::Pedestrian)
{
GetTurnDirectionPedestrian(result, outgoingSegmentIndex, numMwmIds, vehicleSettings, turnItem);
}
else
{
skipTurnSegments = CheckUTurnOnRoute(result, outgoingSegmentIndex, numMwmIds, vehicleSettings, turnItem);
if (turnItem.m_turn == CarDirection::None)
GetTurnDirection(result, outgoingSegmentIndex, numMwmIds, vehicleSettings, turnItem);
// Lane information.
if (turnItem.m_turn != CarDirection::None)
{
auto const & ingoingSegment = loadedSegments[outgoingSegmentIndex - 1];
turnItem.m_lanes = ingoingSegment.m_lanes;
}
}
if (!turnItem.IsTurnNone())
turnsDir.push_back(move(turnItem));
}
if (skipTurnSegments > 0)
--skipTurnSegments;
// Path geometry.
CHECK_GREATER_OR_EQUAL(loadedSegmentIt->m_path.size(), 2, ());
// Note. Every LoadedPathSegment in TUnpackedPathSegments contains LoadedPathSegment::m_path
// of several Junctions. Last PointWithAltitude in a LoadedPathSegment::m_path is equal to first
// junction in next LoadedPathSegment::m_path in vector TUnpackedPathSegments:
// *---*---*---*---* *---* *---*---*---*
// *---*---* *---*---*---*
// To prevent having repetitions in |junctions| list it's necessary to take the first point only
// from the first item of |loadedSegments|. The beginning should be ignored for the rest
// |m_path|.
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());
}
// Path found. Points will be replaced by start and end edges junctions.
if (junctions.size() == 1)
junctions.push_back(junctions.front());
if (junctions.size() < 2)
return RouterResultCode::RouteNotFound;
junctions.front() = result.GetStartPoint();
junctions.back() = result.GetEndPoint();
uint32_t turn_index = static_cast<uint32_t>(junctions.size() - 1);
if (vehicleType == VehicleType::Pedestrian)
{
turnsDir.emplace_back(TurnItem(turn_index, PedestrianDirection::ReachedYourDestination));
FixupTurnsPedestrian(junctions, turnsDir);
}
else
{
turnsDir.emplace_back(TurnItem(turn_index, CarDirection::ReachedYourDestination));
FixupTurns(junctions, turnsDir);
}
#ifdef DEBUG
for (auto const & t : turnsDir)
{
if (vehicleType == VehicleType::Pedestrian)
LOG(LDEBUG, (t.m_pedestrianTurn, ":", t.m_index, t.m_sourceName, "-", t.m_targetName));
else
LOG(LDEBUG, (GetTurnString(t.m_turn), ":", t.m_index, t.m_sourceName, "-",
t.m_targetName, "exit:", t.m_exitNum));
}
#endif
return RouterResultCode::NoError;
}
void FixupTurns(std::vector<geometry::PointWithAltitude> const & junctions, Route::TTurns & turnsDir)
{
double const kMergeDistMeters = 15.0;
// For turns that are not EnterRoundAbout/ExitRoundAbout exitNum is always equal to zero.
// If a turn is EnterRoundAbout exitNum is a number of turns between two junctions:
// (1) the route enters to the roundabout;
// (2) the route leaves the roundabout;
uint32_t exitNum = 0;
TurnItem * currentEnterRoundAbout = nullptr;
for (size_t idx = 0; idx < turnsDir.size(); )
{
TurnItem & t = turnsDir[idx];
if (currentEnterRoundAbout && t.m_turn != CarDirection::StayOnRoundAbout && t.m_turn != CarDirection::LeaveRoundAbout)
{
ASSERT(false, ("Only StayOnRoundAbout or LeaveRoundAbout are expected after EnterRoundAbout."));
exitNum = 0;
currentEnterRoundAbout = nullptr;
}
else if (t.m_turn == CarDirection::EnterRoundAbout)
{
ASSERT(!currentEnterRoundAbout, ("It's not expected to find new EnterRoundAbout until previous EnterRoundAbout was leaved."));
currentEnterRoundAbout = &t;
ASSERT(exitNum == 0, ("exitNum is reset at start and after LeaveRoundAbout."));
exitNum = 0;
}
else if (t.m_turn == CarDirection::StayOnRoundAbout)
{
++exitNum;
turnsDir.erase(turnsDir.begin() + idx);
continue;
}
else if (t.m_turn == CarDirection::LeaveRoundAbout)
{
// It's possible for car to be on roundabout without entering it
// if route calculation started at roundabout (e.g. if user made full turn on roundabout).
if (currentEnterRoundAbout)
currentEnterRoundAbout->m_exitNum = exitNum + 1;
t.m_exitNum = exitNum + 1; // For LeaveRoundAbout turn.
currentEnterRoundAbout = nullptr;
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).
if (idx > 0 && IsStayOnRoad(turnsDir[idx - 1].m_turn) &&
IsLeftOrRightTurn(turnsDir[idx].m_turn) &&
CalcRouteDistanceM(junctions, turnsDir[idx - 1].m_index, turnsDir[idx].m_index) <
kMergeDistMeters)
{
turnsDir.erase(turnsDir.begin() + idx - 1);
continue;
}
++idx;
}
SelectRecommendedLanes(turnsDir);
}
void FixupTurnsPedestrian(std::vector<geometry::PointWithAltitude> 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)
{
for (auto & t : turnsDir)
{
std::vector<SingleLaneInfo> & lanes = t.m_lanes;
if (lanes.empty())
continue;
CarDirection 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.
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.
FixupLaneSet(turn, lanes, &IsLaneWayConformedTurnDirectionApproximately);
}
}
CarDirection GetRoundaboutDirection(bool isIngoingEdgeRoundabout, bool isOutgoingEdgeRoundabout,
bool isMultiTurnJunction, bool keepTurnByHighwayClass)
{
if (isIngoingEdgeRoundabout && isOutgoingEdgeRoundabout)
{
if (isMultiTurnJunction)
return keepTurnByHighwayClass ? CarDirection::StayOnRoundAbout : CarDirection::None;
return CarDirection::None;
}
if (!isIngoingEdgeRoundabout && isOutgoingEdgeRoundabout)
return CarDirection::EnterRoundAbout;
if (isIngoingEdgeRoundabout && !isOutgoingEdgeRoundabout)
return CarDirection::LeaveRoundAbout;
ASSERT(false, ());
return CarDirection::None;
}
CarDirection GetRoundaboutDirection(TurnInfo const & turnInfo, TurnCandidates const & nodes, NumMwmIds const & numMwmIds)
{
bool const keepTurnByHighwayClass = KeepRoundaboutTurnByHighwayClass(nodes, turnInfo, numMwmIds);
return GetRoundaboutDirection(turnInfo.m_ingoing->m_onRoundabout, turnInfo.m_outgoing->m_onRoundabout,
nodes.candidates.size() > 1, keepTurnByHighwayClass);
}
void RemoveUTurnCandidate(TurnInfo const & turnInfo, NumMwmIds const & numMwmIds, std::vector<TurnCandidate> & turnCandidates)
{
Segment lastIngoingSegment;
@ -752,13 +237,13 @@ void RemoveUTurnCandidate(TurnInfo const & turnInfo, NumMwmIds const & numMwmIds
/// \returns true if there is exactly 1 turn in |turnCandidates| with angle less then
/// |kMaxForwardAngleCandidates|.
bool HasSingleForwardTurn(TurnCandidates const & turnCandidates)
bool HasSingleForwardTurn(TurnCandidates const & turnCandidates, float maxForwardAngleCandidates)
{
bool foundForwardTurn = false;
for (auto const & turn : turnCandidates.candidates)
{
if (std::fabs(turn.m_angle) < kMaxForwardAngleCandidates)
if (std::fabs(turn.m_angle) < maxForwardAngleCandidates)
{
if (foundForwardTurn)
return false;
@ -770,8 +255,6 @@ bool HasSingleForwardTurn(TurnCandidates const & turnCandidates)
return foundForwardTurn;
}
/// \returns angle, wchis is calculated using several backward and forward segments
/// from junction to consider smooth turns and remove noise.
double CalcTurnAngle(IRoutingResult const & result,
size_t const outgoingSegmentIndex,
NumMwmIds const & numMwmIds,
@ -791,11 +274,7 @@ double CalcTurnAngle(IRoutingResult const & result,
return base::RadToDeg(PiMinusTwoVectorsAngle(junctionPoint, ingoingPoint, outgoingPoint));
}
// It's possible that |firstOutgoingSeg| is not contained in |turnCandidates|.
// It may happened if |firstOutgoingSeg| and candidates in |turnCandidates| are from different mwms.
// Let's identify it in turnCandidates by angle and update according turnCandidate.
void CorrectCandidatesSegmentByOutgoing(TurnInfo const & turnInfo,
Segment const & firstOutgoingSeg,
void CorrectCandidatesSegmentByOutgoing(TurnInfo const & turnInfo, Segment const & firstOutgoingSeg,
std::vector<TurnCandidate> & candidates)
{
auto IsFirstOutgoingSeg = [&firstOutgoingSeg](TurnCandidate const & turnCandidate) { return turnCandidate.m_segment == firstOutgoingSeg; };
@ -855,290 +334,5 @@ bool GetTurnInfo(IRoutingResult const & result, size_t const outgoingSegmentInde
return true;
}
// If the route goes along the rightmost or the leftmost way among available ones:
// 1. RightmostDirection or LeftmostDirection is selected
// 2. If the turn direction is |CarDirection::GoStraight|
// and there's another not sharp enough turn
// GoStraight is corrected to TurnSlightRight/TurnSlightLeft
// to avoid ambiguity for GoStraight direction: 2 or more almost straight turns.
void CorrectRightmostAndLeftmost(std::vector<TurnCandidate> const & turnCandidates,
Segment const & firstOutgoingSeg, double const turnAngle,
TurnItem & turn)
{
// turnCandidates are sorted by angle from leftmost to rightmost.
// Normally no duplicates should be found. But if they are present we can't identify the leftmost/rightmost by order.
if (adjacent_find(turnCandidates.begin(), turnCandidates.end(), base::EqualsBy(&TurnCandidate::m_angle)) != turnCandidates.end())
{
LOG(LWARNING, ("nodes.candidates are not expected to have same m_angle."));
return;
}
double constexpr kMaxAbsAngleConsideredLeftOrRightMost = 90;
// Go from left to right to findout if the route goes through the leftmost candidate and fixes can be applied.
// Other candidates which are sharper than kMaxAbsAngleConsideredLeftOrRightMost are ignored.
for (auto candidate = turnCandidates.begin(); candidate != turnCandidates.end(); ++candidate)
{
if (candidate->m_segment == firstOutgoingSeg && candidate + 1 != turnCandidates.end())
{
// The route goes along the leftmost candidate.
turn.m_turn = LeftmostDirection(turnAngle);
if (IntermediateDirection(turnAngle) != turn.m_turn)
LOG(LDEBUG, ("Turn: ", turn.m_index, " LeftmostDirection correction."));
// Compare with the next candidate to the right.
CorrectGoStraight(*(candidate + 1), candidate->m_angle, CarDirection::TurnSlightLeft, turn);
break;
}
// Check if this candidate is considered as leftmost as not too sharp.
// If yes - this candidate is leftmost, not route's one.
if (candidate->m_angle > -kMaxAbsAngleConsideredLeftOrRightMost)
break;
}
// Go from right to left to findout if the route goes through the rightmost candidate anf fixes can be applied.
// Other candidates which are sharper than kMaxAbsAngleConsideredLeftOrRightMost are ignored.
for (auto candidate = turnCandidates.rbegin(); candidate != turnCandidates.rend(); ++candidate)
{
if (candidate->m_segment == firstOutgoingSeg && candidate + 1 != turnCandidates.rend())
{
// The route goes along the rightmost candidate.
turn.m_turn = RightmostDirection(turnAngle);
if (IntermediateDirection(turnAngle) != turn.m_turn)
LOG(LDEBUG, ("Turn: ", turn.m_index, " RighmostDirection correction."));
// Compare with the next candidate to the left.
CorrectGoStraight(*(candidate + 1), candidate->m_angle, CarDirection::TurnSlightRight, turn);
break;
}
// Check if this candidate is considered as rightmost as not too sharp.
// If yes - this candidate is rightmost, not route's one.
if (candidate->m_angle < kMaxAbsAngleConsideredLeftOrRightMost)
break;
};
}
void GetTurnDirection(IRoutingResult const & result, size_t const outgoingSegmentIndex,
NumMwmIds const & numMwmIds, RoutingSettings const & vehicleSettings,
TurnItem & turn)
{
TurnInfo turnInfo;
if (!GetTurnInfo(result, outgoingSegmentIndex, vehicleSettings, turnInfo))
return;
turn.m_sourceName = turnInfo.m_ingoing->m_name;
turn.m_targetName = turnInfo.m_outgoing->m_name;
turn.m_turn = CarDirection::None;
ASSERT_GREATER(turnInfo.m_ingoing->m_path.size(), 1, ());
TurnCandidates nodes;
size_t ingoingCount;
m2::PointD const junctionPoint = turnInfo.m_ingoing->m_path.back().GetPoint();
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;
// No turns are needed on transported road.
if (turnInfo.m_ingoing->m_highwayClass == HighwayClass::Transported && turnInfo.m_outgoing->m_highwayClass == HighwayClass::Transported)
return;
Segment firstOutgoingSeg;
bool const isFirstOutgoingSegValid = turnInfo.m_outgoing->m_segmentRange.GetFirstSegment(numMwmIds, firstOutgoingSeg);
if (!isFirstOutgoingSegValid)
return;
CorrectCandidatesSegmentByOutgoing(turnInfo, firstOutgoingSeg, nodes.candidates);
RemoveUTurnCandidate(turnInfo, numMwmIds, nodes.candidates);
auto const & turnCandidates = nodes.candidates;
// Check for enter or exit to/from roundabout.
if (turnInfo.m_ingoing->m_onRoundabout || turnInfo.m_outgoing->m_onRoundabout)
{
turn.m_turn = GetRoundaboutDirection(turnInfo, nodes, numMwmIds);
return;
}
double const turnAngle = CalcTurnAngle(result, outgoingSegmentIndex, numMwmIds, vehicleSettings);
CarDirection const intermediateDirection = IntermediateDirection(turnAngle);
// Checking for exits from highways.
turn.m_turn = TryToGetExitDirection(nodes, turnInfo, firstOutgoingSeg, intermediateDirection);
if (turn.m_turn != CarDirection::None)
return;
if (turnCandidates.size() == 1)
{
ASSERT(turnCandidates.front().m_segment == firstOutgoingSeg, ());
if (IsGoStraightOrSlightTurn(intermediateDirection))
return;
// IngoingCount includes ingoing segment and reversed outgoing (if it is not one-way).
// If any other one is present, turn (not straight or slight) is kept to prevent user from going to oneway alternative.
/// @todo Min abs angle of ingoing ones should be considered. If it's much bigger than route angle - ignore ingoing ones.
/// Now this data is not available from IRoutingResult::GetPossibleTurns().
if (ingoingCount <= 1 + size_t(!turnInfo.m_outgoing->m_isOneWay))
return;
}
// This angle is calculated using only 1 segment back and forward, not like turnAngle.
double turnOneSegmentAngle = CalcOneSegmentTurnAngle(turnInfo);
// To not discard some disputable turns let's use max by modulus from turnOneSegmentAngle and turnAngle.
// It's natural since angles of turnCandidates are calculated in IRoutingResult::GetPossibleTurns()
// according to CalcOneSegmentTurnAngle logic. And to be safe turnAngle is used too.
double turnAngleToCompare = turnAngle;
if (turnOneSegmentAngle <= 0 && turnAngle <= 0)
turnAngleToCompare = min(turnOneSegmentAngle, turnAngle);
else if (turnOneSegmentAngle >= 0 && turnAngle >= 0)
turnAngleToCompare = max(turnOneSegmentAngle, turnAngle);
else if (abs(turnOneSegmentAngle) > 10)
LOG(LWARNING, ("Significant angles are expected to have the same sign."));
if (CanDiscardTurnByHighwayClassOrAngles(intermediateDirection, turnAngleToCompare, turnCandidates, turnInfo, numMwmIds))
return;
turn.m_turn = intermediateDirection;
if (turnCandidates.size() >= 2)
CorrectRightmostAndLeftmost(turnCandidates, firstOutgoingSeg, turnAngle, turn);
}
void GetTurnDirectionPedestrian(IRoutingResult const & result, size_t const outgoingSegmentIndex,
NumMwmIds const & numMwmIds,
RoutingSettings const & vehicleSettings, TurnItem & turn)
{
TurnInfo turnInfo;
if (!GetTurnInfo(result, outgoingSegmentIndex, vehicleSettings, turnInfo))
return;
double const turnAngle = CalcTurnAngle(result, outgoingSegmentIndex, numMwmIds, vehicleSettings);
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;
m2::PointD const junctionPoint = turnInfo.m_ingoing->m_path.back().GetPoint();
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;
return;
}
RemoveUTurnCandidate(turnInfo, numMwmIds, nodes.candidates);
// If there is no fork on the road we don't need to generate any turn. It is pointless because
// there is no possibility of leaving the route.
if (nodes.candidates.size() <= 1 || (std::fabs(CalcOneSegmentTurnAngle(turnInfo)) < kMaxForwardAngleActual && HasSingleForwardTurn(nodes)))
turn.m_pedestrianTurn = PedestrianDirection::None;
}
size_t CheckUTurnOnRoute(IRoutingResult const & result, size_t const outgoingSegmentIndex,
NumMwmIds const & numMwmIds, RoutingSettings const & vehicleSettings,
TurnItem & turn)
{
size_t constexpr kUTurnLookAhead = 3;
double constexpr kUTurnHeadingSensitivity = math::pi / 10.0;
auto const & segments = result.GetSegments();
// In this function we process the turn between the previous and the current
// segments. So we need a shift to get the previous segment.
ASSERT_GREATER(segments.size(), 1, ());
ASSERT_GREATER(outgoingSegmentIndex, 0, ());
ASSERT_GREATER(segments.size(), outgoingSegmentIndex, ());
auto const & masterSegment = segments[outgoingSegmentIndex - 1];
if (masterSegment.m_path.size() < 2)
return 0;
// Roundabout is not the UTurn.
if (masterSegment.m_onRoundabout)
return 0;
for (size_t i = 0; i < kUTurnLookAhead && i + outgoingSegmentIndex < segments.size(); ++i)
{
auto const & checkedSegment = segments[outgoingSegmentIndex + i];
if (checkedSegment.m_path.size() < 2)
return 0;
if (checkedSegment.m_name == masterSegment.m_name &&
checkedSegment.m_highwayClass == masterSegment.m_highwayClass &&
checkedSegment.m_isLink == masterSegment.m_isLink && !checkedSegment.m_onRoundabout)
{
auto const & path = masterSegment.m_path;
auto const & pointBeforeTurn = path[path.size() - 2];
auto const & turnPoint = path[path.size() - 1];
auto const & pointAfterTurn = checkedSegment.m_path[1];
// Same segment UTurn case.
if (i == 0)
{
// TODO Fix direction calculation.
// Warning! We can not determine UTurn direction in single edge case. So we use UTurnLeft.
// We decided to add driving rules (left-right sided driving) to mwm header.
if (pointBeforeTurn == pointAfterTurn && turnPoint != pointBeforeTurn)
{
turn.m_turn = CarDirection::UTurnLeft;
return 1;
}
// Wide UTurn must have link in it's middle.
return 0;
}
// Avoid the UTurn on unnamed roads inside the rectangle based distinct.
if (checkedSegment.m_name.empty())
return 0;
// Avoid returning to the same edge after uturn somewere else.
if (pointBeforeTurn == pointAfterTurn)
return 0;
m2::PointD const v1 = turnPoint.GetPoint() - pointBeforeTurn.GetPoint();
m2::PointD const v2 = pointAfterTurn.GetPoint() - checkedSegment.m_path[0].GetPoint();
auto angle = ang::TwoVectorsAngle(m2::PointD::Zero(), v1, v2);
if (!base::AlmostEqualAbs(angle, math::pi, kUTurnHeadingSensitivity))
return 0;
// Determine turn direction.
m2::PointD const junctionPoint = masterSegment.m_path.back().GetPoint();
m2::PointD const ingoingPoint = GetPointForTurn(
result, outgoingSegmentIndex, numMwmIds, vehicleSettings.m_maxIngoingPointsCount,
vehicleSettings.m_minIngoingDistMeters, false /* forward */);
m2::PointD const outgoingPoint = GetPointForTurn(
result, outgoingSegmentIndex, numMwmIds, vehicleSettings.m_maxOutgoingPointsCount,
vehicleSettings.m_minOutgoingDistMeters, true /* forward */);
if (PiMinusTwoVectorsAngle(junctionPoint, ingoingPoint, outgoingPoint) < 0)
turn.m_turn = CarDirection::UTurnLeft;
else
turn.m_turn = CarDirection::UTurnRight;
return i + 1;
}
}
return 0;
}
} // namespace turns
} // namespace routing

View file

@ -41,7 +41,7 @@ namespace turns
using TGetIndexFunction = std::function<size_t(std::pair<size_t, size_t>)>;
struct RoutePointIndex;
struct TurnInfo;
/*!
* \brief Calculates |nextIndex| which is an index of next route point at result.GetSegments().
@ -60,72 +60,55 @@ struct RoutePointIndex;
bool GetNextRoutePointIndex(IRoutingResult const & result, RoutePointIndex const & index,
NumMwmIds const & numMwmIds, bool const forward, RoutePointIndex & nextIndex);
/*!
* \brief Compute turn and time estimation structs for the abstract route result.
* \param result abstract routing result to annotate.
* \param delegate Routing callbacks delegate.
* \param points Storage for unpacked points of the path.
* \param turnsDir output turns annotation storage.
* \param streets output street names along the path.
* \param segments route segments.
* \return routing operation result code.
*/
RouterResultCode MakeTurnAnnotation(IRoutingResult const & result, NumMwmIds const & numMwmIds,
VehicleType const & vehicleType,
base::Cancellable const & cancellable,
std::vector<geometry::PointWithAltitude> & points,
Route::TTurns & turnsDir, Route::TStreets & streets,
std::vector<Segment> & segments);
/*!
* \brief Selects lanes which are recommended for an end user.
*/
void SelectRecommendedLanes(Route::TTurns & turnsDir);
void FixupTurns(std::vector<geometry::PointWithAltitude> const & points, Route::TTurns & turnsDir);
void FixupTurnsPedestrian(std::vector<geometry::PointWithAltitude> const & junctions,
Route::TTurns & turnsDir);
inline size_t GetFirstSegmentPointIndex(std::pair<size_t, size_t> const & p) { return p.first; }
/*!
* \brief Calculates a turn instruction if the ingoing edge or (and) the outgoing edge belongs to a
* roundabout.
* \return Returns one of the following results:
* - TurnDirection::EnterRoundAbout if the ingoing edge does not belong to a roundabout
* and the outgoing edge belongs to a roundabout.
* - TurnDirection::StayOnRoundAbout if the ingoing edge and the outgoing edge belong to a
* roundabout
* and there is a reasonalbe way to leave the junction besides the outgoing edge.
* This function does not return TurnDirection::StayOnRoundAbout for small ways to leave the
* roundabout.
* - TurnDirection::NoTurn if the ingoing edge and the outgoing edge belong to a roundabout
* (a) and there is a single way (outgoing edge) to leave the junction.
* (b) and there is a way(s) besides outgoing edge to leave the junction (the roundabout)
* but it is (they are) relevantly small.
*/
CarDirection GetRoundaboutDirection(bool isIngoingEdgeRoundabout, bool isOutgoingEdgeRoundabout,
bool isMultiTurnJunction, bool keepTurnByHighwayClass);
bool GetTurnInfo(IRoutingResult const & result, size_t const outgoingSegmentIndex,
RoutingSettings const & vehicleSettings,
TurnInfo & turnInfo);
/// \returns angle, wchis is calculated using several backward and forward segments
/// from junction to consider smooth turns and remove noise.
double CalcTurnAngle(IRoutingResult const & result,
size_t const outgoingSegmentIndex,
NumMwmIds const & numMwmIds,
RoutingSettings const & vehicleSettings);
void RemoveUTurnCandidate(TurnInfo const & turnInfo, NumMwmIds const & numMwmIds, std::vector<TurnCandidate> & turnCandidates);
/// \returns true if there is exactly 1 turn in |turnCandidates| with angle less then
/// |kMaxForwardAngleCandidates|.
bool HasSingleForwardTurn(TurnCandidates const & turnCandidates, float maxForwardAngleCandidates);
// It's possible that |firstOutgoingSeg| is not contained in |turnCandidates|.
// It may happened if |firstOutgoingSeg| and candidates in |turnCandidates| are from different mwms.
// Let's identify it in turnCandidates by angle and update according turnCandidate.
void CorrectCandidatesSegmentByOutgoing(TurnInfo const & turnInfo, Segment const & firstOutgoingSeg,
std::vector<TurnCandidate> & candidates);
/*!
* \brief GetTurnDirection makes a primary decision about turns on the route.
* \param outgoingSegmentIndex index of an outgoing segments in vector result.GetSegments().
* \param turn is used for keeping the result of turn calculation.
* \brief Returns ingoing point or outgoing point for turns.
* These points belong to the route but they often are not neighbor of junction point.
* To calculate the resulting point the function implements the following steps:
* - going from junction point along route path according to the direction which is set in GetPointIndex().
* - until one of following conditions is fulfilled:
* - more than |maxPointsCount| points are passed (returns the maxPointsCount-th point);
* - the length of passed parts of segment exceeds maxDistMeters;
* (returns the next point after the event)
* - an important bifurcation point is reached in case of outgoing point is looked up (forward == true).
* \param result information about the route. |result.GetSegments()| is composed of LoadedPathSegment.
* Each LoadedPathSegment is composed of several Segments. The sequence of Segments belongs to
* single feature and does not split by other features.
* \param outgoingSegmentIndex index in |segments|. Junction point noticed above is the first point
* of |outgoingSegmentIndex| segment in |result.GetSegments()|.
* \param maxPointsCount maximum number between the returned point and junction point.
* \param maxDistMeters maximum distance between the returned point and junction point.
* \param forward is direction of moving along the route to calculate the returned point.
* If forward == true the direction is to route finish. If forward == false the direction is to route start.
* \return an ingoing or outgoing point for a turn calculation.
*/
void GetTurnDirection(IRoutingResult const & result, size_t outgoingSegmentIndex,
NumMwmIds const & numMwmIds, RoutingSettings const & vehicleSettings,
TurnItem & turn);
void GetTurnDirectionPedestrian(IRoutingResult const & result, size_t outgoingSegmentIndex,
NumMwmIds const & numMwmIds,
RoutingSettings const & vehicleSettings, TurnItem & turn);
m2::PointD GetPointForTurn(IRoutingResult const & result, size_t outgoingSegmentIndex,
NumMwmIds const & numMwmIds, size_t const maxPointsCount,
double const maxDistMeters, bool const forward);
/*!
* \brief Finds an U-turn that starts from master segment and returns how many segments it lasts.
* \returns an index in |segments| that has the opposite direction with master segment
* (|segments[currentSegment - 1]|) and 0 if there is no UTurn.
* \warning |currentSegment| must be greater than 0.
*/
size_t CheckUTurnOnRoute(IRoutingResult const & result, size_t outgoingSegmentIndex,
NumMwmIds const & numMwmIds, RoutingSettings const & vehicleSettings,
TurnItem & turn);
} // namespace routing
} // namespace turns

View file

@ -34,6 +34,14 @@ struct TurnInfo
bool IsHighway(ftypes::HighwayClass hwClass, bool isLink);
bool IsSmallRoad(ftypes::HighwayClass hwClass);
// Min difference between HighwayClasses of the route segment and alternative turn segment
// to ignore this alternative candidate.
int constexpr kMinHighwayClassDiff = -2;
// Min difference between HighwayClasses of the route segment and alternative turn segment
// to ignore this alternative candidate (when alternative road is service).
int constexpr kMinHighwayClassDiffForService = -1;
/// * \returns difference between highway classes.
/// * It should be considered that bigger roads have smaller road class.
int CalcDiffRoadClasses(ftypes::HighwayClass const left, ftypes::HighwayClass const right);
@ -72,8 +80,7 @@ double CalcEstimatedTimeToPass(double const distanceMeters, ftypes::HighwayClass
bool PathIsFakeLoop(std::vector<geometry::PointWithAltitude> const & path);
// Returns distance in meters between |junctions[start]| and |junctions[end]|.
double CalcRouteDistanceM(std::vector<geometry::PointWithAltitude> const & junctions, uint32_t start,
uint32_t end);
double CalcRouteDistanceM(std::vector<geometry::PointWithAltitude> const & junctions, uint32_t start, uint32_t end);
/*!
* \brief Index of point in TUnpackedPathSegments. |m_segmentIndex| is a zero based index in vector
@ -91,8 +98,7 @@ std::string DebugPrint(RoutePointIndex const & index);
RoutePointIndex GetFirstOutgoingPointIndex(size_t const outgoingSegmentIndex);
RoutePointIndex GetLastIngoingPointIndex(TUnpackedPathSegments const & segments,
size_t const outgoingSegmentIndex);
RoutePointIndex GetLastIngoingPointIndex(TUnpackedPathSegments const & segments, size_t const outgoingSegmentIndex);
double CalcOneSegmentTurnAngle(TurnInfo const & turnInfo);
double CalcPathTurnAngle(LoadedPathSegment const & segment, size_t const pathIndex);