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