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