Merge branch 'organicmaps:master' into etaswitchfeature

This commit is contained in:
Aryan 2023-03-21 20:53:13 +05:30 committed by GitHub
commit 64697e0184
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 290 additions and 33 deletions

View file

@ -104,4 +104,21 @@
tools:src="@drawable/ic_then_left_sharp"/>
</FrameLayout>
</RelativeLayout>
<FrameLayout
android:id="@+id/lanes_frame"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_marginTop="@dimen/margin_half"
app:layout_constrainedWidth="true"
app:layout_constraintHeight_percent="@fraction/nav_lane_height"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@+id/nav_next_turn_container"
app:layout_constraintTop_toBottomOf="@+id/street_frame"
android:visibility="gone"
tools:visibility="visible">
<include layout="@layout/nav_lanes" />
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:cardBackgroundColor="?navLanesCardBackgroundColor"
tools:showIn="@layout/layout_nav_top">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/lanes"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_margin="@dimen/margin_quarter"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:orientation="horizontal"
tools:listitem="@layout/nav_single_lane"
tools:itemCount="6" />
</androidx.cardview.widget.CardView>

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<app.organicmaps.widget.ArrowView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/lane_image"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:scaleType="fitCenter"
android:theme="?navigationTheme"
app:tint="?iconTint"
app:srcCompat="@drawable/ic_then_right"
tools:showIn="@layout/layout_nav_top" />

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<fraction name="nav_lane_height">0.15</fraction>
</resources>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<fraction name="nav_lane_height">0.08</fraction>
</resources>

View file

@ -19,6 +19,7 @@
<attr name="ppButtonsBackground" format="color" />
<attr name="navNextTurnFrame" format="reference" />
<attr name="navNextNextTurnFrame" format="reference" />
<attr name="navLanesCardBackgroundColor" format="reference" />
<attr name="buttonBackground" format="reference" />
<attr name="buttonTextColor" format="color" />
<attr name="buttonTextColorDisabled" format="color" />

View file

@ -45,6 +45,7 @@
<item name="navNextTurnFrame">@drawable/bg_nav_next_turn</item>
<item name="navNextNextTurnFrame">@drawable/bg_nav_next_next_turn</item>
<item name="navLanesCardBackgroundColor">@color/base_accent</item>
<item name="buttonBackground">@drawable/button</item>
<item name="buttonTextColor">@color/button_text</item>
@ -179,6 +180,7 @@
<item name="navNextTurnFrame">@drawable/bg_nav_next_turn_night</item>
<item name="navNextNextTurnFrame">@drawable/bg_nav_next_next_turn_night</item>
<item name="navLanesCardBackgroundColor">@color/base_accent_night</item>
<item name="buttonBackground">@drawable/button_night</item>
<item name="buttonTextColor">@color/button_text_night</item>

View file

@ -0,0 +1,90 @@
package app.organicmaps.routing;
import android.content.res.ColorStateList;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import app.organicmaps.R;
import app.organicmaps.widget.ArrowView;
import java.util.ArrayList;
import java.util.List;
public class LanesAdapter extends RecyclerView.Adapter<LanesAdapter.LanesViewHolder>
{
@NonNull
private final List<SingleLaneInfo> mItems = new ArrayList<>();
@NonNull
@Override
public LanesViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType)
{
final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
final View root = inflater.inflate(R.layout.nav_single_lane, parent, false);
return new LanesViewHolder(root);
}
@Override
public void onBindViewHolder(@NonNull LanesViewHolder holder, int position)
{
holder.bind(mItems.get(position));
}
@Override
public int getItemCount()
{
return mItems.size();
}
public void setItems(@NonNull List<SingleLaneInfo> items)
{
mItems.clear();
mItems.addAll(items);
notifyDataSetChanged();
}
public void clearItems()
{
boolean alreadyEmpty = mItems.isEmpty();
mItems.clear();
if (!alreadyEmpty)
notifyDataSetChanged();
}
static class LanesViewHolder extends RecyclerView.ViewHolder
{
@NonNull
private final ArrowView mArrow;
public LanesViewHolder(@NonNull View itemView)
{
super(itemView);
mArrow = itemView.findViewById(R.id.lane_image);
}
private void setIconTint(@NonNull SingleLaneInfo info)
{
int iconTint = info.mIsActive ? R.attr.iconTint : R.attr.iconTintLight;
TypedValue color = new TypedValue();
mArrow.getContext().getTheme().resolveAttribute(iconTint, color, true);
mArrow.setImageTintList(ColorStateList.valueOf(color.data));
}
private void setIcon(@NonNull SingleLaneInfo info)
{
boolean haveLaneData = (info.mLane.length > 0);
int imageRes = haveLaneData ? info.mLane[0].mTurnRes : 0;
mArrow.setImageResource(imageRes);
}
void bind(@NonNull SingleLaneInfo info)
{
setIconTint(info);
setIcon(info);
}
}
}

View file

@ -20,6 +20,7 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import app.organicmaps.Framework;
import app.organicmaps.MwmActivity;
@ -32,6 +33,8 @@ import app.organicmaps.widget.menu.NavMenu;
import app.organicmaps.util.UiUtils;
import app.organicmaps.util.Utils;
import java.util.Arrays;
public class NavigationController implements Application.ActivityLifecycleCallbacks,
TrafficManager.TrafficCallback,
NavMenu.NavMenuListener
@ -50,6 +53,13 @@ public class NavigationController implements Application.ActivityLifecycleCallba
private final View mStreetFrame;
private final TextView mNextStreet;
@NonNull
private final View mLanesFrame;
@NonNull
private final RecyclerView mLanes;
@NonNull
private final LanesAdapter mLanesAdapter;
@NonNull
private final MapButtonsController mMapButtonsController;
@ -90,6 +100,12 @@ public class NavigationController implements Application.ActivityLifecycleCallba
});
}
private void initLanesRecycler()
{
mLanes.setAdapter(mLanesAdapter);
mLanes.setNestedScrollingEnabled(false);
}
public NavigationController(AppCompatActivity activity, @NonNull MapButtonsController mapButtonsController, View.OnClickListener onSettingsClickListener)
{
mFrame = activity.findViewById(R.id.navigation_frame);
@ -112,6 +128,11 @@ public class NavigationController implements Application.ActivityLifecycleCallba
mStreetFrame = topFrame.findViewById(R.id.street_frame);
mNextStreet = mStreetFrame.findViewById(R.id.street);
mLanesFrame = topFrame.findViewById(R.id.lanes_frame);
mLanes = mLanesFrame.findViewById(R.id.lanes);
mLanesAdapter = new LanesAdapter();
initLanesRecycler();
// Show a blank view below the navbar to hide the menu content
final View navigationBarBackground = mFrame.findViewById(R.id.nav_bottom_sheet_nav_bar);
final View nextTurnContainer = mFrame.findViewById(R.id.nav_next_turn_container);
@ -185,6 +206,17 @@ public class NavigationController implements Application.ActivityLifecycleCallba
UiUtils.showIf(info.nextCarDirection.containsNextTurn(), mNextNextTurnFrame);
if (info.nextCarDirection.containsNextTurn())
info.nextCarDirection.setNextTurnDrawable(mNextNextTurnImage);
if (info.lanes != null)
{
UiUtils.show(mLanesFrame);
mLanesAdapter.setItems(Arrays.asList(info.lanes));
}
else
{
UiUtils.hide(mLanesFrame);
mLanesAdapter.clearItems();
}
}
private void updatePedestrian(RoutingInfo info)

View file

@ -133,27 +133,6 @@ public class RoutingInfo
}
}
/**
* 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,
REVERSE,
SHARP_LEFT,
LEFT,
SLIGHT_LEFT,
MERGE_TO_RIGHT,
THROUGH,
MERGE_TO_LEFT,
SLIGHT_RIGHT,
RIGHT,
SHARP_RIGHT
}
public RoutingInfo(String distToTarget, String units, String distTurn, String turnSuffix, String currentStreet, String nextStreet, double completionPercent,
int vehicleTurnOrdinal, int vehicleNextTurnOrdinal, int pedestrianTurnOrdinal, int exitNum,
int totalTime, SingleLaneInfo[] lanes, boolean speedLimitExceeded,

View file

@ -1,13 +1,48 @@
package app.organicmaps.routing;
import androidx.annotation.DrawableRes;
import app.organicmaps.R;
public class SingleLaneInfo
{
byte[] mLane;
LaneWay[] mLane;
boolean mIsActive;
SingleLaneInfo(byte[] lane, boolean isActive)
/**
* 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
{
mLane = lane;
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);
public final int mTurnRes;
LaneWay(@DrawableRes int turnRes)
{
mTurnRes = turnRes;
}
}
SingleLaneInfo(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;
}
@ -17,8 +52,8 @@ public class SingleLaneInfo
final int initialCapacity = 32;
StringBuilder sb = new StringBuilder(initialCapacity);
sb.append("Is the lane active? ").append(mIsActive).append(". The lane directions IDs are");
for (byte i : mLane)
sb.append(" ").append(i);
for (LaneWay i : mLane)
sb.append(" ").append(i.ordinal());
return sb.toString();
}
}

View file

@ -3,6 +3,7 @@
#include "routing/data_source.hpp"
#include "routing/fake_feature_ids.hpp"
#include "routing/routing_helpers.hpp"
#include "routing/turns.hpp"
#include "indexer/ftypes_matcher.hpp"
@ -23,6 +24,21 @@ bool IsFakeFeature(uint32_t featureId)
return routing::FakeFeatureIds::IsGuidesFeature(featureId) ||
routing::FakeFeatureIds::IsTransitFeature(featureId);
}
feature::Metadata::EType GetLanesMetadataTag(FeatureType & ft, bool isForward)
{
auto directionTag = isForward ? feature::Metadata::FMD_TURN_LANES_FORWARD
: feature::Metadata::FMD_TURN_LANES_BACKWARD;
if (ft.HasMetadata(directionTag))
return directionTag;
return feature::Metadata::FMD_TURN_LANES;
}
void LoadLanes(LoadedPathSegment & pathSegment, FeatureType & ft, bool isForward)
{
auto tag = GetLanesMetadataTag(ft, isForward);
ParseLanes(std::string(ft.GetMetadata(tag)), pathSegment.m_lanes);
}
} // namespace
DirectionsEngine::DirectionsEngine(MwmDataSource & dataSource, std::shared_ptr<NumMwmIds> numMwmIds)
@ -48,7 +64,7 @@ unique_ptr<FeatureType> DirectionsEngine::GetFeature(FeatureID const & featureId
}
void DirectionsEngine::LoadPathAttributes(FeatureID const & featureId,
LoadedPathSegment & pathSegment)
LoadedPathSegment & pathSegment, bool isForward)
{
if (!featureId.IsValid())
return;
@ -59,6 +75,8 @@ void DirectionsEngine::LoadPathAttributes(FeatureID const & featureId,
feature::TypesHolder types(*ft);
LoadLanes(pathSegment, *ft, isForward);
pathSegment.m_highwayClass = GetHighwayClass(types);
ASSERT_NOT_EQUAL(pathSegment.m_highwayClass, HighwayClass::Error, ());
ASSERT_NOT_EQUAL(pathSegment.m_highwayClass, HighwayClass::Undefined, ());
@ -195,7 +213,7 @@ void DirectionsEngine::FillPathSegmentsAndAdjacentEdgesMap(
pathSegment.m_path = move(prevJunctions);
pathSegment.m_segments = move(prevSegments);
LoadPathAttributes(segmentRange.GetFeature(), pathSegment); // inEdge.IsForward()
LoadPathAttributes(segmentRange.GetFeature(), pathSegment, inEdge.IsForward());
if (!segmentRange.IsEmpty())
{

View file

@ -55,7 +55,7 @@ protected:
RoutingSettings const & vehicleSettings, turns::TurnItem & turn) = 0;
virtual void FixupTurns(std::vector<RouteSegment> & routeSegments) = 0;
std::unique_ptr<FeatureType> GetFeature(FeatureID const & featureId);
void LoadPathAttributes(FeatureID const & featureId, LoadedPathSegment & pathSegment);
void LoadPathAttributes(FeatureID const & featureId, LoadedPathSegment & pathSegment, bool isForward);
void GetSegmentRangeAndAdjacentEdges(IRoadGraph::EdgeListT const & outgoingEdges,
Edge const & inEdge, uint32_t startSegId, uint32_t endSegId,
SegmentRange & segmentRange,

View file

@ -108,6 +108,18 @@ UNIT_TEST(TestParseSingleLane)
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)
@ -162,6 +174,17 @@ UNIT_TEST(TestParseLanes)
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)

View file

@ -22,8 +22,14 @@ 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().
array<pair<LaneWay, char const *>, static_cast<size_t>(LaneWay::Count)> const g_laneWayNames = {
{{LaneWay::Through, "through"},
///
/// 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"},
@ -34,7 +40,7 @@ array<pair<LaneWay, char const *>, static_cast<size_t>(LaneWay::Count)> const g_
{LaneWay::SlightRight, "slight_right"},
{LaneWay::SharpRight, "sharp_right"},
{LaneWay::Reverse, "reverse"}}};
static_assert(g_laneWayNames.size() == static_cast<size_t>(LaneWay::Count),
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
@ -239,12 +245,14 @@ bool IsLaneWayConformedTurnDirection(LaneWay l, CarDirection t)
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:
@ -275,6 +283,10 @@ bool IsLaneWayConformedTurnDirectionApproximately(LaneWay l, CarDirection t)
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;
}
}
@ -292,7 +304,13 @@ void SplitLanes(string const & lanesString, char delimiter, vector<string> & lan
bool ParseSingleLane(string const & laneString, char delimiter, TSingleLane & lane)
{
lane.clear();
istringstream laneStream(laneString);
// 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))
{