QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
GeoMapItem.cc
Go to the documentation of this file.
1/****************************************************************************
2 *
3 * (c) 2009-2024 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
4 *
5 * QGroundControl is licensed according to the terms in the file
6 * COPYING.md in the root of the source code directory.
7 *
8 ****************************************************************************/
9
10#include "GeoMapItem.h"
11
12#include <QtQml/QQmlComponent>
13#include <QtQuick3D/QQuick3DObject>
14
15#include <algorithm>
16#include <cmath>
17
18#include "GeoMapCamera.h"
19#include "GeoScene.h"
20#include "QGCLoggingCategory.h"
21#include "SurfacePatchModel.h"
22#include "TileMath.h"
23
24QGC_LOGGING_CATEGORY(GeoMapItemLog, "GeoMap.GeoMapItem")
25
26// Far enough off-screen for any plausible viewport
27static constexpr QPointF kParkedPosition{-1e6, -1e6};
28
29GeoMapItem::GeoMapItem(QQuickItem* parent) : QQuickItem(parent)
30{
31 connect(this, &QQuickItem::visibleChanged, this, &GeoMapItem::_updateNode3DVisibility);
32}
33
35{
36 delete _node3D;
37}
38
39void GeoMapItem::setCoordinate(const QGeoCoordinate& coordinate)
40{
41 if (coordinate == _coordinate) {
42 return;
43 }
44 _coordinate = coordinate;
45 _updatePosition();
46 _updateNode3DVisibility();
47 emit coordinateChanged();
48}
49
50void GeoMapItem::setAnchorPoint(const QPointF& anchorPoint)
51{
52 if (anchorPoint == _anchorPoint) {
53 return;
54 }
55 _anchorPoint = anchorPoint;
56 _updatePosition();
57 emit anchorPointChanged();
58}
59
61{
62 if (scene == _scene) {
63 return;
64 }
65 qCDebug(GeoMapItemLog) << "scene" << (scene ? "set" : "cleared");
66 if (_scene) {
67 disconnect(_scene, nullptr, this, nullptr);
68 }
69 _scene = scene;
70 if (_scene) {
71 connect(_scene, &GeoScene::sceneOriginChanged, this, &GeoMapItem::_updatePosition);
72 connect(_scene, &GeoScene::terrainScaleChanged, this, &GeoMapItem::_updatePosition);
73 connect(_scene, &GeoScene::terrainScaleChanged, this, &GeoMapItem::_updateCrossfade);
74 connect(_scene, &GeoScene::cameraChanged, this, &GeoMapItem::_connectCamera);
75 connect(_scene, &GeoScene::content3DChanged, this, &GeoMapItem::_rebuildNode3D);
76 connect(_scene, &QObject::destroyed, this, [this] {
77 _scene = nullptr;
78 _connectCamera();
79 _rebuildNode3D();
80 emit sceneChanged();
81 });
82 }
83 _connectCamera();
84 _rebuildNode3D();
85 emit sceneChanged();
86}
87
89{
90 if (surfaceModel == _surfaceModel) {
91 return;
92 }
93 if (_surfaceModel) {
94 disconnect(_surfaceModel, nullptr, this, nullptr);
95 }
96 _surfaceModel = surfaceModel;
97 if (_surfaceModel) {
98 connect(_surfaceModel, &SurfacePatchModel::terrainHeightsChanged, this, &GeoMapItem::_terrainHeightsChanged);
99 connect(_surfaceModel, &QObject::destroyed, this, [this] {
100 _surfaceModel = nullptr;
101 _updatePosition();
102 emit surfaceModelChanged();
103 });
104 }
105 _updatePosition();
106 emit surfaceModelChanged();
107}
108
110{
111 if (mode == _altitudeMode) {
112 return;
113 }
114 _altitudeMode = mode;
115 _updatePosition();
116 emit altitudeModeChanged();
117}
118
119void GeoMapItem::setDelegate3D(QQmlComponent* delegate3D)
120{
121 if (delegate3D == _delegate3D) {
122 return;
123 }
124 qCDebug(GeoMapItemLog) << "delegate3D" << (delegate3D ? "set" : "cleared");
125 if (_delegate3D) {
126 disconnect(_delegate3D, nullptr, this, nullptr);
127 }
128 _delegate3D = delegate3D;
129 if (_delegate3D) {
130 connect(_delegate3D, &QObject::destroyed, this, [this] {
131 _delegate3D = nullptr;
132 _rebuildNode3D();
133 emit delegate3DChanged();
134 });
135 }
136 _rebuildNode3D();
137 emit delegate3DChanged();
138}
139
140void GeoMapItem::setCrossfade3D(bool crossfade3D)
141{
142 if (crossfade3D == _crossfade3D) {
143 return;
144 }
145 _crossfade3D = crossfade3D;
146 _updateCrossfade();
147 emit crossfade3DChanged();
148}
149
150void GeoMapItem::_connectCamera()
151{
152 GeoMapCamera* const camera = _scene ? _scene->camera() : nullptr;
153 if (camera == _connectedCamera) {
154 _updatePosition();
155 return;
156 }
157 if (_connectedCamera) {
158 disconnect(_connectedCamera, nullptr, this, nullptr);
159 }
160 _connectedCamera = camera;
161 if (_connectedCamera) {
162 connect(_connectedCamera, &GeoMapCamera::scenePoseChanged, this, &GeoMapItem::_updatePosition);
163 connect(_connectedCamera, &GeoMapCamera::viewportSizeChanged, this, &GeoMapItem::_updatePosition);
164 connect(_connectedCamera, &GeoMapCamera::fieldOfViewChanged, this, &GeoMapItem::_updatePosition);
165 connect(_connectedCamera, &QObject::destroyed, this, [this] {
166 _connectedCamera = nullptr;
167 _updatePosition();
168 });
169 }
170 _updatePosition();
171}
172
173void GeoMapItem::_terrainHeightsChanged()
174{
175 if (_altitudeMode == AltitudeMode::ClampToGround) {
176 _updatePosition();
177 }
178}
179
180void GeoMapItem::_setProjected(bool projected)
181{
182 if (projected == _projected) {
183 return;
184 }
185 _projected = projected;
186 if (!_projected) {
187 // Park off-screen rather than toggling visible, which would fight
188 // user bindings on this item's own visible property
189 setPosition(kParkedPosition);
190 }
191 emit projectedChanged();
192}
193
194void GeoMapItem::_setScenePosition(const QVector3D& scenePosition)
195{
196 if (scenePosition == _scenePosition) {
197 return;
198 }
199 _scenePosition = scenePosition;
200 if (_node3D) {
201 _node3D->setProperty("position", _scenePosition);
202 }
204}
205
206void GeoMapItem::_rebuildNode3D()
207{
208 const bool hadNode = _node3D != nullptr;
209 if (_node3D) {
210 // Unhook from the scene immediately; deleteLater is safer than delete
211 // here since this can run from the container's destroyed signal
212 if (auto* const object3D = qobject_cast<QQuick3DObject*>(_node3D)) {
213 object3D->setParentItem(nullptr);
214 }
215 _node3D->deleteLater();
216 _node3D = nullptr;
217 }
218
219 QQuick3DObject* const container = _scene ? qobject_cast<QQuick3DObject*>(_scene->content3D()) : nullptr;
220 if (_delegate3D && container) {
221 QObject* const node = _delegate3D->create(_delegate3D->creationContext());
222 if (!node) {
223 qCWarning(GeoMapItemLog) << "delegate3D failed to create:" << _delegate3D->errorString();
224 } else if (auto* const object3D = qobject_cast<QQuick3DObject*>(node)) {
225 node->setParent(container);
226 object3D->setParentItem(container);
227 node->setProperty("position", _scenePosition);
228 _node3D = node;
229 _updateNode3DVisibility();
230 } else {
231 // Caller contract: delegate3D must produce a QQuick3DNode
232 qCWarning(GeoMapItemLog) << "delegate3D did not produce a QQuick3DObject";
233 delete node;
234 }
235 }
236
237 _updateCrossfade();
238 if (hadNode || _node3D) {
239 emit node3DChanged();
240 }
241}
242
243void GeoMapItem::_updateNode3DVisibility()
244{
245 if (_node3D) {
246 _node3D->setProperty("visible", isVisible() && _coordinate.isValid());
247 }
248}
249
250void GeoMapItem::_updateCrossfade()
251{
252 if (!_crossfade3D) {
253 if (_node3D) {
254 _node3D->setProperty("opacity", 1.0);
255 }
256 if (_contentOpacity2D != 1.0) {
257 _contentOpacity2D = 1.0;
259 }
260 return;
261 }
262 const qreal terrainScale = _scene ? std::clamp(_scene->terrainScale(), 0.0, 1.0) : 0.0;
263 if (_node3D) {
264 _node3D->setProperty("opacity", terrainScale);
265 }
266 const qreal contentOpacity2D = _node3D ? (1.0 - terrainScale) : 1.0;
267 if (contentOpacity2D != _contentOpacity2D) {
268 _contentOpacity2D = contentOpacity2D;
270 }
271}
272
273void GeoMapItem::_updatePosition()
274{
275 if (!_scene || !_coordinate.isValid()) {
276 _setScenePosition(QVector3D());
277 _setProjected(false);
278 return;
279 }
280
281 double altitude = 0.0;
282 if (_altitudeMode == AltitudeMode::Absolute) {
283 altitude = std::isfinite(_coordinate.altitude()) ? _coordinate.altitude() : 0.0;
284 } else if (_surfaceModel) {
285 altitude = _surfaceModel->terrainHeightAt(_coordinate);
286 }
287
288 const QPointF world = TileMath::geoToWorld(_coordinate);
289 const double sceneZ = altitude * _scene->verticalScale() * _scene->terrainScale();
290 const QPointF origin = _scene->sceneOrigin();
291 _setScenePosition(QVector3D(static_cast<float>(world.x() - origin.x()), static_cast<float>(world.y() - origin.y()),
292 static_cast<float>(sceneZ)));
293
294 GeoMapCamera* const camera = _scene->camera();
295 if (!camera) {
296 _setProjected(false);
297 return;
298 }
299 const auto screen = camera->worldToScreen(world, sceneZ);
300 if (!screen) {
301 _setProjected(false);
302 return;
303 }
304 setPosition(*screen - _anchorPoint);
305 _setProjected(true);
306}
static constexpr QPointF kParkedPosition
Definition GeoMapItem.cc:27
#define QGC_LOGGING_CATEGORY(name, categoryStr)
std::optional< QPointF > worldToScreen(const QPointF &worldGround, double worldZ=0.0) const
void scenePoseChanged()
void fieldOfViewChanged()
void viewportSizeChanged()
SurfacePatchModel * surfaceModel() const
Definition GeoMapItem.h:80
void crossfade3DChanged()
void setAnchorPoint(const QPointF &anchorPoint)
Definition GeoMapItem.cc:50
void setCrossfade3D(bool crossfade3D)
void surfaceModelChanged()
qreal contentOpacity2D() const
Definition GeoMapItem.h:120
AltitudeMode
How the coordinate's vertical position is resolved.
Definition GeoMapItem.h:60
@ ClampToGround
sit on the rendered terrain surface (default)
@ Absolute
use coordinate.altitude (NaN treated as 0)
void projectedChanged()
void setAltitudeMode(AltitudeMode mode)
void setDelegate3D(QQmlComponent *delegate3D)
void node3DChanged()
QVector3D scenePosition() const
Definition GeoMapItem.h:95
void scenePositionChanged()
GeoMapItem(QQuickItem *parent=nullptr)
Definition GeoMapItem.cc:29
void setScene(GeoScene *scene)
Definition GeoMapItem.cc:60
void coordinateChanged()
QPointF anchorPoint() const
Definition GeoMapItem.h:72
bool projected() const
Definition GeoMapItem.h:90
void anchorPointChanged()
void setSurfaceModel(SurfacePatchModel *surfaceModel)
Definition GeoMapItem.cc:88
void delegate3DChanged()
void sceneChanged()
bool crossfade3D() const
Definition GeoMapItem.h:113
GeoScene * scene() const
Definition GeoMapItem.h:76
QGeoCoordinate coordinate() const
Definition GeoMapItem.h:66
~GeoMapItem() override
Definition GeoMapItem.cc:34
void altitudeModeChanged()
QQmlComponent * delegate3D() const
Definition GeoMapItem.h:102
void setCoordinate(const QGeoCoordinate &coordinate)
Definition GeoMapItem.cc:39
void contentOpacity2DChanged()
qreal verticalScale() const
Definition GeoScene.cc:48
void sceneOriginChanged()
qreal terrainScale() const
Definition GeoScene.h:67
void terrainScaleChanged()
void cameraChanged()
QObject * content3D() const
Definition GeoScene.h:74
GeoMapCamera * camera() const
Definition GeoScene.h:49
void content3DChanged()
QPointF sceneOrigin() const
Definition GeoScene.h:56
void terrainHeightsChanged()
terrainHeightAt answers changed somewhere: consumers re-query
Q_INVOKABLE double terrainHeightAt(const QGeoCoordinate &coordinate) const
QPointF geoToWorld(const QGeoCoordinate &coord)
Geo -> world meters. Latitude is clamped to +/-kMaxLatitude.
Definition TileMath.cc:23