1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the QtLocation module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL3$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPLv3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or later as published by the Free
28 ** Software Foundation and appearing in the file LICENSE.GPL included in
29 ** the packaging of this file. Please review the following information to
30 ** ensure the GNU General Public License version 2.0 requirements will be
31 ** met: http://www.gnu.org/licenses/gpl-2.0.html.
32 **
33 ** $QT_END_LICENSE$
34 **
35 ****************************************************************************/
36 
37 #include "qdeclarativegeomap_p.h"
38 #include "qdeclarativegeomapquickitem_p.h"
39 #include "qdeclarativegeomapcopyrightsnotice_p.h"
40 #include "qdeclarativegeoserviceprovider_p.h"
41 #include "qdeclarativegeomaptype_p.h"
42 #include "qgeomappingmanager_p.h"
43 #include "qgeocameracapabilities_p.h"
44 #include "qgeomap_p.h"
45 #include "qdeclarativegeomapparameter_p.h"
46 #include "qgeomapobject_p.h"
47 #include <QtPositioning/QGeoCircle>
48 #include <QtPositioning/QGeoRectangle>
49 #include <QtPositioning/QGeoPath>
50 #include <QtPositioning/QGeoPolygon>
51 #include <QtQuick/QQuickWindow>
52 #include <QtQuick/QSGRectangleNode>
53 #include <QtQuick/private/qquickwindow_p.h>
54 #include <QtQml/qqmlinfo.h>
55 #include <QtQuick/private/qquickitem_p.h>
56 #include <cmath>
57 
58 #ifndef M_PI
59 #define M_PI 3.141592653589793238463
60 #endif
61 
62 
63 QT_BEGIN_NAMESPACE
64 
sanitizeBearing(qreal bearing)65 static qreal sanitizeBearing(qreal bearing)
66 {
67     bearing = std::fmod(bearing, qreal(360.0));
68     if (bearing < 0.0)
69         bearing += 360.0;
70 
71     return bearing;
72 }
73 
74 /*!
75     \qmltype Map
76     \instantiates QDeclarativeGeoMap
77     \inqmlmodule QtLocation
78     \ingroup qml-QtLocation5-maps
79     \since QtLocation 5.0
80 
81     \brief The Map type displays a map.
82 
83     The Map type is used to display a map or image of the Earth, with
84     the capability to also display interactive objects tied to the map's
85     surface.
86 
87     There are a variety of different ways to visualize the Earth's surface
88     in a 2-dimensional manner, but all of them involve some kind of projection:
89     a mathematical relationship between the 3D coordinates (latitude, longitude
90     and altitude) and 2D coordinates (X and Y in pixels) on the screen.
91 
92     Different sources of map data can use different projections, and from the
93     point of view of the Map type, we treat these as one replaceable unit:
94     the Map plugin. A Map plugin consists of a data source, as well as all other
95     details needed to display its data on-screen.
96 
97     The current Map plugin in use is contained in the \l plugin property of
98     the Map item. In order to display any image in a Map item, you will need
99     to set this property. See the \l Plugin type for a description of how
100     to retrieve an appropriate plugin for use.
101 
102     The geographic region displayed in the Map item is referred to as its
103     viewport, and this is defined by the properties \l center, and
104     \l zoomLevel. The \l center property contains a \l {coordinate}
105     specifying the center of the viewport, while \l zoomLevel controls the scale of the
106     map. See each of these properties for further details about their values.
107 
108     When the map is displayed, each possible geographic coordinate that is
109     visible will map to some pixel X and Y coordinate on the screen. To perform
110     conversions between these two, Map provides the \l toCoordinate and
111     \l fromCoordinate functions, which are of general utility.
112 
113     \section2 Map Objects
114 
115     Map related objects can be declared within the body of a Map object in Qt Quick and will
116     automatically appear on the Map. To add an object programmatically, first be
117     sure it is created with the Map as its parent (for example in an argument to
118     Component::createObject).
119     Then call the \l addMapItem method on the Map, if the type of this object is one of
120     \l MapCircle, \l MapRectangle, \l MapPolyline, \l MapPolygon, \l MapRoute or \l MapQuickItem.
121     A corresponding \l removeMapItem method also exists to do the opposite and
122     remove any of the above types of map objects from the Map.
123 
124     Moving Map objects around, resizing them or changing their shape normally
125     does not involve any special interaction with Map itself -- changing these
126     properties in a map object will automatically update the display.
127 
128     \section2 Interaction
129 
130     The Map type includes support for pinch and flick gestures to control
131     zooming and panning. These are enabled by default, and available at any
132     time by using the \l gesture object. The actual GestureArea is constructed
133     specially at startup and cannot be replaced or destroyed. Its properties
134     can be altered, however, to control its behavior.
135 
136     \section2 Performance
137 
138     Maps are rendered using OpenGL (ES) and the Qt Scene Graph stack, and as
139     a result perform quite well where GL accelerated hardware is available.
140 
141     For "online" Map plugins, network bandwidth and latency can be major
142     contributors to the user's perception of performance. Extensive caching is
143     performed to mitigate this, but such mitigation is not always perfect. For
144     "offline" plugins, the time spent retrieving the stored geographic data
145     and rendering the basic map features can often play a dominant role. Some
146     offline plugins may use hardware acceleration themselves to (partially)
147     avert this.
148 
149     In general, large and complex Map items such as polygons and polylines with
150     large numbers of vertices can have an adverse effect on UI performance.
151     Further, more detailed notes on this are in the documentation for each
152     map item type.
153 
154     \section2 Example Usage
155 
156     The following snippet shows a simple Map and the necessary Plugin type
157     to use it. The map is centered over Oslo, Norway, with zoom level 14.
158 
159     \quotefromfile minimal_map/main.qml
160     \skipto import
161     \printuntil }
162     \printline }
163     \skipto Map
164     \printuntil }
165     \printline }
166 
167     \image minimal_map.png
168 */
169 
170 /*!
171     \qmlsignal QtLocation::Map::copyrightLinkActivated(string link)
172 
173     This signal is emitted when the user clicks on a \a link in the copyright notice. The
174     application should open the link in a browser or display its contents to the user.
175 */
176 
QDeclarativeGeoMap(QQuickItem * parent)177 QDeclarativeGeoMap::QDeclarativeGeoMap(QQuickItem *parent)
178         : QQuickItem(parent),
179         m_plugin(0),
180         m_mappingManager(0),
181         m_activeMapType(0),
182         m_gestureArea(new QQuickGeoMapGestureArea(this)),
183         m_map(0),
184         m_error(QGeoServiceProvider::NoError),
185         m_color(QColor::fromRgbF(0.9, 0.9, 0.9)),
186         m_componentCompleted(false),
187         m_pendingFitViewport(false),
188         m_copyrightsVisible(true),
189         m_maximumViewportLatitude(0.0),
190         m_initialized(false),
191         m_userMinimumZoomLevel(qQNaN()),
192         m_userMaximumZoomLevel(qQNaN()),
193         m_userMinimumTilt(qQNaN()),
194         m_userMaximumTilt(qQNaN()),
195         m_userMinimumFieldOfView(qQNaN()),
196         m_userMaximumFieldOfView(qQNaN())
197 {
198     setAcceptHoverEvents(false);
199     setAcceptedMouseButtons(Qt::LeftButton);
200     setFlags(QQuickItem::ItemHasContents | QQuickItem::ItemClipsChildrenToShape);
201     setFiltersChildMouseEvents(true); // needed for childMouseEventFilter to work.
202 
203     m_activeMapType = new QDeclarativeGeoMapType(QGeoMapType(QGeoMapType::NoMap,
204                                                              tr("No Map"),
205                                                              tr("No Map"),
206                                                              false, false,
207                                                              0,
208                                                              QByteArrayLiteral(""),
209                                                              QGeoCameraCapabilities()), this);
210     m_cameraData.setCenter(QGeoCoordinate(51.5073,-0.1277)); //London city center
211     m_cameraData.setZoomLevel(8.0);
212 
213     m_cameraCapabilities.setTileSize(256);
214     m_cameraCapabilities.setSupportsBearing(true);
215     m_cameraCapabilities.setSupportsTilting(true);
216     m_cameraCapabilities.setMinimumZoomLevel(0);
217     m_cameraCapabilities.setMaximumZoomLevel(30);
218     m_cameraCapabilities.setMinimumTilt(0);
219     m_cameraCapabilities.setMaximumTilt(89.5);
220     m_cameraCapabilities.setMinimumFieldOfView(1);
221     m_cameraCapabilities.setMaximumFieldOfView(179);
222 
223     m_minimumTilt = m_cameraCapabilities.minimumTilt();
224     m_maximumTilt = m_cameraCapabilities.maximumTilt();
225     m_minimumFieldOfView = m_cameraCapabilities.minimumFieldOfView();
226     m_maximumFieldOfView = m_cameraCapabilities.maximumFieldOfView();
227 }
228 
~QDeclarativeGeoMap()229 QDeclarativeGeoMap::~QDeclarativeGeoMap()
230 {
231     // Removing map parameters and map items from m_map
232     if (m_map) {
233         m_map->clearParameters();
234         m_map->clearMapItems();
235     }
236 
237     // Remove the items from the map, making them deletable.
238     // Go in the same order as in removeMapChild: views, groups, then items
239     if (!m_mapViews.isEmpty()) {
240         const auto mapViews = m_mapViews;
241         for (QDeclarativeGeoMapItemView *v : mapViews) { // so that removeMapItemView_real can safely modify m_mapViews;
242             if (!v)
243                 continue;
244 
245             QQuickItem *parent = v->parentItem();
246             QDeclarativeGeoMapItemGroup *group = qobject_cast<QDeclarativeGeoMapItemGroup *>(parent);
247             if (group)
248                 continue; // Ignore non-top-level MIVs. They will be recursively processed.
249                           // Identify them as being parented by a MapItemGroup.
250 
251             removeMapItemView_real(v);
252         }
253     }
254 
255     if (!m_mapItemGroups.isEmpty()) {
256         const auto mapGroups = m_mapItemGroups;
257         for (QDeclarativeGeoMapItemGroup *g : mapGroups) {
258             if (!g)
259                 continue;
260 
261             QQuickItem *parent =g->parentItem();
262             QDeclarativeGeoMapItemGroup *group = qobject_cast<QDeclarativeGeoMapItemGroup *>(parent);
263             if (group)
264                 continue; // Ignore non-top-level Groups. They will be recursively processed.
265                           // Identify them as being parented by a MapItemGroup.
266 
267             removeMapItemGroup_real(g);
268         }
269     }
270 
271     // remove any remaining map items associations
272     const auto mapItems = m_mapItems;
273     for (auto mi: mapItems)
274         removeMapItem_real(mi.data());
275 
276     if (m_copyrights.data())
277         delete m_copyrights.data();
278     m_copyrights.clear();
279 
280     for (auto obj: qAsConst(m_pendingMapObjects))
281         obj->setMap(nullptr); // worst case: going to be setMap(nullptr)'d twice
282 
283     delete m_map; // map objects get reset here
284 }
285 
findMapType(const QList<QDeclarativeGeoMapType * > & types,const QGeoMapType & type)286 static QDeclarativeGeoMapType *findMapType(const QList<QDeclarativeGeoMapType *> &types, const QGeoMapType &type)
287 {
288     for (int i = 0; i < types.size(); ++i)
289         if (types[i]->mapType() == type)
290             return types[i];
291     return nullptr;
292 }
293 
onSupportedMapTypesChanged()294 void QDeclarativeGeoMap::onSupportedMapTypesChanged()
295 {
296     QList<QDeclarativeGeoMapType *> supportedMapTypes;
297     QList<QGeoMapType> types = m_mappingManager->supportedMapTypes();
298     for (int i = 0; i < types.size(); ++i) {
299         // types that are present and get removed will be deleted at QObject destruction
300         QDeclarativeGeoMapType *type = findMapType(m_supportedMapTypes, types[i]);
301         if (!type)
302             type = new QDeclarativeGeoMapType(types[i], this);
303         supportedMapTypes.append(type);
304     }
305     m_supportedMapTypes.swap(supportedMapTypes);
306     if (m_supportedMapTypes.isEmpty()) {
307         m_map->setActiveMapType(QGeoMapType()); // no supported map types: setting an invalid one
308     } else {
309         bool hasMapType = false;
310         foreach (QDeclarativeGeoMapType *declarativeType, m_supportedMapTypes) {
311             if (declarativeType->mapType() == m_map->activeMapType())
312                 hasMapType = true;
313         }
314         if (!hasMapType) {
315             QDeclarativeGeoMapType *type = m_supportedMapTypes.at(0);
316             m_activeMapType = type;
317             m_map->setActiveMapType(type->mapType());
318         }
319     }
320 
321     emit supportedMapTypesChanged();
322 }
323 
setError(QGeoServiceProvider::Error error,const QString & errorString)324 void QDeclarativeGeoMap::setError(QGeoServiceProvider::Error error, const QString &errorString)
325 {
326     if (m_error == error && m_errorString == errorString)
327         return;
328     m_error = error;
329     m_errorString = errorString;
330     emit errorChanged();
331 }
332 
333 /*!
334     \internal
335     Called when the mapping manager is initialized AND the declarative element has a valid size > 0
336 */
initialize()337 void QDeclarativeGeoMap::initialize()
338 {
339     // try to keep change signals in the end
340     bool visibleAreaHasChanged = false;
341 
342     QGeoCoordinate center = m_cameraData.center();
343 
344     if (!qIsFinite(m_userMinimumZoomLevel))
345         setMinimumZoomLevel(m_map->minimumZoom(), false);
346     else
347         setMinimumZoomLevel(qMax<qreal>(m_map->minimumZoom(), m_userMinimumZoomLevel), false);
348 
349     double bearing = m_cameraData.bearing();
350     double tilt = m_cameraData.tilt();
351     double fov = m_cameraData.fieldOfView(); // Must be 45.0
352     QGeoCameraData cameraData = m_cameraData;
353 
354     if (!m_cameraCapabilities.supportsBearing() && bearing != 0.0)
355         cameraData.setBearing(0);
356 
357     if (!m_cameraCapabilities.supportsTilting() && tilt != 0.0)
358         cameraData.setTilt(0);
359 
360     m_map->setVisibleArea(m_visibleArea);
361     if (m_map->visibleArea() != m_visibleArea)
362         visibleAreaHasChanged = true;
363 
364     cameraData.setFieldOfView(qBound(m_cameraCapabilities.minimumFieldOfView(),
365                                        fov,
366                                        m_cameraCapabilities.maximumFieldOfView()));
367 
368     // set latitude boundary check
369     m_maximumViewportLatitude = m_map->maximumCenterLatitudeAtZoom(cameraData);
370     m_minimumViewportLatitude = m_map->minimumCenterLatitudeAtZoom(cameraData);
371 
372     center.setLatitude(qBound(m_minimumViewportLatitude, center.latitude(), m_maximumViewportLatitude));
373     cameraData.setCenter(center);
374 
375     connect(m_map.data(), &QGeoMap::cameraDataChanged,
376             this,  &QDeclarativeGeoMap::onCameraDataChanged);
377     m_map->setCameraData(cameraData); // This normally triggers property changed signals.
378                                       // BUT not in this case, since m_cameraData is already == cameraData.
379                                       // So, emit visibleRegionChanged() separately, as
380                                       // the effective visible region becomes available only now.
381 
382     for (auto obj : qAsConst(m_pendingMapObjects))
383         obj->setMap(m_map);
384 
385     m_initialized = true;
386 
387     if (visibleAreaHasChanged)
388         emit visibleAreaChanged();
389     connect(m_map.data(), &QGeoMap::visibleAreaChanged, this, &QDeclarativeGeoMap::visibleAreaChanged);
390 
391     emit mapReadyChanged(true);
392     emit visibleRegionChanged();
393 
394     if (m_copyrights) // To not update during initialize()
395          update();
396 }
397 
398 /*!
399     \internal
400 */
pluginReady()401 void QDeclarativeGeoMap::pluginReady()
402 {
403     QGeoServiceProvider *provider = m_plugin->sharedGeoServiceProvider();
404     m_mappingManager = provider->mappingManager();
405 
406     if (provider->mappingError() != QGeoServiceProvider::NoError) {
407         setError(provider->mappingError(), provider->mappingErrorString());
408         return;
409     }
410 
411     if (!m_mappingManager) {
412         //TODO Should really be EngineNotSetError (see QML GeoCodeModel)
413         setError(QGeoServiceProvider::NotSupportedError, tr("Plugin does not support mapping."));
414         return;
415     }
416 
417     if (!m_mappingManager->isInitialized())
418         connect(m_mappingManager, SIGNAL(initialized()), this, SLOT(mappingManagerInitialized()));
419     else
420         mappingManagerInitialized();
421 
422     // make sure this is only called once
423     disconnect(this, SLOT(pluginReady()));
424 }
425 
426 /*!
427     \internal
428 */
componentComplete()429 void QDeclarativeGeoMap::componentComplete()
430 {
431     m_componentCompleted = true;
432     populateParameters();
433     populateMap();
434     QQuickItem::componentComplete();
435 }
436 
437 /*!
438     \qmlproperty MapGestureArea QtLocation::Map::gesture
439 
440     Contains the MapGestureArea created with the Map. This covers pan, flick and pinch gestures.
441     Use \c{gesture.enabled: true} to enable basic gestures, or see \l{MapGestureArea} for
442     further details.
443 */
444 
gesture()445 QQuickGeoMapGestureArea *QDeclarativeGeoMap::gesture()
446 {
447     return m_gestureArea;
448 }
449 
450 /*!
451     \internal
452 
453     This may happen before mappingManagerInitialized()
454 */
populateMap()455 void QDeclarativeGeoMap::populateMap()
456 {
457     QSet<QObject *> kids(children().cbegin(), children().cend());
458     const QList<QQuickItem *> quickKids = childItems();
459     for (QQuickItem *ite: quickKids)
460         kids.insert(ite);
461 
462     for (QObject *k : qAsConst(kids)) {
463         addMapChild(k);
464     }
465 }
466 
populateParameters()467 void QDeclarativeGeoMap::populateParameters()
468 {
469     QObjectList kids = children();
470     QList<QQuickItem *> quickKids = childItems();
471     for (int i = 0; i < quickKids.count(); ++i)
472         kids.append(quickKids.at(i));
473     for (int i = 0; i < kids.size(); ++i) {
474         QDeclarativeGeoMapParameter *mapParameter = qobject_cast<QDeclarativeGeoMapParameter *>(kids.at(i));
475         if (mapParameter)
476             addMapParameter(mapParameter);
477     }
478 }
479 
480 /*!
481     \internal
482 */
setupMapView(QDeclarativeGeoMapItemView * view)483 void QDeclarativeGeoMap::setupMapView(QDeclarativeGeoMapItemView *view)
484 {
485     view->setMap(this);
486 }
487 
488 /*!
489  * \internal
490  */
updatePaintNode(QSGNode * oldNode,UpdatePaintNodeData *)491 QSGNode *QDeclarativeGeoMap::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
492 {
493     if (!m_map) {
494         delete oldNode;
495         return 0;
496     }
497 
498     QSGRectangleNode *root = static_cast<QSGRectangleNode *>(oldNode);
499     if (!root)
500         root = window()->createRectangleNode();
501 
502     root->setRect(boundingRect());
503     root->setColor(m_color);
504 
505     QSGNode *content = root->childCount() ? root->firstChild() : 0;
506     content = m_map->updateSceneGraph(content, window());
507     if (content && root->childCount() == 0)
508         root->appendChildNode(content);
509 
510     return root;
511 }
512 
513 /*!
514     \qmlproperty Plugin QtLocation::Map::plugin
515 
516     This property holds the plugin which provides the mapping functionality.
517 
518     This is a write-once property. Once the map has a plugin associated with
519     it, any attempted modifications of the plugin will be ignored.
520 */
521 
setPlugin(QDeclarativeGeoServiceProvider * plugin)522 void QDeclarativeGeoMap::setPlugin(QDeclarativeGeoServiceProvider *plugin)
523 {
524     if (m_plugin) {
525         qmlWarning(this) << QStringLiteral("Plugin is a write-once property, and cannot be set again.");
526         return;
527     }
528     m_plugin = plugin;
529     emit pluginChanged(m_plugin);
530 
531     if (m_plugin->isAttached()) {
532         pluginReady();
533     } else {
534         connect(m_plugin, SIGNAL(attached()),
535                 this, SLOT(pluginReady()));
536     }
537 }
538 
539 /*!
540     \internal
541 */
onCameraCapabilitiesChanged(const QGeoCameraCapabilities & oldCameraCapabilities)542 void QDeclarativeGeoMap::onCameraCapabilitiesChanged(const QGeoCameraCapabilities &oldCameraCapabilities)
543 {
544     if (m_map->cameraCapabilities() == oldCameraCapabilities)
545         return;
546     m_cameraCapabilities = m_map->cameraCapabilities();
547 
548     //The zoom level limits are only restricted by the plugins values, if the user has set a more
549     //strict zoom level limit before initialization nothing is done here.
550     //minimum zoom level might be changed to limit gray bundaries
551     //This code assumes that plugins' maximum zoom level will never exceed 30.0
552     if (m_cameraCapabilities.maximumZoomLevelAt256() < m_gestureArea->maximumZoomLevel()) {
553         setMaximumZoomLevel(m_cameraCapabilities.maximumZoomLevelAt256(), false);
554     } else if (m_cameraCapabilities.maximumZoomLevelAt256() > m_gestureArea->maximumZoomLevel()) {
555         if (!qIsFinite(m_userMaximumZoomLevel)) {
556             // If the user didn't set anything
557             setMaximumZoomLevel(m_cameraCapabilities.maximumZoomLevelAt256(), false);
558         } else {  // Try to set what the user requested
559             // Else if the user set something larger, but that got clamped by the previous camera caps
560             setMaximumZoomLevel(qMin<qreal>(m_cameraCapabilities.maximumZoomLevelAt256(),
561                                             m_userMaximumZoomLevel), false);
562         }
563     }
564 
565     if (m_cameraCapabilities.minimumZoomLevelAt256() > m_gestureArea->minimumZoomLevel()) {
566         setMinimumZoomLevel(m_cameraCapabilities.minimumZoomLevelAt256(), false);
567     } else if (m_cameraCapabilities.minimumZoomLevelAt256() < m_gestureArea->minimumZoomLevel()) {
568         if (!qIsFinite(m_userMinimumZoomLevel)) {
569             // If the user didn't set anything, trying to set the new caps.
570             setMinimumZoomLevel(m_cameraCapabilities.minimumZoomLevelAt256(), false);
571         } else {  // Try to set what the user requested
572             // Else if the user set a minimum, m_gestureArea->minimumZoomLevel() might be larger
573             // because of different reasons. Resetting it, as if it ends to be the same,
574             // no signal will be emitted.
575             setMinimumZoomLevel(qMax<qreal>(m_cameraCapabilities.minimumZoomLevelAt256(),
576                                             m_userMinimumZoomLevel), false);
577         }
578     }
579 
580     // Tilt
581     if (m_cameraCapabilities.maximumTilt() < m_maximumTilt) {
582         setMaximumTilt(m_cameraCapabilities.maximumTilt(), false);
583     } else if (m_cameraCapabilities.maximumTilt() > m_maximumTilt) {
584         if (!qIsFinite(m_userMaximumTilt))
585             setMaximumTilt(m_cameraCapabilities.maximumTilt(), false);
586         else // Try to set what the user requested
587             setMaximumTilt(qMin<qreal>(m_cameraCapabilities.maximumTilt(), m_userMaximumTilt), false);
588     }
589 
590     if (m_cameraCapabilities.minimumTilt() > m_minimumTilt) {
591         setMinimumTilt(m_cameraCapabilities.minimumTilt(), false);
592     } else if (m_cameraCapabilities.minimumTilt() < m_minimumTilt) {
593         if (!qIsFinite(m_userMinimumTilt))
594             setMinimumTilt(m_cameraCapabilities.minimumTilt(), false);
595         else // Try to set what the user requested
596             setMinimumTilt(qMax<qreal>(m_cameraCapabilities.minimumTilt(), m_userMinimumTilt), false);
597     }
598 
599     // FoV
600     if (m_cameraCapabilities.maximumFieldOfView() < m_maximumFieldOfView) {
601         setMaximumFieldOfView(m_cameraCapabilities.maximumFieldOfView(), false);
602     } else if (m_cameraCapabilities.maximumFieldOfView() > m_maximumFieldOfView) {
603         if (!qIsFinite(m_userMaximumFieldOfView))
604             setMaximumFieldOfView(m_cameraCapabilities.maximumFieldOfView(), false);
605         else // Try to set what the user requested
606             setMaximumFieldOfView(qMin<qreal>(m_cameraCapabilities.maximumFieldOfView(), m_userMaximumFieldOfView), false);
607     }
608 
609     if (m_cameraCapabilities.minimumFieldOfView() > m_minimumFieldOfView) {
610         setMinimumFieldOfView(m_cameraCapabilities.minimumFieldOfView(), false);
611     } else if (m_cameraCapabilities.minimumFieldOfView() < m_minimumFieldOfView) {
612         if (!qIsFinite(m_userMinimumFieldOfView))
613             setMinimumFieldOfView(m_cameraCapabilities.minimumFieldOfView(), false);
614         else // Try to set what the user requested
615             setMinimumFieldOfView(qMax<qreal>(m_cameraCapabilities.minimumFieldOfView(), m_userMinimumFieldOfView), false);
616     }
617 }
618 
619 /*!
620     \internal
621     this function will only be ever called once
622 */
mappingManagerInitialized()623 void QDeclarativeGeoMap::mappingManagerInitialized()
624 {
625     m_map = m_mappingManager->createMap(this);
626 
627     if (!m_map)
628         return;
629 
630     // Any map items that were added before the plugin was ready
631     // need to have setMap called again
632     for (const QPointer<QDeclarativeGeoMapItemBase> &item : qAsConst(m_mapItems)) {
633         if (item) {
634             item->setMap(this, m_map);
635             m_map->addMapItem(item.data()); // m_map filters out what is not supported.
636         }
637     }
638 
639     /* COPY NOTICE SETUP */
640     m_copyrights = new QDeclarativeGeoMapCopyrightNotice(this);
641     m_copyrights->setCopyrightsZ(m_maxChildZ + 1);
642     m_copyrights->setCopyrightsVisible(m_copyrightsVisible);
643     m_copyrights->setMapSource(this);
644 
645     m_gestureArea->setMap(m_map);
646 
647     QList<QGeoMapType> types = m_mappingManager->supportedMapTypes();
648     for (int i = 0; i < types.size(); ++i) {
649         QDeclarativeGeoMapType *type = new QDeclarativeGeoMapType(types[i], this);
650         m_supportedMapTypes.append(type);
651     }
652 
653     if (m_activeMapType && m_plugin->name().toLatin1() == m_activeMapType->mapType().pluginName()) {
654         m_map->setActiveMapType(m_activeMapType->mapType());
655     } else {
656         if (m_activeMapType)
657             m_activeMapType->deleteLater();
658 
659         if (!m_supportedMapTypes.isEmpty()) {
660                 m_activeMapType = m_supportedMapTypes.at(0);
661                 m_map->setActiveMapType(m_activeMapType->mapType());
662         } else {
663             m_activeMapType = new QDeclarativeGeoMapType(QGeoMapType(QGeoMapType::NoMap,
664                                                                      tr("No Map"),
665                                                                      tr("No Map"),
666                                                                      false,
667                                                                      false,
668                                                                      0,
669                                                                      QByteArrayLiteral(""),
670                                                                      QGeoCameraCapabilities()), this);
671         }
672     }
673 
674     // Update camera capabilities
675     onCameraCapabilitiesChanged(m_cameraCapabilities);
676 
677     // Map tiles are built in this call. m_map->minimumZoom() becomes operational
678     // after this has been called at least once, after creation.
679     // However, getting into the following block may fire a copyrightsChanged that would get lost,
680     // as the connections are set up after.
681     QString copyrightString;
682     QImage copyrightImage;
683     if (!m_initialized && width() > 0 && height() > 0) {
684         QMetaObject::Connection copyrightStringCatcherConnection =
685                 connect(m_map.data(),
686                         QOverload<const QString &>::of(&QGeoMap::copyrightsChanged),
687                         [&copyrightString](const QString &copy){ copyrightString = copy; });
688         QMetaObject::Connection copyrightImageCatcherConnection =
689                 connect(m_map.data(),
690                         QOverload<const QImage &>::of(&QGeoMap::copyrightsChanged),
691                         [&copyrightImage](const QImage &copy){ copyrightImage = copy; });
692         m_map->setViewportSize(QSize(width(), height()));
693         initialize(); // This emits the caught signals above
694         QObject::disconnect(copyrightStringCatcherConnection);
695         QObject::disconnect(copyrightImageCatcherConnection);
696     }
697 
698 
699     /* COPYRIGHT SIGNALS REWIRING */
700     connect(m_map.data(), SIGNAL(copyrightsChanged(QImage)),
701             this,  SIGNAL(copyrightsChanged(QImage)));
702     connect(m_map.data(), SIGNAL(copyrightsChanged(QString)),
703             this,  SIGNAL(copyrightsChanged(QString)));
704     if (!copyrightString.isEmpty())
705         emit m_map->copyrightsChanged(copyrightString);
706     else if (!copyrightImage.isNull())
707         emit m_map->copyrightsChanged(copyrightImage);
708 
709 
710     connect(window(), &QQuickWindow::beforeSynchronizing, this, &QDeclarativeGeoMap::updateItemToWindowTransform, Qt::DirectConnection);
711     connect(m_map.data(), &QGeoMap::sgNodeChanged, this, &QDeclarativeGeoMap::onSGNodeChanged);
712     connect(m_map.data(), &QGeoMap::cameraCapabilitiesChanged, this, &QDeclarativeGeoMap::onCameraCapabilitiesChanged);
713 
714     // This prefetches a buffer around the map
715     m_map->prefetchData();
716 
717     connect(m_mappingManager, SIGNAL(supportedMapTypesChanged()), this, SLOT(onSupportedMapTypesChanged()));
718     emit minimumZoomLevelChanged();
719     emit maximumZoomLevelChanged();
720     emit supportedMapTypesChanged();
721     emit activeMapTypeChanged();
722 
723     // Any map item groups that were added before the plugin was ready
724     // DO NOT need to have setMap called again on their children map items
725     // because they have been added to m_mapItems, which is processed right above.
726 
727 
728     // All map parameters that were added before the plugin was ready
729     // need to be added to m_map
730     for (QDeclarativeGeoMapParameter *p : qAsConst(m_mapParameters))
731         m_map->addParameter(p);
732 
733     if (m_initialized)
734         update();
735 }
736 
737 /*!
738     \internal
739 */
plugin() const740 QDeclarativeGeoServiceProvider *QDeclarativeGeoMap::plugin() const
741 {
742     return m_plugin;
743 }
744 
745 /*!
746     \internal
747     Sets the gesture areas minimum zoom level. If the camera capabilities
748     has been set this method honors the boundaries set by it.
749     The minimum zoom level will also have a lower bound dependent on the size
750     of the canvas, effectively preventing to display out of bounds areas.
751 */
setMinimumZoomLevel(qreal minimumZoomLevel,bool userSet)752 void QDeclarativeGeoMap::setMinimumZoomLevel(qreal minimumZoomLevel, bool userSet)
753 {
754     if (minimumZoomLevel >= 0) {
755         qreal oldUserMinimumZoomLevel = m_userMinimumZoomLevel;
756         if (userSet)
757             m_userMinimumZoomLevel = minimumZoomLevel;
758         qreal oldMinimumZoomLevel = this->minimumZoomLevel();
759 
760         minimumZoomLevel = qBound(qreal(m_cameraCapabilities.minimumZoomLevelAt256()), minimumZoomLevel, maximumZoomLevel());
761         if (m_map)
762              minimumZoomLevel = qMax<qreal>(minimumZoomLevel, m_map->minimumZoom());
763 
764         // minimumZoomLevel is, at this point, the implicit minimum zoom level
765         m_gestureArea->setMinimumZoomLevel(minimumZoomLevel);
766 
767         if (zoomLevel() < minimumZoomLevel && (m_gestureArea->enabled() || !m_cameraCapabilities.overzoomEnabled()))
768             setZoomLevel(minimumZoomLevel);
769 
770         if (qIsNaN(m_userMinimumZoomLevel) && oldMinimumZoomLevel != minimumZoomLevel)
771             emit minimumZoomLevelChanged();
772         else if (userSet && oldUserMinimumZoomLevel != m_userMinimumZoomLevel)
773             emit minimumZoomLevelChanged();
774     }
775 }
776 
777 /*!
778     \qmlproperty real QtLocation::Map::minimumZoomLevel
779 
780     This property holds the minimum valid zoom level for the map.
781 
782     The minimum zoom level defined by the \l plugin used is a lower bound for
783     this property. However, the returned value is also canvas-size-dependent, and
784     can be higher than the user-specified value, or than the minimum zoom level
785     defined by the plugin used, to prevent the map from being smaller than the
786     viewport in either dimension.
787 
788     If the \l plugin property is not set or the plugin does not support mapping, this property is \c 0.
789 */
790 
minimumZoomLevel() const791 qreal QDeclarativeGeoMap::minimumZoomLevel() const
792 {
793     if (!qIsNaN(m_userMinimumZoomLevel))
794         return m_userMinimumZoomLevel;
795     else
796         return m_gestureArea->minimumZoomLevel();
797 }
798 
799 /*!
800     \internal
801 */
implicitMinimumZoomLevel() const802 qreal QDeclarativeGeoMap::implicitMinimumZoomLevel() const
803 {
804     return m_gestureArea->minimumZoomLevel();
805 }
806 
807 /*!
808     \internal
809 */
effectiveMinimumZoomLevel() const810 qreal QDeclarativeGeoMap::effectiveMinimumZoomLevel() const
811 {
812     return qMax<qreal>(minimumZoomLevel(), implicitMinimumZoomLevel());
813 }
814 
815 /*!
816     \internal
817     Sets the gesture areas maximum zoom level. If the camera capabilities
818     has been set this method honors the boundaries set by it.
819 */
setMaximumZoomLevel(qreal maximumZoomLevel,bool userSet)820 void QDeclarativeGeoMap::setMaximumZoomLevel(qreal maximumZoomLevel, bool userSet)
821 {
822     if (maximumZoomLevel >= 0) {
823         if (userSet)
824             m_userMaximumZoomLevel = maximumZoomLevel;
825         qreal oldMaximumZoomLevel = this->maximumZoomLevel();
826 
827         maximumZoomLevel = qBound(minimumZoomLevel(), maximumZoomLevel, qreal(m_cameraCapabilities.maximumZoomLevelAt256()));
828 
829         m_gestureArea->setMaximumZoomLevel(maximumZoomLevel);
830 
831         if (zoomLevel() > maximumZoomLevel && (m_gestureArea->enabled() || !m_cameraCapabilities.overzoomEnabled()))
832             setZoomLevel(maximumZoomLevel);
833 
834         if (oldMaximumZoomLevel != maximumZoomLevel)
835             emit maximumZoomLevelChanged();
836     }
837 }
838 
839 /*!
840     \qmlproperty real QtLocation::Map::maximumZoomLevel
841 
842     This property holds the maximum valid zoom level for the map.
843 
844     The maximum zoom level is defined by the \l plugin used.
845     If the \l plugin property is not set or the plugin does not support mapping, this property is \c 30.
846 */
847 
maximumZoomLevel() const848 qreal QDeclarativeGeoMap::maximumZoomLevel() const
849 {
850     return m_gestureArea->maximumZoomLevel();
851 }
852 
853 /*!
854     \qmlproperty real QtLocation::Map::zoomLevel
855 
856     This property holds the zoom level for the map.
857 
858     Larger values for the zoom level provide more detail. Zoom levels
859     are always non-negative. The default value is 8.0. Depending on the plugin in use,
860     values outside the [minimumZoomLevel, maximumZoomLevel] range, which represent the range for which
861     tiles are available, may be accepted, or clamped.
862 */
setZoomLevel(qreal zoomLevel)863 void QDeclarativeGeoMap::setZoomLevel(qreal zoomLevel)
864 {
865     return setZoomLevel(zoomLevel, m_cameraCapabilities.overzoomEnabled());
866 }
867 
868 /*!
869     \internal
870 
871     Sets the zoom level.
872     Larger values for the zoom level provide more detail. Zoom levels
873     are always non-negative. The default value is 8.0. Values outside the
874     [minimumZoomLevel, maximumZoomLevel] range, which represent the range for which
875     tiles are available, can be accepted or clamped by setting the overzoom argument
876     to true or false respectively.
877 */
setZoomLevel(qreal zoomLevel,bool overzoom)878 void QDeclarativeGeoMap::setZoomLevel(qreal zoomLevel, bool overzoom)
879 {
880     if (zoomLevel < 0)
881         return;
882 
883     if (m_initialized) {
884         QGeoCameraData cameraData = m_map->cameraData();
885         if (cameraData.zoomLevel() == zoomLevel)
886             return;
887 
888         cameraData.setZoomLevel(qBound<qreal>(overzoom ? m_map->minimumZoom() : effectiveMinimumZoomLevel(),
889                                                 zoomLevel,
890                                                 overzoom ? 30 : maximumZoomLevel()));
891         m_maximumViewportLatitude = m_map->maximumCenterLatitudeAtZoom(cameraData);
892         m_minimumViewportLatitude = m_map->minimumCenterLatitudeAtZoom(cameraData);
893         QGeoCoordinate coord = cameraData.center();
894         coord.setLatitude(qBound(m_minimumViewportLatitude, coord.latitude(), m_maximumViewportLatitude));
895         cameraData.setCenter(coord);
896         m_map->setCameraData(cameraData);
897     } else {
898         const bool zlHasChanged = zoomLevel != m_cameraData.zoomLevel();
899         m_cameraData.setZoomLevel(zoomLevel);
900         if (zlHasChanged) {
901             emit zoomLevelChanged(zoomLevel);
902             // do not emit visibleRegionChanged() here, because, if the map isn't initialized,
903             // the getter won't return anything updated
904         }
905     }
906 }
907 
addMapChild(QObject * child)908 bool QDeclarativeGeoMap::addMapChild(QObject *child)
909 {
910     // dispatch items appropriately
911     QDeclarativeGeoMapItemView *mapView = qobject_cast<QDeclarativeGeoMapItemView *>(child);
912     if (mapView)
913         return addMapItemView_real(mapView);
914 
915     QDeclarativeGeoMapItemGroup *itemGroup = qobject_cast<QDeclarativeGeoMapItemGroup *>(child);
916     if (itemGroup) // addMapItemView calls addMapItemGroup
917         return addMapItemGroup_real(itemGroup);
918 
919     QDeclarativeGeoMapItemBase *mapItem = qobject_cast<QDeclarativeGeoMapItemBase *>(child);
920     if (mapItem)
921         return addMapItem_real(mapItem);
922 
923     QGeoMapObject *mapObject = qobject_cast<QGeoMapObject *>(child);
924     if (mapObject)
925         addMapObject(mapObject); // this emits mapObjectsChanged, != mapItemsChanged
926     return false;
927 }
928 
removeMapChild(QObject * child)929 bool QDeclarativeGeoMap::removeMapChild(QObject *child)
930 {
931     // dispatch items appropriately
932     QDeclarativeGeoMapItemView *mapView = qobject_cast<QDeclarativeGeoMapItemView *>(child);
933     if (mapView)
934         return removeMapItemView_real(mapView);
935 
936     QDeclarativeGeoMapItemGroup *itemGroup = qobject_cast<QDeclarativeGeoMapItemGroup *>(child);
937     if (itemGroup) // removeMapItemView calls removeMapItemGroup for itself.
938         return removeMapItemGroup_real(itemGroup);
939 
940     QDeclarativeGeoMapItemBase *mapItem = qobject_cast<QDeclarativeGeoMapItemBase *>(child);
941     if (mapItem)
942         return removeMapItem_real(mapItem);
943 
944     QGeoMapObject *mapObject = qobject_cast<QGeoMapObject *>(child);
945     if (mapObject)
946         removeMapObject(mapObject); // this emits mapObjectsChanged, != mapItemsChanged
947     return false;
948 }
949 
isGroupNested(QDeclarativeGeoMapItemGroup * group)950 bool QDeclarativeGeoMap::isGroupNested(QDeclarativeGeoMapItemGroup *group)
951 {
952     QObject *parent = group->parent();
953     // Nested groups have parent set in parent's componentComplete()
954     // Those instantiated by MapItemView's delegateModel, however, do not,
955     // but have setParentItem set.
956     return qobject_cast<QDeclarativeGeoMapItemGroup *>(parent)
957             || qobject_cast<QDeclarativeGeoMapItemGroup *>(group->parentItem());
958 }
959 
zoomLevel() const960 qreal QDeclarativeGeoMap::zoomLevel() const
961 {
962     if (m_initialized)
963         return m_map->cameraData().zoomLevel();
964     return m_cameraData.zoomLevel();
965 }
966 
967 /*!
968     \qmlproperty real QtLocation::Map::bearing
969 
970     This property holds the bearing for the map.
971     The default value is 0.
972     If the Plugin used for the Map supports bearing, the valid range for this value is between 0 and 360.
973     If the Plugin used for the Map does not support bearing, changing this property will have no effect.
974 
975     \since QtLocation 5.9
976 */
setBearing(qreal bearing)977 void QDeclarativeGeoMap::setBearing(qreal bearing)
978 {
979     bearing = sanitizeBearing(bearing);
980     if (m_initialized) {
981         QGeoCameraData cameraData = m_map->cameraData();
982         cameraData.setBearing(bearing);
983         m_map->setCameraData(cameraData);
984     } else {
985         const bool bearingHasChanged = bearing != m_cameraData.bearing();
986         m_cameraData.setBearing(bearing);
987         if (bearingHasChanged) {
988             emit bearingChanged(bearing);
989             // do not emit visibleRegionChanged() here, because, if the map isn't initialized,
990             // the getter won't return anything updated
991         }
992     }
993 }
994 
995 /*!
996     \qmlmethod void QtLocation::Map::setBearing(real bearing, coordinate coordinate)
997 
998     Sets the bearing for the map to \a bearing, rotating it around \a coordinate.
999     If the Plugin used for the Map supports bearing, the valid range for \a bearing is between 0 and 360.
1000     If the Plugin used for the Map does not support bearing, or if the map is tilted and \a coordinate happens
1001     to be behind the camera, or if the map is not ready (see \l mapReady), calling this method will have no effect.
1002 
1003     The release of this API with Qt 5.10 is a Technology Preview.
1004 
1005     \since 5.10
1006 */
setBearing(qreal bearing,const QGeoCoordinate & coordinate)1007 void QDeclarativeGeoMap::setBearing(qreal bearing, const QGeoCoordinate &coordinate)
1008 {
1009     if (!m_initialized)
1010         return;
1011 
1012     const QGeoCoordinate currentCenter = center();
1013     const qreal currentBearing = QDeclarativeGeoMap::bearing();
1014     bearing = sanitizeBearing(bearing);
1015 
1016     if (!coordinate.isValid()
1017             || !qIsFinite(bearing)
1018             || (coordinate == currentCenter && bearing == currentBearing))
1019         return;
1020 
1021     if (m_map->capabilities() & QGeoMap::SupportsSetBearing)
1022         m_map->setBearing(bearing, coordinate);
1023 }
1024 
bearing() const1025 qreal QDeclarativeGeoMap::bearing() const
1026 {
1027     if (m_initialized)
1028         return m_map->cameraData().bearing();
1029     return m_cameraData.bearing();
1030 }
1031 
1032 /*!
1033     \qmlproperty real QtLocation::Map::tilt
1034 
1035     This property holds the tilt for the map, in degrees.
1036     The default value is 0.
1037     The valid range for this value is [ minimumTilt, maximumTilt ].
1038     If the Plugin used for the Map does not support tilting, changing this property will have no effect.
1039 
1040     \sa minimumTilt, maximumTilt
1041 
1042     \since QtLocation 5.9
1043 */
setTilt(qreal tilt)1044 void QDeclarativeGeoMap::setTilt(qreal tilt)
1045 {
1046     tilt = qBound(minimumTilt(), tilt, maximumTilt());
1047 
1048     if (m_initialized) {
1049         QGeoCameraData cameraData = m_map->cameraData();
1050         cameraData.setTilt(tilt);
1051         m_map->setCameraData(cameraData);
1052     } else {
1053         const bool tiltHasChanged = tilt != m_cameraData.tilt();
1054         m_cameraData.setTilt(tilt);
1055         if (tiltHasChanged) {
1056             emit tiltChanged(tilt);
1057             // do not emit visibleRegionChanged() here, because, if the map isn't initialized,
1058             // the getter won't return anything updated
1059         }
1060     }
1061 }
1062 
tilt() const1063 qreal QDeclarativeGeoMap::tilt() const
1064 {
1065     if (m_initialized)
1066         return m_map->cameraData().tilt();
1067     return m_cameraData.tilt();
1068 }
1069 
setMinimumTilt(qreal minimumTilt,bool userSet)1070 void QDeclarativeGeoMap::setMinimumTilt(qreal minimumTilt, bool userSet)
1071 {
1072     if (minimumTilt >= 0) {
1073         if (userSet)
1074             m_userMinimumTilt = minimumTilt;
1075         qreal oldMinimumTilt = this->minimumTilt();
1076 
1077         m_minimumTilt = qBound<double>(m_cameraCapabilities.minimumTilt(),
1078                                        minimumTilt,
1079                                        m_cameraCapabilities.maximumTilt());
1080 
1081         if (tilt() < m_minimumTilt)
1082             setTilt(m_minimumTilt);
1083 
1084         if (oldMinimumTilt != m_minimumTilt)
1085             emit minimumTiltChanged(m_minimumTilt);
1086     }
1087 }
1088 
1089 /*!
1090     \qmlproperty real QtLocation::Map::fieldOfView
1091 
1092     This property holds the field of view of the camera used to look at the map, in degrees.
1093     If the plugin property of the map is not set, or the plugin does not support mapping, the value is 45 degrees.
1094 
1095     Note that changing this value implicitly changes also the distance between the camera and the map,
1096     so that, at a tilting angle of 0 degrees, the resulting image is identical for any value used for this property.
1097 
1098     For more information about this parameter, consult the Wikipedia articles about \l {https://en.wikipedia.org/wiki/Field_of_view} {Field of view} and
1099     \l {https://en.wikipedia.org/wiki/Angle_of_view} {Angle of view}.
1100 
1101     \sa minimumFieldOfView, maximumFieldOfView
1102 
1103     \since QtLocation 5.9
1104 */
setFieldOfView(qreal fieldOfView)1105 void QDeclarativeGeoMap::setFieldOfView(qreal fieldOfView)
1106 {
1107     fieldOfView = qBound(minimumFieldOfView(), fieldOfView, maximumFieldOfView());
1108 
1109     if (m_initialized) {
1110         QGeoCameraData cameraData = m_map->cameraData();
1111         cameraData.setFieldOfView(fieldOfView);
1112         m_map->setCameraData(cameraData);
1113     } else {
1114         const bool fovChanged = fieldOfView != m_cameraData.fieldOfView();
1115         m_cameraData.setFieldOfView(fieldOfView);
1116         if (fovChanged) {
1117             emit fieldOfViewChanged(fieldOfView);
1118             // do not emit visibleRegionChanged() here, because, if the map isn't initialized,
1119             // the getter won't return anything updated
1120         }
1121     }
1122 }
1123 
fieldOfView() const1124 qreal QDeclarativeGeoMap::fieldOfView() const
1125 {
1126     if (m_initialized)
1127         return m_map->cameraData().fieldOfView();
1128     return m_cameraData.fieldOfView();
1129 }
1130 
setMinimumFieldOfView(qreal minimumFieldOfView,bool userSet)1131 void QDeclarativeGeoMap::setMinimumFieldOfView(qreal minimumFieldOfView, bool userSet)
1132 {
1133     if (minimumFieldOfView > 0 && minimumFieldOfView < 180.0) {
1134         if (userSet)
1135             m_userMinimumFieldOfView = minimumFieldOfView;
1136         qreal oldMinimumFoV = this->minimumFieldOfView();
1137 
1138         m_minimumFieldOfView = qBound<double>(m_cameraCapabilities.minimumFieldOfView(),
1139                                               minimumFieldOfView,
1140                                               m_cameraCapabilities.maximumFieldOfView());
1141 
1142         if (fieldOfView() < m_minimumFieldOfView)
1143             setFieldOfView(m_minimumFieldOfView);
1144 
1145         if (oldMinimumFoV != m_minimumFieldOfView)
1146             emit minimumFieldOfViewChanged(m_minimumFieldOfView);
1147     }
1148 }
1149 
1150 /*!
1151     \qmlproperty real QtLocation::Map::minimumFieldOfView
1152 
1153     This property holds the minimum valid field of view for the map, in degrees.
1154 
1155     The minimum tilt field of view by the \l plugin used is a lower bound for
1156     this property.
1157     If the \l plugin property is not set or the plugin does not support mapping, this property is \c 1.
1158 
1159     \sa fieldOfView, maximumFieldOfView
1160 
1161     \since QtLocation 5.9
1162 */
minimumFieldOfView() const1163 qreal QDeclarativeGeoMap::minimumFieldOfView() const
1164 {
1165     return m_minimumFieldOfView;
1166 }
1167 
setMaximumFieldOfView(qreal maximumFieldOfView,bool userSet)1168 void QDeclarativeGeoMap::setMaximumFieldOfView(qreal maximumFieldOfView, bool userSet)
1169 {
1170     if (maximumFieldOfView > 0 && maximumFieldOfView < 180.0) {
1171         if (userSet)
1172             m_userMaximumFieldOfView = maximumFieldOfView;
1173         qreal oldMaximumFoV = this->maximumFieldOfView();
1174 
1175         m_maximumFieldOfView = qBound<double>(m_cameraCapabilities.minimumFieldOfView(),
1176                                               maximumFieldOfView,
1177                                               m_cameraCapabilities.maximumFieldOfView());
1178 
1179         if (fieldOfView() > m_maximumFieldOfView)
1180             setFieldOfView(m_maximumFieldOfView);
1181 
1182         if (oldMaximumFoV != m_maximumFieldOfView)
1183             emit maximumFieldOfViewChanged(m_maximumFieldOfView);
1184     }
1185 }
1186 
1187 /*!
1188     \qmlproperty real QtLocation::Map::maximumFieldOfView
1189 
1190     This property holds the maximum valid field of view for the map, in degrees.
1191 
1192     The minimum tilt field of view by the \l plugin used is an upper bound for
1193     this property.
1194     If the \l plugin property is not set or the plugin does not support mapping, this property is \c 179.
1195 
1196     \sa fieldOfView, minimumFieldOfView
1197 
1198     \since QtLocation 5.9
1199 */
maximumFieldOfView() const1200 qreal QDeclarativeGeoMap::maximumFieldOfView() const
1201 {
1202     return m_maximumFieldOfView;
1203 }
1204 
1205 /*!
1206     \qmlproperty real QtLocation::Map::minimumTilt
1207 
1208     This property holds the minimum valid tilt for the map, in degrees.
1209 
1210     The minimum tilt defined by the \l plugin used is a lower bound for
1211     this property.
1212     If the \l plugin property is not set or the plugin does not support mapping, this property is \c 0.
1213 
1214     Since QtLocation 5.12, plugins can additionally restrict this value depending on the current zoom level.
1215 
1216     \sa tilt, maximumTilt
1217 
1218     \since QtLocation 5.9
1219 */
minimumTilt() const1220 qreal QDeclarativeGeoMap::minimumTilt() const
1221 {
1222     return m_minimumTilt;
1223 }
1224 
setMaximumTilt(qreal maximumTilt,bool userSet)1225 void QDeclarativeGeoMap::setMaximumTilt(qreal maximumTilt, bool userSet)
1226 {
1227     if (maximumTilt >= 0) {
1228         if (userSet)
1229             m_userMaximumTilt = maximumTilt;
1230         qreal oldMaximumTilt = this->maximumTilt();
1231 
1232         m_maximumTilt = qBound<double>(m_cameraCapabilities.minimumTilt(),
1233                                        maximumTilt,
1234                                        m_cameraCapabilities.maximumTilt());
1235 
1236         if (tilt() > m_maximumTilt)
1237             setTilt(m_maximumTilt);
1238 
1239         if (oldMaximumTilt != m_maximumTilt)
1240             emit maximumTiltChanged(m_maximumTilt);
1241     }
1242 }
1243 
1244 /*!
1245     \qmlproperty real QtLocation::Map::maximumTilt
1246 
1247     This property holds the maximum valid tilt for the map, in degrees.
1248 
1249     The maximum tilt defined by the \l plugin used is an upper bound for
1250     this property.
1251     If the \l plugin property is not set or the plugin does not support mapping, this property is \c 89.5.
1252 
1253     Since QtLocation 5.12, plugins can additionally restrict this value depending on the current zoom level.
1254 
1255     \sa tilt, minimumTilt
1256 
1257     \since QtLocation 5.9
1258 */
maximumTilt() const1259 qreal QDeclarativeGeoMap::maximumTilt() const
1260 {
1261     return m_maximumTilt;
1262 }
1263 
1264 /*!
1265     \qmlproperty coordinate QtLocation::Map::center
1266 
1267     This property holds the coordinate which occupies the center of the
1268     mapping viewport. Invalid center coordinates are ignored.
1269 
1270     The default value is an arbitrary valid coordinate.
1271 */
setCenter(const QGeoCoordinate & center)1272 void QDeclarativeGeoMap::setCenter(const QGeoCoordinate &center)
1273 {
1274     if (!center.isValid())
1275         return;
1276 
1277     if (m_initialized) {
1278         QGeoCoordinate coord(center);
1279         coord.setLatitude(qBound(m_minimumViewportLatitude, center.latitude(), m_maximumViewportLatitude));
1280         QGeoCameraData cameraData = m_map->cameraData();
1281         cameraData.setCenter(coord);
1282         m_map->setCameraData(cameraData);
1283     } else {
1284         const bool centerHasChanged = center != m_cameraData.center();
1285         m_cameraData.setCenter(center);
1286         if (centerHasChanged) {
1287             emit centerChanged(center);
1288             // do not emit visibleRegionChanged() here, because, if the map isn't initialized,
1289             // the getter won't return anything updated
1290         }
1291     }
1292 }
1293 
center() const1294 QGeoCoordinate QDeclarativeGeoMap::center() const
1295 {
1296     if (m_initialized)
1297         return m_map->cameraData().center();
1298     return m_cameraData.center();
1299 }
1300 
1301 
1302 /*!
1303     \qmlproperty geoshape QtLocation::Map::visibleRegion
1304 
1305     This property holds the region which occupies the viewport of
1306     the map. The camera is positioned in the center of the shape, and
1307     at the largest integral zoom level possible which allows the
1308     whole shape to be visible on the screen. This implies that
1309     reading this property back shortly after having been set the
1310     returned area is equal or larger than the set area.
1311 
1312     Setting this property implicitly changes the \l center and
1313     \l zoomLevel of the map. Any previously set value to those
1314     properties will be overridden.
1315 
1316     \note Since Qt 5.14 This property provides change notifications.
1317 
1318     \since 5.6
1319 */
setVisibleRegion(const QGeoShape & shape)1320 void QDeclarativeGeoMap::setVisibleRegion(const QGeoShape &shape)
1321 {
1322     if (shape.boundingGeoRectangle() == visibleRegion())
1323         return;
1324 
1325     m_visibleRegion = shape.boundingGeoRectangle();
1326     if (!m_visibleRegion.isValid()
1327         || (m_visibleRegion.bottomRight().latitude() >= 85.0) // rect entirely outside web mercator
1328         || (m_visibleRegion.topLeft().latitude() <= -85.0)) {
1329         // shape invalidated -> nothing to fit anymore
1330         m_visibleRegion = QGeoRectangle();
1331         m_pendingFitViewport = false;
1332         emit visibleRegionChanged();
1333         return;
1334     }
1335 
1336     if (!m_map || !width() || !height()) {
1337         m_pendingFitViewport = true;
1338         emit visibleRegionChanged();
1339         return;
1340     }
1341 
1342     fitViewportToGeoShape(m_visibleRegion);
1343     emit visibleRegionChanged();
1344 }
1345 
visibleRegion() const1346 QGeoShape QDeclarativeGeoMap::visibleRegion() const
1347 {
1348     if (!m_map || !width() || !height())
1349         return m_visibleRegion;
1350 
1351     if (m_map->capabilities() & QGeoMap::SupportsVisibleRegion) {
1352         return m_map->visibleRegion();
1353     } else {
1354         // ToDo: handle projections not supporting visible region in a better way.
1355         // This approach will fail when horizon is in the view or the map is greatly zoomed out.
1356         QList<QGeoCoordinate> visiblePoly;
1357         visiblePoly << m_map->geoProjection().itemPositionToCoordinate(QDoubleVector2D(0,0), false);
1358         visiblePoly << m_map->geoProjection().itemPositionToCoordinate(QDoubleVector2D(m_map->viewportWidth() - 1,
1359                                                                                        0), false);
1360         visiblePoly << m_map->geoProjection().itemPositionToCoordinate(QDoubleVector2D(m_map->viewportWidth() - 1,
1361                                                                                        m_map->viewportHeight() - 1), false);
1362         visiblePoly << m_map->geoProjection().itemPositionToCoordinate(QDoubleVector2D(0,
1363                                                                                        m_map->viewportHeight() - 1), false);
1364         QGeoPath path;
1365         path.setPath(visiblePoly);
1366         return path.boundingGeoRectangle();
1367     }
1368 }
1369 
1370 /*!
1371     \qmlproperty bool QtLocation::Map::copyrightsVisible
1372 
1373     This property holds the visibility of the copyrights notice. The notice is usually
1374     displayed in the bottom left corner. By default, this property is set to \c true.
1375 
1376     \note Many map providers require the notice to be visible as part of the terms and conditions.
1377     Please consult the relevant provider documentation before turning this notice off.
1378 
1379     \since 5.7
1380 */
setCopyrightsVisible(bool visible)1381 void QDeclarativeGeoMap::setCopyrightsVisible(bool visible)
1382 {
1383     if (m_copyrightsVisible == visible)
1384         return;
1385 
1386     if (!m_copyrights.isNull())
1387         m_copyrights->setCopyrightsVisible(visible);
1388 
1389     m_copyrightsVisible = visible;
1390     emit copyrightsVisibleChanged(visible);
1391 }
1392 
copyrightsVisible() const1393 bool QDeclarativeGeoMap::copyrightsVisible() const
1394 {
1395     return m_copyrightsVisible;
1396 }
1397 
1398 
1399 
1400 /*!
1401     \qmlproperty color QtLocation::Map::color
1402 
1403     This property holds the background color of the map element.
1404 
1405     \since 5.6
1406 */
setColor(const QColor & color)1407 void QDeclarativeGeoMap::setColor(const QColor &color)
1408 {
1409     if (color != m_color) {
1410         m_color = color;
1411         update();
1412         emit colorChanged(m_color);
1413     }
1414 }
1415 
color() const1416 QColor QDeclarativeGeoMap::color() const
1417 {
1418     return m_color;
1419 }
1420 
1421 /*!
1422     \qmlproperty rect QtLocation::Map::visibleArea
1423 
1424     This property holds the visible area inside the Map QML element.
1425     It is a rect whose coordinates are relative to the Map element.
1426     Its size will be clamped to the size of the Map element.
1427     A null visibleArea means that the whole Map is visible.
1428 
1429     \since 5.12
1430 */
visibleArea() const1431 QRectF QDeclarativeGeoMap::visibleArea() const
1432 {
1433     if (m_initialized)
1434         return m_map->visibleArea();
1435     return m_visibleArea;
1436 }
1437 
setVisibleArea(const QRectF & visibleArea)1438 void QDeclarativeGeoMap::setVisibleArea(const QRectF &visibleArea)
1439 {
1440     const QRectF oldVisibleArea = QDeclarativeGeoMap::visibleArea();
1441     if (visibleArea == oldVisibleArea)
1442         return;
1443 
1444     if (!visibleArea.isValid() && !visibleArea.isEmpty()) // values < 0
1445         return;
1446 
1447     if (m_initialized) {
1448         m_map->setVisibleArea(visibleArea);
1449         const QRectF newVisibleArea = QDeclarativeGeoMap::visibleArea();
1450         if (newVisibleArea != oldVisibleArea) {
1451             // polish map items
1452             for (const QPointer<QDeclarativeGeoMapItemBase> &i: qAsConst(m_mapItems)) {
1453                 if (i)
1454                     i->visibleAreaChanged();
1455             }
1456         }
1457     } else {
1458         m_visibleArea = visibleArea;
1459         const QRectF newVisibleArea = QDeclarativeGeoMap::visibleArea();
1460         if (newVisibleArea != oldVisibleArea)
1461             emit visibleAreaChanged();
1462     }
1463 }
1464 
1465 /*!
1466     \qmlproperty bool QtLocation::Map::mapReady
1467 
1468     This property holds whether the map has been successfully initialized and is ready to be used.
1469     Some methods, such as \l fromCoordinate and \l toCoordinate, will not work before the map is ready.
1470     Due to the architecture of the \l Map, it's advised to use the signal emitted for this property
1471     in place of \l {QtQml::Component::completed()}{Component.onCompleted}, to make sure that everything behaves as expected.
1472 
1473     \since 5.9
1474 */
mapReady() const1475 bool QDeclarativeGeoMap::mapReady() const
1476 {
1477     return m_initialized;
1478 }
1479 
mapMargins() const1480 QMargins QDeclarativeGeoMap::mapMargins() const
1481 {
1482     const QRectF va = m_map->visibleArea();
1483     if (va.isEmpty())
1484         return QMargins();
1485     return QMargins( va.x()
1486                     , va.y()
1487                     , width() - va.width() - va.x()
1488                     , height() - va.height() - va.y());
1489 }
1490 
1491 /*!
1492     \qmlproperty list<MapType> QtLocation::Map::supportedMapTypes
1493 
1494     This read-only property holds the set of \l{MapType}{map types} supported by this map.
1495 
1496     \sa activeMapType
1497 */
supportedMapTypes()1498 QQmlListProperty<QDeclarativeGeoMapType> QDeclarativeGeoMap::supportedMapTypes()
1499 {
1500     return QQmlListProperty<QDeclarativeGeoMapType>(this, &m_supportedMapTypes);
1501 }
1502 
1503 /*!
1504     \qmlmethod void QtLocation::Map::alignCoordinateToPoint(coordinate coordinate, QPointF point)
1505 
1506     Aligns \a coordinate to \a point.
1507     This method effectively extends the functionality offered by the \l center qml property, allowing
1508     to align a coordinate to point of the Map element other than its center.
1509     This is useful in those applications where the center of the scene (e.g., a cursor) is not to be
1510     placed exactly in the center of the map.
1511 
1512     If the map is tilted, and \a coordinate happens to be behind the camera, or if the map is not ready
1513     (see \l mapReady), calling this method will have no effect.
1514 
1515     The release of this API with Qt 5.10 is a Technology Preview.
1516 
1517     \sa center
1518 
1519     \since 5.10
1520 */
alignCoordinateToPoint(const QGeoCoordinate & coordinate,const QPointF & point)1521 void QDeclarativeGeoMap::alignCoordinateToPoint(const QGeoCoordinate &coordinate, const QPointF &point)
1522 {
1523     if (!m_map || !(m_map->capabilities() & QGeoMap::SupportsAnchoringCoordinate))
1524         return;
1525 
1526     if (!coordinate.isValid()
1527             || !qIsFinite(point.x())
1528             || !qIsFinite(point.y()))
1529         return;
1530 
1531     m_map->anchorCoordinateToPoint(coordinate, point);
1532 }
1533 
1534 /*!
1535     \qmlmethod coordinate QtLocation::Map::toCoordinate(QPointF position, bool clipToViewPort)
1536 
1537     Returns the coordinate which corresponds to the \a position relative to the map item.
1538 
1539     If \a clipToViewPort is \c true, or not supplied then returns an invalid coordinate if
1540     \a position is not within the current viewport.
1541 */
toCoordinate(const QPointF & position,bool clipToViewPort) const1542 QGeoCoordinate QDeclarativeGeoMap::toCoordinate(const QPointF &position, bool clipToViewPort) const
1543 {
1544     if (m_map)
1545         return m_map->geoProjection().itemPositionToCoordinate(QDoubleVector2D(position), clipToViewPort);
1546     else
1547         return QGeoCoordinate();
1548 }
1549 
1550 /*!
1551     \qmlmethod point QtLocation::Map::fromCoordinate(coordinate coordinate, bool clipToViewPort)
1552 
1553     Returns the position relative to the map item which corresponds to the \a coordinate.
1554 
1555     If \a clipToViewPort is \c true, or not supplied then returns an invalid QPointF if
1556     \a coordinate is not within the current viewport.
1557 */
fromCoordinate(const QGeoCoordinate & coordinate,bool clipToViewPort) const1558 QPointF QDeclarativeGeoMap::fromCoordinate(const QGeoCoordinate &coordinate, bool clipToViewPort) const
1559 {
1560     if (m_map)
1561         return m_map->geoProjection().coordinateToItemPosition(coordinate, clipToViewPort).toPointF();
1562     else
1563         return QPointF(qQNaN(), qQNaN());
1564 }
1565 
1566 /*!
1567     \qmlmethod void QtLocation::Map::pan(int dx, int dy)
1568 
1569     Starts panning the map by \a dx pixels along the x-axis and
1570     by \a dy pixels along the y-axis.
1571 
1572     Positive values for \a dx move the map right, negative values left.
1573     Positive values for \a dy move the map down, negative values up.
1574 
1575     During panning the \l center, and \l zoomLevel may change.
1576 */
pan(int dx,int dy)1577 void QDeclarativeGeoMap::pan(int dx, int dy)
1578 {
1579     if (!m_map)
1580         return;
1581     if (dx == 0 && dy == 0)
1582         return;
1583 
1584     QGeoCoordinate coord = m_map->geoProjection().itemPositionToCoordinate(
1585                                 QDoubleVector2D(m_map->viewportWidth() / 2 + dx,
1586                                         m_map->viewportHeight() / 2 + dy));
1587     setCenter(coord);
1588 }
1589 
1590 
1591 /*!
1592     \qmlmethod void QtLocation::Map::prefetchData()
1593 
1594     Optional hint that allows the map to prefetch during this idle period
1595 */
prefetchData()1596 void QDeclarativeGeoMap::prefetchData()
1597 {
1598     if (!m_map)
1599         return;
1600     m_map->prefetchData();
1601 }
1602 
1603 /*!
1604     \qmlmethod void QtLocation::Map::clearData()
1605 
1606     Clears map data collected by the currently selected plugin.
1607     \note This method will delete cached files.
1608     \sa plugin
1609 */
clearData()1610 void QDeclarativeGeoMap::clearData()
1611 {
1612     if (m_map)
1613         m_map->clearData();
1614 }
1615 
1616 /*!
1617     \qmlmethod void QtLocation::Map::fitViewportToGeoShape(geoShape, margins)
1618 
1619     Fits the viewport to a specific geo shape \a geoShape.
1620     The \a margins are in screen pixels.
1621 
1622     \note If the projection used by the plugin is not WebMercator, and the plugin does not have fitting to
1623     shape capability, this method will do nothing.
1624 
1625     \sa visibleRegion
1626     \since 5.13
1627 */
fitViewportToGeoShape(const QGeoShape & shape,QVariant margins)1628 void QDeclarativeGeoMap::fitViewportToGeoShape(const QGeoShape &shape, QVariant margins)
1629 {
1630     QMargins m(10, 10, 10, 10); // lets defaults to 10 if margins is invalid
1631     switch (static_cast<QMetaType::Type>(margins.type())) {
1632         case QMetaType::Int:
1633         case QMetaType::Double: {
1634             const int value = int(margins.toDouble());
1635             m = QMargins(value, value, value, value);
1636         }
1637         break;
1638         // ToDo: Support distinct margins in some QML form. Perhaps QRect?
1639         default:
1640             break;
1641     }
1642     fitViewportToGeoShape(shape, m);
1643 }
1644 
fitViewportToGeoShape(const QGeoShape & shape,const QMargins & borders)1645 void QDeclarativeGeoMap::fitViewportToGeoShape(const QGeoShape &shape, const QMargins &borders)
1646 {
1647     if (!m_map  || !shape.isValid())
1648         return;
1649 
1650     if (m_map->geoProjection().projectionType() == QGeoProjection::ProjectionWebMercator) {
1651         // This case remains handled here, and not inside QGeoMap*::fitViewportToGeoRectangle,
1652         // in order to honor animations on center and zoomLevel
1653         const QMargins margins = borders + mapMargins();
1654         const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(m_map->geoProjection());
1655         const QPair<QGeoCoordinate, qreal> fitData = p.fitViewportToGeoRectangle(shape.boundingGeoRectangle(),
1656                                                                                  margins);
1657         if (!fitData.first.isValid())
1658             return;
1659 
1660         // position camera to the center of bounding box
1661         setProperty("center", QVariant::fromValue(fitData.first)); // not using setCenter(centerCoordinate) to honor a possible animation set on the center property
1662 
1663         if (!qIsFinite(fitData.second))
1664             return;
1665         double newZoom = qMax<double>(minimumZoomLevel(), fitData.second);
1666         setProperty("zoomLevel", QVariant::fromValue(newZoom)); // not using setZoomLevel(newZoom)  to honor a possible animation set on the zoomLevel property
1667     } else if (m_map->capabilities() & QGeoMap::SupportsFittingViewportToGeoRectangle) {
1668         // Animations cannot be honored in this case, as m_map acts as a black box
1669         m_map->fitViewportToGeoRectangle(m_visibleRegion, borders);
1670     }
1671     // else out of luck
1672 }
1673 
1674 /*!
1675     \qmlproperty string QtLocation::Map::errorString
1676 
1677     This read-only property holds the textual presentation of the latest mapping provider error.
1678     If no error has occurred, an empty string is returned.
1679 
1680     An empty string may also be returned if an error occurred which has no associated
1681     textual representation.
1682 
1683     \sa QGeoServiceProvider::errorString()
1684 */
1685 
errorString() const1686 QString QDeclarativeGeoMap::errorString() const
1687 {
1688     return m_errorString;
1689 }
1690 
1691 /*!
1692     \qmlproperty enumeration QtLocation::Map::error
1693 
1694     This read-only property holds the last occurred mapping service provider error.
1695 
1696     \list
1697     \li Map.NoError - No error has occurred.
1698     \li Map.NotSupportedError -The maps plugin property was not set or there is no mapping manager associated with the plugin.
1699     \li Map.UnknownParameterError -The plugin did not recognize one of the parameters it was given.
1700     \li Map.MissingRequiredParameterError - The plugin did not find one of the parameters it was expecting.
1701     \li Map.ConnectionError - The plugin could not connect to its backend service or database.
1702     \endlist
1703 
1704     \sa QGeoServiceProvider::Error
1705 */
1706 
error() const1707 QGeoServiceProvider::Error QDeclarativeGeoMap::error() const
1708 {
1709     return m_error;
1710 }
1711 
map() const1712 QGeoMap *QDeclarativeGeoMap::map() const
1713 {
1714     return m_map;
1715 }
1716 
itemChange(ItemChange change,const ItemChangeData & value)1717 void QDeclarativeGeoMap::itemChange(ItemChange change, const ItemChangeData &value)
1718 {
1719     if (change == ItemChildAddedChange) {
1720         QQuickItem *child = value.item;
1721         QQuickItem *mapItem = qobject_cast<QDeclarativeGeoMapItemBase *>(child);
1722         if (!mapItem)
1723             mapItem = qobject_cast<QDeclarativeGeoMapItemGroup *>(child);
1724 
1725         if (mapItem) {
1726             qreal z = mapItem->z();
1727             if (z > m_maxChildZ) { // Ignore children removal
1728                 m_maxChildZ = z;
1729                 // put the copyrights notice object at the highest z order
1730                 if (m_copyrights)
1731                     m_copyrights->setCopyrightsZ(m_maxChildZ + 1);
1732             }
1733         }
1734     }
1735     QQuickItem::itemChange(change, value);
1736 }
1737 
isInteractive()1738 bool QDeclarativeGeoMap::isInteractive()
1739 {
1740     return (m_gestureArea->enabled() && m_gestureArea->acceptedGestures()) || m_gestureArea->isActive();
1741 }
1742 
attachCopyrightNotice(bool initialVisibility)1743 void QDeclarativeGeoMap::attachCopyrightNotice(bool initialVisibility)
1744 {
1745     if (initialVisibility) {
1746         ++m_copyNoticesVisible;
1747         if (m_map)
1748             m_map->setCopyrightVisible(m_copyNoticesVisible > 0);
1749     }
1750 }
1751 
detachCopyrightNotice(bool currentVisibility)1752 void QDeclarativeGeoMap::detachCopyrightNotice(bool currentVisibility)
1753 {
1754     if (currentVisibility) {
1755         --m_copyNoticesVisible;
1756         if (m_map)
1757             m_map->setCopyrightVisible(m_copyNoticesVisible > 0);
1758     }
1759 }
1760 
onAttachedCopyrightNoticeVisibilityChanged()1761 void QDeclarativeGeoMap::onAttachedCopyrightNoticeVisibilityChanged()
1762 {
1763     QDeclarativeGeoMapCopyrightNotice *copy = static_cast<QDeclarativeGeoMapCopyrightNotice *>(sender());
1764     m_copyNoticesVisible += ( int(copy->copyrightsVisible()) * 2 - 1);
1765     if (m_map)
1766         m_map->setCopyrightVisible(m_copyNoticesVisible > 0);
1767 }
1768 
onCameraDataChanged(const QGeoCameraData & cameraData)1769 void QDeclarativeGeoMap::onCameraDataChanged(const QGeoCameraData &cameraData)
1770 {
1771     bool centerHasChanged = cameraData.center() != m_cameraData.center();
1772     bool bearingHasChanged = cameraData.bearing() != m_cameraData.bearing();
1773     bool tiltHasChanged = cameraData.tilt() != m_cameraData.tilt();
1774     bool fovHasChanged = cameraData.fieldOfView() != m_cameraData.fieldOfView();
1775     bool zoomHasChanged = cameraData.zoomLevel() != m_cameraData.zoomLevel();
1776 
1777     m_cameraData = cameraData;
1778     // polish map items
1779     for (const QPointer<QDeclarativeGeoMapItemBase> &i: qAsConst(m_mapItems)) {
1780         if (i)
1781             i->baseCameraDataChanged(m_cameraData); // Consider optimizing this further, removing the contained duplicate if conditions.
1782     }
1783 
1784     if (centerHasChanged)
1785         emit centerChanged(m_cameraData.center());
1786     if (zoomHasChanged)
1787         emit zoomLevelChanged(m_cameraData.zoomLevel());
1788     if (bearingHasChanged)
1789         emit bearingChanged(m_cameraData.bearing());
1790     if (tiltHasChanged)
1791         emit tiltChanged(m_cameraData.tilt());
1792     if (fovHasChanged)
1793         emit fieldOfViewChanged(m_cameraData.fieldOfView());
1794     if (centerHasChanged || zoomHasChanged || bearingHasChanged
1795             || tiltHasChanged || fovHasChanged)
1796         emit visibleRegionChanged();
1797 }
1798 
1799 /*!
1800     \qmlmethod void QtLocation::Map::addMapParameter(MapParameter parameter)
1801 
1802     Adds the \a parameter object to the map. The effect of this call is dependent
1803     on the combination of the content of the MapParameter and the type of
1804     underlying QGeoMap. If a MapParameter that is not supported by the underlying
1805     QGeoMap gets added, the call has no effect.
1806 
1807     The release of this API with Qt 5.9 is a Technology Preview.
1808 
1809     \sa MapParameter, removeMapParameter, mapParameters, clearMapParameters
1810 
1811     \since 5.9
1812 */
addMapParameter(QDeclarativeGeoMapParameter * parameter)1813 void QDeclarativeGeoMap::addMapParameter(QDeclarativeGeoMapParameter *parameter)
1814 {
1815     if (!parameter->isComponentComplete()) {
1816         connect(parameter, &QDeclarativeGeoMapParameter::completed, this, &QDeclarativeGeoMap::addMapParameter);
1817         return;
1818     }
1819 
1820     disconnect(parameter);
1821     if (m_mapParameters.contains(parameter))
1822         return;
1823     parameter->setParent(this);
1824     m_mapParameters.append(parameter); // parameter now owned by QDeclarativeGeoMap
1825     if (m_map)
1826         m_map->addParameter(parameter);
1827 }
1828 
1829 /*!
1830     \qmlmethod void QtLocation::Map::removeMapParameter(MapParameter parameter)
1831 
1832     Removes the given \a parameter object from the map.
1833 
1834     The release of this API with Qt 5.9 is a Technology Preview.
1835 
1836     \sa MapParameter, addMapParameter, mapParameters, clearMapParameters
1837 
1838     \since 5.9
1839 */
removeMapParameter(QDeclarativeGeoMapParameter * parameter)1840 void QDeclarativeGeoMap::removeMapParameter(QDeclarativeGeoMapParameter *parameter)
1841 {
1842     if (!m_mapParameters.contains(parameter))
1843         return;
1844     if (m_map)
1845         m_map->removeParameter(parameter);
1846     m_mapParameters.removeOne(parameter);
1847 }
1848 
1849 /*!
1850     \qmlmethod void QtLocation::Map::clearMapParameters()
1851 
1852     Removes all map parameters from the map.
1853 
1854     The release of this API with Qt 5.9 is a Technology Preview.
1855 
1856     \sa MapParameter, mapParameters, addMapParameter, removeMapParameter, clearMapParameters
1857 
1858     \since 5.9
1859 */
clearMapParameters()1860 void QDeclarativeGeoMap::clearMapParameters()
1861 {
1862     if (m_map)
1863         m_map->clearParameters();
1864     m_mapParameters.clear();
1865 }
1866 
1867 /*!
1868     \qmlproperty list<MapParameters> QtLocation::Map::mapParameters
1869 
1870     Returns the list of all map parameters in no particular order.
1871     These items include map parameters that were declared statically as part of
1872     the type declaration, as well as dynamical map parameters (\l addMapParameter).
1873 
1874     The release of this API with Qt 5.9 is a Technology Preview.
1875 
1876     \sa MapParameter, addMapParameter, removeMapParameter, clearMapParameters
1877 
1878     \since 5.9
1879 */
mapParameters()1880 QList<QObject *> QDeclarativeGeoMap::mapParameters()
1881 {
1882     QList<QObject *> ret;
1883     for (QDeclarativeGeoMapParameter *p : qAsConst(m_mapParameters))
1884         ret << p;
1885     return ret;
1886 }
1887 
1888 /*
1889     \internal
1890 */
addMapObject(QGeoMapObject * object)1891 void QDeclarativeGeoMap::addMapObject(QGeoMapObject *object)
1892 {
1893     if (!object || object->map())
1894         return;
1895 
1896     if (!m_initialized) {
1897         m_pendingMapObjects.append(object);
1898         return;
1899     }
1900 
1901     int curObjects = m_map->mapObjects().size();
1902     // object adds itself to the map
1903     object->setMap(m_map);
1904 
1905     if (curObjects != m_map->mapObjects().size())
1906         emit mapObjectsChanged();
1907 }
1908 
1909 /*
1910     \internal
1911 */
removeMapObject(QGeoMapObject * object)1912 void QDeclarativeGeoMap::removeMapObject(QGeoMapObject *object)
1913 {
1914     if (!object || object->map() != m_map) // if !initialized this is fine, since both object and m_map are supposed to be NULL
1915         return;
1916 
1917     if (!m_initialized) {
1918         m_pendingMapObjects.removeOne(object);
1919         return;
1920     }
1921 
1922     int curObjects = m_map->mapObjects().size();
1923     // object adds itself to the map
1924     object->setMap(nullptr);
1925 
1926     if (curObjects != m_map->mapObjects().size())
1927         emit mapObjectsChanged();
1928 }
1929 
1930 /*
1931     \internal
1932 */
clearMapObjects()1933 void QDeclarativeGeoMap::clearMapObjects()
1934 {
1935     if (!m_initialized) {
1936         m_pendingMapObjects.clear();
1937     } else {
1938         const QList<QGeoMapObject *> objs = m_map->mapObjects();
1939         for (QGeoMapObject *o: objs)
1940             o->setMap(nullptr);
1941         if (objs.size())
1942             emit mapObjectsChanged();
1943     }
1944 }
1945 
1946 /*
1947     \internal
1948 */
mapObjects()1949 QList<QGeoMapObject *> QDeclarativeGeoMap::mapObjects()
1950 {
1951     if (!m_initialized)
1952         return m_pendingMapObjects;
1953     else
1954         return m_map->mapObjects();
1955 }
1956 
1957 /*!
1958     \qmlproperty list<MapItem> QtLocation::Map::mapItems
1959 
1960     Returns the list of all map items in no particular order.
1961     These items include items that were declared statically as part of
1962     the type declaration, as well as dynamical items (\l addMapItem,
1963     \l MapItemView).
1964 
1965     \sa addMapItem, removeMapItem, clearMapItems
1966 */
1967 
mapItems()1968 QList<QObject *> QDeclarativeGeoMap::mapItems()
1969 {
1970     QList<QObject *> ret;
1971     foreach (const QPointer<QDeclarativeGeoMapItemBase> &ptr, m_mapItems) {
1972         if (ptr)
1973             ret << ptr.data();
1974     }
1975     return ret;
1976 }
1977 
1978 /*!
1979     \qmlmethod void QtLocation::Map::addMapItem(MapItem item)
1980 
1981     Adds the given \a item to the Map (for example MapQuickItem, MapCircle). If the object
1982     already is on the Map, it will not be added again.
1983 
1984     As an example, consider the case where you have a MapCircle representing your current position:
1985 
1986     \snippet declarative/maps.qml QtQuick import
1987     \snippet declarative/maps.qml QtLocation import
1988     \codeline
1989     \snippet declarative/maps.qml Map addMapItem MapCircle at current position
1990 
1991     \note MapItemViews cannot be added with this method.
1992 
1993     \sa mapItems, removeMapItem, clearMapItems
1994 */
1995 
addMapItem(QDeclarativeGeoMapItemBase * item)1996 void QDeclarativeGeoMap::addMapItem(QDeclarativeGeoMapItemBase *item)
1997 {
1998     if (addMapItem_real(item))
1999         emit mapItemsChanged();
2000 }
2001 
addMapItem_real(QDeclarativeGeoMapItemBase * item)2002 bool QDeclarativeGeoMap::addMapItem_real(QDeclarativeGeoMapItemBase *item)
2003 {
2004     if (!item || item->quickMap())
2005         return false;
2006     // If the item comes from a MapItemGroup, do not reparent it.
2007     if (!qobject_cast<QDeclarativeGeoMapItemGroup *>(item->parentItem()))
2008         item->setParentItem(this);
2009     m_mapItems.append(item);
2010     if (m_map) {
2011         item->setMap(this, m_map);
2012         m_map->addMapItem(item);
2013     }
2014     return true;
2015 }
2016 
2017 /*!
2018     \qmlmethod void QtLocation::Map::removeMapItem(MapItem item)
2019 
2020     Removes the given \a item from the Map (for example MapQuickItem, MapCircle). If
2021     the MapItem does not exist or was not previously added to the map, the
2022     method does nothing.
2023 
2024     \sa mapItems, addMapItem, clearMapItems
2025 */
removeMapItem(QDeclarativeGeoMapItemBase * ptr)2026 void QDeclarativeGeoMap::removeMapItem(QDeclarativeGeoMapItemBase *ptr)
2027 {
2028     if (removeMapItem_real(ptr))
2029         emit mapItemsChanged();
2030 }
2031 
removeMapItem_real(QDeclarativeGeoMapItemBase * ptr)2032 bool QDeclarativeGeoMap::removeMapItem_real(QDeclarativeGeoMapItemBase *ptr)
2033 {
2034     if (!ptr)
2035         return false;
2036     QPointer<QDeclarativeGeoMapItemBase> item(ptr);
2037     if (!m_mapItems.contains(item))
2038         return false;
2039     if (m_map)
2040         m_map->removeMapItem(ptr);
2041     if (item->parentItem() == this)
2042         item->setParentItem(0);
2043     item->setMap(0, 0);
2044     // these can be optimized for perf, as we already check the 'contains' above
2045     m_mapItems.removeOne(item);
2046     return true;
2047 }
2048 
2049 /*!
2050     \qmlmethod void QtLocation::Map::clearMapItems()
2051 
2052     Removes all items and item groups from the map.
2053 
2054     \sa mapItems, addMapItem, removeMapItem, addMapItemGroup, removeMapItemGroup
2055 */
clearMapItems()2056 void QDeclarativeGeoMap::clearMapItems()
2057 {
2058     if (m_mapItems.isEmpty())
2059         return;
2060 
2061     int removed = 0;
2062     for (auto i : qAsConst(m_mapItemGroups)) {
2063         // Processing only top-level groups (!views)
2064         QDeclarativeGeoMapItemView *view = qobject_cast<QDeclarativeGeoMapItemView *>(i);
2065         if (view)
2066             continue;
2067 
2068         if (i->parentItem() != this)
2069             continue;
2070 
2071         removed += removeMapItemGroup_real(i);
2072     }
2073 
2074     for (auto i : qAsConst(m_mapItems))
2075         removed += removeMapItem_real(i);
2076 
2077     if (removed)
2078         emit mapItemsChanged();
2079 }
2080 
2081 /*!
2082     \qmlmethod void QtLocation::Map::addMapItemGroup(MapItemGroup itemGroup)
2083 
2084     Adds the map items contained in the given \a itemGroup to the Map
2085     (for example MapQuickItem, MapCircle).
2086 
2087     \sa MapItemGroup, removeMapItemGroup
2088 
2089     \since 5.9
2090 */
addMapItemGroup(QDeclarativeGeoMapItemGroup * itemGroup)2091 void QDeclarativeGeoMap::addMapItemGroup(QDeclarativeGeoMapItemGroup *itemGroup)
2092 {
2093     if (addMapItemGroup_real(itemGroup))
2094         emit mapItemsChanged();
2095 }
2096 
addMapItemGroup_real(QDeclarativeGeoMapItemGroup * itemGroup)2097 bool QDeclarativeGeoMap::addMapItemGroup_real(QDeclarativeGeoMapItemGroup *itemGroup)
2098 {
2099     if (!itemGroup || itemGroup->quickMap()) // Already added to some map
2100         return false;
2101 
2102     itemGroup->setQuickMap(this);
2103 
2104     if (!isGroupNested(itemGroup))
2105         itemGroup->setParentItem(this);
2106 
2107     QPointer<QDeclarativeGeoMapItemGroup> g(itemGroup);
2108     m_mapItemGroups.append(g);
2109 
2110     const QList<QQuickItem *> quickKids = itemGroup->childItems();
2111     int count = 0;
2112     for (auto c: quickKids) {
2113         count += addMapChild(c); // this calls addMapItemGroup recursively, if needed
2114     }
2115     return count;
2116 }
2117 
2118 /*!
2119     \qmlmethod void QtLocation::Map::removeMapItemGroup(MapItemGroup itemGroup)
2120 
2121     Removes \a itemGroup and the items contained therein from the Map.
2122 
2123     \sa MapItemGroup, addMapItemGroup
2124 
2125     \since 5.9
2126 */
removeMapItemGroup(QDeclarativeGeoMapItemGroup * itemGroup)2127 void QDeclarativeGeoMap::removeMapItemGroup(QDeclarativeGeoMapItemGroup *itemGroup)
2128 {
2129     if (removeMapItemGroup_real(itemGroup))
2130         emit mapItemsChanged();
2131 }
2132 
removeMapItemGroup_real(QDeclarativeGeoMapItemGroup * itemGroup)2133 bool QDeclarativeGeoMap::removeMapItemGroup_real(QDeclarativeGeoMapItemGroup *itemGroup)
2134 {
2135     if (!itemGroup || itemGroup->quickMap() != this) // cant remove an itemGroup added to another map
2136         return false;
2137 
2138     QPointer<QDeclarativeGeoMapItemGroup> g(itemGroup);
2139     if (!m_mapItemGroups.removeOne(g))
2140         return false;
2141 
2142     const QList<QQuickItem *> quickKids = itemGroup->childItems();
2143     int count = 0;
2144     for (auto c: quickKids) {
2145         count += removeMapChild(c);
2146     }
2147     itemGroup->setQuickMap(nullptr);
2148     if (itemGroup->parentItem() == this)
2149         itemGroup->setParentItem(0);
2150     return count;
2151 }
2152 
2153 /*!
2154     \qmlmethod void QtLocation::Map::removeMapItemView(MapItemView itemView)
2155 
2156     Removes \a itemView and the items instantiated by it from the Map.
2157 
2158     \sa MapItemView, addMapItemView
2159 
2160     \since 5.10
2161 */
removeMapItemView(QDeclarativeGeoMapItemView * itemView)2162 void QDeclarativeGeoMap::removeMapItemView(QDeclarativeGeoMapItemView *itemView)
2163 {
2164     if (removeMapItemView_real(itemView))
2165         emit mapItemsChanged();
2166 }
2167 
removeMapItemView_real(QDeclarativeGeoMapItemView * itemView)2168 bool QDeclarativeGeoMap::removeMapItemView_real(QDeclarativeGeoMapItemView *itemView)
2169 {
2170     if (!itemView || itemView->m_map != this) // can't remove a view that is already added to another map
2171         return false;
2172 
2173     itemView->removeInstantiatedItems(false); // remove the items without using transitions AND abort ongoing ones
2174     itemView->m_map = 0;
2175     m_mapViews.removeOne(itemView);
2176     return removeMapItemGroup_real(itemView); // at this point, all delegate instances have been removed.
2177 }
2178 
updateItemToWindowTransform()2179 void QDeclarativeGeoMap::updateItemToWindowTransform()
2180 {
2181     if (!m_initialized)
2182         return;
2183 
2184     // Update itemToWindowTransform into QGeoProjection
2185     const QTransform item2WindowOld = m_map->geoProjection().itemToWindowTransform();
2186     QTransform item2Window = QQuickItemPrivate::get(this)->itemToWindowTransform();
2187     if (!property("layer").isNull() && property("layer").value<QObject *>()->property("enabled").toBool())
2188         item2Window.reset(); // When layer is enabled, the item is rendered offscreen with no transformation, then the layer is applied
2189 
2190     m_map->setItemToWindowTransform(item2Window);
2191 
2192     // This method is called at every redraw, including those redraws not generated by
2193     // sgNodeChanged.
2194     // In these cases, *if* the item2windowTransform has changed (e.g., if transformation of
2195     // the item or one of its ancestors changed), a forced update of the map items using accelerated
2196     // GL implementation has to be performed in order to have them pulling the updated itemToWindowTransform.
2197     if (!m_sgNodeHasChanged && item2WindowOld != item2Window) {
2198         for (auto i: qAsConst(m_mapItems))
2199             i->setMaterialDirty();
2200     }
2201 
2202     m_sgNodeHasChanged = false;
2203 }
2204 
onSGNodeChanged()2205 void QDeclarativeGeoMap::onSGNodeChanged()
2206 {
2207     m_sgNodeHasChanged = true;
2208     update();
2209 }
2210 
2211 /*!
2212     \qmlmethod void QtLocation::Map::addMapItemView(MapItemView itemView)
2213 
2214     Adds \a itemView to the Map.
2215 
2216     \sa MapItemView, removeMapItemView
2217 
2218     \since 5.10
2219 */
addMapItemView(QDeclarativeGeoMapItemView * itemView)2220 void QDeclarativeGeoMap::addMapItemView(QDeclarativeGeoMapItemView *itemView)
2221 {
2222     if (addMapItemView_real(itemView))
2223         emit mapItemsChanged();
2224 }
2225 
addMapItemView_real(QDeclarativeGeoMapItemView * itemView)2226 bool QDeclarativeGeoMap::addMapItemView_real(QDeclarativeGeoMapItemView *itemView)
2227 {
2228     if (!itemView || itemView->m_map) // can't add a view twice
2229         return false;
2230 
2231     int count = addMapItemGroup_real(itemView); // at this point, delegates aren't yet incubated.
2232     // Not appending it to m_mapViews because it seems unnecessary even if the
2233     // itemView is a child of this (in which case it would be destroyed
2234     m_mapViews.append(itemView);
2235     setupMapView(itemView);
2236     return count;
2237 }
2238 
2239 /*!
2240     \qmlproperty MapType QtLocation::Map::activeMapType
2241 
2242     \brief Access to the currently active \l{MapType}{map type}.
2243 
2244     This property can be set to change the active \l{MapType}{map type}.
2245     See the \l{Map::supportedMapTypes}{supportedMapTypes} property for possible values.
2246 
2247     \sa MapType
2248 */
setActiveMapType(QDeclarativeGeoMapType * mapType)2249 void QDeclarativeGeoMap::setActiveMapType(QDeclarativeGeoMapType *mapType)
2250 {
2251     if (m_activeMapType->mapType() != mapType->mapType()) {
2252         if (m_map) {
2253             if (mapType->mapType().pluginName() == m_plugin->name().toLatin1()) {
2254                 m_map->setActiveMapType(mapType->mapType());
2255                 m_activeMapType = mapType;
2256                 emit activeMapTypeChanged();
2257             }
2258         } else {
2259             m_activeMapType = mapType;
2260             emit activeMapTypeChanged();
2261         }
2262     }
2263 }
2264 
activeMapType() const2265 QDeclarativeGeoMapType * QDeclarativeGeoMap::activeMapType() const
2266 {
2267     return m_activeMapType;
2268 }
2269 
2270 /*!
2271     \internal
2272 */
geometryChanged(const QRectF & newGeometry,const QRectF & oldGeometry)2273 void QDeclarativeGeoMap::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
2274 {
2275     m_gestureArea->setSize(newGeometry.size());
2276     QQuickItem::geometryChanged(newGeometry, oldGeometry);
2277 
2278     if (!m_map || newGeometry.size().isEmpty())
2279         return;
2280 
2281     m_map->setViewportSize(newGeometry.size().toSize());
2282 
2283     if (!m_initialized) {
2284         initialize();
2285     } else {
2286         setMinimumZoomLevel(m_map->minimumZoom(), false);
2287 
2288         // Update the center latitudinal threshold
2289         QGeoCameraData cameraData = m_map->cameraData();
2290         const double maximumCenterLatitudeAtZoom = m_map->maximumCenterLatitudeAtZoom(cameraData);
2291         const double minimumCenterLatitudeAtZoom = m_map->minimumCenterLatitudeAtZoom(cameraData);
2292         if (maximumCenterLatitudeAtZoom != m_maximumViewportLatitude
2293                 || minimumCenterLatitudeAtZoom != m_minimumViewportLatitude) {
2294             m_maximumViewportLatitude = maximumCenterLatitudeAtZoom;
2295             m_minimumViewportLatitude = minimumCenterLatitudeAtZoom;
2296             QGeoCoordinate coord = cameraData.center();
2297             coord.setLatitude(qBound(m_minimumViewportLatitude, coord.latitude(), m_maximumViewportLatitude));
2298             cameraData.setCenter(coord);
2299             m_map->setCameraData(cameraData); // this polishes map items
2300         } else if (oldGeometry.size() != newGeometry.size()) {
2301             // polish map items
2302             for (const QPointer<QDeclarativeGeoMapItemBase> &i: qAsConst(m_mapItems)) {
2303                 if (i)
2304                     i->polishAndUpdate();
2305             }
2306         }
2307     }
2308 
2309     /*
2310         The fitViewportTo*() functions depend on a valid map geometry.
2311         If they were called prior to the first resize they cause
2312         the zoomlevel to jump to 0 (showing the world). Therefore the
2313         calls were queued up until now.
2314 
2315         Multiple fitViewportTo*() calls replace each other.
2316      */
2317     if (m_pendingFitViewport && width() && height()) {
2318         fitViewportToGeoShape(m_visibleRegion);
2319         m_pendingFitViewport = false;
2320     }
2321 
2322 }
2323 
2324 /*!
2325     \qmlmethod void QtLocation::Map::fitViewportToMapItems(list<MapItems> items = {})
2326 
2327     If no argument is provided, fits the current viewport to the boundary of all map items.
2328     The camera is positioned in the center of the map items, and at the largest integral zoom level
2329     possible which allows all map items to be visible on screen.
2330     If \a items is provided, fits the current viewport to the boundary of the specified map items only.
2331 
2332     \note This method gained the optional \a items argument since Qt 5.15.
2333     In previous releases, this method fitted the map to all map items.
2334 
2335     \sa fitViewportToVisibleMapItems
2336 */
fitViewportToMapItems(const QVariantList & items)2337 void QDeclarativeGeoMap::fitViewportToMapItems(const QVariantList &items)
2338 {
2339     if (items.size()) {
2340         QList<QPointer<QDeclarativeGeoMapItemBase> > itms;
2341         for (const QVariant &i: items) {
2342             QDeclarativeGeoMapItemBase *itm = qobject_cast<QDeclarativeGeoMapItemBase *>(i.value<QObject *>());
2343             if (itm)
2344                 itms.append(itm);
2345         }
2346         fitViewportToMapItemsRefine(itms, true, false);
2347     } else {
2348         fitViewportToMapItemsRefine(m_mapItems, true, false);
2349     }
2350 }
2351 
2352 /*!
2353     \qmlmethod void QtLocation::Map::fitViewportToVisibleMapItems()
2354 
2355     Fits the current viewport to the boundary of all \b visible map items.
2356     The camera is positioned in the center of the map items, and at the largest integral
2357     zoom level possible which allows all map items to be visible on screen.
2358 
2359     \sa fitViewportToMapItems
2360 */
fitViewportToVisibleMapItems()2361 void QDeclarativeGeoMap::fitViewportToVisibleMapItems()
2362 {
2363     fitViewportToMapItemsRefine(m_mapItems, true, true);
2364 }
2365 
2366 /*!
2367     \internal
2368 */
fitViewportToMapItemsRefine(const QList<QPointer<QDeclarativeGeoMapItemBase>> & mapItems,bool refine,bool onlyVisible)2369 void QDeclarativeGeoMap::fitViewportToMapItemsRefine(const QList<QPointer<QDeclarativeGeoMapItemBase> > &mapItems,
2370                                                      bool refine,
2371                                                      bool onlyVisible)
2372 {
2373     if (!m_map)
2374         return;
2375 
2376     if (mapItems.size() == 0)
2377         return;
2378 
2379     double minX = qInf();
2380     double maxX = -qInf();
2381     double minY = qInf();
2382     double maxY = -qInf();
2383     double topLeftX = 0;
2384     double topLeftY = 0;
2385     double bottomRightX = 0;
2386     double bottomRightY = 0;
2387     bool haveQuickItem = false;
2388 
2389     // find bounds of all map items
2390     int itemCount = 0;
2391     for (int i = 0; i < mapItems.count(); ++i) {
2392         if (!mapItems.at(i))
2393             continue;
2394         QDeclarativeGeoMapItemBase *item = mapItems.at(i).data();
2395         if (!item || (onlyVisible && (!item->isVisible() || item->mapItemOpacity() <= 0.0)))
2396             continue;
2397 
2398         // skip quick items in the first pass and refine the fit later
2399         QDeclarativeGeoMapQuickItem *quickItem =
2400                 qobject_cast<QDeclarativeGeoMapQuickItem*>(item);
2401         if (refine && quickItem) {
2402                 haveQuickItem = true;
2403                 continue;
2404         }
2405         // Force map items to update immediately. Needed to ensure correct item size and positions
2406         // when recursively calling this function.
2407         // TODO: See if we really need updatePolish on delegated items, in particular
2408         // in relation to
2409         // a) fitViewportToMapItems
2410         // b) presence of MouseArea
2411         //
2412         // This is also legacy code. It must be updated to not operate on screen sizes.
2413         if (item->isPolishScheduled())
2414            item->updatePolish();
2415 
2416         if (quickItem && quickItem->matrix_ && !quickItem->matrix_->m_matrix.isIdentity()) {
2417             // TODO: recalculate the center/zoom level so that the item becomes projectable again
2418             if (quickItem->zoomLevel() == 0.0) // the item is unprojectable, should be skipped.
2419                 continue;
2420 
2421             QRectF brect = item->boundingRect();
2422             brect = quickItem->matrix_->m_matrix.mapRect(brect);
2423             QPointF transformedPosition = quickItem->matrix_->m_matrix * item->position();
2424             topLeftX = transformedPosition.x();
2425             topLeftY = transformedPosition.y();
2426             bottomRightX = topLeftX + brect.width();
2427             bottomRightY = topLeftY + brect.height();
2428         } else {
2429             topLeftX = item->position().x();
2430             topLeftY = item->position().y();
2431             bottomRightX = topLeftX + item->width();
2432             bottomRightY = topLeftY + item->height();
2433         }
2434 
2435         minX = qMin(minX, topLeftX);
2436         maxX = qMax(maxX, bottomRightX);
2437         minY = qMin(minY, topLeftY);
2438         maxY = qMax(maxY, bottomRightY);
2439 
2440         ++itemCount;
2441     }
2442 
2443     if (itemCount == 0) {
2444         if (haveQuickItem)
2445             fitViewportToMapItemsRefine(mapItems, false, onlyVisible);
2446         return;
2447     }
2448     double bboxWidth = maxX - minX;
2449     double bboxHeight = maxY - minY;
2450     double bboxCenterX = minX + (bboxWidth / 2.0);
2451     double bboxCenterY = minY + (bboxHeight / 2.0);
2452 
2453     // position camera to the center of bounding box
2454     QGeoCoordinate coordinate;
2455     coordinate = m_map->geoProjection().itemPositionToCoordinate(QDoubleVector2D(bboxCenterX, bboxCenterY), false);
2456     setProperty("center", QVariant::fromValue(coordinate));
2457 
2458     // adjust zoom
2459     double bboxWidthRatio = bboxWidth / (bboxWidth + bboxHeight);
2460     double mapWidthRatio = width() / (width() + height());
2461     double zoomRatio;
2462 
2463     if (bboxWidthRatio > mapWidthRatio)
2464         zoomRatio = bboxWidth / width();
2465     else
2466         zoomRatio = bboxHeight / height();
2467 
2468     qreal newZoom = std::log10(zoomRatio) / std::log10(0.5);
2469     newZoom = std::floor(qMax(minimumZoomLevel(), (zoomLevel() + newZoom)));
2470     setProperty("zoomLevel", QVariant::fromValue(newZoom));
2471 
2472     // as map quick items retain the same screen size after the camera zooms in/out
2473     // we refine the viewport again to achieve better results
2474     if (refine)
2475         fitViewportToMapItemsRefine(mapItems, false, onlyVisible);
2476 }
2477 
2478 /*!
2479     \internal
2480 */
mousePressEvent(QMouseEvent * event)2481 void QDeclarativeGeoMap::mousePressEvent(QMouseEvent *event)
2482 {
2483     if (isInteractive())
2484         m_gestureArea->handleMousePressEvent(event);
2485     else
2486         QQuickItem::mousePressEvent(event);
2487 }
2488 
2489 /*!
2490     \internal
2491 */
mouseMoveEvent(QMouseEvent * event)2492 void QDeclarativeGeoMap::mouseMoveEvent(QMouseEvent *event)
2493 {
2494     if (isInteractive())
2495         m_gestureArea->handleMouseMoveEvent(event);
2496     else
2497         QQuickItem::mouseMoveEvent(event);
2498 }
2499 
2500 /*!
2501     \internal
2502 */
mouseReleaseEvent(QMouseEvent * event)2503 void QDeclarativeGeoMap::mouseReleaseEvent(QMouseEvent *event)
2504 {
2505     if (isInteractive())
2506         m_gestureArea->handleMouseReleaseEvent(event);
2507     else
2508         QQuickItem::mouseReleaseEvent(event);
2509 }
2510 
2511 /*!
2512     \internal
2513 */
mouseUngrabEvent()2514 void QDeclarativeGeoMap::mouseUngrabEvent()
2515 {
2516     if (isInteractive())
2517         m_gestureArea->handleMouseUngrabEvent();
2518     else
2519         QQuickItem::mouseUngrabEvent();
2520 }
2521 
touchUngrabEvent()2522 void QDeclarativeGeoMap::touchUngrabEvent()
2523 {
2524     if (isInteractive())
2525         m_gestureArea->handleTouchUngrabEvent();
2526     else
2527         QQuickItem::touchUngrabEvent();
2528 }
2529 
2530 /*!
2531     \internal
2532 */
touchEvent(QTouchEvent * event)2533 void QDeclarativeGeoMap::touchEvent(QTouchEvent *event)
2534 {
2535     if (isInteractive()) {
2536         m_gestureArea->handleTouchEvent(event);
2537     } else {
2538         //ignore event so sythesized event is generated;
2539         QQuickItem::touchEvent(event);
2540     }
2541 }
2542 
2543 #if QT_CONFIG(wheelevent)
2544 /*!
2545     \internal
2546 */
wheelEvent(QWheelEvent * event)2547 void QDeclarativeGeoMap::wheelEvent(QWheelEvent *event)
2548 {
2549     if (isInteractive())
2550         m_gestureArea->handleWheelEvent(event);
2551     else
2552         QQuickItem::wheelEvent(event);
2553 
2554 }
2555 #endif
2556 
2557 /*!
2558     \internal
2559 */
childMouseEventFilter(QQuickItem * item,QEvent * event)2560 bool QDeclarativeGeoMap::childMouseEventFilter(QQuickItem *item, QEvent *event)
2561 {
2562     Q_UNUSED(item);
2563     if (!isVisible() || !isEnabled() || !isInteractive())
2564         return QQuickItem::childMouseEventFilter(item, event);
2565 
2566     switch (event->type()) {
2567     case QEvent::MouseButtonPress:
2568     case QEvent::MouseMove:
2569     case QEvent::MouseButtonRelease:
2570         return sendMouseEvent(static_cast<QMouseEvent *>(event));
2571     case QEvent::UngrabMouse: {
2572         QQuickWindow *win = window();
2573         if (!win) break;
2574         if (!win->mouseGrabberItem() ||
2575                 (win->mouseGrabberItem() &&
2576                  win->mouseGrabberItem() != this)) {
2577             // child lost grab, we could even lost
2578             // some events if grab already belongs for example
2579             // in item in diffrent window , clear up states
2580             mouseUngrabEvent();
2581         }
2582         break;
2583     }
2584     case QEvent::TouchBegin:
2585     case QEvent::TouchUpdate:
2586     case QEvent::TouchEnd:
2587     case QEvent::TouchCancel:
2588         if (static_cast<QTouchEvent *>(event)->touchPoints().count() >= 2) {
2589             // 1 touch point = handle with MouseEvent (event is always synthesized)
2590             // let the synthesized mouse event grab the mouse,
2591             // note there is no mouse grabber at this point since
2592             // touch event comes first (see Qt::AA_SynthesizeMouseForUnhandledTouchEvents)
2593             return sendTouchEvent(static_cast<QTouchEvent *>(event));
2594         }
2595     default:
2596         break;
2597     }
2598     return QQuickItem::childMouseEventFilter(item, event);
2599 }
2600 
sendMouseEvent(QMouseEvent * event)2601 bool QDeclarativeGeoMap::sendMouseEvent(QMouseEvent *event)
2602 {
2603     QPointF localPos = mapFromScene(event->windowPos());
2604     QQuickWindow *win = window();
2605     QQuickItem *grabber = win ? win->mouseGrabberItem() : 0;
2606     bool stealEvent = m_gestureArea->isActive();
2607 
2608     if ((stealEvent || contains(localPos)) && (!grabber || (!grabber->keepMouseGrab() && !grabber->keepTouchGrab()))) {
2609         QScopedPointer<QMouseEvent> mouseEvent(QQuickWindowPrivate::cloneMouseEvent(event, &localPos));
2610         mouseEvent->setAccepted(false);
2611 
2612         switch (mouseEvent->type()) {
2613         case QEvent::MouseMove:
2614             m_gestureArea->handleMouseMoveEvent(mouseEvent.data());
2615             break;
2616         case QEvent::MouseButtonPress:
2617             m_gestureArea->handleMousePressEvent(mouseEvent.data());
2618             break;
2619         case QEvent::MouseButtonRelease:
2620             m_gestureArea->handleMouseReleaseEvent(mouseEvent.data());
2621             break;
2622         default:
2623             break;
2624         }
2625 
2626         stealEvent = m_gestureArea->isActive();
2627         grabber = win ? win->mouseGrabberItem() : 0;
2628 
2629         if (grabber && stealEvent && !grabber->keepMouseGrab() && !grabber->keepTouchGrab() && grabber != this)
2630             grabMouse();
2631 
2632         if (stealEvent) {
2633             //do not deliver
2634             event->setAccepted(true);
2635             return true;
2636         } else {
2637             return false;
2638         }
2639     }
2640 
2641     return false;
2642 }
2643 
sendTouchEvent(QTouchEvent * event)2644 bool QDeclarativeGeoMap::sendTouchEvent(QTouchEvent *event)
2645 {
2646     QQuickPointerDevice *touchDevice = QQuickPointerDevice::touchDevice(event->device());
2647     const QTouchEvent::TouchPoint &point = event->touchPoints().first();
2648     QQuickWindowPrivate *windowPriv = QQuickWindowPrivate::get(window());
2649 
2650     auto touchPointGrabberItem = [touchDevice, windowPriv](const QTouchEvent::TouchPoint &point) -> QQuickItem* {
2651         if (QQuickEventPoint *eventPointer = windowPriv->pointerEventInstance(touchDevice)->pointById(point.id()))
2652             return eventPointer->grabberItem();
2653         return nullptr;
2654     };
2655 
2656     QQuickItem *grabber = touchPointGrabberItem(point);
2657 
2658     bool stealEvent = m_gestureArea->isActive();
2659     bool containsPoint = contains(mapFromScene(point.scenePos()));
2660 
2661     if ((stealEvent || containsPoint) && (!grabber || !grabber->keepTouchGrab())) {
2662         QScopedPointer<QTouchEvent> touchEvent(new QTouchEvent(event->type(), event->device(), event->modifiers(), event->touchPointStates(), event->touchPoints()));
2663         touchEvent->setTimestamp(event->timestamp());
2664         touchEvent->setAccepted(false);
2665 
2666         m_gestureArea->handleTouchEvent(touchEvent.data());
2667         stealEvent = m_gestureArea->isActive();
2668         grabber = touchPointGrabberItem(point);
2669 
2670         if (grabber && stealEvent && !grabber->keepTouchGrab() && grabber != this) {
2671             QVector<int> ids;
2672             foreach (const QTouchEvent::TouchPoint &tp, event->touchPoints()) {
2673                 if (!(tp.state() & Qt::TouchPointReleased)) {
2674                     ids.append(tp.id());
2675                 }
2676             }
2677             grabTouchPoints(ids);
2678         }
2679 
2680         if (stealEvent) {
2681             //do not deliver
2682             event->setAccepted(true);
2683             return true;
2684         } else {
2685             return false;
2686         }
2687     }
2688 
2689     return false;
2690 }
2691 
2692 QT_END_NAMESPACE
2693