Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qdeclarativegeomapquickitem.cpp
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5
6#include <QtCore/QScopedValueRollback>
7#include <QtQml/qqmlinfo.h>
8#include <QtQuick/QSGOpacityNode>
9#include <QtPositioning/private/qdoublevector2d_p.h>
10#include <QtQuick/private/qquickitem_p.h>
11#include <QtQuick/private/qquickmousearea_p.h>
12#include <QtLocation/private/qgeomap_p.h>
13#include <QtLocation/private/qgeoprojection_p.h>
14#include <QDebug>
15#include <cmath>
16
17QT_BEGIN_NAMESPACE
18
19/*!
20 \qmltype MapQuickItem
21 \nativetype QDeclarativeGeoMapQuickItem
22 \inqmlmodule QtLocation
23 \ingroup qml-QtLocation5-maps
24 \since QtLocation 5.5
25
26 \brief The MapQuickItem type displays an arbitrary Qt Quick object
27 on a Map.
28
29 The MapQuickItem type is used to place an arbitrary Qt Quick object
30 on a Map at a specified location and size. Compared to floating an item
31 above the Map, a MapQuickItem will follow the panning (and optionally, the
32 zooming) of the Map as if it is on the Map surface.
33
34 The \l{sourceItem} property contains the Qt Quick item to be drawn, which
35 can be any kind of visible type.
36
37 \section2 Positioning and Sizing
38
39 The positioning of the MapQuickItem on the Map is controlled by two
40 properties: \l coordinate and \l anchorPoint. If only \l coordinate is set,
41 it specifies a longitude/latitude coordinate for the item to be placed at.
42 The set coordinate will line up with the top-left corner of the contained
43 item when shown on the screen.
44
45 The \l anchorPoint property provides a way to line up the coordinate with
46 other parts of the item than just the top-left corner, by setting a number
47 of pixels the item will be offset by. A simple way to think about it is
48 to note that the point given by \l anchorPoint on the item itself is the
49 point that will line up with the given \l coordinate when displayed.
50
51 In addition to being anchored to the map, the MapQuickItem can optionally
52 follow the scale of the map, and change size when the Map is zoomed in or
53 zoomed out. This behaviour is controlled by the \l zoomLevel property. The
54 default behaviour if \l zoomLevel is not set is for the item to be drawn
55 "on the screen" rather than "on the map", so that its size remains the same
56 regardless of the zoom level of the Map.
57
58 \section2 Performance
59
60 Performance of a MapQuickItem is normally in the same ballpark as the
61 contained Qt Quick item alone. Overheads added amount to a translation
62 and (possibly) scaling of the original item, as well as a transformation
63 from longitude and latitude to screen position.
64
65 \section2 Limitations
66
67 \note Due to an implementation detail, items placed inside a
68 MapQuickItem will have a \c{parent} item which is not the MapQuickItem.
69 Refer to the MapQuickItem by its \c{id}, and avoid the use of \c{anchor}
70 in the \c{sourceItem}.
71
72 \section2 Example Usage
73
74 The following snippet shows a MapQuickItem containing an Image object,
75 to display a Marker on the Map. This strategy is used to show the map
76 markers in the MapViewer example.
77
78 \snippet mapviewer/map/Marker.qml mqi-top
79 \snippet mapviewer/map/Marker.qml mqi-anchor
80 \snippet mapviewer/map/Marker.qml mqi-closeimage
81 \snippet mapviewer/map/Marker.qml mqi-close
82
83 \image api-mapquickitem.png
84*/
85
86/*!
87 \qmlproperty bool QtLocation::MapQuickItem::autoFadeIn
88
89 This property holds whether the item automatically fades in when zooming into the map
90 starting from very low zoom levels. By default this is \c true.
91 Setting this property to \c false causes the map item to always have the opacity specified
92 with the \l QtQuick::Item::opacity property, which is 1.0 by default.
93
94 \since 5.14
95*/
96
97QMapQuickItemMatrix4x4::QMapQuickItemMatrix4x4(QObject *parent) : QQuickTransform(parent) { }
98
99void QMapQuickItemMatrix4x4::setMatrix(const QMatrix4x4 &matrix)
100{
101 if (m_matrix == matrix)
102 return;
103 m_matrix = matrix;
104 update();
105}
106
107void QMapQuickItemMatrix4x4::applyTo(QMatrix4x4 *matrix) const
108{
109 *matrix *= m_matrix;
110}
111
112
113QDeclarativeGeoMapQuickItem::QDeclarativeGeoMapQuickItem(QQuickItem *parent)
114 : QDeclarativeGeoMapItemBase(parent)
115{
116 m_itemType = QGeoMap::MapQuickItem;
117 setFlag(ItemHasContents, true);
118 opacityContainer_ = new QQuickItem(this);
119 opacityContainer_->setParentItem(this);
120 opacityContainer_->setFlag(ItemHasContents, true);
121}
122
123QDeclarativeGeoMapQuickItem::~QDeclarativeGeoMapQuickItem() {}
124
125/*!
126 \qmlproperty coordinate MapQuickItem::coordinate
127
128 This property holds the anchor coordinate of the MapQuickItem. The point
129 on the sourceItem that is specified by anchorPoint is kept aligned with
130 this coordinate when drawn on the map.
131
132 In the image below, there are 3 MapQuickItems that are identical except
133 for the value of their anchorPoint properties. The values of anchorPoint
134 for each are written on top of the item.
135
136 \image api-mapquickitem-anchor.png
137*/
138void QDeclarativeGeoMapQuickItem::setCoordinate(const QGeoCoordinate &coordinate)
139{
140 if (coordinate_ == coordinate)
141 return;
142
143 coordinate_ = coordinate;
144 geoshape_.setTopLeft(coordinate_);
145 geoshape_.setBottomRight(coordinate_);
146 // TODO: Handle zoomLevel != 0.0
147 polishAndUpdate();
148 emit coordinateChanged();
149}
150
151/*!
152 \internal
153*/
154void QDeclarativeGeoMapQuickItem::setMap(QDeclarativeGeoMap *quickMap, QGeoMap *map)
155{
156 QDeclarativeGeoMapItemBase::setMap(quickMap,map);
157 if (map && quickMap) {
158 connect(map, &QGeoMap::cameraDataChanged,
159 this, &QDeclarativeGeoMapQuickItem::polishAndUpdate);
160 polishAndUpdate();
161 }
162}
163
164/*!
165 \internal
166*/
167void QDeclarativeGeoMapQuickItem::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
168{
169 if (!mapAndSourceItemSet_ || updatingGeometry_ ||
170 newGeometry.topLeft() == oldGeometry.topLeft()) {
171 QDeclarativeGeoMapItemBase::geometryChange(newGeometry, oldGeometry);
172 return;
173 }
174
175 QGeoCoordinate newCoordinate = map()->geoProjection().
176 itemPositionToCoordinate(QDoubleVector2D(x(), y()) + QDoubleVector2D(anchorPoint_), false);
177
178 if (newCoordinate.isValid())
179 setCoordinate(newCoordinate);
180
181 // Not calling QDeclarativeGeoMapItemBase::geometryChange() as it will be called from a nested
182 // call to this function.
183}
184
185/*!
186 \internal
187*/
188QGeoCoordinate QDeclarativeGeoMapQuickItem::coordinate()
189{
190 return coordinate_;
191}
192
193/*!
194 \qmlproperty object MapQuickItem::sourceItem
195
196 This property holds the source item that will be drawn on the map.
197*/
198void QDeclarativeGeoMapQuickItem::setSourceItem(QQuickItem *sourceItem)
199{
200 QQuickItem *item = qobject_cast<QQuickItem *>(sourceItem); // Workaround for QTBUG-72930
201 if (sourceItem_.data() == item)
202 return;
203 sourceItem_ = item;
204 polishAndUpdate();
205 emit sourceItemChanged();
206}
207
208QQuickItem *QDeclarativeGeoMapQuickItem::sourceItem()
209{
210 return sourceItem_.data();
211}
212
213/*!
214 \internal
215*/
216void QDeclarativeGeoMapQuickItem::afterChildrenChanged()
217{
218 const QList<QQuickItem *> kids = childItems();
219 if (kids.size() > 0) {
220 bool printedWarning = false;
221 for (auto *i : kids) {
222 if (i->flags() & QQuickItem::ItemHasContents
223 && !qobject_cast<QQuickMouseArea *>(i)
224 && sourceItem_.data() != i
225 && opacityContainer_ != i) {
226 if (!printedWarning) {
227 qmlWarning(this) << "Use the sourceItem property for the contained item, direct children are not supported";
228 printedWarning = true;
229 }
230
231 qmlWarning(i) << "deleting this child";
232 i->deleteLater();
233 }
234 }
235 }
236}
237
238/*!
239 \qmlproperty point MapQuickItem::anchorPoint
240
241 This property determines which point on the sourceItem that will be lined
242 up with the coordinate on the map.
243*/
244void QDeclarativeGeoMapQuickItem::setAnchorPoint(const QPointF &anchorPoint)
245{
246 if (anchorPoint == anchorPoint_)
247 return;
248 anchorPoint_ = anchorPoint;
249 polishAndUpdate();
250 emit anchorPointChanged();
251}
252
253QPointF QDeclarativeGeoMapQuickItem::anchorPoint() const
254{
255 return anchorPoint_;
256}
257
258/*!
259 \qmlproperty real MapQuickItem::zoomLevel
260
261 This property controls the scaling behaviour of the contents of the
262 MapQuickItem. In particular, by setting this property it is possible
263 to choose between objects that are drawn on the screen (and sized in
264 screen pixels), and those drawn on the map surface (which change size
265 with the zoom level of the map).
266
267 The default value for this property is 0.0, which corresponds to drawing
268 the object on the screen surface. If set to another value, the object will
269 be drawn on the map surface instead. The value (if not zero) specifies the
270 zoomLevel at which the object will be visible at a scale of 1:1 (ie, where
271 object pixels and screen pixels are the same). At zoom levels lower than
272 this, the object will appear smaller, and at higher zoom levels, appear
273 larger. This is in contrast to when this property is set to zero, where
274 the object remains the same size on the screen at all zoom levels.
275*/
276void QDeclarativeGeoMapQuickItem::setZoomLevel(qreal zoomLevel)
277{
278 if (zoomLevel == zoomLevel_)
279 return;
280 zoomLevel_ = zoomLevel;
281 // TODO: update geoshape_!
282 polishAndUpdate();
283 emit zoomLevelChanged();
284}
285
286qreal QDeclarativeGeoMapQuickItem::zoomLevel() const
287{
288 return zoomLevel_;
289}
290
291const QGeoShape &QDeclarativeGeoMapQuickItem::geoShape() const
292{
293 // TODO: return a QGeoRectangle representing the bounding geo rectangle of the quick item
294 // when zoomLevel_ is != 0.0
295 return geoshape_;
296}
297
298void QDeclarativeGeoMapQuickItem::setGeoShape(const QGeoShape &shape)
299{
300 if (shape == geoshape_)
301 return;
302
303 const QGeoRectangle rect = shape.boundingGeoRectangle();
304 geoshape_ = rect;
305 coordinate_ = rect.center();
306
307 // TODO: Handle zoomLevel != 0.0
308 polishAndUpdate();
309 emit coordinateChanged();
310
311}
312
313bool QDeclarativeGeoMapQuickItem::contains(const QPointF &point) const
314{
315 if (zoomLevel_ != 0) {
316 // This follows the mathematical principle of
317 // QDeclarativeGeoMap::fitViewportToMapItemsRefine but in a shorter way:
318 const QTransform t = matrix_->m_matrix.toTransform();
319 const QPointF transformedPoint = t.inverted().map(point);
320 return QQuickItem::contains(transformedPoint);
321 }
322 return QQuickItem::contains(point);
323}
324
325/*!
326 \internal
327*/
328void QDeclarativeGeoMapQuickItem::updatePolish()
329{
330 if (!quickMap() && sourceItem_) {
331 mapAndSourceItemSet_ = false;
332 sourceItem_.data()->setParentItem(0);
333 return;
334 }
335
336 if (!quickMap() || !map() || !sourceItem_) {
337 mapAndSourceItemSet_ = false;
338 return;
339 }
340
341 if (!mapAndSourceItemSet_ && quickMap() && map() && sourceItem_) {
342 mapAndSourceItemSet_ = true;
343 sourceItem_.data()->setParentItem(opacityContainer_);
344 sourceItem_.data()->setTransformOrigin(QQuickItem::TopLeft);
345 connect(sourceItem_.data(), &QQuickItem::xChanged,
346 this, &QDeclarativeGeoMapQuickItem::polishAndUpdate);
347 connect(sourceItem_.data(), &QQuickItem::yChanged,
348 this, &QDeclarativeGeoMapQuickItem::polishAndUpdate);
349 connect(sourceItem_.data(), &QQuickItem::widthChanged,
350 this, &QDeclarativeGeoMapQuickItem::polishAndUpdate);
351 connect(sourceItem_.data(), &QQuickItem::heightChanged,
352 this, &QDeclarativeGeoMapQuickItem::polishAndUpdate);
353 }
354
355 if (!coordinate_.isValid()) {
356 opacityContainer_->setVisible(false);
357 return;
358 } else {
359 opacityContainer_->setVisible(true);
360 }
361
362 QScopedValueRollback<bool> rollback(updatingGeometry_);
363 updatingGeometry_ = true;
364
365 opacityContainer_->setOpacity(zoomLevelOpacity());
366
367 setWidth(sourceItem_.data()->width());
368 setHeight(sourceItem_.data()->height());
369 if (zoomLevel_ != 0.0 // zoom level initialized to 0.0. If it's different, it has been set explicitly.
370 && map()->geoProjection().projectionType() == QGeoProjection::ProjectionWebMercator) { // Currently unsupported on any other projection
371 const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map()->geoProjection());
372
373 if (!matrix_) {
374 matrix_ = new QMapQuickItemMatrix4x4(this);
375 matrix_->appendToItem(opacityContainer_);
376 }
377 matrix_->setMatrix(p.quickItemTransformation(coordinate(), anchorPoint_, zoomLevel_));
378 setPosition(QPointF(0,0));
379 } else {
380 if (map()->geoProjection().projectionType() == QGeoProjection::ProjectionWebMercator) {
381 const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map()->geoProjection());
382 if (map()->cameraData().tilt() > 0.0
383 && !p.isProjectable(p.geoToWrappedMapProjection(coordinate()))) {
384 // if the coordinate is behind the camera, we use the transformation to get the item out of the way
385 if (!matrix_) {
386 matrix_ = new QMapQuickItemMatrix4x4(this);
387 matrix_->appendToItem(opacityContainer_);
388 }
389 matrix_->setMatrix(p.quickItemTransformation(coordinate(), anchorPoint_, map()->cameraData().zoomLevel()));
390 setPosition(QPointF(0,0));
391 } else { // All good, rendering screen-aligned
392 if (matrix_)
393 matrix_->setMatrix(QMatrix4x4());
394 setPositionOnMap(coordinate(), anchorPoint_);
395 }
396 } else { // On other projections we can only currently test if coordinateToItemPosition returns a valid position
397 if (map()->cameraData().tilt() > 0.0
398 && qIsNaN(map()->geoProjection().coordinateToItemPosition(coordinate(), false).x())) {
399 opacityContainer_->setVisible(false);
400 } else {
401 if (matrix_)
402 matrix_->setMatrix(QMatrix4x4());
403 setPositionOnMap(coordinate(), anchorPoint_);
404 }
405 }
406 }
407}
408
409/*!
410 \internal
411*/
412void QDeclarativeGeoMapQuickItem::afterViewportChanged(const QGeoMapViewportChangeEvent &event)
413{
414 Q_UNUSED(event);
415 if (event.mapSize.width() <= 0 || event.mapSize.height() <= 0)
416 return;
417
418 polishAndUpdate();
419}
420
421/*!
422 \internal
423*/
424qreal QDeclarativeGeoMapQuickItem::scaleFactor()
425{
426 qreal scale = 1.0;
427 // use 1+x to avoid fuzzy compare against zero
428 if (!qFuzzyCompare(1.0 + zoomLevel_, 1.0))
429 scale = std::pow(0.5, zoomLevel_ - map()->cameraData().zoomLevel());
430 return scale;
431}
432
433QT_END_NAMESPACE