WIP: [generator][core][android] Lanes refactoring #7077

Draft
AndrewShkrob wants to merge 1 commit from AndrewShkrob/android/lanes-refactoring into master
22 changed files with 722 additions and 606 deletions

View file

@ -1244,7 +1244,7 @@ Java_app_organicmaps_Framework_nativeGetRouteFollowingInfo(JNIEnv * env, jclass)
"Ljava/lang/String;Ljava/lang/String;DIIIII"
"[Lapp/organicmaps/routing/SingleLaneInfo;ZZ)V");
vector<routing::FollowingInfo::SingleLaneInfoClient> const & lanes = info.m_lanes;
auto const & lanes = info.m_lanes;
jobjectArray jLanes = nullptr;
if (!lanes.empty())
{
@ -1252,18 +1252,13 @@ Java_app_organicmaps_Framework_nativeGetRouteFollowingInfo(JNIEnv * env, jclass)
auto const lanesSize = static_cast<jsize>(lanes.size());
jLanes = env->NewObjectArray(lanesSize, laneClass, nullptr);
ASSERT(jLanes, (jni::DescribeException()));
static jmethodID const ctorSingleLaneInfoID = jni::GetConstructorID(env, laneClass, "([BZ)V");
static jmethodID const ctorSingleLaneInfoID = jni::GetConstructorID(env, laneClass, "(SS)V");
for (jsize j = 0; j < lanesSize; ++j)
{
auto const laneSize = static_cast<jsize>(lanes[j].m_lane.size());
jni::TScopedLocalByteArrayRef singleLane(env, env->NewByteArray(laneSize));
ASSERT(singleLane.get(), (jni::DescribeException()));
env->SetByteArrayRegion(singleLane.get(), 0, laneSize, lanes[j].m_lane.data());
jni::TScopedLocalRef singleLaneInfo(
env, env->NewObject(laneClass, ctorSingleLaneInfoID, singleLane.get(),
lanes[j].m_isRecommended));
env, env->NewObject(laneClass, ctorSingleLaneInfoID, static_cast<jshort>(lanes[j].m_laneWays.data),
static_cast<jshort>(lanes[j].m_recommendedLaneWays.data)));
ASSERT(singleLaneInfo.get(), (jni::DescribeException()));
env->SetObjectArrayElement(jLanes, j, singleLaneInfo.get());
}

View file

@ -2,33 +2,45 @@ package app.organicmaps.routing;
import androidx.annotation.DrawableRes;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import app.organicmaps.R;
import app.organicmaps.util.log.Logger;
import java.util.ArrayList;
import java.util.List;
public class SingleLaneInfo
{
public LaneWay[] mLane;
public boolean mIsActive;
@NonNull
public final LaneWay[] mLane;
@Nullable
public final LaneWay[] mRecommendedLaneWays;
public final boolean mIsActive;
/**
* IMPORTANT : Order of enum values MUST BE the same
* with native LaneWay enum (see routing/turns.hpp for details).
* with native LaneWay union (see routing/lanes.hpp for details).
* Information for every lane is composed of some number values below.
* For example, a lane may have THROUGH and RIGHT values.
*/
public enum LaneWay
{
NONE(R.drawable.ic_turn_straight),
REVERSE(R.drawable.ic_turn_uleft),
SHARP_LEFT(R.drawable.ic_turn_left_sharp),
LEFT(R.drawable.ic_turn_left),
SLIGHT_LEFT(R.drawable.ic_turn_left_slight),
MERGE_TO_RIGHT(R.drawable.ic_turn_right_slight),
SHARP_LEFT(R.drawable.ic_turn_left_sharp),
THROUGH(R.drawable.ic_turn_straight),
MERGE_TO_LEFT(R.drawable.ic_turn_left_slight),
SLIGHT_RIGHT(R.drawable.ic_turn_right_slight),
RIGHT(R.drawable.ic_turn_right),
SHARP_RIGHT(R.drawable.ic_turn_right_sharp);
SLIGHT_RIGHT(R.drawable.ic_turn_right_slight),
SHARP_RIGHT(R.drawable.ic_turn_right_sharp),
REVERSE(R.drawable.ic_turn_uleft),
MERGE_TO_LEFT(R.drawable.ic_turn_left_slight),
MERGE_TO_RIGHT(R.drawable.ic_turn_right_slight),
SLIDE_LEFT(R.drawable.ic_turn_left_slight),
SLIDE_RIGHT(R.drawable.ic_turn_right_slight),
NEXT_RIGHT(R.drawable.ic_turn_right);
public final int mTurnRes;
@ -40,25 +52,54 @@ public class SingleLaneInfo
// Called from JNI.
@Keep
@SuppressWarnings("unused")
SingleLaneInfo(byte[] laneOrdinals, boolean isActive)
SingleLaneInfo(final short laneWays, final short recommendedLaneWays)
{
mLane = new LaneWay[laneOrdinals.length];
final LaneWay[] values = LaneWay.values();
for (int i = 0; i < mLane.length; i++)
mLane[i] = values[laneOrdinals[i]];
mIsActive = isActive;
if (laneWays == 0)
mLane = new LaneWay[]{LaneWay.THROUGH};
else
mLane = createLaneWays(laneWays);
if (recommendedLaneWays == 0)
mRecommendedLaneWays = null;
else
mRecommendedLaneWays = createLaneWays(recommendedLaneWays);
mIsActive = recommendedLaneWays != 0;
}
@NonNull
@Override
public String toString()
{
final int initialCapacity = 32;
StringBuilder sb = new StringBuilder(initialCapacity);
sb.append("Is the lane active? ").append(mIsActive).append(". The lane directions IDs are");
StringBuilder sb = new StringBuilder();
sb.append("SingleLaneInfo[mLaneWays=[");
for (LaneWay i : mLane)
sb.append(" ").append(i.ordinal());
sb.append(i.toString()).append(" ");
sb.append("], mRecommendedLaneWays=[");
if (mRecommendedLaneWays != null)
for (LaneWay i : mRecommendedLaneWays)
sb.append(i.toString()).append(" ");
else
sb.append("null]");
sb.append("]");
return sb.toString();
}
@NonNull
private static LaneWay[] createLaneWays(final short laneWays)
{
final LaneWay[] values = LaneWay.values();
final List<LaneWay> result = new ArrayList<>();
for (int index = 0, ways = laneWays; ways > 0; ways >>= 1, index++)
{
if ((ways & 1) != 1)
continue;
if (index >= values.length)
{
Logger.w(SingleLaneInfo.class.getSimpleName(), "Index >= LaneWay.size(); Probably, unused bits are set");
continue;
}
result.add(values[index]);
}
return result.toArray(new LaneWay[0]);
}
}

View file

@ -319,10 +319,11 @@ public:
bool operator()(UniChar c) const;
};
template <class StringT> class SimpleTokenizer : public
TokenizeIterator<SimpleDelimiter, ::utf8::unchecked::iterator<typename StringT::const_iterator>, false /* KeepEmptyTokens */>
template <class StringT, bool KeepEmptyTokens = false>
class SimpleTokenizer : public
TokenizeIterator<SimpleDelimiter, ::utf8::unchecked::iterator<typename StringT::const_iterator>, KeepEmptyTokens>
{
using BaseT = TokenizeIterator<SimpleDelimiter, ::utf8::unchecked::iterator<typename StringT::const_iterator>, false /* KeepEmptyTokens */>;
using BaseT = TokenizeIterator<SimpleDelimiter, ::utf8::unchecked::iterator<typename StringT::const_iterator>, KeepEmptyTokens>;
public:
SimpleTokenizer(StringT const & str, SimpleDelimiter const & delims)
: BaseT(str.begin(), str.end(), delims)
@ -330,10 +331,10 @@ public:
}
};
template <typename TFunctor>
template <typename TFunctor, bool KeepEmptyTokens = false>
void Tokenize(std::string_view str, char const * delims, TFunctor && f)
{
SimpleTokenizer iter(str, delims);
SimpleTokenizer<std::string_view, KeepEmptyTokens> iter(str, delims);
while (iter)
{
f(*iter);
@ -342,11 +343,12 @@ void Tokenize(std::string_view str, char const * delims, TFunctor && f)
}
/// @note Lifetime of return container is the same as \a str lifetime. Avoid temporary input.
template <class ResultT = std::string_view>
template <class ResultT = std::string_view, bool KeepEmtyTokens = false>
std::vector<ResultT> Tokenize(std::string_view str, char const * delims)
{
std::vector<ResultT> c;
Tokenize(str, delims, [&c](std::string_view v) { c.push_back(ResultT(v)); });
auto f = [&c](std::string_view v) { c.push_back(ResultT(v)); };
Tokenize<decltype(f), KeepEmtyTokens>(str, delims, std::move(f));
return c;
}
@ -428,6 +430,16 @@ bool ToInteger(char const * start, T & result, int base = 10)
return internal::ToInteger(s, i, base);
}
[[nodiscard]] inline bool to_int16(char const * s, short & i, int base = 10)
{
return internal::ToInteger(s, i, base);
}
[[nodiscard]] inline bool to_uint16(char const * s, unsigned short & i, int base = 10)
{
return internal::ToInteger(s, i, base);
}
// Note: negative values will be converted too.
// For ex.:
// "-1" converts to std::numeric_limits<uint64_t>::max();

View file

@ -225,6 +225,8 @@ set(SRC
translator_world.hpp
translators_pool.cpp
translators_pool.hpp
turn_lanes_metadata_processor.cpp
turn_lanes_metadata_processor.hpp
unpack_mwm.cpp
unpack_mwm.hpp
utils.cpp

View file

@ -1,5 +1,7 @@
#include "generator/osm2meta.hpp"
#include "generator/turn_lanes_metadata_processor.hpp"
#include "indexer/classificator.hpp"
#include "indexer/ftypes_matcher.hpp"
#include "indexer/editable_map_object.hpp"
@ -170,17 +172,7 @@ std::string MetadataTagProcessorImpl::ValidateAndFormat_junction_ref(std::string
std::string MetadataTagProcessorImpl::ValidateAndFormat_turn_lanes(std::string const & v)
{
return v;
}
std::string MetadataTagProcessorImpl::ValidateAndFormat_turn_lanes_forward(std::string const & v)
{
return v;
}
std::string MetadataTagProcessorImpl::ValidateAndFormat_turn_lanes_backward(std::string const & v)
{
return v;
return generator::TurnLanesMetadataProcessor::ValidateAndFormat(v);
}
std::string MetadataTagProcessorImpl::ValidateAndFormat_email(std::string const & v)
@ -498,9 +490,9 @@ void MetadataTagProcessor::operator()(std::string const & k, std::string const &
case Metadata::FMD_DESTINATION: valid = ValidateAndFormat_destination(v); break;
case Metadata::FMD_DESTINATION_REF: valid = ValidateAndFormat_destination_ref(v); break;
case Metadata::FMD_JUNCTION_REF: valid = ValidateAndFormat_junction_ref(v); break;
case Metadata::FMD_TURN_LANES: valid = ValidateAndFormat_turn_lanes(v); break;
case Metadata::FMD_TURN_LANES_FORWARD: valid = ValidateAndFormat_turn_lanes_forward(v); break;
case Metadata::FMD_TURN_LANES_BACKWARD: valid = ValidateAndFormat_turn_lanes_backward(v); break;
case Metadata::FMD_TURN_LANES:
case Metadata::FMD_TURN_LANES_FORWARD:
case Metadata::FMD_TURN_LANES_BACKWARD: valid = ValidateAndFormat_turn_lanes(v); break;
case Metadata::FMD_EMAIL: valid = ValidateAndFormat_email(v); break;
case Metadata::FMD_POSTCODE: valid = ValidateAndFormat_postcode(v); break;
case Metadata::FMD_WIKIPEDIA: valid = ValidateAndFormat_wikipedia(v); break;

View file

@ -19,9 +19,7 @@ struct MetadataTagProcessorImpl
static std::string ValidateAndFormat_destination(std::string const & v) ;
static std::string ValidateAndFormat_destination_ref(std::string const & v) ;
static std::string ValidateAndFormat_junction_ref(std::string const & v) ;
static std::string ValidateAndFormat_turn_lanes(std::string const & v) ;
static std::string ValidateAndFormat_turn_lanes_forward(std::string const & v) ;
static std::string ValidateAndFormat_turn_lanes_backward(std::string const & v) ;
static std::string ValidateAndFormat_turn_lanes(std::string const & v);
static std::string ValidateAndFormat_email(std::string const & v) ;
static std::string ValidateAndFormat_postcode(std::string const & v) ;
static std::string ValidateAndFormat_flats(std::string const & v) ;

View file

@ -0,0 +1,67 @@
#include "turn_lanes_metadata_processor.hpp"
#include "base/string_utils.hpp"
#include <unordered_map>
#include <vector>
namespace
{
/// @warning The order of these values must be synchronized with @union LaneWays
const std::unordered_map<std::string_view, std::uint8_t> laneWayStrToBitIdx = {
// clang-format off
{"left", 0},
{"slight_left", 1},
{"sharp_left", 2},
{"through", 3},
{"right", 4},
{"slight_right", 5},
{"sharp_right", 6},
{"reverse", 7},
{"merge_to_left", 8},
{"merge_to_right", 9},
{"slide_left", 10},
{"slide_right", 11},
{"next_right", 12},
// clang-format on
};
std::string ToString(std::vector<std::uint16_t> const & lanes)
{
std::ostringstream os;
bool first = true;
for (auto const & laneWays : lanes)
{
if (!first)
os << "|";
os << std::to_string(laneWays);
first = false;
}
return os.str();
}
} // namespace
namespace generator
{
std::string TurnLanesMetadataProcessor::ValidateAndFormat(std::string value)
{
if (value.empty())
return "";
strings::AsciiToLower(value);
base::EraseIf(value, [](char const c) { return isspace(c); });
std::vector<std::uint16_t> lanes;
for (auto const lanesStr : strings::Tokenize(value, "|"))
{
lanes.emplace_back(0);
auto & laneWays = lanes.back();
for (auto const laneWayStr : strings::Tokenize(lanesStr, ";"))
{
if (laneWayStrToBitIdx.count(laneWayStr))
laneWays |= 1 << laneWayStrToBitIdx.at(laneWayStr);
}
}
return ToString(lanes);
}
} // namespace generator

View file

@ -0,0 +1,12 @@
#pragma once
#include <string>
namespace generator
{
class TurnLanesMetadataProcessor
{
public:
static std::string ValidateAndFormat(std::string value);
};
} // namespace generator

View file

@ -80,6 +80,8 @@ set(SRC
joint_segment.hpp
junction_visitor.cpp
junction_visitor.hpp
lanes.cpp
lanes.hpp
latlon_with_altitude.cpp
latlon_with_altitude.hpp
leaps_graph.cpp

View file

@ -6,8 +6,6 @@
#include "geometry/angles.hpp"
#include <limits.h>
namespace routing
{
using namespace std;
@ -85,7 +83,7 @@ void FixupCarTurns(vector<RouteSegment> & routeSegments)
routeSegments[idx - 1].ClearTurn();
}
}
SelectRecommendedLanes(routeSegments);
lanes::SelectRecommendedLanes(routeSegments);
}
void GetTurnDirectionBasic(IRoutingResult const & result, size_t const outgoingSegmentIndex,
@ -599,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)
{
const It 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

@ -24,11 +24,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

@ -27,25 +27,6 @@ public:
{
}
// 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.
@ -61,7 +42,7 @@ public:
//@}
int m_time;
// m_lanes contains lane information on the edge before the turn.
std::vector<SingleLaneInfoClient> m_lanes;
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

249
routing/lanes.cpp Normal file
View file

@ -0,0 +1,249 @@
#include "lanes.hpp"
#include "base/string_utils.hpp"
using routing::turns::CarDirection;
// TODO: remove before merge
namespace
{
/// @warning The order of these values must be synchronized with @union LaneWays
const std::unordered_map<std::string_view, std::uint8_t> laneWayStrToBitIdx = {
// clang-format off
{"left", 0},
{"slight_left", 1},
{"sharp_left", 2},
{"through", 3},
{"right", 4},
{"slight_right", 5},
{"sharp_right", 6},
{"reverse", 7},
{"merge_to_left", 8},
{"merge_to_right", 9},
{"slide_left", 10},
{"slide_right", 11},
{"next_right", 12},
// clang-format on
};
std::string ToString(std::vector<std::uint16_t> const & lanes)
{
std::ostringstream os;
bool first = true;
for (auto const & laneWays : lanes)
{
if (!first)
os << "|";
os << std::to_string(laneWays);
first = false;
}
return os.str();
}
} // namespace
namespace generator
{
std::string ValidateAndFormat(std::string value)
{
if (value.empty())
return "";
strings::AsciiToLower(value);
base::EraseIf(value, [](char const c) { return isspace(c); });
std::vector<std::uint16_t> lanes;
for (auto const lanesStr : strings::Tokenize<std::string_view, true>(value, "|"))
{
lanes.emplace_back(0);
auto & laneWays = lanes.back();
for (auto const laneWayStr : strings::Tokenize<std::string_view, true>(lanesStr, ";"))
{
if (laneWayStrToBitIdx.count(laneWayStr))
laneWays |= 1 << laneWayStrToBitIdx.at(laneWayStr);
}
}
return ToString(lanes);
}
}
// TODO: Remove before merge
namespace
{
using CarDirectionToLaneWays = std::unordered_map<CarDirection, std::uint16_t>;
const CarDirectionToLaneWays carDirectionToLaneWays{
// clang-format off
{CarDirection::TurnLeft, 0b00000001}, // Left
{CarDirection::TurnSlightLeft, 0b00000010}, // SlightLeft
{CarDirection::ExitHighwayToLeft, 0b00000010}, // SlightLeft
{CarDirection::TurnSharpLeft, 0b00000100}, // SharpLeft
{CarDirection::GoStraight, 0b00001000}, // Through
{CarDirection::TurnRight, 0b00010000}, // Right
{CarDirection::TurnSlightRight, 0b00100000}, // SlightRight
{CarDirection::ExitHighwayToRight, 0b00100000}, // SlightRight
{CarDirection::TurnSharpRight, 0b01000000}, // SharpRight
{CarDirection::UTurnLeft, 0b10000000}, // Reverse
{CarDirection::UTurnRight, 0b10000000}, // Reverse
// clang-format on
};
const CarDirectionToLaneWays carDirectionToLaneWaysApproximate{
// clang-format off
{CarDirection::TurnLeft, 0b0000000000111}, // Left, SlightLeft, SharpLeft
{CarDirection::TurnSlightLeft, 0b0000000001011}, // Left, SlightLeft, Through
{CarDirection::ExitHighwayToLeft, 0b0000000000011}, // Left, SlightLeft
{CarDirection::TurnSharpLeft, 0b0000000000101}, // Left, SharpLeft
{CarDirection::GoStraight, 0b0111100101010}, // SlightLeft, Through, SlightRight, MergeToLeft, MergeToRight, SlideLeft, SlideRight
{CarDirection::TurnRight, 0b1000001110000}, // Right, SlightRight, SharpRight, NextRight
{CarDirection::TurnSlightRight, 0b0000000111000}, // Through, Right, SlightRight
{CarDirection::ExitHighwayToRight, 0b0000000110000}, // Right, SlightRight
{CarDirection::TurnSharpRight, 0b0000001010000}, // Right, SharpRight
{CarDirection::UTurnLeft, 0b0000010000000}, // Reverse
{CarDirection::UTurnRight, 0b0000010000000}, // Reverse
// clang-format on
};
} // namespace
namespace routing::turns::lanes
{
namespace
{
bool FixupLaneSet(CarDirection const turn, LanesInfo & lanes, CarDirectionToLaneWays const & mapping)
{
bool isLaneConformed = false;
for (auto & [laneWays, recommendedLaneWays] : lanes)
{
if (mapping.count(turn))
{
recommendedLaneWays.data = laneWays.data & mapping.at(turn);
if (recommendedLaneWays.data != 0)
isLaneConformed = true;
}
}
return isLaneConformed;
}
template <typename It>
bool SelectFirstUnrestrictedLane(bool direction, It lanesBegin, It lanesEnd)
{
const It firstUnrestricted = find_if(lanesBegin, lanesEnd, [](auto const & it) { return it.m_laneWays.data == 0; });
if (firstUnrestricted == lanesEnd)
return false;
if (direction)
{
firstUnrestricted->m_laneWays.turns.left = 1;
firstUnrestricted->m_recommendedLaneWays.turns.left = 1;
}
else
{
firstUnrestricted->m_laneWays.turns.right = 1;
firstUnrestricted->m_recommendedLaneWays.turns.right = 1;
}
return true;
}
bool SelectUnrestrictedLane(CarDirection const turn, LanesInfo & lanes)
{
if (IsTurnMadeFromLeft(turn))
return SelectFirstUnrestrictedLane(true, lanes.begin(), lanes.end());
if (IsTurnMadeFromRight(turn))
return SelectFirstUnrestrictedLane(false, lanes.rbegin(), lanes.rend());
return false;
}
} // namespace
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, carDirectionToLaneWays))
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, carDirectionToLaneWaysApproximate))
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();
}
}
bool ParseLanes(std::string const & lanesString, LanesInfo & lanes)
{
if (lanesString.empty())
return false;
lanes.clear();
// TODO: Complete solution requires maps regeneration. This is a workaround for testing. Remove before merge.
std::string const & lanesStr = generator::ValidateAndFormat(lanesString);
for (auto const laneWaysStr : strings::Tokenize<std::string_view, true>(lanesStr, "|"))
{
SingleLaneInfo info;
// TODO: Fix std::string(laneWaysStr).c_str()
VERIFY(strings::to_uint16(std::string(laneWaysStr).c_str(), info.m_laneWays.data), ());
ASSERT(info.m_laneWays.turns.unused == 0, ());
lanes.push_back(info);
}
return true;
}
std::string DebugPrint(LaneWays const laneWays)
{
std::ostringstream out;
if (laneWays.data == 0)
{
out << "LaneWays[none]";
return out.str();
}
// clang-format off
out << "LaneWays["
<< "Left: " << laneWays.turns.left
<< ", SlightLeft: " << laneWays.turns.slightLeft
<< ", SharpLeft: " << laneWays.turns.sharpLeft
<< ", Through: " << laneWays.turns.through
<< ", Right: " << laneWays.turns.right
<< ", SlightRight: " << laneWays.turns.slightRight
<< ", SharpRight: " << laneWays.turns.sharpRight
<< ", Reverse: " << laneWays.turns.reverse
<< ", MergeToLeft: " << laneWays.turns.mergeToLeft
<< ", MergeToRight: " << laneWays.turns.mergeToRight
<< ", SlideLeft: " << laneWays.turns.slideLeft
<< ", SlideRight: " << laneWays.turns.slideRight
<< ", NextRight: " << laneWays.turns.nextRight
<< ", UnusedBits: " << laneWays.turns.unused
<< "]";
// clang-format on
return out.str();
}
std::string DebugPrint(SingleLaneInfo const & singleLaneInfo)
{
std::stringstream out;
out << "SingleLaneInfo[m_laneWays: " << DebugPrint(singleLaneInfo.m_laneWays)
<< ", m_recommendedLaneWays: " << DebugPrint(singleLaneInfo.m_recommendedLaneWays) << "]";
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

82
routing/lanes.hpp Normal file
View file

@ -0,0 +1,82 @@
#pragma once
#include <cstdint>
#include <string>
#include <vector>
namespace routing
{
class RouteSegment;
namespace turns
{
enum class CarDirection;
}
} // namespace routing
namespace routing::turns::lanes
{
/**
* \brief This union represents all possible lane turns according to
* \link https://wiki.openstreetmap.org/wiki/Key:turn \endlink
*/
union LaneWays
{
/**
* \warning Do not change the order of these variables.
*/
struct
{
// clang-format off
std::uint16_t left : 1;
std::uint16_t slightLeft : 1;
std::uint16_t sharpLeft : 1;
std::uint16_t through : 1;
std::uint16_t right : 1;
std::uint16_t slightRight : 1;
std::uint16_t sharpRight : 1;
std::uint16_t reverse : 1;
std::uint16_t mergeToLeft : 1;
std::uint16_t mergeToRight : 1;
std::uint16_t slideLeft : 1;
std::uint16_t slideRight : 1;
std::uint16_t nextRight : 1;
std::uint16_t unused : 3;
// clang-format on
} turns;
std::uint16_t data = 0;
bool operator==(LaneWays const other) const { return data == other.data; }
};
struct SingleLaneInfo
{
LaneWays m_laneWays;
LaneWays m_recommendedLaneWays;
bool operator==(SingleLaneInfo const & other) const
{
return m_laneWays == other.m_laneWays && m_recommendedLaneWays == other.m_recommendedLaneWays;
}
};
using LanesInfo = std::vector<SingleLaneInfo>;
/*!
* \brief Selects lanes which are recommended for an end user.
*/
void SelectRecommendedLanes(std::vector<RouteSegment> & routeSegments);
/**
* \brief Parse lane information which comes from @lanesString
* \param lanesString lane information. Example 0b01001|0|0b1000. \see \union LaneWays
* \param lanes the result of parsing.
* \return true if @lanesString parsed successfully, false otherwise.
* \note if @lanesString is empty returns false.
*/
bool ParseLanes(std::string const & lanesString, LanesInfo & lanes);
std::string DebugPrint(LaneWays laneWays);
std::string DebugPrint(SingleLaneInfo const & singleLaneInfo);
std::string DebugPrint(LanesInfo const & lanesInfo);
} // namespace routing::turns::lanes

View file

@ -23,7 +23,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

@ -111,7 +111,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

@ -431,14 +431,7 @@ void RoutingSession::GetRouteFollowingInfo(FollowingInfo & info) const
if (distanceToTurnMeters < kShowLanesMinDistInMeters || timeToNearestTurnSec < 60.0)
{
info.m_displayedStreetName = info.m_targetName;
// 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.clear();
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;
}
else
{

View file

@ -19,6 +19,7 @@ set(SRC
index_graph_test.cpp
index_graph_tools.cpp
index_graph_tools.hpp
lanes_tests.cpp
maxspeeds_tests.cpp
mwm_hierarchy_test.cpp
nearest_edge_finder_tests.cpp

View file

@ -0,0 +1,202 @@
#include "testing/testing.hpp"
#include "routing/lanes.hpp"
#include "routing/car_directions.hpp"
/*
namespace routing::turns::lanes::test
{
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)
{
LaneWays result;
TEST(ParseSingleLane("through;right", ';', result), ());
LaneWays 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), ());
LaneWays const 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), ());
LaneWays const expected3 = {LaneWay::Left, LaneWay::None};
TEST_EQUAL(result, expected3, ());
TEST(ParseSingleLane(";", ';', result), ());
LaneWays const expected4 = {LaneWay::None, LaneWay::None};
TEST_EQUAL(result, expected4, ());
TEST(ParseSingleLane("", ';', result), ());
LaneWays const 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(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<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, ());
}
}
*/

View file

@ -65,128 +65,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.;
@ -260,78 +138,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

@ -1,4 +1,4 @@
#include "routing/turns.hpp"
#include "turns.hpp"
#include "geometry/angles.hpp"
@ -20,29 +20,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"},
@ -165,19 +142,13 @@ 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;
out << "TurnItem "
<< "{ m_index = " << turnItem.m_index
<< ", m_turn = " << DebugPrint(turnItem.m_turn)
<< ", m_lanes = " << ::DebugPrint(turnItem.m_lanes)
<< ", m_lanes = " << lanes::DebugPrint(turnItem.m_lanes)
<< ", m_exitNum = " << turnItem.m_exitNum
<< ", m_pedestrianDir = " << DebugPrint(turnItem.m_pedestrianTurn)
<< " }";
@ -242,146 +213,6 @@ bool IsGoStraightOrSlightTurn(CarDirection t)
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(const SingleLaneInfo & 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, [](char c) { return isspace(c); });
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);
@ -406,14 +237,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,6 +1,7 @@
#pragma once
#include "routing/segment.hpp"
#include "routing/lanes.hpp"
#include "routing_common/num_mwm_id.hpp"
@ -118,41 +119,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,10 +158,10 @@ struct TurnItem
return m_turn == CarDirection::None && m_pedestrianTurn == PedestrianDirection::None;
}
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. */
uint32_t m_exitNum; /*!< Number of exit on roundabout. */
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.
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
* if there is no pedestrian specific direction
@ -229,38 +195,6 @@ 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(const SingleLaneInfo & 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.