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 ¤tPosition, 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 ¤tPosition, 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"