diff --git a/routing/routing_integration_tests/osrm_turn_test.cpp b/routing/routing_integration_tests/osrm_turn_test.cpp index f505e9ff0b..a77d6e143b 100644 --- a/routing/routing_integration_tests/osrm_turn_test.cpp +++ b/routing/routing_integration_tests/osrm_turn_test.cpp @@ -202,7 +202,7 @@ UNIT_TEST(RussiaHugeRoundaboutTurnTest) integration::GetNthTurn(route, 1) .TestValid() .TestDirection(TurnDirection::LeaveRoundAbout) - .TestRoundAboutExitNum(0); + .TestRoundAboutExitNum(4); } UNIT_TEST(BelarusMiskProspNezavisimostiMKADTurnTest) diff --git a/routing/routing_tests/turns_generator_test.cpp b/routing/routing_tests/turns_generator_test.cpp index 5b8de984eb..4c6e2ad275 100644 --- a/routing/routing_tests/turns_generator_test.cpp +++ b/routing/routing_tests/turns_generator_test.cpp @@ -138,7 +138,7 @@ UNIT_TEST(TestFixupTurns) FixupTurns(pointsMerc1, turnsDir1); Route::TTurns const expectedTurnDir1 = {{0, TurnDirection::EnterRoundAbout, 2}, - {2, TurnDirection::LeaveRoundAbout}, + {2, TurnDirection::LeaveRoundAbout, 2}, {3, TurnDirection::ReachedYourDestination}}; TEST_EQUAL(turnsDir1, expectedTurnDir1, ()); diff --git a/routing/routing_tests/turns_sound_test.cpp b/routing/routing_tests/turns_sound_test.cpp index 633a38922f..4cdc9f7d1e 100644 --- a/routing/routing_tests/turns_sound_test.cpp +++ b/routing/routing_tests/turns_sound_test.cpp @@ -394,6 +394,114 @@ UNIT_TEST(TurnsSoundComposedTurnTest) turnSound.GenerateTurnSound(turns6, turnNotifications); TEST_EQUAL(turnNotifications, expectedNotification6, ()); } + +UNIT_TEST(TurnsSoundRoundaboutTurnTest) +{ + TurnsSound turnSound; + turnSound.Enable(true); + turnSound.SetLengthUnits(routing::turns::sound::LengthUnits::Meters); + string const engShortJson = + "\ + {\ + \"enter_the_roundabout\":\"Enter the roundabout.\",\ + \"leave_the_roundabout\":\"Leave the roundabout.\",\ + \"take_the_1st_exit\":\"Take the first exit.\",\ + \"take_the_2nd_exit\":\"Take the second exit.\",\ + \"take_the_4th_exit\":\"Take the fourth exit.\",\ + \"in_600_meters\":\"In 600 meters.\",\ + \"then\":\"Then.\"\ + }"; + turnSound.m_getTtsText.ForTestingSetLocaleWithJson(engShortJson); + turnSound.m_settings.ForTestingSetNotificationTimeSecond(30); + + turnSound.Reset(); + turnSound.SetSpeedMetersPerSecond(20.); + vector turnNotifications; + + // Starting nearing the first turn. + // 1000 meters till the first turn. + vector const turns1 = {{{5 /* idx */, TurnDirection::EnterRoundAbout, 2 /* m_exitNum */}, + 1000. /* m_distMeters */}, + {{10 /* idx */, TurnDirection::LeaveRoundAbout, 2 /* m_exitNum */}, + 2000. /* m_distMeters */}}; + turnSound.GenerateTurnSound(turns1, turnNotifications); + TEST(turnNotifications.empty(), ()); + + // 620 meters till the first turn. + vector const turns2 = {{{5 /* idx */, TurnDirection::EnterRoundAbout, 2 /* m_exitNum */}, + 620. /* m_distMeters */}, + {{10 /* idx */, TurnDirection::LeaveRoundAbout, 2 /* m_exitNum */}, + 1620. /* m_distMeters */}}; + vector const expectedNotification2 = {{"In 600 meters. Enter the roundabout."}, + {"Then. Take the second exit."}}; + turnSound.GenerateTurnSound(turns2, turnNotifications); + TEST_EQUAL(turnNotifications, expectedNotification2, ()); + + // 3 meters till the first turn. + vector const turns3 = {{{5 /* idx */, TurnDirection::EnterRoundAbout, 2 /* m_exitNum */}, + 3. /* m_distMeters */}, + {{10 /* idx */, TurnDirection::LeaveRoundAbout, 2 /* m_exitNum */}, + 1003. /* m_distMeters */}}; + vector const expectedNotification3 = {{"Enter the roundabout."}, + {"Then. Take the second exit."}}; + turnSound.GenerateTurnSound(turns3, turnNotifications); + TEST_EQUAL(turnNotifications, expectedNotification3, ()); + + // 900 meters till the second turn. + vector const turns4 = {{{10 /* idx */, TurnDirection::LeaveRoundAbout, 2 /* m_exitNum */}, + 900. /* m_distMeters */}, + {{15 /* idx */, TurnDirection::EnterRoundAbout, 1 /* m_exitNum */}, + 1900. /* m_distMeters */}}; + turnSound.GenerateTurnSound(turns4, turnNotifications); + TEST(turnNotifications.empty(), ()); + + // 300 meters till the second turn. + vector const turns5 = {{{10 /* idx */, TurnDirection::LeaveRoundAbout, 2 /* m_exitNum */}, + 300. /* m_distMeters */}, + {{15 /* idx */, TurnDirection::EnterRoundAbout, 1 /* m_exitNum */}, + 1300. /* m_distMeters */}}; + turnSound.GenerateTurnSound(turns5, turnNotifications); + TEST(turnNotifications.empty(), ()); + + // 3 meters till the second turn. + vector const turns6 = {{{10 /* idx */, TurnDirection::LeaveRoundAbout, 2 /* m_exitNum */}, + 3. /* m_distMeters */}, + {{15 /* idx */, TurnDirection::EnterRoundAbout, 1 /* m_exitNum */}, + 1003. /* m_distMeters */}}; + vector const expectedNotification6 = {{"Leave the roundabout."}}; + turnSound.GenerateTurnSound(turns6, turnNotifications); + TEST_EQUAL(turnNotifications, expectedNotification6, ()); + + // 5 meters till the third turn. + vector const turns7 = {{{15 /* idx */, TurnDirection::EnterRoundAbout, 1 /* m_exitNum */}, + 5. /* m_distMeters */}, + {{20 /* idx */, TurnDirection::LeaveRoundAbout, 1 /* m_exitNum */}, + 1005. /* m_distMeters */}}; + vector const expectedNotification7 = {{"Enter the roundabout."}, + {"Then. Take the first exit."}}; + turnSound.GenerateTurnSound(turns7, turnNotifications); // The first notification fast forwarding. + turnSound.GenerateTurnSound(turns7, turnNotifications); + TEST_EQUAL(turnNotifications, expectedNotification7, ()); + + // 900 meters till the 4th turn. + turnSound.Reset(); + vector const turns8 = {{{25 /* idx */, TurnDirection::EnterRoundAbout, 4 /* m_exitNum */}, + 900. /* m_distMeters */}, + {{30 /* idx */, TurnDirection::LeaveRoundAbout, 4 /* m_exitNum */}, + 1200. /* m_distMeters */}}; + turnSound.GenerateTurnSound(turns8, turnNotifications); + TEST(turnNotifications.empty(), ()); + + // 620 meters till the 4th turn. + vector const turns9 = {{{25 /* idx */, TurnDirection::EnterRoundAbout, 4 /* m_exitNum */}, + 620. /* m_distMeters */}, + {{30 /* idx */, TurnDirection::LeaveRoundAbout, 4 /* m_exitNum */}, + 920. /* m_distMeters */}}; + vector const expectedNotification9 = {{"In 600 meters. Enter the roundabout."}, + {"Then. Take the fourth exit."}}; + turnSound.GenerateTurnSound(turns9, turnNotifications); + TEST_EQUAL(turnNotifications, expectedNotification9, ()); +} } // namespace sound } // namespace turns } // namespace routing diff --git a/routing/turns_generator.cpp b/routing/turns_generator.cpp index b0cab345e3..63721bc686 100644 --- a/routing/turns_generator.cpp +++ b/routing/turns_generator.cpp @@ -579,7 +579,8 @@ void FixupTurns(vector const & points, Route::TTurns & turnsDir) } else if (roundabout && t.m_turn == TurnDirection::LeaveRoundAbout) { - roundabout->m_exitNum = exitNum + 1; + roundabout->m_exitNum = exitNum + 1; // For EnterRoundAbout turn. + t.m_exitNum = roundabout->m_exitNum; // For LeaveRoundAbout turn. roundabout = nullptr; exitNum = 0; } diff --git a/routing/turns_sound.cpp b/routing/turns_sound.cpp index cbad6b4d69..e861605484 100644 --- a/routing/turns_sound.cpp +++ b/routing/turns_sound.cpp @@ -29,6 +29,18 @@ uint32_t CalculateDistBeforeMeters(double m_speedMetersPerSecond) static_cast(m_speedMetersPerSecond * kStartBeforeSeconds); return my::clamp(startBeforeMeters, kMinStartBeforeMeters, kMaxStartBeforeMeters); } + +// Returns true if the closest turn is an entrance to a roundabout and the second is +// an exit form a roundabout. +// Note. There are some cases when another turn (besides an exit from roundabout) +// follows an entrance to a roundabout. It could happend in case of turns inside a roundabout. +// Returns false otherwise. +bool IsClassicEntranceToRoundabout(routing::turns::TurnItemDist const & firstTurn, + routing::turns::TurnItemDist const & secondTurn) +{ + return firstTurn.m_turnItem.m_turn == routing::turns::TurnDirection::EnterRoundAbout + && secondTurn.m_turnItem.m_turn == routing::turns::TurnDirection::LeaveRoundAbout; +} } // namespace namespace routing @@ -64,8 +76,11 @@ void TurnsSound::GenerateTurnSound(vector const & turns, vector kMaxTurnDistM) + if (secondTurn.m_distMeters - firstTurn.m_distMeters > kMaxTurnDistM + && !IsClassicEntranceToRoundabout(firstTurn, secondTurn)) + { return; + } string secondNotification = GenerateTurnText(0 /* distanceUnits is not used because of "Then" is used */, secondTurn.m_turnItem.m_exitNum, true, secondTurn.m_turnItem.m_turn, diff --git a/routing/turns_sound.hpp b/routing/turns_sound.hpp index 9266f6670c..d3eb8d714c 100644 --- a/routing/turns_sound.hpp +++ b/routing/turns_sound.hpp @@ -37,6 +37,7 @@ class TurnsSound friend void UnitTest_TurnsSoundMetersTwoTurnsTest(); friend void UnitTest_TurnsSoundFeetTest(); friend void UnitTest_TurnsSoundComposedTurnTest(); + friend void UnitTest_TurnsSoundRoundaboutTurnTest(); /// m_enabled == true when tts is turned on. /// Important! Clients (iOS/Android) implies that m_enabled is false by default. diff --git a/routing/turns_tts_text.cpp b/routing/turns_tts_text.cpp index 1c8b50978e..38946b1732 100644 --- a/routing/turns_tts_text.cpp +++ b/routing/turns_tts_text.cpp @@ -1,6 +1,8 @@ #include "routing/turns_sound_settings.hpp" #include "routing/turns_tts_text.hpp" +#include "base/string_utils.hpp" + #include "std/algorithm.hpp" #include "std/string.hpp" #include "std/utility.hpp" @@ -100,6 +102,37 @@ string GetDistanceTextId(Notification const & notification) return string(); } +string GetRoundaboutTextId(Notification const & notification) +{ + if (notification.m_turnDir != TurnDirection::LeaveRoundAbout) + { + ASSERT(false, ()); + return string(); + } + if (!notification.m_useThenInsteadOfDistance) + return "leave_the_roundabout"; // Notification just before leaving a roundabout. + + static const uint8_t kMaxSoundedExit = 11; + if (notification.m_exitNum == 0 || notification.m_exitNum > kMaxSoundedExit) + return "leave_the_roundabout"; + + if (notification.m_exitNum < 4) + { + switch (notification.m_exitNum) + { + case 1: + return "take_the_1st_exit"; + case 2: + return "take_the_2nd_exit"; + case 3: + return "take_the_3rd_exit"; + } + ASSERT(false, ()); + return string(); + } + return "take_the_" + strings::to_string(static_cast(notification.m_exitNum)) + "th_exit"; +} + string GetDirectionTextId(Notification const & notification) { switch (notification.m_turnDir) @@ -123,7 +156,7 @@ string GetDirectionTextId(Notification const & notification) case TurnDirection::EnterRoundAbout: return "enter_the_roundabout"; case TurnDirection::LeaveRoundAbout: - return "leave_the_roundabout"; + return GetRoundaboutTextId(notification); case TurnDirection::ReachedYourDestination: return "you_have_reached_the_destination"; case TurnDirection::StayOnRoundAbout: diff --git a/routing/turns_tts_text.hpp b/routing/turns_tts_text.hpp index 26a4df1a54..43390e7463 100644 --- a/routing/turns_tts_text.hpp +++ b/routing/turns_tts_text.hpp @@ -36,6 +36,9 @@ private: /// Generates text message id about the distance of the notification. For example: In 300 meters. string GetDistanceTextId(Notification const & notification); +/// Generates text message id for roundabouts. +/// For example: leave_the_roundabout or take_the_3rd_exit +string GetRoundaboutTextId(Notification const & notification); /// Generates text message id about the direction of the notification. For example: Make a right /// turn. string GetDirectionTextId(Notification const & notification);