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