QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
GeoMapCamera.cc
Go to the documentation of this file.
1#include "GeoMapCamera.h"
2
3#include <algorithm>
4#include <array>
5#include <cmath>
6
7#include <QtCore/QRectF>
8#include <QtCore/QtMath>
9
10#include "QGCLoggingCategory.h"
11#include "TileMath.h"
12
13QGC_LOGGING_CATEGORY(GeoMapCameraLog, "GeoMap.GeoMapCamera")
14
15namespace {
16
17struct Vec3
18{
19 double x = 0;
20 double y = 0;
21 double z = 0;
22};
23
24// Camera-to-world rotation for the pose convention R = Rz(heading) * Rx(tilt),
25// in double precision (QQuaternion/QVector3D floats lose meters at mercator world scale).
26Vec3 rotateCameraToWorld(const Vec3& v, qreal headingDeg, qreal tiltDeg)
27{
28 const double t = qDegreesToRadians(tiltDeg);
29 const double h = qDegreesToRadians(headingDeg);
30
31 // Rx(tilt)
32 const Vec3 a{v.x, (v.y * std::cos(t)) - (v.z * std::sin(t)), (v.y * std::sin(t)) + (v.z * std::cos(t))};
33 // Rz(heading)
34 return Vec3{(a.x * std::cos(h)) - (a.y * std::sin(h)), (a.x * std::sin(h)) + (a.y * std::cos(h)), a.z};
35}
36
37QPointF rotated(const QPointF& vec, qreal degrees)
38{
39 const double rad = qDegreesToRadians(degrees);
40 const double c = std::cos(rad);
41 const double s = std::sin(rad);
42 return QPointF((vec.x() * c) - (vec.y() * s), (vec.x() * s) + (vec.y() * c));
43}
44
45struct Ray
46{
47 Vec3 origin;
48 Vec3 dir;
49};
50
51// Camera offset from the look-at center for the pose convention: the camera
52// sits distance meters along the back-rotated view axis (top-down at tilt 0,
53// moving south and down-range as tilt grows, swung around by heading)
54Vec3 cameraOffset(qreal headingDeg, qreal tiltDeg, qreal distance)
55{
56 const double tiltRad = qDegreesToRadians(tiltDeg);
57 const double headingRad = qDegreesToRadians(headingDeg);
58 const double sinTilt = std::sin(tiltRad);
59 return Vec3{distance * sinTilt * std::sin(headingRad), -distance * sinTilt * std::cos(headingRad),
60 distance * std::cos(tiltRad)};
61}
62
63Ray pickRay(const QPointF& centerWorld, qreal heading, qreal tilt, qreal distance, qreal centerElevation,
64 const QSizeF& viewport, qreal fov, const QPointF& screenPos)
65{
66 const double aspect = viewport.width() / viewport.height();
67 const double tanHalfFov = std::tan(qDegreesToRadians(fov) / 2.0);
68 const double ndcX = ((2.0 * screenPos.x()) / viewport.width()) - 1.0;
69 const double ndcY = 1.0 - ((2.0 * screenPos.y()) / viewport.height());
70
71 const Vec3 dir = rotateCameraToWorld(Vec3{ndcX * tanHalfFov * aspect, ndcY * tanHalfFov, -1.0}, heading, tilt);
72
73 const Vec3 offset = cameraOffset(heading, tilt, distance);
74 return Ray{Vec3{centerWorld.x() + offset.x, centerWorld.y() + offset.y, centerElevation + offset.z}, dir};
75}
76
77// Inverse of pickRay: world point (mercator meters, z in scene units) to screen
78// pixels; std::nullopt at or behind the camera plane
79std::optional<QPointF> projectToScreen(const QPointF& centerWorld, qreal heading, qreal tilt, qreal distance,
80 qreal centerElevation, const QSizeF& viewport, qreal fov,
81 const QPointF& worldGround, double worldZ)
82{
83 const Vec3 offset = cameraOffset(heading, tilt, distance);
84 const Vec3 d{worldGround.x() - (centerWorld.x() + offset.x), worldGround.y() - (centerWorld.y() + offset.y),
85 worldZ - (centerElevation + offset.z)};
86
87 // World-to-camera: inverse of the pose rotation, R^T = Rx(-tilt) * Rz(-heading)
88 const double h = qDegreesToRadians(heading);
89 const double t = qDegreesToRadians(tilt);
90 const Vec3 a{(d.x * std::cos(h)) + (d.y * std::sin(h)), (-d.x * std::sin(h)) + (d.y * std::cos(h)), d.z};
91 const Vec3 c{a.x, (a.y * std::cos(t)) + (a.z * std::sin(t)), (-a.y * std::sin(t)) + (a.z * std::cos(t))};
92
93 // Camera looks along -z; the epsilon guards the projection divide (not a near plane)
94 const double depth = -c.z;
95 if (depth <= 1e-9) {
96 return std::nullopt;
97 }
98
99 const double aspect = viewport.width() / viewport.height();
100 const double tanHalfFov = std::tan(qDegreesToRadians(fov) / 2.0);
101 const double ndcX = c.x / (depth * tanHalfFov * aspect);
102 const double ndcY = c.y / (depth * tanHalfFov);
103 return QPointF(((ndcX + 1.0) / 2.0) * viewport.width(), ((1.0 - ndcY) / 2.0) * viewport.height());
104}
105
106} // namespace
107
108GeoMapCamera::GeoMapCamera(QObject* parent) : QObject(parent) {}
109
110QGeoCoordinate GeoMapCamera::center() const
111{
112 return TileMath::worldToGeo(_centerWorld);
113}
114
115void GeoMapCamera::setCenter(const QGeoCoordinate& center)
116{
117 _setCenterWorld(TileMath::geoToWorld(center));
118}
119
120void GeoMapCamera::_setCenterWorld(const QPointF& world)
121{
122 const double half = TileMath::worldSize() / 2.0;
123 const QPointF clamped(std::clamp(world.x(), -half, half), std::clamp(world.y(), -half, half));
124 const bool firstPosition = !_positioned;
125 _positioned = true; // any explicit center counts, even one equal to the default
126 if ((clamped == _centerWorld) && !firstPosition) {
127 return;
128 }
129 _centerWorld = clamped;
130 emit centerChanged();
131 emit scenePoseChanged();
132}
133
134void GeoMapCamera::setHeading(qreal heading)
135{
136 const qreal normalized = _normalizedHeading(heading);
137 if (qFuzzyCompare(normalized, _heading)) {
138 return;
139 }
140 _heading = normalized;
141 emit headingChanged();
142 emit scenePoseChanged();
143}
144
145void GeoMapCamera::setTilt(qreal tilt)
146{
147 const qreal clamped = std::clamp(tilt, kMinTilt, kMaxTilt);
148 if (qFuzzyCompare(clamped, _tilt)) {
149 return;
150 }
151 _tilt = clamped;
152 emit tiltChanged();
153 emit scenePoseChanged();
154}
155
156void GeoMapCamera::setDistance(qreal distance)
157{
158 const qreal clamped = std::clamp(distance, kMinDistance, kMaxDistance);
159 if (qFuzzyCompare(clamped, _distance)) {
160 return;
161 }
162 _distance = clamped;
163 emit distanceChanged();
164 emit scenePoseChanged();
165}
166
168{
169 // Exact compare: values repeat from the same terrain lookup, and
170 // qFuzzyCompare misbehaves near zero
171 if (elevation == _centerElevation) {
172 return;
173 }
174 _centerElevation = elevation;
176 emit scenePoseChanged();
177}
178
179void GeoMapCamera::setViewportSize(const QSizeF& size)
180{
181 if (size == _viewportSize) {
182 return;
183 }
184 const qreal previousVerticalFov = verticalFieldOfView();
185 _viewportSize = size;
186 emit viewportSizeChanged();
187 if (!qFuzzyCompare(verticalFieldOfView(), previousVerticalFov)) {
189 }
191}
192
194{
195 const qreal clamped = std::clamp(fov, 10.0, 120.0);
196 if (qFuzzyCompare(clamped, _fieldOfView)) {
197 return;
198 }
199 _fieldOfView = clamped;
200 emit fieldOfViewChanged();
203}
204
206{
207 if (_viewportSize.isEmpty() || _viewportSize.width() <= _viewportSize.height()) {
208 return _fieldOfView;
209 }
210 const double aspect = _viewportSize.width() / _viewportSize.height();
211 return qRadiansToDegrees(2.0 * std::atan(std::tan(qDegreesToRadians(_fieldOfView) / 2.0) / aspect));
212}
213
215{
216 if (_viewportSize.height() <= 0) {
217 return 0.0;
218 }
219 return 2.0 * std::tan(qDegreesToRadians(verticalFieldOfView()) / 2.0) / _viewportSize.height();
220}
221
223{
224 if (mode == _mode) {
225 return;
226 }
227 qCDebug(GeoMapCameraLog) << "mode" << _mode << "->" << mode;
228 _mode = mode;
229 emit modeChanged();
230}
231
238
239void GeoMapCamera::lookAt(const QGeoCoordinate& center, qreal heading, qreal tilt, qreal distance)
240{
243 setTilt(tilt);
245}
246
248{
249 const Vec3 offset = cameraOffset(_heading, _tilt, _distance);
250 return QVector3D(static_cast<float>(_centerWorld.x() + offset.x), static_cast<float>(_centerWorld.y() + offset.y),
251 static_cast<float>(_centerElevation + offset.z));
252}
253
255{
256 const Vec3 offset = cameraOffset(_heading, _tilt, _distance);
257 return QPointF(_centerWorld.x() + offset.x, _centerWorld.y() + offset.y);
258}
259
260void GeoMapCamera::setSceneOrigin(const QPointF& origin)
261{
262 if (origin == _sceneOrigin) {
263 return;
264 }
265 _sceneOrigin = origin;
266 emit scenePoseChanged();
267}
268
270{
271 // World offset computed in doubles before the float cast
272 const Vec3 offset = cameraOffset(_heading, _tilt, _distance);
273 return QVector3D(static_cast<float>((_centerWorld.x() - _sceneOrigin.x()) + offset.x),
274 static_cast<float>((_centerWorld.y() - _sceneOrigin.y()) + offset.y),
275 static_cast<float>(_centerElevation + offset.z));
276}
277
279{
280 // R = Rz(heading) * Rx(tilt): identity is top-down (camera -z look direction
281 // points down the scene z axis) with north (+y) up on screen
282 return QQuaternion::fromAxisAndAngle(0, 0, 1, static_cast<float>(_heading)) *
283 QQuaternion::fromAxisAndAngle(1, 0, 0, static_cast<float>(_tilt));
284}
285
286std::optional<GeoMapCamera::PickRay> GeoMapCamera::pickRayAt(const QPointF& screenPos) const
287{
288 if (_viewportSize.isEmpty()) {
289 return std::nullopt;
290 }
291
292 const Ray ray = pickRay(_centerWorld, _heading, _tilt, _distance, _centerElevation, _viewportSize,
293 verticalFieldOfView(), screenPos);
294 return PickRay{ray.origin.x, ray.origin.y, ray.origin.z, ray.dir.x, ray.dir.y, ray.dir.z};
295}
296
297std::optional<QPointF> GeoMapCamera::screenToGround(const QPointF& screenPos, double planeZ) const
298{
299 if (_viewportSize.isEmpty()) {
300 return std::nullopt;
301 }
302
303 const Ray ray =
304 pickRay(_centerWorld, _heading, _tilt, _distance, _centerElevation, _viewportSize, verticalFieldOfView(), screenPos);
305 if (ray.dir.z >= 0.0) {
306 return std::nullopt; // at or above the horizon
307 }
308
309 const double s = (planeZ - ray.origin.z) / ray.dir.z;
310 if (s <= 0.0) {
311 return std::nullopt; // plane is behind/above the camera
312 }
313 return QPointF(ray.origin.x + (s * ray.dir.x), ray.origin.y + (s * ray.dir.y));
314}
315
316QGeoCoordinate GeoMapCamera::coordinateAtScreenPoint(const QPointF& screenPos, double worldZ) const
317{
318 const auto hit = screenToGround(screenPos, worldZ);
319 if (!hit) {
320 return QGeoCoordinate();
321 }
322 return TileMath::worldToGeo(*hit);
323}
324
325std::optional<QPointF> GeoMapCamera::groundPointCapped(const QPointF& screenPos, double maxRange) const
326{
327 if (_viewportSize.isEmpty()) {
328 return std::nullopt;
329 }
330
331 const Ray ray =
332 pickRay(_centerWorld, _heading, _tilt, _distance, _centerElevation, _viewportSize, verticalFieldOfView(), screenPos);
333 const QPointF cameraGround(ray.origin.x, ray.origin.y);
334
335 if (ray.dir.z < 0.0) {
336 const double s = -ray.origin.z / ray.dir.z;
337 const QPointF hit(ray.origin.x + (s * ray.dir.x), ray.origin.y + (s * ray.dir.y));
338 const QPointF offset = hit - cameraGround;
339 const double range = std::hypot(offset.x(), offset.y());
340 if (range <= maxRange) {
341 return hit;
342 }
343 return cameraGround + (offset * (maxRange / range));
344 }
345
346 // Above the horizon: cap along the ray's horizontal direction
347 const double horizontal = std::hypot(ray.dir.x, ray.dir.y);
348 if (horizontal < 1e-12) {
349 return cameraGround; // straight up
350 }
351 return cameraGround + (QPointF(ray.dir.x, ray.dir.y) * (maxRange / horizontal));
352}
353
354std::optional<QPointF> GeoMapCamera::worldToScreen(const QPointF& worldGround, double worldZ) const
355{
356 if (_viewportSize.isEmpty()) {
357 return std::nullopt;
358 }
359
360 return projectToScreen(_centerWorld, _heading, _tilt, _distance, _centerElevation, _viewportSize,
361 verticalFieldOfView(), worldGround, worldZ);
362}
363
365{
366 if (_viewportSize.isEmpty()) {
367 return 0;
368 }
369 const QPointF screenCenter(_viewportSize.width() / 2.0, _viewportSize.height() / 2.0);
370 const auto p0 = screenToGround(screenCenter);
371 const auto p1 = screenToGround(screenCenter + QPointF(1, 0));
372 if (!p0 || !p1) {
373 return 0;
374 }
375 const QPointF d = *p1 - *p0;
376 return std::hypot(d.x(), d.y());
377}
378
379qreal GeoMapCamera::distanceForZoomLevel(qreal zoomLevel) const
380{
381 if (_viewportSize.isEmpty()) {
382 return kDefaultDistance;
383 }
384 const double metersPerPixel = TileMath::worldSize() / (TileMath::kTilePixels * std::exp2(zoomLevel));
385 const double tanHalfFov = std::tan(qDegreesToRadians(verticalFieldOfView()) / 2.0);
386 return std::clamp((metersPerPixel * _viewportSize.height()) / (2.0 * tanHalfFov), kMinDistance, kMaxDistance);
387}
388
389qreal GeoMapCamera::zoomLevelForDistance(qreal distance) const
390{
391 if (_viewportSize.isEmpty()) {
392 return 0;
393 }
394 // Mirror distanceForZoomLevel's contract: inputs outside the camera's
395 // distance limits map to the corresponding zoom bound
396 const double clamped = std::clamp(distance, kMinDistance, kMaxDistance);
397 const double tanHalfFov = std::tan(qDegreesToRadians(verticalFieldOfView()) / 2.0);
398 const double metersPerPixel = (clamped * 2.0 * tanHalfFov) / _viewportSize.height();
399 return std::log2(TileMath::worldSize() / (TileMath::kTilePixels * metersPerPixel));
400}
401
402QGeoCoordinate GeoMapCamera::centerForCoordinateAtScreenPoint(const QGeoCoordinate& coordinate,
403 const QPointF& screenPos, double worldZ,
404 double centerElevation) const
405{
406 if (_viewportSize.isEmpty()) {
407 return center();
408 }
409
410 // Intersect the pick ray with the horizontal plane at worldZ: solving on the
411 // ground plane instead would center the coordinate's ground footprint, leaving
412 // an elevated point (e.g. a flying vehicle) high on screen under a tilted camera.
413 const double pivotElevation = std::isfinite(centerElevation) ? centerElevation : _centerElevation;
414 const Ray ray =
415 pickRay(_centerWorld, _heading, _tilt, _distance, pivotElevation, _viewportSize, verticalFieldOfView(), screenPos);
416 if (ray.dir.z >= 0.0) {
417 return center(); // at or above the horizon
418 }
419 const double s = (worldZ - ray.origin.z) / ray.dir.z;
420 if (s <= 0.0) {
421 return center(); // plane is behind the camera
422 }
423
424 const QPointF hit(ray.origin.x + (s * ray.dir.x), ray.origin.y + (s * ray.dir.y));
425 const QPointF delta = TileMath::geoToWorld(coordinate) - hit;
426 return TileMath::worldToGeo(_centerWorld + delta);
427}
428
429void GeoMapCamera::fitToRegion(const QGeoRectangle& region)
430{
431 if (_viewportSize.isEmpty() || !region.isValid()) {
432 return;
433 }
434
435 const QPointF topLeft = TileMath::geoToWorld(region.topLeft());
436 const QPointF bottomRight = TileMath::geoToWorld(region.bottomRight());
437 const std::array<QPointF, 4> corners{topLeft, bottomRight, QPointF(topLeft.x(), bottomRight.y()),
438 QPointF(bottomRight.x(), topLeft.y())};
439
440 // Center first: consumers re-sample centerElevation on centerChanged, and
441 // the corners are fitted on that pivot plane
442 _setCenterWorld((topLeft + bottomRight) / 2.0);
443
444 const QRectF viewport(QPointF(0, 0), _viewportSize);
445 const qreal fov = verticalFieldOfView();
446 const auto fits = [&](double distance) {
447 return std::ranges::all_of(corners, [&](const QPointF& corner) {
448 const auto screen = projectToScreen(_centerWorld, _heading, _tilt, distance, _centerElevation,
449 _viewportSize, fov, corner, _centerElevation);
450 return screen && viewport.contains(*screen);
451 });
452 };
453
454 // Backing away along the view axis shrinks every on-plane point toward the
455 // screen center, so fits() is monotonic in distance: bisect in log space
456 double fitDistance = kMinDistance;
457 if (!fits(kMinDistance)) {
458 double tooClose = kMinDistance;
459 fitDistance = kMaxDistance;
460 while ((fitDistance / tooClose) > (1.0 + 1e-6)) {
461 const double mid = std::sqrt(tooClose * fitDistance);
462 if (fits(mid)) {
463 fitDistance = mid;
464 } else {
465 tooClose = mid;
466 }
467 }
468 }
469 setDistance(fitDistance);
470}
471
472void GeoMapCamera::beginPan(const QPointF& screenPos, double anchorZ)
473{
474 _panAnchorZ = anchorZ;
475 _panAnchorWorld = screenToGround(screenPos, anchorZ);
476}
477
478void GeoMapCamera::panTo(const QPointF& screenPos)
479{
480 if (!_panAnchorWorld) {
481 return;
482 }
483 _anchorToScreen(*_panAnchorWorld, screenPos, _panAnchorZ);
484}
485
486void GeoMapCamera::beginOrbit(const QPointF& screenPos, double anchorZ)
487{
488 _orbitAnchorZ = anchorZ;
489 _orbitAnchorWorld = screenToGround(screenPos, anchorZ);
490 _orbitAnchorScreen = screenPos;
491 _orbitStartScreen = screenPos;
492 _orbitStartHeading = _heading;
493 _orbitStartTilt = _tilt;
494}
495
496void GeoMapCamera::orbitTo(const QPointF& screenPos)
497{
498 if (!_orbitAnchorWorld || _viewportSize.isEmpty()) {
499 return;
500 }
501
502 const QPointF delta = screenPos - _orbitStartScreen;
503 const qreal headingDelta = (delta.x() / _viewportSize.width()) * 360.0;
504 const qreal tiltDelta = (-delta.y() / _viewportSize.height()) * 180.0;
505
506 // Heading change is a rigid rotation of the camera about the vertical axis
507 // through the anchor: rotate the center around the anchor by the same angle.
508 const qreal newHeading = _normalizedHeading(_orbitStartHeading + headingDelta);
509 const qreal appliedHeadingDelta = std::remainder(newHeading - _heading, 360.0);
510 _setCenterWorld(*_orbitAnchorWorld + rotated(_centerWorld - *_orbitAnchorWorld, appliedHeadingDelta));
511 setHeading(newHeading);
512 if (_mode == Mode::Mode3D) {
513 setTilt(_orbitStartTilt + tiltDelta);
514 }
515
516 // Keep the anchor pinned to its on-screen position from gesture start
517 _anchorToScreen(*_orbitAnchorWorld, _orbitAnchorScreen, _orbitAnchorZ);
518}
519
520void GeoMapCamera::beginLook(const QPointF& screenPos)
521{
522 const Vec3 offset = cameraOffset(_heading, _tilt, _distance);
523 _lookCameraGround = QPointF(_centerWorld.x() + offset.x, _centerWorld.y() + offset.y);
524 _lookCameraZ = _centerElevation + offset.z;
525 _lookStartScreen = screenPos;
526 _lookStartHeading = _heading;
527 _lookStartTilt = _tilt;
528}
529
530void GeoMapCamera::lookTo(const QPointF& screenPos)
531{
532 if (!_lookCameraGround || _viewportSize.isEmpty()) {
533 return;
534 }
535
536 const QPointF delta = screenPos - _lookStartScreen;
537 const qreal headingDelta = (delta.x() / _viewportSize.width()) * 360.0;
538 const qreal tiltDelta = (-delta.y() / _viewportSize.height()) * 180.0;
539
540 const qreal newHeading = _normalizedHeading(_lookStartHeading + headingDelta);
541 const qreal newTilt = (_mode == Mode::Mode3D) ? std::clamp(_lookStartTilt + tiltDelta, kMinTilt, kMaxTilt) : _tilt;
542
543 // The new center is the fixed camera's forward ray intersected with the
544 // pivot-elevation plane; solved via the pose convention so camera z tracks
545 // _lookCameraZ exactly even when centerElevation moves mid-gesture.
546 // When the consumer re-samples centerElevation on centerChanged (GeoMap
547 // terrain-following), that update lands after this solve: camera z is off
548 // by the per-event elevation delta until the next lookTo re-solves — a
549 // one-event lag, self-correcting while the drag continues.
550 const double height = _lookCameraZ - _centerElevation;
551 if (height <= 0.0) {
552 return; // camera at/below the pivot plane: no forward ground intersection
553 }
554 // Range-safety clamp: when it engages, the fixed-camera invariant yields
555 // and the camera slides along the view axis to stay within distance limits
556 const qreal newDistance = std::clamp(height / std::cos(qDegreesToRadians(newTilt)), kMinDistance, kMaxDistance);
557
558 const Vec3 offset = cameraOffset(newHeading, newTilt, newDistance);
559 _setCenterWorld(QPointF(_lookCameraGround->x() - offset.x, _lookCameraGround->y() - offset.y));
560 setHeading(newHeading);
561 setTilt(newTilt);
562 setDistance(newDistance);
563}
564
565void GeoMapCamera::rotateBy(qreal degrees, const QPointF& screenPos)
566{
567 const auto anchor = screenToGround(screenPos);
568 if (!anchor) {
569 return;
570 }
571 _setCenterWorld(*anchor + rotated(_centerWorld - *anchor, degrees));
572 setHeading(_heading + degrees);
573}
574
575void GeoMapCamera::tiltBy(qreal degrees, const QPointF& screenPos)
576{
577 if (_mode != Mode::Mode3D) {
578 return; // 2D locks tilt against gestures
579 }
580 const auto anchor = screenToGround(screenPos);
581 setTilt(_tilt + degrees);
582 if (anchor) {
583 _anchorToScreen(*anchor, screenPos);
584 }
585}
586
587void GeoMapCamera::zoom(qreal amount, const QPointF& screenPos)
588{
589 // 120 angleDelta units (one wheel notch) scales distance by ~0.8
590 zoomBy(std::pow(0.8, amount / 120.0), screenPos);
591}
592
593void GeoMapCamera::zoomBy(qreal factor, const QPointF& screenPos)
594{
595 if (factor <= 0.0) {
596 return;
597 }
598 const auto anchor = screenToGround(screenPos);
599 setDistance(_distance * factor);
600 if (anchor) {
601 _anchorToScreen(*anchor, screenPos);
602 }
603}
604
605void GeoMapCamera::_anchorToScreen(const QPointF& anchorWorld, const QPointF& screenPos, double anchorZ)
606{
607 const auto current = screenToGround(screenPos, anchorZ);
608 if (!current) {
609 return;
610 }
611 _setCenterWorld(_centerWorld + (anchorWorld - *current));
612}
613
614qreal GeoMapCamera::_normalizedHeading(qreal heading)
615{
616 const qreal wrapped = std::fmod(heading, 360.0);
617 return (wrapped < 0.0) ? (wrapped + 360.0) : wrapped;
618}
#define QGC_LOGGING_CATEGORY(name, categoryStr)
int32_t height
Definition UBXPlan.cc:44
Q_INVOKABLE void panTo(const QPointF &screenPos)
Continue a pan gesture: the surface point picked at beginPan stays under the cursor.
void unitsPerPixelAtUnitDistanceChanged()
Q_INVOKABLE QGeoCoordinate coordinateAtScreenPoint(const QPointF &screenPos, double worldZ=0.0) const
void distanceChanged()
void centerElevationChanged()
qreal unitsPerPixelAtUnitDistance() const
void setCenterElevation(qreal elevation)
void setMode(Mode mode)
qreal distance() const
Q_INVOKABLE void beginPan(const QPointF &screenPos, double anchorZ=0.0)
Q_INVOKABLE qreal zoomLevelForDistance(qreal distance) const
Q_INVOKABLE void lookTo(const QPointF &screenPos)
void setFieldOfView(qreal fov)
std::optional< QPointF > worldToScreen(const QPointF &worldGround, double worldZ=0.0) const
void tiltChanged()
static constexpr qreal kMinTilt
void verticalFieldOfViewChanged()
QPointF cameraGroundPosition() const
Q_INVOKABLE void fitToRegion(const QGeoRectangle &region)
Q_INVOKABLE qreal sceneUnitsPerPixel() const
Q_INVOKABLE void zoom(qreal amount, const QPointF &screenPos)
void setCenter(const QGeoCoordinate &center)
void headingChanged()
qreal centerElevation() const
static constexpr qreal kMinDistance
void setSceneOrigin(const QPointF &origin)
void scenePoseChanged()
Q_INVOKABLE void beginLook(const QPointF &screenPos)
Start a first-person look gesture at the given screen position (pixels)
void fieldOfViewChanged()
Q_INVOKABLE void zoomBy(qreal factor, const QPointF &screenPos)
Q_INVOKABLE void beginOrbit(const QPointF &screenPos, double anchorZ=0.0)
Start an orbit gesture at the given screen position (pixels); anchorZ as in beginPan.
Q_INVOKABLE void orbitTo(const QPointF &screenPos)
qreal verticalFieldOfView() const
void setDistance(qreal distance)
GeoMapCamera(QObject *parent=nullptr)
Q_INVOKABLE void lookAt(const QGeoCoordinate &center, qreal heading, qreal tilt, qreal distance)
Set the full pose in one call. tilt/distance are clamped.
std::optional< QPointF > screenToGround(const QPointF &screenPos, double planeZ=0.0) const
QVector3D cameraPosition() const
void setHeading(qreal heading)
QVector3D scenePosition() const
void setViewportSize(const QSizeF &size)
void viewportSizeChanged()
static constexpr qreal kDefaultDistance
Q_INVOKABLE void reset()
Restore the default pose at the current center (heading 0, tilt 0, default distance)
static constexpr qreal kMaxTilt
qreal heading() const
Q_INVOKABLE void tiltBy(qreal degrees, const QPointF &screenPos)
Mode mode() const
std::optional< QPointF > groundPointCapped(const QPointF &screenPos, double maxRange) const
Q_INVOKABLE void rotateBy(qreal degrees, const QPointF &screenPos)
std::optional< PickRay > pickRayAt(const QPointF &screenPos) const
Q_INVOKABLE qreal distanceForZoomLevel(qreal zoomLevel) const
void centerChanged()
qreal tilt() const
Q_INVOKABLE QGeoCoordinate centerForCoordinateAtScreenPoint(const QGeoCoordinate &coordinate, const QPointF &screenPos, double worldZ=0.0, double centerElevation=qQNaN()) const
QGeoCoordinate center() const
void modeChanged()
void setTilt(qreal tilt)
static constexpr qreal kMaxDistance
QQuaternion sceneRotation() const
double distance(const Ecef &left, const Ecef &right)
Straight-line distance between left and right, in metres.
constexpr int kTilePixels
Nominal tile edge used for meters-per-pixel.
Definition TileMath.h:32
double worldSize()
Full mercator world extent (2*pi*R) in world meters.
Definition TileMath.cc:18
QPointF geoToWorld(const QGeoCoordinate &coord)
Geo -> world meters. Latitude is clamped to +/-kMaxLatitude.
Definition TileMath.cc:23
QGeoCoordinate worldToGeo(const QPointF &world)
World meters -> geo (altitude 0)
Definition TileMath.cc:31