Marble

MarbleAbstractPresenter.cpp
1// SPDX-License-Identifier: LGPL-2.1-or-later
2//
3// SPDX-FileCopyrightText: 2006-2007 Torsten Rahn <tackat@kde.org>
4// SPDX-FileCopyrightText: 2007 Inge Wallin <ingwa@kde.org>
5// SPDX-FileCopyrightText: 2010-2012 Bernhard Beschow <bbeschow@cs.tu-berlin.de>
6// SPDX-FileCopyrightText: 2012 Mohammed Nafees <nafees.technocool@gmail.com>
7// SPDX-FileCopyrightText: 2014 Adam Dabrowski <adamdbrw@gmail.com>
8//
9
10#include "GeoDataGeometry.h"
11#include "GeoDataLatLonAltBox.h"
12#include "MarbleMap.h"
13#include "MarbleModel.h"
14#include <GeoDataLookAt.h>
15#include <GeoDataPlacemark.h>
16#include <MarbleAbstractPresenter.h>
17#include <MarbleClock.h>
18#include <MarbleDebug.h>
19#include <MarbleLocale.h>
20#include <Planet.h>
21#include <QtMath>
22#include <Quaternion.h>
23#include <ViewportParams.h>
24
25namespace Marble
26{
27MarbleAbstractPresenter::MarbleAbstractPresenter(MarbleMap *map, QObject *parent)
28 : QObject(parent)
29 , m_map(map)
30 , m_physics(this)
31 , m_animationsEnabled(false)
32 , m_logzoom(0)
33 , m_zoomStep(MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen ? 60 : 40)
34 , m_viewAngle(110)
35{
36}
37
38MarbleAbstractPresenter::~MarbleAbstractPresenter() = default;
39
40qreal MarbleAbstractPresenter::zoom(qreal radius) const
41{
42 return (200.0 * log(radius));
43}
44
45qreal MarbleAbstractPresenter::radius(qreal zoom) const
46{
47 return pow(M_E, (zoom / 200.0));
48}
49
50void MarbleAbstractPresenter::rotateBy(const qreal deltaLon, const qreal deltaLat, FlyToMode mode)
51{
52 GeoDataLookAt target = lookAt();
53 GeoDataCoordinates coords(map()->viewport()->centerLongitude(), map()->viewport()->centerLatitude());
54 GeoDataCoordinates movedCoords = coords.moveByBearing(-map()->heading() * DEG2RAD, -deltaLat * DEG2RAD);
55 movedCoords = movedCoords.moveByBearing((-map()->heading() - 90) * DEG2RAD, -deltaLon * DEG2RAD * map()->viewport()->polarity());
56
57 target.setLongitude(movedCoords.longitude());
58 target.setLatitude(movedCoords.latitude());
59
60 flyTo(target, mode);
61}
62
63void MarbleAbstractPresenter::flyTo(const GeoDataLookAt &newLookAt, FlyToMode mode)
64{
65 if (!m_animationsEnabled || mode == Instant) {
66 const int radius = qRound(radiusFromDistance(newLookAt.range() * METER2KM));
67 qreal const zoomVal = zoom(radius);
68
69 // Prevent exceeding zoom range. Note: Bounding to range is not useful here
70 if (qRound(zoomVal) >= minimumZoom() && qRound(zoomVal) <= maximumZoom()) {
71 map()->setRadius(radius);
72 m_logzoom = qRound(zoom(radius));
73
74 GeoDataCoordinates::Unit deg = GeoDataCoordinates::Degree;
75 map()->centerOn(newLookAt.longitude(deg), newLookAt.latitude(deg));
76
77 Q_EMIT zoomChanged(m_logzoom);
78 Q_EMIT distanceChanged(distanceString());
79 }
80 } else {
81 m_physics.flyTo(newLookAt, mode);
82 }
83}
84
85QString MarbleAbstractPresenter::distanceString() const
86{
87 // distance() returns data in km, so translating to meters
88 qreal dist = distance() * KM2METER, convertedDistance;
89
90 MarbleLocale::MeasureUnit unit;
91 MarbleLocale *locale = MarbleGlobal::getInstance()->locale();
92 locale->meterToTargetUnit(dist, locale->measurementSystem(), convertedDistance, unit);
93 QString unitString = locale->unitAbbreviation(unit);
94
95 return QStringLiteral("%L1 %2").arg(convertedDistance, 8, 'f', 1, QLatin1Char(' ')).arg(unitString);
96}
97
98GeoDataLookAt MarbleAbstractPresenter::lookAt() const
99{
100 GeoDataLookAt result;
101
102 result.setLongitude(map()->viewport()->centerLongitude());
103 result.setLatitude(map()->viewport()->centerLatitude());
104 result.setAltitude(0.0);
105 result.setRange(distance() * KM2METER);
106
107 return result;
108}
109
110qreal MarbleAbstractPresenter::distance() const
111{
112 return distanceFromRadius(radius());
113}
114
115qreal MarbleAbstractPresenter::distanceFromRadius(qreal radius) const
116{
117 // Due to Marble's orthographic projection ("we have no focus")
118 // it's actually not possible to calculate a "real" distance.
119 // Additionally the viewing angle of the earth doesn't adjust to
120 // the window's size.
121 //
122 // So the only possible workaround is to come up with a distance
123 // definition which gives a reasonable approximation of
124 // reality. Therefore we assume that the average window width
125 // (about 800 pixels) equals the viewing angle of a human being.
126
127 return (model()->planet()->radius() * 0.4 / radius / tan(0.5 * m_viewAngle * DEG2RAD));
128}
129
130qreal MarbleAbstractPresenter::radiusFromDistance(qreal distance) const
131{
132 return model()->planet()->radius() / (distance * tan(0.5 * m_viewAngle * DEG2RAD) / 0.4);
133}
134
135int MarbleAbstractPresenter::polarity() const
136{
137 return map()->viewport()->polarity();
138}
139
140int MarbleAbstractPresenter::zoom() const
141{
142 return m_logzoom;
143}
144
145int MarbleAbstractPresenter::minimumZoom() const
146{
147 return map()->minimumZoom();
148}
149
150int MarbleAbstractPresenter::maximumZoom() const
151{
152 return map()->maximumZoom();
153}
154
155void MarbleAbstractPresenter::setZoom(int newZoom, FlyToMode mode)
156{
157 // It won't fly anyway. So we should do everything to keep the zoom value.
158 if (!m_animationsEnabled || mode == Instant) {
159 // Check for under and overflow.
160 if (newZoom < minimumZoom())
161 newZoom = minimumZoom();
162 else if (newZoom > maximumZoom())
163 newZoom = maximumZoom();
164
165 // Prevent infinite loops.
166 if (newZoom == m_logzoom)
167 return;
168
169 map()->setRadius(radius(newZoom));
170 m_logzoom = newZoom;
171
172 Q_EMIT zoomChanged(m_logzoom);
173 Q_EMIT distanceChanged(distanceString());
174 } else {
175 GeoDataLookAt target = lookAt();
176 target.setRange(KM2METER * distanceFromZoom(newZoom));
177 flyTo(target, mode);
178 }
179}
180
181void MarbleAbstractPresenter::zoomView(int zoom, FlyToMode mode)
182{
183 setZoom(zoom, mode);
184}
185
186void MarbleAbstractPresenter::zoomViewBy(int zoomStep, FlyToMode mode)
187{
188 setZoom(zoom() + zoomStep, mode);
189}
190
191void MarbleAbstractPresenter::zoomIn(FlyToMode mode)
192{
193 if (map()->tileZoomLevel() < 0) {
194 zoomViewBy(m_zoomStep, mode);
195 } else {
196 qreal radiusVal = map()->preferredRadiusCeil(map()->radius() / 0.95);
197 radiusVal = qBound(radius(minimumZoom()), radiusVal, radius(maximumZoom()));
198
199 GeoDataLookAt target = lookAt();
200 target.setRange(KM2METER * distanceFromRadius(radiusVal));
201
202 flyTo(target, mode);
203 }
204}
205
206void MarbleAbstractPresenter::zoomOut(FlyToMode mode)
207{
208 if (map()->tileZoomLevel() <= 0) {
209 zoomViewBy(-m_zoomStep, mode);
210 } else {
211 qreal radiusVal = map()->preferredRadiusFloor(map()->radius() * 0.95);
212 radiusVal = qBound(radius(minimumZoom()), radiusVal, radius(maximumZoom()));
213
214 GeoDataLookAt target = lookAt();
215 target.setRange(KM2METER * distanceFromRadius(radiusVal));
216
217 flyTo(target, mode);
218 }
219}
220
221void MarbleAbstractPresenter::zoomAtBy(const QPoint &pos, int zoomStep)
222{
223 qreal radiusVal;
224 if (map()->tileZoomLevel() <= 0) {
225 radiusVal = radius(zoom() + zoomStep);
226 } else {
227 radiusVal = zoomStep > 0 ? map()->preferredRadiusCeil(map()->radius() / 0.95) : map()->preferredRadiusFloor(map()->radius() * 0.95);
228 radiusVal = qBound(radius(minimumZoom()), radiusVal, radius(maximumZoom()));
229 }
230
231 zoomAt(pos, distanceFromRadius(radiusVal));
232}
233
234qreal MarbleAbstractPresenter::distanceFromZoom(qreal zoom) const
235{
236 return distanceFromRadius(radius(zoom));
237}
238
239qreal MarbleAbstractPresenter::zoomFromDistance(qreal distance) const
240{
241 return zoom(radiusFromDistance(distance));
242}
243
244void MarbleAbstractPresenter::goHome(FlyToMode mode)
245{
246 qreal homeLon = 0;
247 qreal homeLat = 0;
248 int homeZoom = 0;
249 model()->home(homeLon, homeLat, homeZoom);
250
251 GeoDataLookAt target;
252 target.setLongitude(homeLon, GeoDataCoordinates::Degree);
253 target.setLatitude(homeLat, GeoDataCoordinates::Degree);
254 target.setRange(1000 * distanceFromZoom(homeZoom));
255
256 flyTo(target, mode);
257}
258
259void MarbleAbstractPresenter::moveByStep(int stepsRight, int stepsDown, FlyToMode mode)
260{
261 int polarity = map()->viewport()->polarity();
262 qreal left = polarity * stepsRight * moveStep();
263 qreal down = stepsDown * moveStep();
264 rotateBy(left, down, mode);
265}
266
267qreal MarbleAbstractPresenter::moveStep() const
268{
269 int width = map()->width();
270 int height = map()->height();
271
272 if (radius() < qSqrt((qreal)(width * width + height * height)))
273 return 180.0 * 0.1;
274 else
275 return 180.0 * qAtan((qreal)width / (qreal)(2 * radius())) * 0.2;
276}
277
278int MarbleAbstractPresenter::radius() const
279{
280 return map()->radius();
281}
282
283void MarbleAbstractPresenter::setRadius(int radiusVal)
284{
285 Q_ASSERT(radiusVal >= 0);
286 bool adjustRadius = radiusVal != map()->radius();
287
288 qreal const zoomVal = zoom(radiusVal);
289
290 // Prevent exceeding zoom range
291 if (zoomVal < minimumZoom()) {
292 radiusVal = radius(minimumZoom());
293 adjustRadius = true;
294 } else if (zoomVal > maximumZoom()) {
295 radiusVal = radius(maximumZoom());
296 adjustRadius = true;
297 }
298
299 if (adjustRadius) {
300 map()->setRadius(radiusVal);
301 m_logzoom = qRound(zoomVal);
302
303 Q_EMIT zoomChanged(m_logzoom);
304 Q_EMIT distanceChanged(distanceString());
305 }
306}
307
308// Moved from MarbleWidgetInputHandlerPrivate - fits more here now
309void MarbleAbstractPresenter::zoomAt(const QPoint &pos, qreal newDistance)
310{
311 Q_ASSERT(newDistance > 0.0);
312
313 qreal destLat;
314 qreal destLon;
315 if (!map()->geoCoordinates(pos.x(), pos.y(), destLon, destLat, GeoDataCoordinates::Degree)) {
316 return;
317 }
318
319 ViewportParams *now = map()->viewport();
320 qreal x(0), y(0);
321 if (!now->screenCoordinates(destLon * DEG2RAD, destLat * DEG2RAD, x, y)) {
322 return;
323 }
324
325 ViewportParams soon;
326 soon.setProjection(now->projection());
327 soon.centerOn(now->centerLongitude(), now->centerLatitude());
328 soon.setSize(now->size());
329
330 qreal newRadius = radiusFromDistance(newDistance);
331 soon.setRadius(newRadius);
332
333 qreal mouseLon, mouseLat;
334 if (!soon.geoCoordinates(int(x), int(y), mouseLon, mouseLat, GeoDataCoordinates::Degree)) {
335 return;
336 }
337
338 const qreal lon = destLon - (mouseLon - map()->centerLongitude());
339 const qreal lat = destLat - (mouseLat - map()->centerLatitude());
340
341 GeoDataLookAt lookAt;
342 lookAt.setLongitude(lon, GeoDataCoordinates::Degree);
343 lookAt.setLatitude(lat, GeoDataCoordinates::Degree);
344 lookAt.setAltitude(0.0);
345 lookAt.setRange(newDistance * KM2METER);
346
347 map()->viewport()->setFocusPoint(GeoDataCoordinates(destLon, destLat, 0, GeoDataCoordinates::Degree));
348 flyTo(lookAt, Linear);
349}
350
351void MarbleAbstractPresenter::moveTo(const QPoint &pos, qreal factor)
352{
353 Q_ASSERT(factor > 0.0);
354
355 qreal destLat;
356 qreal destLon;
357 map()->geoCoordinates(pos.x(), pos.y(), destLon, destLat, GeoDataCoordinates::Radian);
358
359 GeoDataLookAt lookAt;
360 lookAt.setLongitude(destLon);
361 lookAt.setLatitude(destLat);
362 lookAt.setAltitude(0.0);
363 lookAt.setRange(distance() * factor * KM2METER);
364
365 flyTo(lookAt);
366}
367
368void MarbleAbstractPresenter::centerOn(const qreal lon, const qreal lat, bool animated)
369{
370 GeoDataCoordinates target(lon, lat, 0.0, GeoDataCoordinates::Degree);
371 centerOn(target, animated);
372}
373
374void MarbleAbstractPresenter::centerOn(const GeoDataCoordinates &position, bool animated)
375{
376 GeoDataLookAt target = lookAt();
377 target.setCoordinates(position);
378 flyTo(target, animated ? Automatic : Instant);
379}
380
381void MarbleAbstractPresenter::centerOn(const GeoDataLatLonBox &box, bool animated)
382{
383 if (box.isEmpty()) {
384 return;
385 }
386
387 int newRadius = radius();
388 ViewportParams *viewparams = map()->viewport();
389 // prevent divide by zero
390 if (box.height() && box.width()) {
391 // work out the needed zoom level
392 int const horizontalRadius = (0.25 * M_PI) * (viewparams->height() / box.height());
393 int const verticalRadius = (0.25 * M_PI) * (viewparams->width() / box.width());
394 newRadius = qMin<int>(horizontalRadius, verticalRadius);
395 newRadius = qMax<int>(radius(minimumZoom()), qMin<int>(newRadius, radius(maximumZoom())));
396 }
397
398 // move the map
399 GeoDataLookAt target;
400 target.setCoordinates(box.center());
401 target.setAltitude(box.center().altitude());
402 target.setRange(KM2METER * distanceFromRadius(newRadius));
403 flyTo(target, animated ? Automatic : Instant);
404}
405
406void MarbleAbstractPresenter::centerOn(const GeoDataPlacemark &placemark, bool animated)
407{
408 const GeoDataLookAt *lookAt(placemark.lookAt());
409 if (lookAt) {
410 flyTo(*lookAt, animated ? Automatic : Instant);
411 } else {
412 bool icon;
413 GeoDataCoordinates coords = placemark.coordinate(model()->clock()->dateTime(), &icon);
414 if (icon) {
415 centerOn(coords, animated);
416 } else {
417 centerOn(placemark.geometry()->latLonAltBox(), animated);
418 }
419 }
420}
421
422void MarbleAbstractPresenter::headingOn(qreal heading)
423{
424 map()->setHeading(heading);
425}
426
427void MarbleAbstractPresenter::setCenterLatitude(qreal lat, FlyToMode mode)
428{
429 centerOn(centerLongitude(), lat, mode);
430}
431
432void MarbleAbstractPresenter::setCenterLongitude(qreal lon, FlyToMode mode)
433{
434 centerOn(lon, centerLatitude(), mode);
435}
436
437qreal MarbleAbstractPresenter::centerLatitude() const
438{
439 return map()->centerLatitude();
440}
441
442qreal MarbleAbstractPresenter::centerLongitude() const
443{
444 return map()->centerLongitude();
445}
446
447ViewContext MarbleAbstractPresenter::viewContext() const
448{
449 return map()->viewContext();
450}
451
452void MarbleAbstractPresenter::setViewContext(ViewContext viewContext)
453{
454 map()->setViewContext(viewContext);
455}
456
457bool MarbleAbstractPresenter::animationsEnabled() const
458{
459 return m_animationsEnabled;
460}
461
462void MarbleAbstractPresenter::setAnimationsEnabled(bool enabled)
463{
464 m_animationsEnabled = enabled;
465}
466
467int MarbleAbstractPresenter::logzoom() const
468{
469 return m_logzoom;
470}
471
472void MarbleAbstractPresenter::setLogzoom(int value)
473{
474 m_logzoom = value;
475}
476
477int MarbleAbstractPresenter::zoomStep() const
478{
479 return m_zoomStep;
480}
481
482qreal MarbleAbstractPresenter::viewAngle() const
483{
484 return m_viewAngle;
485}
486
487MarbleMap *MarbleAbstractPresenter::map()
488{
489 return m_map;
490}
491
492const MarbleMap *MarbleAbstractPresenter::map() const
493{
494 return m_map;
495}
496
497MarbleModel *MarbleAbstractPresenter::model()
498{
499 return m_map->model();
500}
501
502const MarbleModel *MarbleAbstractPresenter::model() const
503{
504 return m_map->model();
505}
506
507ViewportParams *MarbleAbstractPresenter::viewport()
508{
509 return map()->viewport();
510}
511
512const ViewportParams *MarbleAbstractPresenter::viewport() const
513{
514 return map()->viewport();
515}
516
517void MarbleAbstractPresenter::setDistance(qreal newDistance)
518{
519 qreal minDistance = 0.001;
520
521 if (newDistance <= minDistance) {
522 mDebug() << "Invalid distance: 0 m";
523 newDistance = minDistance;
524 }
525
526 int newRadius = radiusFromDistance(newDistance);
527 setRadius(newRadius);
528}
529
530void MarbleAbstractPresenter::setSelection(const QRect &region)
531{
532 QPoint tl = region.topLeft();
533 QPoint br = region.bottomRight();
534 mDebug() << "Selection region: (" << tl.x() << ", " << tl.y() << ") (" << br.x() << ", " << br.y() << ")" << Qt::endl;
535
536 const GeoDataLatLonAltBox box = viewport()->latLonAltBox(region);
537
538 Q_EMIT regionSelected(box);
539}
540
541}
542
543#include "moc_MarbleAbstractPresenter.cpp"
This file contains the headers for MarbleMap.
This file contains the headers for MarbleModel.
This file contains the headers for ViewportParams.
QAction * zoom(const QObject *recvr, const char *slot, QObject *parent)
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)
int x() const const
int y() const const
QPoint bottomRight() const const
QPoint topLeft() const const
QString arg(Args &&... args) const const
QTextStream & endl(QTextStream &stream)
QTextStream & left(QTextStream &stream)
QFuture< void > map(Iterator begin, Iterator end, MapFunctor &&function)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Nov 8 2024 12:02:44 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.