8std::optional<double> finiteValue(
double value)
10 return std::isfinite(value) ? std::optional<double>(value) : std::nullopt;
13std::optional<double> nonnegativeNumber(std::string_view field)
15 const auto value = NMEA::number<double>(field);
16 return value && *value >= 0.0 ? value : std::nullopt;
41 const auto sequence = ++_sequence;
44 if (!navigation->valid) {
45 _navigationValid =
false;
46 _invalidThroughSequence = sequence;
48 loss.
timeMs = navigation->utcMilliseconds;
59 _navigationValid =
true;
62 if (
auto update = _handlePositionSentence(
sentence, receivedAtUs)) {
65 if (
auto update = _handleAccuracy(
sentence, receivedAtUs)) {
68 return _handleUntimedMetadata(
sentence, receivedAtUs);
73 auto* stored = _current();
74 if (!stored || !stored->epoch.verticalDopReceivedAtUs ||
78 stored->epoch.verticalDop.reset();
79 stored->epoch.verticalDopReceivedAtUs = 0;
83NavigationEpochAssembler::StoredEpoch& NavigationEpochAssembler::_find(std::optional<int> timeMs)
85 if (
auto* existing = _findExisting(timeMs)) {
89 auto oldest = _epochs.begin();
90 for (
auto epoch = _epochs.begin(); epoch != _epochs.end(); ++epoch) {
95 if (epoch->epoch.receivedAtUs < oldest->epoch.receivedAtUs) {
99 _resetEpoch(*oldest, timeMs);
103NavigationEpochAssembler::StoredEpoch* NavigationEpochAssembler::_findExisting(std::optional<int> timeMs)
105 const int key = timeMs.value_or(NO_TIME_KEY);
106 for (
auto& epoch : _epochs) {
107 if (epoch.active && epoch.key == key) {
114NavigationEpochAssembler::StoredEpoch* NavigationEpochAssembler::_current()
119 for (
auto& epoch : _epochs) {
120 if (epoch.active && epoch.key == _currentKey) {
127void NavigationEpochAssembler::_resetEpoch(StoredEpoch& stored, std::optional<int> timeMs)
130 stored.active =
true;
131 stored.key = timeMs.value_or(NO_TIME_KEY);
132 stored.epoch.timeMs = timeMs;
135void NavigationEpochAssembler::_resetExpiredEpoch(StoredEpoch& stored, uint64_t receivedAtUs)
137 if (stored.epoch.receivedAtUs && receivedAtUs > stored.epoch.receivedAtUs &&
139 _resetEpoch(stored, stored.epoch.timeMs);
143NavigationEpoch& NavigationEpochAssembler::_positionEpoch(std::optional<int> timeMs, uint64_t receivedAtUs)
145 auto& stored = _find(timeMs);
146 _resetExpiredEpoch(stored, receivedAtUs);
147 _currentKey = stored.key;
152bool NavigationEpochAssembler::_hiddenByFixLoss(
const NavigationEpoch& epoch)
const
154 return !_navigationValid || epoch.sequence <= _invalidThroughSequence;
157std::optional<NavigationUpdate> NavigationEpochAssembler::_handlePositionSentence(
const Sentence&
sentence,
158 uint64_t receivedAtUs)
161 NavigationEpoch* epoch =
nullptr;
163 if (!rmcFix->utcMilliseconds) {
166 epoch = &_positionEpoch(rmcFix->utcMilliseconds, receivedAtUs);
167 epoch->latitude = rmcFix->latitude;
168 epoch->longitude = rmcFix->longitude;
169 epoch->speedMetersPerSecond = finiteValue(rmcFix->speedMetersPerSecond);
170 epoch->courseDegrees = finiteValue(rmcFix->courseDegrees);
173 epoch->ggaQuality = ggaFix->quality;
174 epoch->latitude = ggaFix->latitude;
175 epoch->longitude = ggaFix->longitude;
176 epoch->altitudeMslMeters = finiteValue(ggaFix->altitude);
177 epoch->geoidSeparationMeters = finiteValue(ggaFix->geoidSeparation);
178 epoch->satellitesUsed = ggaFix->satellitesUsed;
179 epoch->horizontalDop = finiteValue(ggaFix->hdop);
180 if (!epoch->accuracyReceivedAtUs ||
182 epoch->horizontalAccuracyMeters.reset();
183 epoch->verticalAccuracyMeters.reset();
184 epoch->accuracyReceivedAtUs = 0;
187 if (!gllFix->utcMilliseconds) {
190 epoch = &_positionEpoch(gllFix->utcMilliseconds, receivedAtUs);
191 epoch->latitude = gllFix->latitude;
192 epoch->longitude = gllFix->longitude;
198 markReceipt(*epoch, receivedAtUs);
199 epoch->positionReceivedAtUs = receivedAtUs;
200 epoch->sequence = _sequence;
201 return NavigationUpdate{
205std::optional<NavigationUpdate> NavigationEpochAssembler::_handleAccuracy(
const Sentence&
sentence,
206 uint64_t receivedAtUs)
216 auto& stored = _find(timeMs);
217 _resetExpiredEpoch(stored, receivedAtUs);
218 auto& epoch = stored.epoch;
219 epoch.horizontalAccuracyMeters = finiteValue(accuracy->horizontalAccuracy);
220 epoch.verticalAccuracyMeters = finiteValue(accuracy->verticalAccuracy);
221 epoch.accuracyReceivedAtUs = receivedAtUs;
222 markReceipt(epoch, receivedAtUs);
223 if (!_hasCurrent || _currentKey != stored.key || _hiddenByFixLoss(epoch)) {
226 return NavigationUpdate{
230std::optional<NavigationUpdate> NavigationEpochAssembler::_handleUntimedMetadata(
const Sentence&
sentence,
231 uint64_t receivedAtUs)
233 auto* stored = _current();
234 if (!stored || !stored->epoch.positionReceivedAtUs ||
239 auto& epoch = stored->epoch;
244 if (!epoch.horizontalDop) {
248 epoch.verticalDop = vdop;
249 epoch.verticalDopReceivedAtUs = vdop ? receivedAtUs : 0;
251 epoch.speedMetersPerSecond = finiteValue(velocity->speedMetersPerSecond);
252 epoch.courseDegrees = finiteValue(velocity->courseDegrees);
256 if (_hiddenByFixLoss(epoch)) {
259 return NavigationUpdate{
static constexpr std::chrono::microseconds METADATA_MAX_AGE
Receipt age beyond which retained metadata no longer joins a new sentence of its epoch.
std::optional< NavigationEpoch > expireUntimedMetadata(uint64_t nowUs)
std::optional< NavigationUpdate > ingest(const Sentence &sentence, uint64_t receivedAtUs)
constexpr bool withinAge(uint64_t timestampUs, uint64_t nowUs, std::chrono::microseconds maximumAge)
Whether a receipt is at most maximumAge old, its limit included.
constexpr size_t GSA_VDOP
constexpr size_t GSA_MIN_FIELDS
constexpr size_t GGA_SATELLITES_USED
constexpr size_t GSA_HDOP
constexpr size_t UTC_TIME
std::optional< Sentence > sentence(std::string_view text)
GPSFixQuality fixQuality(unsigned quality, GPSFixQuality autonomous)
Autonomous GGA does not distinguish 2D/3D; the caller supplies its independently resolved quality.
std::optional< int > utcMilliseconds(std::string_view field)
std::optional< RMC > rmc(const Sentence &input)
std::optional< GLL > gll(const Sentence &input)
std::optional< GGA > gga(const Sentence &input)
Quality zero is a valid fix-loss observation with empty coordinates and optional hemisphere fields.
std::optional< VTG > vtg(const Sentence &input)
std::optional< NavigationStatus > navigationStatus(const Sentence &input)
std::optional< GST > gst(const Sentence &input)
std::optional< double > altitudeEllipsoidMeters() const
std::optional< double > geoidSeparationMeters
std::optional< double > altitudeMslMeters
std::optional< int > timeMs
std::optional< unsigned > satellitesUsed
uint64_t positionReceivedAtUs