mirror of
https://codeberg.org/comaps/comaps
synced 2025-12-19 13:03:36 +00:00
[routing] New API for lanes
Signed-off-by: Andrei Shkrob <github@shkrob.dev>
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
#include "LaneInfo.hpp"
|
||||
|
||||
#include "app/organicmaps/sdk/core/jni_helper.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
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<jsize>(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<jsize>(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;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include "routing/lanes/lane_info.hpp"
|
||||
|
||||
jobjectArray CreateLanesInfo(JNIEnv * env, routing::turns::lanes::LanesInfo const & lanes);
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "app/organicmaps/sdk/core/jni_helper.hpp"
|
||||
|
||||
#include "routing/following_info.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
jobjectArray CreateLanesInfo(JNIEnv * env, std::vector<routing::FollowingInfo::SingleLaneInfoClient> const & lanes)
|
||||
{
|
||||
if (lanes.empty())
|
||||
return nullptr;
|
||||
|
||||
static jclass const laneClass = jni::GetGlobalClassRef(env, "app/organicmaps/sdk/routing/SingleLaneInfo");
|
||||
auto const lanesSize = static_cast<jsize>(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<jsize>(lanes[j].m_lane.size());
|
||||
jni::TScopedLocalByteArrayRef singleLane(env, env->NewByteArray(laneSize));
|
||||
ASSERT(singleLane.get(), (jni::DescribeException()));
|
||||
env->SetByteArrayRegion(singleLane.get(), 0, laneSize, lanes[j].m_lane.data());
|
||||
|
||||
jni::TScopedLocalRef singleLaneInfo(
|
||||
env, env->NewObject(laneClass, ctorSingleLaneInfoID, singleLane.get(), lanes[j].m_isRecommended));
|
||||
ASSERT(singleLaneInfo.get(), (jni::DescribeException()));
|
||||
env->SetObjectArrayElement(jLanes, j, singleLaneInfo.get());
|
||||
}
|
||||
|
||||
return jLanes;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "routing/car_directions.hpp"
|
||||
|
||||
#include "routing/lanes/lanes_recommendation.hpp"
|
||||
#include "routing/turns.hpp"
|
||||
#include "routing/turns_generator.hpp"
|
||||
#include "routing/turns_generator_utils.hpp"
|
||||
@@ -83,7 +84,7 @@ void FixupCarTurns(vector<RouteSegment> & routeSegments)
|
||||
routeSegments[idx - 1].ClearTurn();
|
||||
}
|
||||
}
|
||||
SelectRecommendedLanes(routeSegments);
|
||||
turns::lanes::SelectRecommendedLanes(routeSegments);
|
||||
}
|
||||
|
||||
void GetTurnDirectionBasic(IRoutingResult const & result, size_t const outgoingSegmentIndex,
|
||||
@@ -596,73 +597,4 @@ size_t CheckUTurnOnRoute(IRoutingResult const & result, size_t const outgoingSeg
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool FixupLaneSet(CarDirection turn, vector<SingleLaneInfo> & lanes, bool (*checker)(LaneWay, CarDirection))
|
||||
{
|
||||
bool isLaneConformed = false;
|
||||
// There are two nested loops below. (There is a for-loop in checker.)
|
||||
// But the number of calls of the body of inner one (in checker) is relatively small.
|
||||
// Less than 10 in most cases.
|
||||
for (auto & singleLane : lanes)
|
||||
{
|
||||
for (LaneWay laneWay : singleLane.m_lane)
|
||||
{
|
||||
if (checker(laneWay, turn))
|
||||
{
|
||||
singleLane.m_isRecommended = true;
|
||||
isLaneConformed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return isLaneConformed;
|
||||
}
|
||||
|
||||
template <typename It>
|
||||
bool SelectFirstUnrestrictedLane(LaneWay direction, It lanesBegin, It lanesEnd)
|
||||
{
|
||||
It const firstUnrestricted = find_if(lanesBegin, lanesEnd, IsLaneUnrestricted);
|
||||
if (firstUnrestricted == lanesEnd)
|
||||
return false;
|
||||
|
||||
firstUnrestricted->m_isRecommended = true;
|
||||
firstUnrestricted->m_lane.insert(firstUnrestricted->m_lane.begin(), direction);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SelectUnrestrictedLane(CarDirection turn, vector<SingleLaneInfo> & lanes)
|
||||
{
|
||||
if (IsTurnMadeFromLeft(turn))
|
||||
return SelectFirstUnrestrictedLane(LaneWay::Left, lanes.begin(), lanes.end());
|
||||
else if (IsTurnMadeFromRight(turn))
|
||||
return SelectFirstUnrestrictedLane(LaneWay::Right, lanes.rbegin(), lanes.rend());
|
||||
return false;
|
||||
}
|
||||
|
||||
void SelectRecommendedLanes(vector<RouteSegment> & routeSegments)
|
||||
{
|
||||
for (auto & segment : routeSegments)
|
||||
{
|
||||
auto & t = segment.GetTurn();
|
||||
if (t.IsTurnNone() || t.m_lanes.empty())
|
||||
continue;
|
||||
auto & lanes = segment.GetTurnLanes();
|
||||
// Checking if there are elements in lanes which correspond with the turn exactly.
|
||||
// If so fixing up all the elements in lanes which correspond with the turn.
|
||||
if (FixupLaneSet(t.m_turn, lanes, &IsLaneWayConformedTurnDirection))
|
||||
continue;
|
||||
// If not checking if there are elements in lanes which corresponds with the turn
|
||||
// approximately. If so fixing up all these elements.
|
||||
if (FixupLaneSet(t.m_turn, lanes, &IsLaneWayConformedTurnDirectionApproximately))
|
||||
continue;
|
||||
// If not, check if there is an unrestricted lane which could correspond to the
|
||||
// turn. If so, fix up that lane.
|
||||
if (SelectUnrestrictedLane(t.m_turn, lanes))
|
||||
continue;
|
||||
// Otherwise, we don't have lane recommendations for the user, so we don't
|
||||
// want to send the lane data any further.
|
||||
segment.ClearTurnLanes();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace routing
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include "routing/route.hpp"
|
||||
#include "routing_common/num_mwm_id.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
@@ -24,11 +23,6 @@ protected:
|
||||
virtual void FixupTurns(std::vector<RouteSegment> & routeSegments);
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief Selects lanes which are recommended for an end user.
|
||||
*/
|
||||
void SelectRecommendedLanes(std::vector<RouteSegment> & routeSegments);
|
||||
|
||||
void FixupCarTurns(std::vector<RouteSegment> & routeSegments);
|
||||
|
||||
/*!
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "geometry/latlon.hpp"
|
||||
|
||||
#include "platform/distance.hpp"
|
||||
|
||||
#include "routing/lanes/lane_info.hpp"
|
||||
#include "routing/turns.hpp"
|
||||
#include "routing/turns_sound_settings.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
@@ -26,23 +24,6 @@ public:
|
||||
, m_pedestrianTurn(turns::PedestrianDirection::None)
|
||||
{}
|
||||
|
||||
// SingleLaneInfoClient is used for passing information about a lane to client platforms such as
|
||||
// Android, iOS and so on.
|
||||
struct SingleLaneInfoClient
|
||||
{
|
||||
std::vector<int8_t> m_lane; // Possible directions for the lane.
|
||||
bool m_isRecommended; // m_isRecommended is true if the lane is recommended for a user.
|
||||
|
||||
explicit SingleLaneInfoClient(turns::SingleLaneInfo const & singleLaneInfo)
|
||||
: m_isRecommended(singleLaneInfo.m_isRecommended)
|
||||
{
|
||||
turns::TSingleLane const & lane = singleLaneInfo.m_lane;
|
||||
m_lane.resize(lane.size());
|
||||
std::transform(lane.cbegin(), lane.cend(), m_lane.begin(),
|
||||
[](turns::LaneWay l) { return static_cast<int8_t>(l); });
|
||||
}
|
||||
};
|
||||
|
||||
bool IsValid() const { return m_distToTarget.IsValid(); }
|
||||
|
||||
/// @name Formatted covered distance.
|
||||
@@ -57,8 +38,8 @@ public:
|
||||
uint32_t m_exitNum;
|
||||
//@}
|
||||
int m_time;
|
||||
// m_lanes contains lane information on the edge before the turn.
|
||||
std::vector<SingleLaneInfoClient> m_lanes;
|
||||
/// Contains lane information on the edge before the turn.
|
||||
turns::lanes::LanesInfo m_lanes;
|
||||
// m_turnNotifications contains information about the next turn notifications.
|
||||
// If there is nothing to pronounce m_turnNotifications is empty.
|
||||
// If there is something to pronounce the size of m_turnNotifications may be one or even more
|
||||
|
||||
24
libs/routing/lanes/lane_info.cpp
Normal file
24
libs/routing/lanes/lane_info.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#include "lane_info.hpp"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
namespace routing::turns::lanes
|
||||
{
|
||||
std::string DebugPrint(LaneInfo const & laneInfo)
|
||||
{
|
||||
std::stringstream out;
|
||||
out << "LaneInfo{" << DebugPrint(laneInfo.laneWays) << ", recommendedWay: " << DebugPrint(laneInfo.recommendedWay)
|
||||
<< "}";
|
||||
return out.str();
|
||||
}
|
||||
|
||||
std::string DebugPrint(LanesInfo const & lanesInfo)
|
||||
{
|
||||
std::stringstream out;
|
||||
out << "LanesInfo[";
|
||||
for (auto const & laneInfo : lanesInfo)
|
||||
out << DebugPrint(laneInfo) << ", ";
|
||||
out << "]";
|
||||
return out.str();
|
||||
}
|
||||
} // namespace routing::turns::lanes
|
||||
23
libs/routing/lanes/lane_info.hpp
Normal file
23
libs/routing/lanes/lane_info.hpp
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "routing/lanes/lane_way.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace routing::turns::lanes
|
||||
{
|
||||
struct LaneInfo
|
||||
{
|
||||
LaneWays laneWays;
|
||||
LaneWay recommendedWay = LaneWay::None;
|
||||
|
||||
bool operator==(LaneInfo const & rhs) const
|
||||
{
|
||||
return laneWays == rhs.laneWays && recommendedWay == rhs.recommendedWay;
|
||||
}
|
||||
};
|
||||
using LanesInfo = std::vector<LaneInfo>;
|
||||
|
||||
std::string DebugPrint(LaneInfo const & laneInfo);
|
||||
std::string DebugPrint(LanesInfo const & lanesInfo);
|
||||
} // namespace routing::turns::lanes
|
||||
50
libs/routing/lanes/lane_way.cpp
Normal file
50
libs/routing/lanes/lane_way.cpp
Normal file
@@ -0,0 +1,50 @@
|
||||
#include "lane_way.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
|
||||
namespace routing::turns::lanes
|
||||
{
|
||||
std::string DebugPrint(LaneWay const laneWay)
|
||||
{
|
||||
using enum LaneWay;
|
||||
switch (laneWay)
|
||||
{
|
||||
case None: return "None";
|
||||
case ReverseLeft: return "ReverseLeft";
|
||||
case SharpLeft: return "SharpLeft";
|
||||
case Left: return "Left";
|
||||
case MergeToLeft: return "MergeToLeft";
|
||||
case SlightLeft: return "SlightLeft";
|
||||
case Through: return "Through";
|
||||
case SlightRight: return "SlightRight";
|
||||
case MergeToRight: return "MergeToRight";
|
||||
case Right: return "Right";
|
||||
case SharpRight: return "SharpRight";
|
||||
case ReverseRight: return "ReverseRight";
|
||||
case Count: return "Count";
|
||||
default:
|
||||
ASSERT_FAIL("Unsupported value: " + std::to_string(static_cast<std::uint8_t>(laneWay)));
|
||||
return "Unsupported";
|
||||
}
|
||||
}
|
||||
|
||||
std::string DebugPrint(LaneWays const & laneWays)
|
||||
{
|
||||
std::stringstream out;
|
||||
out << "LaneWays: [";
|
||||
std::uint8_t const waysCount = laneWays.m_laneWays.count();
|
||||
std::uint8_t waysPrinted = 0;
|
||||
for (std::size_t i = 0; i < laneWays.m_laneWays.size(); ++i)
|
||||
{
|
||||
if (laneWays.m_laneWays.test(i))
|
||||
{
|
||||
out << DebugPrint(static_cast<LaneWay>(i));
|
||||
if (waysPrinted < waysCount - 1)
|
||||
out << ", ";
|
||||
waysPrinted++;
|
||||
}
|
||||
}
|
||||
out << "]";
|
||||
return out.str();
|
||||
}
|
||||
} // namespace routing::turns::lanes
|
||||
84
libs/routing/lanes/lane_way.hpp
Normal file
84
libs/routing/lanes/lane_way.hpp
Normal file
@@ -0,0 +1,84 @@
|
||||
#pragma once
|
||||
|
||||
#include "base/assert.hpp"
|
||||
|
||||
#include <bitset>
|
||||
#include <initializer_list>
|
||||
#include <string>
|
||||
|
||||
namespace routing::turns::lanes
|
||||
{
|
||||
enum class LaneWay : std::uint8_t
|
||||
{
|
||||
None = 0,
|
||||
ReverseLeft,
|
||||
SharpLeft,
|
||||
Left,
|
||||
MergeToLeft,
|
||||
SlightLeft,
|
||||
Through,
|
||||
SlightRight,
|
||||
MergeToRight,
|
||||
Right,
|
||||
SharpRight,
|
||||
ReverseRight,
|
||||
|
||||
Count
|
||||
};
|
||||
|
||||
class LaneWays
|
||||
{
|
||||
using LaneWaysT = std::bitset<static_cast<std::uint8_t>(LaneWay::Count)>;
|
||||
|
||||
friend std::string DebugPrint(LaneWays const & laneWays);
|
||||
|
||||
public:
|
||||
constexpr LaneWays() = default;
|
||||
constexpr LaneWays(std::initializer_list<LaneWay> const laneWays)
|
||||
{
|
||||
for (auto const & laneWay : laneWays)
|
||||
Add(laneWay);
|
||||
}
|
||||
|
||||
constexpr bool operator==(LaneWays const & rhs) const { return m_laneWays == rhs.m_laneWays; }
|
||||
|
||||
constexpr void Add(LaneWay laneWay)
|
||||
{
|
||||
ASSERT_LESS(laneWay, LaneWay::Count, ());
|
||||
m_laneWays.set(static_cast<std::uint8_t>(laneWay));
|
||||
}
|
||||
|
||||
constexpr void Remove(LaneWay laneWay)
|
||||
{
|
||||
ASSERT_LESS(laneWay, LaneWay::Count, ());
|
||||
m_laneWays.reset(static_cast<std::uint8_t>(laneWay));
|
||||
}
|
||||
|
||||
constexpr bool Contains(LaneWay laneWay) const
|
||||
{
|
||||
ASSERT_LESS(laneWay, LaneWay::Count, ());
|
||||
return m_laneWays.test(static_cast<std::uint8_t>(laneWay));
|
||||
}
|
||||
|
||||
/// An unrestricted lane is a lane that has no restrictions, i.e., it contains no lane ways.
|
||||
constexpr bool IsUnrestricted() const
|
||||
{
|
||||
return m_laneWays.none() || (m_laneWays.count() == 1 && Contains(LaneWay::None));
|
||||
}
|
||||
|
||||
[[nodiscard]] std::vector<LaneWay> GetActiveLaneWays() const
|
||||
{
|
||||
std::vector<LaneWay> result;
|
||||
for (std::size_t i = 0; i < m_laneWays.size(); ++i)
|
||||
if (m_laneWays.test(i))
|
||||
result.emplace_back(static_cast<LaneWay>(i));
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
LaneWaysT m_laneWays;
|
||||
};
|
||||
|
||||
std::string DebugPrint(LaneWay laneWay);
|
||||
std::string DebugPrint(LaneWays const & laneWays);
|
||||
} // namespace routing::turns::lanes
|
||||
84
libs/routing/lanes/lanes_parser.cpp
Normal file
84
libs/routing/lanes/lanes_parser.cpp
Normal file
@@ -0,0 +1,84 @@
|
||||
#include "lanes_parser.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <ranges>
|
||||
|
||||
namespace routing::turns::lanes
|
||||
{
|
||||
namespace
|
||||
{
|
||||
std::uint8_t constexpr kLaneWayNamesCount = static_cast<std::uint8_t>(LaneWay::Count) + 4;
|
||||
|
||||
/**
|
||||
* The order is important. Starting with the most frequent tokens according to
|
||||
* taginfo.openstreetmap.org we minimize the number of the comparisons in ParseSingleLane().
|
||||
*
|
||||
* A `none` lane can be represented either as "none" or as "". That means both "none" and ""
|
||||
* should be considered names, even though they refer to the same thing. As a result,
|
||||
* `LaneWay::None` appears twice in this array, which is one longer than the number of
|
||||
* enum values.
|
||||
*/
|
||||
std::array<std::pair<LaneWay, std::string_view>, kLaneWayNamesCount> constexpr g_laneWayNames{{
|
||||
{LaneWay::None, ""},
|
||||
{LaneWay::Through, "through"},
|
||||
{LaneWay::Left, "left"},
|
||||
{LaneWay::Right, "right"},
|
||||
{LaneWay::None, "none"},
|
||||
{LaneWay::SharpLeft, "sharp_left"},
|
||||
{LaneWay::SlightLeft, "slight_left"},
|
||||
{LaneWay::MergeToRight, "merge_to_right"},
|
||||
{LaneWay::MergeToLeft, "merge_to_left"},
|
||||
{LaneWay::SlightRight, "slight_right"},
|
||||
{LaneWay::SharpRight, "sharp_right"},
|
||||
{LaneWay::ReverseLeft, "reverse"},
|
||||
{LaneWay::Right,
|
||||
"next_right"}, // "next_right" means "turn right, not in the first intersection but the one after that".
|
||||
{LaneWay::Through, "slide_left"}, // "slide_left" means "move a bit left within the lane".
|
||||
{LaneWay::Through, "slide_right"} // "slide_right" means "move a bit right within the lane".
|
||||
}};
|
||||
|
||||
bool ParseSingleLane(auto && laneWayRange, LaneWay & laneWay)
|
||||
{
|
||||
auto const it = std::ranges::find_if(
|
||||
g_laneWayNames, [&laneWayRange](auto const & pair) { return std::ranges::equal(laneWayRange, pair.second); });
|
||||
if (it != g_laneWayNames.end())
|
||||
{
|
||||
laneWay = it->first;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
LanesInfo ParseLanes(std::string_view lanesString)
|
||||
{
|
||||
if (lanesString.empty())
|
||||
return {};
|
||||
|
||||
LanesInfo lanes;
|
||||
for (auto && laneInfo : lanesString | std::views::split('|'))
|
||||
{
|
||||
LaneInfo lane;
|
||||
if (std::ranges::empty(laneInfo))
|
||||
lane.laneWays.Add(LaneWay::None);
|
||||
else
|
||||
{
|
||||
for (auto && laneWay : laneInfo | std::views::split(';'))
|
||||
{
|
||||
auto way = LaneWay::None;
|
||||
auto && laneWayProcessed = laneWay | std::views::filter([](char const c) { return !std::isspace(c); }) |
|
||||
std::views::transform([](char const c) { return std::tolower(c); });
|
||||
if (!ParseSingleLane(laneWayProcessed, way))
|
||||
return {};
|
||||
lane.laneWays.Add(way);
|
||||
if (way == LaneWay::ReverseLeft)
|
||||
lane.laneWays.Add(LaneWay::ReverseRight);
|
||||
}
|
||||
}
|
||||
|
||||
lanes.push_back(lane);
|
||||
}
|
||||
return lanes;
|
||||
}
|
||||
} // namespace routing::turns::lanes
|
||||
16
libs/routing/lanes/lanes_parser.hpp
Normal file
16
libs/routing/lanes/lanes_parser.hpp
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include "routing/lanes/lane_info.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace routing::turns::lanes
|
||||
{
|
||||
/**
|
||||
* Parse lane information which comes from lanesString
|
||||
* @param lanesString lane information. Example through|through|through|through;right
|
||||
* @return LanesInfo. @see LanesInfo
|
||||
* @note if lanesString is empty, returns empty LanesInfo.
|
||||
*/
|
||||
LanesInfo ParseLanes(std::string_view lanesString);
|
||||
} // namespace routing::turns::lanes
|
||||
129
libs/routing/lanes/lanes_recommendation.cpp
Normal file
129
libs/routing/lanes/lanes_recommendation.cpp
Normal file
@@ -0,0 +1,129 @@
|
||||
#include "lanes_recommendation.hpp"
|
||||
|
||||
#include "routing/route.hpp"
|
||||
|
||||
namespace routing::turns::lanes
|
||||
{
|
||||
namespace
|
||||
{
|
||||
void FixRecommendedReverseLane(LaneWays & ways, LaneWay const recommendedWay)
|
||||
{
|
||||
if (recommendedWay == LaneWay::ReverseLeft)
|
||||
ways.Remove(LaneWay::ReverseRight);
|
||||
else if (recommendedWay == LaneWay::ReverseRight)
|
||||
ways.Remove(LaneWay::ReverseLeft);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void SelectRecommendedLanes(std::vector<RouteSegment> & routeSegments)
|
||||
{
|
||||
for (auto & segment : routeSegments)
|
||||
{
|
||||
auto & t = segment.GetTurn();
|
||||
if (t.IsTurnNone() || t.m_lanes.empty())
|
||||
continue;
|
||||
auto & lanesInfo = segment.GetTurnLanes();
|
||||
// Check if there are elements in lanesInfo that correspond with the turn exactly.
|
||||
// If so, fix up all the elements in lanesInfo that correspond with the turn.
|
||||
if (impl::SetRecommendedLaneWays(t.m_turn, lanesInfo))
|
||||
continue;
|
||||
// If not, check if there are elements in lanesInfo that correspond with the turn
|
||||
// approximately. If so, fix up all those elements.
|
||||
if (impl::SetRecommendedLaneWaysApproximately(t.m_turn, lanesInfo))
|
||||
continue;
|
||||
// If not, check if there is an unrestricted lane that could correspond to the
|
||||
// turn. If so, fix up that lane.
|
||||
if (impl::SetUnrestrictedLaneAsRecommended(t.m_turn, lanesInfo))
|
||||
continue;
|
||||
// Otherwise, we don't have lane recommendations for the user, so we don't
|
||||
// want to send the lane data any further.
|
||||
segment.ClearTurnLanes();
|
||||
}
|
||||
}
|
||||
|
||||
bool impl::SetRecommendedLaneWays(CarDirection const carDirection, LanesInfo & lanesInfo)
|
||||
{
|
||||
LaneWay laneWay;
|
||||
switch (carDirection)
|
||||
{
|
||||
case CarDirection::GoStraight: laneWay = LaneWay::Through; break;
|
||||
case CarDirection::TurnRight: laneWay = LaneWay::Right; break;
|
||||
case CarDirection::TurnSharpRight: laneWay = LaneWay::SharpRight; break;
|
||||
case CarDirection::TurnSlightRight: [[fallthrough]];
|
||||
case CarDirection::ExitHighwayToRight: laneWay = LaneWay::SlightRight; break;
|
||||
case CarDirection::TurnLeft: laneWay = LaneWay::Left; break;
|
||||
case CarDirection::TurnSharpLeft: laneWay = LaneWay::SharpLeft; break;
|
||||
case CarDirection::TurnSlightLeft: [[fallthrough]];
|
||||
case CarDirection::ExitHighwayToLeft: laneWay = LaneWay::SlightLeft; break;
|
||||
case CarDirection::UTurnLeft: laneWay = LaneWay::ReverseLeft; break;
|
||||
case CarDirection::UTurnRight: laneWay = LaneWay::ReverseRight; break;
|
||||
default: return false;
|
||||
}
|
||||
|
||||
bool isLaneConformed = false;
|
||||
for (auto & [laneWays, recommendedWay] : lanesInfo)
|
||||
{
|
||||
if (laneWays.Contains(laneWay))
|
||||
{
|
||||
recommendedWay = laneWay;
|
||||
isLaneConformed = true;
|
||||
}
|
||||
FixRecommendedReverseLane(laneWays, recommendedWay);
|
||||
}
|
||||
return isLaneConformed;
|
||||
}
|
||||
|
||||
bool impl::SetRecommendedLaneWaysApproximately(CarDirection const carDirection, LanesInfo & lanesInfo)
|
||||
{
|
||||
std::vector<LaneWay> approximateLaneWays;
|
||||
switch (carDirection)
|
||||
{
|
||||
case CarDirection::UTurnLeft: approximateLaneWays = {LaneWay::SharpLeft}; break;
|
||||
case CarDirection::TurnSharpLeft: approximateLaneWays = {LaneWay::Left}; break;
|
||||
case CarDirection::TurnLeft: approximateLaneWays = {LaneWay::SlightLeft, LaneWay::SharpLeft}; break;
|
||||
case CarDirection::TurnSlightLeft: [[fallthrough]];
|
||||
case CarDirection::ExitHighwayToLeft: approximateLaneWays = {LaneWay::Left}; break;
|
||||
case CarDirection::GoStraight: approximateLaneWays = {LaneWay::SlightRight, LaneWay::SlightLeft}; break;
|
||||
case CarDirection::ExitHighwayToRight: [[fallthrough]];
|
||||
case CarDirection::TurnSlightRight: approximateLaneWays = {LaneWay::Right}; break;
|
||||
case CarDirection::TurnRight: approximateLaneWays = {LaneWay::SlightRight, LaneWay::SharpRight}; break;
|
||||
case CarDirection::TurnSharpRight: approximateLaneWays = {LaneWay::Right}; break;
|
||||
case CarDirection::UTurnRight: approximateLaneWays = {LaneWay::SharpRight}; break;
|
||||
default: return false;
|
||||
}
|
||||
|
||||
bool isLaneConformed = false;
|
||||
for (auto & [laneWays, recommendedWay] : lanesInfo)
|
||||
{
|
||||
for (auto const & laneWay : approximateLaneWays)
|
||||
{
|
||||
if (laneWays.Contains(laneWay))
|
||||
{
|
||||
recommendedWay = laneWay;
|
||||
isLaneConformed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return isLaneConformed;
|
||||
}
|
||||
|
||||
bool impl::SetUnrestrictedLaneAsRecommended(CarDirection const carDirection, LanesInfo & lanesInfo)
|
||||
{
|
||||
static auto constexpr setFirstUnrestrictedLane = [](LaneWay const laneWay, auto beginIt, auto endIt)
|
||||
{
|
||||
auto it = std::find_if(beginIt, endIt, [](auto const & laneInfo) { return laneInfo.laneWays.IsUnrestricted(); });
|
||||
if (it == endIt)
|
||||
return false;
|
||||
it->recommendedWay = laneWay;
|
||||
return true;
|
||||
};
|
||||
|
||||
if (IsTurnMadeFromLeft(carDirection))
|
||||
return setFirstUnrestrictedLane(LaneWay::Left, lanesInfo.begin(), lanesInfo.end());
|
||||
if (IsTurnMadeFromRight(carDirection))
|
||||
return setFirstUnrestrictedLane(LaneWay::Right, lanesInfo.rbegin(), lanesInfo.rend());
|
||||
return false;
|
||||
}
|
||||
} // namespace routing::turns::lanes
|
||||
31
libs/routing/lanes/lanes_recommendation.hpp
Normal file
31
libs/routing/lanes/lanes_recommendation.hpp
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "routing/lanes/lane_info.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace routing
|
||||
{
|
||||
class RouteSegment;
|
||||
|
||||
namespace turns
|
||||
{
|
||||
enum class CarDirection;
|
||||
} // namespace turns
|
||||
} // namespace routing
|
||||
|
||||
namespace routing::turns::lanes
|
||||
{
|
||||
/// Selects lanes which are recommended for an end user.
|
||||
void SelectRecommendedLanes(std::vector<RouteSegment> & routeSegments);
|
||||
|
||||
// Keep signatures in the header for testing purposes
|
||||
namespace impl
|
||||
{
|
||||
bool SetRecommendedLaneWays(CarDirection carDirection, LanesInfo & lanesInfo);
|
||||
|
||||
bool SetRecommendedLaneWaysApproximately(CarDirection carDirection, LanesInfo & lanesInfo);
|
||||
|
||||
bool SetUnrestrictedLaneAsRecommended(CarDirection carDirection, LanesInfo & lanesInfo);
|
||||
} // namespace impl
|
||||
} // namespace routing::turns::lanes
|
||||
@@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "routing/maxspeeds.hpp"
|
||||
#include "routing/road_point.hpp"
|
||||
#include "routing/lanes/lane_info.hpp"
|
||||
#include "routing/route.hpp"
|
||||
#include "routing/segment.hpp"
|
||||
#include "routing/turns.hpp"
|
||||
@@ -10,7 +9,6 @@
|
||||
|
||||
#include "geometry/point_with_altitude.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace routing
|
||||
@@ -23,7 +21,7 @@ namespace routing
|
||||
struct LoadedPathSegment
|
||||
{
|
||||
std::vector<geometry::PointWithAltitude> m_path;
|
||||
std::vector<turns::SingleLaneInfo> m_lanes;
|
||||
turns::lanes::LanesInfo m_lanes;
|
||||
RouteSegment::RoadNameInfo m_roadNameInfo;
|
||||
// double m_weight = 0.0; /*!< Time in seconds to pass the segment. */
|
||||
SegmentRange m_segmentRange;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "routing/lanes/lane_info.hpp"
|
||||
#include "routing/routing_options.hpp"
|
||||
#include "routing/routing_settings.hpp"
|
||||
#include "routing/segment.hpp"
|
||||
@@ -139,7 +140,7 @@ public:
|
||||
|
||||
void SetTurnExits(uint32_t exitNum) { m_turn.m_exitNum = exitNum; }
|
||||
|
||||
std::vector<turns::SingleLaneInfo> & GetTurnLanes() { return m_turn.m_lanes; }
|
||||
turns::lanes::LanesInfo & GetTurnLanes() { return m_turn.m_lanes; }
|
||||
|
||||
void SetDistancesAndTime(double distFromBeginningMeters, double distFromBeginningMerc, double timeFromBeginningS)
|
||||
{
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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
|
||||
|
||||
160
libs/routing/routing_tests/lanes/lanes_parser_tests.cpp
Normal file
160
libs/routing/routing_tests/lanes/lanes_parser_tests.cpp
Normal file
@@ -0,0 +1,160 @@
|
||||
#include "testing/testing.hpp"
|
||||
|
||||
#include "routing/lanes/lanes_parser.hpp"
|
||||
|
||||
namespace routing::turns::lanes::test
|
||||
{
|
||||
UNIT_TEST(TestParseLaneWays)
|
||||
{
|
||||
std::vector<std::pair<std::string, LaneWays>> const testData = {
|
||||
{";", {LaneWay::None}},
|
||||
{"none", {LaneWay::None}},
|
||||
{"left", {LaneWay::Left}},
|
||||
{"right", {LaneWay::Right}},
|
||||
{"sharp_left", {LaneWay::SharpLeft}},
|
||||
{"slight_left", {LaneWay::SlightLeft}},
|
||||
{"merge_to_right", {LaneWay::MergeToRight}},
|
||||
{"merge_to_left", {LaneWay::MergeToLeft}},
|
||||
{"slight_right", {LaneWay::SlightRight}},
|
||||
{"sharp_right", {LaneWay::SharpRight}},
|
||||
{"reverse", {LaneWay::ReverseLeft, LaneWay::ReverseRight}},
|
||||
{"next_right", {LaneWay::Right}},
|
||||
{"slide_left", {LaneWay::Through}},
|
||||
{"slide_right", {LaneWay::Through}},
|
||||
};
|
||||
|
||||
for (auto const & [in, out] : testData)
|
||||
{
|
||||
LanesInfo const result = ParseLanes(in);
|
||||
LaneWays const expected = {out};
|
||||
TEST_EQUAL(result.front().laneWays, expected, ());
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(TestParseSingleLane)
|
||||
{
|
||||
{
|
||||
LanesInfo const result = ParseLanes("through;right");
|
||||
LaneWays constexpr expected = {LaneWay::Through, LaneWay::Right};
|
||||
TEST_EQUAL(result.front().laneWays, expected, ());
|
||||
}
|
||||
|
||||
{
|
||||
LanesInfo const result = ParseLanes("through;Right");
|
||||
LaneWays constexpr expected = {LaneWay::Through, LaneWay::Right};
|
||||
TEST_EQUAL(result.front().laneWays, expected, ());
|
||||
}
|
||||
|
||||
{
|
||||
LanesInfo const result = ParseLanes("through ;Right");
|
||||
LaneWays constexpr expected = {LaneWay::Through, LaneWay::Right};
|
||||
TEST_EQUAL(result.front().laneWays, expected, ());
|
||||
}
|
||||
|
||||
{
|
||||
LanesInfo const result = ParseLanes("left;through");
|
||||
LaneWays constexpr expected = {LaneWay::Left, LaneWay::Through};
|
||||
TEST_EQUAL(result.front().laneWays, expected, ());
|
||||
}
|
||||
|
||||
{
|
||||
LanesInfo const result = ParseLanes("left");
|
||||
LaneWays constexpr expected = {LaneWay::Left};
|
||||
TEST_EQUAL(result.front().laneWays, expected, ());
|
||||
}
|
||||
|
||||
{
|
||||
LanesInfo const result = ParseLanes("left;");
|
||||
LaneWays constexpr expected = {LaneWay::Left, LaneWay::None};
|
||||
TEST_EQUAL(result.front().laneWays, expected, ());
|
||||
}
|
||||
|
||||
{
|
||||
LanesInfo const result = ParseLanes(";");
|
||||
LaneWays constexpr expected = {LaneWay::None};
|
||||
TEST_EQUAL(result.front().laneWays, expected, ());
|
||||
}
|
||||
|
||||
TEST_EQUAL(ParseLanes("SD32kk*887;;").empty(), true, ());
|
||||
TEST_EQUAL(ParseLanes("Что-то на кириллице").empty(), true, ());
|
||||
TEST_EQUAL(ParseLanes("משהו בעברית").empty(), true, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(TestParseLanes)
|
||||
{
|
||||
{
|
||||
LanesInfo const result = ParseLanes("through|through|through|through;right");
|
||||
LanesInfo const expected = {
|
||||
{{LaneWay::Through}}, {{LaneWay::Through}}, {{LaneWay::Through}}, {{LaneWay::Through, LaneWay::Right}}};
|
||||
TEST_EQUAL(result, expected, ());
|
||||
}
|
||||
|
||||
{
|
||||
LanesInfo const result = ParseLanes("left|left;through|through|through");
|
||||
LanesInfo const expected = {
|
||||
{{LaneWay::Left}}, {{LaneWay::Left, LaneWay::Through}}, {{LaneWay::Through}}, {{LaneWay::Through}}};
|
||||
TEST_EQUAL(result, expected, ());
|
||||
}
|
||||
|
||||
{
|
||||
LanesInfo const result = ParseLanes("left|through|through");
|
||||
LanesInfo const expected = {{{LaneWay::Left}}, {{LaneWay::Through}}, {{LaneWay::Through}}};
|
||||
TEST_EQUAL(result, expected, ());
|
||||
}
|
||||
|
||||
{
|
||||
LanesInfo const result = ParseLanes("left|le ft| through|through | right");
|
||||
LanesInfo const expected = {
|
||||
{{LaneWay::Left}}, {{LaneWay::Left}}, {{LaneWay::Through}}, {{LaneWay::Through}}, {{LaneWay::Right}}};
|
||||
TEST_EQUAL(result, expected, ());
|
||||
}
|
||||
|
||||
{
|
||||
LanesInfo const result = ParseLanes("left|Left|through|througH|right");
|
||||
LanesInfo const expected = {
|
||||
{{LaneWay::Left}}, {{LaneWay::Left}}, {{LaneWay::Through}}, {{LaneWay::Through}}, {{LaneWay::Right}}};
|
||||
TEST_EQUAL(result, expected, ());
|
||||
}
|
||||
|
||||
{
|
||||
LanesInfo const result = ParseLanes("left|Left|through|througH|through;right;sharp_rIght");
|
||||
LanesInfo const expected = {{{LaneWay::Left}},
|
||||
{{LaneWay::Left}},
|
||||
{{LaneWay::Through}},
|
||||
{{LaneWay::Through}},
|
||||
{{LaneWay::Through, LaneWay::Right, LaneWay::SharpRight}}};
|
||||
TEST_EQUAL(result, expected, ());
|
||||
}
|
||||
|
||||
{
|
||||
LanesInfo const result = ParseLanes("left |Left|through|througH|right");
|
||||
LanesInfo const expected = {
|
||||
{{LaneWay::Left}}, {{LaneWay::Left}}, {{LaneWay::Through}}, {{LaneWay::Through}}, {{LaneWay::Right}}};
|
||||
TEST_EQUAL(result, expected, ());
|
||||
}
|
||||
|
||||
{
|
||||
LanesInfo const result = ParseLanes("|||||slight_right");
|
||||
LanesInfo const expected = {{{LaneWay::None}}, {{LaneWay::None}}, {{LaneWay::None}},
|
||||
{{LaneWay::None}}, {{LaneWay::None}}, {{LaneWay::SlightRight}}};
|
||||
TEST_EQUAL(result, expected, ());
|
||||
}
|
||||
|
||||
{
|
||||
LanesInfo const result = ParseLanes("|");
|
||||
LanesInfo const expected = {{{LaneWay::None}}, {{LaneWay::None}}};
|
||||
TEST_EQUAL(result, expected, ());
|
||||
}
|
||||
|
||||
{
|
||||
LanesInfo const result = ParseLanes(";|;;;");
|
||||
LanesInfo const expected = {{{LaneWay::None}}, {{LaneWay::None}}};
|
||||
TEST_EQUAL(result, expected, ());
|
||||
}
|
||||
|
||||
{
|
||||
LanesInfo const result = ParseLanes("left|Leftt|through|througH|right");
|
||||
TEST_EQUAL(result.empty(), true, ());
|
||||
}
|
||||
}
|
||||
} // namespace routing::turns::lanes::test
|
||||
221
libs/routing/routing_tests/lanes/lanes_recommendation_tests.cpp
Normal file
221
libs/routing/routing_tests/lanes/lanes_recommendation_tests.cpp
Normal file
@@ -0,0 +1,221 @@
|
||||
#include "routing/turns.hpp"
|
||||
#include "testing/testing.hpp"
|
||||
|
||||
#include "routing/lanes/lanes_recommendation.hpp"
|
||||
#include "routing/routing_tests/tools.hpp"
|
||||
|
||||
namespace routing::turns::lanes::test
|
||||
{
|
||||
UNIT_TEST(TestSetRecommendedLaneWays_Smoke)
|
||||
{
|
||||
using impl::SetRecommendedLaneWays;
|
||||
|
||||
struct CarDirectionToLaneWayMapping
|
||||
{
|
||||
CarDirection carDirection;
|
||||
LaneWay laneWay;
|
||||
bool shouldBeRecommended;
|
||||
};
|
||||
std::vector<CarDirectionToLaneWayMapping> const testData = {
|
||||
{CarDirection::GoStraight, LaneWay::Through, true},
|
||||
{CarDirection::TurnRight, LaneWay::Right, true},
|
||||
{CarDirection::TurnSharpRight, LaneWay::SharpRight, true},
|
||||
{CarDirection::TurnSlightRight, LaneWay::SlightRight, true},
|
||||
{CarDirection::TurnLeft, LaneWay::Left, true},
|
||||
{CarDirection::TurnSharpLeft, LaneWay::SharpLeft, true},
|
||||
{CarDirection::TurnSlightLeft, LaneWay::SlightLeft, true},
|
||||
{CarDirection::UTurnLeft, LaneWay::ReverseLeft, true},
|
||||
{CarDirection::UTurnRight, LaneWay::ReverseRight, true},
|
||||
{CarDirection::ExitHighwayToLeft, LaneWay::SlightLeft, true},
|
||||
{CarDirection::ExitHighwayToRight, LaneWay::SlightRight, true},
|
||||
// We do not recommend any lane way for these directions
|
||||
{CarDirection::None, LaneWay::None, false},
|
||||
{CarDirection::EnterRoundAbout, LaneWay::None, false},
|
||||
{CarDirection::LeaveRoundAbout, LaneWay::None, false},
|
||||
{CarDirection::StayOnRoundAbout, LaneWay::None, false},
|
||||
{CarDirection::StartAtEndOfStreet, LaneWay::None, false},
|
||||
{CarDirection::ReachedYourDestination, LaneWay::None, false},
|
||||
};
|
||||
TEST_EQUAL(testData.size(), static_cast<size_t>(CarDirection::Count), ("Not all CarDirection values are covered."));
|
||||
for (auto const & [carDirection, laneWay, shouldBeRecommended] : testData)
|
||||
{
|
||||
LanesInfo lanesInfo = {{{laneWay}}};
|
||||
bool const isRecommended = SetRecommendedLaneWays(carDirection, lanesInfo);
|
||||
TEST_EQUAL(isRecommended, shouldBeRecommended,
|
||||
("CarDirection:", DebugPrint(carDirection), "LaneWay:", DebugPrint(laneWay)));
|
||||
TEST_EQUAL(lanesInfo[0].recommendedWay, shouldBeRecommended ? laneWay : LaneWay::None, ());
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(TestSetRecommendedLaneWays)
|
||||
{
|
||||
{
|
||||
LanesInfo lanesInfo = {
|
||||
{{LaneWay::ReverseLeft, LaneWay::Left, LaneWay::Through}},
|
||||
{{LaneWay::Through}},
|
||||
{{LaneWay::Through}},
|
||||
{{LaneWay::Through, LaneWay::Right}},
|
||||
{{LaneWay::Right}},
|
||||
};
|
||||
TEST(impl::SetRecommendedLaneWays(CarDirection::GoStraight, lanesInfo), ());
|
||||
TEST_EQUAL(lanesInfo[0].recommendedWay, LaneWay::Through, ());
|
||||
TEST_EQUAL(lanesInfo[1].recommendedWay, LaneWay::Through, ());
|
||||
TEST_EQUAL(lanesInfo[2].recommendedWay, LaneWay::Through, ());
|
||||
TEST_EQUAL(lanesInfo[3].recommendedWay, LaneWay::Through, ());
|
||||
TEST_EQUAL(lanesInfo[4].recommendedWay, LaneWay::None, ());
|
||||
}
|
||||
{
|
||||
LanesInfo lanesInfo = {
|
||||
{{LaneWay::ReverseLeft, LaneWay::Left}},
|
||||
{{LaneWay::Right}},
|
||||
};
|
||||
TEST(!impl::SetRecommendedLaneWays(CarDirection::GoStraight, lanesInfo), ());
|
||||
TEST_EQUAL(lanesInfo[0].recommendedWay, LaneWay::None, ());
|
||||
TEST_EQUAL(lanesInfo[1].recommendedWay, LaneWay::None, ());
|
||||
}
|
||||
{
|
||||
LanesInfo lanesInfo = {
|
||||
{{LaneWay::ReverseLeft, LaneWay::ReverseRight}},
|
||||
};
|
||||
TEST(impl::SetRecommendedLaneWays(CarDirection::UTurnLeft, lanesInfo), ());
|
||||
TEST_EQUAL(lanesInfo[0].recommendedWay, LaneWay::ReverseLeft, ());
|
||||
TEST_EQUAL(lanesInfo[0].laneWays.Contains(LaneWay::ReverseRight), false, ());
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(SetRecommendedLaneWaysApproximately_Smoke)
|
||||
{
|
||||
{
|
||||
struct CarDirectionToLaneWaysApproximateMapping
|
||||
{
|
||||
CarDirection carDirection;
|
||||
std::vector<LaneWay> laneWay;
|
||||
};
|
||||
std::vector<CarDirectionToLaneWaysApproximateMapping> const testData = {
|
||||
{CarDirection::UTurnLeft, {LaneWay::SharpLeft}},
|
||||
{CarDirection::TurnSharpLeft, {LaneWay::Left}},
|
||||
{CarDirection::TurnLeft, {LaneWay::SlightLeft, LaneWay::SharpLeft}},
|
||||
{CarDirection::TurnSlightLeft, {LaneWay::Left}},
|
||||
{CarDirection::ExitHighwayToLeft, {LaneWay::Left}},
|
||||
{CarDirection::GoStraight, {LaneWay::SlightRight, LaneWay::SlightLeft}},
|
||||
{CarDirection::ExitHighwayToRight, {LaneWay::Right}},
|
||||
{CarDirection::TurnSlightRight, {LaneWay::Right}},
|
||||
{CarDirection::TurnRight, {LaneWay::SlightRight, LaneWay::SharpRight}},
|
||||
{CarDirection::TurnSharpRight, {LaneWay::Right}},
|
||||
{CarDirection::UTurnRight, {LaneWay::SharpRight}},
|
||||
};
|
||||
for (auto const & [carDirection, laneWays] : testData)
|
||||
{
|
||||
for (auto const & laneWay : laneWays)
|
||||
{
|
||||
LanesInfo lanesInfo = {{{laneWay}}};
|
||||
bool const isRecommended = impl::SetRecommendedLaneWaysApproximately(carDirection, lanesInfo);
|
||||
TEST(isRecommended, ("CarDirection:", DebugPrint(carDirection), "LaneWay:", DebugPrint(laneWay)));
|
||||
TEST_EQUAL(lanesInfo[0].recommendedWay, laneWay, ());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Those directions do not have any recommended lane ways.
|
||||
std::vector const carDirections = {CarDirection::None,
|
||||
CarDirection::EnterRoundAbout,
|
||||
CarDirection::LeaveRoundAbout,
|
||||
CarDirection::StayOnRoundAbout,
|
||||
CarDirection::StartAtEndOfStreet,
|
||||
CarDirection::ReachedYourDestination};
|
||||
for (auto const & carDirection : carDirections)
|
||||
{
|
||||
LanesInfo lanesInfo = {{{LaneWay::Through}}};
|
||||
TEST(!impl::SetRecommendedLaneWaysApproximately(carDirection, lanesInfo), ());
|
||||
TEST_EQUAL(lanesInfo[0].recommendedWay, LaneWay::None, ());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(SetRecommendedLaneWaysApproximately)
|
||||
{
|
||||
{
|
||||
LanesInfo lanesInfo = {
|
||||
{{LaneWay::ReverseLeft, LaneWay::Left, LaneWay::SlightLeft}},
|
||||
{{LaneWay::SlightRight, LaneWay::Right}},
|
||||
{{LaneWay::Right}},
|
||||
};
|
||||
TEST(impl::SetRecommendedLaneWaysApproximately(CarDirection::GoStraight, lanesInfo), ());
|
||||
TEST_EQUAL(lanesInfo[0].recommendedWay, LaneWay::SlightLeft, ());
|
||||
TEST_EQUAL(lanesInfo[1].recommendedWay, LaneWay::SlightRight, ());
|
||||
TEST_EQUAL(lanesInfo[2].recommendedWay, LaneWay::None, ());
|
||||
}
|
||||
{
|
||||
LanesInfo lanesInfo = {
|
||||
{{LaneWay::ReverseLeft, LaneWay::Left}},
|
||||
{{LaneWay::Right}},
|
||||
};
|
||||
TEST(!impl::SetRecommendedLaneWaysApproximately(CarDirection::GoStraight, lanesInfo), ());
|
||||
TEST_EQUAL(lanesInfo[0].recommendedWay, LaneWay::None, ());
|
||||
TEST_EQUAL(lanesInfo[1].recommendedWay, LaneWay::None, ());
|
||||
}
|
||||
{
|
||||
LanesInfo lanesInfo = {
|
||||
{{LaneWay::SharpLeft, LaneWay::SlightLeft}},
|
||||
};
|
||||
TEST(impl::SetRecommendedLaneWaysApproximately(CarDirection::TurnLeft, lanesInfo), ());
|
||||
TEST_EQUAL(lanesInfo[0].recommendedWay, LaneWay::SlightLeft, ());
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(SetUnrestrictedLaneAsRecommended)
|
||||
{
|
||||
LanesInfo const testData = {{{LaneWay::ReverseLeft}}, {{LaneWay::None}}, {{LaneWay::None}}, {{LaneWay::Right}}};
|
||||
{
|
||||
LanesInfo lanesInfo = testData;
|
||||
TEST(impl::SetUnrestrictedLaneAsRecommended(CarDirection::TurnLeft, lanesInfo), ());
|
||||
TEST_EQUAL(lanesInfo[0].recommendedWay, LaneWay::None, ());
|
||||
TEST_EQUAL(lanesInfo[1].recommendedWay, LaneWay::Left, ());
|
||||
TEST_EQUAL(lanesInfo[2].recommendedWay, LaneWay::None, ());
|
||||
TEST_EQUAL(lanesInfo[3].recommendedWay, LaneWay::None, ());
|
||||
}
|
||||
{
|
||||
LanesInfo lanesInfo = testData;
|
||||
TEST(impl::SetUnrestrictedLaneAsRecommended(CarDirection::TurnRight, lanesInfo), ());
|
||||
TEST_EQUAL(lanesInfo[0].recommendedWay, LaneWay::None, ());
|
||||
TEST_EQUAL(lanesInfo[1].recommendedWay, LaneWay::None, ());
|
||||
TEST_EQUAL(lanesInfo[2].recommendedWay, LaneWay::Right, ());
|
||||
TEST_EQUAL(lanesInfo[3].recommendedWay, LaneWay::None, ());
|
||||
}
|
||||
{
|
||||
LanesInfo lanesInfo = {};
|
||||
TEST(!impl::SetUnrestrictedLaneAsRecommended(CarDirection::TurnRight, lanesInfo), ());
|
||||
}
|
||||
{
|
||||
LanesInfo lanesInfo = {{{LaneWay::Right}}};
|
||||
TEST(!impl::SetUnrestrictedLaneAsRecommended(CarDirection::TurnRight, lanesInfo), ());
|
||||
TEST_EQUAL(lanesInfo[0].recommendedWay, LaneWay::None, ());
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(SelectRecommendedLanes)
|
||||
{
|
||||
std::vector<TurnItem> turns = {{1, CarDirection::GoStraight},
|
||||
{2, CarDirection::TurnLeft},
|
||||
{3, CarDirection::TurnRight},
|
||||
{4, CarDirection::ReachedYourDestination}};
|
||||
turns[0].m_lanes.push_back({{LaneWay::Left, LaneWay::Through}});
|
||||
turns[0].m_lanes.push_back({{LaneWay::Right}});
|
||||
turns[1].m_lanes.push_back({{LaneWay::SlightLeft}});
|
||||
turns[1].m_lanes.push_back({{LaneWay::Through}});
|
||||
turns[1].m_lanes.push_back({{LaneWay::None}});
|
||||
turns[2].m_lanes.push_back({{LaneWay::Left, LaneWay::SharpLeft}});
|
||||
turns[2].m_lanes.push_back({{LaneWay::None}});
|
||||
std::vector<RouteSegment> routeSegments;
|
||||
RouteSegmentsFrom({}, {}, turns, {}, routeSegments);
|
||||
SelectRecommendedLanes(routeSegments);
|
||||
TEST_EQUAL(routeSegments[0].GetTurn().m_lanes[0].recommendedWay, LaneWay::Through, ());
|
||||
TEST_EQUAL(routeSegments[0].GetTurn().m_lanes[1].recommendedWay, LaneWay::None, ());
|
||||
TEST_EQUAL(routeSegments[1].GetTurn().m_lanes[0].recommendedWay, LaneWay::SlightLeft, ());
|
||||
TEST_EQUAL(routeSegments[1].GetTurn().m_lanes[1].recommendedWay, LaneWay::None, ());
|
||||
TEST_EQUAL(routeSegments[1].GetTurn().m_lanes[2].recommendedWay, LaneWay::None, ());
|
||||
TEST_EQUAL(routeSegments[2].GetTurn().m_lanes[0].recommendedWay, LaneWay::None, ());
|
||||
TEST_EQUAL(routeSegments[2].GetTurn().m_lanes[1].recommendedWay, LaneWay::Right, ());
|
||||
}
|
||||
} // namespace routing::turns::lanes::test
|
||||
@@ -18,7 +18,6 @@
|
||||
|
||||
#include "base/macros.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -65,118 +64,6 @@ private:
|
||||
TUnpackedPathSegments m_segments;
|
||||
};
|
||||
|
||||
UNIT_TEST(TestSplitLanes)
|
||||
{
|
||||
vector<string> result;
|
||||
SplitLanes("through|through|through|through;right", '|', result);
|
||||
vector<string> const expected1 = {"through", "through", "through", "through;right"};
|
||||
TEST_EQUAL(result, expected1, ());
|
||||
|
||||
SplitLanes("adsjkddfasui8747&sxdsdlad8\"\'", '|', result);
|
||||
TEST_EQUAL(result, vector<string>({"adsjkddfasui8747&sxdsdlad8\"\'"}), ());
|
||||
|
||||
SplitLanes("|||||||", '|', result);
|
||||
vector<string> expected2 = {"", "", "", "", "", "", ""};
|
||||
TEST_EQUAL(result, expected2, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(TestParseSingleLane)
|
||||
{
|
||||
TSingleLane result;
|
||||
TEST(ParseSingleLane("through;right", ';', result), ());
|
||||
TSingleLane const expected1 = {LaneWay::Through, LaneWay::Right};
|
||||
TEST_EQUAL(result, expected1, ());
|
||||
|
||||
TEST(!ParseSingleLane("through;Right", ';', result), ());
|
||||
|
||||
TEST(!ParseSingleLane("through ;right", ';', result), ());
|
||||
TEST_EQUAL(result.size(), 0, ());
|
||||
|
||||
TEST(!ParseSingleLane("SD32kk*887;;", ';', result), ());
|
||||
TEST_EQUAL(result.size(), 0, ());
|
||||
|
||||
TEST(!ParseSingleLane("Что-то на кириллице", ';', result), ());
|
||||
TEST_EQUAL(result.size(), 0, ());
|
||||
|
||||
TEST(!ParseSingleLane("משהו בעברית", ';', result), ());
|
||||
TEST_EQUAL(result.size(), 0, ());
|
||||
|
||||
TEST(ParseSingleLane("left;through", ';', result), ());
|
||||
TSingleLane expected2 = {LaneWay::Left, LaneWay::Through};
|
||||
TEST_EQUAL(result, expected2, ());
|
||||
|
||||
TEST(ParseSingleLane("left", ';', result), ());
|
||||
TEST_EQUAL(result.size(), 1, ());
|
||||
TEST_EQUAL(result[0], LaneWay::Left, ());
|
||||
|
||||
TEST(ParseSingleLane("left;", ';', result), ());
|
||||
TSingleLane expected3 = {LaneWay::Left, LaneWay::None};
|
||||
TEST_EQUAL(result, expected3, ());
|
||||
|
||||
TEST(ParseSingleLane(";", ';', result), ());
|
||||
TSingleLane expected4 = {LaneWay::None, LaneWay::None};
|
||||
TEST_EQUAL(result, expected4, ());
|
||||
|
||||
TEST(ParseSingleLane("", ';', result), ());
|
||||
TSingleLane expected5 = {LaneWay::None};
|
||||
TEST_EQUAL(result, expected5, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(TestParseLanes)
|
||||
{
|
||||
vector<SingleLaneInfo> result;
|
||||
TEST(ParseLanes("through|through|through|through;right", result), ());
|
||||
vector<SingleLaneInfo> const expected1 = {
|
||||
{LaneWay::Through}, {LaneWay::Through}, {LaneWay::Through}, {LaneWay::Through, LaneWay::Right}};
|
||||
TEST_EQUAL(result, expected1, ());
|
||||
|
||||
TEST(ParseLanes("left|left;through|through|through", result), ());
|
||||
vector<SingleLaneInfo> const expected2 = {
|
||||
{LaneWay::Left}, {LaneWay::Left, LaneWay::Through}, {LaneWay::Through}, {LaneWay::Through}};
|
||||
TEST_EQUAL(result, expected2, ());
|
||||
|
||||
TEST(ParseLanes("left|through|through", result), ());
|
||||
vector<SingleLaneInfo> const expected3 = {{LaneWay::Left}, {LaneWay::Through}, {LaneWay::Through}};
|
||||
TEST_EQUAL(result, expected3, ());
|
||||
|
||||
TEST(ParseLanes("left|le ft| through|through | right", result), ());
|
||||
vector<SingleLaneInfo> const expected4 = {
|
||||
{LaneWay::Left}, {LaneWay::Left}, {LaneWay::Through}, {LaneWay::Through}, {LaneWay::Right}};
|
||||
TEST_EQUAL(result, expected4, ());
|
||||
|
||||
TEST(ParseLanes("left|Left|through|througH|right", result), ());
|
||||
vector<SingleLaneInfo> const expected5 = {
|
||||
{LaneWay::Left}, {LaneWay::Left}, {LaneWay::Through}, {LaneWay::Through}, {LaneWay::Right}};
|
||||
TEST_EQUAL(result, expected5, ());
|
||||
|
||||
TEST(ParseLanes("left|Left|through|througH|through;right;sharp_rIght", result), ());
|
||||
vector<SingleLaneInfo> const expected6 = {{LaneWay::Left},
|
||||
{LaneWay::Left},
|
||||
{LaneWay::Through},
|
||||
{LaneWay::Through},
|
||||
{LaneWay::Through, LaneWay::Right, LaneWay::SharpRight}};
|
||||
TEST_EQUAL(result, expected6, ());
|
||||
|
||||
TEST(!ParseLanes("left|Leftt|through|througH|right", result), ());
|
||||
TEST_EQUAL(result.size(), 0, ());
|
||||
|
||||
TEST(!ParseLanes("Что-то на кириллице", result), ());
|
||||
TEST_EQUAL(result.size(), 0, ());
|
||||
|
||||
TEST(!ParseLanes("משהו בעברית", result), ());
|
||||
TEST_EQUAL(result.size(), 0, ());
|
||||
|
||||
TEST(ParseLanes("left |Left|through|througH|right", result), ());
|
||||
vector<SingleLaneInfo> const expected7 = {
|
||||
{LaneWay::Left}, {LaneWay::Left}, {LaneWay::Through}, {LaneWay::Through}, {LaneWay::Right}};
|
||||
TEST_EQUAL(result, expected7, ());
|
||||
|
||||
TEST(ParseLanes("|||||slight_right", result), ());
|
||||
vector<SingleLaneInfo> const expected8 = {{LaneWay::None}, {LaneWay::None}, {LaneWay::None},
|
||||
{LaneWay::None}, {LaneWay::None}, {LaneWay::SlightRight}};
|
||||
TEST_EQUAL(result, expected8, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(TestFixupTurns)
|
||||
{
|
||||
double const kHalfSquareSideMeters = 10.;
|
||||
@@ -238,76 +125,6 @@ UNIT_TEST(TestFixupTurns)
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(TestIsLaneWayConformedTurnDirection)
|
||||
{
|
||||
TEST(IsLaneWayConformedTurnDirection(LaneWay::Left, CarDirection::TurnLeft), ());
|
||||
TEST(IsLaneWayConformedTurnDirection(LaneWay::Right, CarDirection::TurnRight), ());
|
||||
TEST(IsLaneWayConformedTurnDirection(LaneWay::SlightLeft, CarDirection::TurnSlightLeft), ());
|
||||
TEST(IsLaneWayConformedTurnDirection(LaneWay::SharpRight, CarDirection::TurnSharpRight), ());
|
||||
TEST(IsLaneWayConformedTurnDirection(LaneWay::Reverse, CarDirection::UTurnLeft), ());
|
||||
TEST(IsLaneWayConformedTurnDirection(LaneWay::Reverse, CarDirection::UTurnRight), ());
|
||||
TEST(IsLaneWayConformedTurnDirection(LaneWay::Through, CarDirection::GoStraight), ());
|
||||
|
||||
TEST(!IsLaneWayConformedTurnDirection(LaneWay::Left, CarDirection::TurnSlightLeft), ());
|
||||
TEST(!IsLaneWayConformedTurnDirection(LaneWay::Right, CarDirection::TurnSharpRight), ());
|
||||
TEST(!IsLaneWayConformedTurnDirection(LaneWay::SlightLeft, CarDirection::GoStraight), ());
|
||||
TEST(!IsLaneWayConformedTurnDirection(LaneWay::SharpRight, CarDirection::None), ());
|
||||
TEST(!IsLaneWayConformedTurnDirection(LaneWay::Reverse, CarDirection::TurnLeft), ());
|
||||
TEST(!IsLaneWayConformedTurnDirection(LaneWay::None, CarDirection::ReachedYourDestination), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(TestIsLaneWayConformedTurnDirectionApproximately)
|
||||
{
|
||||
TEST(IsLaneWayConformedTurnDirectionApproximately(LaneWay::Left, CarDirection::TurnSharpLeft), ());
|
||||
TEST(IsLaneWayConformedTurnDirectionApproximately(LaneWay::Left, CarDirection::TurnSlightLeft), ());
|
||||
TEST(IsLaneWayConformedTurnDirectionApproximately(LaneWay::Right, CarDirection::TurnSharpRight), ());
|
||||
TEST(IsLaneWayConformedTurnDirectionApproximately(LaneWay::Right, CarDirection::TurnRight), ());
|
||||
TEST(IsLaneWayConformedTurnDirectionApproximately(LaneWay::Reverse, CarDirection::UTurnLeft), ());
|
||||
TEST(IsLaneWayConformedTurnDirectionApproximately(LaneWay::Reverse, CarDirection::UTurnRight), ());
|
||||
TEST(IsLaneWayConformedTurnDirectionApproximately(LaneWay::SlightLeft, CarDirection::GoStraight), ());
|
||||
TEST(IsLaneWayConformedTurnDirectionApproximately(LaneWay::SlightRight, CarDirection::GoStraight), ());
|
||||
|
||||
TEST(!IsLaneWayConformedTurnDirectionApproximately(LaneWay::SharpLeft, CarDirection::UTurnLeft), ());
|
||||
TEST(!IsLaneWayConformedTurnDirectionApproximately(LaneWay::SharpLeft, CarDirection::UTurnRight), ());
|
||||
TEST(!IsLaneWayConformedTurnDirectionApproximately(LaneWay::SharpRight, CarDirection::UTurnLeft), ());
|
||||
TEST(!IsLaneWayConformedTurnDirectionApproximately(LaneWay::SharpRight, CarDirection::UTurnRight), ());
|
||||
TEST(!IsLaneWayConformedTurnDirection(LaneWay::Through, CarDirection::ReachedYourDestination), ());
|
||||
TEST(!IsLaneWayConformedTurnDirectionApproximately(LaneWay::Through, CarDirection::TurnRight), ());
|
||||
TEST(!IsLaneWayConformedTurnDirectionApproximately(LaneWay::SlightRight, CarDirection::TurnSharpLeft), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(TestAddingActiveLaneInformation)
|
||||
{
|
||||
vector<turns::TurnItem> turns = {{1, CarDirection::GoStraight},
|
||||
{2, CarDirection::TurnLeft},
|
||||
{3, CarDirection::TurnRight},
|
||||
{4, CarDirection::ReachedYourDestination}};
|
||||
|
||||
turns[0].m_lanes.push_back({LaneWay::Left, LaneWay::Through});
|
||||
turns[0].m_lanes.push_back({LaneWay::Right});
|
||||
|
||||
turns[1].m_lanes.push_back({LaneWay::SlightLeft});
|
||||
turns[1].m_lanes.push_back({LaneWay::Through});
|
||||
turns[1].m_lanes.push_back({LaneWay::None});
|
||||
|
||||
turns[2].m_lanes.push_back({LaneWay::Left, LaneWay::SharpLeft});
|
||||
turns[2].m_lanes.push_back({LaneWay::None});
|
||||
|
||||
vector<RouteSegment> routeSegments;
|
||||
RouteSegmentsFrom({}, {}, turns, {}, routeSegments);
|
||||
SelectRecommendedLanes(routeSegments);
|
||||
|
||||
TEST(routeSegments[0].GetTurn().m_lanes[0].m_isRecommended, ());
|
||||
TEST(!routeSegments[0].GetTurn().m_lanes[1].m_isRecommended, ());
|
||||
|
||||
TEST(routeSegments[1].GetTurn().m_lanes[0].m_isRecommended, ());
|
||||
TEST(!routeSegments[1].GetTurn().m_lanes[1].m_isRecommended, ());
|
||||
TEST(!routeSegments[1].GetTurn().m_lanes[2].m_isRecommended, ());
|
||||
|
||||
TEST(!routeSegments[2].GetTurn().m_lanes[0].m_isRecommended, ());
|
||||
TEST(routeSegments[2].GetTurn().m_lanes[1].m_isRecommended, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(TestGetRoundaboutDirection)
|
||||
{
|
||||
// The signature of GetRoundaboutDirection function is
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
#include "platform/country_file.hpp"
|
||||
|
||||
#include "base/internal/message.hpp"
|
||||
#include "base/stl_helpers.hpp"
|
||||
#include "base/string_utils.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
@@ -21,28 +19,6 @@ using namespace std;
|
||||
|
||||
namespace
|
||||
{
|
||||
/// The order is important. Starting with the most frequent tokens according to
|
||||
/// taginfo.openstreetmap.org we minimize the number of the comparisons in ParseSingleLane().
|
||||
///
|
||||
/// A `none` lane can be represented either as "none" or as "". That means both "none" and ""
|
||||
/// should be considered names, even though they refer to the same thing. As a result,
|
||||
/// `LaneWay::None` appears twice in this array, which is one longer than the number of
|
||||
/// enum values.
|
||||
array<pair<LaneWay, char const *>, static_cast<size_t>(LaneWay::Count) + 1> const g_laneWayNames = {
|
||||
{{LaneWay::None, ""},
|
||||
{LaneWay::Through, "through"},
|
||||
{LaneWay::Left, "left"},
|
||||
{LaneWay::Right, "right"},
|
||||
{LaneWay::None, "none"},
|
||||
{LaneWay::SharpLeft, "sharp_left"},
|
||||
{LaneWay::SlightLeft, "slight_left"},
|
||||
{LaneWay::MergeToRight, "merge_to_right"},
|
||||
{LaneWay::MergeToLeft, "merge_to_left"},
|
||||
{LaneWay::SlightRight, "slight_right"},
|
||||
{LaneWay::SharpRight, "sharp_right"},
|
||||
{LaneWay::Reverse, "reverse"}}};
|
||||
static_assert(g_laneWayNames.size() == static_cast<size_t>(LaneWay::Count) + 1, "Check the size of g_laneWayNames");
|
||||
|
||||
array<pair<CarDirection, char const *>, static_cast<size_t>(CarDirection::Count)> const g_turnNames = {
|
||||
{{CarDirection::None, "None"},
|
||||
{CarDirection::GoStraight, "GoStraight"},
|
||||
@@ -162,12 +138,6 @@ string DebugPrint(SegmentRange const & segmentRange)
|
||||
|
||||
namespace turns
|
||||
{
|
||||
// SingleLaneInfo ---------------------------------------------------------------------------------
|
||||
bool SingleLaneInfo::operator==(SingleLaneInfo const & other) const
|
||||
{
|
||||
return m_lane == other.m_lane && m_isRecommended == other.m_isRecommended;
|
||||
}
|
||||
|
||||
string DebugPrint(TurnItem const & turnItem)
|
||||
{
|
||||
stringstream out;
|
||||
@@ -232,118 +202,6 @@ bool IsGoStraightOrSlightTurn(CarDirection t)
|
||||
return (t == CarDirection::GoStraight || t == CarDirection::TurnSlightLeft || t == CarDirection::TurnSlightRight);
|
||||
}
|
||||
|
||||
bool IsLaneWayConformedTurnDirection(LaneWay l, CarDirection t)
|
||||
{
|
||||
switch (t)
|
||||
{
|
||||
default: return false;
|
||||
case CarDirection::GoStraight: return l == LaneWay::Through;
|
||||
case CarDirection::TurnRight: return l == LaneWay::Right;
|
||||
case CarDirection::TurnSharpRight: return l == LaneWay::SharpRight;
|
||||
case CarDirection::TurnSlightRight:
|
||||
case CarDirection::ExitHighwayToRight: return l == LaneWay::SlightRight;
|
||||
case CarDirection::TurnLeft: return l == LaneWay::Left;
|
||||
case CarDirection::TurnSharpLeft: return l == LaneWay::SharpLeft;
|
||||
case CarDirection::TurnSlightLeft:
|
||||
case CarDirection::ExitHighwayToLeft: return l == LaneWay::SlightLeft;
|
||||
case CarDirection::UTurnLeft:
|
||||
case CarDirection::UTurnRight: return l == LaneWay::Reverse;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsLaneWayConformedTurnDirectionApproximately(LaneWay l, CarDirection t)
|
||||
{
|
||||
switch (t)
|
||||
{
|
||||
default: return false;
|
||||
case CarDirection::GoStraight: return l == LaneWay::Through || l == LaneWay::SlightRight || l == LaneWay::SlightLeft;
|
||||
case CarDirection::TurnRight: return l == LaneWay::Right || l == LaneWay::SharpRight || l == LaneWay::SlightRight;
|
||||
case CarDirection::TurnSharpRight: return l == LaneWay::SharpRight || l == LaneWay::Right;
|
||||
case CarDirection::TurnSlightRight: return l == LaneWay::SlightRight || l == LaneWay::Through || l == LaneWay::Right;
|
||||
case CarDirection::TurnLeft: return l == LaneWay::Left || l == LaneWay::SlightLeft || l == LaneWay::SharpLeft;
|
||||
case CarDirection::TurnSharpLeft: return l == LaneWay::SharpLeft || l == LaneWay::Left;
|
||||
case CarDirection::TurnSlightLeft: return l == LaneWay::SlightLeft || l == LaneWay::Through || l == LaneWay::Left;
|
||||
case CarDirection::UTurnLeft:
|
||||
case CarDirection::UTurnRight: return l == LaneWay::Reverse;
|
||||
case CarDirection::ExitHighwayToLeft: return l == LaneWay::SlightLeft || l == LaneWay::Left;
|
||||
case CarDirection::ExitHighwayToRight: return l == LaneWay::SlightRight || l == LaneWay::Right;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsLaneUnrestricted(SingleLaneInfo const & lane)
|
||||
{
|
||||
/// @todo Is there any reason to store None single lane?
|
||||
return lane.m_lane.size() == 1 && lane.m_lane[0] == LaneWay::None;
|
||||
}
|
||||
|
||||
void SplitLanes(string const & lanesString, char delimiter, vector<string> & lanes)
|
||||
{
|
||||
lanes.clear();
|
||||
istringstream lanesStream(lanesString);
|
||||
string token;
|
||||
while (getline(lanesStream, token, delimiter))
|
||||
lanes.push_back(token);
|
||||
}
|
||||
|
||||
bool ParseSingleLane(string const & laneString, char delimiter, TSingleLane & lane)
|
||||
{
|
||||
lane.clear();
|
||||
// When `laneString` ends with "" representing none, for example, in "right;",
|
||||
// `getline` will not read any characters, so it exits the loop and does not
|
||||
// handle the "". So, we add a delimiter to the end of `laneString`. Nonempty
|
||||
// final tokens consume the delimiter and act as expected, and empty final tokens
|
||||
// read a the delimiter, so `getline` sets `token` to the empty string rather than
|
||||
// exiting the loop.
|
||||
istringstream laneStream(laneString + delimiter);
|
||||
string token;
|
||||
while (getline(laneStream, token, delimiter))
|
||||
{
|
||||
auto const it = find_if(g_laneWayNames.begin(), g_laneWayNames.end(),
|
||||
[&token](pair<LaneWay, string> const & p) { return p.second == token; });
|
||||
if (it == g_laneWayNames.end())
|
||||
return false;
|
||||
lane.push_back(it->first);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseLanes(string lanesString, vector<SingleLaneInfo> & lanes)
|
||||
{
|
||||
if (lanesString.empty())
|
||||
return false;
|
||||
lanes.clear();
|
||||
strings::AsciiToLower(lanesString);
|
||||
base::EraseIf(lanesString, strings::IsASCIISpace<std::string::value_type>);
|
||||
|
||||
vector<string> SplitLanesStrings;
|
||||
SingleLaneInfo lane;
|
||||
SplitLanes(lanesString, '|', SplitLanesStrings);
|
||||
for (string const & s : SplitLanesStrings)
|
||||
{
|
||||
if (!ParseSingleLane(s, ';', lane.m_lane))
|
||||
{
|
||||
lanes.clear();
|
||||
return false;
|
||||
}
|
||||
lanes.push_back(lane);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
string DebugPrint(LaneWay const l)
|
||||
{
|
||||
auto const it = find_if(g_laneWayNames.begin(), g_laneWayNames.end(),
|
||||
[&l](pair<LaneWay, string> const & p) { return p.first == l; });
|
||||
|
||||
if (it == g_laneWayNames.end())
|
||||
{
|
||||
stringstream out;
|
||||
out << "unknown LaneWay (" << static_cast<int>(l) << ")";
|
||||
return out.str();
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
string DebugPrint(CarDirection const turn)
|
||||
{
|
||||
return GetTurnString(turn);
|
||||
@@ -368,14 +226,6 @@ string DebugPrint(PedestrianDirection const l)
|
||||
return "unknown PedestrianDirection";
|
||||
}
|
||||
|
||||
string DebugPrint(SingleLaneInfo const & singleLaneInfo)
|
||||
{
|
||||
stringstream out;
|
||||
out << "SingleLaneInfo [ m_isRecommended == " << singleLaneInfo.m_isRecommended
|
||||
<< ", m_lane == " << ::DebugPrint(singleLaneInfo.m_lane) << " ]" << endl;
|
||||
return out.str();
|
||||
}
|
||||
|
||||
double PiMinusTwoVectorsAngle(m2::PointD const & junctionPoint, m2::PointD const & ingoingPoint,
|
||||
m2::PointD const & outgoingPoint)
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "routing/lanes/lane_info.hpp"
|
||||
#include "routing/segment.hpp"
|
||||
|
||||
#include "routing_common/num_mwm_id.hpp"
|
||||
@@ -8,7 +9,6 @@
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include <initializer_list>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -118,41 +118,6 @@ enum class PedestrianDirection
|
||||
|
||||
std::string DebugPrint(PedestrianDirection const l);
|
||||
|
||||
/*!
|
||||
* \warning The values of LaneWay shall be synchronized with values of LaneWay enum in java.
|
||||
*/
|
||||
enum class LaneWay
|
||||
{
|
||||
None = 0,
|
||||
Reverse,
|
||||
SharpLeft,
|
||||
Left,
|
||||
SlightLeft,
|
||||
MergeToRight,
|
||||
Through,
|
||||
MergeToLeft,
|
||||
SlightRight,
|
||||
Right,
|
||||
SharpRight,
|
||||
Count /**< This value is used for internals only. */
|
||||
};
|
||||
|
||||
std::string DebugPrint(LaneWay const l);
|
||||
|
||||
typedef std::vector<LaneWay> TSingleLane;
|
||||
|
||||
struct SingleLaneInfo
|
||||
{
|
||||
TSingleLane m_lane;
|
||||
bool m_isRecommended = false;
|
||||
|
||||
SingleLaneInfo() = default;
|
||||
SingleLaneInfo(std::initializer_list<LaneWay> const & l) : m_lane(l) {}
|
||||
bool operator==(SingleLaneInfo const & other) const;
|
||||
};
|
||||
|
||||
std::string DebugPrint(SingleLaneInfo const & singleLaneInfo);
|
||||
|
||||
struct TurnItem
|
||||
{
|
||||
TurnItem()
|
||||
@@ -192,7 +157,7 @@ struct TurnItem
|
||||
|
||||
uint32_t m_index; /*!< Index of point on route polyline (Index of segment + 1). */
|
||||
CarDirection m_turn = CarDirection::None; /*!< The turn instruction of the TurnItem */
|
||||
std::vector<SingleLaneInfo> m_lanes; /*!< Lane information on the edge before the turn. */
|
||||
lanes::LanesInfo m_lanes; /*!< Lane information on the edge before the turn. */
|
||||
uint32_t m_exitNum; /*!< Number of exit on roundabout. */
|
||||
/*!
|
||||
* \brief m_pedestrianTurn is type of corresponding direction for a pedestrian, or None
|
||||
@@ -223,39 +188,6 @@ bool IsTurnMadeFromLeft(CarDirection t);
|
||||
bool IsTurnMadeFromRight(CarDirection t);
|
||||
bool IsStayOnRoad(CarDirection t);
|
||||
bool IsGoStraightOrSlightTurn(CarDirection t);
|
||||
|
||||
/*!
|
||||
* \param l A variant of going along a lane.
|
||||
* \param t A turn direction.
|
||||
* \return True if @l corresponds with @t exactly. For example it returns true
|
||||
* when @l equals to LaneWay::Right and @t equals to TurnDirection::TurnRight.
|
||||
* Otherwise it returns false.
|
||||
*/
|
||||
bool IsLaneWayConformedTurnDirection(LaneWay l, CarDirection t);
|
||||
|
||||
/*!
|
||||
* \param l A variant of going along a lane.
|
||||
* \param t A turn direction.
|
||||
* \return True if @l corresponds with @t approximately. For example it returns true
|
||||
* when @l equals to LaneWay::Right and @t equals to TurnDirection::TurnSlightRight.
|
||||
* Otherwise it returns false.
|
||||
*/
|
||||
bool IsLaneWayConformedTurnDirectionApproximately(LaneWay l, CarDirection t);
|
||||
|
||||
bool IsLaneUnrestricted(SingleLaneInfo const & lane);
|
||||
|
||||
/*!
|
||||
* \brief Parse lane information which comes from @lanesString
|
||||
* \param lanesString lane information. Example through|through|through|through;right
|
||||
* \param lanes the result of parsing.
|
||||
* \return true if @lanesString parsed successfully, false otherwise.
|
||||
* Note 1: if @lanesString is empty returns false.
|
||||
* Note 2: @laneString is passed by value on purpose. It'll be used(changed) in the method.
|
||||
*/
|
||||
bool ParseLanes(std::string lanesString, std::vector<SingleLaneInfo> & lanes);
|
||||
void SplitLanes(std::string const & lanesString, char delimiter, std::vector<std::string> & lanes);
|
||||
bool ParseSingleLane(std::string const & laneString, char delimiter, TSingleLane & lane);
|
||||
|
||||
/*!
|
||||
* \returns pi minus angle from vector [junctionPoint, ingoingPoint]
|
||||
* to vector [junctionPoint, outgoingPoint]. A counterclockwise rotation.
|
||||
|
||||
@@ -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 = "<group>"; };
|
||||
0CF5E8A91E8EA7A1001ED497 /* coding_test.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = coding_test.cpp; sourceTree = "<group>"; };
|
||||
0CF709351F05172200D5067E /* checkpoints.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = checkpoints.cpp; sourceTree = "<group>"; };
|
||||
1667FAF72E3F845D00F06E59 /* lane_info.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = lane_info.hpp; sourceTree = "<group>"; };
|
||||
1667FAF82E3F845D00F06E59 /* lane_info.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = lane_info.cpp; sourceTree = "<group>"; };
|
||||
1667FAF92E3F845D00F06E59 /* lane_way.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = lane_way.hpp; sourceTree = "<group>"; };
|
||||
1667FAFA2E3F845D00F06E59 /* lane_way.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = lane_way.cpp; sourceTree = "<group>"; };
|
||||
1667FAFB2E3F845D00F06E59 /* lanes_parser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = lanes_parser.hpp; sourceTree = "<group>"; };
|
||||
1667FAFC2E3F845D00F06E59 /* lanes_parser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = lanes_parser.cpp; sourceTree = "<group>"; };
|
||||
1667FAFD2E3F845D00F06E59 /* lanes_recommendation.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = lanes_recommendation.hpp; sourceTree = "<group>"; };
|
||||
1667FAFE2E3F845D00F06E59 /* lanes_recommendation.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = lanes_recommendation.cpp; sourceTree = "<group>"; };
|
||||
1667FB082E3F847B00F06E59 /* lanes_parser_tests.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = lanes_parser_tests.cpp; sourceTree = "<group>"; };
|
||||
1667FB092E3F847B00F06E59 /* lanes_recommendation_tests.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = lanes_recommendation_tests.cpp; sourceTree = "<group>"; };
|
||||
349D1CDE1E3F589900A878FD /* restrictions_serialization.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = restrictions_serialization.cpp; sourceTree = "<group>"; };
|
||||
349D1CDF1E3F589900A878FD /* restrictions_serialization.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = restrictions_serialization.hpp; sourceTree = "<group>"; };
|
||||
34F558351DBF2A2600A4FC11 /* common-debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "common-debug.xcconfig"; path = "../common-debug.xcconfig"; sourceTree = "<group>"; };
|
||||
@@ -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 = "<group>";
|
||||
};
|
||||
1667FB0A2E3F847B00F06E59 /* lanes */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1667FB082E3F847B00F06E59 /* lanes_parser_tests.cpp */,
|
||||
1667FB092E3F847B00F06E59 /* lanes_recommendation_tests.cpp */,
|
||||
);
|
||||
path = lanes;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
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 */,
|
||||
|
||||
Reference in New Issue
Block a user