1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 //
3 // SPDX-FileCopyrightText: 2010 Siddharth Srivastava <akssps011@gmail.com>
4 // SPDX-FileCopyrightText: 2011 Bernhard Beschow <bbeschow@cs.tu-berlin.de>
5 //
6
7
8 #include "AutoNavigation.h"
9
10 #include "GeoDataCoordinates.h"
11 #include "PositionTracking.h"
12 #include "MarbleDebug.h"
13 #include "MarbleModel.h"
14 #include "ViewportParams.h"
15 #include "RoutingManager.h"
16 #include "RoutingModel.h"
17 #include "Route.h"
18
19 #include <QWidget>
20 #include <QRect>
21 #include <QPointF>
22 #include <QTimer>
23 #include <cmath>
24
25 namespace Marble {
26
27 class Q_DECL_HIDDEN AutoNavigation::Private
28 {
29 public:
30
31 AutoNavigation *const m_parent;
32 const MarbleModel *const m_model;
33 const ViewportParams *const m_viewport;
34 const PositionTracking *const m_tracking;
35 AutoNavigation::CenterMode m_recenterMode;
36 bool m_adjustZoom;
37 QTimer m_lastWidgetInteraction;
38 bool m_selfInteraction;
39
40 /** Constructor */
41 Private( MarbleModel *model, const ViewportParams *viewport, AutoNavigation *parent );
42
43 /**
44 * @brief To center on when reaching custom defined border
45 * @param position current gps location
46 * @param speed optional speed argument
47 */
48 void moveOnBorderToCenter( const GeoDataCoordinates &position, qreal speed );
49
50 /**
51 * For calculating intersection point of projected LineString from
52 * current gps location with the map border
53 * @param position current gps location
54 */
55 GeoDataCoordinates findIntersection( qreal currentX, qreal currentY ) const;
56
57 /**
58 * @brief Adjust the zoom value of the map
59 * @param currentPosition current location of the gps device
60 */
61 void adjustZoom( const GeoDataCoordinates ¤tPosition, qreal speed );
62
63 /**
64 * Center the widget on the given position unless recentering is currently inhibited
65 */
66 void centerOn( const GeoDataCoordinates &position );
67 };
68
Private(MarbleModel * model,const ViewportParams * viewport,AutoNavigation * parent)69 AutoNavigation::Private::Private( MarbleModel *model, const ViewportParams *viewport, AutoNavigation *parent ) :
70 m_parent( parent ),
71 m_model( model ),
72 m_viewport( viewport ),
73 m_tracking( model->positionTracking() ),
74 m_recenterMode( AutoNavigation::DontRecenter ),
75 m_adjustZoom( 0 ),
76 m_selfInteraction( false )
77 {
78 m_lastWidgetInteraction.setInterval( 10 * 1000 );
79 m_lastWidgetInteraction.setSingleShot( true );
80 }
81
moveOnBorderToCenter(const GeoDataCoordinates & position,qreal)82 void AutoNavigation::Private::moveOnBorderToCenter( const GeoDataCoordinates &position, qreal )
83 {
84 qreal x = 0.0;
85 qreal y = 0.0;
86 //recenter if initially the gps location is not visible on the screen
87 if(!( m_viewport->screenCoordinates( position, x, y ) ) ) {
88 centerOn( position );
89 }
90 qreal centerLon = m_viewport->centerLongitude();
91 qreal centerLat = m_viewport->centerLatitude();
92
93 qreal centerX = 0.0;
94 qreal centerY = 0.0;
95
96 m_viewport->screenCoordinates( centerLon, centerLat, centerX, centerY );
97
98 const qreal borderRatio = 0.25;
99 //defining the default border distance from map center
100 int shiftX = qRound( centerX * borderRatio );
101 int shiftY = qRound( centerY * borderRatio );
102
103 QRect recenterBorderBound;
104 recenterBorderBound.setCoords( centerX-shiftX, centerY-shiftY, centerX+shiftX, centerY+shiftY );
105
106 if( !recenterBorderBound.contains( x,y ) ) {
107 centerOn( position );
108 }
109 }
110
findIntersection(qreal currentX,qreal currentY) const111 GeoDataCoordinates AutoNavigation::Private::findIntersection( qreal currentX, qreal currentY ) const
112 {
113 qreal direction = m_tracking->direction();
114 if ( direction >= 360 ) {
115 direction = fmod( direction,360.0 );
116 }
117
118 const qreal width = m_viewport->width();
119 const qreal height = m_viewport->height();
120
121 QPointF intercept;
122 QPointF destinationHorizontal;
123 QPointF destinationVertical;
124 QPointF destination;
125
126 bool crossHorizontal = false;
127 bool crossVertical = false;
128
129 //calculation of intersection point
130 if( 0 < direction && direction < 90 ) {
131 const qreal angle = direction * DEG2RAD;
132
133 //Intersection with line x = width
134 intercept.setX( width - currentX );
135 intercept.setY( intercept.x() / tan( angle ) );
136 destinationVertical.setX( width );
137 destinationVertical.setY( currentY-intercept.y() );
138
139 //Intersection with line y = 0
140 intercept.setY( currentY );
141 intercept.setX( intercept.y() * tan( angle ) );
142 destinationHorizontal.setX( currentX + intercept.x() );
143 destinationHorizontal.setY( 0 );
144
145 if ( destinationVertical.y() < 0 ) {
146 crossHorizontal = true;
147 }
148 else if( destinationHorizontal.x() > width ) {
149 crossVertical = true;
150 }
151
152 }
153 else if( 270 < direction && direction < 360 ) {
154 const qreal angle = (direction - 270) * DEG2RAD;
155
156 //Intersection with line y = 0
157 intercept.setY( currentY );
158 intercept.setX( intercept.y() / tan( angle ) );
159 destinationHorizontal.setX( currentX - intercept.x() );
160 destinationHorizontal.setY( 0 );
161
162 //Intersection with line x = 0
163 intercept.setX( currentX );
164 intercept.setY( intercept.x() * tan( angle ) );
165 destinationVertical.setY( currentY - intercept.y() );
166 destinationVertical.setX( 0 );
167
168 if( destinationHorizontal.x() > width ) {
169 crossVertical = true;
170 }
171 else if( destinationVertical.y() < 0 ) {
172 crossHorizontal = true;
173 }
174
175 }
176 else if( 180 < direction && direction < 270 ) {
177 const qreal angle = (direction - 180) * DEG2RAD;
178
179 //Intersection with line x = 0
180 intercept.setX( currentX );
181 intercept.setY( intercept.x() / tan( angle ) );
182 destinationVertical.setY( currentY + intercept.y() );
183 destinationVertical.setX( 0 );
184
185 //Intersection with line y = height
186 intercept.setY( currentY );
187 intercept.setX( intercept.y() * tan( angle ) );
188 destinationHorizontal.setX( currentX - intercept.x() );
189 destinationHorizontal.setY( height );
190
191 if ( destinationVertical.y() > height ) {
192 crossHorizontal = true;
193 }
194 else if ( destinationHorizontal.x() < 0 ) {
195 crossVertical = true;
196 }
197
198 }
199 else if( 90 < direction && direction < 180 ) {
200 const qreal angle = (direction - 90) * DEG2RAD;
201
202 //Intersection with line y = height
203 intercept.setY( height - currentY );
204 intercept.setX( intercept.y() / tan( angle ) );
205 destinationHorizontal.setX( currentX + intercept.x() );
206 destinationHorizontal.setY( height );
207
208 //Intersection with line x = width
209 intercept.setX( width - currentX );
210 intercept.setY( intercept.x() * tan( angle ) );
211 destinationVertical.setX( width );
212 destinationVertical.setY( currentY + intercept.y() );
213
214 if ( destinationHorizontal.x() > width ) {
215 crossVertical = true;
216 }
217 else if( destinationVertical.y() > height ) {
218 crossHorizontal = true;
219 }
220
221 }
222 else if( direction == 0 ) {
223 destinationHorizontal.setX( currentX );
224 destinationHorizontal.setY( 0 );
225 crossHorizontal = true;
226 }
227 else if( direction == 90 ) {
228 destinationVertical.setX( width );
229 destinationVertical.setY( currentY );
230 crossVertical = true;
231 }
232 else if( direction == 180 ) {
233 destinationHorizontal.setX( currentX );
234 destinationHorizontal.setY( height );
235 crossHorizontal = true;
236 }
237 else if( direction == 270 ) {
238 destinationVertical.setX( 0 );
239 destinationVertical.setY( currentY );
240 crossVertical = true;
241 }
242
243 if ( crossHorizontal == true && crossVertical == false ) {
244 destination.setX( destinationHorizontal.x() );
245 destination.setY( destinationHorizontal.y() );
246 }
247 else if ( crossVertical == true && crossHorizontal == false ) {
248 destination.setX( destinationVertical.x() );
249 destination.setY( destinationVertical.y() );
250 }
251
252 qreal destinationLon = 0.0;
253 qreal destinationLat = 0.0;
254 m_viewport->geoCoordinates( destination.x(), destination.y(), destinationLon, destinationLat,
255 GeoDataCoordinates::Radian );
256 GeoDataCoordinates destinationCoord( destinationLon, destinationLat, GeoDataCoordinates::Radian );
257
258 return destinationCoord;
259 }
260
adjustZoom(const GeoDataCoordinates & currentPosition,qreal speed)261 void AutoNavigation::Private::adjustZoom( const GeoDataCoordinates ¤tPosition, qreal speed )
262 {
263 qreal currentX = 0;
264 qreal currentY = 0;
265 if( !m_viewport->screenCoordinates(currentPosition, currentX, currentY ) ) {
266 return;
267 }
268
269 const GeoDataCoordinates destination = findIntersection( currentX, currentY );
270
271 const qreal greatCircleDistance = currentPosition.sphericalDistanceTo(destination);
272 qreal radius = m_model->planetRadius();
273 qreal distance = greatCircleDistance * radius;
274
275 if( speed != 0 ) {
276 // time (in seconds) remaining to reach the border of the map
277 qreal remainingTime = distance / speed;
278
279 // tolerance time limits (in seconds) before auto zooming
280 qreal thresholdLow = 15;
281 qreal thresholdHigh = 120;
282
283 m_selfInteraction = true;
284 if ( remainingTime < thresholdLow ) {
285 emit m_parent->zoomOut( Instant );
286 }
287 else if ( remainingTime > thresholdHigh ) {
288 emit m_parent->zoomIn( Instant );
289 }
290 m_selfInteraction = false;
291 }
292 }
293
centerOn(const GeoDataCoordinates & position)294 void AutoNavigation::Private::centerOn( const GeoDataCoordinates &position )
295 {
296 m_selfInteraction = true;
297 RoutingManager const * routingManager = m_model->routingManager();
298 RoutingModel const * routingModel = routingManager->routingModel();
299 if (!routingManager->guidanceModeEnabled() || routingModel->deviatedFromRoute()){
300 emit m_parent->centerOn( position, false );
301 } else {
302 GeoDataCoordinates positionOnRoute = routingModel->route().positionOnRoute();
303 emit m_parent->centerOn( positionOnRoute, false );
304 }
305 m_selfInteraction = false;
306 }
307
AutoNavigation(MarbleModel * model,const ViewportParams * viewport,QObject * parent)308 AutoNavigation::AutoNavigation( MarbleModel *model, const ViewportParams *viewport, QObject *parent ) :
309 QObject( parent ),
310 d( new AutoNavigation::Private( model, viewport, this ) )
311 {
312 connect( d->m_tracking, SIGNAL(gpsLocation(GeoDataCoordinates,qreal)),
313 this, SLOT(adjust(GeoDataCoordinates,qreal)) );
314 }
315
~AutoNavigation()316 AutoNavigation::~AutoNavigation()
317 {
318 delete d;
319 }
320
adjust(const GeoDataCoordinates & position,qreal speed)321 void AutoNavigation::adjust( const GeoDataCoordinates &position, qreal speed )
322 {
323 if ( d->m_lastWidgetInteraction.isActive() ) {
324 return;
325 }
326
327 switch( d->m_recenterMode ) {
328 case DontRecenter:
329 /* nothing to do */
330 break;
331 case AlwaysRecenter:
332 d->centerOn( position );
333 break;
334 case RecenterOnBorder:
335 d->moveOnBorderToCenter( position, speed );
336 break;
337 }
338
339 if ( d->m_adjustZoom ) {
340 switch( d->m_recenterMode ) {
341 case DontRecenter:
342 /* nothing to do */
343 break;
344 case AlwaysRecenter:
345 case RecenterOnBorder: // fallthrough
346 d->adjustZoom( position, speed );
347 break;
348 }
349 }
350 }
351
setAutoZoom(bool autoZoom)352 void AutoNavigation::setAutoZoom( bool autoZoom )
353 {
354 d->m_adjustZoom = autoZoom;
355 emit autoZoomToggled( autoZoom );
356 }
357
setRecenter(CenterMode recenterMode)358 void AutoNavigation::setRecenter( CenterMode recenterMode )
359 {
360 d->m_recenterMode = recenterMode;
361 emit recenterModeChanged( recenterMode );
362 }
363
inhibitAutoAdjustments()364 void AutoNavigation::inhibitAutoAdjustments()
365 {
366 if ( !d->m_selfInteraction ) {
367 d->m_lastWidgetInteraction.start();
368 }
369 }
370
recenterMode() const371 AutoNavigation::CenterMode AutoNavigation::recenterMode() const
372 {
373 return d->m_recenterMode;
374 }
375
autoZoom() const376 bool AutoNavigation::autoZoom() const
377 {
378 return d->m_adjustZoom;
379 }
380
381 } // namespace Marble
382
383 #include "moc_AutoNavigation.cpp"
384