diff --git a/android/app/src/main/java/app/organicmaps/car/util/RoutingHelpers.java b/android/app/src/main/java/app/organicmaps/car/util/RoutingHelpers.java index 345f22596..fc9a4ff7f 100644 --- a/android/app/src/main/java/app/organicmaps/car/util/RoutingHelpers.java +++ b/android/app/src/main/java/app/organicmaps/car/util/RoutingHelpers.java @@ -31,18 +31,19 @@ public final class RoutingHelpers @NonNull public static LaneDirection createLaneDirection(@NonNull LaneWay laneWay, boolean isRecommended) { - int shape = LaneDirection.SHAPE_UNKNOWN; - shape = switch (laneWay) + @LaneDirection.Shape + final int shape = switch (laneWay) { - case REVERSE -> LaneDirection.SHAPE_U_TURN_LEFT; - case SHARP_LEFT -> LaneDirection.SHAPE_SHARP_LEFT; - case LEFT -> LaneDirection.SHAPE_NORMAL_LEFT; - case SLIGHT_LEFT, MERGE_TO_LEFT -> LaneDirection.SHAPE_SLIGHT_LEFT; - case SLIGHT_RIGHT, MERGE_TO_RIGHT -> LaneDirection.SHAPE_SLIGHT_RIGHT; - case THROUGH -> LaneDirection.SHAPE_STRAIGHT; - case RIGHT -> LaneDirection.SHAPE_NORMAL_RIGHT; - case SHARP_RIGHT -> LaneDirection.SHAPE_SHARP_RIGHT; - default -> shape; + case ReverseLeft -> LaneDirection.SHAPE_U_TURN_LEFT; + case SharpLeft -> LaneDirection.SHAPE_SHARP_LEFT; + case Left -> LaneDirection.SHAPE_NORMAL_LEFT; + case MergeToLeft, SlightLeft -> LaneDirection.SHAPE_SLIGHT_LEFT; + case Through -> LaneDirection.SHAPE_STRAIGHT; + case SlightRight, MergeToRight -> LaneDirection.SHAPE_SLIGHT_RIGHT; + case Right -> LaneDirection.SHAPE_NORMAL_RIGHT; + case SharpRight -> LaneDirection.SHAPE_SHARP_RIGHT; + case ReverseRight -> LaneDirection.SHAPE_U_TURN_RIGHT; + default -> LaneDirection.SHAPE_UNKNOWN; }; return LaneDirection.create(shape, isRecommended); diff --git a/android/app/src/main/java/app/organicmaps/car/util/RoutingUtils.java b/android/app/src/main/java/app/organicmaps/car/util/RoutingUtils.java index a677c5d4b..cce0d4b84 100644 --- a/android/app/src/main/java/app/organicmaps/car/util/RoutingUtils.java +++ b/android/app/src/main/java/app/organicmaps/car/util/RoutingUtils.java @@ -8,14 +8,15 @@ import androidx.car.app.CarContext; import androidx.car.app.model.CarIcon; import androidx.car.app.navigation.model.Destination; import androidx.car.app.navigation.model.Lane; +import androidx.car.app.navigation.model.LaneDirection; import androidx.car.app.navigation.model.Step; import androidx.car.app.navigation.model.TravelEstimate; import androidx.car.app.navigation.model.Trip; import androidx.core.graphics.drawable.IconCompat; import app.organicmaps.sdk.bookmarks.data.MapObject; +import app.organicmaps.sdk.routing.LaneInfo; import app.organicmaps.sdk.routing.LaneWay; import app.organicmaps.sdk.routing.RoutingInfo; -import app.organicmaps.sdk.routing.SingleLaneInfo; import app.organicmaps.sdk.util.Distance; import app.organicmaps.util.Graphics; import app.organicmaps.widget.LanesDrawable; @@ -69,11 +70,12 @@ public final class RoutingUtils builder.setManeuver(RoutingHelpers.createManeuver(context, info.carDirection, info.exitNum)); if (info.lanes != null) { - for (final SingleLaneInfo laneInfo : info.lanes) + for (final LaneInfo laneInfo : info.lanes) { final Lane.Builder laneBuilder = new Lane.Builder(); - for (final LaneWay laneWay : laneInfo.mLane) - laneBuilder.addDirection(RoutingHelpers.createLaneDirection(laneWay, laneInfo.mIsActive)); + for (final LaneWay laneWay : laneInfo.mLaneWays) + laneBuilder.addDirection( + RoutingHelpers.createLaneDirection(laneWay, /* isRecommended */ laneWay == laneInfo.mActiveLaneWay)); builder.addLane(laneBuilder.build()); } final LanesDrawable lanesDrawable = new LanesDrawable(context, info.lanes); diff --git a/android/app/src/main/java/app/organicmaps/widget/LanesDrawable.java b/android/app/src/main/java/app/organicmaps/widget/LanesDrawable.java index 0d3df577b..7ee4f9056 100644 --- a/android/app/src/main/java/app/organicmaps/widget/LanesDrawable.java +++ b/android/app/src/main/java/app/organicmaps/widget/LanesDrawable.java @@ -8,12 +8,14 @@ import android.graphics.Rect; import android.graphics.drawable.Drawable; import androidx.annotation.ColorInt; import androidx.annotation.ColorRes; +import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.content.res.AppCompatResources; import androidx.core.content.ContextCompat; import app.organicmaps.R; -import app.organicmaps.sdk.routing.SingleLaneInfo; +import app.organicmaps.sdk.routing.LaneInfo; +import app.organicmaps.sdk.routing.LaneWay; import java.util.Objects; public class LanesDrawable extends Drawable @@ -41,16 +43,19 @@ public class LanesDrawable extends Drawable { private final Drawable mDrawable; - private LaneDrawable(@NonNull final Context context, @NonNull SingleLaneInfo laneInfo, int horizontalOffset, + private LaneDrawable(@NonNull final Context context, @NonNull LaneInfo laneInfo, int horizontalOffset, TintColorInfo colorInfo) { - mDrawable = Objects.requireNonNull(AppCompatResources.getDrawable(context, laneInfo.mLane[0].mTurnRes)); + final boolean isActive = laneInfo.mActiveLaneWay != LaneWay.None; + @DrawableRes + final int turnRes = isActive ? laneInfo.mActiveLaneWay.mTurnRes : laneInfo.mLaneWays[0].mTurnRes; + mDrawable = Objects.requireNonNull(AppCompatResources.getDrawable(context, turnRes)); final int width = mDrawable.getIntrinsicWidth(); final int height = mDrawable.getIntrinsicHeight(); mDrawable.setBounds(horizontalOffset, 0, horizontalOffset + width, height); - mDrawable.setTint(laneInfo.mIsActive ? colorInfo.mActiveLaneTint : colorInfo.mInactiveLaneTint); + mDrawable.setTint(isActive ? colorInfo.mActiveLaneTint : colorInfo.mInactiveLaneTint); } private void draw(@NonNull final Canvas canvas) @@ -65,14 +70,14 @@ public class LanesDrawable extends Drawable private int mWidth; private int mHeight; - public LanesDrawable(@NonNull final Context context, @NonNull SingleLaneInfo[] lanes) + public LanesDrawable(@NonNull final Context context, @NonNull LaneInfo[] lanes) { final TintColorInfo tintColorInfo = new TintColorInfo(ContextCompat.getColor(context, ACTIVE_LANE_TINT_RES), ContextCompat.getColor(context, INACTIVE_LANE_TINT_RES)); mLanes = createLaneDrawables(context, lanes, tintColorInfo); } - public LanesDrawable(@NonNull final Context context, @NonNull SingleLaneInfo[] lanes, @ColorInt int activeLaneTint, + public LanesDrawable(@NonNull final Context context, @NonNull LaneInfo[] lanes, @ColorInt int activeLaneTint, @ColorInt int inactiveLaneTint) { final TintColorInfo tintColorInfo = new TintColorInfo(activeLaneTint, inactiveLaneTint); @@ -143,7 +148,7 @@ public class LanesDrawable extends Drawable } @NonNull - private LaneDrawable[] createLaneDrawables(@NonNull Context context, @NonNull SingleLaneInfo[] lanes, + private LaneDrawable[] createLaneDrawables(@NonNull Context context, @NonNull LaneInfo[] lanes, @NonNull TintColorInfo tintColorInfo) { assert lanes.length > 0; diff --git a/android/app/src/main/java/app/organicmaps/widget/LanesView.java b/android/app/src/main/java/app/organicmaps/widget/LanesView.java index 6198fd268..0342d4593 100644 --- a/android/app/src/main/java/app/organicmaps/widget/LanesView.java +++ b/android/app/src/main/java/app/organicmaps/widget/LanesView.java @@ -16,7 +16,8 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StyleableRes; import app.organicmaps.R; -import app.organicmaps.sdk.routing.SingleLaneInfo; +import app.organicmaps.sdk.routing.LaneInfo; +import app.organicmaps.sdk.routing.LaneWay; public class LanesView extends View { @@ -77,7 +78,7 @@ public class LanesView extends View mBackgroundPaint.setColor(backgroundColor); } - public void setLanes(@Nullable SingleLaneInfo[] lanes) + public void setLanes(@Nullable LaneInfo[] lanes) { if (lanes == null || lanes.length == 0) mLanesDrawable = null; @@ -157,16 +158,16 @@ public class LanesView extends View private void createLanesForEditMode(int lanesCount) { - final SingleLaneInfo[] lanes = new SingleLaneInfo[lanesCount]; - lanes[0] = new SingleLaneInfo(new byte[] {1}, false); + final LaneInfo[] lanes = new LaneInfo[lanesCount]; + lanes[0] = new LaneInfo(new LaneWay[] {LaneWay.ReverseLeft, LaneWay.Left}, LaneWay.None); if (lanes.length > 1) - lanes[1] = new SingleLaneInfo(new byte[] {3}, false); + lanes[1] = new LaneInfo(new LaneWay[] {LaneWay.SharpLeft, LaneWay.Left, LaneWay.Through}, LaneWay.None); for (int i = 2; i <= lanes.length - 1; i++) - lanes[i] = new SingleLaneInfo(new byte[] {0}, true); + lanes[i] = new LaneInfo(new LaneWay[] {LaneWay.Through, LaneWay.Left}, LaneWay.Through); if (lanes.length > 2) - lanes[lanes.length - 2] = new SingleLaneInfo(new byte[] {8}, false); + lanes[lanes.length - 2] = new LaneInfo(new LaneWay[] {LaneWay.SlightRight, LaneWay.Right}, LaneWay.SlightRight); if (lanes.length > 3) - lanes[lanes.length - 1] = new SingleLaneInfo(new byte[] {9}, false); + lanes[lanes.length - 1] = new LaneInfo(new LaneWay[] {LaneWay.ReverseRight}, LaneWay.None); setLanes(lanes); } diff --git a/android/sdk/src/main/cpp/CMakeLists.txt b/android/sdk/src/main/cpp/CMakeLists.txt index d579fca8a..426229441 100644 --- a/android/sdk/src/main/cpp/CMakeLists.txt +++ b/android/sdk/src/main/cpp/CMakeLists.txt @@ -26,13 +26,14 @@ set(SRC app/organicmaps/sdk/search/SearchEngine.cpp app/organicmaps/sdk/search/SearchRecents.cpp app/organicmaps/sdk/routing/JunctionInfo.hpp + app/organicmaps/sdk/routing/LaneInfo.cpp + app/organicmaps/sdk/routing/LaneInfo.hpp app/organicmaps/sdk/routing/RouteMarkData.hpp app/organicmaps/sdk/routing/RouteMarkType.hpp app/organicmaps/sdk/routing/RoutePointInfo.hpp app/organicmaps/sdk/routing/RouteRecommendationType.hpp app/organicmaps/sdk/routing/RoutingInfo.hpp app/organicmaps/sdk/routing/RoutingOptions.cpp - app/organicmaps/sdk/routing/SingleLaneInfo.hpp app/organicmaps/sdk/routing/TransitRouteInfo.hpp app/organicmaps/sdk/routing/TransitStepInfo.hpp app/organicmaps/sdk/ChoosePositionMode.cpp diff --git a/android/sdk/src/main/cpp/app/organicmaps/sdk/routing/LaneInfo.cpp b/android/sdk/src/main/cpp/app/organicmaps/sdk/routing/LaneInfo.cpp new file mode 100644 index 000000000..557338e34 --- /dev/null +++ b/android/sdk/src/main/cpp/app/organicmaps/sdk/routing/LaneInfo.cpp @@ -0,0 +1,50 @@ +#include "LaneInfo.hpp" + +#include "app/organicmaps/sdk/core/jni_helper.hpp" + +#include + +namespace +{ +jobject ToJavaLaneWay(JNIEnv * env, routing::turns::lanes::LaneWay const & laneWay) +{ + static jclass const laneWayClass = jni::GetGlobalClassRef(env, "app/organicmaps/sdk/routing/LaneWay"); + jfieldID fieldID = + env->GetStaticFieldID(laneWayClass, DebugPrint(laneWay).c_str(), "Lapp/organicmaps/sdk/routing/LaneWay;"); + return env->GetStaticObjectField(laneWayClass, fieldID); +} +} // namespace + +jobjectArray CreateLanesInfo(JNIEnv * env, routing::turns::lanes::LanesInfo const & lanes) +{ + if (lanes.empty()) + return nullptr; + + static jclass const laneWayClass = jni::GetGlobalClassRef(env, "app/organicmaps/sdk/routing/LaneWay"); + static jclass const laneInfoClass = jni::GetGlobalClassRef(env, "app/organicmaps/sdk/routing/LaneInfo"); + auto const lanesSize = static_cast(lanes.size()); + jobjectArray jLanes = env->NewObjectArray(lanesSize, laneInfoClass, nullptr); + ASSERT(jLanes, (jni::DescribeException())); + // Java signature : LaneInfo(LaneWay[] laneWays, LaneWay activeLane) + static jmethodID const ctorLaneInfoID = jni::GetConstructorID( + env, laneInfoClass, "([Lapp/organicmaps/sdk/routing/LaneWay;Lapp/organicmaps/sdk/routing/LaneWay;)V"); + + for (jsize j = 0; j < lanesSize; ++j) + { + auto const laneWays = lanes[j].laneWays.GetActiveLaneWays(); + auto const laneWaysSize = static_cast(laneWays.size()); + jni::TScopedLocalObjectArrayRef jLaneWays(env, env->NewObjectArray(laneWaysSize, laneWayClass, nullptr)); + ASSERT(jLanes, (jni::DescribeException())); + for (jsize i = 0; i < laneWaysSize; ++i) + { + jni::TScopedLocalRef jLaneWay(env, ToJavaLaneWay(env, laneWays[i])); + env->SetObjectArrayElement(jLaneWays.get(), i, jLaneWay.get()); + } + jni::TScopedLocalRef jLaneInfo(env, env->NewObject(laneInfoClass, ctorLaneInfoID, jLaneWays.get(), + ToJavaLaneWay(env, lanes[j].recommendedWay))); + ASSERT(jLaneInfo.get(), (jni::DescribeException())); + env->SetObjectArrayElement(jLanes, j, jLaneInfo.get()); + } + + return jLanes; +} diff --git a/android/sdk/src/main/cpp/app/organicmaps/sdk/routing/LaneInfo.hpp b/android/sdk/src/main/cpp/app/organicmaps/sdk/routing/LaneInfo.hpp new file mode 100644 index 000000000..0c8e4c4e3 --- /dev/null +++ b/android/sdk/src/main/cpp/app/organicmaps/sdk/routing/LaneInfo.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include "routing/lanes/lane_info.hpp" + +jobjectArray CreateLanesInfo(JNIEnv * env, routing::turns::lanes::LanesInfo const & lanes); diff --git a/android/sdk/src/main/cpp/app/organicmaps/sdk/routing/RoutingInfo.hpp b/android/sdk/src/main/cpp/app/organicmaps/sdk/routing/RoutingInfo.hpp index 3dc84846f..0a1188e15 100644 --- a/android/sdk/src/main/cpp/app/organicmaps/sdk/routing/RoutingInfo.hpp +++ b/android/sdk/src/main/cpp/app/organicmaps/sdk/routing/RoutingInfo.hpp @@ -1,7 +1,7 @@ #pragma once #include "app/organicmaps/sdk/core/jni_helper.hpp" -#include "app/organicmaps/sdk/routing/SingleLaneInfo.hpp" +#include "app/organicmaps/sdk/routing/LaneInfo.hpp" #include "app/organicmaps/sdk/util/Distance.hpp" #include "map/routing_manager.hpp" @@ -13,12 +13,13 @@ jobject CreateRoutingInfo(JNIEnv * env, routing::FollowingInfo const & info, Rou // String currentStreet, String nextStreet, String nextNextStreet, // double completionPercent, int vehicleTurnOrdinal, int // vehicleNextTurnOrdinal, int pedestrianTurnOrdinal, int exitNum, - // int totalTime, SingleLaneInfo[] lanes) + // int totalTime, LaneInfo[] lanes, double speedLimitMps, boolean speedLimitExceeded, + // boolean shouldPlayWarningSignal) static jmethodID const ctorRouteInfoID = jni::GetConstructorID(env, klass, "(Lapp/organicmaps/sdk/util/Distance;Lapp/organicmaps/sdk/util/Distance;" "Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;DIIIII" - "[Lapp/organicmaps/sdk/routing/SingleLaneInfo;DZZ)V"); + "[Lapp/organicmaps/sdk/routing/LaneInfo;DZZ)V"); jobjectArray jLanes = CreateLanesInfo(env, info.m_lanes); diff --git a/android/sdk/src/main/cpp/app/organicmaps/sdk/routing/SingleLaneInfo.hpp b/android/sdk/src/main/cpp/app/organicmaps/sdk/routing/SingleLaneInfo.hpp deleted file mode 100644 index 2852d2f3d..000000000 --- a/android/sdk/src/main/cpp/app/organicmaps/sdk/routing/SingleLaneInfo.hpp +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#include "app/organicmaps/sdk/core/jni_helper.hpp" - -#include "routing/following_info.hpp" - -#include - -jobjectArray CreateLanesInfo(JNIEnv * env, std::vector const & lanes) -{ - if (lanes.empty()) - return nullptr; - - static jclass const laneClass = jni::GetGlobalClassRef(env, "app/organicmaps/sdk/routing/SingleLaneInfo"); - auto const lanesSize = static_cast(lanes.size()); - jobjectArray jLanes = env->NewObjectArray(lanesSize, laneClass, nullptr); - ASSERT(jLanes, (jni::DescribeException())); - static jmethodID const ctorSingleLaneInfoID = jni::GetConstructorID(env, laneClass, "([BZ)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)); - ASSERT(singleLaneInfo.get(), (jni::DescribeException())); - env->SetObjectArrayElement(jLanes, j, singleLaneInfo.get()); - } - - return jLanes; -} diff --git a/android/sdk/src/main/java/app/organicmaps/sdk/routing/LaneInfo.java b/android/sdk/src/main/java/app/organicmaps/sdk/routing/LaneInfo.java new file mode 100644 index 000000000..ca0bae20d --- /dev/null +++ b/android/sdk/src/main/java/app/organicmaps/sdk/routing/LaneInfo.java @@ -0,0 +1,27 @@ +package app.organicmaps.sdk.routing; + +import androidx.annotation.NonNull; + +public final class LaneInfo +{ + public final LaneWay[] mLaneWays; + public final LaneWay mActiveLaneWay; + + public LaneInfo(@NonNull LaneWay[] laneWays, LaneWay activeLane) + { + mLaneWays = laneWays; + mActiveLaneWay = activeLane; + } + + @NonNull + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("LaneInfo{activeLaneWay=").append(mActiveLaneWay.toString()).append(", laneWays=["); + for (LaneWay i : mLaneWays) + sb.append(" ").append(i); + sb.append("]}"); + return sb.toString(); + } +} diff --git a/android/sdk/src/main/java/app/organicmaps/sdk/routing/LaneWay.java b/android/sdk/src/main/java/app/organicmaps/sdk/routing/LaneWay.java index 3fab6e0d6..179d79782 100644 --- a/android/sdk/src/main/java/app/organicmaps/sdk/routing/LaneWay.java +++ b/android/sdk/src/main/java/app/organicmaps/sdk/routing/LaneWay.java @@ -3,25 +3,20 @@ package app.organicmaps.sdk.routing; import androidx.annotation.DrawableRes; import app.organicmaps.sdk.R; -/** - * IMPORTANT : Order of enum values MUST BE the same - * with native LaneWay enum (see routing/turns.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), - 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); + None(R.drawable.ic_turn_straight), + ReverseLeft(R.drawable.ic_turn_uleft), + SharpLeft(R.drawable.ic_turn_left_sharp), + Left(R.drawable.ic_turn_left), + MergeToLeft(R.drawable.ic_turn_left_slight), + SlightLeft(R.drawable.ic_turn_left_slight), + Through(R.drawable.ic_turn_straight), + SlightRight(R.drawable.ic_turn_right_slight), + MergeToRight(R.drawable.ic_turn_right_slight), + Right(R.drawable.ic_turn_right), + SharpRight(R.drawable.ic_turn_right_sharp), + ReverseRight(R.drawable.ic_turn_uright); public final int mTurnRes; diff --git a/android/sdk/src/main/java/app/organicmaps/sdk/routing/RoutingInfo.java b/android/sdk/src/main/java/app/organicmaps/sdk/routing/RoutingInfo.java index b025675d3..4aa83964a 100644 --- a/android/sdk/src/main/java/app/organicmaps/sdk/routing/RoutingInfo.java +++ b/android/sdk/src/main/java/app/organicmaps/sdk/routing/RoutingInfo.java @@ -1,6 +1,7 @@ package app.organicmaps.sdk.routing; import androidx.annotation.Keep; +import androidx.annotation.Nullable; import app.organicmaps.sdk.util.Distance; // Called from JNI. @@ -25,7 +26,8 @@ public final class RoutingInfo public final CarDirection carDirection; public final CarDirection nextCarDirection; public final int exitNum; - public final SingleLaneInfo[] lanes; + @Nullable + public final LaneInfo[] lanes; // For pedestrian routing. public final PedestrianTurnDirection pedestrianTurnDirection; // Current speed limit in meters per second. @@ -37,7 +39,7 @@ public final class RoutingInfo private RoutingInfo(Distance distToTarget, Distance distToTurn, String currentStreet, String nextStreet, String nextNextStreet, double completionPercent, int vehicleTurnOrdinal, int vehicleNextTurnOrdinal, int pedestrianTurnOrdinal, int exitNum, int totalTime, - SingleLaneInfo[] lanes, double speedLimitMps, boolean speedLimitExceeded, + @Nullable LaneInfo[] lanes, double speedLimitMps, boolean speedLimitExceeded, boolean shouldPlayWarningSignal) { this.distToTarget = distToTarget; diff --git a/android/sdk/src/main/java/app/organicmaps/sdk/routing/SingleLaneInfo.java b/android/sdk/src/main/java/app/organicmaps/sdk/routing/SingleLaneInfo.java deleted file mode 100644 index ef53087f5..000000000 --- a/android/sdk/src/main/java/app/organicmaps/sdk/routing/SingleLaneInfo.java +++ /dev/null @@ -1,31 +0,0 @@ -package app.organicmaps.sdk.routing; - -import androidx.annotation.NonNull; - -public final class SingleLaneInfo -{ - public LaneWay[] mLane; - public boolean mIsActive; - - public SingleLaneInfo(@NonNull byte[] laneOrdinals, boolean isActive) - { - 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; - } - - @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"); - for (LaneWay i : mLane) - sb.append(" ").append(i.ordinal()); - return sb.toString(); - } -} diff --git a/libs/routing/CMakeLists.txt b/libs/routing/CMakeLists.txt index 8194471db..aa5fabbfb 100644 --- a/libs/routing/CMakeLists.txt +++ b/libs/routing/CMakeLists.txt @@ -16,6 +16,14 @@ set(SRC base/routing_result.hpp base/small_list.hpp base/small_list.cpp + lanes/lane_info.cpp + lanes/lane_info.hpp + lanes/lane_way.cpp + lanes/lane_way.hpp + lanes/lanes_parser.cpp + lanes/lanes_parser.hpp + lanes/lanes_recommendation.cpp + lanes/lanes_recommendation.hpp car_directions.cpp car_directions.hpp checkpoint_predictor.cpp diff --git a/libs/routing/car_directions.cpp b/libs/routing/car_directions.cpp index d8267a836..2642ddc8c 100644 --- a/libs/routing/car_directions.cpp +++ b/libs/routing/car_directions.cpp @@ -1,5 +1,6 @@ #include "routing/car_directions.hpp" +#include "routing/lanes/lanes_recommendation.hpp" #include "routing/turns.hpp" #include "routing/turns_generator.hpp" #include "routing/turns_generator_utils.hpp" @@ -83,7 +84,7 @@ void FixupCarTurns(vector & routeSegments) routeSegments[idx - 1].ClearTurn(); } } - SelectRecommendedLanes(routeSegments); + turns::lanes::SelectRecommendedLanes(routeSegments); } void GetTurnDirectionBasic(IRoutingResult const & result, size_t const outgoingSegmentIndex, @@ -596,73 +597,4 @@ size_t CheckUTurnOnRoute(IRoutingResult const & result, size_t const outgoingSeg return 0; } - -bool FixupLaneSet(CarDirection turn, vector & 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) -{ - It const firstUnrestricted = find_if(lanesBegin, lanesEnd, IsLaneUnrestricted); - if (firstUnrestricted == lanesEnd) - return false; - - firstUnrestricted->m_isRecommended = true; - firstUnrestricted->m_lane.insert(firstUnrestricted->m_lane.begin(), direction); - return true; -} - -bool SelectUnrestrictedLane(CarDirection turn, vector & 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/libs/routing/car_directions.hpp b/libs/routing/car_directions.hpp index 444a7b3a3..f743076d4 100644 --- a/libs/routing/car_directions.hpp +++ b/libs/routing/car_directions.hpp @@ -5,7 +5,6 @@ #include "routing/route.hpp" #include "routing_common/num_mwm_id.hpp" -#include #include #include @@ -24,11 +23,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/libs/routing/directions_engine.cpp b/libs/routing/directions_engine.cpp index 0a708bc1c..95555632f 100644 --- a/libs/routing/directions_engine.cpp +++ b/libs/routing/directions_engine.cpp @@ -2,6 +2,7 @@ #include "routing/data_source.hpp" #include "routing/fake_feature_ids.hpp" +#include "routing/lanes/lanes_parser.hpp" #include "routing/routing_helpers.hpp" #include "routing/turns.hpp" @@ -36,7 +37,7 @@ feature::Metadata::EType GetLanesMetadataTag(FeatureType & ft, bool isForward) void LoadLanes(LoadedPathSegment & pathSegment, FeatureType & ft, bool isForward) { auto tag = GetLanesMetadataTag(ft, isForward); - ParseLanes(std::string(ft.GetMetadata(tag)), pathSegment.m_lanes); + pathSegment.m_lanes = lanes::ParseLanes(ft.GetMetadata(tag)); } } // namespace diff --git a/libs/routing/following_info.hpp b/libs/routing/following_info.hpp index 56db759f6..44994ec21 100644 --- a/libs/routing/following_info.hpp +++ b/libs/routing/following_info.hpp @@ -1,11 +1,9 @@ #pragma once -#include "geometry/latlon.hpp" - #include "platform/distance.hpp" +#include "routing/lanes/lane_info.hpp" #include "routing/turns.hpp" -#include "routing/turns_sound_settings.hpp" #include #include @@ -26,23 +24,6 @@ public: , m_pedestrianTurn(turns::PedestrianDirection::None) {} - // SingleLaneInfoClient is used for passing information about a lane to client platforms such as - // Android, iOS and so on. - struct SingleLaneInfoClient - { - std::vector 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. @@ -57,8 +38,8 @@ public: uint32_t m_exitNum; //@} int m_time; - // m_lanes contains lane information on the edge before the turn. - std::vector m_lanes; + /// Contains lane information on the edge before the turn. + turns::lanes::LanesInfo m_lanes; // m_turnNotifications contains information about the next turn notifications. // If there is nothing to pronounce m_turnNotifications is empty. // If there is something to pronounce the size of m_turnNotifications may be one or even more diff --git a/libs/routing/lanes/lane_info.cpp b/libs/routing/lanes/lane_info.cpp new file mode 100644 index 000000000..d3232a567 --- /dev/null +++ b/libs/routing/lanes/lane_info.cpp @@ -0,0 +1,24 @@ +#include "lane_info.hpp" + +#include + +namespace routing::turns::lanes +{ +std::string DebugPrint(LaneInfo const & laneInfo) +{ + std::stringstream out; + out << "LaneInfo{" << DebugPrint(laneInfo.laneWays) << ", recommendedWay: " << DebugPrint(laneInfo.recommendedWay) + << "}"; + return out.str(); +} + +std::string DebugPrint(LanesInfo const & lanesInfo) +{ + std::stringstream out; + out << "LanesInfo["; + for (auto const & laneInfo : lanesInfo) + out << DebugPrint(laneInfo) << ", "; + out << "]"; + return out.str(); +} +} // namespace routing::turns::lanes diff --git a/libs/routing/lanes/lane_info.hpp b/libs/routing/lanes/lane_info.hpp new file mode 100644 index 000000000..afcc6dee7 --- /dev/null +++ b/libs/routing/lanes/lane_info.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include "routing/lanes/lane_way.hpp" + +#include + +namespace routing::turns::lanes +{ +struct LaneInfo +{ + LaneWays laneWays; + LaneWay recommendedWay = LaneWay::None; + + bool operator==(LaneInfo const & rhs) const + { + return laneWays == rhs.laneWays && recommendedWay == rhs.recommendedWay; + } +}; +using LanesInfo = std::vector; + +std::string DebugPrint(LaneInfo const & laneInfo); +std::string DebugPrint(LanesInfo const & lanesInfo); +} // namespace routing::turns::lanes diff --git a/libs/routing/lanes/lane_way.cpp b/libs/routing/lanes/lane_way.cpp new file mode 100644 index 000000000..ad4aa4a66 --- /dev/null +++ b/libs/routing/lanes/lane_way.cpp @@ -0,0 +1,50 @@ +#include "lane_way.hpp" + +#include "base/assert.hpp" + +namespace routing::turns::lanes +{ +std::string DebugPrint(LaneWay const laneWay) +{ + using enum LaneWay; + switch (laneWay) + { + case None: return "None"; + case ReverseLeft: return "ReverseLeft"; + case SharpLeft: return "SharpLeft"; + case Left: return "Left"; + case MergeToLeft: return "MergeToLeft"; + case SlightLeft: return "SlightLeft"; + case Through: return "Through"; + case SlightRight: return "SlightRight"; + case MergeToRight: return "MergeToRight"; + case Right: return "Right"; + case SharpRight: return "SharpRight"; + case ReverseRight: return "ReverseRight"; + case Count: return "Count"; + default: + ASSERT_FAIL("Unsupported value: " + std::to_string(static_cast(laneWay))); + return "Unsupported"; + } +} + +std::string DebugPrint(LaneWays const & laneWays) +{ + std::stringstream out; + out << "LaneWays: ["; + std::uint8_t const waysCount = laneWays.m_laneWays.count(); + std::uint8_t waysPrinted = 0; + for (std::size_t i = 0; i < laneWays.m_laneWays.size(); ++i) + { + if (laneWays.m_laneWays.test(i)) + { + out << DebugPrint(static_cast(i)); + if (waysPrinted < waysCount - 1) + out << ", "; + waysPrinted++; + } + } + out << "]"; + return out.str(); +} +} // namespace routing::turns::lanes diff --git a/libs/routing/lanes/lane_way.hpp b/libs/routing/lanes/lane_way.hpp new file mode 100644 index 000000000..8ff779136 --- /dev/null +++ b/libs/routing/lanes/lane_way.hpp @@ -0,0 +1,84 @@ +#pragma once + +#include "base/assert.hpp" + +#include +#include +#include + +namespace routing::turns::lanes +{ +enum class LaneWay : std::uint8_t +{ + None = 0, + ReverseLeft, + SharpLeft, + Left, + MergeToLeft, + SlightLeft, + Through, + SlightRight, + MergeToRight, + Right, + SharpRight, + ReverseRight, + + Count +}; + +class LaneWays +{ + using LaneWaysT = std::bitset(LaneWay::Count)>; + + friend std::string DebugPrint(LaneWays const & laneWays); + +public: + constexpr LaneWays() = default; + constexpr LaneWays(std::initializer_list const laneWays) + { + for (auto const & laneWay : laneWays) + Add(laneWay); + } + + constexpr bool operator==(LaneWays const & rhs) const { return m_laneWays == rhs.m_laneWays; } + + constexpr void Add(LaneWay laneWay) + { + ASSERT_LESS(laneWay, LaneWay::Count, ()); + m_laneWays.set(static_cast(laneWay)); + } + + constexpr void Remove(LaneWay laneWay) + { + ASSERT_LESS(laneWay, LaneWay::Count, ()); + m_laneWays.reset(static_cast(laneWay)); + } + + constexpr bool Contains(LaneWay laneWay) const + { + ASSERT_LESS(laneWay, LaneWay::Count, ()); + return m_laneWays.test(static_cast(laneWay)); + } + + /// An unrestricted lane is a lane that has no restrictions, i.e., it contains no lane ways. + constexpr bool IsUnrestricted() const + { + return m_laneWays.none() || (m_laneWays.count() == 1 && Contains(LaneWay::None)); + } + + [[nodiscard]] std::vector GetActiveLaneWays() const + { + std::vector result; + for (std::size_t i = 0; i < m_laneWays.size(); ++i) + if (m_laneWays.test(i)) + result.emplace_back(static_cast(i)); + return result; + } + +private: + LaneWaysT m_laneWays; +}; + +std::string DebugPrint(LaneWay laneWay); +std::string DebugPrint(LaneWays const & laneWays); +} // namespace routing::turns::lanes diff --git a/libs/routing/lanes/lanes_parser.cpp b/libs/routing/lanes/lanes_parser.cpp new file mode 100644 index 000000000..682663eb5 --- /dev/null +++ b/libs/routing/lanes/lanes_parser.cpp @@ -0,0 +1,84 @@ +#include "lanes_parser.hpp" + +#include +#include + +namespace routing::turns::lanes +{ +namespace +{ +std::uint8_t constexpr kLaneWayNamesCount = static_cast(LaneWay::Count) + 4; + +/** + * The order is important. Starting with the most frequent tokens according to + * taginfo.openstreetmap.org we minimize the number of the comparisons in ParseSingleLane(). + * + * A `none` lane can be represented either as "none" or as "". That means both "none" and "" + * should be considered names, even though they refer to the same thing. As a result, + * `LaneWay::None` appears twice in this array, which is one longer than the number of + * enum values. + */ +std::array, kLaneWayNamesCount> constexpr g_laneWayNames{{ + {LaneWay::None, ""}, + {LaneWay::Through, "through"}, + {LaneWay::Left, "left"}, + {LaneWay::Right, "right"}, + {LaneWay::None, "none"}, + {LaneWay::SharpLeft, "sharp_left"}, + {LaneWay::SlightLeft, "slight_left"}, + {LaneWay::MergeToRight, "merge_to_right"}, + {LaneWay::MergeToLeft, "merge_to_left"}, + {LaneWay::SlightRight, "slight_right"}, + {LaneWay::SharpRight, "sharp_right"}, + {LaneWay::ReverseLeft, "reverse"}, + {LaneWay::Right, + "next_right"}, // "next_right" means "turn right, not in the first intersection but the one after that". + {LaneWay::Through, "slide_left"}, // "slide_left" means "move a bit left within the lane". + {LaneWay::Through, "slide_right"} // "slide_right" means "move a bit right within the lane". +}}; + +bool ParseSingleLane(auto && laneWayRange, LaneWay & laneWay) +{ + auto const it = std::ranges::find_if( + g_laneWayNames, [&laneWayRange](auto const & pair) { return std::ranges::equal(laneWayRange, pair.second); }); + if (it != g_laneWayNames.end()) + { + laneWay = it->first; + return true; + } + return false; +} + +} // namespace + +LanesInfo ParseLanes(std::string_view lanesString) +{ + if (lanesString.empty()) + return {}; + + LanesInfo lanes; + for (auto && laneInfo : lanesString | std::views::split('|')) + { + LaneInfo lane; + if (std::ranges::empty(laneInfo)) + lane.laneWays.Add(LaneWay::None); + else + { + for (auto && laneWay : laneInfo | std::views::split(';')) + { + auto way = LaneWay::None; + auto && laneWayProcessed = laneWay | std::views::filter([](char const c) { return !std::isspace(c); }) | + std::views::transform([](char const c) { return std::tolower(c); }); + if (!ParseSingleLane(laneWayProcessed, way)) + return {}; + lane.laneWays.Add(way); + if (way == LaneWay::ReverseLeft) + lane.laneWays.Add(LaneWay::ReverseRight); + } + } + + lanes.push_back(lane); + } + return lanes; +} +} // namespace routing::turns::lanes diff --git a/libs/routing/lanes/lanes_parser.hpp b/libs/routing/lanes/lanes_parser.hpp new file mode 100644 index 000000000..d7f632e96 --- /dev/null +++ b/libs/routing/lanes/lanes_parser.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "routing/lanes/lane_info.hpp" + +#include + +namespace routing::turns::lanes +{ +/** + * Parse lane information which comes from lanesString + * @param lanesString lane information. Example through|through|through|through;right + * @return LanesInfo. @see LanesInfo + * @note if lanesString is empty, returns empty LanesInfo. + */ +LanesInfo ParseLanes(std::string_view lanesString); +} // namespace routing::turns::lanes diff --git a/libs/routing/lanes/lanes_recommendation.cpp b/libs/routing/lanes/lanes_recommendation.cpp new file mode 100644 index 000000000..25bc7748e --- /dev/null +++ b/libs/routing/lanes/lanes_recommendation.cpp @@ -0,0 +1,129 @@ +#include "lanes_recommendation.hpp" + +#include "routing/route.hpp" + +namespace routing::turns::lanes +{ +namespace +{ +void FixRecommendedReverseLane(LaneWays & ways, LaneWay const recommendedWay) +{ + if (recommendedWay == LaneWay::ReverseLeft) + ways.Remove(LaneWay::ReverseRight); + else if (recommendedWay == LaneWay::ReverseRight) + ways.Remove(LaneWay::ReverseLeft); +} +} // namespace + +void SelectRecommendedLanes(std::vector & routeSegments) +{ + for (auto & segment : routeSegments) + { + auto & t = segment.GetTurn(); + if (t.IsTurnNone() || t.m_lanes.empty()) + continue; + auto & lanesInfo = segment.GetTurnLanes(); + // Check if there are elements in lanesInfo that correspond with the turn exactly. + // If so, fix up all the elements in lanesInfo that correspond with the turn. + if (impl::SetRecommendedLaneWays(t.m_turn, lanesInfo)) + continue; + // If not, check if there are elements in lanesInfo that correspond with the turn + // approximately. If so, fix up all those elements. + if (impl::SetRecommendedLaneWaysApproximately(t.m_turn, lanesInfo)) + continue; + // If not, check if there is an unrestricted lane that could correspond to the + // turn. If so, fix up that lane. + if (impl::SetUnrestrictedLaneAsRecommended(t.m_turn, lanesInfo)) + continue; + // Otherwise, we don't have lane recommendations for the user, so we don't + // want to send the lane data any further. + segment.ClearTurnLanes(); + } +} + +bool impl::SetRecommendedLaneWays(CarDirection const carDirection, LanesInfo & lanesInfo) +{ + LaneWay laneWay; + switch (carDirection) + { + case CarDirection::GoStraight: laneWay = LaneWay::Through; break; + case CarDirection::TurnRight: laneWay = LaneWay::Right; break; + case CarDirection::TurnSharpRight: laneWay = LaneWay::SharpRight; break; + case CarDirection::TurnSlightRight: [[fallthrough]]; + case CarDirection::ExitHighwayToRight: laneWay = LaneWay::SlightRight; break; + case CarDirection::TurnLeft: laneWay = LaneWay::Left; break; + case CarDirection::TurnSharpLeft: laneWay = LaneWay::SharpLeft; break; + case CarDirection::TurnSlightLeft: [[fallthrough]]; + case CarDirection::ExitHighwayToLeft: laneWay = LaneWay::SlightLeft; break; + case CarDirection::UTurnLeft: laneWay = LaneWay::ReverseLeft; break; + case CarDirection::UTurnRight: laneWay = LaneWay::ReverseRight; break; + default: return false; + } + + bool isLaneConformed = false; + for (auto & [laneWays, recommendedWay] : lanesInfo) + { + if (laneWays.Contains(laneWay)) + { + recommendedWay = laneWay; + isLaneConformed = true; + } + FixRecommendedReverseLane(laneWays, recommendedWay); + } + return isLaneConformed; +} + +bool impl::SetRecommendedLaneWaysApproximately(CarDirection const carDirection, LanesInfo & lanesInfo) +{ + std::vector approximateLaneWays; + switch (carDirection) + { + case CarDirection::UTurnLeft: approximateLaneWays = {LaneWay::SharpLeft}; break; + case CarDirection::TurnSharpLeft: approximateLaneWays = {LaneWay::Left}; break; + case CarDirection::TurnLeft: approximateLaneWays = {LaneWay::SlightLeft, LaneWay::SharpLeft}; break; + case CarDirection::TurnSlightLeft: [[fallthrough]]; + case CarDirection::ExitHighwayToLeft: approximateLaneWays = {LaneWay::Left}; break; + case CarDirection::GoStraight: approximateLaneWays = {LaneWay::SlightRight, LaneWay::SlightLeft}; break; + case CarDirection::ExitHighwayToRight: [[fallthrough]]; + case CarDirection::TurnSlightRight: approximateLaneWays = {LaneWay::Right}; break; + case CarDirection::TurnRight: approximateLaneWays = {LaneWay::SlightRight, LaneWay::SharpRight}; break; + case CarDirection::TurnSharpRight: approximateLaneWays = {LaneWay::Right}; break; + case CarDirection::UTurnRight: approximateLaneWays = {LaneWay::SharpRight}; break; + default: return false; + } + + bool isLaneConformed = false; + for (auto & [laneWays, recommendedWay] : lanesInfo) + { + for (auto const & laneWay : approximateLaneWays) + { + if (laneWays.Contains(laneWay)) + { + recommendedWay = laneWay; + isLaneConformed = true; + break; + } + } + } + + return isLaneConformed; +} + +bool impl::SetUnrestrictedLaneAsRecommended(CarDirection const carDirection, LanesInfo & lanesInfo) +{ + static auto constexpr setFirstUnrestrictedLane = [](LaneWay const laneWay, auto beginIt, auto endIt) + { + auto it = std::find_if(beginIt, endIt, [](auto const & laneInfo) { return laneInfo.laneWays.IsUnrestricted(); }); + if (it == endIt) + return false; + it->recommendedWay = laneWay; + return true; + }; + + if (IsTurnMadeFromLeft(carDirection)) + return setFirstUnrestrictedLane(LaneWay::Left, lanesInfo.begin(), lanesInfo.end()); + if (IsTurnMadeFromRight(carDirection)) + return setFirstUnrestrictedLane(LaneWay::Right, lanesInfo.rbegin(), lanesInfo.rend()); + return false; +} +} // namespace routing::turns::lanes diff --git a/libs/routing/lanes/lanes_recommendation.hpp b/libs/routing/lanes/lanes_recommendation.hpp new file mode 100644 index 000000000..24d2dd0f5 --- /dev/null +++ b/libs/routing/lanes/lanes_recommendation.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include "routing/lanes/lane_info.hpp" + +#include + +namespace routing +{ +class RouteSegment; + +namespace turns +{ +enum class CarDirection; +} // namespace turns +} // namespace routing + +namespace routing::turns::lanes +{ +/// Selects lanes which are recommended for an end user. +void SelectRecommendedLanes(std::vector & routeSegments); + +// Keep signatures in the header for testing purposes +namespace impl +{ +bool SetRecommendedLaneWays(CarDirection carDirection, LanesInfo & lanesInfo); + +bool SetRecommendedLaneWaysApproximately(CarDirection carDirection, LanesInfo & lanesInfo); + +bool SetUnrestrictedLaneAsRecommended(CarDirection carDirection, LanesInfo & lanesInfo); +} // namespace impl +} // namespace routing::turns::lanes diff --git a/libs/routing/loaded_path_segment.hpp b/libs/routing/loaded_path_segment.hpp index 27e02483e..4b9c07a8e 100644 --- a/libs/routing/loaded_path_segment.hpp +++ b/libs/routing/loaded_path_segment.hpp @@ -1,7 +1,6 @@ #pragma once -#include "routing/maxspeeds.hpp" -#include "routing/road_point.hpp" +#include "routing/lanes/lane_info.hpp" #include "routing/route.hpp" #include "routing/segment.hpp" #include "routing/turns.hpp" @@ -10,7 +9,6 @@ #include "geometry/point_with_altitude.hpp" -#include #include namespace routing @@ -23,7 +21,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/libs/routing/route.hpp b/libs/routing/route.hpp index db7c601f9..fecec1066 100644 --- a/libs/routing/route.hpp +++ b/libs/routing/route.hpp @@ -1,5 +1,6 @@ #pragma once +#include "routing/lanes/lane_info.hpp" #include "routing/routing_options.hpp" #include "routing/routing_settings.hpp" #include "routing/segment.hpp" @@ -139,7 +140,7 @@ public: void SetTurnExits(uint32_t exitNum) { m_turn.m_exitNum = exitNum; } - std::vector & 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/libs/routing/routing_session.cpp b/libs/routing/routing_session.cpp index 48bb2b9f8..35bb5500b 100644 --- a/libs/routing/routing_session.cpp +++ b/libs/routing/routing_session.cpp @@ -422,15 +422,7 @@ void RoutingSession::GetRouteFollowingInfo(FollowingInfo & info) const // Lane information info.m_lanes.clear(); if (distanceToTurnMeters < kShowLanesMinDistInMeters || m_route->GetCurrentTimeToNearestTurnSec() < 60.0) - { - // There are two nested loops below. Outer one is for lanes and inner one (ctor of - // SingleLaneInfo) is - // for each lane's directions. The size of turn.m_lanes is relatively small. Less than 10 in - // most cases. - info.m_lanes.reserve(turn.m_lanes.size()); - for (size_t j = 0; j < turn.m_lanes.size(); ++j) - info.m_lanes.emplace_back(turn.m_lanes[j]); - } + info.m_lanes = turn.m_lanes; // Pedestrian info. info.m_pedestrianTurn = diff --git a/libs/routing/routing_tests/CMakeLists.txt b/libs/routing/routing_tests/CMakeLists.txt index 898b34865..06adf631e 100644 --- a/libs/routing/routing_tests/CMakeLists.txt +++ b/libs/routing/routing_tests/CMakeLists.txt @@ -1,6 +1,8 @@ project(routing_tests) set(SRC + lanes/lanes_parser_tests.cpp + lanes/lanes_recommendation_tests.cpp applying_traffic_test.cpp astar_algorithm_test.cpp astar_progress_test.cpp diff --git a/libs/routing/routing_tests/lanes/lanes_parser_tests.cpp b/libs/routing/routing_tests/lanes/lanes_parser_tests.cpp new file mode 100644 index 000000000..9c5633d9c --- /dev/null +++ b/libs/routing/routing_tests/lanes/lanes_parser_tests.cpp @@ -0,0 +1,160 @@ +#include "testing/testing.hpp" + +#include "routing/lanes/lanes_parser.hpp" + +namespace routing::turns::lanes::test +{ +UNIT_TEST(TestParseLaneWays) +{ + std::vector> const testData = { + {";", {LaneWay::None}}, + {"none", {LaneWay::None}}, + {"left", {LaneWay::Left}}, + {"right", {LaneWay::Right}}, + {"sharp_left", {LaneWay::SharpLeft}}, + {"slight_left", {LaneWay::SlightLeft}}, + {"merge_to_right", {LaneWay::MergeToRight}}, + {"merge_to_left", {LaneWay::MergeToLeft}}, + {"slight_right", {LaneWay::SlightRight}}, + {"sharp_right", {LaneWay::SharpRight}}, + {"reverse", {LaneWay::ReverseLeft, LaneWay::ReverseRight}}, + {"next_right", {LaneWay::Right}}, + {"slide_left", {LaneWay::Through}}, + {"slide_right", {LaneWay::Through}}, + }; + + for (auto const & [in, out] : testData) + { + LanesInfo const result = ParseLanes(in); + LaneWays const expected = {out}; + TEST_EQUAL(result.front().laneWays, expected, ()); + } +} + +UNIT_TEST(TestParseSingleLane) +{ + { + LanesInfo const result = ParseLanes("through;right"); + LaneWays constexpr expected = {LaneWay::Through, LaneWay::Right}; + TEST_EQUAL(result.front().laneWays, expected, ()); + } + + { + LanesInfo const result = ParseLanes("through;Right"); + LaneWays constexpr expected = {LaneWay::Through, LaneWay::Right}; + TEST_EQUAL(result.front().laneWays, expected, ()); + } + + { + LanesInfo const result = ParseLanes("through ;Right"); + LaneWays constexpr expected = {LaneWay::Through, LaneWay::Right}; + TEST_EQUAL(result.front().laneWays, expected, ()); + } + + { + LanesInfo const result = ParseLanes("left;through"); + LaneWays constexpr expected = {LaneWay::Left, LaneWay::Through}; + TEST_EQUAL(result.front().laneWays, expected, ()); + } + + { + LanesInfo const result = ParseLanes("left"); + LaneWays constexpr expected = {LaneWay::Left}; + TEST_EQUAL(result.front().laneWays, expected, ()); + } + + { + LanesInfo const result = ParseLanes("left;"); + LaneWays constexpr expected = {LaneWay::Left, LaneWay::None}; + TEST_EQUAL(result.front().laneWays, expected, ()); + } + + { + LanesInfo const result = ParseLanes(";"); + LaneWays constexpr expected = {LaneWay::None}; + TEST_EQUAL(result.front().laneWays, expected, ()); + } + + TEST_EQUAL(ParseLanes("SD32kk*887;;").empty(), true, ()); + TEST_EQUAL(ParseLanes("Что-то на кириллице").empty(), true, ()); + TEST_EQUAL(ParseLanes("משהו בעברית").empty(), true, ()); +} + +UNIT_TEST(TestParseLanes) +{ + { + LanesInfo const result = ParseLanes("through|through|through|through;right"); + LanesInfo const expected = { + {{LaneWay::Through}}, {{LaneWay::Through}}, {{LaneWay::Through}}, {{LaneWay::Through, LaneWay::Right}}}; + TEST_EQUAL(result, expected, ()); + } + + { + LanesInfo const result = ParseLanes("left|left;through|through|through"); + LanesInfo const expected = { + {{LaneWay::Left}}, {{LaneWay::Left, LaneWay::Through}}, {{LaneWay::Through}}, {{LaneWay::Through}}}; + TEST_EQUAL(result, expected, ()); + } + + { + LanesInfo const result = ParseLanes("left|through|through"); + LanesInfo const expected = {{{LaneWay::Left}}, {{LaneWay::Through}}, {{LaneWay::Through}}}; + TEST_EQUAL(result, expected, ()); + } + + { + LanesInfo const result = ParseLanes("left|le ft| through|through | right"); + LanesInfo const expected = { + {{LaneWay::Left}}, {{LaneWay::Left}}, {{LaneWay::Through}}, {{LaneWay::Through}}, {{LaneWay::Right}}}; + TEST_EQUAL(result, expected, ()); + } + + { + LanesInfo const result = ParseLanes("left|Left|through|througH|right"); + LanesInfo const expected = { + {{LaneWay::Left}}, {{LaneWay::Left}}, {{LaneWay::Through}}, {{LaneWay::Through}}, {{LaneWay::Right}}}; + TEST_EQUAL(result, expected, ()); + } + + { + LanesInfo const result = ParseLanes("left|Left|through|througH|through;right;sharp_rIght"); + LanesInfo const expected = {{{LaneWay::Left}}, + {{LaneWay::Left}}, + {{LaneWay::Through}}, + {{LaneWay::Through}}, + {{LaneWay::Through, LaneWay::Right, LaneWay::SharpRight}}}; + TEST_EQUAL(result, expected, ()); + } + + { + LanesInfo const result = ParseLanes("left |Left|through|througH|right"); + LanesInfo const expected = { + {{LaneWay::Left}}, {{LaneWay::Left}}, {{LaneWay::Through}}, {{LaneWay::Through}}, {{LaneWay::Right}}}; + TEST_EQUAL(result, expected, ()); + } + + { + LanesInfo const result = ParseLanes("|||||slight_right"); + LanesInfo const expected = {{{LaneWay::None}}, {{LaneWay::None}}, {{LaneWay::None}}, + {{LaneWay::None}}, {{LaneWay::None}}, {{LaneWay::SlightRight}}}; + TEST_EQUAL(result, expected, ()); + } + + { + LanesInfo const result = ParseLanes("|"); + LanesInfo const expected = {{{LaneWay::None}}, {{LaneWay::None}}}; + TEST_EQUAL(result, expected, ()); + } + + { + LanesInfo const result = ParseLanes(";|;;;"); + LanesInfo const expected = {{{LaneWay::None}}, {{LaneWay::None}}}; + TEST_EQUAL(result, expected, ()); + } + + { + LanesInfo const result = ParseLanes("left|Leftt|through|througH|right"); + TEST_EQUAL(result.empty(), true, ()); + } +} +} // namespace routing::turns::lanes::test diff --git a/libs/routing/routing_tests/lanes/lanes_recommendation_tests.cpp b/libs/routing/routing_tests/lanes/lanes_recommendation_tests.cpp new file mode 100644 index 000000000..ddcc20aae --- /dev/null +++ b/libs/routing/routing_tests/lanes/lanes_recommendation_tests.cpp @@ -0,0 +1,221 @@ +#include "routing/turns.hpp" +#include "testing/testing.hpp" + +#include "routing/lanes/lanes_recommendation.hpp" +#include "routing/routing_tests/tools.hpp" + +namespace routing::turns::lanes::test +{ +UNIT_TEST(TestSetRecommendedLaneWays_Smoke) +{ + using impl::SetRecommendedLaneWays; + + struct CarDirectionToLaneWayMapping + { + CarDirection carDirection; + LaneWay laneWay; + bool shouldBeRecommended; + }; + std::vector const testData = { + {CarDirection::GoStraight, LaneWay::Through, true}, + {CarDirection::TurnRight, LaneWay::Right, true}, + {CarDirection::TurnSharpRight, LaneWay::SharpRight, true}, + {CarDirection::TurnSlightRight, LaneWay::SlightRight, true}, + {CarDirection::TurnLeft, LaneWay::Left, true}, + {CarDirection::TurnSharpLeft, LaneWay::SharpLeft, true}, + {CarDirection::TurnSlightLeft, LaneWay::SlightLeft, true}, + {CarDirection::UTurnLeft, LaneWay::ReverseLeft, true}, + {CarDirection::UTurnRight, LaneWay::ReverseRight, true}, + {CarDirection::ExitHighwayToLeft, LaneWay::SlightLeft, true}, + {CarDirection::ExitHighwayToRight, LaneWay::SlightRight, true}, + // We do not recommend any lane way for these directions + {CarDirection::None, LaneWay::None, false}, + {CarDirection::EnterRoundAbout, LaneWay::None, false}, + {CarDirection::LeaveRoundAbout, LaneWay::None, false}, + {CarDirection::StayOnRoundAbout, LaneWay::None, false}, + {CarDirection::StartAtEndOfStreet, LaneWay::None, false}, + {CarDirection::ReachedYourDestination, LaneWay::None, false}, + }; + TEST_EQUAL(testData.size(), static_cast(CarDirection::Count), ("Not all CarDirection values are covered.")); + for (auto const & [carDirection, laneWay, shouldBeRecommended] : testData) + { + LanesInfo lanesInfo = {{{laneWay}}}; + bool const isRecommended = SetRecommendedLaneWays(carDirection, lanesInfo); + TEST_EQUAL(isRecommended, shouldBeRecommended, + ("CarDirection:", DebugPrint(carDirection), "LaneWay:", DebugPrint(laneWay))); + TEST_EQUAL(lanesInfo[0].recommendedWay, shouldBeRecommended ? laneWay : LaneWay::None, ()); + } +} + +UNIT_TEST(TestSetRecommendedLaneWays) +{ + { + LanesInfo lanesInfo = { + {{LaneWay::ReverseLeft, LaneWay::Left, LaneWay::Through}}, + {{LaneWay::Through}}, + {{LaneWay::Through}}, + {{LaneWay::Through, LaneWay::Right}}, + {{LaneWay::Right}}, + }; + TEST(impl::SetRecommendedLaneWays(CarDirection::GoStraight, lanesInfo), ()); + TEST_EQUAL(lanesInfo[0].recommendedWay, LaneWay::Through, ()); + TEST_EQUAL(lanesInfo[1].recommendedWay, LaneWay::Through, ()); + TEST_EQUAL(lanesInfo[2].recommendedWay, LaneWay::Through, ()); + TEST_EQUAL(lanesInfo[3].recommendedWay, LaneWay::Through, ()); + TEST_EQUAL(lanesInfo[4].recommendedWay, LaneWay::None, ()); + } + { + LanesInfo lanesInfo = { + {{LaneWay::ReverseLeft, LaneWay::Left}}, + {{LaneWay::Right}}, + }; + TEST(!impl::SetRecommendedLaneWays(CarDirection::GoStraight, lanesInfo), ()); + TEST_EQUAL(lanesInfo[0].recommendedWay, LaneWay::None, ()); + TEST_EQUAL(lanesInfo[1].recommendedWay, LaneWay::None, ()); + } + { + LanesInfo lanesInfo = { + {{LaneWay::ReverseLeft, LaneWay::ReverseRight}}, + }; + TEST(impl::SetRecommendedLaneWays(CarDirection::UTurnLeft, lanesInfo), ()); + TEST_EQUAL(lanesInfo[0].recommendedWay, LaneWay::ReverseLeft, ()); + TEST_EQUAL(lanesInfo[0].laneWays.Contains(LaneWay::ReverseRight), false, ()); + } +} + +UNIT_TEST(SetRecommendedLaneWaysApproximately_Smoke) +{ + { + struct CarDirectionToLaneWaysApproximateMapping + { + CarDirection carDirection; + std::vector laneWay; + }; + std::vector const testData = { + {CarDirection::UTurnLeft, {LaneWay::SharpLeft}}, + {CarDirection::TurnSharpLeft, {LaneWay::Left}}, + {CarDirection::TurnLeft, {LaneWay::SlightLeft, LaneWay::SharpLeft}}, + {CarDirection::TurnSlightLeft, {LaneWay::Left}}, + {CarDirection::ExitHighwayToLeft, {LaneWay::Left}}, + {CarDirection::GoStraight, {LaneWay::SlightRight, LaneWay::SlightLeft}}, + {CarDirection::ExitHighwayToRight, {LaneWay::Right}}, + {CarDirection::TurnSlightRight, {LaneWay::Right}}, + {CarDirection::TurnRight, {LaneWay::SlightRight, LaneWay::SharpRight}}, + {CarDirection::TurnSharpRight, {LaneWay::Right}}, + {CarDirection::UTurnRight, {LaneWay::SharpRight}}, + }; + for (auto const & [carDirection, laneWays] : testData) + { + for (auto const & laneWay : laneWays) + { + LanesInfo lanesInfo = {{{laneWay}}}; + bool const isRecommended = impl::SetRecommendedLaneWaysApproximately(carDirection, lanesInfo); + TEST(isRecommended, ("CarDirection:", DebugPrint(carDirection), "LaneWay:", DebugPrint(laneWay))); + TEST_EQUAL(lanesInfo[0].recommendedWay, laneWay, ()); + } + } + } + + { + // Those directions do not have any recommended lane ways. + std::vector const carDirections = {CarDirection::None, + CarDirection::EnterRoundAbout, + CarDirection::LeaveRoundAbout, + CarDirection::StayOnRoundAbout, + CarDirection::StartAtEndOfStreet, + CarDirection::ReachedYourDestination}; + for (auto const & carDirection : carDirections) + { + LanesInfo lanesInfo = {{{LaneWay::Through}}}; + TEST(!impl::SetRecommendedLaneWaysApproximately(carDirection, lanesInfo), ()); + TEST_EQUAL(lanesInfo[0].recommendedWay, LaneWay::None, ()); + } + } +} + +UNIT_TEST(SetRecommendedLaneWaysApproximately) +{ + { + LanesInfo lanesInfo = { + {{LaneWay::ReverseLeft, LaneWay::Left, LaneWay::SlightLeft}}, + {{LaneWay::SlightRight, LaneWay::Right}}, + {{LaneWay::Right}}, + }; + TEST(impl::SetRecommendedLaneWaysApproximately(CarDirection::GoStraight, lanesInfo), ()); + TEST_EQUAL(lanesInfo[0].recommendedWay, LaneWay::SlightLeft, ()); + TEST_EQUAL(lanesInfo[1].recommendedWay, LaneWay::SlightRight, ()); + TEST_EQUAL(lanesInfo[2].recommendedWay, LaneWay::None, ()); + } + { + LanesInfo lanesInfo = { + {{LaneWay::ReverseLeft, LaneWay::Left}}, + {{LaneWay::Right}}, + }; + TEST(!impl::SetRecommendedLaneWaysApproximately(CarDirection::GoStraight, lanesInfo), ()); + TEST_EQUAL(lanesInfo[0].recommendedWay, LaneWay::None, ()); + TEST_EQUAL(lanesInfo[1].recommendedWay, LaneWay::None, ()); + } + { + LanesInfo lanesInfo = { + {{LaneWay::SharpLeft, LaneWay::SlightLeft}}, + }; + TEST(impl::SetRecommendedLaneWaysApproximately(CarDirection::TurnLeft, lanesInfo), ()); + TEST_EQUAL(lanesInfo[0].recommendedWay, LaneWay::SlightLeft, ()); + } +} + +UNIT_TEST(SetUnrestrictedLaneAsRecommended) +{ + LanesInfo const testData = {{{LaneWay::ReverseLeft}}, {{LaneWay::None}}, {{LaneWay::None}}, {{LaneWay::Right}}}; + { + LanesInfo lanesInfo = testData; + TEST(impl::SetUnrestrictedLaneAsRecommended(CarDirection::TurnLeft, lanesInfo), ()); + TEST_EQUAL(lanesInfo[0].recommendedWay, LaneWay::None, ()); + TEST_EQUAL(lanesInfo[1].recommendedWay, LaneWay::Left, ()); + TEST_EQUAL(lanesInfo[2].recommendedWay, LaneWay::None, ()); + TEST_EQUAL(lanesInfo[3].recommendedWay, LaneWay::None, ()); + } + { + LanesInfo lanesInfo = testData; + TEST(impl::SetUnrestrictedLaneAsRecommended(CarDirection::TurnRight, lanesInfo), ()); + TEST_EQUAL(lanesInfo[0].recommendedWay, LaneWay::None, ()); + TEST_EQUAL(lanesInfo[1].recommendedWay, LaneWay::None, ()); + TEST_EQUAL(lanesInfo[2].recommendedWay, LaneWay::Right, ()); + TEST_EQUAL(lanesInfo[3].recommendedWay, LaneWay::None, ()); + } + { + LanesInfo lanesInfo = {}; + TEST(!impl::SetUnrestrictedLaneAsRecommended(CarDirection::TurnRight, lanesInfo), ()); + } + { + LanesInfo lanesInfo = {{{LaneWay::Right}}}; + TEST(!impl::SetUnrestrictedLaneAsRecommended(CarDirection::TurnRight, lanesInfo), ()); + TEST_EQUAL(lanesInfo[0].recommendedWay, LaneWay::None, ()); + } +} + +UNIT_TEST(SelectRecommendedLanes) +{ + std::vector turns = {{1, CarDirection::GoStraight}, + {2, CarDirection::TurnLeft}, + {3, CarDirection::TurnRight}, + {4, CarDirection::ReachedYourDestination}}; + turns[0].m_lanes.push_back({{LaneWay::Left, LaneWay::Through}}); + turns[0].m_lanes.push_back({{LaneWay::Right}}); + turns[1].m_lanes.push_back({{LaneWay::SlightLeft}}); + turns[1].m_lanes.push_back({{LaneWay::Through}}); + turns[1].m_lanes.push_back({{LaneWay::None}}); + turns[2].m_lanes.push_back({{LaneWay::Left, LaneWay::SharpLeft}}); + turns[2].m_lanes.push_back({{LaneWay::None}}); + std::vector routeSegments; + RouteSegmentsFrom({}, {}, turns, {}, routeSegments); + SelectRecommendedLanes(routeSegments); + TEST_EQUAL(routeSegments[0].GetTurn().m_lanes[0].recommendedWay, LaneWay::Through, ()); + TEST_EQUAL(routeSegments[0].GetTurn().m_lanes[1].recommendedWay, LaneWay::None, ()); + TEST_EQUAL(routeSegments[1].GetTurn().m_lanes[0].recommendedWay, LaneWay::SlightLeft, ()); + TEST_EQUAL(routeSegments[1].GetTurn().m_lanes[1].recommendedWay, LaneWay::None, ()); + TEST_EQUAL(routeSegments[1].GetTurn().m_lanes[2].recommendedWay, LaneWay::None, ()); + TEST_EQUAL(routeSegments[2].GetTurn().m_lanes[0].recommendedWay, LaneWay::None, ()); + TEST_EQUAL(routeSegments[2].GetTurn().m_lanes[1].recommendedWay, LaneWay::Right, ()); +} +} // namespace routing::turns::lanes::test diff --git a/libs/routing/routing_tests/turns_generator_test.cpp b/libs/routing/routing_tests/turns_generator_test.cpp index 41cde9746..19ec98d84 100644 --- a/libs/routing/routing_tests/turns_generator_test.cpp +++ b/libs/routing/routing_tests/turns_generator_test.cpp @@ -18,7 +18,6 @@ #include "base/macros.hpp" -#include #include #include @@ -65,118 +64,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.; @@ -238,76 +125,6 @@ UNIT_TEST(TestFixupTurns) } } -UNIT_TEST(TestIsLaneWayConformedTurnDirection) -{ - TEST(IsLaneWayConformedTurnDirection(LaneWay::Left, CarDirection::TurnLeft), ()); - TEST(IsLaneWayConformedTurnDirection(LaneWay::Right, CarDirection::TurnRight), ()); - TEST(IsLaneWayConformedTurnDirection(LaneWay::SlightLeft, CarDirection::TurnSlightLeft), ()); - TEST(IsLaneWayConformedTurnDirection(LaneWay::SharpRight, CarDirection::TurnSharpRight), ()); - TEST(IsLaneWayConformedTurnDirection(LaneWay::Reverse, CarDirection::UTurnLeft), ()); - TEST(IsLaneWayConformedTurnDirection(LaneWay::Reverse, CarDirection::UTurnRight), ()); - TEST(IsLaneWayConformedTurnDirection(LaneWay::Through, CarDirection::GoStraight), ()); - - TEST(!IsLaneWayConformedTurnDirection(LaneWay::Left, CarDirection::TurnSlightLeft), ()); - TEST(!IsLaneWayConformedTurnDirection(LaneWay::Right, CarDirection::TurnSharpRight), ()); - TEST(!IsLaneWayConformedTurnDirection(LaneWay::SlightLeft, CarDirection::GoStraight), ()); - TEST(!IsLaneWayConformedTurnDirection(LaneWay::SharpRight, CarDirection::None), ()); - TEST(!IsLaneWayConformedTurnDirection(LaneWay::Reverse, CarDirection::TurnLeft), ()); - TEST(!IsLaneWayConformedTurnDirection(LaneWay::None, CarDirection::ReachedYourDestination), ()); -} - -UNIT_TEST(TestIsLaneWayConformedTurnDirectionApproximately) -{ - TEST(IsLaneWayConformedTurnDirectionApproximately(LaneWay::Left, CarDirection::TurnSharpLeft), ()); - TEST(IsLaneWayConformedTurnDirectionApproximately(LaneWay::Left, CarDirection::TurnSlightLeft), ()); - TEST(IsLaneWayConformedTurnDirectionApproximately(LaneWay::Right, CarDirection::TurnSharpRight), ()); - TEST(IsLaneWayConformedTurnDirectionApproximately(LaneWay::Right, CarDirection::TurnRight), ()); - TEST(IsLaneWayConformedTurnDirectionApproximately(LaneWay::Reverse, CarDirection::UTurnLeft), ()); - TEST(IsLaneWayConformedTurnDirectionApproximately(LaneWay::Reverse, CarDirection::UTurnRight), ()); - TEST(IsLaneWayConformedTurnDirectionApproximately(LaneWay::SlightLeft, CarDirection::GoStraight), ()); - TEST(IsLaneWayConformedTurnDirectionApproximately(LaneWay::SlightRight, CarDirection::GoStraight), ()); - - TEST(!IsLaneWayConformedTurnDirectionApproximately(LaneWay::SharpLeft, CarDirection::UTurnLeft), ()); - TEST(!IsLaneWayConformedTurnDirectionApproximately(LaneWay::SharpLeft, CarDirection::UTurnRight), ()); - TEST(!IsLaneWayConformedTurnDirectionApproximately(LaneWay::SharpRight, CarDirection::UTurnLeft), ()); - TEST(!IsLaneWayConformedTurnDirectionApproximately(LaneWay::SharpRight, CarDirection::UTurnRight), ()); - TEST(!IsLaneWayConformedTurnDirection(LaneWay::Through, CarDirection::ReachedYourDestination), ()); - TEST(!IsLaneWayConformedTurnDirectionApproximately(LaneWay::Through, CarDirection::TurnRight), ()); - TEST(!IsLaneWayConformedTurnDirectionApproximately(LaneWay::SlightRight, CarDirection::TurnSharpLeft), ()); -} - -UNIT_TEST(TestAddingActiveLaneInformation) -{ - vector turns = {{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/libs/routing/turns.cpp b/libs/routing/turns.cpp index 21b82a0ab..65da6fa07 100644 --- a/libs/routing/turns.cpp +++ b/libs/routing/turns.cpp @@ -5,8 +5,6 @@ #include "platform/country_file.hpp" #include "base/internal/message.hpp" -#include "base/stl_helpers.hpp" -#include "base/string_utils.hpp" #include #include @@ -21,28 +19,6 @@ using namespace std; namespace { -/// The order is important. Starting with the most frequent tokens according to -/// taginfo.openstreetmap.org we minimize the number of the comparisons in ParseSingleLane(). -/// -/// A `none` lane can be represented either as "none" or as "". That means both "none" and "" -/// should be considered names, even though they refer to the same thing. As a result, -/// `LaneWay::None` appears twice in this array, which is one longer than the number of -/// enum values. -array, 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"}, @@ -162,12 +138,6 @@ string DebugPrint(SegmentRange const & segmentRange) namespace turns { -// SingleLaneInfo --------------------------------------------------------------------------------- -bool SingleLaneInfo::operator==(SingleLaneInfo const & other) const -{ - return m_lane == other.m_lane && m_isRecommended == other.m_isRecommended; -} - string DebugPrint(TurnItem const & turnItem) { stringstream out; @@ -232,118 +202,6 @@ bool IsGoStraightOrSlightTurn(CarDirection t) return (t == CarDirection::GoStraight || t == CarDirection::TurnSlightLeft || t == CarDirection::TurnSlightRight); } -bool IsLaneWayConformedTurnDirection(LaneWay l, CarDirection t) -{ - switch (t) - { - default: return false; - case CarDirection::GoStraight: return l == LaneWay::Through; - case CarDirection::TurnRight: return l == LaneWay::Right; - case CarDirection::TurnSharpRight: return l == LaneWay::SharpRight; - case CarDirection::TurnSlightRight: - case CarDirection::ExitHighwayToRight: return l == LaneWay::SlightRight; - case CarDirection::TurnLeft: return l == LaneWay::Left; - case CarDirection::TurnSharpLeft: return l == LaneWay::SharpLeft; - case CarDirection::TurnSlightLeft: - case CarDirection::ExitHighwayToLeft: return l == LaneWay::SlightLeft; - case CarDirection::UTurnLeft: - case CarDirection::UTurnRight: return l == LaneWay::Reverse; - } -} - -bool IsLaneWayConformedTurnDirectionApproximately(LaneWay l, CarDirection t) -{ - switch (t) - { - default: return false; - case CarDirection::GoStraight: return l == LaneWay::Through || l == LaneWay::SlightRight || l == LaneWay::SlightLeft; - case CarDirection::TurnRight: return l == LaneWay::Right || l == LaneWay::SharpRight || l == LaneWay::SlightRight; - case CarDirection::TurnSharpRight: return l == LaneWay::SharpRight || l == LaneWay::Right; - case CarDirection::TurnSlightRight: return l == LaneWay::SlightRight || l == LaneWay::Through || l == LaneWay::Right; - case CarDirection::TurnLeft: return l == LaneWay::Left || l == LaneWay::SlightLeft || l == LaneWay::SharpLeft; - case CarDirection::TurnSharpLeft: return l == LaneWay::SharpLeft || l == LaneWay::Left; - case CarDirection::TurnSlightLeft: return l == LaneWay::SlightLeft || l == LaneWay::Through || l == LaneWay::Left; - case CarDirection::UTurnLeft: - case CarDirection::UTurnRight: return l == LaneWay::Reverse; - case CarDirection::ExitHighwayToLeft: return l == LaneWay::SlightLeft || l == LaneWay::Left; - case CarDirection::ExitHighwayToRight: return l == LaneWay::SlightRight || l == LaneWay::Right; - } -} - -bool IsLaneUnrestricted(SingleLaneInfo const & lane) -{ - /// @todo Is there any reason to store None single lane? - return lane.m_lane.size() == 1 && lane.m_lane[0] == LaneWay::None; -} - -void SplitLanes(string const & lanesString, char delimiter, vector & 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, strings::IsASCIISpace); - - 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); @@ -368,14 +226,6 @@ string DebugPrint(PedestrianDirection const l) return "unknown PedestrianDirection"; } -string DebugPrint(SingleLaneInfo const & singleLaneInfo) -{ - stringstream out; - out << "SingleLaneInfo [ m_isRecommended == " << singleLaneInfo.m_isRecommended - << ", m_lane == " << ::DebugPrint(singleLaneInfo.m_lane) << " ]" << endl; - return out.str(); -} - double PiMinusTwoVectorsAngle(m2::PointD const & junctionPoint, m2::PointD const & ingoingPoint, m2::PointD const & outgoingPoint) { diff --git a/libs/routing/turns.hpp b/libs/routing/turns.hpp index 63790f983..80437160a 100644 --- a/libs/routing/turns.hpp +++ b/libs/routing/turns.hpp @@ -1,5 +1,6 @@ #pragma once +#include "routing/lanes/lane_info.hpp" #include "routing/segment.hpp" #include "routing_common/num_mwm_id.hpp" @@ -8,7 +9,6 @@ #include "geometry/point2d.hpp" -#include #include #include #include @@ -118,41 +118,6 @@ enum class PedestrianDirection std::string DebugPrint(PedestrianDirection const l); -/*! - * \warning The values of LaneWay shall be synchronized with values of LaneWay enum in java. - */ -enum class LaneWay -{ - None = 0, - Reverse, - SharpLeft, - Left, - SlightLeft, - MergeToRight, - Through, - MergeToLeft, - SlightRight, - Right, - SharpRight, - Count /**< This value is used for internals only. */ -}; - -std::string DebugPrint(LaneWay const l); - -typedef std::vector 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,7 +157,7 @@ struct TurnItem uint32_t m_index; /*!< Index of point on route polyline (Index of segment + 1). */ CarDirection m_turn = CarDirection::None; /*!< The turn instruction of the TurnItem */ - std::vector m_lanes; /*!< Lane information on the edge before the turn. */ + lanes::LanesInfo m_lanes; /*!< Lane information on the edge before the turn. */ uint32_t m_exitNum; /*!< Number of exit on roundabout. */ /*! * \brief m_pedestrianTurn is type of corresponding direction for a pedestrian, or None @@ -223,39 +188,6 @@ bool IsTurnMadeFromLeft(CarDirection t); bool IsTurnMadeFromRight(CarDirection t); bool IsStayOnRoad(CarDirection t); bool IsGoStraightOrSlightTurn(CarDirection t); - -/*! - * \param l A variant of going along a lane. - * \param t A turn direction. - * \return True if @l corresponds with @t exactly. For example it returns true - * when @l equals to LaneWay::Right and @t equals to TurnDirection::TurnRight. - * Otherwise it returns false. - */ -bool IsLaneWayConformedTurnDirection(LaneWay l, CarDirection t); - -/*! - * \param l A variant of going along a lane. - * \param t A turn direction. - * \return True if @l corresponds with @t approximately. For example it returns true - * when @l equals to LaneWay::Right and @t equals to TurnDirection::TurnSlightRight. - * Otherwise it returns false. - */ -bool IsLaneWayConformedTurnDirectionApproximately(LaneWay l, CarDirection t); - -bool IsLaneUnrestricted(SingleLaneInfo const & lane); - -/*! - * \brief Parse lane information which comes from @lanesString - * \param lanesString lane information. Example through|through|through|through;right - * \param lanes the result of parsing. - * \return true if @lanesString parsed successfully, false otherwise. - * Note 1: if @lanesString is empty returns false. - * Note 2: @laneString is passed by value on purpose. It'll be used(changed) in the method. - */ -bool ParseLanes(std::string lanesString, std::vector & 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. diff --git a/xcode/routing/routing.xcodeproj/project.pbxproj b/xcode/routing/routing.xcodeproj/project.pbxproj index e216877a9..00dcb5ab5 100644 --- a/xcode/routing/routing.xcodeproj/project.pbxproj +++ b/xcode/routing/routing.xcodeproj/project.pbxproj @@ -48,6 +48,16 @@ 0C81E1581F0258AA00DC66DF /* segmented_route.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 0C81E1561F0258AA00DC66DF /* segmented_route.hpp */; }; 0C8705051E0182F200BCAF71 /* route_point.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 0C8705041E0182F200BCAF71 /* route_point.hpp */; }; 0CF709361F05172200D5067E /* checkpoints.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0CF709351F05172200D5067E /* checkpoints.cpp */; }; + 1667FB002E3F845D00F06E59 /* lanes_parser.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 1667FAFB2E3F845D00F06E59 /* lanes_parser.hpp */; }; + 1667FB012E3F845D00F06E59 /* lane_info.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 1667FAF72E3F845D00F06E59 /* lane_info.hpp */; }; + 1667FB022E3F845D00F06E59 /* lanes_recommendation.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 1667FAFD2E3F845D00F06E59 /* lanes_recommendation.hpp */; }; + 1667FB032E3F845D00F06E59 /* lane_way.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 1667FAF92E3F845D00F06E59 /* lane_way.hpp */; }; + 1667FB042E3F845D00F06E59 /* lane_info.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1667FAF82E3F845D00F06E59 /* lane_info.cpp */; }; + 1667FB052E3F845D00F06E59 /* lanes_parser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1667FAFC2E3F845D00F06E59 /* lanes_parser.cpp */; }; + 1667FB062E3F845D00F06E59 /* lane_way.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1667FAFA2E3F845D00F06E59 /* lane_way.cpp */; }; + 1667FB072E3F845D00F06E59 /* lanes_recommendation.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1667FAFE2E3F845D00F06E59 /* lanes_recommendation.cpp */; }; + 1667FB0B2E3F847B00F06E59 /* lanes_recommendation_tests.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1667FB092E3F847B00F06E59 /* lanes_recommendation_tests.cpp */; }; + 1667FB0C2E3F847B00F06E59 /* lanes_parser_tests.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1667FB082E3F847B00F06E59 /* lanes_parser_tests.cpp */; }; 349D1CE01E3F589900A878FD /* restrictions_serialization.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 349D1CDE1E3F589900A878FD /* restrictions_serialization.cpp */; }; 349D1CE11E3F589900A878FD /* restrictions_serialization.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 349D1CDF1E3F589900A878FD /* restrictions_serialization.hpp */; }; 40576F781F7A788B000B593B /* fake_vertex.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 40576F771F7A788B000B593B /* fake_vertex.hpp */; }; @@ -329,6 +339,16 @@ 0C8705041E0182F200BCAF71 /* route_point.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = route_point.hpp; sourceTree = ""; }; 0CF5E8A91E8EA7A1001ED497 /* coding_test.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = coding_test.cpp; sourceTree = ""; }; 0CF709351F05172200D5067E /* checkpoints.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = checkpoints.cpp; sourceTree = ""; }; + 1667FAF72E3F845D00F06E59 /* lane_info.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = lane_info.hpp; sourceTree = ""; }; + 1667FAF82E3F845D00F06E59 /* lane_info.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = lane_info.cpp; sourceTree = ""; }; + 1667FAF92E3F845D00F06E59 /* lane_way.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = lane_way.hpp; sourceTree = ""; }; + 1667FAFA2E3F845D00F06E59 /* lane_way.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = lane_way.cpp; sourceTree = ""; }; + 1667FAFB2E3F845D00F06E59 /* lanes_parser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = lanes_parser.hpp; sourceTree = ""; }; + 1667FAFC2E3F845D00F06E59 /* lanes_parser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = lanes_parser.cpp; sourceTree = ""; }; + 1667FAFD2E3F845D00F06E59 /* lanes_recommendation.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = lanes_recommendation.hpp; sourceTree = ""; }; + 1667FAFE2E3F845D00F06E59 /* lanes_recommendation.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = lanes_recommendation.cpp; sourceTree = ""; }; + 1667FB082E3F847B00F06E59 /* lanes_parser_tests.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = lanes_parser_tests.cpp; sourceTree = ""; }; + 1667FB092E3F847B00F06E59 /* lanes_recommendation_tests.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = lanes_recommendation_tests.cpp; sourceTree = ""; }; 349D1CDE1E3F589900A878FD /* restrictions_serialization.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = restrictions_serialization.cpp; sourceTree = ""; }; 349D1CDF1E3F589900A878FD /* restrictions_serialization.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = restrictions_serialization.hpp; sourceTree = ""; }; 34F558351DBF2A2600A4FC11 /* common-debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "common-debug.xcconfig"; path = "../common-debug.xcconfig"; sourceTree = ""; }; @@ -575,6 +595,30 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 1667FAFF2E3F845D00F06E59 /* lanes */ = { + isa = PBXGroup; + children = ( + 1667FAF72E3F845D00F06E59 /* lane_info.hpp */, + 1667FAF82E3F845D00F06E59 /* lane_info.cpp */, + 1667FAF92E3F845D00F06E59 /* lane_way.hpp */, + 1667FAFA2E3F845D00F06E59 /* lane_way.cpp */, + 1667FAFB2E3F845D00F06E59 /* lanes_parser.hpp */, + 1667FAFC2E3F845D00F06E59 /* lanes_parser.cpp */, + 1667FAFD2E3F845D00F06E59 /* lanes_recommendation.hpp */, + 1667FAFE2E3F845D00F06E59 /* lanes_recommendation.cpp */, + ); + path = lanes; + sourceTree = ""; + }; + 1667FB0A2E3F847B00F06E59 /* lanes */ = { + isa = PBXGroup; + children = ( + 1667FB082E3F847B00F06E59 /* lanes_parser_tests.cpp */, + 1667FB092E3F847B00F06E59 /* lanes_recommendation_tests.cpp */, + ); + path = lanes; + sourceTree = ""; + }; 56F0D7611D896DAF00045886 /* Frameworks */ = { isa = PBXGroup; children = ( @@ -601,6 +645,7 @@ 6742ACA01C68A07C009CB89E /* routing_tests */ = { isa = PBXGroup; children = ( + 1667FB0A2E3F847B00F06E59 /* lanes */, 56CA09DE1E30E73B00D05C9A /* applying_traffic_test.cpp */, 6742ACA61C68A0B1009CB89E /* astar_algorithm_test.cpp */, 6742ACA71C68A0B1009CB89E /* astar_progress_test.cpp */, @@ -695,6 +740,7 @@ 675343FA1A3F640D00A0A8C3 /* routing */ = { isa = PBXGroup; children = ( + 1667FAFF2E3F845D00F06E59 /* lanes */, D549BEBA25765CFA009131F2 /* absent_regions_finder.hpp */, D549BEBC25765CFB009131F2 /* absent_regions_finder.cpp */, 44A95C6F225F6A4F00C22F4F /* astar_graph.hpp */, @@ -963,6 +1009,10 @@ 56C439291E93BF8C00998E29 /* cross_mwm_graph.hpp in Headers */, 44C56C0A22296498006C2A1D /* routing_options.hpp in Headers */, 4408A63C21F1E7F0008171B8 /* joint_segment.hpp in Headers */, + 1667FB002E3F845D00F06E59 /* lanes_parser.hpp in Headers */, + 1667FB012E3F845D00F06E59 /* lane_info.hpp in Headers */, + 1667FB022E3F845D00F06E59 /* lanes_recommendation.hpp in Headers */, + 1667FB032E3F845D00F06E59 /* lane_way.hpp in Headers */, 0C81E1541F02589800DC66DF /* traffic_stash.hpp in Headers */, 40A111D01F2F9704005E6AD5 /* astar_weight.hpp in Headers */, 0C8705051E0182F200BCAF71 /* route_point.hpp in Headers */, @@ -1212,6 +1262,8 @@ 6742AD271C68A9DF009CB89E /* astar_algorithm_test.cpp in Sources */, FAA838A726BB4B1D002E54C6 /* road_access_test.cpp in Sources */, 6742AD351C68A9DF009CB89E /* turns_generator_test.cpp in Sources */, + 1667FB0B2E3F847B00F06E59 /* lanes_recommendation_tests.cpp in Sources */, + 1667FB0C2E3F847B00F06E59 /* lanes_parser_tests.cpp in Sources */, FAA838AE26BB4B44002E54C6 /* fake_graph_test.cpp in Sources */, FAA838AA26BB4B28002E54C6 /* cumulative_restriction_test.cpp in Sources */, FAA8389E26BB4AE1002E54C6 /* opening_hours_serdes_tests.cpp in Sources */, @@ -1230,6 +1282,10 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 1667FB062E3F845D00F06E59 /* lane_way.cpp in Sources */, + 1667FB052E3F845D00F06E59 /* lanes_parser.cpp in Sources */, + 1667FB042E3F845D00F06E59 /* lane_info.cpp in Sources */, + 1667FB072E3F845D00F06E59 /* lanes_recommendation.cpp in Sources */, D5481E4F24BF4F70008FB1D8 /* mwm_hierarchy_handler.cpp in Sources */, 0C5FEC641DDE192A0017688C /* joint.cpp in Sources */, 0C090C871E4E276700D52AFD /* world_graph.cpp in Sources */,