Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-22 13:58:55 +01:00
parent 4af19165ec
commit 68073add76
12458 changed files with 12350765 additions and 2 deletions

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