diff --git a/android/UnitTests/jni/Android.mk b/android/UnitTests/jni/Android.mk index 839ba603c8..9928732568 100644 --- a/android/UnitTests/jni/Android.mk +++ b/android/UnitTests/jni/Android.mk @@ -2,7 +2,7 @@ LOCAL_PATH := $(call my-dir) # Add prebuilt shared lib -# todo(VB) extract the names of the libs from tests_list.sh +# todo(vbykoianko) extract the names of the libs from tests_list.sh MY_PREBUILT_LIBS_PATH := ../libs/tmp/$(TARGET_ARCH_ABI) $(info $(MY_PREBUILT_LIBS_PATH)) @@ -26,7 +26,7 @@ LOCAL_MODULE := all_tests LOCAL_SRC_FILES := ./main.cpp LOCAL_STATIC_LIBRARIES := android_native_app_glue -# todo(VB) extract the names of the libs from tests_list.sh +# todo(vbykoianko) extract the names of the libs from tests_list.sh LOCAL_SHARED_LIBRARIES := integration_tests indexer_tests include ./jni/AndroidEnding.mk diff --git a/android/UnitTests/jni/Application.mk b/android/UnitTests/jni/Application.mk index d2d2ced6bc..b344b3daec 100644 --- a/android/UnitTests/jni/Application.mk +++ b/android/UnitTests/jni/Application.mk @@ -9,7 +9,7 @@ APP_STL := c++_static APP_CPPFLAGS += -Wno-deprecated-register -#@todo(VB) Build tests for android x86 platform +#@todo(vbykoianko) Build tests for android x86 platform #ifeq (x$(NDK_ABI_TO_BUILD), x) # APP_ABI := armeabi-v7a-hard x86 #else diff --git a/android/UnitTests/jni/main.cpp b/android/UnitTests/jni/main.cpp index 53c752ee8c..6f2d5d0207 100644 --- a/android/UnitTests/jni/main.cpp +++ b/android/UnitTests/jni/main.cpp @@ -1,4 +1,4 @@ -// @todo(VB) this file should be generated with a script based on tests_list.sh +// @todo(vbykoianko) this file should be generated with a script based on tests_list.sh #include "mock.hpp" #include "integration_tests/jni/test.hpp" @@ -15,6 +15,7 @@ void android_main(struct android_app * state) integration_tests::test(state); indexer_tests::test(state); - // @todo(VB) you need to write code to leave the activity correctly after all tests are finished. And show a dialog with the results. - // @todo(VB) It's nice to show the result of the test on Android screen. Message box or something like that. + // @todo(vbykoianko) Take care of correctly leaving the activity after all tests have + // finished. It's nice to show the result of the test on Android screen. Message box or + // something like that. } diff --git a/android/UnitTests/jni/mock.cpp b/android/UnitTests/jni/mock.cpp index d06c074309..beb0a44803 100644 --- a/android/UnitTests/jni/mock.cpp +++ b/android/UnitTests/jni/mock.cpp @@ -14,8 +14,9 @@ using namespace my; -// @todo(VB) probably it's worth thinking about output of the function to make the result of the tests more readable -// @todo(VB) it's necessary display test name android log +// @todo(vbykoianko) Probably it's worth thinking about output of the function to make the result of +// the tests more readable. +// @todo(vbykoianko) It's necessary display the test name in the android log. static void AndroidLogMessage(LogLevel l, SrcPoint const & src, string const & s) { android_LogPriority pr = ANDROID_LOG_SILENT; diff --git a/android/UnitTests/src/com/mapswithme/maps/unittests/AllTestsActivity.java b/android/UnitTests/src/com/mapswithme/maps/unittests/AllTestsActivity.java index 5dcfb6355f..c4505adbc5 100644 --- a/android/UnitTests/src/com/mapswithme/maps/unittests/AllTestsActivity.java +++ b/android/UnitTests/src/com/mapswithme/maps/unittests/AllTestsActivity.java @@ -1,5 +1,5 @@ package com.mapswithme.maps.unittests; -// @todo(VB) This file should be generaged based on tests_list.h +// @todo(vbykoianko) This file should be generated based on tests_list.h public class AllTestsActivity extends android.app.NativeActivity { diff --git a/android/jni/com/mapswithme/maps/Framework.cpp b/android/jni/com/mapswithme/maps/Framework.cpp index 5d30fa47ca..4d0cc6e8c6 100644 --- a/android/jni/com/mapswithme/maps/Framework.cpp +++ b/android/jni/com/mapswithme/maps/Framework.cpp @@ -1305,52 +1305,55 @@ extern "C" Java_com_mapswithme_maps_Framework_nativeGetRouteFollowingInfo(JNIEnv * env, jclass thiz) { ::Framework * fr = frm(); - if (fr->IsRoutingActive()) + if (!fr->IsRoutingActive()) + return nullptr; + + location::FollowingInfo info; + fr->GetRouteFollowingInfo(info); + if (!info.IsValid()) + return nullptr; + + jclass const klass = env->FindClass("com/mapswithme/maps/LocationState$RoutingInfo"); + ASSERT(klass, (jni::DescribeException())); + static jmethodID const methodID = + env->GetMethodID(klass, "", + "(Ljava/lang/String;Ljava/lang/String;" + "Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;II[[B)V"); + ASSERT(methodID, (jni::DescribeException())); + + vector> const & lanes = info.m_lanes; + jobjectArray jLanes = nullptr; + if (!lanes.empty()) { - location::FollowingInfo info; - fr->GetRouteFollowingInfo(info); - if (info.IsValid()) + // A new java two-dimensional array for lane information is allocated here. + // Then it will be saved in com.mapswithme.maps.LocationState, and then removed by java + // GC. + jclass const myClassArray = env->FindClass("[B"); + ASSERT(myClassArray, (jni::DescribeException())); + size_t const lanesSize = lanes.size(); + jLanes = env->NewObjectArray(lanesSize, myClassArray, nullptr); + ASSERT(jLanes, (jni::DescribeException())); + jbyteArray jOneLane = nullptr; + + for (size_t j = 0; j < lanesSize; ++j) { - jclass const klass = env->FindClass("com/mapswithme/maps/LocationState$RoutingInfo"); - ASSERT(klass, (jni::DescribeException())); - static jmethodID const methodID = env->GetMethodID(klass, "", "(Ljava/lang/String;Ljava/lang/String;" - "Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;II[[I)V"); - ASSERT(methodID, (jni::DescribeException())); - - vector> const & lanes = info.m_lanes; - // A new java two-dimentional array for lane information is allocated here. - // Then it will be saved in com.mapswithme.maps.LocationState, and then removed by java GC. - jclass const myClassArray = env->FindClass("[I"); - ASSERT(myClassArray, (jni::DescribeException())); - size_t const lanesSize = lanes.size(); - jobjectArray const jLanes = env->NewObjectArray(lanesSize, myClassArray, nullptr); - ASSERT(jLanes, (jni::DescribeException())); - jintArray jOneLane = nullptr; - - for (size_t j = 0; j < lanesSize; ++j) - { - size_t const laneSize = lanes[j].size(); - jOneLane = env->NewIntArray(laneSize); - ASSERT(jOneLane, (jni::DescribeException())); - env->SetIntArrayRegion(jOneLane, 0, laneSize, lanes[j].data()); - env->SetObjectArrayElement(jLanes, j, jOneLane); - } - - - jobject const result = env->NewObject(klass, methodID, - jni::ToJavaString(env, info.m_distToTarget), - jni::ToJavaString(env, info.m_targetUnitsSuffix), - jni::ToJavaString(env, info.m_distToTurn), - jni::ToJavaString(env, info.m_turnUnitsSuffix), - jni::ToJavaString(env, info.m_trgName), - static_cast(info.m_turn), - info.m_time, - jLanes); - ASSERT(result, (jni::DescribeException())); - return result; + size_t const laneSize = lanes[j].size(); + jOneLane = env->NewByteArray(laneSize); + ASSERT(jOneLane, (jni::DescribeException())); + env->SetByteArrayRegion(jOneLane, 0, laneSize, lanes[j].data()); + env->SetObjectArrayElement(jLanes, j, jOneLane); + env->DeleteLocalRef(jOneLane); } } - return nullptr; + + jobject const result = env->NewObject( + klass, methodID, jni::ToJavaString(env, info.m_distToTarget), + jni::ToJavaString(env, info.m_targetUnitsSuffix), + jni::ToJavaString(env, info.m_distToTurn), + jni::ToJavaString(env, info.m_turnUnitsSuffix), jni::ToJavaString(env, info.m_targetName), + info.m_turn, info.m_time, jLanes); + ASSERT(result, (jni::DescribeException())); + return result; } JNIEXPORT jobject JNICALL diff --git a/android/src/com/mapswithme/maps/LocationState.java b/android/src/com/mapswithme/maps/LocationState.java index 103c7dd07d..562d8ba682 100644 --- a/android/src/com/mapswithme/maps/LocationState.java +++ b/android/src/com/mapswithme/maps/LocationState.java @@ -36,7 +36,7 @@ public enum LocationState public String mTurnUnitsSuffix; // The next street according to the navigation route. - public String mTrgName; + public String mTargetName; public TurnDirection mTurnDirection; @@ -79,17 +79,18 @@ public enum LocationState } /** - * IMPORTANT : Order of enum values MUST BE the same with native Lane enum. - * Information for every lane is composed of some number values bellow. - * For example, a lane could have THROUGH and RIGHT values. + * 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. */ - enum Lane + enum LaneWay { NONE, REVERSE, SHARP_LEFT, LEFT, - SLIGH_LEFT, + SLIGHT_LEFT, MERGE_TO_RIGHT, THROUGH, MERGE_TO_LEFT, @@ -98,7 +99,7 @@ public enum LocationState SHARP_RIGHT }; - private void DumpLanes(int[][] lanes) + private void DumpLanes(byte[][] lanes) { for (int j = 0; j < lanes.length; j++) { @@ -112,12 +113,15 @@ public enum LocationState } public RoutingInfo(String distToTarget, String units, String distTurn, String turnSuffix, String trgName, int direction, int totalTime - , int[][] lanes) + , byte[][] lanes) { - // The size of lanes array is not zero if any lane information is available and should be displayed. + // lanes is not equal to null if any lane information is available and should be displayed. // If so, lanes contains values of Lane enum for every lane. // Log.d("JNIARRAY", "RoutingInfo(" + distToTarget + ", " + units + ", " + distTurn + ", ... , " + trgName); - // DumpLanes(lanes); + // if (lanes == null) + // Log.d("JNIARRAY", "lanes is empty"); + // else + // DumpLanes(lanes); //@todo use lanes and trgName in java code. @@ -125,7 +129,7 @@ public enum LocationState mUnits = units; mTurnUnitsSuffix = turnSuffix; mDistToTurn = distTurn; - mTrgName = trgName; + mTargetName = trgName; mTotalTimeInSeconds = totalTime; mTurnDirection = TurnDirection.values()[direction]; } diff --git a/indexer/indexer_tests/jni/Android.mk b/indexer/indexer_tests/jni/Android.mk index 7aba10c34e..5211049c1a 100644 --- a/indexer/indexer_tests/jni/Android.mk +++ b/indexer/indexer_tests/jni/Android.mk @@ -1,4 +1,4 @@ -# @todo(VB) +# @todo(vbykoianko) # Probably this file shell be generated with a script based on .pro. # If you do this take into acount: # - the order of libs is important. It solves some linking problems; diff --git a/indexer/indexer_tests/jni/test.hpp b/indexer/indexer_tests/jni/test.hpp index bc91ebeffd..a7b6e84a01 100644 --- a/indexer/indexer_tests/jni/test.hpp +++ b/indexer/indexer_tests/jni/test.hpp @@ -1,4 +1,5 @@ -// @todo(VB) files test.hpp and test.cpp should be generated with a script based on tests_list.sh file or *.pro files +// @todo(vbykoianko) files test.hpp and test.cpp should be generated with a script based on +// tests_list.sh file or *.pro files #pragma once struct android_app; diff --git a/indexer/mercator.hpp b/indexer/mercator.hpp index 27fc5b95fb..fd4a7cdcd5 100644 --- a/indexer/mercator.hpp +++ b/indexer/mercator.hpp @@ -1,4 +1,5 @@ -// @todo(VB) the mercator compilation unit is located in the indexer module for the time being. +// @todo(vbykoianko) the mercator compilation unit is located in the indexer module for the time +// being. // On the other hand it is not connected with indexer anywhere. // The mercator unit is based on base, geometry. // I guess indexer is not the best place for this compilation unit. diff --git a/integration_tests/jni/Android.mk b/integration_tests/jni/Android.mk index ccb8c04eab..0eb13287d1 100644 --- a/integration_tests/jni/Android.mk +++ b/integration_tests/jni/Android.mk @@ -1,4 +1,4 @@ -# @todo(VB) +# @todo(vbykoianko) # Probably this file shell be generated with a script based on .pro. # If you do this take into acount: # - the order of libs is important. It solves some linking problems; diff --git a/integration_tests/osrm_test_tools.cpp b/integration_tests/osrm_test_tools.cpp index c24e98516b..4afd863800 100644 --- a/integration_tests/osrm_test_tools.cpp +++ b/integration_tests/osrm_test_tools.cpp @@ -224,7 +224,7 @@ namespace integration turns::TurnGeom const & turnGeom = turnsGeom[referenceTurnNumber]; ASSERT_LESS(turnGeom.m_turnIndex, turnGeom.m_points.size(), ()); - Route::TurnItem const & turn = turns[referenceTurnNumber]; + TurnItem const & turn = turns[referenceTurnNumber]; return TestTurn(turnGeom.m_points[turnGeom.m_turnIndex], turn.m_turn, turn.m_exitNum); } @@ -242,7 +242,7 @@ namespace integration m2::PointD const turnPnt = turnGeom.m_points[turnGeom.m_turnIndex]; if (ms::DistanceOnEarth(turnPnt.y, turnPnt.x, referenceTurnPnt.y, referenceTurnPnt.x) <= inaccuracy) { - Route::TurnItem const & turn = turns[i]; + TurnItem const & turn = turns[i]; return TestTurn(turnPnt, turn.m_turn, turn.m_exitNum); } } diff --git a/map/routing_session.cpp b/map/routing_session.cpp index 2fb9861830..9798180729 100644 --- a/map/routing_session.cpp +++ b/map/routing_session.cpp @@ -160,34 +160,37 @@ void RoutingSession::GetRouteFollowingInfo(FollowingInfo & info) const formatDistFn(m_route.GetCurrentDistanceToEnd(), info.m_distToTarget, info.m_targetUnitsSuffix); double dist; - Route::TurnItem turn; + TurnItem turn; m_route.GetTurn(dist, turn); formatDistFn(dist, info.m_distToTurn, info.m_turnUnitsSuffix); info.m_turn = turn.m_turn; info.m_exitNum = turn.m_exitNum; info.m_time = m_route.GetTime(); - info.m_trgName = turn.m_trgName; - // @todo(VB) the distance should depend on the current speed. - unsigned int const showLanesDistInMeters = 500; - if (dist < showLanesDistInMeters) + info.m_targetName = turn.m_targetName; + // @todo(vbykoianko) The distance should depend on the current speed. + double const kShowLanesDistInMeters = 500.; + if (dist < kShowLanesDistInMeters) { - // There are two nested for-loops bellow. Outer one is for lanes and inner one(transform) is for every lane directions. - // The meening of the code bellow is info.m_lanes = turn.m_lanes; (vector> = vector>). - // The size of turn.m_lanes is relatively small. Less then 10 for most cases. + // There are two nested for-loops below. Outer one is for lanes and inner one (transform) is + // for each lane's directions. + // The meaning of the code below is info.m_lanes = turn.m_lanes; (vector> = + // vector>). + // The size of turn.m_lanes is relatively small. Less than 10 in most cases. info.m_lanes.clear(); - size_t const lanesSize = turn.m_lanes.size(); - for (size_t j = 0; j < lanesSize; ++j) + for (size_t j = 0; j < turn.m_lanes.size(); ++j) { - info.m_lanes.push_back(vector()); - vector & back = info.m_lanes.back(); - back.reserve(turn.m_lanes[j].size()); - transform(turn.m_lanes[j].begin(), turn.m_lanes[j].end(), back_inserter(back), - [] (routing::turns::Lane l) { return static_cast(l); }); + vector lane; + lane.reserve(turn.m_lanes[j].size()); + transform(turn.m_lanes[j].begin(), turn.m_lanes[j].end(), back_inserter(lane), + [](routing::turns::LaneWay l) { return static_cast(l); }); + info.m_lanes.push_back(move(lane)); } } else + { info.m_lanes.clear(); + } } else { @@ -195,7 +198,7 @@ void RoutingSession::GetRouteFollowingInfo(FollowingInfo & info) const info.m_turn = turns::NoTurn; info.m_exitNum = 0; info.m_time = 0; - info.m_trgName.clear(); + info.m_targetName.clear(); info.m_lanes.clear(); } } diff --git a/platform/location.hpp b/platform/location.hpp index 6a65949fdf..a93aada073 100644 --- a/platform/location.hpp +++ b/platform/location.hpp @@ -8,7 +8,6 @@ #include "std/string.hpp" #include "std/vector.hpp" - namespace location { /// @note Do not change values of this constants. @@ -110,9 +109,9 @@ namespace location int m_time; // m_lanes contains lane information on the edge before the turn. // Template parameter int is used for passing the information to Android and iOS. - vector> m_lanes; + vector> m_lanes; // The next street name - string m_trgName; + string m_targetName; bool IsValid() const { return !m_distToTarget.empty(); } }; diff --git a/routing/osrm_router.cpp b/routing/osrm_router.cpp index 09b51060d3..3cab2686a1 100644 --- a/routing/osrm_router.cpp +++ b/routing/osrm_router.cpp @@ -1,4 +1,5 @@ #include "routing/osrm_router.hpp" +#include "routing/turns_generator.hpp" #include "routing/vehicle_model.hpp" #include "platform/platform.hpp" @@ -348,15 +349,11 @@ public: } }; -OsrmMappingTypes::FtSeg GetSegment(PathData const & node, RoutingMapping const & routingMapping, - function)> GetIndex) +size_t GetLastSegmentPointIndex(pair const & p) { - auto nSegs = routingMapping.m_segMapping.GetSegmentsRange(node.node); - OsrmMappingTypes::FtSeg seg; - routingMapping.m_segMapping.GetSegmentByIndex(GetIndex(nSegs), seg); - return seg; + ASSERT_GREATER(p.second, 0, ()); + return p.second - 1; } - } // namespace RoutingMappingPtrT RoutingIndexManager::GetMappingByPoint(m2::PointD const & point, Index const * pIndex) @@ -1033,7 +1030,7 @@ OsrmRouter::ResultCode OsrmRouter::MakeTurnAnnotation(RawRoutingResultT const & if (j > 0 && !points.empty()) { - Route::TurnItem t; + TurnItem t; t.m_index = points.size() - 1; GetTurnDirection(routingResult.m_routePath.unpacked_path_segments[i][j - 1], @@ -1042,44 +1039,10 @@ OsrmRouter::ResultCode OsrmRouter::MakeTurnAnnotation(RawRoutingResultT const & if (t.m_turn != turns::NoTurn) { // adding lane info - OsrmMappingTypes::FtSeg const seg1 = GetSegment(routingResult.m_routePath.unpacked_path_segments[i][j - 1], *mapping, - [](pair const & p) - { - ASSERT_GREATER(p.second, 0, ()); - return p.second - 1; - }); - if (seg1.IsValid()) - { - FeatureType ft1; - Index::FeaturesLoaderGuard loader1(*m_pIndex, mapping->GetMwmId()); - loader1.GetFeature(seg1.m_fid, ft1); - ft1.ParseMetadata(); - vector> lanes; - - if (seg1.m_pointStart < seg1.m_pointEnd) - { - // forward direction including oneway streets - string const turnLanes = ft1.GetMetadata().Get(feature::FeatureMetadata::FMD_TURN_LANES); - if (routing::turns::ParseLanes(turnLanes, lanes)) - { - t.m_lanes = lanes; - } - else - { - string const turnLanesForward = ft1.GetMetadata().Get(feature::FeatureMetadata::FMD_TURN_LANES_FORWARD); - if (routing::turns::ParseLanes(turnLanesForward, lanes)) - t.m_lanes = lanes; - } - } - else - { - // backward direction - string const turnLanesBackward = ft1.GetMetadata().Get(feature::FeatureMetadata::FMD_TURN_LANES_BACKWARD); - if (routing::turns::ParseLanes(turnLanesBackward, lanes)) - t.m_lanes = lanes; - } - } - turnsDir.push_back(t); + t.m_lanes = + turns::GetLanesInfo(routingResult.m_routePath.unpacked_path_segments[i][j - 1], + *mapping, GetLastSegmentPointIndex, *m_pIndex); + turnsDir.push_back(move(t)); } // osrm multiple seconds to 10, so we need to divide it back @@ -1178,15 +1141,15 @@ OsrmRouter::ResultCode OsrmRouter::MakeTurnAnnotation(RawRoutingResultT const & times.push_back(Route::TimeItemT(points.size() - 1, estimateTime)); if (routingResult.m_targetEdge.m_seg.IsValid()) - turnsDir.push_back(Route::TurnItem(points.size() - 1, turns::ReachedYourDestination)); - FixupTurns(points, turnsDir); + turnsDir.push_back(TurnItem(points.size() - 1, turns::ReachedYourDestination)); + turns::FixupTurns(points, turnsDir); - CalculateTurnGeometry(points, turnsDir, turnsGeom); + turns::CalculateTurnGeometry(points, turnsDir, turnsGeom); #ifdef _DEBUG for (auto t : turnsDir) { - LOG(LDEBUG, (turns::GetTurnString(t.m_turn), ":", t.m_index, t.m_srcName, "-", t.m_trgName, "exit:", t.m_exitNum)); + LOG(LDEBUG, (turns::GetTurnString(t.m_turn), ":", t.m_index, t.m_sourceName, "-", t.m_targetName, "exit:", t.m_exitNum)); } size_t last = 0; @@ -1394,8 +1357,9 @@ turns::TurnDirection OsrmRouter::IntermediateDirection(const double angle) const return turns::NoTurn; } -bool OsrmRouter::KeepOnewayOutgoingTurnIncomingEdges(Route::TurnItem const & turn, - m2::PointD const & p, m2::PointD const & p1OneSeg, RoutingMappingPtrT const & mapping) const +bool OsrmRouter::KeepOnewayOutgoingTurnIncomingEdges(TurnItem const & turn, + m2::PointD const & p, m2::PointD const & p1OneSeg, + RoutingMappingPtrT const & mapping) const { ASSERT(mapping.get(), ()); size_t const outgoingNotesCount = 1; @@ -1417,7 +1381,7 @@ bool OsrmRouter::KeepOnewayOutgoingTurnRoundabout(bool isRound1, bool isRound2) } turns::TurnDirection OsrmRouter::RoundaboutDirection(bool isRound1, bool isRound2, - bool hasMultiTurns, Route::TurnItem const & turn) const + bool hasMultiTurns) const { if (isRound1 && isRound2) { @@ -1437,19 +1401,19 @@ turns::TurnDirection OsrmRouter::RoundaboutDirection(bool isRound1, bool isRound return turns::NoTurn; } +// @todo(vbykoianko) Move this method and all dependencies to turns_generator.cpp void OsrmRouter::GetTurnDirection(PathData const & node1, PathData const & node2, - RoutingMappingPtrT const & routingMapping, Route::TurnItem & turn) + RoutingMappingPtrT const & routingMapping, TurnItem & turn) { ASSERT(routingMapping.get(), ()); - OsrmMappingTypes::FtSeg const seg1 = GetSegment(node1, *routingMapping, - [] (pair const & p) - { - ASSERT_GREATER(p.second, 0, ()); - return p.second - 1; - }); - OsrmMappingTypes::FtSeg const seg2 = GetSegment(node2, *routingMapping, - [] (pair const & p) { return p.first; }); + OsrmMappingTypes::FtSeg const seg1 = + turns::GetSegment(node1, *routingMapping, GetLastSegmentPointIndex); + OsrmMappingTypes::FtSeg const seg2 = + turns::GetSegment(node2, *routingMapping, [](pair const & p) + { + return p.first; + }); if (!seg1.IsValid() || !seg2.IsValid()) { @@ -1506,7 +1470,7 @@ void OsrmRouter::GetTurnDirection(PathData const & node1, if (isRound1 || isRound2) { - turn.m_turn = RoundaboutDirection(isRound1, isRound2, hasMultiTurns, turn); + turn.m_turn = RoundaboutDirection(isRound1, isRound2, hasMultiTurns); return; } @@ -1516,11 +1480,11 @@ void OsrmRouter::GetTurnDirection(PathData const & node1, // get names string name1, name2; { - ft1.GetName(FeatureType::DEFAULT_LANG, turn.m_srcName); - ft2.GetName(FeatureType::DEFAULT_LANG, turn.m_trgName); + ft1.GetName(FeatureType::DEFAULT_LANG, turn.m_sourceName); + ft2.GetName(FeatureType::DEFAULT_LANG, turn.m_targetName); - search::GetStreetNameAsKey(turn.m_srcName, name1); - search::GetStreetNameAsKey(turn.m_trgName, name2); + search::GetStreetNameAsKey(turn.m_sourceName, name1); + search::GetStreetNameAsKey(turn.m_targetName, name2); } string road1 = ft1.GetRoadNumber(); @@ -1553,92 +1517,8 @@ void OsrmRouter::GetTurnDirection(PathData const & node1, turn.m_turn = turns::UTurn; } -void OsrmRouter::CalculateTurnGeometry(vector const & points, Route::TurnsT const & turnsDir, turns::TurnsGeomT & turnsGeom) const -{ - size_t const pointsSz = points.size(); - for (Route::TurnItem const & t : turnsDir) - { - ASSERT(t.m_index < pointsSz, ()); - if (t.m_index == 0 || t.m_index == (pointsSz - 1)) - continue; - - uint32_t const beforePivotCount = 10; - /// afterPivotCount is more because there are half body and the arrow after the pivot point - uint32_t const afterPivotCount = beforePivotCount + 10; - uint32_t const fromIndex = (t.m_index <= beforePivotCount) ? 0 : t.m_index - beforePivotCount; - uint32_t const toIndex = min(pointsSz, t.m_index + afterPivotCount); - uint32_t const turnIndex = (t.m_index <= beforePivotCount ? t.m_index : beforePivotCount); - turnsGeom.emplace_back(t.m_index, turnIndex, points.begin() + fromIndex, points.begin() + toIndex); - } -} - -void OsrmRouter::FixupTurns(vector const & points, Route::TurnsT & turnsDir) const -{ - uint32_t exitNum = 0; - Route::TurnItem * roundabout = 0; - - auto distance = [&points](uint32_t start, uint32_t end) - { - double res = 0.0; - for (uint32_t i = start + 1; i < end; ++i) - res += MercatorBounds::DistanceOnEarth(points[i - 1], points[i]); - return res; - }; - - for (uint32_t idx = 0; idx < turnsDir.size(); ) - { - Route::TurnItem & t = turnsDir[idx]; - if (roundabout && t.m_turn != turns::StayOnRoundAbout && t.m_turn != turns::LeaveRoundAbout) - { - exitNum = 0; - roundabout = 0; - } - else if (t.m_turn == turns::EnterRoundAbout) - { - ASSERT_EQUAL(roundabout, 0, ()); - roundabout = &t; - } - else if (t.m_turn == turns::StayOnRoundAbout) - { - ++exitNum; - turnsDir.erase(turnsDir.begin() + idx); - continue; - } - else if (roundabout && t.m_turn == turns::LeaveRoundAbout) - { - roundabout->m_exitNum = exitNum + 1; - roundabout = 0; - exitNum = 0; - } - - double const mergeDist = 30.0; - - if (idx > 0 && - turns::IsStayOnRoad(turnsDir[idx - 1].m_turn) && - turns::IsLeftOrRightTurn(turnsDir[idx].m_turn) && - distance(turnsDir[idx - 1].m_index, turnsDir[idx].m_index) < mergeDist) - { - turnsDir.erase(turnsDir.begin() + idx - 1); - continue; - } - - if (!t.m_keepAnyway - && turns::IsGoStraightOrSlightTurn(t.m_turn) - && !t.m_srcName.empty() - && strings::AlmostEqual(t.m_srcName, t.m_trgName, 2)) - { - turnsDir.erase(turnsDir.begin() + idx); - continue; - } - - ++idx; - } -} - -IRouter::ResultCode OsrmRouter::FindPhantomNodes(string const & fName, m2::PointD const & point, - m2::PointD const & direction, - FeatureGraphNodeVecT & res, size_t maxCount, - RoutingMappingPtrT const & mapping) +IRouter::ResultCode OsrmRouter::FindPhantomNodes(string const & fName, m2::PointD const & point, m2::PointD const & direction, + FeatureGraphNodeVecT & res, size_t maxCount, RoutingMappingPtrT const & mapping) { Point2PhantomNode getter(mapping->m_segMapping, m_pIndex, direction); getter.SetPoint(point); diff --git a/routing/osrm_router.hpp b/routing/osrm_router.hpp index 9d17bc3984..c7668cf410 100644 --- a/routing/osrm_router.hpp +++ b/routing/osrm_router.hpp @@ -216,9 +216,7 @@ private: void GetTurnDirection(PathData const & node1, PathData const & node2, RoutingMappingPtrT const & routingMapping, - Route::TurnItem & turn); - void CalculateTurnGeometry(vector const & points, Route::TurnsT const & turnsDir, turns::TurnsGeomT & turnsGeom) const; - void FixupTurns(vector const & points, Route::TurnsT & turnsDir) const; + TurnItem & turn); m2::PointD GetPointForTurnAngle(OsrmMappingTypes::FtSeg const & seg, FeatureType const & ft, m2::PointD const & turnPnt, size_t (*GetPndInd)(const size_t, const size_t, const size_t)) const; @@ -228,11 +226,11 @@ private: turns::TurnDirection IntermediateDirection(double angle) const; void GetTurnGeometry(m2::PointD const & p, m2::PointD const & p1, OsrmRouter::GeomTurnCandidateT & candidates, RoutingMappingPtrT const & mapping) const; - bool KeepOnewayOutgoingTurnIncomingEdges(Route::TurnItem const & turn, + bool KeepOnewayOutgoingTurnIncomingEdges(TurnItem const & turn, m2::PointD const & p, m2::PointD const & p1, RoutingMappingPtrT const & mapping) const; bool KeepOnewayOutgoingTurnRoundabout(bool isRound1, bool isRound2) const; turns::TurnDirection RoundaboutDirection(bool isRound1, bool isRound2, - bool hasMultiTurns, Route::TurnItem const & turn) const; + bool hasMultiTurns) const; Index const * m_pIndex; diff --git a/routing/route.cpp b/routing/route.cpp index 963f46f455..4b67923724 100644 --- a/routing/route.cpp +++ b/routing/route.cpp @@ -21,6 +21,18 @@ static double const LOCATION_TIME_THRESHOLD = 60.0*1.0; static double const ON_ROAD_TOLERANCE_M = 50.0; static double const ON_END_TOLERANCE_M = 10.0; +string DebugPrint(TurnItem const & turnItem) +{ + stringstream out; + out << "[ TurnItem: m_index = " << turnItem.m_index + << ", m_turn = " << turnItem.m_turn + << ", m_exitNum = " << turnItem.m_exitNum + << ", m_sourceName = " << turnItem.m_sourceName + << ", m_targetName = " << turnItem.m_targetName + << ", m_keepAnyway = " << turnItem.m_keepAnyway << " ]" << endl; + return out.str(); +} + Route::Route(string const & router, vector const & points, string const & name) : m_router(router), m_poly(points), m_name(name) @@ -132,14 +144,14 @@ uint32_t Route::GetTime() const return (uint32_t)((GetAllTime() - (*it).second)); } -void Route::GetTurn(double & distance, Route::TurnItem & turn) const +void Route::GetTurn(double & distance, TurnItem & turn) const { if (m_segDistance.empty() || m_turns.empty()) { ASSERT(!m_segDistance.empty(), ()); ASSERT(!m_turns.empty(), ()); distance = 0; - turn = Route::TurnItem(); + turn = TurnItem(); return; } diff --git a/routing/route.hpp b/routing/route.hpp index 38455743fb..00b88d515d 100644 --- a/routing/route.hpp +++ b/routing/route.hpp @@ -17,33 +17,43 @@ namespace location namespace routing { +struct TurnItem +{ + TurnItem() + : m_index(numeric_limits::max()) + , m_turn(turns::NoTurn), m_exitNum(0), m_keepAnyway(false) + { + } + + TurnItem(uint32_t idx, turns::TurnDirection t, uint32_t exitNum = 0) + : m_index(idx), m_turn(t), m_exitNum(exitNum), m_keepAnyway(false) + { + } + + bool operator==(TurnItem const & rhs) const + { + return m_index == rhs.m_index && m_turn == rhs.m_turn + && m_lanes == rhs.m_lanes && m_exitNum == rhs.m_exitNum + && m_sourceName == rhs.m_sourceName && m_targetName == rhs.m_targetName + && m_keepAnyway == rhs.m_keepAnyway; + } + + uint32_t m_index; // Index of point on polyline (number of segment + 1). + turns::TurnDirection m_turn; + vector m_lanes; // Lane information on the edge before the turn. + uint32_t m_exitNum; // Number of exit on roundabout. + string m_sourceName; + string m_targetName; + // m_keepAnyway is true if the turn shall not be deleted + // and shall be demonstrated to an end user. + bool m_keepAnyway; +}; + +string DebugPrint(TurnItem const & turnItem); + class Route { public: - - struct TurnItem - { - TurnItem() - : m_index(std::numeric_limits::max()) - , m_turn(turns::NoTurn), m_exitNum(0), m_keepAnyway(false) - { - } - - TurnItem(uint32_t idx, turns::TurnDirection t) - : m_index(idx), m_turn(t), m_exitNum(0), m_keepAnyway(false) - { - } - - uint32_t m_index; // number of point on polyline (number of segment + 1) - turns::TurnDirection m_turn; - vector> m_lanes; // lane information on the edge before the turn. - uint32_t m_exitNum; // number of exit on roundabout - string m_srcName; - string m_trgName; - bool m_keepAnyway; // m_keepAnyway is equel to true if the turn shell not be deleted - // and shell be demonstrated to an end user - }; - typedef vector TurnsT; typedef pair TimeItemT; @@ -101,7 +111,7 @@ public: double GetCurrentDistanceToEnd() const; //@} - void GetTurn(double & distance, Route::TurnItem & turn) const; + void GetTurn(double & distance, TurnItem & turn) const; /// @return true If position was updated successfully (projection within gps error radius). bool MoveIterator(location::GpsInfo const & info) const; diff --git a/routing/routing.pro b/routing/routing.pro index 096cf03431..aa40301050 100644 --- a/routing/routing.pro +++ b/routing/routing.pro @@ -25,6 +25,7 @@ SOURCES += \ route.cpp \ routing_mapping.cpp \ turns.cpp \ + turns_generator.cpp \ vehicle_model.cpp \ HEADERS += \ @@ -45,4 +46,5 @@ HEADERS += \ router.hpp \ routing_mapping.h \ turns.hpp \ + turns_generator.hpp \ vehicle_model.hpp \ diff --git a/routing/routing_tests/osrm_router_test.cpp b/routing/routing_tests/osrm_router_test.cpp index 3b93179472..b5d6d342b8 100644 --- a/routing/routing_tests/osrm_router_test.cpp +++ b/routing/routing_tests/osrm_router_test.cpp @@ -14,7 +14,6 @@ #include "base/scope_guard.hpp" #include "std/bind.hpp" -#include "std/string.hpp" #include "std/unique_ptr.hpp" #include "std/vector.hpp" @@ -244,132 +243,3 @@ UNIT_TEST(OsrmFtSegMappingBuilder_Smoke) TestMapping(data, nodeIds, ranges); } } - -UNIT_TEST(TestParseLanesToStrings) -{ - vector result; - routing::turns::ParseLanesToStrings("through|through|through|through;right", '|', result); - TEST_EQUAL(result.size(), 4, ()); - TEST_EQUAL(result[0], "through", ()); - TEST_EQUAL(result[1], "through", ()); - TEST_EQUAL(result[2], "through", ()); - TEST_EQUAL(result[3], "through;right", ()); - - routing::turns::ParseLanesToStrings("adsjkddfasui8747&sxdsdlad8\"\'", '|', result); - TEST_EQUAL(result.size(), 1, ()); - TEST_EQUAL(result[0], "adsjkddfasui8747&sxdsdlad8\"\'", ()); - - routing::turns::ParseLanesToStrings("|||||||", '|', result); - TEST_EQUAL(result.size(), 7, ()); - TEST_EQUAL(result[0], "", ()); - TEST_EQUAL(result[1], "", ()); - TEST_EQUAL(result[2], "", ()); - TEST_EQUAL(result[3], "", ()); - TEST_EQUAL(result[4], "", ()); - TEST_EQUAL(result[5], "", ()); - TEST_EQUAL(result[6], "", ()); -} - -UNIT_TEST(TestParseOneLane) -{ - vector result; - TEST(routing::turns::ParseOneLane("through;right", ';', result), ()); - TEST_EQUAL(result.size(), 2, ()); - TEST_EQUAL(result[0], routing::turns::Lane::THROUGH, ()); - TEST_EQUAL(result[1], routing::turns::Lane::RIGHT, ()); - - TEST(!routing::turns::ParseOneLane("through;Right", ';', result), ()); - TEST_EQUAL(result.size(), 0, ()); - - TEST(!routing::turns::ParseOneLane("through ;right", ';', result), ()); - TEST_EQUAL(result.size(), 0, ()); - - TEST(!routing::turns::ParseOneLane("SD32kk*887;;", ';', result), ()); - TEST_EQUAL(result.size(), 0, ()); - - TEST(!routing::turns::ParseOneLane("Что-то на кириллице", ';', result), ()); - TEST_EQUAL(result.size(), 0, ()); - - TEST(!routing::turns::ParseOneLane("משהו בעברית", ';', result), ()); - TEST_EQUAL(result.size(), 0, ()); - - TEST(routing::turns::ParseOneLane("left;through", ';', result), ()); - TEST_EQUAL(result.size(), 2, ()); - TEST_EQUAL(result[0], routing::turns::Lane::LEFT, ()); - TEST_EQUAL(result[1], routing::turns::Lane::THROUGH, ()); - - TEST(routing::turns::ParseOneLane("left", ';', result), ()); - TEST_EQUAL(result.size(), 1, ()); - TEST_EQUAL(result[0], routing::turns::Lane::LEFT, ()); -} - -UNIT_TEST(TestParseLanes) -{ - vector> result; - TEST(routing::turns::ParseLanes("through|through|through|through;right", result), ()); - TEST_EQUAL(result.size(), 4, ()); - TEST_EQUAL(result[0].size(), 1, ()); - TEST_EQUAL(result[3].size(), 2, ()); - TEST_EQUAL(result[0][0], routing::turns::Lane::THROUGH, ()); - TEST_EQUAL(result[3][0], routing::turns::Lane::THROUGH, ()); - TEST_EQUAL(result[3][1], routing::turns::Lane::RIGHT, ()); - - TEST(routing::turns::ParseLanes("left|left;through|through|through", result), ()); - TEST_EQUAL(result.size(), 4, ()); - TEST_EQUAL(result[0].size(), 1, ()); - TEST_EQUAL(result[1].size(), 2, ()); - TEST_EQUAL(result[3].size(), 1, ()); - TEST_EQUAL(result[0][0], routing::turns::Lane::LEFT, ()); - TEST_EQUAL(result[1][0], routing::turns::Lane::LEFT, ()); - TEST_EQUAL(result[1][1], routing::turns::Lane::THROUGH, ()); - TEST_EQUAL(result[3][0], routing::turns::Lane::THROUGH, ()); - - TEST(routing::turns::ParseLanes("left|through|through", result), ()); - TEST_EQUAL(result.size(), 3, ()); - TEST_EQUAL(result[0].size(), 1, ()); - TEST_EQUAL(result[1].size(), 1, ()); - TEST_EQUAL(result[2].size(), 1, ()); - TEST_EQUAL(result[0][0], routing::turns::Lane::LEFT, ()); - TEST_EQUAL(result[1][0], routing::turns::Lane::THROUGH, ()); - TEST_EQUAL(result[2][0], routing::turns::Lane::THROUGH, ()); - - TEST(routing::turns::ParseLanes("left|le ft| through|through | right", result), ()); - TEST_EQUAL(result.size(), 5, ()); - TEST_EQUAL(result[0].size(), 1, ()); - TEST_EQUAL(result[4].size(), 1, ()); - TEST_EQUAL(result[0][0], routing::turns::Lane::LEFT, ()); - TEST_EQUAL(result[1][0], routing::turns::Lane::LEFT, ()); - TEST_EQUAL(result[2][0], routing::turns::Lane::THROUGH, ()); - TEST_EQUAL(result[3][0], routing::turns::Lane::THROUGH, ()); - TEST_EQUAL(result[4][0], routing::turns::Lane::RIGHT, ()); - - TEST(routing::turns::ParseLanes("left|Left|through|througH|right", result), ()); - TEST_EQUAL(result.size(), 5, ()); - TEST_EQUAL(result[0].size(), 1, ()); - TEST_EQUAL(result[4].size(), 1, ()); - TEST_EQUAL(result[0][0], routing::turns::Lane::LEFT, ()); - TEST_EQUAL(result[4][0], routing::turns::Lane::RIGHT, ()); - - TEST(routing::turns::ParseLanes("left|Left|through|througH|through;right;sharp_rIght", result), ()); - TEST_EQUAL(result.size(), 5, ()); - TEST_EQUAL(result[0].size(), 1, ()); - TEST_EQUAL(result[4].size(), 3, ()); - TEST_EQUAL(result[0][0], routing::turns::Lane::LEFT, ()); - TEST_EQUAL(result[4][0], routing::turns::Lane::THROUGH, ()); - TEST_EQUAL(result[4][1], routing::turns::Lane::RIGHT, ()); - TEST_EQUAL(result[4][2], routing::turns::Lane::SHARP_RIGHT, ()); - - TEST(!routing::turns::ParseLanes("left|Leftt|through|througH|right", result), ()); - TEST_EQUAL(result.size(), 0, ()); - - TEST(!routing::turns::ParseLanes("Что-то на кириллице", result), ()); - TEST_EQUAL(result.size(), 0, ()); - - TEST(!routing::turns::ParseLanes("משהו בעברית", result), ()); - TEST_EQUAL(result.size(), 0, ()); - - TEST(routing::turns::ParseLanes("left |Left|through|througH|right", result), ()); - TEST_EQUAL(result.size(), 5, ()); - TEST_EQUAL(result[0][0], routing::turns::Lane::LEFT, ()); - TEST_EQUAL(result[1][0], routing::turns::Lane::LEFT, ()); -} diff --git a/routing/routing_tests/routing_tests.pro b/routing/routing_tests/routing_tests.pro index 02c12ea782..01096e62e7 100644 --- a/routing/routing_tests/routing_tests.pro +++ b/routing/routing_tests/routing_tests.pro @@ -25,6 +25,7 @@ SOURCES += \ osrm_router_test.cpp \ road_graph_builder.cpp \ road_graph_nearest_turns_test.cpp \ + turns_generator_test.cpp \ vehicle_model_test.cpp \ HEADERS += \ diff --git a/routing/routing_tests/turns_generator_test.cpp b/routing/routing_tests/turns_generator_test.cpp new file mode 100644 index 0000000000..b78fb94335 --- /dev/null +++ b/routing/routing_tests/turns_generator_test.cpp @@ -0,0 +1,272 @@ +#include "testing/testing.hpp" + +#include "routing/route.hpp" +#include "routing/turns.hpp" +#include "routing/turns_generator.hpp" + +#include "indexer/mercator.hpp" + +#include "geometry/point2d.hpp" + +#include "std/cmath.hpp" +#include "std/string.hpp" +#include "std/vector.hpp" + +using namespace routing; + +namespace +{ +UNIT_TEST(TestSplitLanes) +{ + vector result; + routing::turns::SplitLanes("through|through|through|through;right", '|', result); + vector expected1 = {"through", "through", "through", "through;right"}; + TEST_EQUAL(result, expected1, ()); + + routing::turns::SplitLanes("adsjkddfasui8747&sxdsdlad8\"\'", '|', result); + TEST_EQUAL(result.size(), 1, ()); + TEST_EQUAL(result[0], "adsjkddfasui8747&sxdsdlad8\"\'", ()); + + routing::turns::SplitLanes("|||||||", '|', result); + vector expected2 = {"", "", "", "", "", "", ""}; + TEST_EQUAL(result, expected2, ()); +} + +UNIT_TEST(TestParseSingleLane) +{ + routing::turns::TSingleLane result; + TEST(routing::turns::ParseSingleLane("through;right", ';', result), ()); + vector expected1 = {routing::turns::LaneWay::Through, routing::turns::LaneWay::Right}; + TEST_EQUAL(result, expected1, ()); + + TEST(!routing::turns::ParseSingleLane("through;Right", ';', result), ()); + + TEST(!routing::turns::ParseSingleLane("through ;right", ';', result), ()); + TEST_EQUAL(result.size(), 0, ()); + + TEST(!routing::turns::ParseSingleLane("SD32kk*887;;", ';', result), ()); + TEST_EQUAL(result.size(), 0, ()); + + TEST(!routing::turns::ParseSingleLane("Что-то на кириллице", ';', result), ()); + TEST_EQUAL(result.size(), 0, ()); + + TEST(!routing::turns::ParseSingleLane("משהו בעברית", ';', result), ()); + TEST_EQUAL(result.size(), 0, ()); + + TEST(routing::turns::ParseSingleLane("left;through", ';', result), ()); + vector expected2 = {routing::turns::LaneWay::Left, routing::turns::LaneWay::Through}; + TEST_EQUAL(result, expected2, ()); + + TEST(routing::turns::ParseSingleLane("left", ';', result), ()); + TEST_EQUAL(result.size(), 1, ()); + TEST_EQUAL(result[0], routing::turns::LaneWay::Left, ()); +} + +UNIT_TEST(TestParseLanes) +{ + vector result; + TEST(routing::turns::ParseLanes("through|through|through|through;right", result), ()); + TEST_EQUAL(result.size(), 4, ()); + TEST_EQUAL(result[0].size(), 1, ()); + TEST_EQUAL(result[3].size(), 2, ()); + TEST_EQUAL(result[0][0], routing::turns::LaneWay::Through, ()); + TEST_EQUAL(result[3][0], routing::turns::LaneWay::Through, ()); + TEST_EQUAL(result[3][1], routing::turns::LaneWay::Right, ()); + + TEST(routing::turns::ParseLanes("left|left;through|through|through", result), ()); + TEST_EQUAL(result.size(), 4, ()); + TEST_EQUAL(result[0].size(), 1, ()); + TEST_EQUAL(result[1].size(), 2, ()); + TEST_EQUAL(result[3].size(), 1, ()); + TEST_EQUAL(result[0][0], routing::turns::LaneWay::Left, ()); + TEST_EQUAL(result[1][0], routing::turns::LaneWay::Left, ()); + TEST_EQUAL(result[1][1], routing::turns::LaneWay::Through, ()); + TEST_EQUAL(result[3][0], routing::turns::LaneWay::Through, ()); + + TEST(routing::turns::ParseLanes("left|through|through", result), ()); + TEST_EQUAL(result.size(), 3, ()); + TEST_EQUAL(result[0].size(), 1, ()); + TEST_EQUAL(result[1].size(), 1, ()); + TEST_EQUAL(result[2].size(), 1, ()); + TEST_EQUAL(result[0][0], routing::turns::LaneWay::Left, ()); + TEST_EQUAL(result[1][0], routing::turns::LaneWay::Through, ()); + TEST_EQUAL(result[2][0], routing::turns::LaneWay::Through, ()); + + TEST(routing::turns::ParseLanes("left|le ft| through|through | right", result), ()); + TEST_EQUAL(result.size(), 5, ()); + TEST_EQUAL(result[0].size(), 1, ()); + TEST_EQUAL(result[4].size(), 1, ()); + TEST_EQUAL(result[0][0], routing::turns::LaneWay::Left, ()); + TEST_EQUAL(result[1][0], routing::turns::LaneWay::Left, ()); + TEST_EQUAL(result[2][0], routing::turns::LaneWay::Through, ()); + TEST_EQUAL(result[3][0], routing::turns::LaneWay::Through, ()); + TEST_EQUAL(result[4][0], routing::turns::LaneWay::Right, ()); + + TEST(routing::turns::ParseLanes("left|Left|through|througH|right", result), ()); + TEST_EQUAL(result.size(), 5, ()); + TEST_EQUAL(result[0].size(), 1, ()); + TEST_EQUAL(result[4].size(), 1, ()); + TEST_EQUAL(result[0][0], routing::turns::LaneWay::Left, ()); + TEST_EQUAL(result[4][0], routing::turns::LaneWay::Right, ()); + + TEST(routing::turns::ParseLanes("left|Left|through|througH|through;right;sharp_rIght", result), ()); + TEST_EQUAL(result.size(), 5, ()); + TEST_EQUAL(result[0].size(), 1, ()); + TEST_EQUAL(result[4].size(), 3, ()); + TEST_EQUAL(result[0][0], routing::turns::LaneWay::Left, ()); + TEST_EQUAL(result[4][0], routing::turns::LaneWay::Through, ()); + TEST_EQUAL(result[4][1], routing::turns::LaneWay::Right, ()); + TEST_EQUAL(result[4][2], routing::turns::LaneWay::SharpRight, ()); + + TEST(!routing::turns::ParseLanes("left|Leftt|through|througH|right", result), ()); + TEST_EQUAL(result.size(), 0, ()); + + TEST(!routing::turns::ParseLanes("Что-то на кириллице", result), ()); + TEST_EQUAL(result.size(), 0, ()); + + TEST(!routing::turns::ParseLanes("משהו בעברית", result), ()); + TEST_EQUAL(result.size(), 0, ()); + + TEST(routing::turns::ParseLanes("left |Left|through|througH|right", result), ()); + TEST_EQUAL(result.size(), 5, ()); + TEST_EQUAL(result[0][0], routing::turns::LaneWay::Left, ()); + TEST_EQUAL(result[1][0], routing::turns::LaneWay::Left, ()); +} + +UNIT_TEST(TestFixupTurns) +{ + double const kHalfSquareSideMeters = 10.; + m2::PointD const kSquareCenterLonLat = {0., 0.}; + m2::RectD const kSquareNearZero = MercatorBounds::MetresToXY(kSquareCenterLonLat.x, + kSquareCenterLonLat.y, kHalfSquareSideMeters); + // Removing a turn in case staying on a roundabout. + vector const pointsMerc1 = { + { kSquareNearZero.minX(), kSquareNearZero.minY()}, + { kSquareNearZero.minX(), kSquareNearZero.maxY() }, + { kSquareNearZero.maxX(), kSquareNearZero.maxY() }, + { kSquareNearZero.maxX(), kSquareNearZero.minY() } + }; + // The constructor TurnItem(uint32_t idx, turns::TurnDirection t, uint32_t exitNum = 0) + // is used for initialization of vector below. + Route::TurnsT turnsDir1 = { + { 0, turns::EnterRoundAbout }, + { 1, turns::StayOnRoundAbout }, + { 2, turns::LeaveRoundAbout }, + { 3, turns::ReachedYourDestination } + }; + + turns::FixupTurns(pointsMerc1, turnsDir1); + Route::TurnsT expectedTurnDir1 = { + { 0, turns::EnterRoundAbout, 2 }, + { 2, turns::LeaveRoundAbout }, + { 3, turns::ReachedYourDestination } + }; + TEST_EQUAL(turnsDir1, expectedTurnDir1, ()); + + // Merging turns which are close to each other. + vector const pointsMerc2 = { + { kSquareNearZero.minX(), kSquareNearZero.minY()}, + { kSquareCenterLonLat.x, kSquareCenterLonLat.y }, + { kSquareNearZero.maxX(), kSquareNearZero.maxY() }}; + Route::TurnsT turnsDir2 = { + { 0, turns::GoStraight }, + { 1, turns::TurnLeft }, + { 2, turns::ReachedYourDestination } + }; + + turns::FixupTurns(pointsMerc2, turnsDir2); + Route::TurnsT expectedTurnDir2 = { + { 1, turns::TurnLeft }, + { 2, turns::ReachedYourDestination } + }; + TEST_EQUAL(turnsDir2, expectedTurnDir2, ()); + + // No turn is removed. + vector const pointsMerc3 = { + { kSquareNearZero.minX(), kSquareNearZero.minY()}, + { kSquareNearZero.minX(), kSquareNearZero.maxY() }, + { kSquareNearZero.maxX(), kSquareNearZero.maxY() }}; + Route::TurnsT turnsDir3 = { + { 1, turns::TurnRight }, + { 2, turns::ReachedYourDestination } + }; + + turns::FixupTurns(pointsMerc3, turnsDir3); + Route::TurnsT expectedTurnDir3 = { + { 1, turns::TurnRight }, + { 2, turns::ReachedYourDestination } + }; + TEST_EQUAL(turnsDir3, expectedTurnDir3, ()); +} + +UNIT_TEST(TestCalculateTurnGeometry) +{ + double constexpr kHalfSquareSideMeters = 10.; + double constexpr kSquareSideMeters = 2 * kHalfSquareSideMeters; + double const kErrorMeters = 1.; + m2::PointD const kSquareCenterLonLat = {0., 0.}; + m2::RectD const kSquareNearZero = MercatorBounds::MetresToXY(kSquareCenterLonLat.x, + kSquareCenterLonLat.y, kHalfSquareSideMeters); + + // Empty vectors + vector const points1; + Route::TurnsT const turnsDir1; + turns::TurnsGeomT turnsGeom1; + turns::CalculateTurnGeometry(points1, turnsDir1, turnsGeom1); + TEST(turnsGeom1.empty(), ()); + + // A turn is in the middle of a very short route. + vector const points2 = { + { kSquareNearZero.minX(), kSquareNearZero.minY() }, + { kSquareNearZero.maxX(), kSquareNearZero.maxY() }, + { kSquareNearZero.minX(), kSquareNearZero.maxY() } + }; + Route::TurnsT const turnsDir2 = { + { 1, turns::TurnLeft }, + { 2, turns::ReachedYourDestination } + }; + turns::TurnsGeomT turnsGeom2; + + turns::CalculateTurnGeometry(points2, turnsDir2, turnsGeom2); + TEST_EQUAL(turnsGeom2.size(), 1, ()); + TEST_EQUAL(turnsGeom2[0].m_indexInRoute, 1, ()); + TEST_EQUAL(turnsGeom2[0].m_turnIndex, 1, ()); + TEST_EQUAL(turnsGeom2[0].m_points.size(), 3, ()); + TEST_LESS(fabs(MercatorBounds::DistanceOnEarth(turnsGeom2[0].m_points[1], + turnsGeom2[0].m_points[2]) - kSquareSideMeters), + kErrorMeters, ()); + + // Two turns. One is in the very beginnig of a short route and another one is at the end. + // The first turn is located at point 0 and the second one at the point 3. + // 1----->2 + // ^ | + // | 4 | + // | \ v + // 0 3 + vector const points3 = { + { kSquareNearZero.minX(), kSquareNearZero.minY() }, + { kSquareNearZero.minX(), kSquareNearZero.maxY() }, + { kSquareNearZero.maxX(), kSquareNearZero.maxY() }, + { kSquareNearZero.maxX(), kSquareNearZero.minY() }, + { kSquareCenterLonLat.x, kSquareCenterLonLat.y } + }; + Route::TurnsT const turnsDir3 = { + { 0, turns::GoStraight }, + { 3, turns::TurnSharpRight }, + { 4, turns::ReachedYourDestination } + }; + turns::TurnsGeomT turnsGeom3; + + turns::CalculateTurnGeometry(points3, turnsDir3, turnsGeom3); + TEST_EQUAL(turnsGeom3.size(), 1, ()); + TEST_EQUAL(turnsGeom3[0].m_indexInRoute, 3, ()); + TEST_EQUAL(turnsGeom3[0].m_turnIndex, 3, ()); + TEST_EQUAL(turnsGeom3[0].m_points.size(), 5, ()); + TEST_LESS(fabs(MercatorBounds::DistanceOnEarth(turnsGeom3[0].m_points[1], + turnsGeom3[0].m_points[2]) - kSquareSideMeters), + kErrorMeters, ()); + TEST_LESS(fabs(MercatorBounds::DistanceOnEarth(turnsGeom3[0].m_points[2], + turnsGeom3[0].m_points[3]) - kSquareSideMeters), + kErrorMeters, ()); +} +} diff --git a/routing/turns.cpp b/routing/turns.cpp index 4d5e024558..ab2906c449 100644 --- a/routing/turns.cpp +++ b/routing/turns.cpp @@ -1,12 +1,39 @@ #include "routing/turns.hpp" +#include "std/array.hpp" + + +namespace +{ +using namespace routing::turns; +// The order is important. Starting with the most frequent tokens according to +// taginfo.openstreetmap.org to minimize the number of comparisons in ParseSingleLane(). +array, static_cast(LaneWay::Count)> const g_laneWayNames = +{{ + { LaneWay::Through, "through" }, + { LaneWay::Left, "left" }, + { LaneWay::Right, "right" }, + { LaneWay::None, "none" }, + { LaneWay::SharpLeft, "sharp_left" }, + { LaneWay::SlightLeft, "slight_left" }, + { LaneWay::MergeToRight, "merge_to_right" }, + { LaneWay::MergeToLeft, "merge_to_left" }, + { LaneWay::SlightRight, "slight_right" }, + { LaneWay::SharpRight, "sharp_right" }, + { LaneWay::Reverse, "reverse" } +}}; +static_assert(g_laneWayNames.size() == static_cast(LaneWay::Count), "Check the size of g_laneWayNames"); +} namespace routing { - namespace turns { - +bool TurnGeom::operator==(TurnGeom const & other) const +{ + return m_indexInRoute == other.m_indexInRoute && m_turnIndex == other.m_turnIndex + && m_points == other.m_points; +} string turnStrings[] = { "NoTurn", @@ -59,8 +86,7 @@ bool IsGoStraightOrSlightTurn(TurnDirection t) return (t == turns::GoStraight || t == turns::TurnSlightLeft || t == turns::TurnSlightRight); } - -void ParseLanesToStrings(string const & lanesString, char delimiter, vector & lanes) +void SplitLanes(string const & lanesString, char delimiter, vector & lanes) { lanes.clear(); istringstream lanesStream(lanesString); @@ -71,65 +97,40 @@ void ParseLanesToStrings(string const & lanesString, char delimiter, vector & lane) +bool ParseSingleLane(string const & laneString, char delimiter, TSingleLane & lane) { lane.clear(); istringstream laneStream(laneString); string token; while (getline(laneStream, token, delimiter)) { - Lane l = Lane::NONE; - // Staring compare with the most offen tokens according to taginfo.openstreetmap.org to minimize number of comparations. - if (token == "through") - l = Lane::THROUGH; - else if (token == "left") - l = Lane::LEFT; - else if (token == "right") - l = Lane::RIGHT; - else if (token == "none") - l = Lane::NONE; - else if (token == "sharp_left") - l = Lane::SHARP_LEFT; - else if (token == "slight_left") - l = Lane::SLIGH_LEFT; - else if (token == "merge_to_right") - l = Lane::MERGE_TO_RIGHT; - else if (token == "merge_to_left") - l = Lane::MERGE_TO_LEFT; - else if (token == "slight_right") - l = Lane::SLIGHT_RIGHT; - else if (token == "sharp_right") - l = Lane::SHARP_RIGHT; - else if (token == "reverse") - l = Lane::REVERSE; - else + auto const it = find_if(g_laneWayNames.begin(), g_laneWayNames.end(), + [&token](pair const & p) { - lane.clear(); + return p.second == token; + }); + if (it == g_laneWayNames.end()) return false; - } - lane.push_back(l); + lane.push_back(it->first); } return true; } -bool ParseLanes(string const & lanesString, vector> & lanes) +bool ParseLanes(string lanesString, vector & lanes) { - lanes.clear(); if (lanesString.empty()) return false; - // convert lanesString to lower case - string lanesStringLower; - lanesStringLower.reserve(lanesString.size()); - transform(lanesString.begin(), lanesString.end(), back_inserter(lanesStringLower), tolower); - // removing all spaces - lanesStringLower.erase(remove_if(lanesStringLower.begin(), lanesStringLower.end(), isspace), lanesStringLower.end()); + lanes.clear(); + transform(lanesString.begin(), lanesString.end(), lanesString.begin(), tolower); + lanesString.erase(remove_if(lanesString.begin(), lanesString.end(), isspace), + lanesString.end()); - vector lanesStrings; - vector lane; - ParseLanesToStrings(lanesStringLower, '|', lanesStrings); - for (string const & s : lanesStrings) + vector SplitLanesStrings; + TSingleLane lane; + SplitLanes(lanesString, '|', SplitLanesStrings); + for (string const & s : SplitLanesStrings) { - if (!ParseOneLane(s, ';', lane)) + if (!ParseSingleLane(s, ';', lane)) { lanes.clear(); return false; @@ -139,5 +140,28 @@ bool ParseLanes(string const & lanesString, vector> & lanes) return true; } +string DebugPrint(routing::turns::TurnGeom const & turnGeom) +{ + stringstream out; + out << "[ TurnGeom: m_indexInRoute = " << turnGeom.m_indexInRoute + << ", m_turnIndex = " << turnGeom.m_turnIndex << " ]" << endl; + return out.str(); +} + +string DebugPrint(routing::turns::LaneWay const l) +{ + stringstream out; + auto const it = find_if(g_laneWayNames.begin(), g_laneWayNames.end(), + [&l](pair const & p) + { + return p.first == l; + }); + + if (it == g_laneWayNames.end()) + out << "unknown LaneWay (" << static_cast(l) << ")"; + out << it->second; + return out.str(); +} + } } diff --git a/routing/turns.hpp b/routing/turns.hpp index e7d7361b63..a08c6e1f02 100644 --- a/routing/turns.hpp +++ b/routing/turns.hpp @@ -1,5 +1,6 @@ #pragma once +#include "std/iostream.hpp" #include "std/string.hpp" #include "std/vector.hpp" @@ -10,13 +11,16 @@ namespace routing namespace turns { +// @todo(vbykoianko) It's a good idea to gather all the turns information into one entity. +// For the time being several separate entities reflect turn information. Like Route::TurnsT or +// turns::TurnsGeomT // Do not change the order of right and left turns // TurnRight(TurnLeft) must have a minimal value // TurnSlightRight(TurnSlightLeft) must have a maximum value // to make check TurnRight <= turn <= TurnSlightRight work // -// turnStrings array in cpp file must be synchronized with state of TurnDirection enum. +// TurnDirection array in cpp file must be synchronized with state of TurnDirection enum in java. enum TurnDirection { NoTurn = 0, @@ -40,24 +44,27 @@ enum TurnDirection StartAtEndOfStreet, ReachedYourDestination, - }; -enum Lane +// LaneWay array in cpp file must be synchronized with state of LaneWay enum in java. +enum class LaneWay { - NONE = 0, - REVERSE, - SHARP_LEFT, - LEFT, - SLIGH_LEFT, - MERGE_TO_RIGHT, - THROUGH, - MERGE_TO_LEFT, - SLIGHT_RIGHT, - RIGHT, - SHARP_RIGHT + None = 0, + Reverse, + SharpLeft, + Left, + SlightLeft, + MergeToRight, + Through, + MergeToLeft, + SlightRight, + Right, + SharpRight, + Count // This value is used for internals only. }; +string DebugPrint(LaneWay const l); + struct TurnGeom { TurnGeom(uint32_t indexInRoute, uint32_t turnIndex, @@ -66,12 +73,17 @@ struct TurnGeom { } + bool operator==(TurnGeom const & other) const; + uint32_t m_indexInRoute; uint32_t m_turnIndex; vector m_points; }; +string DebugPrint(TurnGeom const & turnGeom); + typedef vector TurnsGeomT; +typedef vector TSingleLane; string const & GetTurnString(TurnDirection turn); @@ -86,11 +98,13 @@ bool IsGoStraightOrSlightTurn(TurnDirection t); * \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: if @lanesString is empty returns false. + * 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(string const & lanesString, vector> & lanes); -void ParseLanesToStrings(string const & lanesString, char delimiter, vector & lanes); -bool ParseOneLane(string const & laneString, char delimiter, vector & lane); +bool ParseLanes(string lanesString, vector & lanes); +void SplitLanes(string const & lanesString, char delimiter, vector & lanes); +bool ParseSingleLane(string const & laneString, char delimiter, TSingleLane & lane); } } + diff --git a/routing/turns_generator.cpp b/routing/turns_generator.cpp new file mode 100644 index 0000000000..d04890d532 --- /dev/null +++ b/routing/turns_generator.cpp @@ -0,0 +1,166 @@ +#include "routing/routing_mapping.h" +#include "routing/turns_generator.hpp" + +#include "indexer/ftypes_matcher.hpp" + +#include "3party/osrm/osrm-backend/DataStructures/RawRouteData.h" + +#include "std/numeric.hpp" +#include "std/string.hpp" + +namespace routing +{ +namespace turns +{ +OsrmMappingTypes::FtSeg GetSegment(PathData const & node, RoutingMapping const & routingMapping, + TGetIndexFunction GetIndex) +{ + auto const segmentsRange = routingMapping.m_segMapping.GetSegmentsRange(node.node); + OsrmMappingTypes::FtSeg seg; + routingMapping.m_segMapping.GetSegmentByIndex(GetIndex(segmentsRange), seg); + return seg; +} + +vector GetLanesInfo(PathData const & node, + RoutingMapping const & routingMapping, + TGetIndexFunction GetIndex, Index const & index) +{ + // seg1 is the last segment before a point of bifurcation (before turn) + OsrmMappingTypes::FtSeg const seg1 = GetSegment(node, routingMapping, GetIndex); + vector lanes; + if (seg1.IsValid()) + { + FeatureType ft1; + Index::FeaturesLoaderGuard loader1(index, routingMapping.GetMwmId()); + loader1.GetFeature(seg1.m_fid, ft1); + ft1.ParseMetadata(); + + if (ftypes::IsOneWayChecker::Instance()(ft1)) + { + string const turnLanes = ft1.GetMetadata().Get(feature::FeatureMetadata::FMD_TURN_LANES); + routing::turns::ParseLanes(turnLanes, lanes); + return lanes; + } + // two way roads + if (seg1.m_pointStart < seg1.m_pointEnd) + { + // forward direction + string const turnLanesForward = + ft1.GetMetadata().Get(feature::FeatureMetadata::FMD_TURN_LANES_FORWARD); + routing::turns::ParseLanes(turnLanesForward, lanes); + return lanes; + } + // backward direction + string const turnLanesBackward = + ft1.GetMetadata().Get(feature::FeatureMetadata::FMD_TURN_LANES_BACKWARD); + routing::turns::ParseLanes(turnLanesBackward, lanes); + return lanes; + } + return lanes; +} + +void CalculateTurnGeometry(vector const & points, Route::TurnsT const & turnsDir, + turns::TurnsGeomT & turnsGeom) +{ + size_t const kNumPoints = points.size(); + /// "Pivot point" is a point of bifurcation (a point of a turn). + /// kNumPointsBeforePivot is number of points before the pivot point. + uint32_t const kNumPointsBeforePivot = 10; + /// kNumPointsAfterPivot is a number of points follows by the pivot point. + /// kNumPointsAfterPivot is greater because there are half body and the arrow after the pivot point + uint32_t constexpr kNumPointsAfterPivot = kNumPointsBeforePivot + 10; + + for (TurnItem const & t : turnsDir) + { + ASSERT_LESS(t.m_index, kNumPoints, ()); + if (t.m_index == 0 || t.m_index == (kNumPoints - 1)) + continue; + + uint32_t const fromIndex = (t.m_index <= kNumPointsBeforePivot) ? 0 : t.m_index - kNumPointsBeforePivot; + uint32_t toIndex = 0; + if (t.m_index + kNumPointsAfterPivot >= kNumPoints || t.m_index + kNumPointsAfterPivot < t.m_index) + toIndex = kNumPoints; + else + toIndex = t.m_index + kNumPointsAfterPivot; + + uint32_t const turnIndex = min(t.m_index, kNumPointsBeforePivot); + turnsGeom.emplace_back(t.m_index, turnIndex, points.begin() + fromIndex, + points.begin() + toIndex); + } + return; +} + +void FixupTurns(vector const & points, Route::TurnsT & turnsDir) +{ + double const kMergeDistMeters = 30.0; + // For turns that are not EnterRoundAbout exitNum is always equal to zero. + // If a turn is EnterRoundAbout exitNum is a number of turns between two points: + // (1) the route enters to the roundabout; + // (2) the route leaves the roundabout; + uint32_t exitNum = 0; + // If a roundabout is worked up the roundabout value points to the turn + // of the enter to the roundabout. If not, roundabout is equal to nullptr. + TurnItem * roundabout = nullptr; + + auto routeDistanceMeters = [&points](uint32_t start, uint32_t end) + { + double res = 0.0; + for (uint32_t i = start + 1; i < end; ++i) + res += MercatorBounds::DistanceOnEarth(points[i - 1], points[i]); + return res; + }; + + for (size_t idx = 0; idx < turnsDir.size(); ) + { + TurnItem & t = turnsDir[idx]; + if (roundabout && t.m_turn != turns::StayOnRoundAbout && t.m_turn != turns::LeaveRoundAbout) + { + exitNum = 0; + roundabout = nullptr; + } + else if (t.m_turn == turns::EnterRoundAbout) + { + ASSERT(!roundabout, ()); + roundabout = &t; + } + else if (t.m_turn == turns::StayOnRoundAbout) + { + ++exitNum; + turnsDir.erase(turnsDir.begin() + idx); + continue; + } + else if (roundabout && t.m_turn == turns::LeaveRoundAbout) + { + roundabout->m_exitNum = exitNum + 1; + roundabout = nullptr; + exitNum = 0; + } + + // Merging turns which are closed to each other under some circumstance. + // distance(turnsDir[idx - 1].m_index, turnsDir[idx].m_index) < kMergeDistMeters + // means the distance in meters between the former turn (idx - 1) + // and the current turn (idx). + if (idx > 0 && + turns::IsStayOnRoad(turnsDir[idx - 1].m_turn) && + turns::IsLeftOrRightTurn(turnsDir[idx].m_turn) && + routeDistanceMeters(turnsDir[idx - 1].m_index, turnsDir[idx].m_index) < kMergeDistMeters) + { + turnsDir.erase(turnsDir.begin() + idx - 1); + continue; + } + + if (!t.m_keepAnyway + && turns::IsGoStraightOrSlightTurn(t.m_turn) + && !t.m_sourceName.empty() + && strings::AlmostEqual(t.m_sourceName, t.m_targetName, 2 /* mismatched symbols count */)) + { + turnsDir.erase(turnsDir.begin() + idx); + continue; + } + + ++idx; + } + return; +} +} +} diff --git a/routing/turns_generator.hpp b/routing/turns_generator.hpp new file mode 100644 index 0000000000..c4e3099514 --- /dev/null +++ b/routing/turns_generator.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include "routing/osrm2feature_map.hpp" +#include "routing/route.hpp" +#include "routing/turns.hpp" + +#include "std/function.hpp" +#include "std/utility.hpp" +#include "std/vector.hpp" + +struct PathData; +class Index; + +namespace routing +{ +struct RoutingMapping; + +namespace turns +{ +// Returns a segment index by STL-like range [s, e) of segments indices for passed node. +typedef function)> TGetIndexFunction; + +OsrmMappingTypes::FtSeg GetSegment(PathData const & node, RoutingMapping const & routingMapping, + TGetIndexFunction GetIndex); +vector GetLanesInfo(PathData const & node, + RoutingMapping const & routingMapping, + TGetIndexFunction GetIndex, Index const & index); +/// CalculateTurnGeometry calculates geometry for all the turns. That means that for every turn +/// CalculateTurnGeometry calculates a sequence of points which will be used +/// for displaying arrows on the route. +void CalculateTurnGeometry(vector const & points, Route::TurnsT const & turnsDir, + turns::TurnsGeomT & turnsGeom); +void FixupTurns(vector const & points, Route::TurnsT & turnsDir); +} +}