From 1cec443b179368132c499bbde73854b27b5ad599 Mon Sep 17 00:00:00 2001 From: Andrew Shkrob Date: Fri, 5 Jan 2024 11:16:59 +0100 Subject: [PATCH] [generator][core][android] Lanes refactoring Signed-off-by: Andrew Shkrob --- .../main/cpp/app/organicmaps/Framework.cpp | 13 +- .../organicmaps/routing/SingleLaneInfo.java | 85 ++++-- base/string_utils.hpp | 26 +- generator/CMakeLists.txt | 2 + generator/osm2meta.cpp | 20 +- generator/osm2meta.hpp | 4 +- generator/turn_lanes_metadata_processor.cpp | 67 +++++ generator/turn_lanes_metadata_processor.hpp | 12 + routing/CMakeLists.txt | 2 + routing/car_directions.cpp | 73 +---- routing/car_directions.hpp | 5 - routing/following_info.hpp | 21 +- routing/lanes.cpp | 249 ++++++++++++++++++ routing/lanes.hpp | 82 ++++++ routing/loaded_path_segment.hpp | 2 +- routing/route.hpp | 2 +- routing/routing_session.cpp | 9 +- routing/routing_tests/CMakeLists.txt | 1 + routing/routing_tests/lanes_tests.cpp | 202 ++++++++++++++ .../routing_tests/turns_generator_test.cpp | 194 -------------- routing/turns.cpp | 181 +------------ routing/turns.hpp | 76 +----- 22 files changed, 722 insertions(+), 606 deletions(-) create mode 100644 generator/turn_lanes_metadata_processor.cpp create mode 100644 generator/turn_lanes_metadata_processor.hpp create mode 100644 routing/lanes.cpp create mode 100644 routing/lanes.hpp create mode 100644 routing/routing_tests/lanes_tests.cpp diff --git a/android/app/src/main/cpp/app/organicmaps/Framework.cpp b/android/app/src/main/cpp/app/organicmaps/Framework.cpp index fe5897546f..1ad049b17f 100644 --- a/android/app/src/main/cpp/app/organicmaps/Framework.cpp +++ b/android/app/src/main/cpp/app/organicmaps/Framework.cpp @@ -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 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(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(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(lanes[j].m_laneWays.data), + static_cast(lanes[j].m_recommendedLaneWays.data))); ASSERT(singleLaneInfo.get(), (jni::DescribeException())); env->SetObjectArrayElement(jLanes, j, singleLaneInfo.get()); } diff --git a/android/app/src/main/java/app/organicmaps/routing/SingleLaneInfo.java b/android/app/src/main/java/app/organicmaps/routing/SingleLaneInfo.java index 4805fe81c5..86239a00bc 100644 --- a/android/app/src/main/java/app/organicmaps/routing/SingleLaneInfo.java +++ b/android/app/src/main/java/app/organicmaps/routing/SingleLaneInfo.java @@ -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 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]); + } } diff --git a/base/string_utils.hpp b/base/string_utils.hpp index d2cf9db10a..2dabb9a51f 100644 --- a/base/string_utils.hpp +++ b/base/string_utils.hpp @@ -319,10 +319,11 @@ public: bool operator()(UniChar c) const; }; -template class SimpleTokenizer : public - TokenizeIterator, false /* KeepEmptyTokens */> +template +class SimpleTokenizer : public + TokenizeIterator, KeepEmptyTokens> { - using BaseT = TokenizeIterator, false /* KeepEmptyTokens */>; + using BaseT = TokenizeIterator, KeepEmptyTokens>; public: SimpleTokenizer(StringT const & str, SimpleDelimiter const & delims) : BaseT(str.begin(), str.end(), delims) @@ -330,10 +331,10 @@ public: } }; -template +template void Tokenize(std::string_view str, char const * delims, TFunctor && f) { - SimpleTokenizer iter(str, delims); + SimpleTokenizer 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 +template std::vector Tokenize(std::string_view str, char const * delims) { std::vector 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(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::max(); diff --git a/generator/CMakeLists.txt b/generator/CMakeLists.txt index a669096cd0..05ff1bf441 100644 --- a/generator/CMakeLists.txt +++ b/generator/CMakeLists.txt @@ -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 diff --git a/generator/osm2meta.cpp b/generator/osm2meta.cpp index 3e930bbc73..6c40ac9bfa 100644 --- a/generator/osm2meta.cpp +++ b/generator/osm2meta.cpp @@ -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; diff --git a/generator/osm2meta.hpp b/generator/osm2meta.hpp index 231c25763b..cfe56aa9f5 100644 --- a/generator/osm2meta.hpp +++ b/generator/osm2meta.hpp @@ -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) ; diff --git a/generator/turn_lanes_metadata_processor.cpp b/generator/turn_lanes_metadata_processor.cpp new file mode 100644 index 0000000000..49fdf5f183 --- /dev/null +++ b/generator/turn_lanes_metadata_processor.cpp @@ -0,0 +1,67 @@ +#include "turn_lanes_metadata_processor.hpp" + +#include "base/string_utils.hpp" + +#include +#include + +namespace +{ +/// @warning The order of these values must be synchronized with @union LaneWays +const std::unordered_map 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 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 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 diff --git a/generator/turn_lanes_metadata_processor.hpp b/generator/turn_lanes_metadata_processor.hpp new file mode 100644 index 0000000000..ed1faa0548 --- /dev/null +++ b/generator/turn_lanes_metadata_processor.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include + +namespace generator +{ +class TurnLanesMetadataProcessor +{ +public: + static std::string ValidateAndFormat(std::string value); +}; +} // namespace generator diff --git a/routing/CMakeLists.txt b/routing/CMakeLists.txt index 5dd2df78d0..c2e9a87362 100644 --- a/routing/CMakeLists.txt +++ b/routing/CMakeLists.txt @@ -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 diff --git a/routing/car_directions.cpp b/routing/car_directions.cpp index aebbd08c06..bd0a44818f 100644 --- a/routing/car_directions.cpp +++ b/routing/car_directions.cpp @@ -6,8 +6,6 @@ #include "geometry/angles.hpp" -#include - namespace routing { using namespace std; @@ -85,7 +83,7 @@ void FixupCarTurns(vector & 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 & 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 -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 & 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 & 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 diff --git a/routing/car_directions.hpp b/routing/car_directions.hpp index 46f29ec8a6..64a3ee8f67 100644 --- a/routing/car_directions.hpp +++ b/routing/car_directions.hpp @@ -24,11 +24,6 @@ protected: virtual void FixupTurns(std::vector & routeSegments); }; -/*! - * \brief Selects lanes which are recommended for an end user. - */ -void SelectRecommendedLanes(std::vector & routeSegments); - void FixupCarTurns(std::vector & routeSegments); /*! diff --git a/routing/following_info.hpp b/routing/following_info.hpp index 04f06905ef..f2469d04ad 100644 --- a/routing/following_info.hpp +++ b/routing/following_info.hpp @@ -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 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(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 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 diff --git a/routing/lanes.cpp b/routing/lanes.cpp new file mode 100644 index 0000000000..15abab61e7 --- /dev/null +++ b/routing/lanes.cpp @@ -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 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 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 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); +} +} +// TODO: Remove before merge + +namespace +{ +using CarDirectionToLaneWays = std::unordered_map; + +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 +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 & 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(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 diff --git a/routing/lanes.hpp b/routing/lanes.hpp new file mode 100644 index 0000000000..ca260ea772 --- /dev/null +++ b/routing/lanes.hpp @@ -0,0 +1,82 @@ +#pragma once + +#include +#include +#include + +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; + +/*! + * \brief Selects lanes which are recommended for an end user. + */ +void SelectRecommendedLanes(std::vector & 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 diff --git a/routing/loaded_path_segment.hpp b/routing/loaded_path_segment.hpp index b14bd1487b..dd9d578877 100644 --- a/routing/loaded_path_segment.hpp +++ b/routing/loaded_path_segment.hpp @@ -23,7 +23,7 @@ namespace routing struct LoadedPathSegment { std::vector m_path; - std::vector 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; diff --git a/routing/route.hpp b/routing/route.hpp index 86520f70ae..dbdbbbc02e 100644 --- a/routing/route.hpp +++ b/routing/route.hpp @@ -111,7 +111,7 @@ public: void SetTurnExits(uint32_t exitNum) { m_turn.m_exitNum = exitNum; } - std::vector & GetTurnLanes() { return m_turn.m_lanes; }; + turns::lanes::LanesInfo & GetTurnLanes() { return m_turn.m_lanes; }; void SetDistancesAndTime(double distFromBeginningMeters, double distFromBeginningMerc, double timeFromBeginningS) { diff --git a/routing/routing_session.cpp b/routing/routing_session.cpp index bbdf9d64f7..07beaa1311 100644 --- a/routing/routing_session.cpp +++ b/routing/routing_session.cpp @@ -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 { diff --git a/routing/routing_tests/CMakeLists.txt b/routing/routing_tests/CMakeLists.txt index 3340b8777b..1c6203d07f 100644 --- a/routing/routing_tests/CMakeLists.txt +++ b/routing/routing_tests/CMakeLists.txt @@ -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 diff --git a/routing/routing_tests/lanes_tests.cpp b/routing/routing_tests/lanes_tests.cpp new file mode 100644 index 0000000000..f6a8e0cc41 --- /dev/null +++ b/routing/routing_tests/lanes_tests.cpp @@ -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 result; + SplitLanes("through|through|through|through;right", '|', result); + vector const expected1 = {"through", "through", "through", "through;right"}; + TEST_EQUAL(result, expected1, ()); + + SplitLanes("adsjkddfasui8747&sxdsdlad8\"\'", '|', result); + TEST_EQUAL(result, vector({"adsjkddfasui8747&sxdsdlad8\"\'"}), ()); + + SplitLanes("|||||||", '|', result); + vector 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 result; + TEST(ParseLanes("through|through|through|through;right", result), ()); + vector 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 const expected2 = { + {LaneWay::Left}, {LaneWay::Left, LaneWay::Through}, {LaneWay::Through}, {LaneWay::Through}}; + TEST_EQUAL(result, expected2, ()); + + TEST(ParseLanes("left|through|through", result), ()); + vector const expected3 = { + {LaneWay::Left}, {LaneWay::Through}, {LaneWay::Through}}; + TEST_EQUAL(result, expected3, ()); + + TEST(ParseLanes("left|le ft| through|through | right", result), ()); + vector 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 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 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 const expected7 = { + {LaneWay::Left}, {LaneWay::Left}, {LaneWay::Through}, {LaneWay::Through}, {LaneWay::Right}}; + TEST_EQUAL(result, expected7, ()); + + TEST(ParseLanes("|||||slight_right", result), ()); + vector 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 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 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, ()); +} +} +*/ \ No newline at end of file diff --git a/routing/routing_tests/turns_generator_test.cpp b/routing/routing_tests/turns_generator_test.cpp index 9573da75c7..145b39f838 100644 --- a/routing/routing_tests/turns_generator_test.cpp +++ b/routing/routing_tests/turns_generator_test.cpp @@ -65,128 +65,6 @@ private: TUnpackedPathSegments m_segments; }; -UNIT_TEST(TestSplitLanes) -{ - vector result; - SplitLanes("through|through|through|through;right", '|', result); - vector const expected1 = {"through", "through", "through", "through;right"}; - TEST_EQUAL(result, expected1, ()); - - SplitLanes("adsjkddfasui8747&sxdsdlad8\"\'", '|', result); - TEST_EQUAL(result, vector({"adsjkddfasui8747&sxdsdlad8\"\'"}), ()); - - SplitLanes("|||||||", '|', result); - vector 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 result; - TEST(ParseLanes("through|through|through|through;right", result), ()); - vector 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 const expected2 = { - {LaneWay::Left}, {LaneWay::Left, LaneWay::Through}, {LaneWay::Through}, {LaneWay::Through}}; - TEST_EQUAL(result, expected2, ()); - - TEST(ParseLanes("left|through|through", result), ()); - vector const expected3 = { - {LaneWay::Left}, {LaneWay::Through}, {LaneWay::Through}}; - TEST_EQUAL(result, expected3, ()); - - TEST(ParseLanes("left|le ft| through|through | right", result), ()); - vector 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 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 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 const expected7 = { - {LaneWay::Left}, {LaneWay::Left}, {LaneWay::Through}, {LaneWay::Through}, {LaneWay::Right}}; - TEST_EQUAL(result, expected7, ()); - - TEST(ParseLanes("|||||slight_right", result), ()); - vector 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 = - {{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 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 diff --git a/routing/turns.cpp b/routing/turns.cpp index bd495c9a28..9bbb18cfba 100644 --- a/routing/turns.cpp +++ b/routing/turns.cpp @@ -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, static_cast(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(LaneWay::Count) + 1, - "Check the size of g_laneWayNames"); - array, static_cast(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 & 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 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 & lanes) -{ - if (lanesString.empty()) - return false; - lanes.clear(); - strings::AsciiToLower(lanesString); - base::EraseIf(lanesString, [](char c) { return isspace(c); }); - - vector 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 const & p) - { - return p.first == l; - }); - - if (it == g_laneWayNames.end()) - { - stringstream out; - out << "unknown LaneWay (" << static_cast(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) { diff --git a/routing/turns.hpp b/routing/turns.hpp index 2ec1590d22..1e9332ac2f 100644 --- a/routing/turns.hpp +++ b/routing/turns.hpp @@ -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 TSingleLane; - -struct SingleLaneInfo -{ - TSingleLane m_lane; - bool m_isRecommended = false; - - SingleLaneInfo() = default; - SingleLaneInfo(std::initializer_list 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 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 & lanes); -void SplitLanes(std::string const & lanesString, char delimiter, std::vector & 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. -- 2.45.3