Marble

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

KDE's Doxygen guidelines are available online.