File indexing completed on 2024-04-28 15:15:34

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2010 Siddharth Srivastava <akssps011@gmail.com>
0004 // SPDX-FileCopyrightText: 2011 Bernhard Beschow <bbeschow@cs.tu-berlin.de>
0005 //
0006 
0007 
0008 #include "AutoNavigation.h"
0009 
0010 #include "GeoDataCoordinates.h"
0011 #include "PositionTracking.h"
0012 #include "MarbleDebug.h"
0013 #include "MarbleModel.h"
0014 #include "ViewportParams.h"
0015 #include "RoutingManager.h"
0016 #include "RoutingModel.h"
0017 #include "Route.h"
0018 
0019 #include <QWidget>
0020 #include <QRect>
0021 #include <QPointF>
0022 #include <QTimer>
0023 #include <cmath>
0024 
0025 namespace Marble {
0026 
0027 class Q_DECL_HIDDEN AutoNavigation::Private
0028 {
0029 public:
0030 
0031     AutoNavigation *const m_parent;
0032     const MarbleModel *const m_model;
0033     const ViewportParams *const m_viewport;
0034     const PositionTracking *const m_tracking;
0035     AutoNavigation::CenterMode m_recenterMode;
0036     bool m_adjustZoom;
0037     QTimer m_lastWidgetInteraction;
0038     bool m_selfInteraction;
0039 
0040     /** Constructor */
0041     Private( MarbleModel *model, const ViewportParams *viewport, AutoNavigation *parent );
0042 
0043     /**
0044      * @brief To center on when reaching custom defined border
0045      * @param position current gps location
0046      * @param speed optional speed argument
0047      */
0048      void moveOnBorderToCenter( const GeoDataCoordinates &position, qreal speed );
0049 
0050     /**
0051      * For calculating intersection point of projected LineString from
0052      * current gps location with the map border
0053      * @param position current gps location
0054      */
0055      GeoDataCoordinates findIntersection( qreal currentX, qreal currentY ) const;
0056 
0057     /**
0058      * @brief Adjust the zoom value of the map
0059      * @param currentPosition current location of the gps device
0060      */
0061      void adjustZoom( const GeoDataCoordinates &currentPosition, qreal speed );
0062 
0063      /**
0064        * Center the widget on the given position unless recentering is currently inhibited
0065        */
0066      void centerOn( const GeoDataCoordinates &position );
0067 };
0068 
0069 AutoNavigation::Private::Private( MarbleModel *model, const ViewportParams *viewport, AutoNavigation *parent ) :
0070         m_parent( parent ),
0071         m_model( model ),
0072         m_viewport( viewport ),
0073         m_tracking( model->positionTracking() ),
0074         m_recenterMode( AutoNavigation::DontRecenter ),
0075         m_adjustZoom( 0 ),
0076         m_selfInteraction( false )
0077 {
0078     m_lastWidgetInteraction.setInterval( 10 * 1000 );
0079     m_lastWidgetInteraction.setSingleShot( true );
0080 }
0081 
0082 void AutoNavigation::Private::moveOnBorderToCenter( const GeoDataCoordinates &position, qreal )
0083 {
0084     qreal x = 0.0;
0085     qreal y = 0.0;
0086     //recenter if initially the gps location is not visible on the screen
0087     if(!( m_viewport->screenCoordinates( position, x, y ) ) ) {
0088          centerOn( position );
0089     }
0090     qreal centerLon = m_viewport->centerLongitude();
0091     qreal centerLat = m_viewport->centerLatitude();
0092 
0093     qreal centerX = 0.0;
0094     qreal centerY = 0.0;
0095 
0096     m_viewport->screenCoordinates( centerLon, centerLat, centerX, centerY );
0097 
0098     const qreal borderRatio = 0.25;
0099     //defining the default border distance from map center
0100     int shiftX = qRound( centerX * borderRatio );
0101     int shiftY = qRound( centerY * borderRatio );
0102 
0103     QRect recenterBorderBound;
0104     recenterBorderBound.setCoords( centerX-shiftX, centerY-shiftY, centerX+shiftX,  centerY+shiftY );
0105 
0106     if( !recenterBorderBound.contains( x,y ) ) {
0107         centerOn( position );
0108     }
0109 }
0110 
0111 GeoDataCoordinates AutoNavigation::Private::findIntersection( qreal currentX, qreal currentY ) const
0112 {
0113     qreal direction = m_tracking->direction();
0114     if ( direction >= 360 ) {
0115         direction = fmod( direction,360.0 );
0116     }
0117 
0118     const qreal width = m_viewport->width();
0119     const qreal height = m_viewport->height();
0120 
0121     QPointF intercept;
0122     QPointF destinationHorizontal;
0123     QPointF destinationVertical;
0124     QPointF destination;
0125 
0126     bool crossHorizontal =  false;
0127     bool crossVertical = false;
0128 
0129     //calculation of intersection point
0130     if( 0 < direction && direction < 90 ) {
0131         const qreal angle = direction * DEG2RAD;
0132 
0133         //Intersection with line x = width
0134         intercept.setX( width - currentX );
0135         intercept.setY( intercept.x() / tan( angle ) );
0136         destinationVertical.setX( width );
0137         destinationVertical.setY( currentY-intercept.y() );
0138 
0139         //Intersection with line y = 0
0140         intercept.setY( currentY );
0141         intercept.setX( intercept.y() * tan( angle ) );
0142         destinationHorizontal.setX( currentX + intercept.x() );
0143         destinationHorizontal.setY( 0 );
0144 
0145         if ( destinationVertical.y() < 0 ) {
0146             crossHorizontal = true;
0147         }
0148         else if( destinationHorizontal.x() > width ) {
0149             crossVertical = true;
0150         }
0151 
0152     }
0153     else if( 270 < direction && direction < 360 ) {
0154         const qreal angle = (direction - 270) * DEG2RAD;
0155 
0156         //Intersection with line y = 0
0157         intercept.setY( currentY );
0158         intercept.setX( intercept.y() / tan( angle ) );
0159         destinationHorizontal.setX( currentX - intercept.x() );
0160         destinationHorizontal.setY( 0 );
0161 
0162         //Intersection with line x = 0
0163         intercept.setX( currentX );
0164         intercept.setY( intercept.x() * tan( angle ) );
0165         destinationVertical.setY( currentY - intercept.y() );
0166         destinationVertical.setX( 0 );
0167 
0168         if( destinationHorizontal.x() > width ) {
0169             crossVertical = true;
0170         }
0171         else if( destinationVertical.y() < 0 ) {
0172             crossHorizontal = true;
0173         }
0174 
0175     }
0176     else if( 180 < direction && direction < 270  ) {
0177         const qreal angle = (direction - 180) * DEG2RAD;
0178 
0179         //Intersection with line x = 0
0180         intercept.setX( currentX );
0181         intercept.setY( intercept.x() / tan( angle ) );
0182         destinationVertical.setY( currentY + intercept.y() );
0183         destinationVertical.setX( 0 );
0184 
0185         //Intersection with line y = height
0186         intercept.setY( currentY );
0187         intercept.setX( intercept.y() * tan( angle ) );
0188         destinationHorizontal.setX( currentX - intercept.x() );
0189         destinationHorizontal.setY( height );
0190 
0191         if ( destinationVertical.y() > height ) {
0192             crossHorizontal = true;
0193         }
0194         else if ( destinationHorizontal.x() < 0 ) {
0195             crossVertical = true;
0196         }
0197 
0198     }
0199     else if( 90 < direction && direction < 180  ) {
0200         const qreal angle = (direction - 90) * DEG2RAD;
0201 
0202         //Intersection with line y = height
0203         intercept.setY( height - currentY );
0204         intercept.setX( intercept.y() / tan( angle ) );
0205         destinationHorizontal.setX( currentX + intercept.x() );
0206         destinationHorizontal.setY( height );
0207 
0208         //Intersection with line x = width
0209         intercept.setX( width - currentX );
0210         intercept.setY( intercept.x() * tan( angle ) );
0211         destinationVertical.setX( width );
0212         destinationVertical.setY( currentY + intercept.y() );
0213 
0214         if ( destinationHorizontal.x() > width ) {
0215             crossVertical = true;
0216         }
0217         else if( destinationVertical.y() > height ) {
0218             crossHorizontal = true;
0219         }
0220 
0221     }
0222     else if( direction == 0 ) {
0223         destinationHorizontal.setX( currentX );
0224         destinationHorizontal.setY( 0 );
0225         crossHorizontal = true;
0226     }
0227     else if( direction == 90 ) {
0228         destinationVertical.setX( width );
0229         destinationVertical.setY( currentY );
0230         crossVertical = true;
0231     }
0232     else if( direction == 180 ) {
0233         destinationHorizontal.setX( currentX );
0234         destinationHorizontal.setY( height );
0235         crossHorizontal = true;
0236     }
0237     else if( direction == 270 ) {
0238         destinationVertical.setX( 0 );
0239         destinationVertical.setY( currentY );
0240         crossVertical = true;
0241     }
0242 
0243     if ( crossHorizontal == true && crossVertical == false ) {
0244         destination.setX( destinationHorizontal.x() );
0245         destination.setY( destinationHorizontal.y() );
0246     }
0247     else if ( crossVertical == true && crossHorizontal == false ) {
0248         destination.setX( destinationVertical.x() );
0249         destination.setY( destinationVertical.y() );
0250     }
0251 
0252     qreal destinationLon = 0.0;
0253     qreal destinationLat = 0.0;
0254     m_viewport->geoCoordinates( destination.x(), destination.y(), destinationLon, destinationLat,
0255                               GeoDataCoordinates::Radian );
0256     GeoDataCoordinates destinationCoord( destinationLon, destinationLat, GeoDataCoordinates::Radian );
0257 
0258     return destinationCoord;
0259 }
0260 
0261 void AutoNavigation::Private::adjustZoom( const GeoDataCoordinates &currentPosition, qreal speed )
0262 {
0263     qreal currentX = 0;
0264     qreal currentY = 0;
0265     if( !m_viewport->screenCoordinates(currentPosition, currentX, currentY ) ) {
0266         return;
0267     }
0268 
0269     const GeoDataCoordinates destination = findIntersection( currentX, currentY );
0270 
0271     const qreal greatCircleDistance = currentPosition.sphericalDistanceTo(destination);
0272     qreal radius = m_model->planetRadius();
0273     qreal distance = greatCircleDistance *  radius;
0274 
0275     if( speed != 0 ) {
0276         // time (in seconds) remaining to reach the border of the map
0277         qreal  remainingTime = distance / speed;
0278 
0279         // tolerance time limits (in seconds) before auto zooming
0280         qreal thresholdLow = 15;
0281         qreal thresholdHigh = 120;
0282 
0283         m_selfInteraction = true;
0284         if ( remainingTime < thresholdLow ) {
0285             emit m_parent->zoomOut( Instant );
0286         }
0287         else if ( remainingTime > thresholdHigh ) {
0288             emit m_parent->zoomIn( Instant );
0289         }
0290         m_selfInteraction = false;
0291     }
0292 }
0293 
0294 void AutoNavigation::Private::centerOn( const GeoDataCoordinates &position )
0295 {
0296     m_selfInteraction = true;
0297     RoutingManager const * routingManager = m_model->routingManager();
0298     RoutingModel const * routingModel = routingManager->routingModel();
0299     if (!routingManager->guidanceModeEnabled() || routingModel->deviatedFromRoute()){
0300         emit m_parent->centerOn( position, false );
0301     } else {
0302         GeoDataCoordinates positionOnRoute = routingModel->route().positionOnRoute();
0303         emit m_parent->centerOn( positionOnRoute, false );
0304     }
0305     m_selfInteraction = false;
0306 }
0307 
0308 AutoNavigation::AutoNavigation( MarbleModel *model, const ViewportParams *viewport, QObject *parent ) :
0309     QObject( parent ),
0310     d( new AutoNavigation::Private( model, viewport, this ) )
0311 {
0312     connect( d->m_tracking, SIGNAL(gpsLocation(GeoDataCoordinates,qreal)),
0313                 this, SLOT(adjust(GeoDataCoordinates,qreal)) );
0314 }
0315 
0316 AutoNavigation::~AutoNavigation()
0317 {
0318     delete d;
0319 }
0320 
0321 void AutoNavigation::adjust( const GeoDataCoordinates &position, qreal speed )
0322 {
0323     if ( d->m_lastWidgetInteraction.isActive() ) {
0324         return;
0325     }
0326 
0327     switch( d->m_recenterMode ) {
0328     case DontRecenter:
0329         /* nothing to do */
0330         break;
0331     case AlwaysRecenter:
0332         d->centerOn( position );
0333         break;
0334     case RecenterOnBorder:
0335         d->moveOnBorderToCenter( position, speed );
0336         break;
0337     }
0338 
0339     if ( d->m_adjustZoom ) {
0340         switch( d->m_recenterMode ) {
0341         case DontRecenter:
0342             /* nothing to do */
0343             break;
0344         case AlwaysRecenter:
0345         case RecenterOnBorder: // fallthrough
0346             d->adjustZoom( position, speed );
0347             break;
0348         }
0349     }
0350 }
0351 
0352 void AutoNavigation::setAutoZoom( bool autoZoom )
0353 {
0354     d->m_adjustZoom = autoZoom;
0355     emit autoZoomToggled( autoZoom );
0356 }
0357 
0358 void AutoNavigation::setRecenter( CenterMode recenterMode )
0359 {
0360     d->m_recenterMode = recenterMode;
0361     emit recenterModeChanged( recenterMode );
0362 }
0363 
0364 void AutoNavigation::inhibitAutoAdjustments()
0365 {
0366     if ( !d->m_selfInteraction ) {
0367         d->m_lastWidgetInteraction.start();
0368     }
0369 }
0370 
0371 AutoNavigation::CenterMode AutoNavigation::recenterMode() const
0372 {
0373     return d->m_recenterMode;
0374 }
0375 
0376 bool AutoNavigation::autoZoom() const
0377 {
0378     return d->m_adjustZoom;
0379 }
0380 
0381 } // namespace Marble
0382 
0383 #include "moc_AutoNavigation.cpp"