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