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.
A 3d point representation.
GeoDataCoordinates moveByBearing(qreal bearing, qreal distance) const
Returns the coordinates of the resulting point after moving this point according to the distance and ...
Unit
enum used constructor to specify the units used
void setLongitude(qreal lon, GeoDataCoordinates::Unit unit=GeoDataCoordinates::Radian)
set the longitude in a GeoDataCoordinates object
A class that can paint a view of the earth.
Definition MarbleMap.h:84
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-2025 The KDE developers.
Generated on Fri Jan 24 2025 11:52:10 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.