diff --git a/libs/drape_frontend/drape_engine.cpp b/libs/drape_frontend/drape_engine.cpp index 50e3372c3..e8c6a57e4 100644 --- a/libs/drape_frontend/drape_engine.cpp +++ b/libs/drape_frontend/drape_engine.cpp @@ -9,6 +9,7 @@ #include "drape/support_manager.hpp" #include "platform/settings.hpp" +#include "routing/base/followed_polyline.hpp" #include @@ -40,7 +41,7 @@ DrapeEngine::DrapeEngine(Params && params) using namespace location; EMyPositionMode mode = PendingPosition; - if (settings::Get(kLocationStateMode, mode) && mode == FollowAndRotate) + if (settings::Get(kLocationStateMode, mode) && mode == FollowAndRotateCompass) { // If the screen rect setting in follow and rotate mode is missing or invalid, it could cause // invalid animations, so the follow and rotate mode should be discarded. @@ -441,13 +442,12 @@ void DrapeEngine::SetCompassInfo(location::CompassInfo const & info) MessagePriority::Normal); } -void DrapeEngine::SetGpsInfo(location::GpsInfo const & info, bool isNavigable, double distToNextTurn, double speedLimit, +void DrapeEngine::SetGpsInfo(location::GpsInfo const & info, df::NavigationContext const & navigationContext, location::RouteMatchingInfo const & routeInfo) { - m_threadCommutator->PostMessage( - ThreadsCommutator::RenderThread, - make_unique_dp(info, isNavigable, distToNextTurn, speedLimit, routeInfo), - MessagePriority::Normal); + m_threadCommutator->PostMessage(ThreadsCommutator::RenderThread, + make_unique_dp(info, navigationContext, routeInfo), + MessagePriority::Normal); } void DrapeEngine::SwitchMyPositionNextMode() @@ -474,12 +474,13 @@ void DrapeEngine::StopLocationFollow() MessagePriority::Normal); } -void DrapeEngine::FollowRoute(int preferredZoomLevel, int preferredZoomLevel3d, bool enableAutoZoom, bool isArrowGlued) +void DrapeEngine::FollowRoute(int preferredZoomLevel, int preferredZoomLevel3d, bool enableAutoZoom, bool isArrowGlued, + bool allowRouteRotation) { - m_threadCommutator->PostMessage( - ThreadsCommutator::RenderThread, - make_unique_dp(preferredZoomLevel, preferredZoomLevel3d, enableAutoZoom, isArrowGlued), - MessagePriority::Normal); + m_threadCommutator->PostMessage(ThreadsCommutator::RenderThread, + make_unique_dp(preferredZoomLevel, preferredZoomLevel3d, + enableAutoZoom, isArrowGlued, allowRouteRotation), + MessagePriority::Normal); } void DrapeEngine::SetModelViewListener(ModelViewChangedHandler && fn) diff --git a/libs/drape_frontend/drape_engine.hpp b/libs/drape_frontend/drape_engine.hpp index 9b83c1959..649f2dcd5 100644 --- a/libs/drape_frontend/drape_engine.hpp +++ b/libs/drape_frontend/drape_engine.hpp @@ -1,5 +1,7 @@ #pragma once +#include "drape_frontend/my_position_controller.hpp" +#include "routing/base/followed_polyline.hpp" #include "traffic/traffic_info.hpp" #include "drape_frontend/backend_renderer.hpp" @@ -154,7 +156,7 @@ public: void UpdateMapStyle(); void SetCompassInfo(location::CompassInfo const & info); - void SetGpsInfo(location::GpsInfo const & info, bool isNavigable, double distToNextTurn, double speedLimit, + void SetGpsInfo(location::GpsInfo const & info, df::NavigationContext const & navigationContext, location::RouteMatchingInfo const & routeInfo); void SwitchMyPositionNextMode(); void LoseLocation(); @@ -171,7 +173,8 @@ public: dp::DrapeID AddSubroute(SubrouteConstPtr subroute); void RemoveSubroute(dp::DrapeID subrouteId, bool deactivateFollowing); - void FollowRoute(int preferredZoomLevel, int preferredZoomLevel3d, bool enableAutoZoom, bool isArrowGlued); + void FollowRoute(int preferredZoomLevel, int preferredZoomLevel3d, bool enableAutoZoom, bool isArrowGlued, + bool allowRouteRotation); void DeactivateRouteFollowing(); void SetSubrouteVisibility(dp::DrapeID subrouteId, bool isVisible); dp::DrapeID AddRoutePreviewSegment(m2::PointD const & startPt, m2::PointD const & finishPt); diff --git a/libs/drape_frontend/frontend_renderer.cpp b/libs/drape_frontend/frontend_renderer.cpp index 37f07850a..f8b0838ce 100644 --- a/libs/drape_frontend/frontend_renderer.cpp +++ b/libs/drape_frontend/frontend_renderer.cpp @@ -438,8 +438,8 @@ void FrontendRenderer::AcceptMessage(ref_ptr message) break; #endif ref_ptr msg = message; - m_myPositionController->OnLocationUpdate(msg->GetInfo(), msg->IsNavigable(), msg->GetDistanceToNextTurn(), - msg->GetSpeedLimit(), m_userEventStream.GetCurrentScreen()); + m_myPositionController->OnLocationUpdate(msg->GetInfo(), msg->GetNavigationContext(), + m_userEventStream.GetCurrentScreen()); location::RouteMatchingInfo const & info = msg->GetRouteInfo(); if (info.HasDistanceFromBegin()) @@ -512,7 +512,8 @@ void FrontendRenderer::AcceptMessage(ref_ptr message) if (m_pendingFollowRoute != nullptr) { FollowRoute(m_pendingFollowRoute->m_preferredZoomLevel, m_pendingFollowRoute->m_preferredZoomLevelIn3d, - m_pendingFollowRoute->m_enableAutoZoom, m_pendingFollowRoute->m_isArrowGlued); + m_pendingFollowRoute->m_enableAutoZoom, m_pendingFollowRoute->m_isArrowGlued, + m_pendingFollowRoute->m_allowRouteRotation); m_pendingFollowRoute.reset(); } break; @@ -584,13 +585,14 @@ void FrontendRenderer::AcceptMessage(ref_ptr message) // receive FollowRoute message before FlushSubroute message, so we need to postpone its processing. if (m_routeRenderer->GetSubroutes().empty()) { - m_pendingFollowRoute = std::make_unique( - msg->GetPreferredZoomLevel(), msg->GetPreferredZoomLevelIn3d(), msg->EnableAutoZoom(), msg->IsArrowGlued()); + m_pendingFollowRoute = + std::make_unique(msg->GetPreferredZoomLevel(), msg->GetPreferredZoomLevelIn3d(), + msg->EnableAutoZoom(), msg->IsArrowGlued(), msg->AllowRouteRotation()); } else { FollowRoute(msg->GetPreferredZoomLevel(), msg->GetPreferredZoomLevelIn3d(), msg->EnableAutoZoom(), - msg->IsArrowGlued()); + msg->IsArrowGlued(), msg->AllowRouteRotation()); } break; } @@ -1057,10 +1059,11 @@ void FrontendRenderer::UpdateContextDependentResources() } void FrontendRenderer::FollowRoute(int preferredZoomLevel, int preferredZoomLevelIn3d, bool enableAutoZoom, - bool isArrowGlued) + bool isArrowGlued, bool allowRouteRotation) { m_myPositionController->ActivateRouting( - !m_enablePerspectiveInNavigation ? preferredZoomLevel : preferredZoomLevelIn3d, enableAutoZoom, isArrowGlued); + !m_enablePerspectiveInNavigation ? preferredZoomLevel : preferredZoomLevelIn3d, enableAutoZoom, isArrowGlued, + allowRouteRotation); if (m_enablePerspectiveInNavigation) AddUserEvent(make_unique_dp(true /* isAutoPerspective */)); diff --git a/libs/drape_frontend/frontend_renderer.hpp b/libs/drape_frontend/frontend_renderer.hpp index ba7f4c541..7ce3ff0d2 100644 --- a/libs/drape_frontend/frontend_renderer.hpp +++ b/libs/drape_frontend/frontend_renderer.hpp @@ -261,7 +261,8 @@ private: using TRenderGroupRemovePredicate = std::function const &)>; void RemoveRenderGroupsLater(TRenderGroupRemovePredicate const & predicate); - void FollowRoute(int preferredZoomLevel, int preferredZoomLevelIn3d, bool enableAutoZoom, bool isArrowGlued); + void FollowRoute(int preferredZoomLevel, int preferredZoomLevelIn3d, bool enableAutoZoom, bool isArrowGlued, + bool allowRouteRotation); bool CheckRouteRecaching(ref_ptr subrouteData); @@ -372,17 +373,20 @@ private: struct FollowRouteData { - FollowRouteData(int preferredZoomLevel, int preferredZoomLevelIn3d, bool enableAutoZoom, bool isArrowGlued) + FollowRouteData(int preferredZoomLevel, int preferredZoomLevelIn3d, bool enableAutoZoom, bool isArrowGlued, + bool allowRouteRotation) : m_preferredZoomLevel(preferredZoomLevel) , m_preferredZoomLevelIn3d(preferredZoomLevelIn3d) , m_enableAutoZoom(enableAutoZoom) , m_isArrowGlued(isArrowGlued) + , m_allowRouteRotation(allowRouteRotation) {} int m_preferredZoomLevel; int m_preferredZoomLevelIn3d; bool m_enableAutoZoom; bool m_isArrowGlued; + bool m_allowRouteRotation; }; std::unique_ptr m_pendingFollowRoute; diff --git a/libs/drape_frontend/message_subclasses.hpp b/libs/drape_frontend/message_subclasses.hpp index 7a0fe6ed3..7a990c370 100644 --- a/libs/drape_frontend/message_subclasses.hpp +++ b/libs/drape_frontend/message_subclasses.hpp @@ -10,6 +10,7 @@ #include "drape_frontend/gui/skin.hpp" #include "drape_frontend/message.hpp" #include "drape_frontend/my_position.hpp" +#include "drape_frontend/my_position_controller.hpp" #include "drape_frontend/overlay_batcher.hpp" #include "drape_frontend/postprocess_renderer.hpp" #include "drape_frontend/render_node.hpp" @@ -29,6 +30,7 @@ #include "geometry/rect2d.hpp" #include "geometry/triangle2d.hpp" +#include "routing/base/followed_polyline.hpp" #include #include @@ -475,28 +477,22 @@ private: class GpsInfoMessage : public Message { public: - GpsInfoMessage(location::GpsInfo const & info, bool isNavigable, double distToNextTurn, double speedLimit, + GpsInfoMessage(location::GpsInfo const & info, df::NavigationContext const & navigationContext, location::RouteMatchingInfo const & routeInfo) : m_info(info) - , m_isNavigable(isNavigable) - , m_distToNextTurn(distToNextTurn) - , m_speedLimit(speedLimit) + , m_navigationContext(navigationContext) , m_routeInfo(routeInfo) {} Type GetType() const override { return Type::GpsInfo; } location::GpsInfo const & GetInfo() const { return m_info; } - bool IsNavigable() const { return m_isNavigable; } - double const & GetSpeedLimit() const { return m_speedLimit; } - double const & GetDistanceToNextTurn() const { return m_distToNextTurn; } + df::NavigationContext const & GetNavigationContext() const { return m_navigationContext; } location::RouteMatchingInfo const & GetRouteInfo() const { return m_routeInfo; } private: location::GpsInfo const m_info; - bool const m_isNavigable; - double const m_distToNextTurn; - double const m_speedLimit; + df::NavigationContext const m_navigationContext; location::RouteMatchingInfo const m_routeInfo; }; @@ -740,11 +736,13 @@ public: class FollowRouteMessage : public Message { public: - FollowRouteMessage(int preferredZoomLevel, int preferredZoomLevelIn3d, bool enableAutoZoom, bool isArrowGlued) + FollowRouteMessage(int preferredZoomLevel, int preferredZoomLevelIn3d, bool enableAutoZoom, bool isArrowGlued, + bool allowRouteRotation) : m_preferredZoomLevel(preferredZoomLevel) , m_preferredZoomLevelIn3d(preferredZoomLevelIn3d) , m_enableAutoZoom(enableAutoZoom) , m_isArrowGlued(isArrowGlued) + , m_allowRouteRotation(allowRouteRotation) {} Type GetType() const override { return Type::FollowRoute; } @@ -753,12 +751,14 @@ public: int GetPreferredZoomLevelIn3d() const { return m_preferredZoomLevelIn3d; } bool EnableAutoZoom() const { return m_enableAutoZoom; } bool IsArrowGlued() const { return m_isArrowGlued; } + bool AllowRouteRotation() const { return m_allowRouteRotation; } private: int const m_preferredZoomLevel; int const m_preferredZoomLevelIn3d; bool const m_enableAutoZoom; bool const m_isArrowGlued; + bool const m_allowRouteRotation; }; class SwitchMapStyleMessage : public BaseBlockingMessage diff --git a/libs/drape_frontend/my_position_controller.cpp b/libs/drape_frontend/my_position_controller.cpp index f46fa8879..4c0a52de2 100644 --- a/libs/drape_frontend/my_position_controller.cpp +++ b/libs/drape_frontend/my_position_controller.cpp @@ -9,9 +9,11 @@ #include "geometry/mercator.hpp" +#include "platform/location.hpp" #include "platform/measurement_utils.hpp" #include "base/math.hpp" +#include "routing/base/followed_polyline.hpp" #include #include @@ -31,6 +33,9 @@ double constexpr kMaxTimeInBackgroundSec = 60.0 * 60 * 30; // 30 hours before s double constexpr kMaxNotFollowRoutingTimeSec = 20.0; double constexpr kMaxUpdateLocationInvervalSec = 30.0; double constexpr kMaxBlockAutoZoomTimeSec = 10.0; +double constexpr kDefaultSpeedLimitKmpH = 50.0; +double constexpr kLookaheadTimeSpeedRatio = 0.3; +double constexpr kMaxLookaheadTimeSec = 25.0; int constexpr kZoomThreshold = 10; int constexpr kMaxScaleZoomLevel = 16; @@ -66,7 +71,6 @@ double CalculateZoomByMaxSpeed(double speedMpS, bool isPerspectiveAllowed) std::array const & scales = isPerspectiveAllowed ? scales3d : scales2d; - double constexpr kDefaultSpeedLimitKmpH = 50.0; double const speedKmpH = speedMpS > 0 ? measurement_utils::MpsToKmph(speedMpS) : kDefaultSpeedLimitKmpH; size_t i = 0; @@ -143,7 +147,7 @@ void ResetNotification(uint64_t & notifyId) bool IsModeChangeViewport(location::EMyPositionMode mode) { - return mode == location::Follow || mode == location::FollowAndRotate; + return mode == location::Follow || mode == location::FollowAndRotateCompass || mode == location::FollowAndRotateRoute; } } // namespace @@ -157,9 +161,11 @@ MyPositionController::MyPositionController(Params && params, ref_ptr follow-and-rotate, otherwise not-follow -> follow. if (m_mode == location::NotFollow) { - ChangeMode(m_isInRouting ? location::FollowAndRotate : location::Follow); + ChangeMode(m_isInRouting ? location::FollowAndRotateCompass : location::Follow); UpdateViewport(preferredZoomLevel); return; } - // From follow mode we transit to follow-and-rotate if compass is available or - // routing is enabled. + // From follow mode we transit to follow-and-rotate-compass, if in routing if (m_mode == location::Follow) { - if (IsRotationAvailable() || m_isInRouting) + if (IsArrowRotationAvailable() || m_isInRouting) { - ChangeMode(location::FollowAndRotate); + ChangeMode(location::FollowAndRotateCompass); UpdateViewport(preferredZoomLevel); } return; } - // From follow-and-rotate mode we can transit to follow mode. - if (m_mode == location::FollowAndRotate) + // From -rotate-compass mode we transit to -rotate-route mode, if allowed + if (m_mode == location::FollowAndRotateCompass && IsRouteRotationAvailable() && m_allowRouteRotationInRouting) + { + ChangeMode(location::FollowAndRotateRoute); + UpdateViewport(preferredZoomLevel); + return; + } + + // From -rotate-route mode, or from -rotate-compass if -rotate-route is not allowed, we return to follow mode + if (m_mode == location::FollowAndRotateRoute || m_mode == location::FollowAndRotateCompass) { if (m_isInRouting && screen.isPerspective()) preferredZoomLevel = static_cast(GetZoomLevel(ScreenBase::GetStartPerspectiveScale() * 1.1)); @@ -425,8 +439,8 @@ void MyPositionController::NextMode(ScreenBase const & screen) } } -void MyPositionController::OnLocationUpdate(location::GpsInfo const & info, bool isNavigable, double distanceToNextTurn, - double speedLimit, ScreenBase const & screen) +void MyPositionController::OnLocationUpdate(location::GpsInfo const & info, + df::NavigationContext const & navigationContext, ScreenBase const & screen) { m2::PointD const oldPos = GetDrawablePosition(); double const oldAzimut = GetDrawableAzimut(); @@ -438,33 +452,62 @@ void MyPositionController::OnLocationUpdate(location::GpsInfo const & info, bool m_errorRadius = rect.SizeX() * 0.5; m_horizontalAccuracy = info.m_horizontalAccuracy; - if (distanceToNextTurn >= 0.0 || speedLimit >= 0.0) + if (navigationContext.m_distanceToNextTurn >= 0.0 || navigationContext.m_speedLimit >= 0.0) { double const mercatorPerMeter = m_errorRadius / info.m_horizontalAccuracy; m_autoScale2d = - mercatorPerMeter * CalculateAutoZoom(speedLimit, distanceToNextTurn, false /* isPerspectiveAllowed */); + mercatorPerMeter * CalculateAutoZoom(navigationContext.m_speedLimit, navigationContext.m_distanceToNextTurn, + false /* isPerspectiveAllowed */); m_autoScale3d = - mercatorPerMeter * CalculateAutoZoom(speedLimit, distanceToNextTurn, true /* isPerspectiveAllowed */); + mercatorPerMeter * CalculateAutoZoom(navigationContext.m_speedLimit, navigationContext.m_distanceToNextTurn, + true /* isPerspectiveAllowed */); } else { m_autoScale2d = m_autoScale3d = kUnknownAutoZoom; } - // Sets direction based on GPS if: + // Sets arrow direction based on GPS if: // 1. Compass is not available. // 2. Direction must be glued to the route during routing (route-corrected angle is set only in // OnLocationUpdate(): in OnCompassUpdate() the angle always has the original value. // 3. Device is moving faster then pedestrian. bool const isMovingFast = info.HasSpeed() && info.m_speed > kMinSpeedThresholdMps; - bool const glueArrowInRouting = isNavigable && m_isArrowGluedInRouting; + bool const glueArrowInRouting = navigationContext.m_isNavigable && m_isArrowGluedInRouting; + + // Calculate the route direction by looking ahead a distance in the route + // depending on the current speed + if (glueArrowInRouting && navigationContext.m_followedPolyline != nullptr) + { + double const speed = + (navigationContext.m_speedLimit > 0 ? measurement_utils::MpsToKmph(navigationContext.m_speedLimit) + : kDefaultSpeedLimitKmpH); + double const lookaheadTimeSec = std::min(speed * kLookaheadTimeSpeedRatio, kMaxLookaheadTimeSec); + double const lookaheadDistance = std::min(speed * lookaheadTimeSec, navigationContext.m_distanceToNextTurn); + + auto const & iter = navigationContext.m_followedPolyline->GetCurrentIter(); + m2::PointD const point = navigationContext.m_followedPolyline->GetLookaheadPoint(lookaheadDistance); + + auto angle = math::RadToDeg(ang::AngleTo(iter.m_pt, point)); + if (std::isnan(angle) || std::isinf(angle)) + { + // fallback in case the current route becomes invalid for any reason + SetRouteDirection(info.m_bearing); + } + else + { + SetRouteDirection(math::DegToRad(location::AngleToBearing(angle))); + } + } if ((!m_isCompassAvailable || glueArrowInRouting || isMovingFast) && info.HasBearing()) { - SetDirection(math::DegToRad(info.m_bearing)); + SetArrowDirection(math::DegToRad(info.m_bearing)); m_lastGPSBearingTimer.Reset(); } + m_direction = m_mode == location::FollowAndRotateRoute ? m_routeDirection : m_arrowDirection; + if (m_isPositionAssigned && (!AlmostCurrentPosition(oldPos) || !AlmostCurrentAzimut(oldAzimut))) { CreateAnim(oldPos, oldAzimut, screen); @@ -487,9 +530,9 @@ void MyPositionController::OnLocationUpdate(location::GpsInfo const & info, bool { ChangeModelView(m_position, kDoNotChangeZoom); } - else if (m_mode == location::FollowAndRotate) + else if (m_mode == location::FollowAndRotateCompass || m_mode == location::FollowAndRotateRoute) { - ChangeModelView(m_position, m_drawDirection, + ChangeModelView(m_position, m_direction, m_isInRouting ? GetRoutingRotationPixelCenter() : m_visiblePixelRect.Center(), kDoNotChangeZoom); } @@ -499,7 +542,7 @@ void MyPositionController::OnLocationUpdate(location::GpsInfo const & info, bool { if (m_isInRouting) { - ChangeMode(location::FollowAndRotate); + ChangeMode(location::FollowAndRotateCompass); UpdateViewport(kMaxScaleZoomLevel); } else @@ -525,7 +568,7 @@ void MyPositionController::OnLocationUpdate(location::GpsInfo const & info, bool { if (m_isInRouting) { - ChangeMode(location::FollowAndRotate); + ChangeMode(location::FollowAndRotateCompass); UpdateViewport(kMaxScaleZoomLevel); } else @@ -552,7 +595,8 @@ void MyPositionController::LoseLocation() { if (m_mode == location::NotFollowNoPosition) return; - else if (m_mode == location::Follow || m_mode == location::FollowAndRotate) + else if (m_mode == location::Follow || m_mode == location::FollowAndRotateCompass || + m_mode == location::FollowAndRotateRoute) ChangeMode(location::PendingPosition); else ChangeMode(location::NotFollowNoPosition); @@ -570,10 +614,11 @@ void MyPositionController::OnCompassUpdate(location::CompassInfo const & info, S if ((IsInRouting() && m_isArrowGluedInRouting) || existsFreshGpsBearing) return; - SetDirection(info.m_bearing); + SetArrowDirection(info.m_bearing); - if (m_isPositionAssigned && !AlmostCurrentAzimut(oldAzimut) && m_mode == location::FollowAndRotate) + if (m_isPositionAssigned && !AlmostCurrentAzimut(oldAzimut) && m_mode == location::FollowAndRotateCompass) { + m_direction = info.m_bearing; CreateAnim(GetDrawablePosition(), oldAzimut, screen); m_isDirtyViewport = true; } @@ -582,10 +627,10 @@ void MyPositionController::OnCompassUpdate(location::CompassInfo const & info, S bool MyPositionController::UpdateViewportWithAutoZoom() { double const autoScale = m_enablePerspectiveInRouting ? m_autoScale3d : m_autoScale2d; - if (autoScale > 0.0 && m_mode == location::FollowAndRotate && m_isInRouting && m_enableAutoZoomInRouting && - !m_needBlockAutoZoom) + if (autoScale > 0.0 && (m_mode == location::FollowAndRotateCompass || m_mode == location::FollowAndRotateRoute) && + m_isInRouting && m_enableAutoZoomInRouting && !m_needBlockAutoZoom) { - ChangeModelView(autoScale, m_position, m_drawDirection, GetRoutingRotationPixelCenter()); + ChangeModelView(autoScale, m_position, m_direction, GetRoutingRotationPixelCenter()); return true; } return false; @@ -617,7 +662,7 @@ void MyPositionController::Render(ref_ptr context, ref_ptr< m_shape->SetPositionObsolete(m_positionIsObsolete); m_shape->SetPosition(m2::PointF(GetDrawablePosition())); m_shape->SetAzimuth(static_cast(GetDrawableAzimut())); - m_shape->SetIsValidAzimuth(IsRotationAvailable()); + m_shape->SetIsValidAzimuth(IsArrowRotationAvailable()); m_shape->SetAccuracy(static_cast(m_errorRadius)); m_shape->SetRoutingMode(IsInRouting()); @@ -631,7 +676,7 @@ void MyPositionController::Render(ref_ptr context, ref_ptr< bool MyPositionController::IsRouteFollowingActive() const { - return IsInRouting() && m_mode == location::FollowAndRotate; + return IsInRouting() && m_mode == location::FollowAndRotateCompass; } bool MyPositionController::AlmostCurrentPosition(m2::PointD const & pos) const @@ -643,18 +688,25 @@ bool MyPositionController::AlmostCurrentPosition(m2::PointD const & pos) const bool MyPositionController::AlmostCurrentAzimut(double azimut) const { double constexpr kDirectionEqualityDelta = 1e-3; - return AlmostEqualAbs(azimut, m_drawDirection, kDirectionEqualityDelta); + return AlmostEqualAbs(azimut, m_direction, kDirectionEqualityDelta); } -void MyPositionController::SetDirection(double bearing) +void MyPositionController::SetRouteDirection(double bearing) { - m_drawDirection = bearing; - m_isDirectionAssigned = true; + m_routeDirection = bearing; + m_isRouteDirectionAssigned = true; +} + +void MyPositionController::SetArrowDirection(double bearing) +{ + m_arrowDirection = bearing; + m_isArrowDirectionAssigned = true; } void MyPositionController::ChangeMode(location::EMyPositionMode newMode) { - if (m_isInRouting && (m_mode != newMode) && (newMode == location::FollowAndRotate)) + if (m_isInRouting && (m_mode != newMode) && + (newMode == location::FollowAndRotateCompass || newMode == location::FollowAndRotateRoute)) ResetBlockAutoZoomTimer(); m_mode = newMode; @@ -675,7 +727,8 @@ bool MyPositionController::IsWaitingForLocation() const void MyPositionController::StopLocationFollow() { - if (m_mode == location::Follow || m_mode == location::FollowAndRotate) + if (m_mode == location::Follow || m_mode == location::FollowAndRotateCompass || + m_mode == location::FollowAndRotateRoute) ChangeMode(location::NotFollow); m_desiredInitMode = location::NotFollow; @@ -690,7 +743,7 @@ void MyPositionController::OnEnterForeground(double backgroundTime) // When location was active during previous session the app will try to follow the user. if (m_mode == location::NotFollow) { - ChangeMode(m_isInRouting ? location::FollowAndRotate : location::Follow); + ChangeMode(m_isInRouting ? location::FollowAndRotateCompass : location::Follow); UpdateViewport(kDoNotChangeZoom); } @@ -706,7 +759,7 @@ void MyPositionController::OnEnterBackground() {} void MyPositionController::OnCompassTapped() { - if (m_mode == location::FollowAndRotate) + if (m_mode == location::FollowAndRotateCompass) { ChangeMode(location::Follow); ChangeModelView(m_position, 0.0, m_visiblePixelRect.Center(), kDoNotChangeZoom); @@ -763,9 +816,9 @@ void MyPositionController::UpdateViewport(int zoomLevel) { ChangeModelView(m_position, zoomLevel); } - else if (m_mode == location::FollowAndRotate) + else if (m_mode == location::FollowAndRotateCompass || m_mode == location::FollowAndRotateRoute) { - ChangeModelView(m_position, m_drawDirection, + ChangeModelView(m_position, m_direction, m_isInRouting ? GetRoutingRotationPixelCenter() : m_visiblePixelRect.Center(), zoomLevel); } } @@ -775,7 +828,7 @@ m2::PointD MyPositionController::GetRotationPixelCenter() const if (m_mode == location::Follow) return m_visiblePixelRect.Center(); - if (m_mode == location::FollowAndRotate) + if (m_mode == location::FollowAndRotateCompass || m_mode == location::FollowAndRotateRoute) return m_isInRouting ? GetRoutingRotationPixelCenter() : m_visiblePixelRect.Center(); return m2::PointD::Zero(); @@ -817,15 +870,15 @@ double MyPositionController::GetDrawableAzimut() } if (m_isPendingAnimation) - return m_oldDrawDirection; + return m_oldArrowDirection; - return m_drawDirection; + return m_arrowDirection; } void MyPositionController::CreateAnim(m2::PointD const & oldPos, double oldAzimut, ScreenBase const & screen) { double const moveDuration = PositionInterpolator::GetMoveDuration(oldPos, m_position, screen); - double const rotateDuration = AngleInterpolator::GetRotateDuration(oldAzimut, m_drawDirection); + double const rotateDuration = AngleInterpolator::GetRotateDuration(oldAzimut, m_arrowDirection); if (df::IsAnimationAllowed(std::max(moveDuration, rotateDuration), screen)) { if (IsModeChangeViewport()) @@ -834,7 +887,7 @@ void MyPositionController::CreateAnim(m2::PointD const & oldPos, double oldAzimu { drape_ptr anim = make_unique_dp( GetDrawablePosition(), m_position, syncAnim == nullptr ? moveDuration : syncAnim->GetDuration(), - GetDrawableAzimut(), m_drawDirection); + GetDrawableAzimut(), m_arrowDirection); if (syncAnim != nullptr) { anim->SetMaxDuration(syncAnim->GetMaxDuration()); @@ -843,13 +896,13 @@ void MyPositionController::CreateAnim(m2::PointD const & oldPos, double oldAzimu return anim; }; m_oldPosition = oldPos; - m_oldDrawDirection = oldAzimut; + m_oldArrowDirection = oldAzimut; m_isPendingAnimation = true; } else { AnimationSystem::Instance().CombineAnimation( - make_unique_dp(oldPos, m_position, moveDuration, oldAzimut, m_drawDirection)); + make_unique_dp(oldPos, m_position, moveDuration, oldAzimut, m_arrowDirection)); } } } @@ -868,16 +921,18 @@ void MyPositionController::EnableAutoZoomInRouting(bool enableAutoZoom) } } -void MyPositionController::ActivateRouting(int zoomLevel, bool enableAutoZoom, bool isArrowGlued) +void MyPositionController::ActivateRouting(int zoomLevel, bool enableAutoZoom, bool isArrowGlued, + bool allowRouteRotation) { if (!m_isInRouting) { m_isInRouting = true; m_isArrowGluedInRouting = isArrowGlued; m_enableAutoZoomInRouting = enableAutoZoom; + m_allowRouteRotationInRouting = allowRouteRotation; - ChangeMode(location::FollowAndRotate); - ChangeModelView(m_position, m_isDirectionAssigned ? m_drawDirection : 0.0, GetRoutingRotationPixelCenter(), + ChangeMode(location::FollowAndRotateCompass); + ChangeModelView(m_position, m_isRouteDirectionAssigned ? m_routeDirection : 0.0, GetRoutingRotationPixelCenter(), zoomLevel, [this](ref_ptr anim) { UpdateViewport(kDoNotChangeZoom); }); ResetRoutingNotFollowTimer(); } @@ -889,8 +944,10 @@ void MyPositionController::DeactivateRouting() { m_isInRouting = false; m_isArrowGluedInRouting = false; + m_allowRouteRotationInRouting = false; - m_isDirectionAssigned = m_isCompassAvailable && m_isDirectionAssigned; + m_isArrowDirectionAssigned = m_isCompassAvailable && m_isArrowDirectionAssigned; + m_isRouteDirectionAssigned = false; ChangeMode(location::Follow); ChangeModelView(m_position, 0.0, m_visiblePixelRect.Center(), kDoNotChangeZoom); @@ -919,7 +976,7 @@ void MyPositionController::CheckNotFollowRouting() CHECK_ON_TIMEOUT(m_routingNotFollowNotifyId, kMaxNotFollowRoutingTimeSec, CheckNotFollowRouting); if (m_routingNotFollowTimer.ElapsedSeconds() >= kMaxNotFollowRoutingTimeSec) { - ChangeMode(location::FollowAndRotate); + ChangeMode(location::FollowAndRotateCompass); UpdateViewport(kDoNotChangeZoom); } } diff --git a/libs/drape_frontend/my_position_controller.hpp b/libs/drape_frontend/my_position_controller.hpp index 0ac4a638e..9523b3c3d 100644 --- a/libs/drape_frontend/my_position_controller.hpp +++ b/libs/drape_frontend/my_position_controller.hpp @@ -7,6 +7,7 @@ #include "drape/pointers.hpp" +#include "routing/base/followed_polyline.hpp" #include "shaders/program_manager.hpp" #include "platform/location.hpp" @@ -24,6 +25,24 @@ using TAnimationCreator = std::function(ref_ptr) class DrapeNotifier; +struct NavigationContext +{ + bool m_isNavigable = false; + double m_distanceToNextTurn = 0.0; + double m_speedLimit = 0.0; + routing::FollowedPolyline const * m_followedPolyline = nullptr; + + NavigationContext() = default; + + NavigationContext(bool navigable, double distanceToTurn, double speedLimit, + routing::FollowedPolyline const & followedPolyline) + : m_isNavigable(navigable) + , m_distanceToNextTurn(distanceToTurn) + , m_speedLimit(speedLimit) + , m_followedPolyline(&followedPolyline) + {} +}; + class MyPositionController { public: @@ -102,7 +121,7 @@ public: drape_ptr && shape, Arrow3d::PreloadedData && preloadedData); void ResetRenderShape(); - void ActivateRouting(int zoomLevel, bool enableAutoZoom, bool isArrowGlued); + void ActivateRouting(int zoomLevel, bool enableAutoZoom, bool isArrowGlued, bool allowRouteRotation); void DeactivateRouting(); void EnablePerspectiveInRouting(bool enablePerspective); @@ -117,14 +136,15 @@ public: void OnEnterBackground(); void OnCompassTapped(); - void OnLocationUpdate(location::GpsInfo const & info, bool isNavigable, double distanceToNextTurn, double speedLimit, + void OnLocationUpdate(location::GpsInfo const & info, df::NavigationContext const & navigationContext, ScreenBase const & screen); void OnCompassUpdate(location::CompassInfo const & info, ScreenBase const & screen); void Render(ref_ptr context, ref_ptr mng, ScreenBase const & screen, int zoomLevel, FrameValues const & frameValues); - bool IsRotationAvailable() const { return m_isDirectionAssigned; } + bool IsArrowRotationAvailable() const { return m_isArrowDirectionAssigned; } + bool IsRouteRotationAvailable() const { return m_isRouteDirectionAssigned; } bool IsInRouting() const { return m_isInRouting; } bool IsRouteFollowingActive() const; bool IsModeChangeViewport() const; @@ -135,7 +155,8 @@ public: private: void ChangeMode(location::EMyPositionMode newMode); - void SetDirection(double bearing); + void SetRouteDirection(double bearing); + void SetArrowDirection(double bearing); void ChangeModelView(m2::PointD const & center, int zoomLevel); void ChangeModelView(double azimuth); @@ -178,12 +199,15 @@ private: double m_errorRadius; // error radius in mercator. double m_horizontalAccuracy; m2::PointD m_position; // position in mercator. - double m_drawDirection; + double m_direction; + double m_routeDirection; + double m_arrowDirection; m2::PointD m_oldPosition; // position in mercator. - double m_oldDrawDirection; + double m_oldArrowDirection; bool m_enablePerspectiveInRouting; bool m_enableAutoZoomInRouting; + bool m_allowRouteRotationInRouting; double m_autoScale2d; double m_autoScale3d; @@ -205,7 +229,8 @@ private: TAnimationCreator m_animCreator; bool m_isPositionAssigned; - bool m_isDirectionAssigned; + bool m_isArrowDirectionAssigned; + bool m_isRouteDirectionAssigned; bool m_isCompassAvailable; bool m_positionIsObsolete; diff --git a/libs/map/framework.cpp b/libs/map/framework.cpp index ba683261f..bd03ec0bd 100644 --- a/libs/map/framework.cpp +++ b/libs/map/framework.cpp @@ -7,6 +7,7 @@ #include "ge0/url_generator.hpp" +#include "platform/location.hpp" #include "routing/route.hpp" #include "routing/speed_camera_prohibition.hpp" @@ -1453,7 +1454,7 @@ void Framework::CreateDrapeEngine(ref_ptr contextFac GetPlatform().RunTask(Platform::Thread::Gui, [this, mode, routingActive]() { // Deactivate selection (and hide place page) if we return to routing in F&R mode. - if (routingActive && mode == location::FollowAndRotate) + if (routingActive && (mode == location::FollowAndRotateCompass || mode == location::FollowAndRotateRoute)) DeactivateMapSelection(); if (m_myPositionListener != nullptr) @@ -3208,6 +3209,7 @@ void Framework::ReadFeatures(function const & reader, vecto void Framework::OnRouteFollow(routing::RouterType type) { bool const isPedestrianRoute = type == RouterType::Pedestrian; + bool const allowRouteRotation = type == RouterType::Vehicle; bool const enableAutoZoom = isPedestrianRoute ? false : LoadAutoZoom(); int const scale = isPedestrianRoute ? scales::GetPedestrianNavigationScale() : scales::GetNavigationScale(); int scale3d = isPedestrianRoute ? scales::GetPedestrianNavigation3dScale() : scales::GetNavigation3dScale(); @@ -3223,7 +3225,7 @@ void Framework::OnRouteFollow(routing::RouterType type) // TODO. We need to sync two enums VehicleType and RouterType to be able to pass // GetRoutingSettings(type).m_matchRoute to the FollowRoute() instead of |isPedestrianRoute|. // |isArrowGlued| parameter fully corresponds to |m_matchRoute| in RoutingSettings. - m_drapeEngine->FollowRoute(scale, scale3d, enableAutoZoom, !isPedestrianRoute /* isArrowGlued */); + m_drapeEngine->FollowRoute(scale, scale3d, enableAutoZoom, !isPedestrianRoute /* isArrowGlued */, allowRouteRotation); } // RoutingManager::Delegate diff --git a/libs/map/routing_manager.cpp b/libs/map/routing_manager.cpp index 5034ed81f..a932d16b0 100644 --- a/libs/map/routing_manager.cpp +++ b/libs/map/routing_manager.cpp @@ -1,9 +1,12 @@ #include "routing_manager.hpp" +#include "drape_frontend/my_position_controller.hpp" +#include "geometry/angles.hpp" #include "map/chart_generator.hpp" #include "map/routing_mark.hpp" #include "routing/absent_regions_finder.hpp" +#include "routing/base/followed_polyline.hpp" #include "routing/checkpoint_predictor.hpp" #include "routing/index_router.hpp" #include "routing/route.hpp" @@ -1170,9 +1173,9 @@ void RoutingManager::SetDrapeEngine(ref_ptr engine, bool is3dAl if (m_gpsInfoCache != nullptr) { auto routeMatchingInfo = GetRouteMatchingInfo(*m_gpsInfoCache); - m_drapeEngine.SafeCall(&df::DrapeEngine::SetGpsInfo, *m_gpsInfoCache, m_routingSession.IsNavigable(), - m_routingSession.GetDistanceToNextTurn(), m_routingSession.GetCurrentSpeedLimit(), - routeMatchingInfo); + df::NavigationContext navigationContext(m_routingSession.IsNavigable(), m_routingSession.GetDistanceToNextTurn(), + m_routingSession.GetCurrentSpeedLimit(), GetRoutePolyline()); + m_drapeEngine.SafeCall(&df::DrapeEngine::SetGpsInfo, *m_gpsInfoCache, navigationContext, routeMatchingInfo); m_gpsInfoCache.reset(); } @@ -1516,9 +1519,9 @@ void RoutingManager::OnExtrapolatedLocationUpdate(location::GpsInfo const & info m_gpsInfoCache = make_unique(gpsInfo); auto routeMatchingInfo = GetRouteMatchingInfo(gpsInfo); - m_drapeEngine.SafeCall(&df::DrapeEngine::SetGpsInfo, gpsInfo, m_routingSession.IsNavigable(), - m_routingSession.GetDistanceToNextTurn(), m_routingSession.GetCurrentSpeedLimit(), - routeMatchingInfo); + df::NavigationContext navigationContext(m_routingSession.IsNavigable(), m_routingSession.GetDistanceToNextTurn(), + m_routingSession.GetCurrentSpeedLimit(), GetRoutePolyline()); + m_drapeEngine.SafeCall(&df::DrapeEngine::SetGpsInfo, gpsInfo, navigationContext, routeMatchingInfo); } void RoutingManager::DeleteSavedRoutePoints() diff --git a/libs/platform/location.hpp b/libs/platform/location.hpp index 5bff9fbfd..60482fab9 100644 --- a/libs/platform/location.hpp +++ b/libs/platform/location.hpp @@ -139,7 +139,8 @@ enum EMyPositionMode NotFollowNoPosition, NotFollow, Follow, - FollowAndRotate + FollowAndRotateCompass, + FollowAndRotateRoute }; using TMyPositionModeChanged = std::function; diff --git a/libs/platform/settings.cpp b/libs/platform/settings.cpp index 500906c13..460d9bac7 100644 --- a/libs/platform/settings.cpp +++ b/libs/platform/settings.cpp @@ -308,7 +308,8 @@ string ToString(location::EMyPositionMode const & v) case location::NotFollow: return "NotFollow"; case location::NotFollowNoPosition: return "NotFollowNoPosition"; case location::Follow: return "Follow"; - case location::FollowAndRotate: return "FollowAndRotate"; + case location::FollowAndRotateCompass: return "FollowAndRotateCompass"; + case location::FollowAndRotateRoute: return "FollowAndRotateRoute"; default: return "Pending"; } } @@ -324,8 +325,10 @@ bool FromString(string const & s, location::EMyPositi v = location::NotFollowNoPosition; else if (s == "Follow") v = location::Follow; - else if (s == "FollowAndRotate") - v = location::FollowAndRotate; + else if (s == "FollowAndRotateCompass") + v = location::FollowAndRotateCompass; + else if (s == "FollowAndRotateRoute") + v = location::FollowAndRotateRoute; else return false; diff --git a/libs/routing/base/followed_polyline.cpp b/libs/routing/base/followed_polyline.cpp index 113ffb836..0622ac76f 100644 --- a/libs/routing/base/followed_polyline.cpp +++ b/libs/routing/base/followed_polyline.cpp @@ -237,4 +237,34 @@ Iter FollowedPolyline::GetClosestMatchingProjectionInInterval(m2::RectD const & return nearestIter; } + +m2::PointD FollowedPolyline::GetLookaheadPoint(double lookaheadDistanceM) const +{ + if (!IsValid()) + return m2::PointD(); + + size_t segmentIdx = m_current.m_ind; + m2::PointD prev = m_current.m_pt; + + size_t const maxSegmentIdx = m_poly.GetSize() - 1; + double remaining = lookaheadDistanceM; + + while (segmentIdx < maxSegmentIdx) + { + m2::PointD const & next = m_poly.GetPoint(segmentIdx + 1); + double segLen = mercator::DistanceOnEarth(prev, next); + + if (remaining <= segLen) + { + double t = remaining / segLen; + return prev + (next - prev) * t; + } + + remaining -= segLen; + prev = next; + segmentIdx += 1; + } + + return m_poly.GetPoint(segmentIdx); +} } // namespace routing diff --git a/libs/routing/base/followed_polyline.hpp b/libs/routing/base/followed_polyline.hpp index efd8c1a50..a6f2a8de9 100644 --- a/libs/routing/base/followed_polyline.hpp +++ b/libs/routing/base/followed_polyline.hpp @@ -125,6 +125,9 @@ public: bool IsFakeSegment(size_t index) const; + /// \brief Obtain a point |lookaheadDistanceM| meters along the current route + m2::PointD GetLookaheadPoint(double lookaheadDistanceM) const; + private: /// \returns iterator to the best projection of center of |posRect| to the |m_poly|. /// If there's a good projection of center of |posRect| to two closest segments of |m_poly|