[routing] New API for lanes

Signed-off-by: Andrei Shkrob <github@shkrob.dev>
This commit is contained in:
Andrei Shkrob
2025-07-29 22:54:24 +02:00
committed by x7z4w
parent 93293e7b31
commit c014e224b2
36 changed files with 1047 additions and 634 deletions

View File

@@ -16,6 +16,14 @@ set(SRC
base/routing_result.hpp
base/small_list.hpp
base/small_list.cpp
lanes/lane_info.cpp
lanes/lane_info.hpp
lanes/lane_way.cpp
lanes/lane_way.hpp
lanes/lanes_parser.cpp
lanes/lanes_parser.hpp
lanes/lanes_recommendation.cpp
lanes/lanes_recommendation.hpp
car_directions.cpp
car_directions.hpp
checkpoint_predictor.cpp

View File

@@ -1,5 +1,6 @@
#include "routing/car_directions.hpp"
#include "routing/lanes/lanes_recommendation.hpp"
#include "routing/turns.hpp"
#include "routing/turns_generator.hpp"
#include "routing/turns_generator_utils.hpp"
@@ -83,7 +84,7 @@ void FixupCarTurns(vector<RouteSegment> & routeSegments)
routeSegments[idx - 1].ClearTurn();
}
}
SelectRecommendedLanes(routeSegments);
turns::lanes::SelectRecommendedLanes(routeSegments);
}
void GetTurnDirectionBasic(IRoutingResult const & result, size_t const outgoingSegmentIndex,
@@ -596,73 +597,4 @@ size_t CheckUTurnOnRoute(IRoutingResult const & result, size_t const outgoingSeg
return 0;
}
bool FixupLaneSet(CarDirection turn, vector<SingleLaneInfo> & lanes, bool (*checker)(LaneWay, CarDirection))
{
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;
}
template <typename It>
bool SelectFirstUnrestrictedLane(LaneWay direction, It lanesBegin, It lanesEnd)
{
It const firstUnrestricted = find_if(lanesBegin, lanesEnd, IsLaneUnrestricted);
if (firstUnrestricted == lanesEnd)
return false;
firstUnrestricted->m_isRecommended = true;
firstUnrestricted->m_lane.insert(firstUnrestricted->m_lane.begin(), direction);
return true;
}
bool SelectUnrestrictedLane(CarDirection turn, vector<SingleLaneInfo> & lanes)
{
if (IsTurnMadeFromLeft(turn))
return SelectFirstUnrestrictedLane(LaneWay::Left, lanes.begin(), lanes.end());
else if (IsTurnMadeFromRight(turn))
return SelectFirstUnrestrictedLane(LaneWay::Right, lanes.rbegin(), lanes.rend());
return false;
}
void SelectRecommendedLanes(vector<RouteSegment> & routeSegments)
{
for (auto & segment : routeSegments)
{
auto & t = segment.GetTurn();
if (t.IsTurnNone() || t.m_lanes.empty())
continue;
auto & lanes = segment.GetTurnLanes();
// Checking if there 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(t.m_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 (FixupLaneSet(t.m_turn, lanes, &IsLaneWayConformedTurnDirectionApproximately))
continue;
// If not, check if there is an unrestricted lane which could correspond to the
// turn. If so, fix up that lane.
if (SelectUnrestrictedLane(t.m_turn, lanes))
continue;
// Otherwise, we don't have lane recommendations for the user, so we don't
// want to send the lane data any further.
segment.ClearTurnLanes();
}
}
} // namespace routing

View File

@@ -5,7 +5,6 @@
#include "routing/route.hpp"
#include "routing_common/num_mwm_id.hpp"
#include <map>
#include <memory>
#include <vector>
@@ -24,11 +23,6 @@ protected:
virtual void FixupTurns(std::vector<RouteSegment> & routeSegments);
};
/*!
* \brief Selects lanes which are recommended for an end user.
*/
void SelectRecommendedLanes(std::vector<RouteSegment> & routeSegments);
void FixupCarTurns(std::vector<RouteSegment> & routeSegments);
/*!

View File

@@ -2,6 +2,7 @@
#include "routing/data_source.hpp"
#include "routing/fake_feature_ids.hpp"
#include "routing/lanes/lanes_parser.hpp"
#include "routing/routing_helpers.hpp"
#include "routing/turns.hpp"
@@ -36,7 +37,7 @@ feature::Metadata::EType GetLanesMetadataTag(FeatureType & ft, bool isForward)
void LoadLanes(LoadedPathSegment & pathSegment, FeatureType & ft, bool isForward)
{
auto tag = GetLanesMetadataTag(ft, isForward);
ParseLanes(std::string(ft.GetMetadata(tag)), pathSegment.m_lanes);
pathSegment.m_lanes = lanes::ParseLanes(ft.GetMetadata(tag));
}
} // namespace

View File

@@ -1,11 +1,9 @@
#pragma once
#include "geometry/latlon.hpp"
#include "platform/distance.hpp"
#include "routing/lanes/lane_info.hpp"
#include "routing/turns.hpp"
#include "routing/turns_sound_settings.hpp"
#include <algorithm>
#include <cstdint>
@@ -26,23 +24,6 @@ public:
, m_pedestrianTurn(turns::PedestrianDirection::None)
{}
// SingleLaneInfoClient is used for passing information about a lane to client platforms such as
// Android, iOS and so on.
struct SingleLaneInfoClient
{
std::vector<int8_t> m_lane; // Possible directions for the lane.
bool m_isRecommended; // m_isRecommended is true if the lane is recommended for a user.
explicit SingleLaneInfoClient(turns::SingleLaneInfo const & singleLaneInfo)
: m_isRecommended(singleLaneInfo.m_isRecommended)
{
turns::TSingleLane const & lane = singleLaneInfo.m_lane;
m_lane.resize(lane.size());
std::transform(lane.cbegin(), lane.cend(), m_lane.begin(),
[](turns::LaneWay l) { return static_cast<int8_t>(l); });
}
};
bool IsValid() const { return m_distToTarget.IsValid(); }
/// @name Formatted covered distance.
@@ -57,8 +38,8 @@ public:
uint32_t m_exitNum;
//@}
int m_time;
// m_lanes contains lane information on the edge before the turn.
std::vector<SingleLaneInfoClient> m_lanes;
/// Contains lane information on the edge before the turn.
turns::lanes::LanesInfo m_lanes;
// m_turnNotifications contains information about the next turn notifications.
// If there is nothing to pronounce m_turnNotifications is empty.
// If there is something to pronounce the size of m_turnNotifications may be one or even more

View File

@@ -0,0 +1,24 @@
#include "lane_info.hpp"
#include <sstream>
namespace routing::turns::lanes
{
std::string DebugPrint(LaneInfo const & laneInfo)
{
std::stringstream out;
out << "LaneInfo{" << DebugPrint(laneInfo.laneWays) << ", recommendedWay: " << DebugPrint(laneInfo.recommendedWay)
<< "}";
return out.str();
}
std::string DebugPrint(LanesInfo const & lanesInfo)
{
std::stringstream out;
out << "LanesInfo[";
for (auto const & laneInfo : lanesInfo)
out << DebugPrint(laneInfo) << ", ";
out << "]";
return out.str();
}
} // namespace routing::turns::lanes

View File

@@ -0,0 +1,23 @@
#pragma once
#include "routing/lanes/lane_way.hpp"
#include <vector>
namespace routing::turns::lanes
{
struct LaneInfo
{
LaneWays laneWays;
LaneWay recommendedWay = LaneWay::None;
bool operator==(LaneInfo const & rhs) const
{
return laneWays == rhs.laneWays && recommendedWay == rhs.recommendedWay;
}
};
using LanesInfo = std::vector<LaneInfo>;
std::string DebugPrint(LaneInfo const & laneInfo);
std::string DebugPrint(LanesInfo const & lanesInfo);
} // namespace routing::turns::lanes

View File

@@ -0,0 +1,50 @@
#include "lane_way.hpp"
#include "base/assert.hpp"
namespace routing::turns::lanes
{
std::string DebugPrint(LaneWay const laneWay)
{
using enum LaneWay;
switch (laneWay)
{
case None: return "None";
case ReverseLeft: return "ReverseLeft";
case SharpLeft: return "SharpLeft";
case Left: return "Left";
case MergeToLeft: return "MergeToLeft";
case SlightLeft: return "SlightLeft";
case Through: return "Through";
case SlightRight: return "SlightRight";
case MergeToRight: return "MergeToRight";
case Right: return "Right";
case SharpRight: return "SharpRight";
case ReverseRight: return "ReverseRight";
case Count: return "Count";
default:
ASSERT_FAIL("Unsupported value: " + std::to_string(static_cast<std::uint8_t>(laneWay)));
return "Unsupported";
}
}
std::string DebugPrint(LaneWays const & laneWays)
{
std::stringstream out;
out << "LaneWays: [";
std::uint8_t const waysCount = laneWays.m_laneWays.count();
std::uint8_t waysPrinted = 0;
for (std::size_t i = 0; i < laneWays.m_laneWays.size(); ++i)
{
if (laneWays.m_laneWays.test(i))
{
out << DebugPrint(static_cast<LaneWay>(i));
if (waysPrinted < waysCount - 1)
out << ", ";
waysPrinted++;
}
}
out << "]";
return out.str();
}
} // namespace routing::turns::lanes

View File

@@ -0,0 +1,84 @@
#pragma once
#include "base/assert.hpp"
#include <bitset>
#include <initializer_list>
#include <string>
namespace routing::turns::lanes
{
enum class LaneWay : std::uint8_t
{
None = 0,
ReverseLeft,
SharpLeft,
Left,
MergeToLeft,
SlightLeft,
Through,
SlightRight,
MergeToRight,
Right,
SharpRight,
ReverseRight,
Count
};
class LaneWays
{
using LaneWaysT = std::bitset<static_cast<std::uint8_t>(LaneWay::Count)>;
friend std::string DebugPrint(LaneWays const & laneWays);
public:
constexpr LaneWays() = default;
constexpr LaneWays(std::initializer_list<LaneWay> const laneWays)
{
for (auto const & laneWay : laneWays)
Add(laneWay);
}
constexpr bool operator==(LaneWays const & rhs) const { return m_laneWays == rhs.m_laneWays; }
constexpr void Add(LaneWay laneWay)
{
ASSERT_LESS(laneWay, LaneWay::Count, ());
m_laneWays.set(static_cast<std::uint8_t>(laneWay));
}
constexpr void Remove(LaneWay laneWay)
{
ASSERT_LESS(laneWay, LaneWay::Count, ());
m_laneWays.reset(static_cast<std::uint8_t>(laneWay));
}
constexpr bool Contains(LaneWay laneWay) const
{
ASSERT_LESS(laneWay, LaneWay::Count, ());
return m_laneWays.test(static_cast<std::uint8_t>(laneWay));
}
/// An unrestricted lane is a lane that has no restrictions, i.e., it contains no lane ways.
constexpr bool IsUnrestricted() const
{
return m_laneWays.none() || (m_laneWays.count() == 1 && Contains(LaneWay::None));
}
[[nodiscard]] std::vector<LaneWay> GetActiveLaneWays() const
{
std::vector<LaneWay> result;
for (std::size_t i = 0; i < m_laneWays.size(); ++i)
if (m_laneWays.test(i))
result.emplace_back(static_cast<LaneWay>(i));
return result;
}
private:
LaneWaysT m_laneWays;
};
std::string DebugPrint(LaneWay laneWay);
std::string DebugPrint(LaneWays const & laneWays);
} // namespace routing::turns::lanes

View File

@@ -0,0 +1,84 @@
#include "lanes_parser.hpp"
#include <algorithm>
#include <ranges>
namespace routing::turns::lanes
{
namespace
{
std::uint8_t constexpr kLaneWayNamesCount = static_cast<std::uint8_t>(LaneWay::Count) + 4;
/**
* The order is important. Starting with the most frequent tokens according to
* taginfo.openstreetmap.org we minimize the number of the comparisons in ParseSingleLane().
*
* A `none` lane can be represented either as "none" or as "". That means both "none" and ""
* should be considered names, even though they refer to the same thing. As a result,
* `LaneWay::None` appears twice in this array, which is one longer than the number of
* enum values.
*/
std::array<std::pair<LaneWay, std::string_view>, kLaneWayNamesCount> constexpr g_laneWayNames{{
{LaneWay::None, ""},
{LaneWay::Through, "through"},
{LaneWay::Left, "left"},
{LaneWay::Right, "right"},
{LaneWay::None, "none"},
{LaneWay::SharpLeft, "sharp_left"},
{LaneWay::SlightLeft, "slight_left"},
{LaneWay::MergeToRight, "merge_to_right"},
{LaneWay::MergeToLeft, "merge_to_left"},
{LaneWay::SlightRight, "slight_right"},
{LaneWay::SharpRight, "sharp_right"},
{LaneWay::ReverseLeft, "reverse"},
{LaneWay::Right,
"next_right"}, // "next_right" means "turn right, not in the first intersection but the one after that".
{LaneWay::Through, "slide_left"}, // "slide_left" means "move a bit left within the lane".
{LaneWay::Through, "slide_right"} // "slide_right" means "move a bit right within the lane".
}};
bool ParseSingleLane(auto && laneWayRange, LaneWay & laneWay)
{
auto const it = std::ranges::find_if(
g_laneWayNames, [&laneWayRange](auto const & pair) { return std::ranges::equal(laneWayRange, pair.second); });
if (it != g_laneWayNames.end())
{
laneWay = it->first;
return true;
}
return false;
}
} // namespace
LanesInfo ParseLanes(std::string_view lanesString)
{
if (lanesString.empty())
return {};
LanesInfo lanes;
for (auto && laneInfo : lanesString | std::views::split('|'))
{
LaneInfo lane;
if (std::ranges::empty(laneInfo))
lane.laneWays.Add(LaneWay::None);
else
{
for (auto && laneWay : laneInfo | std::views::split(';'))
{
auto way = LaneWay::None;
auto && laneWayProcessed = laneWay | std::views::filter([](char const c) { return !std::isspace(c); }) |
std::views::transform([](char const c) { return std::tolower(c); });
if (!ParseSingleLane(laneWayProcessed, way))
return {};
lane.laneWays.Add(way);
if (way == LaneWay::ReverseLeft)
lane.laneWays.Add(LaneWay::ReverseRight);
}
}
lanes.push_back(lane);
}
return lanes;
}
} // namespace routing::turns::lanes

View File

@@ -0,0 +1,16 @@
#pragma once
#include "routing/lanes/lane_info.hpp"
#include <vector>
namespace routing::turns::lanes
{
/**
* Parse lane information which comes from lanesString
* @param lanesString lane information. Example through|through|through|through;right
* @return LanesInfo. @see LanesInfo
* @note if lanesString is empty, returns empty LanesInfo.
*/
LanesInfo ParseLanes(std::string_view lanesString);
} // namespace routing::turns::lanes

View File

@@ -0,0 +1,129 @@
#include "lanes_recommendation.hpp"
#include "routing/route.hpp"
namespace routing::turns::lanes
{
namespace
{
void FixRecommendedReverseLane(LaneWays & ways, LaneWay const recommendedWay)
{
if (recommendedWay == LaneWay::ReverseLeft)
ways.Remove(LaneWay::ReverseRight);
else if (recommendedWay == LaneWay::ReverseRight)
ways.Remove(LaneWay::ReverseLeft);
}
} // namespace
void SelectRecommendedLanes(std::vector<RouteSegment> & routeSegments)
{
for (auto & segment : routeSegments)
{
auto & t = segment.GetTurn();
if (t.IsTurnNone() || t.m_lanes.empty())
continue;
auto & lanesInfo = segment.GetTurnLanes();
// Check if there are elements in lanesInfo that correspond with the turn exactly.
// If so, fix up all the elements in lanesInfo that correspond with the turn.
if (impl::SetRecommendedLaneWays(t.m_turn, lanesInfo))
continue;
// If not, check if there are elements in lanesInfo that correspond with the turn
// approximately. If so, fix up all those elements.
if (impl::SetRecommendedLaneWaysApproximately(t.m_turn, lanesInfo))
continue;
// If not, check if there is an unrestricted lane that could correspond to the
// turn. If so, fix up that lane.
if (impl::SetUnrestrictedLaneAsRecommended(t.m_turn, lanesInfo))
continue;
// Otherwise, we don't have lane recommendations for the user, so we don't
// want to send the lane data any further.
segment.ClearTurnLanes();
}
}
bool impl::SetRecommendedLaneWays(CarDirection const carDirection, LanesInfo & lanesInfo)
{
LaneWay laneWay;
switch (carDirection)
{
case CarDirection::GoStraight: laneWay = LaneWay::Through; break;
case CarDirection::TurnRight: laneWay = LaneWay::Right; break;
case CarDirection::TurnSharpRight: laneWay = LaneWay::SharpRight; break;
case CarDirection::TurnSlightRight: [[fallthrough]];
case CarDirection::ExitHighwayToRight: laneWay = LaneWay::SlightRight; break;
case CarDirection::TurnLeft: laneWay = LaneWay::Left; break;
case CarDirection::TurnSharpLeft: laneWay = LaneWay::SharpLeft; break;
case CarDirection::TurnSlightLeft: [[fallthrough]];
case CarDirection::ExitHighwayToLeft: laneWay = LaneWay::SlightLeft; break;
case CarDirection::UTurnLeft: laneWay = LaneWay::ReverseLeft; break;
case CarDirection::UTurnRight: laneWay = LaneWay::ReverseRight; break;
default: return false;
}
bool isLaneConformed = false;
for (auto & [laneWays, recommendedWay] : lanesInfo)
{
if (laneWays.Contains(laneWay))
{
recommendedWay = laneWay;
isLaneConformed = true;
}
FixRecommendedReverseLane(laneWays, recommendedWay);
}
return isLaneConformed;
}
bool impl::SetRecommendedLaneWaysApproximately(CarDirection const carDirection, LanesInfo & lanesInfo)
{
std::vector<LaneWay> approximateLaneWays;
switch (carDirection)
{
case CarDirection::UTurnLeft: approximateLaneWays = {LaneWay::SharpLeft}; break;
case CarDirection::TurnSharpLeft: approximateLaneWays = {LaneWay::Left}; break;
case CarDirection::TurnLeft: approximateLaneWays = {LaneWay::SlightLeft, LaneWay::SharpLeft}; break;
case CarDirection::TurnSlightLeft: [[fallthrough]];
case CarDirection::ExitHighwayToLeft: approximateLaneWays = {LaneWay::Left}; break;
case CarDirection::GoStraight: approximateLaneWays = {LaneWay::SlightRight, LaneWay::SlightLeft}; break;
case CarDirection::ExitHighwayToRight: [[fallthrough]];
case CarDirection::TurnSlightRight: approximateLaneWays = {LaneWay::Right}; break;
case CarDirection::TurnRight: approximateLaneWays = {LaneWay::SlightRight, LaneWay::SharpRight}; break;
case CarDirection::TurnSharpRight: approximateLaneWays = {LaneWay::Right}; break;
case CarDirection::UTurnRight: approximateLaneWays = {LaneWay::SharpRight}; break;
default: return false;
}
bool isLaneConformed = false;
for (auto & [laneWays, recommendedWay] : lanesInfo)
{
for (auto const & laneWay : approximateLaneWays)
{
if (laneWays.Contains(laneWay))
{
recommendedWay = laneWay;
isLaneConformed = true;
break;
}
}
}
return isLaneConformed;
}
bool impl::SetUnrestrictedLaneAsRecommended(CarDirection const carDirection, LanesInfo & lanesInfo)
{
static auto constexpr setFirstUnrestrictedLane = [](LaneWay const laneWay, auto beginIt, auto endIt)
{
auto it = std::find_if(beginIt, endIt, [](auto const & laneInfo) { return laneInfo.laneWays.IsUnrestricted(); });
if (it == endIt)
return false;
it->recommendedWay = laneWay;
return true;
};
if (IsTurnMadeFromLeft(carDirection))
return setFirstUnrestrictedLane(LaneWay::Left, lanesInfo.begin(), lanesInfo.end());
if (IsTurnMadeFromRight(carDirection))
return setFirstUnrestrictedLane(LaneWay::Right, lanesInfo.rbegin(), lanesInfo.rend());
return false;
}
} // namespace routing::turns::lanes

View File

@@ -0,0 +1,31 @@
#pragma once
#include "routing/lanes/lane_info.hpp"
#include <vector>
namespace routing
{
class RouteSegment;
namespace turns
{
enum class CarDirection;
} // namespace turns
} // namespace routing
namespace routing::turns::lanes
{
/// Selects lanes which are recommended for an end user.
void SelectRecommendedLanes(std::vector<RouteSegment> & routeSegments);
// Keep signatures in the header for testing purposes
namespace impl
{
bool SetRecommendedLaneWays(CarDirection carDirection, LanesInfo & lanesInfo);
bool SetRecommendedLaneWaysApproximately(CarDirection carDirection, LanesInfo & lanesInfo);
bool SetUnrestrictedLaneAsRecommended(CarDirection carDirection, LanesInfo & lanesInfo);
} // namespace impl
} // namespace routing::turns::lanes

View File

@@ -1,7 +1,6 @@
#pragma once
#include "routing/maxspeeds.hpp"
#include "routing/road_point.hpp"
#include "routing/lanes/lane_info.hpp"
#include "routing/route.hpp"
#include "routing/segment.hpp"
#include "routing/turns.hpp"
@@ -10,7 +9,6 @@
#include "geometry/point_with_altitude.hpp"
#include <string>
#include <vector>
namespace routing
@@ -23,7 +21,7 @@ namespace routing
struct LoadedPathSegment
{
std::vector<geometry::PointWithAltitude> m_path;
std::vector<turns::SingleLaneInfo> m_lanes;
turns::lanes::LanesInfo m_lanes;
RouteSegment::RoadNameInfo m_roadNameInfo;
// double m_weight = 0.0; /*!< Time in seconds to pass the segment. */
SegmentRange m_segmentRange;

View File

@@ -1,5 +1,6 @@
#pragma once
#include "routing/lanes/lane_info.hpp"
#include "routing/routing_options.hpp"
#include "routing/routing_settings.hpp"
#include "routing/segment.hpp"
@@ -139,7 +140,7 @@ public:
void SetTurnExits(uint32_t exitNum) { m_turn.m_exitNum = exitNum; }
std::vector<turns::SingleLaneInfo> & GetTurnLanes() { return m_turn.m_lanes; }
turns::lanes::LanesInfo & GetTurnLanes() { return m_turn.m_lanes; }
void SetDistancesAndTime(double distFromBeginningMeters, double distFromBeginningMerc, double timeFromBeginningS)
{

View File

@@ -422,15 +422,7 @@ void RoutingSession::GetRouteFollowingInfo(FollowingInfo & info) const
// Lane information
info.m_lanes.clear();
if (distanceToTurnMeters < kShowLanesMinDistInMeters || m_route->GetCurrentTimeToNearestTurnSec() < 60.0)
{
// There are two nested loops below. Outer one is for lanes and inner one (ctor of
// SingleLaneInfo) is
// for each lane's directions. The size of turn.m_lanes is relatively small. Less than 10 in
// most cases.
info.m_lanes.reserve(turn.m_lanes.size());
for (size_t j = 0; j < turn.m_lanes.size(); ++j)
info.m_lanes.emplace_back(turn.m_lanes[j]);
}
info.m_lanes = turn.m_lanes;
// Pedestrian info.
info.m_pedestrianTurn =

View File

@@ -1,6 +1,8 @@
project(routing_tests)
set(SRC
lanes/lanes_parser_tests.cpp
lanes/lanes_recommendation_tests.cpp
applying_traffic_test.cpp
astar_algorithm_test.cpp
astar_progress_test.cpp

View File

@@ -0,0 +1,160 @@
#include "testing/testing.hpp"
#include "routing/lanes/lanes_parser.hpp"
namespace routing::turns::lanes::test
{
UNIT_TEST(TestParseLaneWays)
{
std::vector<std::pair<std::string, LaneWays>> const testData = {
{";", {LaneWay::None}},
{"none", {LaneWay::None}},
{"left", {LaneWay::Left}},
{"right", {LaneWay::Right}},
{"sharp_left", {LaneWay::SharpLeft}},
{"slight_left", {LaneWay::SlightLeft}},
{"merge_to_right", {LaneWay::MergeToRight}},
{"merge_to_left", {LaneWay::MergeToLeft}},
{"slight_right", {LaneWay::SlightRight}},
{"sharp_right", {LaneWay::SharpRight}},
{"reverse", {LaneWay::ReverseLeft, LaneWay::ReverseRight}},
{"next_right", {LaneWay::Right}},
{"slide_left", {LaneWay::Through}},
{"slide_right", {LaneWay::Through}},
};
for (auto const & [in, out] : testData)
{
LanesInfo const result = ParseLanes(in);
LaneWays const expected = {out};
TEST_EQUAL(result.front().laneWays, expected, ());
}
}
UNIT_TEST(TestParseSingleLane)
{
{
LanesInfo const result = ParseLanes("through;right");
LaneWays constexpr expected = {LaneWay::Through, LaneWay::Right};
TEST_EQUAL(result.front().laneWays, expected, ());
}
{
LanesInfo const result = ParseLanes("through;Right");
LaneWays constexpr expected = {LaneWay::Through, LaneWay::Right};
TEST_EQUAL(result.front().laneWays, expected, ());
}
{
LanesInfo const result = ParseLanes("through ;Right");
LaneWays constexpr expected = {LaneWay::Through, LaneWay::Right};
TEST_EQUAL(result.front().laneWays, expected, ());
}
{
LanesInfo const result = ParseLanes("left;through");
LaneWays constexpr expected = {LaneWay::Left, LaneWay::Through};
TEST_EQUAL(result.front().laneWays, expected, ());
}
{
LanesInfo const result = ParseLanes("left");
LaneWays constexpr expected = {LaneWay::Left};
TEST_EQUAL(result.front().laneWays, expected, ());
}
{
LanesInfo const result = ParseLanes("left;");
LaneWays constexpr expected = {LaneWay::Left, LaneWay::None};
TEST_EQUAL(result.front().laneWays, expected, ());
}
{
LanesInfo const result = ParseLanes(";");
LaneWays constexpr expected = {LaneWay::None};
TEST_EQUAL(result.front().laneWays, expected, ());
}
TEST_EQUAL(ParseLanes("SD32kk*887;;").empty(), true, ());
TEST_EQUAL(ParseLanes("Что-то на кириллице").empty(), true, ());
TEST_EQUAL(ParseLanes("משהו בעברית").empty(), true, ());
}
UNIT_TEST(TestParseLanes)
{
{
LanesInfo const result = ParseLanes("through|through|through|through;right");
LanesInfo const expected = {
{{LaneWay::Through}}, {{LaneWay::Through}}, {{LaneWay::Through}}, {{LaneWay::Through, LaneWay::Right}}};
TEST_EQUAL(result, expected, ());
}
{
LanesInfo const result = ParseLanes("left|left;through|through|through");
LanesInfo const expected = {
{{LaneWay::Left}}, {{LaneWay::Left, LaneWay::Through}}, {{LaneWay::Through}}, {{LaneWay::Through}}};
TEST_EQUAL(result, expected, ());
}
{
LanesInfo const result = ParseLanes("left|through|through");
LanesInfo const expected = {{{LaneWay::Left}}, {{LaneWay::Through}}, {{LaneWay::Through}}};
TEST_EQUAL(result, expected, ());
}
{
LanesInfo const result = ParseLanes("left|le ft| through|through | right");
LanesInfo const expected = {
{{LaneWay::Left}}, {{LaneWay::Left}}, {{LaneWay::Through}}, {{LaneWay::Through}}, {{LaneWay::Right}}};
TEST_EQUAL(result, expected, ());
}
{
LanesInfo const result = ParseLanes("left|Left|through|througH|right");
LanesInfo const expected = {
{{LaneWay::Left}}, {{LaneWay::Left}}, {{LaneWay::Through}}, {{LaneWay::Through}}, {{LaneWay::Right}}};
TEST_EQUAL(result, expected, ());
}
{
LanesInfo const result = ParseLanes("left|Left|through|througH|through;right;sharp_rIght");
LanesInfo const expected = {{{LaneWay::Left}},
{{LaneWay::Left}},
{{LaneWay::Through}},
{{LaneWay::Through}},
{{LaneWay::Through, LaneWay::Right, LaneWay::SharpRight}}};
TEST_EQUAL(result, expected, ());
}
{
LanesInfo const result = ParseLanes("left |Left|through|througH|right");
LanesInfo const expected = {
{{LaneWay::Left}}, {{LaneWay::Left}}, {{LaneWay::Through}}, {{LaneWay::Through}}, {{LaneWay::Right}}};
TEST_EQUAL(result, expected, ());
}
{
LanesInfo const result = ParseLanes("|||||slight_right");
LanesInfo const expected = {{{LaneWay::None}}, {{LaneWay::None}}, {{LaneWay::None}},
{{LaneWay::None}}, {{LaneWay::None}}, {{LaneWay::SlightRight}}};
TEST_EQUAL(result, expected, ());
}
{
LanesInfo const result = ParseLanes("|");
LanesInfo const expected = {{{LaneWay::None}}, {{LaneWay::None}}};
TEST_EQUAL(result, expected, ());
}
{
LanesInfo const result = ParseLanes(";|;;;");
LanesInfo const expected = {{{LaneWay::None}}, {{LaneWay::None}}};
TEST_EQUAL(result, expected, ());
}
{
LanesInfo const result = ParseLanes("left|Leftt|through|througH|right");
TEST_EQUAL(result.empty(), true, ());
}
}
} // namespace routing::turns::lanes::test

View File

@@ -0,0 +1,221 @@
#include "routing/turns.hpp"
#include "testing/testing.hpp"
#include "routing/lanes/lanes_recommendation.hpp"
#include "routing/routing_tests/tools.hpp"
namespace routing::turns::lanes::test
{
UNIT_TEST(TestSetRecommendedLaneWays_Smoke)
{
using impl::SetRecommendedLaneWays;
struct CarDirectionToLaneWayMapping
{
CarDirection carDirection;
LaneWay laneWay;
bool shouldBeRecommended;
};
std::vector<CarDirectionToLaneWayMapping> const testData = {
{CarDirection::GoStraight, LaneWay::Through, true},
{CarDirection::TurnRight, LaneWay::Right, true},
{CarDirection::TurnSharpRight, LaneWay::SharpRight, true},
{CarDirection::TurnSlightRight, LaneWay::SlightRight, true},
{CarDirection::TurnLeft, LaneWay::Left, true},
{CarDirection::TurnSharpLeft, LaneWay::SharpLeft, true},
{CarDirection::TurnSlightLeft, LaneWay::SlightLeft, true},
{CarDirection::UTurnLeft, LaneWay::ReverseLeft, true},
{CarDirection::UTurnRight, LaneWay::ReverseRight, true},
{CarDirection::ExitHighwayToLeft, LaneWay::SlightLeft, true},
{CarDirection::ExitHighwayToRight, LaneWay::SlightRight, true},
// We do not recommend any lane way for these directions
{CarDirection::None, LaneWay::None, false},
{CarDirection::EnterRoundAbout, LaneWay::None, false},
{CarDirection::LeaveRoundAbout, LaneWay::None, false},
{CarDirection::StayOnRoundAbout, LaneWay::None, false},
{CarDirection::StartAtEndOfStreet, LaneWay::None, false},
{CarDirection::ReachedYourDestination, LaneWay::None, false},
};
TEST_EQUAL(testData.size(), static_cast<size_t>(CarDirection::Count), ("Not all CarDirection values are covered."));
for (auto const & [carDirection, laneWay, shouldBeRecommended] : testData)
{
LanesInfo lanesInfo = {{{laneWay}}};
bool const isRecommended = SetRecommendedLaneWays(carDirection, lanesInfo);
TEST_EQUAL(isRecommended, shouldBeRecommended,
("CarDirection:", DebugPrint(carDirection), "LaneWay:", DebugPrint(laneWay)));
TEST_EQUAL(lanesInfo[0].recommendedWay, shouldBeRecommended ? laneWay : LaneWay::None, ());
}
}
UNIT_TEST(TestSetRecommendedLaneWays)
{
{
LanesInfo lanesInfo = {
{{LaneWay::ReverseLeft, LaneWay::Left, LaneWay::Through}},
{{LaneWay::Through}},
{{LaneWay::Through}},
{{LaneWay::Through, LaneWay::Right}},
{{LaneWay::Right}},
};
TEST(impl::SetRecommendedLaneWays(CarDirection::GoStraight, lanesInfo), ());
TEST_EQUAL(lanesInfo[0].recommendedWay, LaneWay::Through, ());
TEST_EQUAL(lanesInfo[1].recommendedWay, LaneWay::Through, ());
TEST_EQUAL(lanesInfo[2].recommendedWay, LaneWay::Through, ());
TEST_EQUAL(lanesInfo[3].recommendedWay, LaneWay::Through, ());
TEST_EQUAL(lanesInfo[4].recommendedWay, LaneWay::None, ());
}
{
LanesInfo lanesInfo = {
{{LaneWay::ReverseLeft, LaneWay::Left}},
{{LaneWay::Right}},
};
TEST(!impl::SetRecommendedLaneWays(CarDirection::GoStraight, lanesInfo), ());
TEST_EQUAL(lanesInfo[0].recommendedWay, LaneWay::None, ());
TEST_EQUAL(lanesInfo[1].recommendedWay, LaneWay::None, ());
}
{
LanesInfo lanesInfo = {
{{LaneWay::ReverseLeft, LaneWay::ReverseRight}},
};
TEST(impl::SetRecommendedLaneWays(CarDirection::UTurnLeft, lanesInfo), ());
TEST_EQUAL(lanesInfo[0].recommendedWay, LaneWay::ReverseLeft, ());
TEST_EQUAL(lanesInfo[0].laneWays.Contains(LaneWay::ReverseRight), false, ());
}
}
UNIT_TEST(SetRecommendedLaneWaysApproximately_Smoke)
{
{
struct CarDirectionToLaneWaysApproximateMapping
{
CarDirection carDirection;
std::vector<LaneWay> laneWay;
};
std::vector<CarDirectionToLaneWaysApproximateMapping> const testData = {
{CarDirection::UTurnLeft, {LaneWay::SharpLeft}},
{CarDirection::TurnSharpLeft, {LaneWay::Left}},
{CarDirection::TurnLeft, {LaneWay::SlightLeft, LaneWay::SharpLeft}},
{CarDirection::TurnSlightLeft, {LaneWay::Left}},
{CarDirection::ExitHighwayToLeft, {LaneWay::Left}},
{CarDirection::GoStraight, {LaneWay::SlightRight, LaneWay::SlightLeft}},
{CarDirection::ExitHighwayToRight, {LaneWay::Right}},
{CarDirection::TurnSlightRight, {LaneWay::Right}},
{CarDirection::TurnRight, {LaneWay::SlightRight, LaneWay::SharpRight}},
{CarDirection::TurnSharpRight, {LaneWay::Right}},
{CarDirection::UTurnRight, {LaneWay::SharpRight}},
};
for (auto const & [carDirection, laneWays] : testData)
{
for (auto const & laneWay : laneWays)
{
LanesInfo lanesInfo = {{{laneWay}}};
bool const isRecommended = impl::SetRecommendedLaneWaysApproximately(carDirection, lanesInfo);
TEST(isRecommended, ("CarDirection:", DebugPrint(carDirection), "LaneWay:", DebugPrint(laneWay)));
TEST_EQUAL(lanesInfo[0].recommendedWay, laneWay, ());
}
}
}
{
// Those directions do not have any recommended lane ways.
std::vector const carDirections = {CarDirection::None,
CarDirection::EnterRoundAbout,
CarDirection::LeaveRoundAbout,
CarDirection::StayOnRoundAbout,
CarDirection::StartAtEndOfStreet,
CarDirection::ReachedYourDestination};
for (auto const & carDirection : carDirections)
{
LanesInfo lanesInfo = {{{LaneWay::Through}}};
TEST(!impl::SetRecommendedLaneWaysApproximately(carDirection, lanesInfo), ());
TEST_EQUAL(lanesInfo[0].recommendedWay, LaneWay::None, ());
}
}
}
UNIT_TEST(SetRecommendedLaneWaysApproximately)
{
{
LanesInfo lanesInfo = {
{{LaneWay::ReverseLeft, LaneWay::Left, LaneWay::SlightLeft}},
{{LaneWay::SlightRight, LaneWay::Right}},
{{LaneWay::Right}},
};
TEST(impl::SetRecommendedLaneWaysApproximately(CarDirection::GoStraight, lanesInfo), ());
TEST_EQUAL(lanesInfo[0].recommendedWay, LaneWay::SlightLeft, ());
TEST_EQUAL(lanesInfo[1].recommendedWay, LaneWay::SlightRight, ());
TEST_EQUAL(lanesInfo[2].recommendedWay, LaneWay::None, ());
}
{
LanesInfo lanesInfo = {
{{LaneWay::ReverseLeft, LaneWay::Left}},
{{LaneWay::Right}},
};
TEST(!impl::SetRecommendedLaneWaysApproximately(CarDirection::GoStraight, lanesInfo), ());
TEST_EQUAL(lanesInfo[0].recommendedWay, LaneWay::None, ());
TEST_EQUAL(lanesInfo[1].recommendedWay, LaneWay::None, ());
}
{
LanesInfo lanesInfo = {
{{LaneWay::SharpLeft, LaneWay::SlightLeft}},
};
TEST(impl::SetRecommendedLaneWaysApproximately(CarDirection::TurnLeft, lanesInfo), ());
TEST_EQUAL(lanesInfo[0].recommendedWay, LaneWay::SlightLeft, ());
}
}
UNIT_TEST(SetUnrestrictedLaneAsRecommended)
{
LanesInfo const testData = {{{LaneWay::ReverseLeft}}, {{LaneWay::None}}, {{LaneWay::None}}, {{LaneWay::Right}}};
{
LanesInfo lanesInfo = testData;
TEST(impl::SetUnrestrictedLaneAsRecommended(CarDirection::TurnLeft, lanesInfo), ());
TEST_EQUAL(lanesInfo[0].recommendedWay, LaneWay::None, ());
TEST_EQUAL(lanesInfo[1].recommendedWay, LaneWay::Left, ());
TEST_EQUAL(lanesInfo[2].recommendedWay, LaneWay::None, ());
TEST_EQUAL(lanesInfo[3].recommendedWay, LaneWay::None, ());
}
{
LanesInfo lanesInfo = testData;
TEST(impl::SetUnrestrictedLaneAsRecommended(CarDirection::TurnRight, lanesInfo), ());
TEST_EQUAL(lanesInfo[0].recommendedWay, LaneWay::None, ());
TEST_EQUAL(lanesInfo[1].recommendedWay, LaneWay::None, ());
TEST_EQUAL(lanesInfo[2].recommendedWay, LaneWay::Right, ());
TEST_EQUAL(lanesInfo[3].recommendedWay, LaneWay::None, ());
}
{
LanesInfo lanesInfo = {};
TEST(!impl::SetUnrestrictedLaneAsRecommended(CarDirection::TurnRight, lanesInfo), ());
}
{
LanesInfo lanesInfo = {{{LaneWay::Right}}};
TEST(!impl::SetUnrestrictedLaneAsRecommended(CarDirection::TurnRight, lanesInfo), ());
TEST_EQUAL(lanesInfo[0].recommendedWay, LaneWay::None, ());
}
}
UNIT_TEST(SelectRecommendedLanes)
{
std::vector<TurnItem> turns = {{1, CarDirection::GoStraight},
{2, CarDirection::TurnLeft},
{3, CarDirection::TurnRight},
{4, CarDirection::ReachedYourDestination}};
turns[0].m_lanes.push_back({{LaneWay::Left, LaneWay::Through}});
turns[0].m_lanes.push_back({{LaneWay::Right}});
turns[1].m_lanes.push_back({{LaneWay::SlightLeft}});
turns[1].m_lanes.push_back({{LaneWay::Through}});
turns[1].m_lanes.push_back({{LaneWay::None}});
turns[2].m_lanes.push_back({{LaneWay::Left, LaneWay::SharpLeft}});
turns[2].m_lanes.push_back({{LaneWay::None}});
std::vector<RouteSegment> routeSegments;
RouteSegmentsFrom({}, {}, turns, {}, routeSegments);
SelectRecommendedLanes(routeSegments);
TEST_EQUAL(routeSegments[0].GetTurn().m_lanes[0].recommendedWay, LaneWay::Through, ());
TEST_EQUAL(routeSegments[0].GetTurn().m_lanes[1].recommendedWay, LaneWay::None, ());
TEST_EQUAL(routeSegments[1].GetTurn().m_lanes[0].recommendedWay, LaneWay::SlightLeft, ());
TEST_EQUAL(routeSegments[1].GetTurn().m_lanes[1].recommendedWay, LaneWay::None, ());
TEST_EQUAL(routeSegments[1].GetTurn().m_lanes[2].recommendedWay, LaneWay::None, ());
TEST_EQUAL(routeSegments[2].GetTurn().m_lanes[0].recommendedWay, LaneWay::None, ());
TEST_EQUAL(routeSegments[2].GetTurn().m_lanes[1].recommendedWay, LaneWay::Right, ());
}
} // namespace routing::turns::lanes::test

View File

@@ -18,7 +18,6 @@
#include "base/macros.hpp"
#include <cmath>
#include <string>
#include <vector>
@@ -65,118 +64,6 @@ private:
TUnpackedPathSegments m_segments;
};
UNIT_TEST(TestSplitLanes)
{
vector<string> result;
SplitLanes("through|through|through|through;right", '|', result);
vector<string> const expected1 = {"through", "through", "through", "through;right"};
TEST_EQUAL(result, expected1, ());
SplitLanes("adsjkddfasui8747&sxdsdlad8\"\'", '|', result);
TEST_EQUAL(result, vector<string>({"adsjkddfasui8747&sxdsdlad8\"\'"}), ());
SplitLanes("|||||||", '|', result);
vector<string> expected2 = {"", "", "", "", "", "", ""};
TEST_EQUAL(result, expected2, ());
}
UNIT_TEST(TestParseSingleLane)
{
TSingleLane result;
TEST(ParseSingleLane("through;right", ';', result), ());
TSingleLane const expected1 = {LaneWay::Through, LaneWay::Right};
TEST_EQUAL(result, expected1, ());
TEST(!ParseSingleLane("through;Right", ';', result), ());
TEST(!ParseSingleLane("through ;right", ';', result), ());
TEST_EQUAL(result.size(), 0, ());
TEST(!ParseSingleLane("SD32kk*887;;", ';', result), ());
TEST_EQUAL(result.size(), 0, ());
TEST(!ParseSingleLane("Что-то на кириллице", ';', result), ());
TEST_EQUAL(result.size(), 0, ());
TEST(!ParseSingleLane("משהו בעברית", ';', result), ());
TEST_EQUAL(result.size(), 0, ());
TEST(ParseSingleLane("left;through", ';', result), ());
TSingleLane expected2 = {LaneWay::Left, LaneWay::Through};
TEST_EQUAL(result, expected2, ());
TEST(ParseSingleLane("left", ';', result), ());
TEST_EQUAL(result.size(), 1, ());
TEST_EQUAL(result[0], LaneWay::Left, ());
TEST(ParseSingleLane("left;", ';', result), ());
TSingleLane expected3 = {LaneWay::Left, LaneWay::None};
TEST_EQUAL(result, expected3, ());
TEST(ParseSingleLane(";", ';', result), ());
TSingleLane expected4 = {LaneWay::None, LaneWay::None};
TEST_EQUAL(result, expected4, ());
TEST(ParseSingleLane("", ';', result), ());
TSingleLane expected5 = {LaneWay::None};
TEST_EQUAL(result, expected5, ());
}
UNIT_TEST(TestParseLanes)
{
vector<SingleLaneInfo> result;
TEST(ParseLanes("through|through|through|through;right", result), ());
vector<SingleLaneInfo> const expected1 = {
{LaneWay::Through}, {LaneWay::Through}, {LaneWay::Through}, {LaneWay::Through, LaneWay::Right}};
TEST_EQUAL(result, expected1, ());
TEST(ParseLanes("left|left;through|through|through", result), ());
vector<SingleLaneInfo> const expected2 = {
{LaneWay::Left}, {LaneWay::Left, LaneWay::Through}, {LaneWay::Through}, {LaneWay::Through}};
TEST_EQUAL(result, expected2, ());
TEST(ParseLanes("left|through|through", result), ());
vector<SingleLaneInfo> const expected3 = {{LaneWay::Left}, {LaneWay::Through}, {LaneWay::Through}};
TEST_EQUAL(result, expected3, ());
TEST(ParseLanes("left|le ft| through|through | right", result), ());
vector<SingleLaneInfo> const expected4 = {
{LaneWay::Left}, {LaneWay::Left}, {LaneWay::Through}, {LaneWay::Through}, {LaneWay::Right}};
TEST_EQUAL(result, expected4, ());
TEST(ParseLanes("left|Left|through|througH|right", result), ());
vector<SingleLaneInfo> const expected5 = {
{LaneWay::Left}, {LaneWay::Left}, {LaneWay::Through}, {LaneWay::Through}, {LaneWay::Right}};
TEST_EQUAL(result, expected5, ());
TEST(ParseLanes("left|Left|through|througH|through;right;sharp_rIght", result), ());
vector<SingleLaneInfo> const expected6 = {{LaneWay::Left},
{LaneWay::Left},
{LaneWay::Through},
{LaneWay::Through},
{LaneWay::Through, LaneWay::Right, LaneWay::SharpRight}};
TEST_EQUAL(result, expected6, ());
TEST(!ParseLanes("left|Leftt|through|througH|right", result), ());
TEST_EQUAL(result.size(), 0, ());
TEST(!ParseLanes("Что-то на кириллице", result), ());
TEST_EQUAL(result.size(), 0, ());
TEST(!ParseLanes("משהו בעברית", result), ());
TEST_EQUAL(result.size(), 0, ());
TEST(ParseLanes("left |Left|through|througH|right", result), ());
vector<SingleLaneInfo> const expected7 = {
{LaneWay::Left}, {LaneWay::Left}, {LaneWay::Through}, {LaneWay::Through}, {LaneWay::Right}};
TEST_EQUAL(result, expected7, ());
TEST(ParseLanes("|||||slight_right", result), ());
vector<SingleLaneInfo> const expected8 = {{LaneWay::None}, {LaneWay::None}, {LaneWay::None},
{LaneWay::None}, {LaneWay::None}, {LaneWay::SlightRight}};
TEST_EQUAL(result, expected8, ());
}
UNIT_TEST(TestFixupTurns)
{
double const kHalfSquareSideMeters = 10.;
@@ -238,76 +125,6 @@ UNIT_TEST(TestFixupTurns)
}
}
UNIT_TEST(TestIsLaneWayConformedTurnDirection)
{
TEST(IsLaneWayConformedTurnDirection(LaneWay::Left, CarDirection::TurnLeft), ());
TEST(IsLaneWayConformedTurnDirection(LaneWay::Right, CarDirection::TurnRight), ());
TEST(IsLaneWayConformedTurnDirection(LaneWay::SlightLeft, CarDirection::TurnSlightLeft), ());
TEST(IsLaneWayConformedTurnDirection(LaneWay::SharpRight, CarDirection::TurnSharpRight), ());
TEST(IsLaneWayConformedTurnDirection(LaneWay::Reverse, CarDirection::UTurnLeft), ());
TEST(IsLaneWayConformedTurnDirection(LaneWay::Reverse, CarDirection::UTurnRight), ());
TEST(IsLaneWayConformedTurnDirection(LaneWay::Through, CarDirection::GoStraight), ());
TEST(!IsLaneWayConformedTurnDirection(LaneWay::Left, CarDirection::TurnSlightLeft), ());
TEST(!IsLaneWayConformedTurnDirection(LaneWay::Right, CarDirection::TurnSharpRight), ());
TEST(!IsLaneWayConformedTurnDirection(LaneWay::SlightLeft, CarDirection::GoStraight), ());
TEST(!IsLaneWayConformedTurnDirection(LaneWay::SharpRight, CarDirection::None), ());
TEST(!IsLaneWayConformedTurnDirection(LaneWay::Reverse, CarDirection::TurnLeft), ());
TEST(!IsLaneWayConformedTurnDirection(LaneWay::None, CarDirection::ReachedYourDestination), ());
}
UNIT_TEST(TestIsLaneWayConformedTurnDirectionApproximately)
{
TEST(IsLaneWayConformedTurnDirectionApproximately(LaneWay::Left, CarDirection::TurnSharpLeft), ());
TEST(IsLaneWayConformedTurnDirectionApproximately(LaneWay::Left, CarDirection::TurnSlightLeft), ());
TEST(IsLaneWayConformedTurnDirectionApproximately(LaneWay::Right, CarDirection::TurnSharpRight), ());
TEST(IsLaneWayConformedTurnDirectionApproximately(LaneWay::Right, CarDirection::TurnRight), ());
TEST(IsLaneWayConformedTurnDirectionApproximately(LaneWay::Reverse, CarDirection::UTurnLeft), ());
TEST(IsLaneWayConformedTurnDirectionApproximately(LaneWay::Reverse, CarDirection::UTurnRight), ());
TEST(IsLaneWayConformedTurnDirectionApproximately(LaneWay::SlightLeft, CarDirection::GoStraight), ());
TEST(IsLaneWayConformedTurnDirectionApproximately(LaneWay::SlightRight, CarDirection::GoStraight), ());
TEST(!IsLaneWayConformedTurnDirectionApproximately(LaneWay::SharpLeft, CarDirection::UTurnLeft), ());
TEST(!IsLaneWayConformedTurnDirectionApproximately(LaneWay::SharpLeft, CarDirection::UTurnRight), ());
TEST(!IsLaneWayConformedTurnDirectionApproximately(LaneWay::SharpRight, CarDirection::UTurnLeft), ());
TEST(!IsLaneWayConformedTurnDirectionApproximately(LaneWay::SharpRight, CarDirection::UTurnRight), ());
TEST(!IsLaneWayConformedTurnDirection(LaneWay::Through, CarDirection::ReachedYourDestination), ());
TEST(!IsLaneWayConformedTurnDirectionApproximately(LaneWay::Through, CarDirection::TurnRight), ());
TEST(!IsLaneWayConformedTurnDirectionApproximately(LaneWay::SlightRight, CarDirection::TurnSharpLeft), ());
}
UNIT_TEST(TestAddingActiveLaneInformation)
{
vector<turns::TurnItem> turns = {{1, CarDirection::GoStraight},
{2, CarDirection::TurnLeft},
{3, CarDirection::TurnRight},
{4, CarDirection::ReachedYourDestination}};
turns[0].m_lanes.push_back({LaneWay::Left, LaneWay::Through});
turns[0].m_lanes.push_back({LaneWay::Right});
turns[1].m_lanes.push_back({LaneWay::SlightLeft});
turns[1].m_lanes.push_back({LaneWay::Through});
turns[1].m_lanes.push_back({LaneWay::None});
turns[2].m_lanes.push_back({LaneWay::Left, LaneWay::SharpLeft});
turns[2].m_lanes.push_back({LaneWay::None});
vector<RouteSegment> routeSegments;
RouteSegmentsFrom({}, {}, turns, {}, routeSegments);
SelectRecommendedLanes(routeSegments);
TEST(routeSegments[0].GetTurn().m_lanes[0].m_isRecommended, ());
TEST(!routeSegments[0].GetTurn().m_lanes[1].m_isRecommended, ());
TEST(routeSegments[1].GetTurn().m_lanes[0].m_isRecommended, ());
TEST(!routeSegments[1].GetTurn().m_lanes[1].m_isRecommended, ());
TEST(!routeSegments[1].GetTurn().m_lanes[2].m_isRecommended, ());
TEST(!routeSegments[2].GetTurn().m_lanes[0].m_isRecommended, ());
TEST(routeSegments[2].GetTurn().m_lanes[1].m_isRecommended, ());
}
UNIT_TEST(TestGetRoundaboutDirection)
{
// The signature of GetRoundaboutDirection function is

View File

@@ -5,8 +5,6 @@
#include "platform/country_file.hpp"
#include "base/internal/message.hpp"
#include "base/stl_helpers.hpp"
#include "base/string_utils.hpp"
#include <algorithm>
#include <array>
@@ -21,28 +19,6 @@ using namespace std;
namespace
{
/// The order is important. Starting with the most frequent tokens according to
/// taginfo.openstreetmap.org we minimize the number of the comparisons in ParseSingleLane().
///
/// A `none` lane can be represented either as "none" or as "". That means both "none" and ""
/// should be considered names, even though they refer to the same thing. As a result,
/// `LaneWay::None` appears twice in this array, which is one longer than the number of
/// enum values.
array<pair<LaneWay, char const *>, static_cast<size_t>(LaneWay::Count) + 1> const g_laneWayNames = {
{{LaneWay::None, ""},
{LaneWay::Through, "through"},
{LaneWay::Left, "left"},
{LaneWay::Right, "right"},
{LaneWay::None, "none"},
{LaneWay::SharpLeft, "sharp_left"},
{LaneWay::SlightLeft, "slight_left"},
{LaneWay::MergeToRight, "merge_to_right"},
{LaneWay::MergeToLeft, "merge_to_left"},
{LaneWay::SlightRight, "slight_right"},
{LaneWay::SharpRight, "sharp_right"},
{LaneWay::Reverse, "reverse"}}};
static_assert(g_laneWayNames.size() == static_cast<size_t>(LaneWay::Count) + 1, "Check the size of g_laneWayNames");
array<pair<CarDirection, char const *>, static_cast<size_t>(CarDirection::Count)> const g_turnNames = {
{{CarDirection::None, "None"},
{CarDirection::GoStraight, "GoStraight"},
@@ -162,12 +138,6 @@ string DebugPrint(SegmentRange const & segmentRange)
namespace turns
{
// SingleLaneInfo ---------------------------------------------------------------------------------
bool SingleLaneInfo::operator==(SingleLaneInfo const & other) const
{
return m_lane == other.m_lane && m_isRecommended == other.m_isRecommended;
}
string DebugPrint(TurnItem const & turnItem)
{
stringstream out;
@@ -232,118 +202,6 @@ bool IsGoStraightOrSlightTurn(CarDirection t)
return (t == CarDirection::GoStraight || t == CarDirection::TurnSlightLeft || t == CarDirection::TurnSlightRight);
}
bool IsLaneWayConformedTurnDirection(LaneWay l, CarDirection t)
{
switch (t)
{
default: return false;
case CarDirection::GoStraight: return l == LaneWay::Through;
case CarDirection::TurnRight: return l == LaneWay::Right;
case CarDirection::TurnSharpRight: return l == LaneWay::SharpRight;
case CarDirection::TurnSlightRight:
case CarDirection::ExitHighwayToRight: return l == LaneWay::SlightRight;
case CarDirection::TurnLeft: return l == LaneWay::Left;
case CarDirection::TurnSharpLeft: return l == LaneWay::SharpLeft;
case CarDirection::TurnSlightLeft:
case CarDirection::ExitHighwayToLeft: return l == LaneWay::SlightLeft;
case CarDirection::UTurnLeft:
case CarDirection::UTurnRight: return l == LaneWay::Reverse;
}
}
bool IsLaneWayConformedTurnDirectionApproximately(LaneWay l, CarDirection t)
{
switch (t)
{
default: return false;
case CarDirection::GoStraight: return l == LaneWay::Through || l == LaneWay::SlightRight || l == LaneWay::SlightLeft;
case CarDirection::TurnRight: return l == LaneWay::Right || l == LaneWay::SharpRight || l == LaneWay::SlightRight;
case CarDirection::TurnSharpRight: return l == LaneWay::SharpRight || l == LaneWay::Right;
case CarDirection::TurnSlightRight: return l == LaneWay::SlightRight || l == LaneWay::Through || l == LaneWay::Right;
case CarDirection::TurnLeft: return l == LaneWay::Left || l == LaneWay::SlightLeft || l == LaneWay::SharpLeft;
case CarDirection::TurnSharpLeft: return l == LaneWay::SharpLeft || l == LaneWay::Left;
case CarDirection::TurnSlightLeft: return l == LaneWay::SlightLeft || l == LaneWay::Through || l == LaneWay::Left;
case CarDirection::UTurnLeft:
case CarDirection::UTurnRight: return l == LaneWay::Reverse;
case CarDirection::ExitHighwayToLeft: return l == LaneWay::SlightLeft || l == LaneWay::Left;
case CarDirection::ExitHighwayToRight: return l == LaneWay::SlightRight || l == LaneWay::Right;
}
}
bool IsLaneUnrestricted(SingleLaneInfo const & lane)
{
/// @todo Is there any reason to store None single lane?
return lane.m_lane.size() == 1 && lane.m_lane[0] == LaneWay::None;
}
void SplitLanes(string const & lanesString, char delimiter, vector<string> & lanes)
{
lanes.clear();
istringstream lanesStream(lanesString);
string token;
while (getline(lanesStream, token, delimiter))
lanes.push_back(token);
}
bool ParseSingleLane(string const & laneString, char delimiter, TSingleLane & lane)
{
lane.clear();
// When `laneString` ends with "" representing none, for example, in "right;",
// `getline` will not read any characters, so it exits the loop and does not
// handle the "". So, we add a delimiter to the end of `laneString`. Nonempty
// final tokens consume the delimiter and act as expected, and empty final tokens
// read a the delimiter, so `getline` sets `token` to the empty string rather than
// exiting the loop.
istringstream laneStream(laneString + delimiter);
string token;
while (getline(laneStream, token, delimiter))
{
auto const it = find_if(g_laneWayNames.begin(), g_laneWayNames.end(),
[&token](pair<LaneWay, string> const & p) { return p.second == token; });
if (it == g_laneWayNames.end())
return false;
lane.push_back(it->first);
}
return true;
}
bool ParseLanes(string lanesString, vector<SingleLaneInfo> & lanes)
{
if (lanesString.empty())
return false;
lanes.clear();
strings::AsciiToLower(lanesString);
base::EraseIf(lanesString, strings::IsASCIISpace<std::string::value_type>);
vector<string> SplitLanesStrings;
SingleLaneInfo lane;
SplitLanes(lanesString, '|', SplitLanesStrings);
for (string const & s : SplitLanesStrings)
{
if (!ParseSingleLane(s, ';', lane.m_lane))
{
lanes.clear();
return false;
}
lanes.push_back(lane);
}
return true;
}
string DebugPrint(LaneWay const l)
{
auto const it = find_if(g_laneWayNames.begin(), g_laneWayNames.end(),
[&l](pair<LaneWay, string> const & p) { return p.first == l; });
if (it == g_laneWayNames.end())
{
stringstream out;
out << "unknown LaneWay (" << static_cast<int>(l) << ")";
return out.str();
}
return it->second;
}
string DebugPrint(CarDirection const turn)
{
return GetTurnString(turn);
@@ -368,14 +226,6 @@ string DebugPrint(PedestrianDirection const l)
return "unknown PedestrianDirection";
}
string DebugPrint(SingleLaneInfo const & singleLaneInfo)
{
stringstream out;
out << "SingleLaneInfo [ m_isRecommended == " << singleLaneInfo.m_isRecommended
<< ", m_lane == " << ::DebugPrint(singleLaneInfo.m_lane) << " ]" << endl;
return out.str();
}
double PiMinusTwoVectorsAngle(m2::PointD const & junctionPoint, m2::PointD const & ingoingPoint,
m2::PointD const & outgoingPoint)
{

View File

@@ -1,5 +1,6 @@
#pragma once
#include "routing/lanes/lane_info.hpp"
#include "routing/segment.hpp"
#include "routing_common/num_mwm_id.hpp"
@@ -8,7 +9,6 @@
#include "geometry/point2d.hpp"
#include <initializer_list>
#include <limits>
#include <string>
#include <vector>
@@ -118,41 +118,6 @@ enum class PedestrianDirection
std::string DebugPrint(PedestrianDirection const l);
/*!
* \warning The values of LaneWay shall be synchronized with values of LaneWay enum in java.
*/
enum class LaneWay
{
None = 0,
Reverse,
SharpLeft,
Left,
SlightLeft,
MergeToRight,
Through,
MergeToLeft,
SlightRight,
Right,
SharpRight,
Count /**< This value is used for internals only. */
};
std::string DebugPrint(LaneWay const l);
typedef std::vector<LaneWay> TSingleLane;
struct SingleLaneInfo
{
TSingleLane m_lane;
bool m_isRecommended = false;
SingleLaneInfo() = default;
SingleLaneInfo(std::initializer_list<LaneWay> const & l) : m_lane(l) {}
bool operator==(SingleLaneInfo const & other) const;
};
std::string DebugPrint(SingleLaneInfo const & singleLaneInfo);
struct TurnItem
{
TurnItem()
@@ -192,7 +157,7 @@ struct TurnItem
uint32_t m_index; /*!< Index of point on route polyline (Index of segment + 1). */
CarDirection m_turn = CarDirection::None; /*!< The turn instruction of the TurnItem */
std::vector<SingleLaneInfo> m_lanes; /*!< Lane information on the edge before the turn. */
lanes::LanesInfo m_lanes; /*!< Lane information on the edge before the turn. */
uint32_t m_exitNum; /*!< Number of exit on roundabout. */
/*!
* \brief m_pedestrianTurn is type of corresponding direction for a pedestrian, or None
@@ -223,39 +188,6 @@ bool IsTurnMadeFromLeft(CarDirection t);
bool IsTurnMadeFromRight(CarDirection t);
bool IsStayOnRoad(CarDirection t);
bool IsGoStraightOrSlightTurn(CarDirection t);
/*!
* \param l A variant of going along a lane.
* \param t A turn direction.
* \return True if @l corresponds with @t exactly. For example it returns true
* when @l equals to LaneWay::Right and @t equals to TurnDirection::TurnRight.
* Otherwise it returns false.
*/
bool IsLaneWayConformedTurnDirection(LaneWay l, CarDirection t);
/*!
* \param l A variant of going along a lane.
* \param t A turn direction.
* \return True if @l corresponds with @t approximately. For example it returns true
* when @l equals to LaneWay::Right and @t equals to TurnDirection::TurnSlightRight.
* Otherwise it returns false.
*/
bool IsLaneWayConformedTurnDirectionApproximately(LaneWay l, CarDirection t);
bool IsLaneUnrestricted(SingleLaneInfo const & lane);
/*!
* \brief Parse lane information which comes from @lanesString
* \param lanesString lane information. Example through|through|through|through;right
* \param lanes the result of parsing.
* \return true if @lanesString parsed successfully, false otherwise.
* Note 1: if @lanesString is empty returns false.
* Note 2: @laneString is passed by value on purpose. It'll be used(changed) in the method.
*/
bool ParseLanes(std::string lanesString, std::vector<SingleLaneInfo> & lanes);
void SplitLanes(std::string const & lanesString, char delimiter, std::vector<std::string> & lanes);
bool ParseSingleLane(std::string const & laneString, char delimiter, TSingleLane & lane);
/*!
* \returns pi minus angle from vector [junctionPoint, ingoingPoint]
* to vector [junctionPoint, outgoingPoint]. A counterclockwise rotation.