QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
GPSObservation.cc
Go to the documentation of this file.
1#include "GPSObservation.h"
2
3#include <algorithm>
4#include <cmath>
5
6#include <QtCore/QDateTime>
7#include <QtCore/QTimeZone>
8#include <QtCore/QtMath>
9
10#include "GPSReceiverConfig.h"
11#include "GPSReceiverReports.h"
12
14{
15 GPSObservation observation;
16 observation.monotonicTimestampUs = receivedAtUs;
17 observation.fixQuality = navigation.fixType;
18 observation.receiverFixValid = navigation.fixType != FixQuality::Unknown && navigation.fixType != FixQuality::NoFix;
19 if (qIsFinite(navigation.latitudeDegrees) && qIsFinite(navigation.longitudeDegrees)) {
20 QGeoCoordinate coordinate(navigation.latitudeDegrees, navigation.longitudeDegrees);
21 if (qIsFinite(navigation.altitudeMslMeters)) {
22 coordinate.setAltitude(navigation.altitudeMslMeters);
24 }
25 const QDateTime timestamp =
26 navigation.utcTimeUs
27 ? QDateTime::fromMSecsSinceEpoch(static_cast<qint64>(navigation.utcTimeUs / 1000), QTimeZone::UTC)
28 : QDateTime::currentDateTimeUtc();
29 observation.position = QGeoPositionInfo(coordinate, timestamp);
30 }
31 const auto setAttribute = [&observation](QGeoPositionInfo::Attribute attribute, double value, bool allowZero) {
32 if (qIsFinite(value) && (value > 0 || (allowZero && value == 0))) {
33 observation.position.setAttribute(attribute, value);
34 }
35 };
36 // Receivers without a metric estimate, such as NMEA without GST, fall back to DOP.
37 const auto accuracy = [](float meters, float dop) {
38 return qIsFinite(meters) && meters > 0 ? meters : (qIsFinite(dop) && dop > 0 ? accuracyFromDop(dop) : qQNaN());
39 };
40 setAttribute(QGeoPositionInfo::HorizontalAccuracy,
41 accuracy(navigation.horizontalAccuracyMeters, navigation.horizontalDop), false);
42 setAttribute(QGeoPositionInfo::VerticalAccuracy,
43 accuracy(navigation.verticalAccuracyMeters, navigation.verticalDop), false);
44 setAttribute(QGeoPositionInfo::GroundSpeed, navigation.speedMetersPerSecond, true);
45 if (qIsFinite(navigation.courseRadians)) {
46 const double degrees = std::fmod(qRadiansToDegrees(static_cast<double>(navigation.courseRadians)), 360.0);
47 observation.position.setAttribute(QGeoPositionInfo::Direction, degrees < 0 ? degrees + 360.0 : degrees);
48 }
49 if (qIsFinite(navigation.altitudeEllipsoidMeters)) {
50 observation.altitudeEllipsoidMeters = navigation.altitudeEllipsoidMeters;
51 }
52 if (navigation.satellitesUsed) {
53 observation.satellitesUsed = static_cast<int>(*navigation.satellitesUsed);
54 }
55 if (qIsFinite(navigation.horizontalDop) && navigation.horizontalDop > 0) {
56 observation.horizontalDop = navigation.horizontalDop;
57 }
58 return observation;
59}
60
62 quint64 receivedAtUs)
63{
64 // A declared position with no stated accuracy is still a precise, operator-supplied position.
65 constexpr double MINIMUM_ACCURACY_METERS = 0.01;
66 GPSObservation observation;
67 observation.monotonicTimestampUs = receivedAtUs;
68 observation.fixQuality = FixQuality::Fix3D;
69 observation.receiverFixValid = true;
70 if (qIsFinite(position.latitudeDegrees) && qIsFinite(position.longitudeDegrees) && qIsFinite(accuracyMeters)) {
71 // Only ellipsoid height is known, so the ground-station coordinate stays two-dimensional.
72 observation.position = QGeoPositionInfo(QGeoCoordinate(position.latitudeDegrees, position.longitudeDegrees),
73 QDateTime::currentDateTimeUtc());
74 observation.position.setAttribute(QGeoPositionInfo::HorizontalAccuracy,
75 (std::max) (accuracyMeters, MINIMUM_ACCURACY_METERS));
76 }
77 if (qIsFinite(position.altitudeMeters)) {
78 observation.altitudeEllipsoidMeters = position.altitudeMeters;
79 }
80 return observation;
81}
82
84{
85 constexpr double USER_EQUIVALENT_RANGE_ERROR_METERS = 5.1;
86 constexpr double CONFIDENCE_SCALE = 2.0;
87 return dop * USER_EQUIVALENT_RANGE_ERROR_METERS * CONFIDENCE_SCALE;
88}
89
90std::optional<GPSObservation> GPSObservation::projected(PositionUse use) const
91{
92 if (!hasNavigationSolution() || (use != PositionUse::Gga && !usable())) {
93 return std::nullopt;
94 }
95 GPSObservation accepted = *this;
96 switch (use) {
99 break;
101 accepted.position.setCoordinate(coordinate());
102 if (accepted.position.coordinate().type() != QGeoCoordinate::Coordinate3D) {
103 accepted.position.removeAttribute(QGeoPositionInfo::VerticalAccuracy);
104 }
105 break;
108 auto ellipsoidCoordinate = accepted.position.coordinate();
109 ellipsoidCoordinate.setAltitude(*altitudeEllipsoidMeters);
110 accepted.position.setCoordinate(ellipsoidCoordinate);
112 } else if (!qIsFinite(accepted.position.coordinate().altitude())) {
113 auto horizontalCoordinate = accepted.position.coordinate();
114 horizontalCoordinate.setAltitude(qQNaN());
115 accepted.position.setCoordinate(horizontalCoordinate);
116 accepted.position.removeAttribute(QGeoPositionInfo::VerticalAccuracy);
118 }
119 // Otherwise the altitude keeps its datum, which the consumer checks before calling it geodetic.
120 break;
121 }
122 if (use == PositionUse::Motion) {
123 const double course = heading();
124 if (qIsFinite(course)) {
125 accepted.position.setAttribute(QGeoPositionInfo::Direction, course);
126 } else {
127 accepted.position.removeAttribute(QGeoPositionInfo::Direction);
128 accepted.position.removeAttribute(QGeoPositionInfo::DirectionAccuracy);
129 }
130 }
131 return accepted;
132}
133
135{
136 return position.isValid() && receiverFixValid.value_or(true) && fixQuality != FixQuality::NoFix;
137}
138
140{
141 const double accuracy = position.attribute(QGeoPositionInfo::HorizontalAccuracy);
142 return hasNavigationSolution() && position.hasAttribute(QGeoPositionInfo::HorizontalAccuracy) &&
143 qIsFinite(accuracy) && accuracy > 0 && accuracy <= 100;
144}
145
146QGeoCoordinate GPSObservation::coordinate() const
147{
148 if (!usable()) {
149 return {};
150 }
151 QGeoCoordinate coordinate(position.coordinate().latitude(), position.coordinate().longitude());
152 const double accuracy = position.attribute(QGeoPositionInfo::VerticalAccuracy);
153 if (position.hasAttribute(QGeoPositionInfo::VerticalAccuracy) && qIsFinite(accuracy) && accuracy > 0 &&
154 accuracy <= 10 && qIsFinite(position.coordinate().altitude())) {
155 coordinate.setAltitude(position.coordinate().altitude());
156 }
157 return coordinate;
158}
159
161{
162 const double direction = position.attribute(QGeoPositionInfo::Direction);
163 const double speed = position.attribute(QGeoPositionInfo::GroundSpeed);
164 const double accuracy = position.attribute(QGeoPositionInfo::DirectionAccuracy);
165 const bool accuracyAcceptable = !position.hasAttribute(QGeoPositionInfo::DirectionAccuracy) ||
166 (qIsFinite(accuracy) && accuracy >= 0 && accuracy <= 30);
167 // Course over ground is unreliable when nearly stationary.
168 if (!usable() || !position.hasAttribute(QGeoPositionInfo::Direction) || !qIsFinite(direction) || direction < 0 ||
169 direction > 360 || !position.hasAttribute(QGeoPositionInfo::GroundSpeed) || !qIsFinite(speed) || speed < 0.5 ||
170 !accuracyAcceptable) {
171 return qQNaN();
172 }
173 return direction == 360 ? 0 : direction;
174}
WGS84 geodetic position with ellipsoid height, not mean-sea-level altitude.
std::optional< uint8_t > satellitesUsed
Receiver-independent data. Unknown metadata remains absent, never a manufactured zero.
std::optional< double > altitudeEllipsoidMeters
FixQuality fixQuality
double heading() const
QGeoPositionInfo position
static GPSObservation fromSurveyedPosition(const GPSEllipsoidPosition &position, double accuracyMeters, quint64 receivedAtUs)
A known antenna position, such as a fixed or surveyed RTK base, which receivers report as a time-only...
static GPSObservation fromNavigation(const GPSNavigationValues &navigation, quint64 receivedAtUs)
Converts a native receiver solution that arrived at receivedAtUs on the monotonic clock.
QGeoCoordinate coordinate() const
std::optional< GPSObservation > projected(PositionUse use) const
The owner separately enforces freshness and session authorization.
std::optional< int > satellitesUsed
bool hasNavigationSolution() const
quint64 monotonicTimestampUs
GPSAltitudeDatum altitudeDatum
std::optional< bool > receiverFixValid
bool usable() const
static double accuracyFromDop(double dop)
Nominal accuracy in meters for receivers that report only DOP (DOP x 5.1 m UERE x 2).
std::optional< double > horizontalDop