diff options
Diffstat (limited to 'src/location/quickmapitems/qdeclarativegeomap.cpp')
-rw-r--r-- | src/location/quickmapitems/qdeclarativegeomap.cpp | 2619 |
1 files changed, 2619 insertions, 0 deletions
diff --git a/src/location/quickmapitems/qdeclarativegeomap.cpp b/src/location/quickmapitems/qdeclarativegeomap.cpp new file mode 100644 index 00000000..0823772c --- /dev/null +++ b/src/location/quickmapitems/qdeclarativegeomap.cpp @@ -0,0 +1,2619 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativegeomap_p.h" +#include "qdeclarativegeomapquickitem_p.h" +#include "qdeclarativegeomapcopyrightsnotice_p.h" +#include "qdeclarativegeoserviceprovider_p.h" +#include "qgeomappingmanager_p.h" +#include "qgeocameracapabilities_p.h" +#include "qgeomap_p.h" +#include "qdeclarativegeomapparameter_p.h" +#include "qgeomapobject_p.h" +#include "qgeoprojection_p.h" +#include <QtPositioning/QGeoCircle> +#include <QtPositioning/QGeoRectangle> +#include <QtPositioning/QGeoPath> +#include <QtPositioning/QGeoPolygon> +#include <QtQuick/QQuickWindow> +#include <QtQuick/QSGRectangleNode> +#include <QtQml/qqmlinfo.h> +#include <QtQuick/private/qquickitem_p.h> +#include <cmath> + +#ifndef M_PI +#define M_PI 3.141592653589793238463 +#endif + + +QT_BEGIN_NAMESPACE + +static qreal sanitizeBearing(qreal bearing) +{ + bearing = std::fmod(bearing, qreal(360.0)); + if (bearing < 0.0) + bearing += 360.0; + + return bearing; +} + +/*! + \qmltype Map + \instantiates QDeclarativeGeoMap + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-maps + \since QtLocation 5.0 + + \brief The Map type displays a map. + + The Map type is used to display a map or image of the Earth, with + the capability to also display interactive objects tied to the map's + surface. + + There are a variety of different ways to visualize the Earth's surface + in a 2-dimensional manner, but all of them involve some kind of projection: + a mathematical relationship between the 3D coordinates (latitude, longitude + and altitude) and 2D coordinates (X and Y in pixels) on the screen. + + Different sources of map data can use different projections, and from the + point of view of the Map type, we treat these as one replaceable unit: + the Map plugin. A Map plugin consists of a data source, as well as all other + details needed to display its data on-screen. + + The current Map plugin in use is contained in the \l plugin property of + the Map item. In order to display any image in a Map item, you will need + to set this property. See the \l Plugin type for a description of how + to retrieve an appropriate plugin for use. + + The geographic region displayed in the Map item is referred to as its + viewport, and this is defined by the properties \l center, and + \l zoomLevel. The \l center property contains a \l {coordinate} + specifying the center of the viewport, while \l zoomLevel controls the scale of the + map. See each of these properties for further details about their values. + + When the map is displayed, each possible geographic coordinate that is + visible will map to some pixel X and Y coordinate on the screen. To perform + conversions between these two, Map provides the \l toCoordinate and + \l fromCoordinate functions, which are of general utility. + + \section2 Map Objects + + Map related objects can be declared within the body of a Map object in Qt Quick and will + automatically appear on the Map. To add an object programmatically, first be + sure it is created with the Map as its parent (for example in an argument to + Component::createObject). + Then call the \l addMapItem method on the Map, if the type of this object is one of + \l MapCircle, \l MapRectangle, \l MapPolyline, \l MapPolygon, \l MapRoute or \l MapQuickItem. + A corresponding \l removeMapItem method also exists to do the opposite and + remove any of the above types of map objects from the Map. + + Moving Map objects around, resizing them or changing their shape normally + does not involve any special interaction with Map itself -- changing these + properties in a map object will automatically update the display. + + \section2 Interaction + + The Map type includes support for pinch and flick gestures to control + zooming and panning. These are enabled by default, and available at any + time by using the \l gesture object. The actual GestureArea is constructed + specially at startup and cannot be replaced or destroyed. Its properties + can be altered, however, to control its behavior. + + \section2 Performance + + Maps are rendered using OpenGL (ES) and the Qt Scene Graph stack, and as + a result perform quite well where GL accelerated hardware is available. + + For "online" Map plugins, network bandwidth and latency can be major + contributors to the user's perception of performance. Extensive caching is + performed to mitigate this, but such mitigation is not always perfect. For + "offline" plugins, the time spent retrieving the stored geographic data + and rendering the basic map features can often play a dominant role. Some + offline plugins may use hardware acceleration themselves to (partially) + avert this. + + In general, large and complex Map items such as polygons and polylines with + large numbers of vertices can have an adverse effect on UI performance. + Further, more detailed notes on this are in the documentation for each + map item type. + + \section2 Example Usage + + The following snippet shows a simple Map and the necessary Plugin type + to use it. The map is centered over Oslo, Norway, with zoom level 14. + + \quotefromfile minimal_map/main.qml + \skipto import + \printuntil } + \printline } + \skipto Map + \printuntil } + \printline } + + \image minimal_map.png +*/ + +/*! + \qmlsignal QtLocation::Map::copyrightLinkActivated(string link) + + This signal is emitted when the user clicks on a \a link in the copyright notice. The + application should open the link in a browser or display its contents to the user. +*/ + +QDeclarativeGeoMap::QDeclarativeGeoMap(QQuickItem *parent) + : QQuickItem(parent) +{ + m_gestureArea = new QQuickGeoMapGestureArea(this); + + setAcceptHoverEvents(false); + setAcceptTouchEvents(true); + setAcceptedMouseButtons(Qt::LeftButton); + setFlags(QQuickItem::ItemHasContents | QQuickItem::ItemClipsChildrenToShape); + setFiltersChildMouseEvents(true); // needed for childMouseEventFilter to work. + + m_activeMapType = QGeoMapType(QGeoMapType::NoMap, + tr("No Map"), + tr("No Map"), + false, false, + 0, + QByteArrayLiteral(""), + QGeoCameraCapabilities()); + m_cameraData.setCenter(QGeoCoordinate(51.5073,-0.1277)); //London city center + m_cameraData.setZoomLevel(8.0); + + m_cameraCapabilities.setTileSize(256); + m_cameraCapabilities.setSupportsBearing(true); + m_cameraCapabilities.setSupportsTilting(true); + m_cameraCapabilities.setMinimumZoomLevel(0); + m_cameraCapabilities.setMaximumZoomLevel(30); + m_cameraCapabilities.setMinimumTilt(0); + m_cameraCapabilities.setMaximumTilt(89.5); + m_cameraCapabilities.setMinimumFieldOfView(1); + m_cameraCapabilities.setMaximumFieldOfView(179); + + m_minimumTilt = m_cameraCapabilities.minimumTilt(); + m_maximumTilt = m_cameraCapabilities.maximumTilt(); + m_minimumFieldOfView = m_cameraCapabilities.minimumFieldOfView(); + m_maximumFieldOfView = m_cameraCapabilities.maximumFieldOfView(); +} + +QDeclarativeGeoMap::~QDeclarativeGeoMap() +{ + // Removing map parameters and map items from m_map + if (m_map) { + m_map->clearParameters(); + m_map->clearMapItems(); + } + + // Remove the items from the map, making them deletable. + // Go in the same order as in removeMapChild: views, groups, then items + if (!m_mapViews.isEmpty()) { + const auto mapViews = m_mapViews; + for (QDeclarativeGeoMapItemView *v : mapViews) { // so that removeMapItemView_real can safely modify m_mapViews; + if (!v) + continue; + + QQuickItem *parent = v->parentItem(); + QDeclarativeGeoMapItemGroup *group = qobject_cast<QDeclarativeGeoMapItemGroup *>(parent); + if (group) + continue; // Ignore non-top-level MIVs. They will be recursively processed. + // Identify them as being parented by a MapItemGroup. + + removeMapItemView_real(v); + } + } + + if (!m_mapItemGroups.isEmpty()) { + const auto mapGroups = m_mapItemGroups; + for (QDeclarativeGeoMapItemGroup *g : mapGroups) { + if (!g) + continue; + + QQuickItem *parent =g->parentItem(); + QDeclarativeGeoMapItemGroup *group = qobject_cast<QDeclarativeGeoMapItemGroup *>(parent); + if (group) + continue; // Ignore non-top-level Groups. They will be recursively processed. + // Identify them as being parented by a MapItemGroup. + + removeMapItemGroup_real(g); + } + } + + // remove any remaining map items associations + const auto mapItems = m_mapItems; + for (auto mi: mapItems) + removeMapItem_real(mi.data()); + + if (m_copyrights.data()) + delete m_copyrights.data(); + m_copyrights.clear(); + + for (auto obj: qAsConst(m_pendingMapObjects)) + obj->setMap(nullptr); // worst case: going to be setMap(nullptr)'d twice + + delete m_map; // map objects get reset here +} + +void QDeclarativeGeoMap::onSupportedMapTypesChanged() +{ + m_supportedMapTypes = m_mappingManager->supportedMapTypes(); + if (m_supportedMapTypes.isEmpty()) { + m_map->setActiveMapType(QGeoMapType()); // no supported map types: setting an invalid one + } else if (!m_supportedMapTypes.contains(m_map->activeMapType())) { + QGeoMapType type = m_supportedMapTypes.at(0); + m_activeMapType = type; + m_map->setActiveMapType(type); + } + + emit supportedMapTypesChanged(); +} + +void QDeclarativeGeoMap::setError(QGeoServiceProvider::Error error, const QString &errorString) +{ + if (m_error == error && m_errorString == errorString) + return; + m_error = error; + m_errorString = errorString; + emit errorChanged(); +} + +/*! + \internal + Called when the mapping manager is initialized AND the declarative element has a valid size > 0 +*/ +void QDeclarativeGeoMap::initialize() +{ + // try to keep change signals in the end + bool visibleAreaHasChanged = false; + + QGeoCoordinate center = m_cameraData.center(); + + if (!qIsFinite(m_userMinimumZoomLevel)) + setMinimumZoomLevel(m_map->minimumZoom(), false); + else + setMinimumZoomLevel(qMax<qreal>(m_map->minimumZoom(), m_userMinimumZoomLevel), false); + + double bearing = m_cameraData.bearing(); + double tilt = m_cameraData.tilt(); + double fov = m_cameraData.fieldOfView(); // Must be 45.0 + QGeoCameraData cameraData = m_cameraData; + + if (!m_cameraCapabilities.supportsBearing() && bearing != 0.0) + cameraData.setBearing(0); + + if (!m_cameraCapabilities.supportsTilting() && tilt != 0.0) + cameraData.setTilt(0); + + m_map->setVisibleArea(m_visibleArea); + if (m_map->visibleArea() != m_visibleArea) + visibleAreaHasChanged = true; + + cameraData.setFieldOfView(qBound(m_cameraCapabilities.minimumFieldOfView(), + fov, + m_cameraCapabilities.maximumFieldOfView())); + + // set latitude boundary check + m_maximumViewportLatitude = m_map->maximumCenterLatitudeAtZoom(cameraData); + m_minimumViewportLatitude = m_map->minimumCenterLatitudeAtZoom(cameraData); + + center.setLatitude(qBound(m_minimumViewportLatitude, center.latitude(), m_maximumViewportLatitude)); + cameraData.setCenter(center); + + connect(m_map.data(), &QGeoMap::cameraDataChanged, + this, &QDeclarativeGeoMap::onCameraDataChanged); + m_map->setCameraData(cameraData); // This normally triggers property changed signals. + // BUT not in this case, since m_cameraData is already == cameraData. + // So, emit visibleRegionChanged() separately, as + // the effective visible region becomes available only now. + + for (const auto &obj : qAsConst(m_pendingMapObjects)) + obj->setMap(m_map); + + m_initialized = true; + + if (visibleAreaHasChanged) + emit visibleAreaChanged(); + connect(m_map.data(), &QGeoMap::visibleAreaChanged, this, &QDeclarativeGeoMap::visibleAreaChanged); + + emit mapReadyChanged(true); + emit visibleRegionChanged(); + + if (m_copyrights) // To not update during initialize() + update(); +} + +/*! + \internal +*/ +void QDeclarativeGeoMap::pluginReady() +{ + QGeoServiceProvider *provider = m_plugin->sharedGeoServiceProvider(); + m_mappingManager = provider->mappingManager(); + + if (provider->mappingError() != QGeoServiceProvider::NoError) { + setError(provider->mappingError(), provider->mappingErrorString()); + return; + } + + if (!m_mappingManager) { + //TODO Should really be EngineNotSetError (see QML GeoCodeModel) + setError(QGeoServiceProvider::NotSupportedError, tr("Plugin does not support mapping.")); + return; + } + + if (!m_mappingManager->isInitialized()) { + connect(m_mappingManager, &QGeoMappingManager::initialized, + this, &QDeclarativeGeoMap::mappingManagerInitialized); + } else { + mappingManagerInitialized(); + } + + // make sure this is only called once + disconnect(m_plugin, &QDeclarativeGeoServiceProvider::attached, + this, &QDeclarativeGeoMap::pluginReady); +} + +/*! + \internal +*/ +void QDeclarativeGeoMap::componentComplete() +{ + m_componentCompleted = true; + populateParameters(); + populateMap(); + QQuickItem::componentComplete(); +} + +/*! + \qmlproperty MapGestureArea QtLocation::Map::gesture + + Contains the MapGestureArea created with the Map. This covers pan, flick and pinch gestures. + Use \c{gesture.enabled: true} to enable basic gestures, or see \l{MapGestureArea} for + further details. +*/ + +QQuickGeoMapGestureArea *QDeclarativeGeoMap::gesture() +{ + return m_gestureArea; +} + +/*! + \internal + + This may happen before mappingManagerInitialized() +*/ +void QDeclarativeGeoMap::populateMap() +{ + QSet<QObject *> kids(children().cbegin(), children().cend()); + const QList<QQuickItem *> quickKids = childItems(); + for (QQuickItem *ite: quickKids) + kids.insert(ite); + + for (QObject *k : qAsConst(kids)) { + addMapChild(k); + } +} + +void QDeclarativeGeoMap::populateParameters() +{ + QObjectList kids = children(); + const QList<QQuickItem *> quickKids = childItems(); + for (const auto &quickKid : quickKids) + kids.append(quickKid); + for (auto *kid : qAsConst(kids)) { + if (auto *mapParameter = qobject_cast<QDeclarativeGeoMapParameter *>(kid)) + addMapParameter(mapParameter); + } +} + +/*! + \internal +*/ +void QDeclarativeGeoMap::setupMapView(QDeclarativeGeoMapItemView *view) +{ + view->setMap(this); +} + +/*! + * \internal + */ +QSGNode *QDeclarativeGeoMap::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) +{ + if (!m_map) { + delete oldNode; + return nullptr; + } + + QSGRectangleNode *root = static_cast<QSGRectangleNode *>(oldNode); + if (!root) + root = window()->createRectangleNode(); + + root->setRect(boundingRect()); + root->setColor(m_color); + + QSGNode *content = root->childCount() ? root->firstChild() : 0; + content = m_map->updateSceneGraph(content, window()); + if (content && root->childCount() == 0) + root->appendChildNode(content); + + return root; +} + +/*! + \qmlproperty Plugin QtLocation::Map::plugin + + This property holds the plugin which provides the mapping functionality. + + This is a write-once property. Once the map has a plugin associated with + it, any attempted modifications of the plugin will be ignored. +*/ + +void QDeclarativeGeoMap::setPlugin(QDeclarativeGeoServiceProvider *plugin) +{ + if (m_plugin) { + qmlWarning(this) << QStringLiteral("Plugin is a write-once property, and cannot be set again."); + return; + } + m_plugin = plugin; + emit pluginChanged(m_plugin); + + if (m_plugin->isAttached()) { + pluginReady(); + } else { + connect(m_plugin, &QDeclarativeGeoServiceProvider::attached, + this, &QDeclarativeGeoMap::pluginReady); + } +} + +/*! + \internal +*/ +void QDeclarativeGeoMap::onCameraCapabilitiesChanged(const QGeoCameraCapabilities &oldCameraCapabilities) +{ + if (m_map->cameraCapabilities() == oldCameraCapabilities) + return; + m_cameraCapabilities = m_map->cameraCapabilities(); + + //The zoom level limits are only restricted by the plugins values, if the user has set a more + //strict zoom level limit before initialization nothing is done here. + //minimum zoom level might be changed to limit gray bundaries + //This code assumes that plugins' maximum zoom level will never exceed 30.0 + if (m_cameraCapabilities.maximumZoomLevelAt256() < m_gestureArea->maximumZoomLevel()) { + setMaximumZoomLevel(m_cameraCapabilities.maximumZoomLevelAt256(), false); + } else if (m_cameraCapabilities.maximumZoomLevelAt256() > m_gestureArea->maximumZoomLevel()) { + if (!qIsFinite(m_userMaximumZoomLevel)) { + // If the user didn't set anything + setMaximumZoomLevel(m_cameraCapabilities.maximumZoomLevelAt256(), false); + } else { // Try to set what the user requested + // Else if the user set something larger, but that got clamped by the previous camera caps + setMaximumZoomLevel(qMin<qreal>(m_cameraCapabilities.maximumZoomLevelAt256(), + m_userMaximumZoomLevel), false); + } + } + + if (m_cameraCapabilities.minimumZoomLevelAt256() > m_gestureArea->minimumZoomLevel()) { + setMinimumZoomLevel(m_cameraCapabilities.minimumZoomLevelAt256(), false); + } else if (m_cameraCapabilities.minimumZoomLevelAt256() < m_gestureArea->minimumZoomLevel()) { + if (!qIsFinite(m_userMinimumZoomLevel)) { + // If the user didn't set anything, trying to set the new caps. + setMinimumZoomLevel(m_cameraCapabilities.minimumZoomLevelAt256(), false); + } else { // Try to set what the user requested + // Else if the user set a minimum, m_gestureArea->minimumZoomLevel() might be larger + // because of different reasons. Resetting it, as if it ends to be the same, + // no signal will be emitted. + setMinimumZoomLevel(qMax<qreal>(m_cameraCapabilities.minimumZoomLevelAt256(), + m_userMinimumZoomLevel), false); + } + } + + // Tilt + if (m_cameraCapabilities.minimumTilt() > m_minimumTilt) { + setMinimumTilt(m_cameraCapabilities.minimumTilt(), false); + } else if (m_cameraCapabilities.minimumTilt() < m_minimumTilt) { + if (!qIsFinite(m_userMinimumTilt)) + setMinimumTilt(m_cameraCapabilities.minimumTilt(), false); + else // Try to set what the user requested + setMinimumTilt(qMax<qreal>(m_cameraCapabilities.minimumTilt(), m_userMinimumTilt), false); + } + + if (m_cameraCapabilities.maximumTilt() < m_maximumTilt) { + setMaximumTilt(m_cameraCapabilities.maximumTilt(), false); + } else if (m_cameraCapabilities.maximumTilt() > m_maximumTilt) { + if (!qIsFinite(m_userMaximumTilt)) + setMaximumTilt(m_cameraCapabilities.maximumTilt(), false); + else // Try to set what the user requested + setMaximumTilt(qMin<qreal>(m_cameraCapabilities.maximumTilt(), m_userMaximumTilt), false); + } + + // FoV + if (m_cameraCapabilities.minimumFieldOfView() > m_minimumFieldOfView) { + setMinimumFieldOfView(m_cameraCapabilities.minimumFieldOfView(), false); + } else if (m_cameraCapabilities.minimumFieldOfView() < m_minimumFieldOfView) { + if (!qIsFinite(m_userMinimumFieldOfView)) + setMinimumFieldOfView(m_cameraCapabilities.minimumFieldOfView(), false); + else // Try to set what the user requested + setMinimumFieldOfView(qMax<qreal>(m_cameraCapabilities.minimumFieldOfView(), m_userMinimumFieldOfView), false); + } + + if (m_cameraCapabilities.maximumFieldOfView() < m_maximumFieldOfView) { + setMaximumFieldOfView(m_cameraCapabilities.maximumFieldOfView(), false); + } else if (m_cameraCapabilities.maximumFieldOfView() > m_maximumFieldOfView) { + if (!qIsFinite(m_userMaximumFieldOfView)) + setMaximumFieldOfView(m_cameraCapabilities.maximumFieldOfView(), false); + else // Try to set what the user requested + setMaximumFieldOfView(qMin<qreal>(m_cameraCapabilities.maximumFieldOfView(), m_userMaximumFieldOfView), false); + } +} + +/*! + \internal + this function will only be ever called once +*/ +void QDeclarativeGeoMap::mappingManagerInitialized() +{ + m_map = m_mappingManager->createMap(this); + + if (!m_map) + return; + + // Any map items that were added before the plugin was ready + // need to have setMap called again + for (const QPointer<QDeclarativeGeoMapItemBase> &item : qAsConst(m_mapItems)) { + if (item) { + item->setMap(this, m_map); + m_map->addMapItem(item.data()); // m_map filters out what is not supported. + } + } + + /* COPY NOTICE SETUP */ + m_copyrights = new QDeclarativeGeoMapCopyrightNotice(this); + m_copyrights->setCopyrightsZ(m_maxChildZ + 1); + m_copyrights->setCopyrightsVisible(m_copyrightsVisible); + m_copyrights->setMapSource(this); + + m_gestureArea->setMap(m_map); + + m_supportedMapTypes = m_mappingManager->supportedMapTypes(); + + if (m_activeMapType != QGeoMapType() && m_plugin->name().toLatin1() == m_activeMapType.pluginName()) { + m_map->setActiveMapType(m_activeMapType); + } else { + if (!m_supportedMapTypes.isEmpty()) { + m_activeMapType = m_supportedMapTypes.at(0); + m_map->setActiveMapType(m_activeMapType); + } else { + m_activeMapType = QGeoMapType(QGeoMapType::NoMap, + tr("No Map"), + tr("No Map"), + false, + false, + 0, + QByteArrayLiteral(""), + QGeoCameraCapabilities()); + } + } + + // Update camera capabilities + onCameraCapabilitiesChanged(m_cameraCapabilities); + + // Map tiles are built in this call. m_map->minimumZoom() becomes operational + // after this has been called at least once, after creation. + // However, getting into the following block may fire a copyrightsChanged that would get lost, + // as the connections are set up after. + QString copyrightString; + QImage copyrightImage; + if (!m_initialized && width() > 0 && height() > 0) { + QMetaObject::Connection copyrightStringCatcherConnection = + connect(m_map.data(), &QGeoMap::copyrightsChanged, + [©rightString](const QString ©){ copyrightString = copy; }); + QMetaObject::Connection copyrightImageCatcherConnection = + connect(m_map.data(), &QGeoMap::copyrightsImageChanged, + [©rightImage](const QImage ©){ copyrightImage = copy; }); + m_map->setViewportSize(QSize(width(), height())); + initialize(); // This emits the caught signals above + QObject::disconnect(copyrightStringCatcherConnection); + QObject::disconnect(copyrightImageCatcherConnection); + } + + + /* COPYRIGHT SIGNALS REWIRING */ + connect(m_map.data(), &QGeoMap::copyrightsImageChanged, + this, &QDeclarativeGeoMap::copyrightsImageChanged); + connect(m_map.data(), &QGeoMap::copyrightsChanged, + this, &QDeclarativeGeoMap::copyrightsChanged); + if (!copyrightString.isEmpty()) + emit m_map->copyrightsChanged(copyrightString); + else if (!copyrightImage.isNull()) + emit m_map->copyrightsImageChanged(copyrightImage); + + m_window = window(); + if (m_window) { + connect(m_window, &QQuickWindow::beforeSynchronizing, + this, &QDeclarativeGeoMap::updateItemToWindowTransform, Qt::DirectConnection); + } + connect(m_map.data(), &QGeoMap::sgNodeChanged, this, &QDeclarativeGeoMap::onSGNodeChanged); + connect(m_map.data(), &QGeoMap::cameraCapabilitiesChanged, + this, &QDeclarativeGeoMap::onCameraCapabilitiesChanged); + + // This prefetches a buffer around the map + m_map->prefetchData(); + + connect(m_mappingManager, &QGeoMappingManager::supportedMapTypesChanged, + this, &QDeclarativeGeoMap::onSupportedMapTypesChanged); + emit minimumZoomLevelChanged(); + emit maximumZoomLevelChanged(); + emit supportedMapTypesChanged(); + emit activeMapTypeChanged(); + + // Any map item groups that were added before the plugin was ready + // DO NOT need to have setMap called again on their children map items + // because they have been added to m_mapItems, which is processed right above. + + + // All map parameters that were added before the plugin was ready + // need to be added to m_map + for (QDeclarativeGeoMapParameter *p : qAsConst(m_mapParameters)) + m_map->addParameter(p); + + if (m_initialized) + update(); +} + +/*! + \internal +*/ +QDeclarativeGeoServiceProvider *QDeclarativeGeoMap::plugin() const +{ + return m_plugin; +} + +/*! + \internal + Sets the gesture areas minimum zoom level. If the camera capabilities + has been set this method honors the boundaries set by it. + The minimum zoom level will also have a lower bound dependent on the size + of the canvas, effectively preventing to display out of bounds areas. +*/ +void QDeclarativeGeoMap::setMinimumZoomLevel(qreal minimumZoomLevel, bool userSet) +{ + if (minimumZoomLevel >= 0) { + qreal oldUserMinimumZoomLevel = m_userMinimumZoomLevel; + if (userSet) + m_userMinimumZoomLevel = minimumZoomLevel; + qreal oldMinimumZoomLevel = this->minimumZoomLevel(); + + minimumZoomLevel = qBound(qreal(m_cameraCapabilities.minimumZoomLevelAt256()), minimumZoomLevel, maximumZoomLevel()); + if (m_map) + minimumZoomLevel = qMax<qreal>(minimumZoomLevel, m_map->minimumZoom()); + + // minimumZoomLevel is, at this point, the implicit minimum zoom level + m_gestureArea->setMinimumZoomLevel(minimumZoomLevel); + + if (zoomLevel() < minimumZoomLevel && (m_gestureArea->enabled() || !m_cameraCapabilities.overzoomEnabled())) + setZoomLevel(minimumZoomLevel); + + if (qIsNaN(m_userMinimumZoomLevel) && oldMinimumZoomLevel != minimumZoomLevel) + emit minimumZoomLevelChanged(); + else if (userSet && oldUserMinimumZoomLevel != m_userMinimumZoomLevel) + emit minimumZoomLevelChanged(); + } +} + +/*! + \qmlproperty real QtLocation::Map::minimumZoomLevel + + This property holds the minimum valid zoom level for the map. + + The minimum zoom level defined by the \l plugin used is a lower bound for + this property. However, the returned value is also canvas-size-dependent, and + can be higher than the user-specified value, or than the minimum zoom level + defined by the plugin used, to prevent the map from being smaller than the + viewport in either dimension. + + If the \l plugin property is not set or the plugin does not support mapping, this property is \c 0. +*/ + +qreal QDeclarativeGeoMap::minimumZoomLevel() const +{ + if (!qIsNaN(m_userMinimumZoomLevel)) + return m_userMinimumZoomLevel; + else + return m_gestureArea->minimumZoomLevel(); +} + +/*! + \internal +*/ +qreal QDeclarativeGeoMap::implicitMinimumZoomLevel() const +{ + return m_gestureArea->minimumZoomLevel(); +} + +/*! + \internal +*/ +qreal QDeclarativeGeoMap::effectiveMinimumZoomLevel() const +{ + return qMax<qreal>(minimumZoomLevel(), implicitMinimumZoomLevel()); +} + +/*! + \internal + Sets the gesture areas maximum zoom level. If the camera capabilities + has been set this method honors the boundaries set by it. +*/ +void QDeclarativeGeoMap::setMaximumZoomLevel(qreal maximumZoomLevel, bool userSet) +{ + if (maximumZoomLevel >= 0) { + if (userSet) + m_userMaximumZoomLevel = maximumZoomLevel; + qreal oldMaximumZoomLevel = this->maximumZoomLevel(); + + maximumZoomLevel = qBound(minimumZoomLevel(), maximumZoomLevel, qreal(m_cameraCapabilities.maximumZoomLevelAt256())); + + m_gestureArea->setMaximumZoomLevel(maximumZoomLevel); + + if (zoomLevel() > maximumZoomLevel && (m_gestureArea->enabled() || !m_cameraCapabilities.overzoomEnabled())) + setZoomLevel(maximumZoomLevel); + + if (oldMaximumZoomLevel != maximumZoomLevel) + emit maximumZoomLevelChanged(); + } +} + +/*! + \qmlproperty real QtLocation::Map::maximumZoomLevel + + This property holds the maximum valid zoom level for the map. + + The maximum zoom level is defined by the \l plugin used. + If the \l plugin property is not set or the plugin does not support mapping, this property is \c 30. +*/ + +qreal QDeclarativeGeoMap::maximumZoomLevel() const +{ + return m_gestureArea->maximumZoomLevel(); +} + +/*! + \qmlproperty real QtLocation::Map::zoomLevel + + This property holds the zoom level for the map. + + Larger values for the zoom level provide more detail. Zoom levels + are always non-negative. The default value is 8.0. Depending on the plugin in use, + values outside the [minimumZoomLevel, maximumZoomLevel] range, which represent the range for which + tiles are available, may be accepted, or clamped. +*/ +void QDeclarativeGeoMap::setZoomLevel(qreal zoomLevel) +{ + return setZoomLevel(zoomLevel, m_cameraCapabilities.overzoomEnabled()); +} + +/*! + \internal + + Sets the zoom level. + Larger values for the zoom level provide more detail. Zoom levels + are always non-negative. The default value is 8.0. Values outside the + [minimumZoomLevel, maximumZoomLevel] range, which represent the range for which + tiles are available, can be accepted or clamped by setting the overzoom argument + to true or false respectively. +*/ +void QDeclarativeGeoMap::setZoomLevel(qreal zoomLevel, bool overzoom) +{ + if (zoomLevel < 0) + return; + + if (m_initialized) { + QGeoCameraData cameraData = m_map->cameraData(); + if (cameraData.zoomLevel() == zoomLevel) + return; + + cameraData.setZoomLevel(qBound<qreal>(overzoom ? m_map->minimumZoom() : effectiveMinimumZoomLevel(), + zoomLevel, + overzoom ? 30 : maximumZoomLevel())); + m_maximumViewportLatitude = m_map->maximumCenterLatitudeAtZoom(cameraData); + m_minimumViewportLatitude = m_map->minimumCenterLatitudeAtZoom(cameraData); + QGeoCoordinate coord = cameraData.center(); + coord.setLatitude(qBound(m_minimumViewportLatitude, coord.latitude(), m_maximumViewportLatitude)); + cameraData.setCenter(coord); + m_map->setCameraData(cameraData); + } else { + const bool zlHasChanged = zoomLevel != m_cameraData.zoomLevel(); + m_cameraData.setZoomLevel(zoomLevel); + if (zlHasChanged) { + emit zoomLevelChanged(zoomLevel); + // do not emit visibleRegionChanged() here, because, if the map isn't initialized, + // the getter won't return anything updated + } + } +} + +bool QDeclarativeGeoMap::addMapChild(QObject *child) +{ + // dispatch items appropriately + QDeclarativeGeoMapItemView *mapView = qobject_cast<QDeclarativeGeoMapItemView *>(child); + if (mapView) + return addMapItemView_real(mapView); + + QDeclarativeGeoMapItemGroup *itemGroup = qobject_cast<QDeclarativeGeoMapItemGroup *>(child); + if (itemGroup) // addMapItemView calls addMapItemGroup + return addMapItemGroup_real(itemGroup); + + QDeclarativeGeoMapItemBase *mapItem = qobject_cast<QDeclarativeGeoMapItemBase *>(child); + if (mapItem) + return addMapItem_real(mapItem); + + QGeoMapObject *mapObject = qobject_cast<QGeoMapObject *>(child); + if (mapObject) + addMapObject(mapObject); // this emits mapObjectsChanged, != mapItemsChanged + return false; +} + +bool QDeclarativeGeoMap::removeMapChild(QObject *child) +{ + // dispatch items appropriately + QDeclarativeGeoMapItemView *mapView = qobject_cast<QDeclarativeGeoMapItemView *>(child); + if (mapView) + return removeMapItemView_real(mapView); + + QDeclarativeGeoMapItemGroup *itemGroup = qobject_cast<QDeclarativeGeoMapItemGroup *>(child); + if (itemGroup) // removeMapItemView calls removeMapItemGroup for itself. + return removeMapItemGroup_real(itemGroup); + + QDeclarativeGeoMapItemBase *mapItem = qobject_cast<QDeclarativeGeoMapItemBase *>(child); + if (mapItem) + return removeMapItem_real(mapItem); + + QGeoMapObject *mapObject = qobject_cast<QGeoMapObject *>(child); + if (mapObject) + removeMapObject(mapObject); // this emits mapObjectsChanged, != mapItemsChanged + return false; +} + +bool QDeclarativeGeoMap::isGroupNested(QDeclarativeGeoMapItemGroup *group) const +{ + QObject *parent = group->parent(); + // Nested groups have parent set in parent's componentComplete() + // Those instantiated by MapItemView's delegateModel, however, do not, + // but have setParentItem set. + return qobject_cast<QDeclarativeGeoMapItemGroup *>(parent) + || qobject_cast<QDeclarativeGeoMapItemGroup *>(group->parentItem()); +} + +qreal QDeclarativeGeoMap::zoomLevel() const +{ + if (m_initialized) + return m_map->cameraData().zoomLevel(); + return m_cameraData.zoomLevel(); +} + +/*! + \qmlproperty real QtLocation::Map::bearing + + This property holds the bearing for the map. + The default value is 0. + If the Plugin used for the Map supports bearing, the valid range for this value is between 0 and 360. + If the Plugin used for the Map does not support bearing, changing this property will have no effect. + + \since QtLocation 5.9 +*/ +void QDeclarativeGeoMap::setBearing(qreal bearing) +{ + bearing = sanitizeBearing(bearing); + if (m_initialized) { + QGeoCameraData cameraData = m_map->cameraData(); + cameraData.setBearing(bearing); + m_map->setCameraData(cameraData); + } else { + const bool bearingHasChanged = bearing != m_cameraData.bearing(); + m_cameraData.setBearing(bearing); + if (bearingHasChanged) { + emit bearingChanged(bearing); + // do not emit visibleRegionChanged() here, because, if the map isn't initialized, + // the getter won't return anything updated + } + } +} + +/*! + \qmlmethod void QtLocation::Map::setBearing(real bearing, coordinate coordinate) + + Sets the bearing for the map to \a bearing, rotating it around \a coordinate. + If the Plugin used for the Map supports bearing, the valid range for \a bearing is between 0 and 360. + If the Plugin used for the Map does not support bearing, or if the map is tilted and \a coordinate happens + to be behind the camera, or if the map is not ready (see \l mapReady), calling this method will have no effect. + + The release of this API with Qt 5.10 is a Technology Preview. + + \since 5.10 +*/ +void QDeclarativeGeoMap::setBearing(qreal bearing, const QGeoCoordinate &coordinate) +{ + if (!m_initialized) + return; + + const QGeoCoordinate currentCenter = center(); + const qreal currentBearing = QDeclarativeGeoMap::bearing(); + bearing = sanitizeBearing(bearing); + + if (!coordinate.isValid() + || !qIsFinite(bearing) + || (coordinate == currentCenter && bearing == currentBearing)) + return; + + if (m_map->capabilities() & QGeoMap::SupportsSetBearing) + m_map->setBearing(bearing, coordinate); +} + +qreal QDeclarativeGeoMap::bearing() const +{ + if (m_initialized) + return m_map->cameraData().bearing(); + return m_cameraData.bearing(); +} + +/*! + \qmlproperty real QtLocation::Map::tilt + + This property holds the tilt for the map, in degrees. + The default value is 0. + The valid range for this value is [ minimumTilt, maximumTilt ]. + If the Plugin used for the Map does not support tilting, changing this property will have no effect. + + \sa minimumTilt, maximumTilt + + \since QtLocation 5.9 +*/ +void QDeclarativeGeoMap::setTilt(qreal tilt) +{ + tilt = qBound(minimumTilt(), tilt, maximumTilt()); + + if (m_initialized) { + QGeoCameraData cameraData = m_map->cameraData(); + cameraData.setTilt(tilt); + m_map->setCameraData(cameraData); + } else { + const bool tiltHasChanged = tilt != m_cameraData.tilt(); + m_cameraData.setTilt(tilt); + if (tiltHasChanged) { + emit tiltChanged(tilt); + // do not emit visibleRegionChanged() here, because, if the map isn't initialized, + // the getter won't return anything updated + } + } +} + +qreal QDeclarativeGeoMap::tilt() const +{ + if (m_initialized) + return m_map->cameraData().tilt(); + return m_cameraData.tilt(); +} + +void QDeclarativeGeoMap::setMinimumTilt(qreal minimumTilt, bool userSet) +{ + if (minimumTilt >= 0) { + if (userSet) + m_userMinimumTilt = minimumTilt; + qreal oldMinimumTilt = this->minimumTilt(); + + m_minimumTilt = qBound<double>(m_cameraCapabilities.minimumTilt(), + minimumTilt, + m_cameraCapabilities.maximumTilt()); + + if (tilt() < m_minimumTilt) + setTilt(m_minimumTilt); + + if (oldMinimumTilt != m_minimumTilt) + emit minimumTiltChanged(m_minimumTilt); + } +} + +/*! + \qmlproperty real QtLocation::Map::fieldOfView + + This property holds the field of view of the camera used to look at the map, in degrees. + If the plugin property of the map is not set, or the plugin does not support mapping, the value is 45 degrees. + + Note that changing this value implicitly changes also the distance between the camera and the map, + so that, at a tilting angle of 0 degrees, the resulting image is identical for any value used for this property. + + For more information about this parameter, consult the Wikipedia articles about \l {https://en.wikipedia.org/wiki/Field_of_view} {Field of view} and + \l {https://en.wikipedia.org/wiki/Angle_of_view} {Angle of view}. + + \sa minimumFieldOfView, maximumFieldOfView + + \since QtLocation 5.9 +*/ +void QDeclarativeGeoMap::setFieldOfView(qreal fieldOfView) +{ + fieldOfView = qBound(minimumFieldOfView(), fieldOfView, maximumFieldOfView()); + + if (m_initialized) { + QGeoCameraData cameraData = m_map->cameraData(); + cameraData.setFieldOfView(fieldOfView); + m_map->setCameraData(cameraData); + } else { + const bool fovChanged = fieldOfView != m_cameraData.fieldOfView(); + m_cameraData.setFieldOfView(fieldOfView); + if (fovChanged) { + emit fieldOfViewChanged(fieldOfView); + // do not emit visibleRegionChanged() here, because, if the map isn't initialized, + // the getter won't return anything updated + } + } +} + +qreal QDeclarativeGeoMap::fieldOfView() const +{ + if (m_initialized) + return m_map->cameraData().fieldOfView(); + return m_cameraData.fieldOfView(); +} + +void QDeclarativeGeoMap::setMinimumFieldOfView(qreal minimumFieldOfView, bool userSet) +{ + if (minimumFieldOfView > 0 && minimumFieldOfView < 180.0) { + if (userSet) + m_userMinimumFieldOfView = minimumFieldOfView; + qreal oldMinimumFoV = this->minimumFieldOfView(); + + m_minimumFieldOfView = qBound<double>(m_cameraCapabilities.minimumFieldOfView(), + minimumFieldOfView, + m_cameraCapabilities.maximumFieldOfView()); + + if (fieldOfView() < m_minimumFieldOfView) + setFieldOfView(m_minimumFieldOfView); + + if (oldMinimumFoV != m_minimumFieldOfView) + emit minimumFieldOfViewChanged(m_minimumFieldOfView); + } +} + +/*! + \qmlproperty real QtLocation::Map::minimumFieldOfView + + This property holds the minimum valid field of view for the map, in degrees. + + The minimum tilt field of view by the \l plugin used is a lower bound for + this property. + If the \l plugin property is not set or the plugin does not support mapping, this property is \c 1. + + \sa fieldOfView, maximumFieldOfView + + \since QtLocation 5.9 +*/ +qreal QDeclarativeGeoMap::minimumFieldOfView() const +{ + return m_minimumFieldOfView; +} + +void QDeclarativeGeoMap::setMaximumFieldOfView(qreal maximumFieldOfView, bool userSet) +{ + if (maximumFieldOfView > 0 && maximumFieldOfView < 180.0) { + if (userSet) + m_userMaximumFieldOfView = maximumFieldOfView; + qreal oldMaximumFoV = this->maximumFieldOfView(); + + m_maximumFieldOfView = qBound<double>(m_cameraCapabilities.minimumFieldOfView(), + maximumFieldOfView, + m_cameraCapabilities.maximumFieldOfView()); + + if (fieldOfView() > m_maximumFieldOfView) + setFieldOfView(m_maximumFieldOfView); + + if (oldMaximumFoV != m_maximumFieldOfView) + emit maximumFieldOfViewChanged(m_maximumFieldOfView); + } +} + +/*! + \qmlproperty real QtLocation::Map::maximumFieldOfView + + This property holds the maximum valid field of view for the map, in degrees. + + The minimum tilt field of view by the \l plugin used is an upper bound for + this property. + If the \l plugin property is not set or the plugin does not support mapping, this property is \c 179. + + \sa fieldOfView, minimumFieldOfView + + \since QtLocation 5.9 +*/ +qreal QDeclarativeGeoMap::maximumFieldOfView() const +{ + return m_maximumFieldOfView; +} + +/*! + \qmlproperty real QtLocation::Map::minimumTilt + + This property holds the minimum valid tilt for the map, in degrees. + + The minimum tilt defined by the \l plugin used is a lower bound for + this property. + If the \l plugin property is not set or the plugin does not support mapping, this property is \c 0. + + Since QtLocation 5.12, plugins can additionally restrict this value depending on the current zoom level. + + \sa tilt, maximumTilt + + \since QtLocation 5.9 +*/ +qreal QDeclarativeGeoMap::minimumTilt() const +{ + return m_minimumTilt; +} + +void QDeclarativeGeoMap::setMaximumTilt(qreal maximumTilt, bool userSet) +{ + if (maximumTilt >= 0) { + if (userSet) + m_userMaximumTilt = maximumTilt; + qreal oldMaximumTilt = this->maximumTilt(); + + m_maximumTilt = qBound<double>(m_cameraCapabilities.minimumTilt(), + maximumTilt, + m_cameraCapabilities.maximumTilt()); + + if (tilt() > m_maximumTilt) + setTilt(m_maximumTilt); + + if (oldMaximumTilt != m_maximumTilt) + emit maximumTiltChanged(m_maximumTilt); + } +} + +/*! + \qmlproperty real QtLocation::Map::maximumTilt + + This property holds the maximum valid tilt for the map, in degrees. + + The maximum tilt defined by the \l plugin used is an upper bound for + this property. + If the \l plugin property is not set or the plugin does not support mapping, this property is \c 89.5. + + Since QtLocation 5.12, plugins can additionally restrict this value depending on the current zoom level. + + \sa tilt, minimumTilt + + \since QtLocation 5.9 +*/ +qreal QDeclarativeGeoMap::maximumTilt() const +{ + return m_maximumTilt; +} + +/*! + \qmlproperty coordinate QtLocation::Map::center + + This property holds the coordinate which occupies the center of the + mapping viewport. Invalid center coordinates are ignored. + + The default value is an arbitrary valid coordinate. +*/ +void QDeclarativeGeoMap::setCenter(const QGeoCoordinate ¢er) +{ + if (!center.isValid()) + return; + + if (m_initialized) { + QGeoCoordinate coord(center); + coord.setLatitude(qBound(m_minimumViewportLatitude, center.latitude(), m_maximumViewportLatitude)); + QGeoCameraData cameraData = m_map->cameraData(); + cameraData.setCenter(coord); + m_map->setCameraData(cameraData); + } else { + const bool centerHasChanged = center != m_cameraData.center(); + m_cameraData.setCenter(center); + if (centerHasChanged) { + emit centerChanged(center); + // do not emit visibleRegionChanged() here, because, if the map isn't initialized, + // the getter won't return anything updated + } + } +} + +QGeoCoordinate QDeclarativeGeoMap::center() const +{ + if (m_initialized) + return m_map->cameraData().center(); + return m_cameraData.center(); +} + + +/*! + \qmlproperty geoshape QtLocation::Map::visibleRegion + + This property holds the region which occupies the viewport of + the map. The camera is positioned in the center of the shape, and + at the largest integral zoom level possible which allows the + whole shape to be visible on the screen. This implies that + reading this property back shortly after having been set the + returned area is equal or larger than the set area. + + Setting this property implicitly changes the \l center and + \l zoomLevel of the map. Any previously set value to those + properties will be overridden. + + \note Since Qt 5.14 This property provides change notifications. + + \since 5.6 +*/ +void QDeclarativeGeoMap::setVisibleRegion(const QGeoShape &shape) +{ + if (shape.boundingGeoRectangle() == visibleRegion()) + return; + + m_visibleRegion = shape.boundingGeoRectangle(); + if (!m_visibleRegion.isValid() + || (m_visibleRegion.bottomRight().latitude() >= 85.0) // rect entirely outside web mercator + || (m_visibleRegion.topLeft().latitude() <= -85.0)) { + // shape invalidated -> nothing to fit anymore + m_visibleRegion = QGeoRectangle(); + m_pendingFitViewport = false; + emit visibleRegionChanged(); + return; + } + + if (!m_map || !width() || !height()) { + m_pendingFitViewport = true; + emit visibleRegionChanged(); + return; + } + + fitViewportToGeoShape(m_visibleRegion); + emit visibleRegionChanged(); +} + +QGeoShape QDeclarativeGeoMap::visibleRegion() const +{ + if (!m_map || !width() || !height()) + return m_visibleRegion; + + if (m_map->capabilities() & QGeoMap::SupportsVisibleRegion) { + return m_map->visibleRegion(); + } else { + // ToDo: handle projections not supporting visible region in a better way. + // This approach will fail when horizon is in the view or the map is greatly zoomed out. + QList<QGeoCoordinate> visiblePoly; + visiblePoly << m_map->geoProjection().itemPositionToCoordinate(QDoubleVector2D(0,0), false); + visiblePoly << m_map->geoProjection().itemPositionToCoordinate(QDoubleVector2D(m_map->viewportWidth() - 1, + 0), false); + visiblePoly << m_map->geoProjection().itemPositionToCoordinate(QDoubleVector2D(m_map->viewportWidth() - 1, + m_map->viewportHeight() - 1), false); + visiblePoly << m_map->geoProjection().itemPositionToCoordinate(QDoubleVector2D(0, + m_map->viewportHeight() - 1), false); + QGeoPath path; + path.setPath(visiblePoly); + return path.boundingGeoRectangle(); + } +} + +/*! + \qmlproperty bool QtLocation::Map::copyrightsVisible + + This property holds the visibility of the copyrights notice. The notice is usually + displayed in the bottom left corner. By default, this property is set to \c true. + + \note Many map providers require the notice to be visible as part of the terms and conditions. + Please consult the relevant provider documentation before turning this notice off. + + \since 5.7 +*/ +void QDeclarativeGeoMap::setCopyrightsVisible(bool visible) +{ + if (m_copyrightsVisible == visible) + return; + + if (!m_copyrights.isNull()) + m_copyrights->setCopyrightsVisible(visible); + + m_copyrightsVisible = visible; + emit copyrightsVisibleChanged(visible); +} + +bool QDeclarativeGeoMap::copyrightsVisible() const +{ + return m_copyrightsVisible; +} + + + +/*! + \qmlproperty color QtLocation::Map::color + + This property holds the background color of the map element. + + \since 5.6 +*/ +void QDeclarativeGeoMap::setColor(const QColor &color) +{ + if (color != m_color) { + m_color = color; + update(); + emit colorChanged(m_color); + } +} + +QColor QDeclarativeGeoMap::color() const +{ + return m_color; +} + +/*! + \qmlproperty rect QtLocation::Map::visibleArea + + This property holds the visible area inside the Map QML element. + It is a rect whose coordinates are relative to the Map element. + Its size will be clamped to the size of the Map element. + A null visibleArea means that the whole Map is visible. + + \since 5.12 +*/ +QRectF QDeclarativeGeoMap::visibleArea() const +{ + if (m_initialized) + return m_map->visibleArea(); + return m_visibleArea; +} + +void QDeclarativeGeoMap::setVisibleArea(const QRectF &visibleArea) +{ + const QRectF oldVisibleArea = QDeclarativeGeoMap::visibleArea(); + if (visibleArea == oldVisibleArea) + return; + + if (!visibleArea.isValid() && !visibleArea.isEmpty()) // values < 0 + return; + + if (m_initialized) { + m_map->setVisibleArea(visibleArea); + const QRectF newVisibleArea = QDeclarativeGeoMap::visibleArea(); + if (newVisibleArea != oldVisibleArea) { + // polish map items + for (const QPointer<QDeclarativeGeoMapItemBase> &i: qAsConst(m_mapItems)) { + if (i) + i->visibleAreaChanged(); + } + } + } else { + m_visibleArea = visibleArea; + const QRectF newVisibleArea = QDeclarativeGeoMap::visibleArea(); + if (newVisibleArea != oldVisibleArea) + emit visibleAreaChanged(); + } +} + +/*! + \qmlproperty bool QtLocation::Map::mapReady + + This property holds whether the map has been successfully initialized and is ready to be used. + Some methods, such as \l fromCoordinate and \l toCoordinate, will not work before the map is ready. + Due to the architecture of the \l Map, it's advised to use the signal emitted for this property + in place of \l {QtQml::Component::completed()}{Component.onCompleted}, to make sure that everything behaves as expected. + + \since 5.9 +*/ +bool QDeclarativeGeoMap::mapReady() const +{ + return m_initialized; +} + +QMargins QDeclarativeGeoMap::mapMargins() const +{ + const QRectF va = m_map->visibleArea(); + if (va.isEmpty()) + return QMargins(); + return QMargins( va.x() + , va.y() + , width() - va.width() - va.x() + , height() - va.height() - va.y()); +} + +/*! + \qmlproperty list<mapType> QtLocation::Map::supportedMapTypes + + This read-only property holds the set of \l{mapType}{map types} supported by this map. + + \sa activeMapType +*/ +QList<QGeoMapType> QDeclarativeGeoMap::supportedMapTypes() +{ + return m_supportedMapTypes; +} + +/*! + \qmlmethod void QtLocation::Map::alignCoordinateToPoint(coordinate coordinate, QPointF point) + + Aligns \a coordinate to \a point. + This method effectively extends the functionality offered by the \l center qml property, allowing + to align a coordinate to point of the Map element other than its center. + This is useful in those applications where the center of the scene (e.g., a cursor) is not to be + placed exactly in the center of the map. + + If the map is tilted, and \a coordinate happens to be behind the camera, or if the map is not ready + (see \l mapReady), calling this method will have no effect. + + The release of this API with Qt 5.10 is a Technology Preview. + + \sa center + + \since 5.10 +*/ +void QDeclarativeGeoMap::alignCoordinateToPoint(const QGeoCoordinate &coordinate, const QPointF &point) +{ + if (!m_map || !(m_map->capabilities() & QGeoMap::SupportsAnchoringCoordinate)) + return; + + if (!coordinate.isValid() + || !qIsFinite(point.x()) + || !qIsFinite(point.y())) + return; + + m_map->anchorCoordinateToPoint(coordinate, point); +} + +/*! + \qmlmethod coordinate QtLocation::Map::toCoordinate(QPointF position, bool clipToViewPort) + + Returns the coordinate which corresponds to the \a position relative to the map item. + + If \a clipToViewPort is \c true, or not supplied then returns an invalid coordinate if + \a position is not within the current viewport. +*/ +QGeoCoordinate QDeclarativeGeoMap::toCoordinate(const QPointF &position, bool clipToViewPort) const +{ + if (m_map) + return m_map->geoProjection().itemPositionToCoordinate(QDoubleVector2D(position), clipToViewPort); + else + return QGeoCoordinate(); +} + +/*! + \qmlmethod point QtLocation::Map::fromCoordinate(coordinate coordinate, bool clipToViewPort) + + Returns the position relative to the map item which corresponds to the \a coordinate. + + If \a clipToViewPort is \c true, or not supplied then returns an invalid QPointF if + \a coordinate is not within the current viewport. +*/ +QPointF QDeclarativeGeoMap::fromCoordinate(const QGeoCoordinate &coordinate, bool clipToViewPort) const +{ + if (m_map) + return m_map->geoProjection().coordinateToItemPosition(coordinate, clipToViewPort).toPointF(); + else + return QPointF(qQNaN(), qQNaN()); +} + +/*! + \qmlmethod void QtLocation::Map::pan(int dx, int dy) + + Starts panning the map by \a dx pixels along the x-axis and + by \a dy pixels along the y-axis. + + Positive values for \a dx move the map right, negative values left. + Positive values for \a dy move the map down, negative values up. + + During panning the \l center, and \l zoomLevel may change. +*/ +void QDeclarativeGeoMap::pan(int dx, int dy) +{ + if (!m_map) + return; + if (dx == 0 && dy == 0) + return; + + QGeoCoordinate coord = m_map->geoProjection().itemPositionToCoordinate( + QDoubleVector2D(m_map->viewportWidth() / 2 + dx, + m_map->viewportHeight() / 2 + dy)); + setCenter(coord); +} + + +/*! + \qmlmethod void QtLocation::Map::prefetchData() + + Optional hint that allows the map to prefetch during this idle period +*/ +void QDeclarativeGeoMap::prefetchData() +{ + if (!m_map) + return; + m_map->prefetchData(); +} + +/*! + \qmlmethod void QtLocation::Map::clearData() + + Clears map data collected by the currently selected plugin. + \note This method will delete cached files. + \sa plugin +*/ +void QDeclarativeGeoMap::clearData() +{ + if (m_map) + m_map->clearData(); +} + +/*! + \qmlmethod void QtLocation::Map::fitViewportToGeoShape(geoShape, margins) + + Fits the viewport to a specific geo shape \a geoShape. + The \a margins are in screen pixels. + + \note If the projection used by the plugin is not WebMercator, and the plugin does not have fitting to + shape capability, this method will do nothing. + + \sa visibleRegion + \since 5.13 +*/ +void QDeclarativeGeoMap::fitViewportToGeoShape(const QGeoShape &shape, QVariant margins) +{ + QMargins m(10, 10, 10, 10); // lets defaults to 10 if margins is invalid + switch (margins.typeId()) { + case QMetaType::Int: + case QMetaType::Double: { + const int value = int(margins.toDouble()); + m = QMargins(value, value, value, value); + } + break; + // ToDo: Support distinct margins in some QML form. Perhaps QRect? + default: + break; + } + fitViewportToGeoShape(shape, m); +} + +void QDeclarativeGeoMap::fitViewportToGeoShape(const QGeoShape &shape, const QMargins &borders) +{ + if (!m_map || !shape.isValid()) + return; + + if (m_map->geoProjection().projectionType() == QGeoProjection::ProjectionWebMercator) { + // This case remains handled here, and not inside QGeoMap*::fitViewportToGeoRectangle, + // in order to honor animations on center and zoomLevel + const QMargins margins = borders + mapMargins(); + const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(m_map->geoProjection()); + const QPair<QGeoCoordinate, qreal> fitData = p.fitViewportToGeoRectangle(shape.boundingGeoRectangle(), + margins); + if (!fitData.first.isValid()) + return; + + // position camera to the center of bounding box + setProperty("center", QVariant::fromValue(fitData.first)); // not using setCenter(centerCoordinate) to honor a possible animation set on the center property + + if (!qIsFinite(fitData.second)) + return; + double newZoom = qMax<double>(minimumZoomLevel(), fitData.second); + setProperty("zoomLevel", QVariant::fromValue(newZoom)); // not using setZoomLevel(newZoom) to honor a possible animation set on the zoomLevel property + } else if (m_map->capabilities() & QGeoMap::SupportsFittingViewportToGeoRectangle) { + // Animations cannot be honored in this case, as m_map acts as a black box + m_map->fitViewportToGeoRectangle(m_visibleRegion, borders); + } + // else out of luck +} + +/*! + \qmlproperty string QtLocation::Map::errorString + + This read-only property holds the textual presentation of the latest mapping provider error. + If no error has occurred, an empty string is returned. + + An empty string may also be returned if an error occurred which has no associated + textual representation. + + \sa QGeoServiceProvider::errorString() +*/ + +QString QDeclarativeGeoMap::errorString() const +{ + return m_errorString; +} + +/*! + \qmlproperty enumeration QtLocation::Map::error + + This read-only property holds the last occurred mapping service provider error. + + \list + \li Map.NoError - No error has occurred. + \li Map.NotSupportedError -The maps plugin property was not set or there is no mapping manager associated with the plugin. + \li Map.UnknownParameterError -The plugin did not recognize one of the parameters it was given. + \li Map.MissingRequiredParameterError - The plugin did not find one of the parameters it was expecting. + \li Map.ConnectionError - The plugin could not connect to its backend service or database. + \endlist + + \sa QGeoServiceProvider::Error +*/ + +QGeoServiceProvider::Error QDeclarativeGeoMap::error() const +{ + return m_error; +} + +QGeoMap *QDeclarativeGeoMap::map() const +{ + return m_map; +} + +void QDeclarativeGeoMap::itemChange(ItemChange change, const ItemChangeData &value) +{ + if (change == ItemChildAddedChange) { + QQuickItem *child = value.item; + QQuickItem *mapItem = qobject_cast<QDeclarativeGeoMapItemBase *>(child); + if (!mapItem) + mapItem = qobject_cast<QDeclarativeGeoMapItemGroup *>(child); + + if (mapItem) { + qreal z = mapItem->z(); + if (z > m_maxChildZ) { // Ignore children removal + m_maxChildZ = z; + // put the copyrights notice object at the highest z order + if (m_copyrights) + m_copyrights->setCopyrightsZ(m_maxChildZ + 1); + } + } + } else if (change == ItemSceneChange) { + if (m_window) { + disconnect(m_window, &QQuickWindow::beforeSynchronizing, + this, &QDeclarativeGeoMap::updateItemToWindowTransform); + } + m_window = value.window; + if (m_window) { + connect(m_window, &QQuickWindow::beforeSynchronizing, + this, &QDeclarativeGeoMap::updateItemToWindowTransform, Qt::DirectConnection); + } + } + QQuickItem::itemChange(change, value); +} + +bool QDeclarativeGeoMap::isInteractive() const +{ + return (m_gestureArea->enabled() && m_gestureArea->acceptedGestures()) || m_gestureArea->isActive(); +} + +void QDeclarativeGeoMap::attachCopyrightNotice(bool initialVisibility) +{ + if (initialVisibility) { + ++m_copyNoticesVisible; + if (m_map) + m_map->setCopyrightVisible(m_copyNoticesVisible > 0); + } +} + +void QDeclarativeGeoMap::detachCopyrightNotice(bool currentVisibility) +{ + if (currentVisibility) { + --m_copyNoticesVisible; + if (m_map) + m_map->setCopyrightVisible(m_copyNoticesVisible > 0); + } +} + +void QDeclarativeGeoMap::onAttachedCopyrightNoticeVisibilityChanged() +{ + QDeclarativeGeoMapCopyrightNotice *copy = static_cast<QDeclarativeGeoMapCopyrightNotice *>(sender()); + m_copyNoticesVisible += ( int(copy->copyrightsVisible()) * 2 - 1); + if (m_map) + m_map->setCopyrightVisible(m_copyNoticesVisible > 0); +} + +void QDeclarativeGeoMap::onCameraDataChanged(const QGeoCameraData &cameraData) +{ + bool centerHasChanged = cameraData.center() != m_cameraData.center(); + bool bearingHasChanged = cameraData.bearing() != m_cameraData.bearing(); + bool tiltHasChanged = cameraData.tilt() != m_cameraData.tilt(); + bool fovHasChanged = cameraData.fieldOfView() != m_cameraData.fieldOfView(); + bool zoomHasChanged = cameraData.zoomLevel() != m_cameraData.zoomLevel(); + + m_cameraData = cameraData; + // polish map items + for (const QPointer<QDeclarativeGeoMapItemBase> &i: qAsConst(m_mapItems)) { + if (i) + i->baseCameraDataChanged(m_cameraData); // Consider optimizing this further, removing the contained duplicate if conditions. + } + + if (centerHasChanged) + emit centerChanged(m_cameraData.center()); + if (zoomHasChanged) + emit zoomLevelChanged(m_cameraData.zoomLevel()); + if (bearingHasChanged) + emit bearingChanged(m_cameraData.bearing()); + if (tiltHasChanged) + emit tiltChanged(m_cameraData.tilt()); + if (fovHasChanged) + emit fieldOfViewChanged(m_cameraData.fieldOfView()); + if (centerHasChanged || zoomHasChanged || bearingHasChanged + || tiltHasChanged || fovHasChanged) + emit visibleRegionChanged(); +} + +/*! + \qmlmethod void QtLocation::Map::addMapParameter(MapParameter parameter) + + Adds the \a parameter object to the map. The effect of this call is dependent + on the combination of the content of the MapParameter and the type of + underlying QGeoMap. If a MapParameter that is not supported by the underlying + QGeoMap gets added, the call has no effect. + + The release of this API with Qt 5.9 is a Technology Preview. + + \sa MapParameter, removeMapParameter, mapParameters, clearMapParameters + + \since 5.9 +*/ +void QDeclarativeGeoMap::addMapParameter(QDeclarativeGeoMapParameter *parameter) +{ + if (!parameter->isComponentComplete()) { + connect(parameter, &QDeclarativeGeoMapParameter::completed, this, &QDeclarativeGeoMap::addMapParameter); + return; + } + + disconnect(parameter); + if (m_mapParameters.contains(parameter)) + return; + parameter->setParent(this); + m_mapParameters.append(parameter); // parameter now owned by QDeclarativeGeoMap + if (m_map) + m_map->addParameter(parameter); +} + +/*! + \qmlmethod void QtLocation::Map::removeMapParameter(MapParameter parameter) + + Removes the given \a parameter object from the map. + + The release of this API with Qt 5.9 is a Technology Preview. + + \sa MapParameter, addMapParameter, mapParameters, clearMapParameters + + \since 5.9 +*/ +void QDeclarativeGeoMap::removeMapParameter(QDeclarativeGeoMapParameter *parameter) +{ + if (!m_mapParameters.contains(parameter)) + return; + if (m_map) + m_map->removeParameter(parameter); + m_mapParameters.removeOne(parameter); +} + +/*! + \qmlmethod void QtLocation::Map::clearMapParameters() + + Removes all map parameters from the map. + + The release of this API with Qt 5.9 is a Technology Preview. + + \sa MapParameter, mapParameters, addMapParameter, removeMapParameter, clearMapParameters + + \since 5.9 +*/ +void QDeclarativeGeoMap::clearMapParameters() +{ + if (m_map) + m_map->clearParameters(); + m_mapParameters.clear(); +} + +/*! + \qmlproperty list<MapParameters> QtLocation::Map::mapParameters + + Returns the list of all map parameters in no particular order. + These items include map parameters that were declared statically as part of + the type declaration, as well as dynamical map parameters (\l addMapParameter). + + The release of this API with Qt 5.9 is a Technology Preview. + + \sa MapParameter, addMapParameter, removeMapParameter, clearMapParameters + + \since 5.9 +*/ +QList<QObject *> QDeclarativeGeoMap::mapParameters() +{ + QList<QObject *> ret; + for (QDeclarativeGeoMapParameter *p : qAsConst(m_mapParameters)) + ret << p; + return ret; +} + +/* + \internal +*/ +void QDeclarativeGeoMap::addMapObject(QGeoMapObject *object) +{ + if (!object || object->map()) + return; + + if (!m_initialized) { + m_pendingMapObjects.append(object); + return; + } + + int curObjects = m_map->mapObjects().size(); + // object adds itself to the map + object->setMap(m_map); + + if (curObjects != m_map->mapObjects().size()) + emit mapObjectsChanged(); +} + +/* + \internal +*/ +void QDeclarativeGeoMap::removeMapObject(QGeoMapObject *object) +{ + if (!object || object->map() != m_map) // if !initialized this is fine, since both object and m_map are supposed to be NULL + return; + + if (!m_initialized) { + m_pendingMapObjects.removeOne(object); + return; + } + + int curObjects = m_map->mapObjects().size(); + // object adds itself to the map + object->setMap(nullptr); + + if (curObjects != m_map->mapObjects().size()) + emit mapObjectsChanged(); +} + +/* + \internal +*/ +void QDeclarativeGeoMap::clearMapObjects() +{ + if (!m_initialized) { + m_pendingMapObjects.clear(); + } else { + const QList<QGeoMapObject *> objs = m_map->mapObjects(); + for (QGeoMapObject *o: objs) + o->setMap(nullptr); + if (objs.size()) + emit mapObjectsChanged(); + } +} + +/* + \internal +*/ +QList<QGeoMapObject *> QDeclarativeGeoMap::mapObjects() +{ + if (!m_initialized) + return m_pendingMapObjects; + else + return m_map->mapObjects(); +} + +/*! + \qmlproperty list<MapItem> QtLocation::Map::mapItems + + Returns the list of all map items in no particular order. + These items include items that were declared statically as part of + the type declaration, as well as dynamical items (\l addMapItem, + \l MapItemView). + + \sa addMapItem, removeMapItem, clearMapItems +*/ + +QList<QObject *> QDeclarativeGeoMap::mapItems() +{ + QList<QObject *> ret; + for (const auto &ptr : m_mapItems) { + if (ptr) + ret << ptr.data(); + } + return ret; +} + +/*! + \qmlmethod void QtLocation::Map::addMapItem(MapItem item) + + Adds the given \a item to the Map (for example MapQuickItem, MapCircle). If the object + already is on the Map, it will not be added again. + + As an example, consider the case where you have a MapCircle representing your current position: + + \snippet declarative/maps.qml QtQuick import + \snippet declarative/maps.qml QtLocation import + \codeline + \snippet declarative/maps.qml Map addMapItem MapCircle at current position + + \note MapItemViews cannot be added with this method. + + \sa mapItems, removeMapItem, clearMapItems +*/ + +void QDeclarativeGeoMap::addMapItem(QDeclarativeGeoMapItemBase *item) +{ + if (addMapItem_real(item)) + emit mapItemsChanged(); +} + +bool QDeclarativeGeoMap::addMapItem_real(QDeclarativeGeoMapItemBase *item) +{ + if (!item || item->quickMap()) + return false; + // If the item comes from a MapItemGroup, do not reparent it. + if (!qobject_cast<QDeclarativeGeoMapItemGroup *>(item->parentItem())) + item->setParentItem(this); + m_mapItems.append(item); + if (m_map) { + item->setMap(this, m_map); + m_map->addMapItem(item); + } + return true; +} + +/*! + \qmlmethod void QtLocation::Map::removeMapItem(MapItem item) + + Removes the given \a item from the Map (for example MapQuickItem, MapCircle). If + the MapItem does not exist or was not previously added to the map, the + method does nothing. + + \sa mapItems, addMapItem, clearMapItems +*/ +void QDeclarativeGeoMap::removeMapItem(QDeclarativeGeoMapItemBase *ptr) +{ + if (removeMapItem_real(ptr)) + emit mapItemsChanged(); +} + +bool QDeclarativeGeoMap::removeMapItem_real(QDeclarativeGeoMapItemBase *ptr) +{ + if (!ptr) + return false; + QPointer<QDeclarativeGeoMapItemBase> item(ptr); + if (!m_mapItems.contains(item)) + return false; + if (m_map) + m_map->removeMapItem(ptr); + if (item->parentItem() == this) + item->setParentItem(0); + item->setMap(0, 0); + // these can be optimized for perf, as we already check the 'contains' above + m_mapItems.removeOne(item); + return true; +} + +/*! + \qmlmethod void QtLocation::Map::clearMapItems() + + Removes all items and item groups from the map. + + \sa mapItems, addMapItem, removeMapItem, addMapItemGroup, removeMapItemGroup +*/ +void QDeclarativeGeoMap::clearMapItems() +{ + if (m_mapItems.isEmpty()) + return; + + qsizetype removed = 0; + for (qsizetype i = 0; i < m_mapItemGroups.count(); ++i) { + auto item = m_mapItemGroups.at(i); + // Processing only top-level groups (!views) + if (qobject_cast<QDeclarativeGeoMapItemView *>(item)) + continue; + + + if (item->parentItem() != this) + continue; + + if (removeMapItemGroup_real(item)) { + removed++; + --i; + } + } + + while (!m_mapItems.isEmpty()) + removed += removeMapItem_real(m_mapItems.first()); + + if (removed) + emit mapItemsChanged(); +} + +/*! + \qmlmethod void QtLocation::Map::addMapItemGroup(MapItemGroup itemGroup) + + Adds the map items contained in the given \a itemGroup to the Map + (for example MapQuickItem, MapCircle). + + \sa MapItemGroup, removeMapItemGroup + + \since 5.9 +*/ +void QDeclarativeGeoMap::addMapItemGroup(QDeclarativeGeoMapItemGroup *itemGroup) +{ + if (addMapItemGroup_real(itemGroup)) + emit mapItemsChanged(); +} + +bool QDeclarativeGeoMap::addMapItemGroup_real(QDeclarativeGeoMapItemGroup *itemGroup) +{ + if (!itemGroup || itemGroup->quickMap()) // Already added to some map + return false; + + itemGroup->setQuickMap(this); + + if (!isGroupNested(itemGroup)) + itemGroup->setParentItem(this); + + QPointer<QDeclarativeGeoMapItemGroup> g(itemGroup); + m_mapItemGroups.append(g); + + const QList<QQuickItem *> quickKids = itemGroup->childItems(); + int count = 0; + for (auto c: quickKids) { + count += addMapChild(c); // this calls addMapItemGroup recursively, if needed + } + return count; +} + +/*! + \qmlmethod void QtLocation::Map::removeMapItemGroup(MapItemGroup itemGroup) + + Removes \a itemGroup and the items contained therein from the Map. + + \sa MapItemGroup, addMapItemGroup + + \since 5.9 +*/ +void QDeclarativeGeoMap::removeMapItemGroup(QDeclarativeGeoMapItemGroup *itemGroup) +{ + if (removeMapItemGroup_real(itemGroup)) + emit mapItemsChanged(); +} + +bool QDeclarativeGeoMap::removeMapItemGroup_real(QDeclarativeGeoMapItemGroup *itemGroup) +{ + if (!itemGroup || itemGroup->quickMap() != this) // cant remove an itemGroup added to another map + return false; + + QPointer<QDeclarativeGeoMapItemGroup> g(itemGroup); + if (!m_mapItemGroups.removeOne(g)) + return false; + + const QList<QQuickItem *> quickKids = itemGroup->childItems(); + int count = 0; + for (auto c: quickKids) { + count += removeMapChild(c); + } + itemGroup->setQuickMap(nullptr); + if (itemGroup->parentItem() == this) + itemGroup->setParentItem(0); + return count; +} + +/*! + \qmlmethod void QtLocation::Map::removeMapItemView(MapItemView itemView) + + Removes \a itemView and the items instantiated by it from the Map. + + \sa MapItemView, addMapItemView + + \since 5.10 +*/ +void QDeclarativeGeoMap::removeMapItemView(QDeclarativeGeoMapItemView *itemView) +{ + if (removeMapItemView_real(itemView)) + emit mapItemsChanged(); +} + +bool QDeclarativeGeoMap::removeMapItemView_real(QDeclarativeGeoMapItemView *itemView) +{ + if (!itemView || itemView->m_map != this) // can't remove a view that is already added to another map + return false; + + itemView->removeInstantiatedItems(false); // remove the items without using transitions AND abort ongoing ones + itemView->m_map = 0; + m_mapViews.removeOne(itemView); + return removeMapItemGroup_real(itemView); // at this point, all delegate instances have been removed. +} + +void QDeclarativeGeoMap::updateItemToWindowTransform() +{ + if (!m_initialized) + return; + + // Update itemToWindowTransform into QGeoProjection + const QTransform item2WindowOld = m_map->geoProjection().itemToWindowTransform(); + QTransform item2Window = QQuickItemPrivate::get(this)->itemToWindowTransform(); + if (!property("layer").isNull() && property("layer").value<QObject *>()->property("enabled").toBool()) + item2Window.reset(); // When layer is enabled, the item is rendered offscreen with no transformation, then the layer is applied + + m_map->setItemToWindowTransform(item2Window); + + // This method is called at every redraw, including those redraws not generated by + // sgNodeChanged. + // In these cases, *if* the item2windowTransform has changed (e.g., if transformation of + // the item or one of its ancestors changed), a forced update of the map items using accelerated + // GL implementation has to be performed in order to have them pulling the updated itemToWindowTransform. + if (!m_sgNodeHasChanged && item2WindowOld != item2Window) { + for (auto i: qAsConst(m_mapItems)) + i->setMaterialDirty(); + } + + m_sgNodeHasChanged = false; +} + +void QDeclarativeGeoMap::onSGNodeChanged() +{ + m_sgNodeHasChanged = true; + update(); +} + +/*! + \qmlmethod void QtLocation::Map::addMapItemView(MapItemView itemView) + + Adds \a itemView to the Map. + + \sa MapItemView, removeMapItemView + + \since 5.10 +*/ +void QDeclarativeGeoMap::addMapItemView(QDeclarativeGeoMapItemView *itemView) +{ + if (addMapItemView_real(itemView)) + emit mapItemsChanged(); +} + +bool QDeclarativeGeoMap::addMapItemView_real(QDeclarativeGeoMapItemView *itemView) +{ + if (!itemView || itemView->m_map) // can't add a view twice + return false; + + int count = addMapItemGroup_real(itemView); // at this point, delegates aren't yet incubated. + // Not appending it to m_mapViews because it seems unnecessary even if the + // itemView is a child of this (in which case it would be destroyed + m_mapViews.append(itemView); + setupMapView(itemView); + return count; +} + +/*! + \qmlproperty mapType QtLocation::Map::activeMapType + + \brief Access to the currently active \l{mapType}{map type}. + + This property can be set to change the active \l{mapType}{map type}. + See the \l{Map::supportedMapTypes}{supportedMapTypes} property for possible values. + + \sa mapType +*/ +void QDeclarativeGeoMap::setActiveMapType(const QGeoMapType &mapType) +{ + if (m_activeMapType != mapType) { + if (m_map) { + if (mapType.pluginName() == m_plugin->name().toLatin1()) { + m_map->setActiveMapType(mapType); + m_activeMapType = mapType; + emit activeMapTypeChanged(); + } + } else { + m_activeMapType = mapType; + emit activeMapTypeChanged(); + } + } +} + +QGeoMapType QDeclarativeGeoMap::activeMapType() const +{ + return m_activeMapType; +} + +/*! + \internal +*/ +void QDeclarativeGeoMap::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + m_gestureArea->setSize(newGeometry.size()); + QQuickItem::geometryChange(newGeometry, oldGeometry); + + if (!m_map || newGeometry.size().isEmpty()) + return; + + m_map->setViewportSize(newGeometry.size().toSize()); + + if (!m_initialized) { + initialize(); + } else { + setMinimumZoomLevel(m_map->minimumZoom(), false); + + // Update the center latitudinal threshold + QGeoCameraData cameraData = m_map->cameraData(); + const double maximumCenterLatitudeAtZoom = m_map->maximumCenterLatitudeAtZoom(cameraData); + const double minimumCenterLatitudeAtZoom = m_map->minimumCenterLatitudeAtZoom(cameraData); + if (maximumCenterLatitudeAtZoom != m_maximumViewportLatitude + || minimumCenterLatitudeAtZoom != m_minimumViewportLatitude) { + m_maximumViewportLatitude = maximumCenterLatitudeAtZoom; + m_minimumViewportLatitude = minimumCenterLatitudeAtZoom; + QGeoCoordinate coord = cameraData.center(); + coord.setLatitude(qBound(m_minimumViewportLatitude, coord.latitude(), m_maximumViewportLatitude)); + cameraData.setCenter(coord); + m_map->setCameraData(cameraData); // this polishes map items + } else if (oldGeometry.size() != newGeometry.size()) { + // polish map items + for (const QPointer<QDeclarativeGeoMapItemBase> &i: qAsConst(m_mapItems)) { + if (i) + i->polishAndUpdate(); + } + } + } + + /* + The fitViewportTo*() functions depend on a valid map geometry. + If they were called prior to the first resize they cause + the zoomlevel to jump to 0 (showing the world). Therefore the + calls were queued up until now. + + Multiple fitViewportTo*() calls replace each other. + */ + if (m_pendingFitViewport && width() && height()) { + fitViewportToGeoShape(m_visibleRegion); + m_pendingFitViewport = false; + } + +} + +/*! + \qmlmethod void QtLocation::Map::fitViewportToMapItems(list<MapItems> items = {}) + + If no argument is provided, fits the current viewport to the boundary of all map items. + The camera is positioned in the center of the map items, and at the largest integral zoom level + possible which allows all map items to be visible on screen. + If \a items is provided, fits the current viewport to the boundary of the specified map items only. + + \note This method gained the optional \a items argument since Qt 5.15. + In previous releases, this method fitted the map to all map items. + + \sa fitViewportToVisibleMapItems +*/ +void QDeclarativeGeoMap::fitViewportToMapItems(const QVariantList &items) +{ + if (items.size()) { + QList<QPointer<QDeclarativeGeoMapItemBase> > itms; + for (const QVariant &i: items) { + QDeclarativeGeoMapItemBase *itm = qobject_cast<QDeclarativeGeoMapItemBase *>(i.value<QObject *>()); + if (itm) + itms.append(itm); + } + fitViewportToMapItemsRefine(itms, true, false); + } else { + fitViewportToMapItemsRefine(m_mapItems, true, false); + } +} + +/*! + \qmlmethod void QtLocation::Map::fitViewportToVisibleMapItems() + + Fits the current viewport to the boundary of all \b visible map items. + The camera is positioned in the center of the map items, and at the largest integral + zoom level possible which allows all map items to be visible on screen. + + \sa fitViewportToMapItems +*/ +void QDeclarativeGeoMap::fitViewportToVisibleMapItems() +{ + fitViewportToMapItemsRefine(m_mapItems, true, true); +} + +/*! + \internal +*/ +void QDeclarativeGeoMap::fitViewportToMapItemsRefine(const QList<QPointer<QDeclarativeGeoMapItemBase> > &mapItems, + bool refine, + bool onlyVisible) +{ + if (!m_map) + return; + + if (mapItems.size() == 0) + return; + + double minX = qInf(); + double maxX = -qInf(); + double minY = qInf(); + double maxY = -qInf(); + double topLeftX = 0; + double topLeftY = 0; + double bottomRightX = 0; + double bottomRightY = 0; + bool haveQuickItem = false; + + // find bounds of all map items + qsizetype itemCount = 0; + for (qsizetype i = 0; i < mapItems.count(); ++i) { + if (!mapItems.at(i)) + continue; + QDeclarativeGeoMapItemBase *item = mapItems.at(i).data(); + if (!item || (onlyVisible && (!item->isVisible() || item->mapItemOpacity() <= 0.0))) + continue; + + // skip quick items in the first pass and refine the fit later + QDeclarativeGeoMapQuickItem *quickItem = + qobject_cast<QDeclarativeGeoMapQuickItem*>(item); + if (refine && quickItem) { + haveQuickItem = true; + continue; + } + // Force map items to update immediately. Needed to ensure correct item size and positions + // when recursively calling this function. + // TODO: See if we really need updatePolish on delegated items, in particular + // in relation to + // a) fitViewportToMapItems + // b) presence of MouseArea + // + // This is also legacy code. It must be updated to not operate on screen sizes. + if (item->isPolishScheduled()) + item->updatePolish(); + + if (quickItem && quickItem->matrix_ && !quickItem->matrix_->m_matrix.isIdentity()) { + // TODO: recalculate the center/zoom level so that the item becomes projectable again + if (quickItem->zoomLevel() == 0.0) // the item is unprojectable, should be skipped. + continue; + + QRectF brect = item->boundingRect(); + brect = quickItem->matrix_->m_matrix.mapRect(brect); + QPointF transformedPosition = quickItem->matrix_->m_matrix.map(item->position()); + topLeftX = transformedPosition.x(); + topLeftY = transformedPosition.y(); + bottomRightX = topLeftX + brect.width(); + bottomRightY = topLeftY + brect.height(); + } else { + topLeftX = item->position().x(); + topLeftY = item->position().y(); + bottomRightX = topLeftX + item->width(); + bottomRightY = topLeftY + item->height(); + } + + minX = qMin(minX, topLeftX); + maxX = qMax(maxX, bottomRightX); + minY = qMin(minY, topLeftY); + maxY = qMax(maxY, bottomRightY); + + ++itemCount; + } + + if (itemCount == 0) { + if (haveQuickItem) + fitViewportToMapItemsRefine(mapItems, false, onlyVisible); + return; + } + double bboxWidth = maxX - minX; + double bboxHeight = maxY - minY; + double bboxCenterX = minX + (bboxWidth / 2.0); + double bboxCenterY = minY + (bboxHeight / 2.0); + + // position camera to the center of bounding box + QGeoCoordinate coordinate; + coordinate = m_map->geoProjection().itemPositionToCoordinate(QDoubleVector2D(bboxCenterX, bboxCenterY), false); + setProperty("center", QVariant::fromValue(coordinate)); + + // adjust zoom + double bboxWidthRatio = bboxWidth / (bboxWidth + bboxHeight); + double mapWidthRatio = width() / (width() + height()); + double zoomRatio; + + if (bboxWidthRatio > mapWidthRatio) + zoomRatio = bboxWidth / width(); + else + zoomRatio = bboxHeight / height(); + + qreal newZoom = std::log10(zoomRatio) / std::log10(0.5); + newZoom = std::floor(qMax(minimumZoomLevel(), (zoomLevel() + newZoom))); + setProperty("zoomLevel", QVariant::fromValue(newZoom)); + + // as map quick items retain the same screen size after the camera zooms in/out + // we refine the viewport again to achieve better results + if (refine) + fitViewportToMapItemsRefine(mapItems, false, onlyVisible); +} + +/*! + \internal +*/ +void QDeclarativeGeoMap::mousePressEvent(QMouseEvent *event) +{ + if (isInteractive() && event->source() == Qt::MouseEventNotSynthesized) + m_gestureArea->handleMousePressEvent(event); + else + QQuickItem::mousePressEvent(event); +} + +/*! + \internal +*/ +void QDeclarativeGeoMap::mouseMoveEvent(QMouseEvent *event) +{ + if (isInteractive() && event->source() == Qt::MouseEventNotSynthesized) + m_gestureArea->handleMouseMoveEvent(event); + else + QQuickItem::mouseMoveEvent(event); +} + +/*! + \internal +*/ +void QDeclarativeGeoMap::mouseReleaseEvent(QMouseEvent *event) +{ + if (isInteractive() && event->source() == Qt::MouseEventNotSynthesized) + m_gestureArea->handleMouseReleaseEvent(event); + else + QQuickItem::mouseReleaseEvent(event); +} + +void QDeclarativeGeoMap::touchUngrabEvent() +{ + if (isInteractive()) + m_gestureArea->handleTouchUngrabEvent(); + else + QQuickItem::touchUngrabEvent(); +} + +/*! + \internal +*/ +void QDeclarativeGeoMap::touchEvent(QTouchEvent *event) +{ + if (isInteractive()) { + m_gestureArea->handleTouchEvent(event); + } else { + //ignore event so sythesized event is generated; + QQuickItem::touchEvent(event); + } +} + +#if QT_CONFIG(wheelevent) +/*! + \internal +*/ +void QDeclarativeGeoMap::wheelEvent(QWheelEvent *event) +{ + if (isInteractive()) + m_gestureArea->handleWheelEvent(event); + else + QQuickItem::wheelEvent(event); + +} +#endif + +/*! + \internal +*/ +bool QDeclarativeGeoMap::childMouseEventFilter(QQuickItem *item, QEvent *event) +{ + Q_UNUSED(item); + if (!isVisible() || !isEnabled() || !isInteractive()) + return QQuickItem::childMouseEventFilter(item, event); + + switch (event->type()) { + case QEvent::TouchBegin: + case QEvent::TouchUpdate: + case QEvent::TouchEnd: + case QEvent::TouchCancel: + return sendTouchEvent(static_cast<QTouchEvent *>(event)); + case QEvent::MouseButtonPress: + case QEvent::MouseMove: + case QEvent::MouseButtonRelease: + { + auto mEvent = static_cast<QMouseEvent*>(event); + if (mEvent->source() == Qt::MouseEventNotSynthesized){ + return sendTouchEvent(mEvent); + } + } + break; + case QEvent::UngrabMouse: { + QQuickWindow *win = window(); + if (!win) break; + if (!win->mouseGrabberItem() || + (win->mouseGrabberItem() && + win->mouseGrabberItem() != this)) { + // child lost grab, we could even lost + // some events if grab already belongs for example + // in item in diffrent window , clear up states + mouseUngrabEvent(); + } + break; + } + default: + break; + } + return QQuickItem::childMouseEventFilter(item, event); +} + +bool QDeclarativeGeoMap::sendMouseEvent(QMouseEvent *event) +{ + bool stealEvent = m_gestureArea->isActive(); + + if ((stealEvent || contains(mapFromScene(event->scenePosition())))) { + switch (event->type()) { + case QEvent::MouseMove: + m_gestureArea->handleMouseMoveEvent(event); + break; + case QEvent::MouseButtonPress: + m_gestureArea->handleMousePressEvent(event); + break; + case QEvent::MouseButtonRelease: + m_gestureArea->handleMouseReleaseEvent(event); + break; + default: + break; + } + + stealEvent = m_gestureArea->isActive(); + + if (stealEvent) { + //do not deliver + event->setAccepted(true); + return true; + } else { + return false; + } + } + return false; +} + +bool QDeclarativeGeoMap::sendTouchEvent(QPointerEvent *event) +{ + const QTouchEvent::TouchPoint &point = event->points().first(); + + bool stealEvent = m_gestureArea->isActive(); + bool containsPoint = contains(mapFromScene(point.scenePosition())); + + if ((stealEvent || containsPoint)) { + + m_gestureArea->handleTouchEvent(event); + stealEvent = m_gestureArea->isActive(); + + if (stealEvent) { + //event->setAccepted(true); + //return true; + } else { + return false; + } + } + return false; +} + +QT_END_NAMESPACE |