Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qdeclarativegeomap.cpp
Go to the documentation of this file.
1// Copyright (C) 2015 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
11#include "qgeomap_p.h"
13#include <QtPositioning/QGeoCircle>
14#include <QtPositioning/QGeoRectangle>
15#include <QtPositioning/QGeoPath>
16#include <QtPositioning/QGeoPolygon>
17#include <QtQuick/QQuickWindow>
18#include <QtQuick/QSGRectangleNode>
19#include <QtQml/qqmlinfo.h>
20#include <QtQuick/private/qquickitem_p.h>
21#include <cmath>
22
23#ifndef M_PI
24#define M_PI 3.141592653589793238463
25#endif
26
27
28QT_BEGIN_NAMESPACE
29
30static qreal sanitizeBearing(qreal bearing)
31{
32 bearing = std::fmod(bearing, qreal(360.0));
33 if (bearing < 0.0)
34 bearing += 360.0;
35
36 return bearing;
37}
38
39/*!
40 \qmltype Map
41 \nativetype QDeclarativeGeoMap
42 \inqmlmodule QtLocation
43 \ingroup qml-QtLocation5-maps
44 \since QtLocation 5.0
45
46 \brief The Map type displays a map.
47
48 The Map type is used to display a map or image of the Earth, with
49 the capability to also display interactive objects tied to the map's
50 surface.
51
52 There are a variety of different ways to visualize the Earth's surface
53 in a 2-dimensional manner, but all of them involve some kind of projection:
54 a mathematical relationship between the 3D coordinates (latitude, longitude
55 and altitude) and 2D coordinates (X and Y in pixels) on the screen.
56
57 Different sources of map data can use different projections, and from the
58 point of view of the Map type, we treat these as one replaceable unit:
59 the Map plugin. A Map plugin consists of a data source, as well as all other
60 details needed to display its data on-screen.
61
62 The current Map plugin in use is contained in the \l plugin property of
63 the Map item. In order to display any image in a Map item, you will need
64 to set this property. See the \l Plugin type for a description of how
65 to retrieve an appropriate plugin for use.
66
67 The geographic region displayed in the Map item is referred to as its
68 viewport, and this is defined by the properties \l center, and
69 \l zoomLevel. The \l center property contains a
70 \l [QtPositioning]{geoCoordinate} specifying the center of the viewport,
71 while \l zoomLevel controls the scale of the map. See each of these
72 properties for further details about their values.
73
74 When the map is displayed, each possible geographic coordinate that is
75 visible will map to some pixel X and Y coordinate on the screen. To perform
76 conversions between these two, Map provides the \l toCoordinate and
77 \l fromCoordinate functions, which are of general utility.
78
79 \section2 Map Objects
80
81 Map related objects can be declared within the body of a Map object in Qt Quick and will
82 automatically appear on the Map. To add an object programmatically, first be
83 sure it is created with the Map as its parent (for example in an argument to
84 Component::createObject).
85 Then call the \l addMapItem method on the Map, if the type of this object is one of
86 \l MapCircle, \l MapRectangle, \l MapPolyline, \l MapPolygon, \l MapRoute or \l MapQuickItem.
87 A corresponding \l removeMapItem method also exists to do the opposite and
88 remove any of the above types of map objects from the Map.
89
90 Moving Map objects around, resizing them or changing their shape normally
91 does not involve any special interaction with Map itself -- changing these
92 properties in a map object will automatically update the display.
93
94 \section2 Performance
95
96 Maps are rendered using OpenGL (ES) and the Qt Scene Graph stack, and as
97 a result perform quite well where GL accelerated hardware is available.
98
99 For "online" Maps, network bandwidth and latency can be major
100 contributors to the user's perception of performance. Extensive caching is
101 performed to mitigate this, but such mitigation is not always perfect.
102
103 In general, large and complex Map items such as polygons and polylines with
104 large numbers of vertices can have an adverse effect on UI performance.
105
106 \section2 Example Usage
107
108 The following snippet shows a simple Map and the necessary Plugin type
109 to use it. The map is centered over Oslo, Norway, with zoom level 14.
110
111 \quotefromfile minimal_map/main.qml
112 \skipto import
113 \printuntil Window {
114 \dots
115 \skipto Plugin
116 \printuntil
117
118 \image minimal_map.png
119*/
120
121/*!
122 \qmlsignal QtLocation::Map::copyrightLinkActivated(string link)
123
124 This signal is emitted when the user clicks on a \a link in the copyright notice. The
125 application should open the link in a browser or display its contents to the user.
126*/
127
128QDeclarativeGeoMap::QDeclarativeGeoMap(QQuickItem *parent)
129 : QQuickItem(parent)
130{
131 setFlags(QQuickItem::ItemHasContents | QQuickItem::ItemClipsChildrenToShape);
132
133 m_activeMapType = QGeoMapType(QGeoMapType::NoMap,
134 tr("No Map"),
135 tr("No Map"),
136 false, false,
137 0,
138 QByteArrayLiteral(""),
139 QGeoCameraCapabilities());
140 m_cameraData.setCenter(QGeoCoordinate(51.5073,-0.1277)); //London city center
141 m_cameraData.setZoomLevel(8.0);
142
143 m_cameraCapabilities.setTileSize(256);
144 m_cameraCapabilities.setSupportsBearing(true);
145 m_cameraCapabilities.setSupportsTilting(true);
146 m_cameraCapabilities.setMinimumZoomLevel(0);
147 m_cameraCapabilities.setMaximumZoomLevel(30);
148 m_cameraCapabilities.setMinimumTilt(0);
149 m_cameraCapabilities.setMaximumTilt(89.5);
150 m_cameraCapabilities.setMinimumFieldOfView(1);
151 m_cameraCapabilities.setMaximumFieldOfView(179);
152
153 m_minimumZoomLevel = m_cameraCapabilities.minimumZoomLevel();
154 m_maximumZoomLevel = m_cameraCapabilities.maximumZoomLevel();
155 m_minimumTilt = m_cameraCapabilities.minimumTilt();
156 m_maximumTilt = m_cameraCapabilities.maximumTilt();
157 m_minimumFieldOfView = m_cameraCapabilities.minimumFieldOfView();
158 m_maximumFieldOfView = m_cameraCapabilities.maximumFieldOfView();
159}
160
161QDeclarativeGeoMap::~QDeclarativeGeoMap()
162{
163 // Removing map items from m_map
164 if (m_map) {
165 m_map->clearMapItems();
166 }
167
168 // Remove the items from the map, making them deletable.
169 // Go in the same order as in removeMapChild: views, groups, then items
170 if (!m_mapViews.isEmpty()) {
171 const auto mapViews = m_mapViews;
172 for (QDeclarativeGeoMapItemView *v : mapViews) { // so that removeMapItemView_real can safely modify m_mapViews;
173 if (!v)
174 continue;
175
176 QQuickItem *parent = v->parentItem();
177 QDeclarativeGeoMapItemGroup *group = qobject_cast<QDeclarativeGeoMapItemGroup *>(parent);
178 if (group)
179 continue; // Ignore non-top-level MIVs. They will be recursively processed.
180 // Identify them as being parented by a MapItemGroup.
181
182 removeMapItemView_real(v);
183 }
184 }
185
186 if (!m_mapItemGroups.isEmpty()) {
187 const auto mapGroups = m_mapItemGroups;
188 for (QDeclarativeGeoMapItemGroup *g : mapGroups) {
189 if (!g)
190 continue;
191
192 QQuickItem *parent =g->parentItem();
193 QDeclarativeGeoMapItemGroup *group = qobject_cast<QDeclarativeGeoMapItemGroup *>(parent);
194 if (group)
195 continue; // Ignore non-top-level Groups. They will be recursively processed.
196 // Identify them as being parented by a MapItemGroup.
197
198 removeMapItemGroup_real(g);
199 }
200 }
201
202 // remove any remaining map items associations
203 const auto mapItems = m_mapItems;
204 for (auto mi: mapItems)
205 removeMapItem_real(mi.data());
206
207 if (m_copyrights.data())
208 delete m_copyrights.data();
209 m_copyrights.clear();
210
211 delete m_map; // map objects get reset here
212}
213
214void QDeclarativeGeoMap::onSupportedMapTypesChanged()
215{
216 m_supportedMapTypes = m_mappingManager->supportedMapTypes();
217 if (m_supportedMapTypes.isEmpty()) {
218 m_map->setActiveMapType(QGeoMapType()); // no supported map types: setting an invalid one
219 } else if (!m_supportedMapTypes.contains(m_map->activeMapType())) {
220 QGeoMapType type = m_supportedMapTypes.at(0);
221 m_activeMapType = type;
222 m_map->setActiveMapType(type);
223 }
224
225 emit supportedMapTypesChanged();
226}
227
228void QDeclarativeGeoMap::setError(QGeoServiceProvider::Error error, const QString &errorString)
229{
230 if (m_error == error && m_errorString == errorString)
231 return;
232 m_error = error;
233 m_errorString = errorString;
234 emit errorChanged();
235}
236
237/*!
238 \internal
239 Called when the mapping manager is initialized AND the declarative element has a valid size > 0
240*/
241void QDeclarativeGeoMap::initialize()
242{
243 // try to keep change signals in the end
244 bool visibleAreaHasChanged = false;
245
246 QGeoCoordinate center = m_cameraData.center();
247
248 setMinimumZoomLevel(m_map->minimumZoom(), false);
249
250 double bearing = m_cameraData.bearing();
251 double tilt = m_cameraData.tilt();
252 double fov = m_cameraData.fieldOfView(); // Must be 45.0
253 QGeoCameraData cameraData = m_cameraData;
254
255 if (!m_cameraCapabilities.supportsBearing() && bearing != 0.0)
256 cameraData.setBearing(0);
257
258 if (!m_cameraCapabilities.supportsTilting() && tilt != 0.0)
259 cameraData.setTilt(0);
260
261 m_map->setVisibleArea(m_visibleArea);
262 if (m_map->visibleArea() != m_visibleArea)
263 visibleAreaHasChanged = true;
264
265 cameraData.setFieldOfView(qBound(m_cameraCapabilities.minimumFieldOfView(),
266 fov,
267 m_cameraCapabilities.maximumFieldOfView()));
268
269 // set latitude boundary check
270 m_maximumViewportLatitude = m_map->maximumCenterLatitudeAtZoom(cameraData);
271 m_minimumViewportLatitude = m_map->minimumCenterLatitudeAtZoom(cameraData);
272
273 center.setLatitude(qBound(m_minimumViewportLatitude, center.latitude(), m_maximumViewportLatitude));
274 cameraData.setCenter(center);
275
276 connect(m_map.data(), &QGeoMap::cameraDataChanged,
277 this, &QDeclarativeGeoMap::onCameraDataChanged);
278 m_map->setCameraData(cameraData); // This normally triggers property changed signals.
279 // BUT not in this case, since m_cameraData is already == cameraData.
280 // So, emit visibleRegionChanged() separately, as
281 // the effective visible region becomes available only now.
282
283 m_initialized = true;
284
285 if (visibleAreaHasChanged)
286 emit visibleAreaChanged();
287 connect(m_map.data(), &QGeoMap::visibleAreaChanged, this, &QDeclarativeGeoMap::visibleAreaChanged);
288
289 emit mapReadyChanged(true);
290 emit visibleRegionChanged();
291
292 if (m_copyrights) // To not update during initialize()
293 update();
294}
295
296/*!
297 \internal
298*/
299void QDeclarativeGeoMap::pluginReady()
300{
301 QGeoServiceProvider *provider = m_plugin->sharedGeoServiceProvider();
302 m_mappingManager = provider->mappingManager();
303
304 if (provider->mappingError() != QGeoServiceProvider::NoError) {
305 setError(provider->mappingError(), provider->mappingErrorString());
306 return;
307 }
308
309 if (!m_mappingManager) {
310 //TODO Should really be EngineNotSetError (see QML GeoCodeModel)
311 setError(QGeoServiceProvider::NotSupportedError, tr("Plugin does not support mapping."));
312 return;
313 }
314
315 if (!m_mappingManager->isInitialized()) {
316 connect(m_mappingManager, &QGeoMappingManager::initialized,
317 this, &QDeclarativeGeoMap::mappingManagerInitialized);
318 } else {
319 mappingManagerInitialized();
320 }
321
322 // make sure this is only called once
323 disconnect(m_plugin, &QDeclarativeGeoServiceProvider::attached,
324 this, &QDeclarativeGeoMap::pluginReady);
325}
326
327/*!
328 \internal
329*/
330void QDeclarativeGeoMap::componentComplete()
331{
332 m_componentCompleted = true;
333 populateMap();
334 QQuickItem::componentComplete();
335}
336
337/*!
338 \internal
339
340 This may happen before mappingManagerInitialized()
341*/
342void QDeclarativeGeoMap::populateMap()
343{
344 QSet<QObject *> kids(children().cbegin(), children().cend());
345 const QList<QQuickItem *> quickKids = childItems();
346 for (QQuickItem *ite: quickKids)
347 kids.insert(ite);
348
349 for (QObject *k : std::as_const(kids)) {
350 addMapChild(k);
351 }
352}
353
354/*!
355 \internal
356*/
357void QDeclarativeGeoMap::setupMapView(QDeclarativeGeoMapItemView *view)
358{
359 view->setMap(this);
360}
361
362/*!
363 * \internal
364 */
365QSGNode *QDeclarativeGeoMap::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
366{
367 if (!m_map) {
368 delete oldNode;
369 return nullptr;
370 }
371
372 QSGRectangleNode *root = static_cast<QSGRectangleNode *>(oldNode);
373 if (!root)
374 root = window()->createRectangleNode();
375
376 root->setRect(boundingRect());
377 root->setColor(m_color);
378
379 QSGNode *content = root->childCount() ? root->firstChild() : 0;
380 content = m_map->updateSceneGraph(content, window());
381 if (content && root->childCount() == 0)
382 root->appendChildNode(content);
383
384 return root;
385}
386
387/*!
388 \qmlproperty Plugin QtLocation::Map::plugin
389
390 This property holds the plugin which provides the mapping functionality.
391
392 This is a write-once property. Once the map has a plugin associated with
393 it, any attempted modifications of the plugin will be ignored.
394*/
395
396void QDeclarativeGeoMap::setPlugin(QDeclarativeGeoServiceProvider *plugin)
397{
398 if (m_plugin) {
399 qmlWarning(this) << QStringLiteral("Plugin is a write-once property, and cannot be set again.");
400 return;
401 }
402 m_plugin = plugin;
403 emit pluginChanged(m_plugin);
404
405 if (m_plugin->isAttached()) {
406 pluginReady();
407 } else {
408 connect(m_plugin, &QDeclarativeGeoServiceProvider::attached,
409 this, &QDeclarativeGeoMap::pluginReady);
410 }
411}
412
413/*!
414 \internal
415*/
416void QDeclarativeGeoMap::onCameraCapabilitiesChanged(const QGeoCameraCapabilities &oldCameraCapabilities)
417{
418 if (m_map->cameraCapabilities() == oldCameraCapabilities)
419 return;
420
421 m_cameraCapabilities = m_map->cameraCapabilities();
422
423 setMaximumZoomLevel(m_cameraCapabilities.maximumZoomLevel(), false);
424 setMinimumZoomLevel(m_cameraCapabilities.minimumZoomLevel(), false);
425 setMinimumTilt(m_cameraCapabilities.minimumTilt(), false);
426 setMaximumTilt(m_cameraCapabilities.maximumTilt(), false);
427 setMinimumFieldOfView(m_cameraCapabilities.minimumFieldOfView(), false);
428 setMaximumFieldOfView(m_cameraCapabilities.maximumFieldOfView(), false);
429}
430
431/*!
432 \internal
433 this function will only be ever called once
434*/
435void QDeclarativeGeoMap::mappingManagerInitialized()
436{
437 m_map = m_mappingManager->createMap(this);
438
439 if (!m_map)
440 return;
441
442 // Any map items that were added before the plugin was ready
443 // need to have setMap called again
444 for (const QPointer<QDeclarativeGeoMapItemBase> &item : std::as_const(m_mapItems)) {
445 if (item) {
446 item->setMap(this, m_map);
447 m_map->addMapItem(item.data()); // m_map filters out what is not supported.
448 }
449 }
450
451 /* COPY NOTICE SETUP */
452 m_copyrights = new QDeclarativeGeoMapCopyrightNotice(this);
453 m_copyrights->setCopyrightsZ(m_maxChildZ + 1);
454 m_copyrights->setCopyrightsVisible(m_copyrightsVisible);
455 m_copyrights->setMapSource(this);
456
457 m_supportedMapTypes = m_mappingManager->supportedMapTypes();
458
459 if (m_activeMapType != QGeoMapType() && m_plugin->name().toLatin1() == m_activeMapType.pluginName()) {
460 m_map->setActiveMapType(m_activeMapType);
461 } else {
462 if (!m_supportedMapTypes.isEmpty()) {
463 m_activeMapType = m_supportedMapTypes.at(0);
464 m_map->setActiveMapType(m_activeMapType);
465 } else {
466 m_activeMapType = QGeoMapType(QGeoMapType::NoMap,
467 tr("No Map"),
468 tr("No Map"),
469 false,
470 false,
471 0,
472 QByteArrayLiteral(""),
473 QGeoCameraCapabilities());
474 }
475 }
476
477 // Update camera capabilities
478 onCameraCapabilitiesChanged(m_cameraCapabilities);
479
480 // Map tiles are built in this call. m_map->minimumZoom() becomes operational
481 // after this has been called at least once, after creation.
482 // However, getting into the following block may fire a copyrightsChanged that would get lost,
483 // as the connections are set up after.
484 QString copyrightString;
485 QImage copyrightImage;
486 if (!m_initialized && width() > 0 && height() > 0) {
487 QMetaObject::Connection copyrightStringCatcherConnection =
488 connect(m_map.data(), &QGeoMap::copyrightsChanged, this,
489 [&copyrightString](const QString &copy){ copyrightString = copy; });
490 QMetaObject::Connection copyrightImageCatcherConnection =
491 connect(m_map.data(), &QGeoMap::copyrightsImageChanged, this,
492 [&copyrightImage](const QImage &copy){ copyrightImage = copy; });
493 m_map->setViewportSize(QSize(width(), height()));
494 initialize(); // This emits the caught signals above
495 QObject::disconnect(copyrightStringCatcherConnection);
496 QObject::disconnect(copyrightImageCatcherConnection);
497 }
498
499
500 /* COPYRIGHT SIGNALS REWIRING */
501 connect(m_map.data(), &QGeoMap::copyrightsImageChanged,
502 this, &QDeclarativeGeoMap::copyrightsImageChanged);
503 connect(m_map.data(), &QGeoMap::copyrightsChanged,
504 this, &QDeclarativeGeoMap::copyrightsChanged);
505 if (!copyrightString.isEmpty())
506 emit m_map->copyrightsChanged(copyrightString);
507 else if (!copyrightImage.isNull())
508 emit m_map->copyrightsImageChanged(copyrightImage);
509
510 m_window = window();
511 if (m_window) {
512 connect(m_window, &QQuickWindow::beforeSynchronizing,
513 this, &QDeclarativeGeoMap::updateItemToWindowTransform, Qt::DirectConnection);
514 }
515 connect(m_map.data(), &QGeoMap::sgNodeChanged, this, &QDeclarativeGeoMap::onSGNodeChanged);
516 connect(m_map.data(), &QGeoMap::cameraCapabilitiesChanged,
517 this, &QDeclarativeGeoMap::onCameraCapabilitiesChanged);
518
519 // This prefetches a buffer around the map
520 m_map->prefetchData();
521
522 connect(m_mappingManager, &QGeoMappingManager::supportedMapTypesChanged,
523 this, &QDeclarativeGeoMap::onSupportedMapTypesChanged);
524 emit minimumZoomLevelChanged(minimumZoomLevel());
525 emit maximumZoomLevelChanged(maximumZoomLevel());
526 emit supportedMapTypesChanged();
527 emit activeMapTypeChanged();
528
529 // Any map item groups that were added before the plugin was ready
530 // DO NOT need to have setMap called again on their children map items
531 // because they have been added to m_mapItems, which is processed right above.
532
533 if (m_initialized)
534 update();
535}
536
537/*!
538 \internal
539*/
540QDeclarativeGeoServiceProvider *QDeclarativeGeoMap::plugin() const
541{
542 return m_plugin;
543}
544
545/*!
546 \internal
547 Sets the gesture areas minimum zoom level. If the camera capabilities
548 has been set this method honors the boundaries set by it.
549 The minimum zoom level will also have a lower bound dependent on the size
550 of the canvas, effectively preventing to display out of bounds areas.
551*/
552void QDeclarativeGeoMap::setMinimumZoomLevel(qreal minimumZoomLevel, bool userSet)
553{
554 if (minimumZoomLevel >= 0) {
555 qreal oldMinimumZoomLevel = this->minimumZoomLevel();
556
557 if (userSet)
558 m_userMinimumZoomLevel = minimumZoomLevel;
559 else
560 m_minimumZoomLevel = minimumZoomLevel;
561
562 if (zoomLevel() < minimumZoomLevel)
563 setZoomLevel(minimumZoomLevel);
564
565 if (oldMinimumZoomLevel != this->minimumZoomLevel())
566 emit minimumZoomLevelChanged(this->minimumZoomLevel());
567 }
568}
569
570/*!
571 \qmlproperty real QtLocation::Map::minimumZoomLevel
572
573 This property holds the minimum valid zoom level for the map.
574
575 The minimum zoom level defined by the \l plugin used is a lower bound for
576 this property. However, the returned value is also canvas-size-dependent, and
577 can be higher than the user-specified value, or than the minimum zoom level
578 defined by the plugin used, to prevent the map from being smaller than the
579 viewport in either dimension.
580
581 If the \l plugin property is not set or the plugin does not support mapping, this property is \c 0.
582*/
583
584qreal QDeclarativeGeoMap::minimumZoomLevel() const
585{
586 return qMax(qMin(m_maximumZoomLevel, m_userMinimumZoomLevel), m_minimumZoomLevel);
587}
588
589/*!
590 \internal
591 Sets the gesture areas maximum zoom level. If the camera capabilities
592 has been set this method honors the boundaries set by it.
593*/
594void QDeclarativeGeoMap::setMaximumZoomLevel(qreal maximumZoomLevel, bool userSet)
595{
596 if (maximumZoomLevel >= 0) {
597 qreal oldMaximumZoomLevel = this->maximumZoomLevel();
598
599 if (userSet)
600 m_userMaximumZoomLevel = maximumZoomLevel;
601 else
602 m_maximumZoomLevel = maximumZoomLevel;
603
604 if (zoomLevel() > maximumZoomLevel)
605 setZoomLevel(maximumZoomLevel);
606
607 if (oldMaximumZoomLevel != this->maximumZoomLevel())
608 emit maximumZoomLevelChanged(this->maximumZoomLevel());
609 }
610}
611
612/*!
613 \qmlproperty real QtLocation::Map::maximumZoomLevel
614
615 This property holds the maximum valid zoom level for the map.
616
617 The maximum zoom level is defined by the \l plugin used.
618 If the \l plugin property is not set or the plugin does not support mapping, this property is \c 30.
619*/
620
621qreal QDeclarativeGeoMap::maximumZoomLevel() const
622{
623 return qMin(qMax(m_minimumZoomLevel, m_userMaximumZoomLevel), m_maximumZoomLevel);
624}
625
626/*!
627 \qmlproperty real QtLocation::Map::zoomLevel
628
629 This property holds the zoom level for the map.
630
631 Larger values for the zoom level provide more detail. Zoom levels
632 are always non-negative. The default value is 8.0. Depending on the plugin in use,
633 values outside the [minimumZoomLevel, maximumZoomLevel] range, which represent the range for which
634 tiles are available, may be accepted, or clamped.
635*/
636void QDeclarativeGeoMap::setZoomLevel(qreal zoomLevel)
637{
638 return setZoomLevel(zoomLevel, m_cameraCapabilities.overzoomEnabled());
639}
640
641/*!
642 \internal
643
644 Sets the zoom level.
645 Larger values for the zoom level provide more detail. Zoom levels
646 are always non-negative. The default value is 8.0. Values outside the
647 [minimumZoomLevel, maximumZoomLevel] range, which represent the range for which
648 tiles are available, can be accepted or clamped by setting the overzoom argument
649 to true or false respectively.
650*/
651void QDeclarativeGeoMap::setZoomLevel(qreal zoomLevel, bool overzoom)
652{
653 if (zoomLevel < 0)
654 return;
655
656 if (m_initialized) {
657 QGeoCameraData cameraData = m_map->cameraData();
658 if (cameraData.zoomLevel() == zoomLevel)
659 return;
660
661 cameraData.setZoomLevel(qBound<qreal>(overzoom ? m_map->minimumZoom() : m_cameraCapabilities.minimumZoomLevel(),
662 zoomLevel,
663 overzoom ? 30 : maximumZoomLevel()));
664 m_maximumViewportLatitude = m_map->maximumCenterLatitudeAtZoom(cameraData);
665 m_minimumViewportLatitude = m_map->minimumCenterLatitudeAtZoom(cameraData);
666 QGeoCoordinate coord = cameraData.center();
667 coord.setLatitude(qBound(m_minimumViewportLatitude, coord.latitude(), m_maximumViewportLatitude));
668 cameraData.setCenter(coord);
669 m_map->setCameraData(cameraData);
670 } else {
671 const bool zlHasChanged = zoomLevel != m_cameraData.zoomLevel();
672 m_cameraData.setZoomLevel(zoomLevel);
673 if (zlHasChanged) {
674 emit zoomLevelChanged(zoomLevel);
675 // do not emit visibleRegionChanged() here, because, if the map isn't initialized,
676 // the getter won't return anything updated
677 }
678 }
679}
680
681bool QDeclarativeGeoMap::addMapChild(QObject *child)
682{
683 // dispatch items appropriately
684 QDeclarativeGeoMapItemView *mapView = qobject_cast<QDeclarativeGeoMapItemView *>(child);
685 if (mapView)
686 return addMapItemView_real(mapView);
687
688 QDeclarativeGeoMapItemGroup *itemGroup = qobject_cast<QDeclarativeGeoMapItemGroup *>(child);
689 if (itemGroup) // addMapItemView calls addMapItemGroup
690 return addMapItemGroup_real(itemGroup);
691
692 QDeclarativeGeoMapItemBase *mapItem = qobject_cast<QDeclarativeGeoMapItemBase *>(child);
693 if (mapItem)
694 return addMapItem_real(mapItem);
695
696 return false;
697}
698
699bool QDeclarativeGeoMap::removeMapChild(QObject *child)
700{
701 // dispatch items appropriately
702 QDeclarativeGeoMapItemView *mapView = qobject_cast<QDeclarativeGeoMapItemView *>(child);
703 if (mapView)
704 return removeMapItemView_real(mapView);
705
706 QDeclarativeGeoMapItemGroup *itemGroup = qobject_cast<QDeclarativeGeoMapItemGroup *>(child);
707 if (itemGroup) // removeMapItemView calls removeMapItemGroup for itself.
708 return removeMapItemGroup_real(itemGroup);
709
710 QDeclarativeGeoMapItemBase *mapItem = qobject_cast<QDeclarativeGeoMapItemBase *>(child);
711 if (mapItem)
712 return removeMapItem_real(mapItem);
713
714 return false;
715}
716
717bool QDeclarativeGeoMap::isGroupNested(QDeclarativeGeoMapItemGroup *group) const
718{
719 QObject *parent = group->parent();
720 // Nested groups have parent set in parent's componentComplete()
721 // Those instantiated by MapItemView's delegateModel, however, do not,
722 // but have setParentItem set.
723 return qobject_cast<QDeclarativeGeoMapItemGroup *>(parent)
724 || qobject_cast<QDeclarativeGeoMapItemGroup *>(group->parentItem());
725}
726
727qreal QDeclarativeGeoMap::zoomLevel() const
728{
729 if (m_initialized)
730 return m_map->cameraData().zoomLevel();
731 return m_cameraData.zoomLevel();
732}
733
734/*!
735 \qmlproperty real QtLocation::Map::bearing
736
737 This property holds the bearing for the map.
738 The default value is 0.
739 If the Plugin used for the Map supports bearing, the valid range for this value is between 0 and 360.
740 If the Plugin used for the Map does not support bearing, changing this property will have no effect.
741
742 \since QtLocation 5.9
743*/
744void QDeclarativeGeoMap::setBearing(qreal bearing)
745{
746 bearing = sanitizeBearing(bearing);
747 if (m_initialized) {
748 QGeoCameraData cameraData = m_map->cameraData();
749 cameraData.setBearing(bearing);
750 m_map->setCameraData(cameraData);
751 } else {
752 const bool bearingHasChanged = bearing != m_cameraData.bearing();
753 m_cameraData.setBearing(bearing);
754 if (bearingHasChanged) {
755 emit bearingChanged(bearing);
756 // do not emit visibleRegionChanged() here, because, if the map isn't initialized,
757 // the getter won't return anything updated
758 }
759 }
760}
761
762/*!
763 \qmlmethod void QtLocation::Map::setBearing(real bearing, coordinate coordinate)
764
765 Sets the bearing for the map to \a bearing, rotating it around \a coordinate.
766 If the Plugin used for the Map supports bearing, the valid range for \a bearing is between 0 and 360.
767 If the Plugin used for the Map does not support bearing, or if the map is tilted and \a coordinate happens
768 to be behind the camera, or if the map is not ready (see \l mapReady), calling this method will have no effect.
769
770 The release of this API with Qt 5.10 is a Technology Preview.
771
772 \since 5.10
773*/
774void QDeclarativeGeoMap::setBearing(qreal bearing, const QGeoCoordinate &coordinate)
775{
776 if (!m_initialized)
777 return;
778
779 const QGeoCoordinate currentCenter = center();
780 const qreal currentBearing = QDeclarativeGeoMap::bearing();
781 bearing = sanitizeBearing(bearing);
782
783 if (!coordinate.isValid()
784 || !qIsFinite(bearing)
785 || (coordinate == currentCenter && bearing == currentBearing))
786 return;
787
788 if (m_map->capabilities() & QGeoMap::SupportsSetBearing)
789 m_map->setBearing(bearing, coordinate);
790}
791
792qreal QDeclarativeGeoMap::bearing() const
793{
794 if (m_initialized)
795 return m_map->cameraData().bearing();
796 return m_cameraData.bearing();
797}
798
799/*!
800 \qmlproperty real QtLocation::Map::tilt
801
802 This property holds the tilt for the map, in degrees.
803 The default value is 0.
804 The valid range for this value is [ minimumTilt, maximumTilt ].
805 If the Plugin used for the Map does not support tilting, changing this property will have no effect.
806
807 \sa minimumTilt, maximumTilt
808
809 \since QtLocation 5.9
810*/
811void QDeclarativeGeoMap::setTilt(qreal tilt)
812{
813 tilt = qBound(minimumTilt(), tilt, maximumTilt());
814
815 if (m_initialized) {
816 QGeoCameraData cameraData = m_map->cameraData();
817 cameraData.setTilt(tilt);
818 m_map->setCameraData(cameraData);
819 } else {
820 const bool tiltHasChanged = tilt != m_cameraData.tilt();
821 m_cameraData.setTilt(tilt);
822 if (tiltHasChanged) {
823 emit tiltChanged(tilt);
824 // do not emit visibleRegionChanged() here, because, if the map isn't initialized,
825 // the getter won't return anything updated
826 }
827 }
828}
829
830qreal QDeclarativeGeoMap::tilt() const
831{
832 if (m_initialized)
833 return m_map->cameraData().tilt();
834 return m_cameraData.tilt();
835}
836
837void QDeclarativeGeoMap::setMinimumTilt(qreal minimumTilt, bool userSet)
838{
839 if (minimumTilt >= 0) {
840 qreal oldMinimumTilt = this->minimumTilt();
841
842 if (userSet)
843 m_userMinimumTilt = minimumTilt;
844 else
845 m_minimumTilt = minimumTilt;
846
847 if (tilt() < minimumTilt)
848 setTilt(minimumTilt);
849
850 if (oldMinimumTilt != this->minimumTilt())
851 emit minimumTiltChanged(this->minimumTilt());
852 }
853}
854
855/*!
856 \qmlproperty real QtLocation::Map::fieldOfView
857
858 This property holds the field of view of the camera used to look at the map, in degrees.
859 If the plugin property of the map is not set, or the plugin does not support mapping, the value is 45 degrees.
860
861 Note that changing this value implicitly changes also the distance between the camera and the map,
862 so that, at a tilting angle of 0 degrees, the resulting image is identical for any value used for this property.
863
864 For more information about this parameter, consult the Wikipedia articles about \l {https://en.wikipedia.org/wiki/Field_of_view} {Field of view} and
865 \l {https://en.wikipedia.org/wiki/Angle_of_view} {Angle of view}.
866
867 \sa minimumFieldOfView, maximumFieldOfView
868
869 \since QtLocation 5.9
870*/
871void QDeclarativeGeoMap::setFieldOfView(qreal fieldOfView)
872{
873 fieldOfView = qBound(minimumFieldOfView(), fieldOfView, maximumFieldOfView());
874
875 if (m_initialized) {
876 QGeoCameraData cameraData = m_map->cameraData();
877 cameraData.setFieldOfView(fieldOfView);
878 m_map->setCameraData(cameraData);
879 } else {
880 const bool fovChanged = fieldOfView != m_cameraData.fieldOfView();
881 m_cameraData.setFieldOfView(fieldOfView);
882 if (fovChanged) {
883 emit fieldOfViewChanged(fieldOfView);
884 // do not emit visibleRegionChanged() here, because, if the map isn't initialized,
885 // the getter won't return anything updated
886 }
887 }
888}
889
890qreal QDeclarativeGeoMap::fieldOfView() const
891{
892 if (m_initialized)
893 return m_map->cameraData().fieldOfView();
894 return m_cameraData.fieldOfView();
895}
896
897void QDeclarativeGeoMap::setMinimumFieldOfView(qreal minimumFieldOfView, bool userSet)
898{
899 if (minimumFieldOfView > 0 && minimumFieldOfView < 180.0) {
900 qreal oldMinimumFoV = this->minimumFieldOfView();
901
902 if (userSet)
903 m_userMinimumFieldOfView = minimumFieldOfView;
904 else
905 m_minimumFieldOfView = minimumFieldOfView;
906
907 if (fieldOfView() < minimumFieldOfView)
908 setFieldOfView(minimumFieldOfView);
909
910 if (oldMinimumFoV != this->minimumFieldOfView())
911 emit minimumFieldOfViewChanged(this->minimumFieldOfView());
912 }
913}
914
915/*!
916 \qmlproperty real QtLocation::Map::minimumFieldOfView
917
918 This property holds the minimum valid field of view for the map, in degrees.
919
920 The minimum tilt field of view by the \l plugin used is a lower bound for
921 this property.
922 If the \l plugin property is not set or the plugin does not support mapping, this property is \c 1.
923
924 \sa fieldOfView, maximumFieldOfView
925
926 \since QtLocation 5.9
927*/
928qreal QDeclarativeGeoMap::minimumFieldOfView() const
929{
930 return qMax(qMin(m_maximumFieldOfView, m_userMinimumFieldOfView), m_minimumFieldOfView);
931}
932
933void QDeclarativeGeoMap::setMaximumFieldOfView(qreal maximumFieldOfView, bool userSet)
934{
935 if (maximumFieldOfView > 0 && maximumFieldOfView < 180.0) {
936 qreal oldMaximumFoV = this->maximumFieldOfView();
937 if (userSet)
938 m_userMaximumFieldOfView = maximumFieldOfView;
939 else
940 m_maximumFieldOfView = maximumFieldOfView;
941
942 if (fieldOfView() > maximumFieldOfView)
943 setFieldOfView(maximumFieldOfView);
944
945 if (oldMaximumFoV != this->maximumFieldOfView())
946 emit maximumFieldOfViewChanged(this->maximumFieldOfView());
947 }
948}
949
950/*!
951 \qmlproperty real QtLocation::Map::maximumFieldOfView
952
953 This property holds the maximum valid field of view for the map, in degrees.
954
955 The minimum tilt field of view by the \l plugin used is an upper bound for
956 this property.
957 If the \l plugin property is not set or the plugin does not support mapping, this property is \c 179.
958
959 \sa fieldOfView, minimumFieldOfView
960
961 \since QtLocation 5.9
962*/
963qreal QDeclarativeGeoMap::maximumFieldOfView() const
964{
965 return qMin(qMax(m_minimumFieldOfView, m_userMaximumFieldOfView), m_maximumFieldOfView);
966}
967
968/*!
969 \qmlproperty real QtLocation::Map::minimumTilt
970
971 This property holds the minimum valid tilt for the map, in degrees.
972
973 The minimum tilt defined by the \l plugin used is a lower bound for
974 this property.
975 If the \l plugin property is not set or the plugin does not support mapping, this property is \c 0.
976
977 Since QtLocation 5.12, plugins can additionally restrict this value depending on the current zoom level.
978
979 \sa tilt, maximumTilt
980
981 \since QtLocation 5.9
982*/
983qreal QDeclarativeGeoMap::minimumTilt() const
984{
985 return qMax(qMin(m_maximumTilt, m_userMinimumTilt), m_minimumTilt);
986}
987
988void QDeclarativeGeoMap::setMaximumTilt(qreal maximumTilt, bool userSet)
989{
990 if (maximumTilt >= 0) {
991 qreal oldMaximumTilt = this->maximumTilt();
992
993 if (userSet)
994 m_userMaximumTilt = maximumTilt;
995 else
996 m_maximumTilt = maximumTilt;
997
998 if (tilt() > maximumTilt)
999 setTilt(maximumTilt);
1000
1001 if (oldMaximumTilt != this->maximumTilt())
1002 emit maximumTiltChanged(this->maximumTilt());
1003 }
1004}
1005
1006/*!
1007 \qmlproperty real QtLocation::Map::maximumTilt
1008
1009 This property holds the maximum valid tilt for the map, in degrees.
1010
1011 The maximum tilt defined by the \l plugin used is an upper bound for
1012 this property.
1013 If the \l plugin property is not set or the plugin does not support mapping, this property is \c 89.5.
1014
1015 Since QtLocation 5.12, plugins can additionally restrict this value depending on the current zoom level.
1016
1017 \sa tilt, minimumTilt
1018
1019 \since QtLocation 5.9
1020*/
1021qreal QDeclarativeGeoMap::maximumTilt() const
1022{
1023 return qMin(qMax(m_minimumTilt, m_userMaximumTilt), m_maximumTilt);
1024}
1025
1026/*!
1027 \qmlproperty coordinate QtLocation::Map::center
1028
1029 This property holds the coordinate which occupies the center of the
1030 mapping viewport. Invalid center coordinates are ignored.
1031
1032 The default value is an arbitrary valid coordinate.
1033*/
1034void QDeclarativeGeoMap::setCenter(const QGeoCoordinate &center)
1035{
1036 if (!center.isValid())
1037 return;
1038
1039 if (m_initialized) {
1040 QGeoCoordinate coord(center);
1041 coord.setLatitude(qBound(m_minimumViewportLatitude, center.latitude(), m_maximumViewportLatitude));
1042 QGeoCameraData cameraData = m_map->cameraData();
1043 cameraData.setCenter(coord);
1044 m_map->setCameraData(cameraData);
1045 } else {
1046 const bool centerHasChanged = center != m_cameraData.center();
1047 m_cameraData.setCenter(center);
1048 if (centerHasChanged) {
1049 emit centerChanged(center);
1050 // do not emit visibleRegionChanged() here, because, if the map isn't initialized,
1051 // the getter won't return anything updated
1052 }
1053 }
1054}
1055
1056QGeoCoordinate QDeclarativeGeoMap::center() const
1057{
1058 if (m_initialized)
1059 return m_map->cameraData().center();
1060 return m_cameraData.center();
1061}
1062
1063
1064/*!
1065 \qmlproperty geoShape QtLocation::Map::visibleRegion
1066
1067 This property holds the region which occupies the viewport of
1068 the map. The camera is positioned in the center of the shape, and
1069 at the largest integral zoom level possible which allows the
1070 whole shape to be visible on the screen. This implies that
1071 reading this property back shortly after having been set the
1072 returned area is equal or larger than the set area.
1073
1074 Setting this property implicitly changes the \l center and
1075 \l zoomLevel of the map. Any previously set value to those
1076 properties will be overridden.
1077
1078 \note Since Qt 5.14 This property provides change notifications.
1079
1080 \since 5.6
1081*/
1082void QDeclarativeGeoMap::setVisibleRegion(const QGeoShape &shape)
1083{
1084 if (shape.boundingGeoRectangle() == visibleRegion())
1085 return;
1086
1087 m_visibleRegion = shape.boundingGeoRectangle();
1088 if (!m_visibleRegion.isValid()
1089 || (m_visibleRegion.bottomRight().latitude() >= 85.0) // rect entirely outside web mercator
1090 || (m_visibleRegion.topLeft().latitude() <= -85.0)) {
1091 // shape invalidated -> nothing to fit anymore
1092 m_visibleRegion = QGeoRectangle();
1093 m_pendingFitViewport = false;
1094 emit visibleRegionChanged();
1095 return;
1096 }
1097
1098 if (!m_map || !width() || !height()) {
1099 m_pendingFitViewport = true;
1100 emit visibleRegionChanged();
1101 return;
1102 }
1103
1104 fitViewportToGeoShape(m_visibleRegion);
1105 emit visibleRegionChanged();
1106}
1107
1108QGeoShape QDeclarativeGeoMap::visibleRegion() const
1109{
1110 if (!m_map || !width() || !height())
1111 return m_visibleRegion;
1112
1113 if (m_map->capabilities() & QGeoMap::SupportsVisibleRegion) {
1114 return m_map->visibleRegion();
1115 } else {
1116 // ToDo: handle projections not supporting visible region in a better way.
1117 // This approach will fail when horizon is in the view or the map is greatly zoomed out.
1118 QList<QGeoCoordinate> visiblePoly;
1119 visiblePoly << m_map->geoProjection().itemPositionToCoordinate(QDoubleVector2D(0,0), false);
1120 visiblePoly << m_map->geoProjection().itemPositionToCoordinate(QDoubleVector2D(m_map->viewportWidth() - 1,
1121 0), false);
1122 visiblePoly << m_map->geoProjection().itemPositionToCoordinate(QDoubleVector2D(m_map->viewportWidth() - 1,
1123 m_map->viewportHeight() - 1), false);
1124 visiblePoly << m_map->geoProjection().itemPositionToCoordinate(QDoubleVector2D(0,
1125 m_map->viewportHeight() - 1), false);
1126 QGeoPath path;
1127 path.setPath(visiblePoly);
1128 return path.boundingGeoRectangle();
1129 }
1130}
1131
1132/*!
1133 \qmlproperty bool QtLocation::Map::copyrightsVisible
1134
1135 This property holds the visibility of the copyrights notice. The notice is usually
1136 displayed in the bottom left corner. By default, this property is set to \c true.
1137
1138 \note Many map providers require the notice to be visible as part of the terms and conditions.
1139 Please consult the relevant provider documentation before turning this notice off.
1140
1141 \since 5.7
1142*/
1143void QDeclarativeGeoMap::setCopyrightsVisible(bool visible)
1144{
1145 if (m_copyrightsVisible == visible)
1146 return;
1147
1148 if (!m_copyrights.isNull())
1149 m_copyrights->setCopyrightsVisible(visible);
1150
1151 m_copyrightsVisible = visible;
1152 emit copyrightsVisibleChanged(visible);
1153}
1154
1155bool QDeclarativeGeoMap::copyrightsVisible() const
1156{
1157 return m_copyrightsVisible;
1158}
1159
1160
1161
1162/*!
1163 \qmlproperty color QtLocation::Map::color
1164
1165 This property holds the background color of the map element.
1166
1167 \since 5.6
1168*/
1169void QDeclarativeGeoMap::setColor(const QColor &color)
1170{
1171 if (color != m_color) {
1172 m_color = color;
1173 update();
1174 emit colorChanged(m_color);
1175 }
1176}
1177
1178QColor QDeclarativeGeoMap::color() const
1179{
1180 return m_color;
1181}
1182
1183/*!
1184 \qmlproperty rect QtLocation::Map::visibleArea
1185
1186 This property holds the visible area inside the Map QML element.
1187 It is a rect whose coordinates are relative to the Map element.
1188 Its size will be clamped to the size of the Map element.
1189 A null visibleArea means that the whole Map is visible.
1190
1191 \since 5.12
1192*/
1193QRectF QDeclarativeGeoMap::visibleArea() const
1194{
1195 if (m_initialized)
1196 return m_map->visibleArea();
1197 return m_visibleArea;
1198}
1199
1200void QDeclarativeGeoMap::setVisibleArea(const QRectF &visibleArea)
1201{
1202 const QRectF oldVisibleArea = QDeclarativeGeoMap::visibleArea();
1203 if (visibleArea == oldVisibleArea)
1204 return;
1205
1206 if (!visibleArea.isValid() && !visibleArea.isEmpty()) // values < 0
1207 return;
1208
1209 if (m_initialized) {
1210 m_map->setVisibleArea(visibleArea);
1211 const QRectF newVisibleArea = QDeclarativeGeoMap::visibleArea();
1212 if (newVisibleArea != oldVisibleArea) {
1213 // polish map items
1214 for (const QPointer<QDeclarativeGeoMapItemBase> &i: std::as_const(m_mapItems)) {
1215 if (i)
1216 i->visibleAreaChanged();
1217 }
1218 }
1219 } else {
1220 m_visibleArea = visibleArea;
1221 const QRectF newVisibleArea = QDeclarativeGeoMap::visibleArea();
1222 if (newVisibleArea != oldVisibleArea)
1223 emit visibleAreaChanged();
1224 }
1225}
1226
1227/*!
1228 \qmlproperty bool QtLocation::Map::mapReady
1229
1230 This property holds whether the map has been successfully initialized and is ready to be used.
1231 Some methods, such as \l fromCoordinate and \l toCoordinate, will not work before the map is ready.
1232 Due to the architecture of the \l Map, it's advised to use the signal emitted for this property
1233 in place of \l {QtQml::Component::completed()}{Component.onCompleted}, to make sure that everything behaves as expected.
1234
1235 \since 5.9
1236*/
1237bool QDeclarativeGeoMap::mapReady() const
1238{
1239 return m_initialized;
1240}
1241
1242QMargins QDeclarativeGeoMap::mapMargins() const
1243{
1244 const QRectF va = m_map->visibleArea();
1245 if (va.isEmpty())
1246 return QMargins();
1247 return QMargins( va.x()
1248 , va.y()
1249 , width() - va.width() - va.x()
1250 , height() - va.height() - va.y());
1251}
1252
1253/*!
1254 \qmlproperty list<mapType> QtLocation::Map::supportedMapTypes
1255
1256 This read-only property holds the set of \l{mapType}{map types} supported by this map.
1257
1258 \sa activeMapType
1259*/
1260QList<QGeoMapType> QDeclarativeGeoMap::supportedMapTypes()
1261{
1262 return m_supportedMapTypes;
1263}
1264
1265/*!
1266 \qmlmethod void QtLocation::Map::alignCoordinateToPoint(coordinate coordinate, QPointF point)
1267
1268 Aligns \a coordinate to \a point.
1269 This method effectively extends the functionality offered by the \l center qml property, allowing
1270 to align a coordinate to point of the Map element other than its center.
1271 This is useful in those applications where the center of the scene (e.g., a cursor) is not to be
1272 placed exactly in the center of the map.
1273
1274 If the map is tilted, and \a coordinate happens to be behind the camera, or if the map is not ready
1275 (see \l mapReady), calling this method will have no effect.
1276
1277 The release of this API with Qt 5.10 is a Technology Preview.
1278
1279 \sa center
1280
1281 \since 5.10
1282*/
1283void QDeclarativeGeoMap::alignCoordinateToPoint(const QGeoCoordinate &coordinate, const QPointF &point)
1284{
1285 if (!m_map || !(m_map->capabilities() & QGeoMap::SupportsAnchoringCoordinate))
1286 return;
1287
1288 if (!coordinate.isValid()
1289 || !qIsFinite(point.x())
1290 || !qIsFinite(point.y()))
1291 return;
1292
1293 m_map->anchorCoordinateToPoint(coordinate, point);
1294}
1295
1296/*!
1297 \qmlmethod coordinate QtLocation::Map::toCoordinate(QPointF position, bool clipToViewPort)
1298
1299 Returns the coordinate which corresponds to the \a position relative to the map item.
1300
1301 If \a clipToViewPort is \c true, or not supplied then returns an invalid coordinate if
1302 \a position is not within the current viewport.
1303*/
1304QGeoCoordinate QDeclarativeGeoMap::toCoordinate(const QPointF &position, bool clipToViewPort) const
1305{
1306 if (m_map)
1307 return m_map->geoProjection().itemPositionToCoordinate(QDoubleVector2D(position), clipToViewPort);
1308 else
1309 return QGeoCoordinate();
1310}
1311
1312/*!
1313 \qmlmethod point QtLocation::Map::fromCoordinate(coordinate coordinate, bool clipToViewPort)
1314
1315 Returns the position relative to the map item which corresponds to the \a coordinate.
1316
1317 If \a clipToViewPort is \c true, or not supplied then returns an invalid QPointF if
1318 \a coordinate is not within the current viewport.
1319*/
1320QPointF QDeclarativeGeoMap::fromCoordinate(const QGeoCoordinate &coordinate, bool clipToViewPort) const
1321{
1322 if (m_map)
1323 return m_map->geoProjection().coordinateToItemPosition(coordinate, clipToViewPort).toPointF();
1324 else
1325 return QPointF(qQNaN(), qQNaN());
1326}
1327
1328/*!
1329 \qmlmethod void QtLocation::Map::pan(int dx, int dy)
1330
1331 Starts panning the map by \a dx pixels along the x-axis and
1332 by \a dy pixels along the y-axis.
1333
1334 Positive values for \a dx move the map right, negative values left.
1335 Positive values for \a dy move the map down, negative values up.
1336
1337 During panning the \l center, and \l zoomLevel may change.
1338*/
1339void QDeclarativeGeoMap::pan(int dx, int dy)
1340{
1341 if (!m_map)
1342 return;
1343 if (dx == 0 && dy == 0)
1344 return;
1345
1346 QGeoCoordinate coord = m_map->geoProjection().itemPositionToCoordinate(
1347 QDoubleVector2D(m_map->viewportWidth() / 2 + dx,
1348 m_map->viewportHeight() / 2 + dy));
1349 setCenter(coord);
1350}
1351
1352
1353/*!
1354 \qmlmethod void QtLocation::Map::prefetchData()
1355
1356 Optional hint that allows the map to prefetch during this idle period
1357*/
1358void QDeclarativeGeoMap::prefetchData()
1359{
1360 if (!m_map)
1361 return;
1362 m_map->prefetchData();
1363}
1364
1365/*!
1366 \qmlmethod void QtLocation::Map::clearData()
1367
1368 Clears map data collected by the currently selected plugin.
1369 \note This method will delete cached files.
1370 \sa plugin
1371*/
1372void QDeclarativeGeoMap::clearData()
1373{
1374 if (m_map)
1375 m_map->clearData();
1376}
1377
1378/*!
1379 \qmlmethod void QtLocation::Map::fitViewportToGeoShape(geoShape, margins)
1380
1381 Fits the viewport to a specific geo shape \a geoShape.
1382 The \a margins are in screen pixels.
1383
1384 \note If the projection used by the plugin is not WebMercator, and the plugin does not have fitting to
1385 shape capability, this method will do nothing.
1386
1387 \sa visibleRegion
1388 \since 5.13
1389*/
1390void QDeclarativeGeoMap::fitViewportToGeoShape(const QGeoShape &shape, QVariant margins)
1391{
1392 QMargins m(10, 10, 10, 10); // lets defaults to 10 if margins is invalid
1393 switch (margins.typeId()) {
1394 case QMetaType::Int:
1395 case QMetaType::Double: {
1396 const int value = int(margins.toDouble());
1397 m = QMargins(value, value, value, value);
1398 }
1399 break;
1400 // ToDo: Support distinct margins in some QML form. Perhaps QRect?
1401 default:
1402 break;
1403 }
1404 fitViewportToGeoShape(shape, m);
1405}
1406
1407void QDeclarativeGeoMap::fitViewportToGeoShape(const QGeoShape &shape, const QMargins &borders)
1408{
1409 if (!m_map || !shape.isValid())
1410 return;
1411
1412 if (m_map->geoProjection().projectionType() == QGeoProjection::ProjectionWebMercator) {
1413 // This case remains handled here, and not inside QGeoMap*::fitViewportToGeoRectangle,
1414 // in order to honor animations on center and zoomLevel
1415 const QMargins margins = borders + mapMargins();
1416 const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(m_map->geoProjection());
1417 const QPair<QGeoCoordinate, qreal> fitData = p.fitViewportToGeoRectangle(shape.boundingGeoRectangle(),
1418 margins);
1419 if (!fitData.first.isValid())
1420 return;
1421
1422 // position camera to the center of bounding box
1423 setProperty("center", QVariant::fromValue(fitData.first)); // not using setCenter(centerCoordinate) to honor a possible animation set on the center property
1424
1425 if (!qIsFinite(fitData.second))
1426 return;
1427 double newZoom = qMax<double>(minimumZoomLevel(), fitData.second);
1428 setProperty("zoomLevel", QVariant::fromValue(newZoom)); // not using setZoomLevel(newZoom) to honor a possible animation set on the zoomLevel property
1429 } else if (m_map->capabilities() & QGeoMap::SupportsFittingViewportToGeoRectangle) {
1430 // Animations cannot be honored in this case, as m_map acts as a black box
1431 m_map->fitViewportToGeoRectangle(m_visibleRegion, borders);
1432 }
1433 // else out of luck
1434}
1435
1436/*!
1437 \qmlproperty string QtLocation::Map::errorString
1438
1439 This read-only property holds the textual presentation of the latest mapping provider error.
1440 If no error has occurred, an empty string is returned.
1441
1442 An empty string may also be returned if an error occurred which has no associated
1443 textual representation.
1444
1445 \sa QGeoServiceProvider::errorString()
1446*/
1447
1448QString QDeclarativeGeoMap::errorString() const
1449{
1450 return m_errorString;
1451}
1452
1453/*!
1454 \qmlproperty enumeration QtLocation::Map::error
1455
1456 This read-only property holds the last occurred mapping service provider error.
1457
1458 \list
1459 \li Map.NoError - No error has occurred.
1460 \li Map.NotSupportedError -The maps plugin property was not set or there is no mapping manager associated with the plugin.
1461 \li Map.UnknownParameterError -The plugin did not recognize one of the parameters it was given.
1462 \li Map.MissingRequiredParameterError - The plugin did not find one of the parameters it was expecting.
1463 \li Map.ConnectionError - The plugin could not connect to its backend service or database.
1464 \endlist
1465
1466 \sa QGeoServiceProvider::Error
1467*/
1468
1469QGeoServiceProvider::Error QDeclarativeGeoMap::error() const
1470{
1471 return m_error;
1472}
1473
1474QGeoMap *QDeclarativeGeoMap::map() const
1475{
1476 return m_map;
1477}
1478
1479void QDeclarativeGeoMap::itemChange(ItemChange change, const ItemChangeData &value)
1480{
1481 if (change == ItemChildAddedChange) {
1482 QQuickItem *child = value.item;
1483 QQuickItem *mapItem = qobject_cast<QDeclarativeGeoMapItemBase *>(child);
1484 if (!mapItem)
1485 mapItem = qobject_cast<QDeclarativeGeoMapItemGroup *>(child);
1486
1487 if (mapItem) {
1488 qreal z = mapItem->z();
1489 if (z > m_maxChildZ) { // Ignore children removal
1490 m_maxChildZ = z;
1491 // put the copyrights notice object at the highest z order
1492 if (m_copyrights)
1493 m_copyrights->setCopyrightsZ(m_maxChildZ + 1);
1494 }
1495 }
1496 } else if (change == ItemSceneChange) {
1497 if (m_window) {
1498 disconnect(m_window, &QQuickWindow::beforeSynchronizing,
1499 this, &QDeclarativeGeoMap::updateItemToWindowTransform);
1500 }
1501 m_window = value.window;
1502 if (m_window) {
1503 connect(m_window, &QQuickWindow::beforeSynchronizing,
1504 this, &QDeclarativeGeoMap::updateItemToWindowTransform, Qt::DirectConnection);
1505 }
1506 }
1507 QQuickItem::itemChange(change, value);
1508}
1509
1510void QDeclarativeGeoMap::attachCopyrightNotice(bool initialVisibility)
1511{
1512 if (initialVisibility) {
1513 ++m_copyNoticesVisible;
1514 if (m_map)
1515 m_map->setCopyrightVisible(m_copyNoticesVisible > 0);
1516 }
1517}
1518
1519void QDeclarativeGeoMap::detachCopyrightNotice(bool currentVisibility)
1520{
1521 if (currentVisibility) {
1522 --m_copyNoticesVisible;
1523 if (m_map)
1524 m_map->setCopyrightVisible(m_copyNoticesVisible > 0);
1525 }
1526}
1527
1528void QDeclarativeGeoMap::onAttachedCopyrightNoticeVisibilityChanged()
1529{
1530 QDeclarativeGeoMapCopyrightNotice *copy = static_cast<QDeclarativeGeoMapCopyrightNotice *>(sender());
1531 m_copyNoticesVisible += ( int(copy->copyrightsVisible()) * 2 - 1);
1532 if (m_map)
1533 m_map->setCopyrightVisible(m_copyNoticesVisible > 0);
1534}
1535
1536void QDeclarativeGeoMap::onCameraDataChanged(const QGeoCameraData &cameraData)
1537{
1538 bool centerHasChanged = cameraData.center() != m_cameraData.center();
1539 bool bearingHasChanged = cameraData.bearing() != m_cameraData.bearing();
1540 bool tiltHasChanged = cameraData.tilt() != m_cameraData.tilt();
1541 bool fovHasChanged = cameraData.fieldOfView() != m_cameraData.fieldOfView();
1542 bool zoomHasChanged = cameraData.zoomLevel() != m_cameraData.zoomLevel();
1543
1544 m_cameraData = cameraData;
1545 // polish map items
1546 for (const QPointer<QDeclarativeGeoMapItemBase> &i: std::as_const(m_mapItems)) {
1547 if (i)
1548 i->baseCameraDataChanged(m_cameraData); // Consider optimizing this further, removing the contained duplicate if conditions.
1549 }
1550
1551 if (centerHasChanged)
1552 emit centerChanged(m_cameraData.center());
1553 if (zoomHasChanged)
1554 emit zoomLevelChanged(m_cameraData.zoomLevel());
1555 if (bearingHasChanged)
1556 emit bearingChanged(m_cameraData.bearing());
1557 if (tiltHasChanged)
1558 emit tiltChanged(m_cameraData.tilt());
1559 if (fovHasChanged)
1560 emit fieldOfViewChanged(m_cameraData.fieldOfView());
1561 if (centerHasChanged || zoomHasChanged || bearingHasChanged
1562 || tiltHasChanged || fovHasChanged)
1563 emit visibleRegionChanged();
1564}
1565
1566/*!
1567 \qmlproperty list<MapItem> QtLocation::Map::mapItems
1568
1569 Returns the list of all map items in no particular order.
1570 These items include items that were declared statically as part of
1571 the type declaration, as well as dynamical items (\l addMapItem,
1572 \l MapItemView).
1573
1574 \sa addMapItem, removeMapItem, clearMapItems
1575*/
1576
1577QList<QObject *> QDeclarativeGeoMap::mapItems()
1578{
1579 QList<QObject *> ret;
1580 for (const auto &ptr : m_mapItems) {
1581 if (ptr)
1582 ret << ptr.data();
1583 }
1584 return ret;
1585}
1586
1587/*!
1588 \qmlmethod void QtLocation::Map::addMapItem(MapItem item)
1589
1590 Adds the given \a item to the Map (for example MapQuickItem, MapCircle). If the object
1591 already is on the Map, it will not be added again.
1592
1593 As an example, consider the case where you have a MapCircle representing your current position:
1594
1595 \snippet declarative/maps.qml QtQuick import
1596 \snippet declarative/maps.qml QtLocation import
1597 \codeline
1598 \snippet declarative/maps.qml Map addMapItem MapCircle at current position
1599
1600 \note MapItemViews cannot be added with this method.
1601
1602 \sa mapItems, removeMapItem, clearMapItems
1603*/
1604
1605void QDeclarativeGeoMap::addMapItem(QDeclarativeGeoMapItemBase *item)
1606{
1607 if (addMapItem_real(item))
1608 emit mapItemsChanged();
1609}
1610
1611bool QDeclarativeGeoMap::addMapItem_real(QDeclarativeGeoMapItemBase *item)
1612{
1613 if (!item || item->quickMap())
1614 return false;
1615 // If the item comes from a MapItemGroup, do not reparent it.
1616 if (!qobject_cast<QDeclarativeGeoMapItemGroup *>(item->parentItem()))
1617 item->setParentItem(this);
1618 m_mapItems.append(item);
1619 if (m_map) {
1620 item->setMap(this, m_map);
1621 m_map->addMapItem(item);
1622 }
1623 return true;
1624}
1625
1626/*!
1627 \qmlmethod void QtLocation::Map::removeMapItem(MapItem item)
1628
1629 Removes the given \a item from the Map (for example MapQuickItem, MapCircle). If
1630 the MapItem does not exist or was not previously added to the map, the
1631 method does nothing.
1632
1633 \sa mapItems, addMapItem, clearMapItems
1634*/
1635void QDeclarativeGeoMap::removeMapItem(QDeclarativeGeoMapItemBase *ptr)
1636{
1637 if (removeMapItem_real(ptr))
1638 emit mapItemsChanged();
1639}
1640
1641bool QDeclarativeGeoMap::removeMapItem_real(QDeclarativeGeoMapItemBase *ptr)
1642{
1643 if (!ptr)
1644 return false;
1645 QPointer<QDeclarativeGeoMapItemBase> item(ptr);
1646 if (!m_mapItems.contains(item))
1647 return false;
1648 if (m_map)
1649 m_map->removeMapItem(ptr);
1650 if (item->parentItem() == this)
1651 item->setParentItem(0);
1652 item->setMap(0, 0);
1653 // these can be optimized for perf, as we already check the 'contains' above
1654 m_mapItems.removeOne(item);
1655 return true;
1656}
1657
1658/*!
1659 \qmlmethod void QtLocation::Map::clearMapItems()
1660
1661 Removes all items and item groups from the map.
1662
1663 \sa mapItems, addMapItem, removeMapItem, addMapItemGroup, removeMapItemGroup
1664*/
1665void QDeclarativeGeoMap::clearMapItems()
1666{
1667 if (m_mapItems.isEmpty())
1668 return;
1669
1670 qsizetype removed = 0;
1671 for (qsizetype i = 0; i < m_mapItemGroups.count(); ++i) {
1672 auto item = m_mapItemGroups.at(i);
1673 // Processing only top-level groups (!views)
1674 if (qobject_cast<QDeclarativeGeoMapItemView *>(item))
1675 continue;
1676
1677
1678 if (item->parentItem() != this)
1679 continue;
1680
1681 if (removeMapItemGroup_real(item)) {
1682 removed++;
1683 --i;
1684 }
1685 }
1686
1687 while (!m_mapItems.isEmpty())
1688 removed += removeMapItem_real(m_mapItems.first());
1689
1690 if (removed)
1691 emit mapItemsChanged();
1692}
1693
1694/*!
1695 \qmlmethod void QtLocation::Map::addMapItemGroup(MapItemGroup itemGroup)
1696
1697 Adds the map items contained in the given \a itemGroup to the Map
1698 (for example MapQuickItem, MapCircle).
1699
1700 \sa MapItemGroup, removeMapItemGroup
1701
1702 \since 5.9
1703*/
1704void QDeclarativeGeoMap::addMapItemGroup(QDeclarativeGeoMapItemGroup *itemGroup)
1705{
1706 if (addMapItemGroup_real(itemGroup))
1707 emit mapItemsChanged();
1708}
1709
1710bool QDeclarativeGeoMap::addMapItemGroup_real(QDeclarativeGeoMapItemGroup *itemGroup)
1711{
1712 if (!itemGroup || itemGroup->quickMap()) // Already added to some map
1713 return false;
1714
1715 itemGroup->setQuickMap(this);
1716
1717 if (!isGroupNested(itemGroup))
1718 itemGroup->setParentItem(this);
1719
1720 QPointer<QDeclarativeGeoMapItemGroup> g(itemGroup);
1721 m_mapItemGroups.append(g);
1722
1723 const QList<QQuickItem *> quickKids = itemGroup->childItems();
1724 int count = 0;
1725 for (auto c: quickKids) {
1726 count += addMapChild(c); // this calls addMapItemGroup recursively, if needed
1727 }
1728 return count;
1729}
1730
1731/*!
1732 \qmlmethod void QtLocation::Map::removeMapItemGroup(MapItemGroup itemGroup)
1733
1734 Removes \a itemGroup and the items contained therein from the Map.
1735
1736 \sa MapItemGroup, addMapItemGroup
1737
1738 \since 5.9
1739*/
1740void QDeclarativeGeoMap::removeMapItemGroup(QDeclarativeGeoMapItemGroup *itemGroup)
1741{
1742 if (removeMapItemGroup_real(itemGroup))
1743 emit mapItemsChanged();
1744}
1745
1746bool QDeclarativeGeoMap::removeMapItemGroup_real(QDeclarativeGeoMapItemGroup *itemGroup)
1747{
1748 if (!itemGroup || itemGroup->quickMap() != this) // cant remove an itemGroup added to another map
1749 return false;
1750
1751 QPointer<QDeclarativeGeoMapItemGroup> g(itemGroup);
1752 if (!m_mapItemGroups.removeOne(g))
1753 return false;
1754
1755 const QList<QQuickItem *> quickKids = itemGroup->childItems();
1756 int count = 0;
1757 for (auto c: quickKids) {
1758 count += removeMapChild(c);
1759 }
1760 itemGroup->setQuickMap(nullptr);
1761 if (itemGroup->parentItem() == this)
1762 itemGroup->setParentItem(0);
1763 return count;
1764}
1765
1766/*!
1767 \qmlmethod void QtLocation::Map::removeMapItemView(MapItemView itemView)
1768
1769 Removes \a itemView and the items instantiated by it from the Map.
1770
1771 \sa MapItemView, addMapItemView
1772
1773 \since 5.10
1774*/
1775void QDeclarativeGeoMap::removeMapItemView(QDeclarativeGeoMapItemView *itemView)
1776{
1777 if (removeMapItemView_real(itemView))
1778 emit mapItemsChanged();
1779}
1780
1781bool QDeclarativeGeoMap::removeMapItemView_real(QDeclarativeGeoMapItemView *itemView)
1782{
1783 if (!itemView || itemView->m_map != this) // can't remove a view that is already added to another map
1784 return false;
1785
1786 itemView->removeInstantiatedItems(false); // remove the items without using transitions AND abort ongoing ones
1787 itemView->m_map = 0;
1788 m_mapViews.removeOne(itemView);
1789 return removeMapItemGroup_real(itemView); // at this point, all delegate instances have been removed.
1790}
1791
1792void QDeclarativeGeoMap::updateItemToWindowTransform()
1793{
1794 if (!m_initialized)
1795 return;
1796
1797 // Update itemToWindowTransform into QGeoProjection
1798 QTransform item2Window = QQuickItemPrivate::get(this)->itemToWindowTransform();
1799 if (!property("layer").isNull() && property("layer").value<QObject *>()->property("enabled").toBool())
1800 item2Window.reset(); // When layer is enabled, the item is rendered offscreen with no transformation, then the layer is applied
1801
1802 m_map->setItemToWindowTransform(item2Window);
1803
1804 m_sgNodeHasChanged = false;
1805}
1806
1807void QDeclarativeGeoMap::onSGNodeChanged()
1808{
1809 m_sgNodeHasChanged = true;
1810 update();
1811}
1812
1813/*!
1814 \qmlmethod void QtLocation::Map::addMapItemView(MapItemView itemView)
1815
1816 Adds \a itemView to the Map.
1817
1818 \sa MapItemView, removeMapItemView
1819
1820 \since 5.10
1821*/
1822void QDeclarativeGeoMap::addMapItemView(QDeclarativeGeoMapItemView *itemView)
1823{
1824 if (addMapItemView_real(itemView))
1825 emit mapItemsChanged();
1826}
1827
1828bool QDeclarativeGeoMap::addMapItemView_real(QDeclarativeGeoMapItemView *itemView)
1829{
1830 if (!itemView || itemView->m_map) // can't add a view twice
1831 return false;
1832
1833 int count = addMapItemGroup_real(itemView); // at this point, delegates aren't yet incubated.
1834 // Not appending it to m_mapViews because it seems unnecessary even if the
1835 // itemView is a child of this (in which case it would be destroyed
1836 m_mapViews.append(itemView);
1837 setupMapView(itemView);
1838 return count;
1839}
1840
1841/*!
1842 \qmlproperty mapType QtLocation::Map::activeMapType
1843
1844 \brief Access to the currently active \l{mapType}{map type}.
1845
1846 This property can be set to change the active \l{mapType}{map type}.
1847 See the \l{Map::supportedMapTypes}{supportedMapTypes} property for possible values.
1848
1849 \sa mapType
1850*/
1851void QDeclarativeGeoMap::setActiveMapType(const QGeoMapType &mapType)
1852{
1853 if (m_activeMapType != mapType) {
1854 if (m_map) {
1855 if (mapType.pluginName() == m_plugin->name().toLatin1()) {
1856 m_map->setActiveMapType(mapType);
1857 m_activeMapType = mapType;
1858 emit activeMapTypeChanged();
1859 }
1860 } else {
1861 m_activeMapType = mapType;
1862 emit activeMapTypeChanged();
1863 }
1864 }
1865}
1866
1867QGeoMapType QDeclarativeGeoMap::activeMapType() const
1868{
1869 return m_activeMapType;
1870}
1871
1872/*!
1873 \internal
1874*/
1875void QDeclarativeGeoMap::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
1876{
1877 QQuickItem::geometryChange(newGeometry, oldGeometry);
1878
1879 if (!m_map || newGeometry.size().isEmpty())
1880 return;
1881
1882 m_map->setViewportSize(newGeometry.size().toSize());
1883
1884 if (!m_initialized) {
1885 initialize();
1886 } else {
1887 setMinimumZoomLevel(m_map->minimumZoom(), false);
1888
1889 // Update the center latitudinal threshold
1890 QGeoCameraData cameraData = m_map->cameraData();
1891 const double maximumCenterLatitudeAtZoom = m_map->maximumCenterLatitudeAtZoom(cameraData);
1892 const double minimumCenterLatitudeAtZoom = m_map->minimumCenterLatitudeAtZoom(cameraData);
1893 if (maximumCenterLatitudeAtZoom != m_maximumViewportLatitude
1894 || minimumCenterLatitudeAtZoom != m_minimumViewportLatitude) {
1895 m_maximumViewportLatitude = maximumCenterLatitudeAtZoom;
1896 m_minimumViewportLatitude = minimumCenterLatitudeAtZoom;
1897 QGeoCoordinate coord = cameraData.center();
1898 coord.setLatitude(qBound(m_minimumViewportLatitude, coord.latitude(), m_maximumViewportLatitude));
1899 cameraData.setCenter(coord);
1900 m_map->setCameraData(cameraData);
1901 }
1902 if (oldGeometry.size() != newGeometry.size()) {
1903 // polish map items
1904 for (const QPointer<QDeclarativeGeoMapItemBase> &i: std::as_const(m_mapItems)) {
1905 if (i)
1906 i->polishAndUpdate();
1907 }
1908 }
1909 }
1910
1911 /*
1912 The fitViewportTo*() functions depend on a valid map geometry.
1913 If they were called prior to the first resize they cause
1914 the zoomlevel to jump to 0 (showing the world). Therefore the
1915 calls were queued up until now.
1916
1917 Multiple fitViewportTo*() calls replace each other.
1918 */
1919 if (m_pendingFitViewport && width() && height()) {
1920 fitViewportToGeoShape(m_visibleRegion);
1921 m_pendingFitViewport = false;
1922 }
1923
1924}
1925
1926/*!
1927 \qmlmethod void QtLocation::Map::fitViewportToMapItems(list<MapItems> items = {})
1928
1929 If no argument is provided, fits the current viewport to the boundary of all map items.
1930 The camera is positioned in the center of the map items, and at the largest integral zoom level
1931 possible which allows all map items to be visible on screen.
1932 If \a items is provided, fits the current viewport to the boundary of the specified map items only.
1933
1934 \note This method gained the optional \a items argument since Qt 5.15.
1935 In previous releases, this method fitted the map to all map items.
1936
1937 \sa fitViewportToVisibleMapItems
1938*/
1939void QDeclarativeGeoMap::fitViewportToMapItems(const QVariantList &items)
1940{
1941 if (items.size()) {
1942 QList<QPointer<QDeclarativeGeoMapItemBase> > itms;
1943 for (const QVariant &i: items) {
1944 QDeclarativeGeoMapItemBase *itm = qobject_cast<QDeclarativeGeoMapItemBase *>(i.value<QObject *>());
1945 if (itm)
1946 itms.append(itm);
1947 }
1948 fitViewportToMapItemsRefine(itms, true, false);
1949 } else {
1950 fitViewportToMapItemsRefine(m_mapItems, true, false);
1951 }
1952}
1953
1954/*!
1955 \qmlmethod void QtLocation::Map::fitViewportToVisibleMapItems()
1956
1957 Fits the current viewport to the boundary of all \b visible map items.
1958 The camera is positioned in the center of the map items, and at the largest integral
1959 zoom level possible which allows all map items to be visible on screen.
1960
1961 \sa fitViewportToMapItems
1962*/
1963void QDeclarativeGeoMap::fitViewportToVisibleMapItems()
1964{
1965 fitViewportToMapItemsRefine(m_mapItems, true, true);
1966}
1967
1968/*!
1969 \internal
1970*/
1971void QDeclarativeGeoMap::fitViewportToMapItemsRefine(const QList<QPointer<QDeclarativeGeoMapItemBase> > &mapItems,
1972 bool refine,
1973 bool onlyVisible)
1974{
1975 if (!m_map)
1976 return;
1977
1978 if (mapItems.size() == 0)
1979 return;
1980
1981 double minX = qInf();
1982 double maxX = -qInf();
1983 double minY = qInf();
1984 double maxY = -qInf();
1985 double topLeftX = 0;
1986 double topLeftY = 0;
1987 double bottomRightX = 0;
1988 double bottomRightY = 0;
1989 bool haveQuickItem = false;
1990
1991 // find bounds of all map items
1992 qsizetype itemCount = 0;
1993 for (qsizetype i = 0; i < mapItems.count(); ++i) {
1994 if (!mapItems.at(i))
1995 continue;
1996 QDeclarativeGeoMapItemBase *item = mapItems.at(i).data();
1997 if (!item || (onlyVisible && (!item->isVisible() || item->mapItemOpacity() <= 0.0)))
1998 continue;
1999
2000 // skip quick items in the first pass and refine the fit later
2001 QDeclarativeGeoMapQuickItem *quickItem =
2002 qobject_cast<QDeclarativeGeoMapQuickItem*>(item);
2003 if (refine && quickItem) {
2004 haveQuickItem = true;
2005 continue;
2006 }
2007 // Force map items to update immediately. Needed to ensure correct item size and positions
2008 // when recursively calling this function.
2009 // TODO: See if we really need updatePolish on delegated items, in particular
2010 // in relation to
2011 // a) fitViewportToMapItems
2012 // b) presence of MouseArea
2013 //
2014 // This is also legacy code. It must be updated to not operate on screen sizes.
2015 if (item->isPolishScheduled())
2016 item->updatePolish();
2017
2018 if (quickItem && quickItem->matrix_ && !quickItem->matrix_->m_matrix.isIdentity()) {
2019 // TODO: recalculate the center/zoom level so that the item becomes projectable again
2020 if (quickItem->zoomLevel() == 0.0) // the item is unprojectable, should be skipped.
2021 continue;
2022
2023 QRectF brect = item->boundingRect();
2024 brect = quickItem->matrix_->m_matrix.mapRect(brect);
2025 QPointF transformedPosition = quickItem->matrix_->m_matrix.map(item->position());
2026 topLeftX = transformedPosition.x();
2027 topLeftY = transformedPosition.y();
2028 bottomRightX = topLeftX + brect.width();
2029 bottomRightY = topLeftY + brect.height();
2030 } else {
2031 QGeoRectangle brect = item->geoShape().boundingGeoRectangle();
2032 topLeftX = fromCoordinate(brect.topLeft(), false).x();
2033 topLeftY = fromCoordinate(brect.topLeft(), false).y();
2034 bottomRightX = fromCoordinate(brect.bottomRight(), false).x();
2035 bottomRightY = fromCoordinate(brect.bottomRight(), false).y();
2036 }
2037
2038 minX = qMin(minX, topLeftX);
2039 maxX = qMax(maxX, bottomRightX);
2040 minY = qMin(minY, topLeftY);
2041 maxY = qMax(maxY, bottomRightY);
2042
2043 ++itemCount;
2044 }
2045
2046 if (itemCount == 0) {
2047 if (haveQuickItem)
2048 fitViewportToMapItemsRefine(mapItems, false, onlyVisible);
2049 return;
2050 }
2051 double bboxWidth = maxX - minX;
2052 double bboxHeight = maxY - minY;
2053 double bboxCenterX = minX + (bboxWidth / 2.0);
2054 double bboxCenterY = minY + (bboxHeight / 2.0);
2055
2056 // position camera to the center of bounding box
2057 QGeoCoordinate coordinate;
2058 coordinate = m_map->geoProjection().itemPositionToCoordinate(QDoubleVector2D(bboxCenterX, bboxCenterY), false);
2059 setProperty("center", QVariant::fromValue(coordinate));
2060
2061 // adjust zoom
2062 double bboxWidthRatio = bboxWidth / (bboxWidth + bboxHeight);
2063 double mapWidthRatio = width() / (width() + height());
2064 double zoomRatio;
2065
2066 if (bboxWidthRatio > mapWidthRatio)
2067 zoomRatio = bboxWidth / width();
2068 else
2069 zoomRatio = bboxHeight / height();
2070
2071 qreal newZoom = std::log10(zoomRatio) / std::log10(0.5);
2072 newZoom = std::floor(qMax(minimumZoomLevel(), (zoomLevel() + newZoom)));
2073 setProperty("zoomLevel", QVariant::fromValue(newZoom));
2074
2075 // as map quick items retain the same screen size after the camera zooms in/out
2076 // we refine the viewport again to achieve better results
2077 if (refine)
2078 fitViewportToMapItemsRefine(mapItems, false, onlyVisible);
2079}
2080
2081QT_END_NAMESPACE
#define M_PI
Definition qmath.h:201