20#include <unordered_map>
21#include <unordered_set>
28#include <spdlog/common.h>
29#include <spdlog/spdlog.h>
57#include <fmt/format.h>
97 return "RealTimeKinematic - RTK";
107 return "Data Processor";
112 auto nSatColumnContent = [&](
size_t pinIndex) ->
bool {
115 size_t usedSatNum = 0;
116 std::string usedSats;
119 std::string filler =
", ";
120 for (
const auto& satellite : gnssNavInfo->v->satellites())
122 if (
_obsFilter.isSatelliteAllowed(satellite.first))
125 usedSats += (allSats.empty() ?
"" : filler) + fmt::format(
"{}", satellite.first);
127 allSats += (allSats.empty() ?
"" : filler) + fmt::format(
"{}", satellite.first);
129 ImGui::TextUnformatted(fmt::format(
"{} / {}", usedSatNum, gnssNavInfo->v->nSatellites()).c_str());
130 if (ImGui::IsItemHovered())
132 ImGui::SetTooltip(
"Used satellites: %s\n"
133 "Avail satellites: %s",
134 usedSats.c_str(), allSats.c_str());
147 ImGui::PushFont(Application::MonoFont());
148 auto totalSolutions = nFixSolutions + nFloatSolutions + nSingleSolutions;
149 ImGui::SetNextItemOpen(
true, ImGuiCond_Always);
150 if (ImGui::TreeNode(
"Solution statistics:"))
152 ImGui::BulletText(
"Fix: %6.2f%% (%zu)", totalSolutions > 0 ?
static_cast<double>(nFixSolutions) /
static_cast<double>(totalSolutions) * 100.0 : 0.0, nFixSolutions);
153 ImGui::BulletText(
"Float: %6.2f%% (%zu)", totalSolutions > 0 ?
static_cast<double>(nFloatSolutions) /
static_cast<double>(totalSolutions) * 100.0 : 0.0, nFloatSolutions);
154 ImGui::BulletText(
"Single: %6.2f%% (%zu)", totalSolutions > 0 ?
static_cast<double>(nSingleSolutions) /
static_cast<double>(totalSolutions) * 100.0 : 0.0, nSingleSolutions);
157 if (ImGui::TreeNode(fmt::format(
"Cycle-slips: {}", _nCycleSlipsLLI + _nCycleSlipsSingle + _nCycleSlipsDual).c_str()))
159 ImGui::BulletText(
"LLI: %zu", _nCycleSlipsLLI);
160 ImGui::BulletText(
"Single Freq: %zu", _nCycleSlipsSingle);
162 gui::widgets::HelpMarker(
"Single Frequency detection is confirmed by\nthe dual frequency detector before accepted.\nIt then only counts towards the single frequency count though.");
163 ImGui::BulletText(
"Dual Freq: %zu", _nCycleSlipsDual);
165 gui::widgets::HelpMarker(
"Only cycle-slips which were not recognized by\nthe single frequency detector or had the LLI flag set.");
168 ImGui::BulletText(
"Pivot changes: %zu", _nPivotChange);
169 if (_kalmanFilter.isNISenabled()) { ImGui::BulletText(
"NIS triggered: %zu", _nMeasExcludedNIS); }
175 const float cWidth = itemWidth - ImGui::GetStyle().IndentSpacing;
177 if (_obsFilter.ShowGuiWidgets<ReceiverType>(nameId().c_str(), itemWidth))
182 if (_obsEstimator.ShowGuiWidgets<ReceiverType>(nameId().c_str(), itemWidth))
187 ImGui::SetNextItemOpen(
true, ImGuiCond_FirstUseEver);
188 if (ImGui::TreeNode(fmt::format(
"Kalman Filter##{}", nameId()).c_str()))
190 if (_kalmanFilter.showKalmanFilterGUI(nameId().c_str(), itemWidth))
195 if (_kalmanFilter.isNISenabled())
197 auto tmp =
static_cast<int>(_maxRemoveOutlier);
198 ImGui::SetNextItemWidth(cWidth);
199 if (
ImGui::InputIntL(fmt::format(
"Max. remove per epoch##{}", nameId()).c_str(), &tmp, 0))
201 _maxRemoveOutlier =
static_cast<size_t>(tmp);
207 tmp =
static_cast<int>(_outlierRemoveEpochs);
208 ImGui::SetNextItemWidth(cWidth);
209 if (
ImGui::InputIntL(fmt::format(
"Epochs to exclude outlier##{}", nameId()).c_str(), &tmp, 1))
211 _outlierRemoveEpochs =
static_cast<size_t>(tmp);
217 tmp =
static_cast<int>(_outlierMinSat);
218 ImGui::SetNextItemWidth(cWidth);
219 if (
ImGui::InputIntL(fmt::format(
"Min. satellites for check##{}", nameId()).c_str(), &tmp, 1))
221 _outlierMinSat =
static_cast<size_t>(tmp);
227 tmp =
static_cast<int>(_outlierMinPsrObsKeep);
228 ImGui::SetNextItemWidth(cWidth);
229 if (
ImGui::InputIntL(fmt::format(
"# of pseudorange obs to keep##{}", nameId()).c_str(), &tmp, 1))
231 _outlierMinPsrObsKeep =
static_cast<size_t>(tmp);
237 ImGui::SetNextItemWidth(cWidth);
238 if (
ImGui::InputDoubleL(fmt::format(
"Max Position Var for NIS in startup##{}",
size_t(
id)).c_str(), &_outlierMaxPosVarStartup, 0, std::numeric_limits<double>::max(), 0.0, 0.0,
"%.4f m"))
243 gui::widgets::HelpMarker(
"Maximum position variance\nin order to attempt outlier removal during the startup phase.");
245 ImGui::SetNextItemOpen(
false, ImGuiCond_FirstUseEver);
246 if (ImGui::TreeNode(fmt::format(
"Matrices##Kalman Filter {}", nameId()).c_str()))
248 _kalmanFilter.showKalmanFilterMatrixViews(nameId().c_str());
254 ImGui::SetNextItemOpen(
true, ImGuiCond_FirstUseEver);
255 if (ImGui::TreeNode(fmt::format(
"System/Process noise (Standard Deviations)##{}",
size_t(
id)).c_str()))
257 if (
gui::widgets::InputDouble2WithUnit(fmt::format(
"Acceleration due to user motion (Hor/Ver)##{}",
size_t(
id)).c_str(), cWidth, unitWidth, _gui_stdevAccel.data(), _gui_stdevAccelUnits,
"m/√(s^3)\0\0",
"%.2e", ImGuiInputTextFlags_CharsScientific))
259 LOG_DEBUG(
"{}: stdevAccel changed to horizontal {} and vertical {}", nameId(), _gui_stdevAccel.at(0), _gui_stdevAccel.at(1));
260 LOG_DEBUG(
"{}: stdevAccelNoiseUnits changed to {}", nameId(), fmt::underlying(_gui_stdevAccelUnits));
264 cWidth, unitWidth, &_gui_ambiguityFloatProcessNoiseStDev,
265 _gui_stdevAmbiguityFloatUnits,
"cycle\0\0",
266 0.0, 0.0,
"%.2e", ImGuiInputTextFlags_CharsScientific))
268 LOG_DEBUG(
"{}: ambiguityFloatProcessNoiseStDev changed to {}", nameId(), _gui_ambiguityFloatProcessNoiseStDev);
269 LOG_DEBUG(
"{}: stdevAmbiguityFloatUnits changed to {}", nameId(), fmt::underlying(_gui_stdevAmbiguityFloatUnits));
271 switch (_gui_stdevAmbiguityFloatUnits)
273 case StdevAmbiguityUnits::Cycle:
274 _ambiguityFloatProcessNoiseVariance = std::pow(_gui_ambiguityFloatProcessNoiseStDev, 2);
281 cWidth, unitWidth, &_gui_ambiguityFixProcessNoiseStDev,
282 _gui_stdevAmbiguityFixUnits,
"cycle\0\0",
283 0.0, 0.0,
"%.2e", ImGuiInputTextFlags_CharsScientific))
285 LOG_DEBUG(
"{}: ambiguityFixProcessNoiseStDev changed to {}", nameId(), _gui_ambiguityFixProcessNoiseStDev);
286 LOG_DEBUG(
"{}: stdevAmbiguityFixUnits changed to {}", nameId(), fmt::underlying(_gui_stdevAmbiguityFixUnits));
288 switch (_gui_stdevAmbiguityFixUnits)
290 case StdevAmbiguityUnits::Cycle:
291 _ambiguityFixProcessNoiseVariance = std::pow(_gui_ambiguityFixProcessNoiseStDev, 2);
301 ImGui::SetNextItemOpen(
true, ImGuiCond_FirstUseEver);
302 if (ImGui::TreeNode(fmt::format(
"Ambiguity resolution##{}",
size_t(
id)).c_str()))
304 if (!_obsFilter.isObsTypeUsed(
GnssObs::Carrier)) { ImGui::BeginDisabled(); }
306 ImGui::SetNextItemWidth(cWidth);
309 LOG_DEBUG(
"{}: Ambiguity resolution strategy changed to {}", nameId(),
NAV::to_string(_ambiguityResolutionStrategy));
313 if (
GuiAmbiguityResolution(fmt::format(
"Ambiguity resolution##{}",
size_t(
id)).c_str(), _ambiguityResolutionParameters, itemWidth))
315 LOG_DEBUG(
"{}: Ambiguity resolution parameters changed to [{}][{}]", nameId(),
316 NAV::to_string(_ambiguityResolutionParameters.decorrelationAlgorithm),
321 ImGui::SetNextItemWidth(cWidth);
322 if (
ImGui::InputDoubleL(fmt::format(
"Max Position Var for AR##{}",
size_t(
id)).c_str(), &_maxPosVar, 0, std::numeric_limits<double>::max(), 0.0, 0.0,
"%.4f m"))
329 auto tmp =
static_cast<int>(_nMinSatForAmbFix);
330 ImGui::SetNextItemWidth(cWidth);
331 if (
ImGui::InputIntL(fmt::format(
"Min Sat for Fix##{}",
size_t(
id)).c_str(), &tmp, 0))
333 _nMinSatForAmbFix =
static_cast<size_t>(tmp);
337 gui::widgets::HelpMarker(
"Minimum amount of satellites with carrier observations\nin order to attempt ambiguity fixing.");
340 tmp =
static_cast<int>(_nMinSatForAmbHold);
341 ImGui::SetNextItemWidth(cWidth);
342 if (
ImGui::InputIntL(fmt::format(
"Min Sat for Hold##{}",
size_t(
id)).c_str(), &tmp, 0))
344 _nMinSatForAmbHold =
static_cast<size_t>(tmp);
348 gui::widgets::HelpMarker(
"Minimum amount of satellites with carrier observations\nin order to attempt ambiguity holding.");
350 if (ImGui::Checkbox(fmt::format(
"Apply fixed ambiguities with KF update##{}",
size_t(
id)).c_str(), &_applyFixedAmbiguitiesWithUpdate))
356 "Otherwise apply via\n"
358 "- b = b_float - Q_ba * Q_aa^-1 (a_fix - a_float)");
360 if (_applyFixedAmbiguitiesWithUpdate
362 cWidth, unitWidth, &_gui_ambFixUpdateStdDev,
363 _gui_ambFixUpdateStdDevUnits,
"cycle\0\0",
364 0.0, 0.0,
"%.2e", ImGuiInputTextFlags_CharsScientific))
366 LOG_DEBUG(
"{}: ambFixUpdateStdDev changed to {}", nameId(), _gui_ambFixUpdateStdDev);
367 LOG_DEBUG(
"{}: ambFixUpdateStdDevUnits changed to {}", nameId(), fmt::underlying(_gui_ambFixUpdateStdDevUnits));
369 switch (_gui_ambFixUpdateStdDevUnits)
371 case StdevAmbiguityUnits::Cycle:
372 _ambFixUpdateVariance = std::pow(_gui_ambFixUpdateStdDev, 2);
379 ImGui::SetNextItemOpen(
true, ImGuiCond_FirstUseEver);
380 if (ImGui::TreeNode(fmt::format(
"Cycle-slip detection##Tree{}",
size_t(
id)).c_str()))
382 ImGui::SetNextItemOpen(
true, ImGuiCond_FirstUseEver);
383 if (ImGui::TreeNode(fmt::format(
"Base##Tree{}",
size_t(
id)).c_str()))
385 if (
CycleSlipDetectorGui(fmt::format(
"Cycle-slip detector Base##{}",
size_t(
id)).c_str(), _cycleSlipDetector[Base], 145.0F))
391 ImGui::SetNextItemOpen(
true, ImGuiCond_FirstUseEver);
392 if (ImGui::TreeNode(fmt::format(
"Rover##Tree{}",
size_t(
id)).c_str()))
394 if (
CycleSlipDetectorGui(fmt::format(
"Cycle-slip detector Rover##{}",
size_t(
id)).c_str(), _cycleSlipDetector[Rover], 145.0F))
407 if (ImGui::TreeNode(fmt::format(
"Misc##{}",
size_t(
id)).c_str()))
409 if (ImGui::Checkbox(fmt::format(
"Output a SPP solution if no base observation available##{}",
size_t(
id)).c_str(), &_calcSPPIfNoBase))
413 ImGui::SetNextItemWidth(cWidth);
414 if (
ImGui::InputDoubleL(fmt::format(
"Max Time between rover & base obs##{}",
size_t(
id)).c_str(), &_maxTimeBetweenBaseRoverForRTK, 1e-3))
422 ImGui::SetNextItemOpen(
true, ImGuiCond_FirstUseEver);
423 if (ImGui::TreeNode(fmt::format(
"Events##{}",
size_t(
id)).c_str()))
425 if (ImGui::Checkbox(fmt::format(
"Output State Change Events##{}",
size_t(
id)).c_str(), &_outputStateEvents))
429 ImGui::SetNextItemWidth(cWidth);
430 if (ImGui::InputText(fmt::format(
"Events Filter Regex##{}",
size_t(
id)).c_str(), &_eventFilterRegex))
434 if (!_events.empty())
436 std::optional<std::regex> filter;
439 filter = std::regex(_eventFilterRegex, std::regex_constants::ECMAScript | std::regex_constants::icase);
444 if (ImGui::BeginChild(fmt::format(
"Events list {}",
size_t(
id)).c_str(), ImVec2(0.0F, 600.0F),
true))
446 ImGui::PushFont(Application::MonoFont());
447 for (
size_t i = 0; i < _events.size(); i++)
449 if (!std::any_of(_events.at(i).second.begin(), _events.at(i).second.end(), [&](
const std::string& text) {
450 return _eventFilterRegex.empty() || (filter && std::regex_search(text, *filter));
456 if (i != 0) { ImGui::Separator(); }
457 ImGui::SetNextItemOpen(
true, ImGuiCond_Always);
458 if (ImGui::TreeNode(fmt::format(
"{} GPST", _events.at(i).first.toYMDHMS(
GPST)).c_str()))
460 for (
const auto& text : _events.at(i).second)
462 if (_eventFilterRegex.empty() || (filter && std::regex_search(text, *filter)))
464 ImGui::BulletText(
"%s", text.c_str());
526 if (j.contains(
"obsFilter")) { j.at(
"obsFilter").get_to(
_obsFilter); }
527 if (j.contains(
"obsEstimator")) { j.at(
"obsEstimator").get_to(
_obsEstimator); }
528 if (j.contains(
"kalmanFilter")) { j.at(
"kalmanFilter").get_to(
_kalmanFilter); }
529 if (j.contains(
"maxRemoveOutlier")) { j.at(
"maxRemoveOutlier").get_to(
_maxRemoveOutlier); }
530 if (j.contains(
"outlierRemoveEpochs")) { j.at(
"outlierRemoveEpochs").get_to(
_outlierRemoveEpochs); }
531 if (j.contains(
"outlierMinSat")) { j.at(
"outlierMinSat").get_to(
_outlierMinSat); }
532 if (j.contains(
"outlierMinPsrObsKeep")) { j.at(
"outlierMinPsrObsKeep").get_to(
_outlierMinPsrObsKeep); }
534 if (j.contains(
"calcSPPIfNoBase")) { j.at(
"calcSPPIfNoBase").get_to(
_calcSPPIfNoBase); }
539 if (j.contains(
"nMinSatForAmbFix")) { j.at(
"nMinSatForAmbFix").get_to(
_nMinSatForAmbFix); }
540 if (j.contains(
"nMinSatForAmbHold")) { j.at(
"nMinSatForAmbHold").get_to(
_nMinSatForAmbHold); }
541 if (j.contains(
"maxPosVar")) { j.at(
"maxPosVar").get_to(
_maxPosVar); }
544 if (j.contains(
"ambFixUpdateStdDev"))
555 if (j.contains(
"cycleSlipDetector")) { j.at(
"cycleSlipDetector").get_to(
_cycleSlipDetector); }
556 if (j.contains(
"eventFilterRegex")) { j.at(
"eventFilterRegex").get_to(
_eventFilterRegex); }
557 if (j.contains(
"outputStateEvents")) { j.at(
"outputStateEvents").get_to(
_outputStateEvents); }
560 if (j.contains(
"stdevAccel")) {
_gui_stdevAccel = j.at(
"stdevAccel"); }
563 if (j.contains(
"ambiguityFloatProcessNoiseStDev"))
575 if (j.contains(
"ambiguityFixProcessNoiseStDev"))
593 LOG_ERROR(
"{}: You need to connect a GNSS NavigationInfo provider",
nameId());
638 std::array<double, 2> varAccel{};
645 LOG_DATA(
" sigma2_accel = h: {}, v: {} [m^2 / s^3]",
nameId(), varAccel.at(0), varAccel.at(1));
648 LOG_DEBUG(
"RealTimeKinematic initialized");
670 auto iter = std::ranges::find_if(
_events, [&](
const auto& event) {
return event.first == rtkSol->insTime; });
673 _events.emplace_back(rtkSol->insTime, std::vector{ text });
677 iter->second.emplace_back(text);
679 rtkSol->addEvent(text);
684#if LOG_LEVEL <= LOG_LEVEL_DATA
687 for (
const auto& obs : observations.signals)
689 satData[obs.first.toSatId()].first |= obs.first.freq();
690 satData[obs.first.toSatId()].second |= obs.first.code;
693 if (std::ranges::all_of(obs.second.recvObs, [&obsType](
const auto& recvObs) {
694 return recvObs.second->obs.contains(static_cast<GnssObs::ObservationType>(obsType));
701 for ([[maybe_unused]]
const auto& [satId, freqCode] : satData)
703 LOG_DATA(
"{}: [{}] on frequencies [{}] with codes [{}]",
nameId(), satId, freqCode.first, freqCode.second);
704 for (
const auto& [satSigId, obs] : sigData)
706 if (satSigId.toSatId() != satId) {
continue; }
708 for (
const auto& o : obs)
710 if (!
str.empty()) {
str +=
", "; }
711 str += fmt::format(
"{}", o);
721 auto gnssObs = std::static_pointer_cast<const Pos>(queue.
extract_front());
722 LOG_DATA(
"{}: Received Base Position for [{}]",
nameId(), gnssObs->insTime.toYMDHMS(
GPST));
749 LOG_TRACE(
"{}: Data gap between Base [{}] and Rover [{}] is {:.1f}s. Falling back to SPP for gaps larger than 0.5s",
nameId(),
774 LOG_TRACE(
"{}: Calculating SPP Fallback, as Rover obs [{}] is {:.3f} seconds away from last base obs [{}]. Maximum time to consider the base observation is {} seconds.",
798 if (!sppSol->e_velocity().hasNaN())
806 if (sppSol->e_CovarianceMatrix().has_value())
826 std::vector<InputPin::IncomingLink::ValueWrapper<GnssNavInfo>> gnssNavInfoWrappers;
827 std::vector<const GnssNavInfo*> gnssNavInfos;
832 gnssNavInfoWrappers.push_back(*gnssNavInfo);
833 gnssNavInfos.push_back(gnssNavInfo->v);
836 if (!gnssNavInfos.empty())
838 auto rtkSol = std::make_shared<RtkSolution>();
850 std::vector<States::StateKeyType> ambKeys;
853 if (
const auto* ambDD = std::get_if<States::AmbiguityDD>(&
_kalmanFilter.x.rowKeys().at(i)))
855 ambKeys.emplace_back(*ambDD);
862 std::string text = fmt::format(
"Outage of {:.1f} [s] (interval {:.1f} [s]). Reinitializing with SPP solution.", dt,
_dataInterval);
869 std::vector<States::StateKeyType> ambKeys;
872 if (
const auto* ambDD = std::get_if<States::AmbiguityDD>(&
_kalmanFilter.x.rowKeys().at(i)))
874 ambKeys.emplace_back(*ambDD);
879 std::string text = fmt::format(
"Outage of {:.1f} [s] (interval {:.1f} [s]). Resetting ambiguities.", dt,
_dataInterval);
892 LOG_DEBUG(
"{}: Initial base position: {}°, {}°, {}m (ECEF {} {} {} [m])",
nameId(),
895 LOG_DEBUG(
"{}: Initial rover position: {}°, {}°, {}m (ECEF {} {} {} [m])",
nameId(),
902 LOG_DATA(
"{}: Could not calculate initial position solution.",
nameId());
908 else if (startupPhase)
919 auto ionosphericCorrections = std::make_shared<const IonosphericCorrections>(gnssNavInfos);
924 auto logLevel = spdlog::get_level();
925 spdlog::set_level(spdlog::level::debug);
936 observations,
nullptr,
nameId() +
" no filter");
942 observations,
nullptr,
nameId() +
" no filter");
945 for (
const auto& [satSigId, sigObs] : observations.
signals)
947 auto satId = satSigId.toSatId();
948 if (std::ranges::find_if(rtkSol->satData, [&satId](
const auto& sat) {
949 return satId == sat.first;
951 != rtkSol->satData.end())
955 rtkSol->satData.emplace_back(satSigId.toSatId(),
956 RtkSolution::SatData{ .satElevation = sigObs.recvObs.at(Rover)->satElevation(_receiver[Rover].e_posMarker, _receiver[Rover].lla_posMarker),
957 .satAzimuth = sigObs.recvObs.at(Rover)->satElevation(_receiver[Rover].e_posMarker, _receiver[Rover].lla_posMarker) });
959 for (
const auto& [satSigId, sigObs] : observations.
signals)
961 for (
const auto& obs : sigObs.recvObs.at(
Rover)->obs)
963 rtkSol->observableReceived.emplace(satSigId, obs.first);
966 spdlog::set_level(logLevel);
976 observations, &filtered,
nameId());
982 observations, &filtered,
nameId());
984 rtkSol->filtered = filtered;
986 for (
const auto& [satSigId, sigObs] : observations.
signals)
988 for (
const auto& obs : sigObs.recvObs.at(
Rover)->obs)
990 rtkSol->observableFiltered.emplace(satSigId, obs.first);
1006 bool nisEnabled = !startupPhase &&
_kalmanFilter.isNISenabled();
1009 bool pivotChanged =
true;
1014 for (
size_t i = 0; i < maxIter; i++)
1025 pivotChanged =
false;
1028 if (nisEnabled && !startupPhase)
1032 if (!rtkSol->nisResultInitial) { rtkSol->nisResultInitial = nisResult; }
1033 rtkSol->nisResultFinal = nisResult;
1036 nisResult.triggered =
false;
1037 addEventToGui(rtkSol,
"Stopped doing NIS check, as minimum amount of satellites reached.");
1039 if (nisResult.triggered)
1047 for (
const auto& outlier : outliers)
1049 pivotChanged |= outlier.pivot.has_value();
1067 LOG_DATA(
"{}: Final NIS = {} < {} = r2. {} removed",
nameId(), rtkSol->nisResultFinal->NIS, rtkSol->nisResultFinal->r2, rtkSol->nisRemovedCnt);
1089 LOG_TRACE(
"{}: No observations found. Check that your base and rover file match and reconfigure the filter.",
nameId());
1100 rtkSol->nSatellites = observations.
satellites.size();
1110 if (
const auto* ambDD = std::get_if<States::AmbiguityDD>(&
_kalmanFilter.x.rowKeys().at(i)))
1116 .satSigId = ambDD->satSigId,
1120 rtkSol->ambiguityDD_br.push_back(ambDDSol);
1125 rtkSol->pivots.emplace(pivot.second.satSigId, pivot.first.second);
1127 for (
const auto& [satSigId, sigObs] : observations.
signals)
1129 for (
const auto& obs : sigObs.recvObs.at(
Rover)->obs)
1131 rtkSol->observableUsed.emplace(satSigId, obs.first);
1141 _receiver[Rover].gnssObs =
nullptr;
1142 _baseObsReceivedThisEpoch =
false;
1150 std::vector<InputPin::IncomingLink::ValueWrapper<GnssNavInfo>> gnssNavInfoWrappers;
1151 std::vector<const GnssNavInfo*> gnssNavInfos;
1156 gnssNavInfoWrappers.push_back(*gnssNavInfo);
1157 gnssNavInfos.push_back(gnssNavInfo->v);
1163 auto rtkSol = std::make_shared<RtkSolution>();
1164 rtkSol->insTime = sppSol->insTime;
1166 rtkSol->nSatellites = sppSol->nSatellites;
1174 if (sppSol->e_CovarianceMatrix().has_value())
1178 rtkSol->setPosVelAndCov_e(sppSol->e_position(), sppSol->e_velocity(),
1183 rtkSol->setPositionAndCov_e(sppSol->e_position(),
1189 rtkSol->setPosition_e(sppSol->e_position());
1190 rtkSol->setVelocity_e(sppSol->e_velocity());
1229 if (
const auto* ambDD = std::get_if<States::AmbiguityDD>(&
_kalmanFilter.x.rowKeys().at(i)))
1264 std::array<std::vector<CycleSlipDetector::SatelliteObservation>,
ReceiverType_COUNT> cycleSlipObservations;
1266 for (
const auto& satId : observations.
satellites)
1269 for (
const auto& [satSigId, signalObs] : observations.
signals)
1271 if (satSigId.toSatId() != satId) {
continue; }
1287 for (
const auto& receiverObs :
_receiver.at(recvType).gnssObs->data)
1289 if (receiverObs.satSigId.toSatId() == satId && receiverObs.carrierPhase)
1291 [[maybe_unused]]
auto lambda =
InsConst::C / receiverObs.satSigId.freq().getFrequency(signalObs.freqNum());
1292 LOG_DATA(
"[{}] [{}] Added to cycle-slip detection: {:.1f} [m]", receiverObs.satSigId, recvType, receiverObs.carrierPhase->value * lambda);
1297 auto sat = std::ranges::find_if(cycleSlipObservations.at(recvType),
1298 [&satId](
const auto& satObs) { return satObs.satId == satId; });
1300 if (sat == cycleSlipObservations.at(recvType).end())
1303 .signals = { signal },
1304 .freqNum = signalObs.freqNum() };
1305 cycleSlipObservations.at(recvType).push_back(satObs);
1309 sat->signals.push_back(signal);
1315 if (added) {
break; }
1319 std::vector<NAV::Code> pivotsToChange;
1320 std::vector<States::AmbiguityDD> removedStates;
1321 auto removeSatSig = [&](
const SatSigId& satSigId, [[maybe_unused]]
const std::string& reason) ->
bool {
1325 LOG_TRACE(
"{}: [{}] Cycle-slip detected in [{}], due to {}. Removing state: {}",
nameId(),
1334 LOG_TRACE(
"{}: [{}] Cycle-slip detected in pivot satellite [{}], due to {}",
nameId(),
1336 pivotsToChange.push_back(satSigId.
code);
1340 LOG_DATA(
"{}: [{}] Cycle-slip detected in [{}], due to {}. Not yet estimated (no action taken).",
nameId(),
1347 for (
size_t i = 0; i < cycleSlipObservations.size(); ++i)
1349 std::string receiverName = fmt::format(
"{}",
static_cast<ReceiverType>(i));
1353 for (
const auto& cycleSlip : cycleSlips)
1355 if (
const auto* s = std::get_if<CycleSlipDetector::CycleSlipLossOfLockIndicator>(&cycleSlip))
1357 if (s->signal.code &
_obsFilter.getCodeFilter()
1358 && removeSatSig(s->signal,
"LLI set in " + receiverName))
1360 rtkSol->cycleSlipDetectorResult.emplace_back(cycleSlip, receiverName);
1363 else if (
const auto* s = std::get_if<CycleSlipDetector::CycleSlipSingleFrequency>(&cycleSlip))
1365 if (s->signal.code &
_obsFilter.getCodeFilter()
1366 && removeSatSig(s->signal,
"single frequency check in " + receiverName))
1368 rtkSol->cycleSlipDetectorResult.emplace_back(cycleSlip, receiverName);
1371 else if (
const auto* s = std::get_if<CycleSlipDetector::CycleSlipDualFrequency>(&cycleSlip))
1374 for (
const auto& signal : s->signals)
1377 && removeSatSig(signal,
"dual frequency check in " + receiverName)
1380 rtkSol->cycleSlipDetectorResult.emplace_back(cycleSlip, receiverName);
1388 for (
const auto& code : pivotsToChange)
1394 for (
const auto& cycleSlip : rtkSol->cycleSlipDetectorResult)
1396 switch (cycleSlip.first.index())
1410 auto text = fmt::format(
"{}: {}", cycleSlip.second, cycleSlip.first);
1414 for (
const auto& state : removedStates)
1416 auto text = fmt::format(
"State [{}] removed, because cycle-slip detected.", state);
1424 LOG_DATA(
"{}: Checking for observations to remove when only one observable per code for double difference",
nameId());
1426 std::unordered_map<std::pair<Code, GnssObs::ObservationType>, uint16_t> signalCount;
1427 for (
const auto& [satSigId, sigObs] : observations.
signals)
1429 const auto& recvObs = sigObs.recvObs.at(
Base);
1430 for (
const auto& obs : recvObs->obs)
1432 signalCount[std::make_pair(satSigId.code, obs.first)]++;
1436 std::vector<SatSigId> sigToRemove;
1437 for (
auto& [satSigId, sigObs] : observations.
signals)
1439 for (
auto& recvObs : sigObs.recvObs)
1441 std::vector<GnssObs::ObservationType> obsToRemove;
1442 for (
const auto& obs : recvObs.second->obs)
1444 if (signalCount[std::make_pair(satSigId.code, obs.first)] < 2)
1446 obsToRemove.push_back(obs.first);
1452 recvObs.second->obs.erase(obs);
1455 if (recvObs.second->obs.empty())
1457 sigToRemove.push_back(satSigId);
1463 for (
const auto& satSigId : sigToRemove)
1465 LOG_DATA(
"{}: Removing [{}], because no more observations left for this signal",
nameId(), satSigId);
1469 for (
const auto& obsType :
_obsFilter.getUsedObservationTypes())
1471 auto key = std::make_pair(satSigId.code, obsType);
1474 LOG_DATA(
"{}: Pivot [{}][{}] does not have enough signals anymore",
nameId(), satSigId.code, obsType);
1485 Observations& observations,
const std::shared_ptr<RtkSolution>& rtkSol,
1489 std::optional<RtkSolution::PivotChange> pivotChange;
1490 if (reason != Reason::None)
1493 pivotChange->
reason = reason;
1494 pivotChange->obsType = obsType;
1499 if (observations.
signals.contains(pivot.satSigId))
1501 auto oldPivotObs = observations.
signals.at(pivot.satSigId).recvObs.begin();
1502 pivotChange->oldPivotSat = pivot.satSigId;
1509 auto sumRecvElevation = [&](
double el,
const auto& recvData) {
1513 for (
const auto& [satSigId, observation] : observations.
signals)
1515 if (satSigId.code != code) {
continue; }
1517 bool anySignalOnThisCodeWasEstimatedYet = std::ranges::any_of(observations.
signals,
1518 [&](
const auto& obs) { return obs.first.code == code
1519 && _kalmanFilter.x.hasRow(States::AmbiguityDD(obs.first)); });
1521 [&code](
const auto& amb) {
return amb.first.code == code; });
1523 LOG_DATA(
"{}: [{}] Checking [{}] for new pivot and obsType [{}]",
1527 for (
const auto& recvObs : observation.recvObs)
1529 if (recvObs.second->obs.contains(obsType)) { nFound++; }
1531 if (nFound != observation.recvObs.size()) {
continue; }
1533#if LOG_LEVEL <= LOG_LEVEL_DATA
1536 std::string ambHold;
1540 ambHold += fmt::format(
"[{} = {}]", amb.first, amb.second);
1542 LOG_DATA(
"{}: [{}] anySignalOnThisCodeWasEstimatedYet = {}, anySignalOnThisCodeWithFix = {}, ambHold = {}",
1543 nameId(),
_receiver[
Rover].gnssObs->insTime.toYMDHMS(
GPST), anySignalOnThisCodeWasEstimatedYet, anySignalOnThisCodeWithFix, ambHold);
1548 if (pivotChange->oldPivotSat == satSigId) {
continue; }
1552 && (anySignalOnThisCodeWasEstimatedYet ||
_pivotSatellites.contains({ code, GnssObs::Carrier }))
1555 && (anySignalOnThisCodeWithFix ||
_pivotSatellites.contains({ code, GnssObs::Carrier }))
1561 double satElevation = std::accumulate(observation.recvObs.begin(), observation.recvObs.end(), 0.0, sumRecvElevation)
1562 /
static_cast<double>(observation.recvObs.size());
1566 _pivotSatellites.insert(std::make_pair(std::make_pair(code, obsType), satSigId));
1569 LOG_DATA(
"{}: [{}] Setting [{}] as new pivot for obsType [{}] (elevation {:.4}°, no previous pivot on this code)",
nameId(),
1574 .oldPivotElevation = 0.0,
1575 .newPivotSat = satSigId,
1576 .newPivotElevation = satElevation };
1580 LOG_DATA(
"{}: [{}] Setting [{}] as new pivot for obsType [{}] (elevation {:.4}°)",
nameId(),
1582 pivotChange->newPivotSat = satSigId;
1583 pivotChange->newPivotElevation = satElevation;
1590 if (!observations.
signals.contains(pivotSat.satSigId))
1592 LOG_DATA(
"{}: [{}] Setting [{}] as new pivot for obsType [{}] (elevation {:.4}°, old pivot [{}] not observed this epoch)",
nameId(),
1597 .oldPivotElevation = 0.0,
1598 .newPivotSat = satSigId,
1599 .newPivotElevation = satElevation };
1604 const auto& pivotObs = observations.
signals.at(pivotSat.satSigId);
1605 double pivElevation = std::accumulate(pivotObs.recvObs.begin(), pivotObs.recvObs.end(), 0.0, sumRecvElevation)
1606 /
static_cast<double>(pivotObs.recvObs.size());
1608 if (satElevation > pivElevation)
1610 LOG_DATA(
"{}: [{}] Setting [{}] as new pivot for obsType [{}] (elevation {:.4}°, larger than previous pivot [{}] elevation {:.4}°)",
nameId(),
1617 .oldPivotElevation = pivElevation,
1618 .newPivotSat = satSigId,
1619 .newPivotElevation = satElevation };
1623 pivotChange->newPivotSat = satSigId;
1624 pivotChange->newPivotElevation = satElevation;
1635 ||
_pivotSatellites.at({ code, obsType }).satSigId == pivotChange->newPivotSat,
1636 "The new pivot satellite and current one must match.");
1638 || pivotChange->newPivotSat ==
SatSigId{},
1639 "Old and new pivot satellite have to be different.");
1643 rtkSol->changedPivotSatellites.emplace(std::make_pair(code, obsType), *pivotChange);
1647 if (pivotChange->newPivotSat ==
SatSigId{})
1655 if (
const auto* ambDD = std::get_if<States::AmbiguityDD>(&key))
1657 if (ambDD->satSigId.code != pivotChange->oldPivotSat.code) {
continue; }
1659 LOG_DATA(
"{}: [{}] Removing Amibguity as no pivot anymore {}",
nameId(), ambDD->satSigId, key);
1668 else if (pivotChange->reason != Reason::NewCode && obsType ==
GnssObs::Carrier)
1671 pivotChange->reason != Reason::PivotNotObservedInEpoch
1672 && pivotChange->reason != Reason::PivotCycleSlip
1673 && pivotChange->reason != Reason::PivotOutlier,
1685 LOG_DATA(
"{}: [{} - {}]: {}",
nameId(), key.first, key.second, pivot.satSigId);
1690 LOG_ERROR(
"{}: [{}] The Kalman Filter has the state [{}] but this is a pivot. Pivots should not be in the state.",
nameId(),
_receiver[
Rover].gnssObs->insTime.toYMDHMS(
GPST), state);
1699 std::set<Code> codes;
1700 for (
const auto& [satSigId, observation] : observations.
signals)
1702 codes.insert(satSigId.code);
1704 bool anyPivotChanged =
false;
1705 for (
const auto& code : codes)
1707 for (
const auto& obsType :
_obsFilter.getUsedObservationTypes())
1714 const auto& pivotSatSigId = pivotSat.satSigId;
1715 auto obs = std::ranges::find_if(observations.
signals, [&](
const auto& obs) {
1717 for (const auto& recvObs : obs.second.recvObs)
1719 if (recvObs.second->obs.contains(obsType)) { nFound++; }
1721 return obs.first == pivotSatSigId && nFound == obs.second.recvObs.size();
1723 if (obs == observations.
signals.end())
1725 LOG_DATA(
"{}: [{}] Removing pivot satellite [{}] on observable [{}] because not observed this epoch.",
nameId(),
1731 anyPivotChanged |=
updatePivotSatellite(code, obsType, observations, rtkSol, reason).has_value();
1735 LOG_DATA(
"{}: {}", nameId(), anyPivotChanged ?
"Pivot satellite changed" :
"No pivot satellite change");
1736 if (anyPivotChanged)
1738 removeSingleObservations(observations, &rtkSol->filtered, rtkSol);
1739 printPivotSatellites();
1744 bool oldPivotObservedInEpoch,
const std::shared_ptr<RtkSolution>& rtkSol)
1749 std::vector<States::StateKeyType> ambiguitiesToChange;
1752 const auto* ambDD = std::get_if<States::AmbiguityDD>(&
_kalmanFilter.x.rowKeys().at(i));
1753 if (ambDD && newPivotSatSigId.
code == ambDD->satSigId.code
1754 && (oldPivotObservedInEpoch || newPivotSatSigId != ambDD->satSigId))
1756 ambiguitiesToChange.emplace_back(*ambDD);
1760 newPivotSatSigId, fmt::join(ambiguitiesToChange,
", "));
1762 auto nStates =
static_cast<int>(
_kalmanFilter.x.rowKeys().size());
1766 D(ambiguitiesToChange, newPivotKey).setConstant(-1.0);
1767 if (!oldPivotObservedInEpoch) { D(newPivotKey, newPivotKey) = 0.0; }
1778#if LOG_LEVEL <= LOG_LEVEL_DATA
1780 std::string ambHold;
1782 for (
const auto& amb : ambiguitiesToChange)
1784 if (
const auto* ambDD = std::get_if<States::AmbiguityDD>(&amb))
1788 ambHold += fmt::format(
"[{} = {}]", ambDD->satSigId,
_ambiguitiesHold.at(ambDD->satSigId));
1796 for (
const auto& amb : ambiguitiesToChange)
1798 if (
const auto* ambDD = std::get_if<States::AmbiguityDD>(&amb))
1807 if (oldPivotObservedInEpoch)
1819 addEventToGui(rtkSol, fmt::format(
"State [{}] replaced with [{}] (because [{}] is new pivot satellite)", newPivotKey, oldPivotKey, newPivotSatSigId));
1821 LOG_TRACE(
"{}: [{}] State [{}] replaced with [{}] (because [{}] is new pivot satellite)",
nameId(),
1822 _receiver[
Rover].gnssObs->insTime.toYMDHMS(
GPST), newPivotKey, oldPivotKey, newPivotSatSigId);
1830 detector.resetSignal(newPivotSatSigId);
1831 detector.resetSignal(oldPivotSatSigId);
1835 addEventToGui(rtkSol, fmt::format(
"State [{}] removed (because new pivot satellite)", newPivotKey));
1842#if LOG_LEVEL <= LOG_LEVEL_DATA
1844 std::erase_if(ambiguitiesToChange, [&newPivotSatSigId](
const auto& amb) {
1845 if (
const auto* ambDD = std::get_if<States::AmbiguityDD>(&amb))
1847 return ambDD->satSigId == newPivotSatSigId;
1851 if (oldPivotObservedInEpoch)
1856 std::string ambHold;
1858 for (
const auto& amb : ambiguitiesToChange)
1860 if (
const auto* ambDD = std::get_if<States::AmbiguityDD>(&amb))
1864 ambHold += fmt::format(
"[{} = {}]", ambDD->satSigId,
_ambiguitiesHold.at(ambDD->satSigId));
1876 bool anyStateChanged =
false;
1878 auto addEvent = [&](
const std::string& text) {
1882 std::vector<States::StateKeyType> newAddedAmbiguities;
1884 for (
const auto& [satSigId, observation] : observations.
signals)
1892 &&
_pivotSatellites.at({ satSigId.code, GnssObs::Carrier }).satSigId != satSigId)
1894 anyStateChanged =
true;
1897 newAddedAmbiguities.emplace_back(key);
1898 if (std::ranges::find(rtkSol->newEstimatedAmbiguity, satSigId)
1899 == rtkSol->newEstimatedAmbiguity.end())
1901 rtkSol->newEstimatedAmbiguity.push_back(satSigId);
1912 LOG_DATA(
"{}: {} - pivot {}",
nameId(), satSigId, pivotSat.satSigId);
1913 if (!observations.
signals.contains(pivotSat.satSigId))
1915 LOG_CRITICAL(
"{}: [{}] The pivot satellite [{}] does not have a carrier measurement. It should have been switched before.",
1918 const auto& pivotObs = observations.
signals.at(pivotSat.satSigId);
1921 double lambda_j =
InsConst::C / satSigId.freq().getFrequency(observation.freqNum());
1929 double phi_br_s_meas = phi_r_s.measurement - phi_b_s.measurement;
1930 double phi_br_1_meas = phi_r_1.measurement - phi_b_1.measurement;
1931 double phi_br_1s_meas = phi_br_s_meas - phi_br_1_meas;
1933 double varCarrierPseudorange = std::numeric_limits<double>::infinity();
1934 bool forceCarrierPseudorange =
false;
1942 if (pivotSatPsr.satSigId == satSigId)
1944 const auto& pivotObsPsr = observations.
signals.at(pivotSatPsr.satSigId);
1950 varCarrierPseudorange = phi_r_s.measVar + p_r_s.measVar + phi_b_s.measVar + p_b_s.measVar
1951 + phi_r_1.measVar + p_r_1.measVar + phi_b_1.measVar + p_b_1.measVar;
1954 forceCarrierPseudorange = posUncertanty > 0.06;
1958 Eigen::Vector3d e_pLOS_1s = pivotObs.recvObs.at(
Rover)->e_pLOS(
_receiver[
Rover].e_posMarker)
1960 const double varCarrierGeometry = phi_r_s.measVar + phi_b_s.measVar + phi_r_1.measVar + phi_b_1.measVar
1962 LOG_DATA(
"{}: varCarrierGeometry = {:.2g} [m], varCarrierPseudorange = {:.2g} [m]",
nameId(), varCarrierGeometry, varCarrierPseudorange);
1964 if (varCarrierPseudorange < varCarrierGeometry || forceCarrierPseudorange)
1971 double p_br_s_meas = p_r_s.measurement - p_b_s.measurement;
1972 double p_br_1_meas = p_r_1.measurement - p_b_1.measurement;
1973 double p_br_1s_meas = p_br_s_meas - p_br_1_meas;
1975 _kalmanFilter.x(key) = (phi_br_1s_meas - p_br_1s_meas) / lambda_j;
1977 _kalmanFilter.P(key, key) = varCarrierPseudorange * 3.0 / std::pow(lambda_j, 2);
1978 auto msg = fmt::format(
"ddCP({:.1f}m) - ddPR({:.1f}m) = {:.1f} [cycles], std = {:.2f} [cycles]",
1980 LOG_DATA(
"{}: [{}] Init Amibguity: {}",
nameId(), satSigId, msg);
1982 addEvent(fmt::format(
"State [{}] added ({})", key, msg));
1986 double phi_br_s_est = phi_r_s.estimate - phi_b_s.estimate;
1987 double phi_br_1_est = phi_r_1.estimate - phi_b_1.estimate;
1988 double phi_br_1s_est = phi_br_s_est - phi_br_1_est;
1990 _kalmanFilter.x(key) = (phi_br_1s_meas - phi_br_1s_est) / lambda_j;
1992 _kalmanFilter.P(key, key) = varCarrierGeometry * 3.0 / std::pow(lambda_j, 2);
1993 auto msg = fmt::format(
"ddCP({:.1f}m) - ddEst({:.1f}m) = {:.1f} [cycles], std = {:.2f} [cycles]",
1995 LOG_DATA(
"{}: [{}] Init Amibguity: {}",
nameId(), satSigId, msg);
1997 addEvent(fmt::format(
"State [{}] added ({})", key, msg));
2005 if (
const auto* ambDD = std::get_if<States::AmbiguityDD>(&key))
2009 LOG_DATA(
"{}: [{}] Removing Ambiguity as not observed anymore {}",
nameId(), ambDD->satSigId, key);
2011 addEvent(fmt::format(
"State [{}] removed (not observed anymore)", key));
2017 if (anyStateChanged)
2029 singleDifferences.reserve(observations.signals.size());
2031 LOG_DATA(
"{}: Calculating single differences (rover - base):",
nameId());
2032 for (
const auto& [satSigId, observation] : observations.signals)
2034 const auto& baseObservations = observation.recvObs.at(
Base)->obs;
2035 const auto& roverObservations = observation.recvObs.at(
Rover)->obs;
2037 for (
const auto& [obsType, baseObs] : baseObservations)
2039 const auto& roverObs = roverObservations.at(obsType);
2041 singleDifferences[satSigId][obsType].estimate = roverObs.estimate - baseObs.estimate;
2042 singleDifferences[satSigId].at(obsType).measurement = roverObs.measurement - baseObs.measurement;
2044 LOG_DATA(
"{}: [{}][{:11}][Meas] r {:.3f} - b {:.3f} = {:.3f}",
nameId(), satSigId, obsType,
2045 roverObs.measurement, baseObs.measurement, singleDifferences[satSigId][obsType].measurement);
2046 LOG_DATA(
"{}: [{}][{:11}][Est ] r {:.3f} - b {:.3f} = {:.3f} (diff to meas {:.3e})",
nameId(), satSigId, obsType,
2047 roverObs.estimate, baseObs.estimate, singleDifferences[satSigId][obsType].estimate,
2048 singleDifferences[satSigId][obsType].measurement - singleDifferences[satSigId][obsType].estimate);
2052 return singleDifferences;
2058 if (singleDifferences.empty()) {
return doubleDifferences; }
2059 doubleDifferences.reserve(singleDifferences.size() - 1);
2062 for (
const auto& [satSigId_s, singleDiff_s] : singleDifferences)
2064 for (
const auto& [obsType, sDiff_s] : singleDiff_s)
2068 LOG_DATA(
"{}: [{}] Not calculating double difference for [{} {}] because no pivot satellite.",
nameId(),
_receiver[
Rover].gnssObs->insTime.toYMDHMS(
GPST), satSigId_s.code, obsType);
2071 const auto& satSigId_1 =
_pivotSatellites.at({ satSigId_s.code, obsType }).satSigId;
2072 if (satSigId_s == satSigId_1) {
continue; }
2074 if (!singleDifferences.contains(satSigId_1))
2079 if (!singleDifferences.at(satSigId_1).contains(obsType))
2081 LOG_WARN(
"{}: [{}] Pivot [{}] has no observation type [{}]. This is a bug.",
nameId(),
_receiver[
Rover].gnssObs->insTime.toYMDHMS(
GPST), satSigId_1, obsType);
2085 const auto& singleDiff_1 = singleDifferences.at(satSigId_1);
2087 const auto& sDiff_1 = singleDiff_1.at(obsType);
2089 doubleDifferences[satSigId_s][obsType].estimate = sDiff_s.estimate - sDiff_1.estimate;
2090 doubleDifferences[satSigId_s].at(obsType).measurement = sDiff_s.measurement - sDiff_1.measurement;
2091 LOG_DATA(
"{}: [{} - {}][{:11}][Meas] sat {:.3f} - piv {:.3f} = {:.3f}",
nameId(), satSigId_s, satSigId_1, obsType,
2092 sDiff_s.measurement, sDiff_1.measurement, doubleDifferences[satSigId_s][obsType].measurement);
2096 double lambda_j_1 =
InsConst::C / satSigId_1.freq().getFrequency(observations.
signals.at(satSigId_1).freqNum());
2098 doubleDifferences[satSigId_s].at(obsType).estimate += lambda_j_1 * N_br_1s;
2100 LOG_DATA(
"{}: [{} - {}][{:11}][Est ] sat {:.3f} - piv {:.3f} = {:.3f} (diff to meas {:.3e})",
nameId(), satSigId_s, satSigId_1, obsType,
2101 sDiff_s.estimate, sDiff_1.estimate, doubleDifferences[satSigId_s][obsType].estimate,
2102 doubleDifferences[satSigId_s][obsType].measurement - doubleDifferences[satSigId_s][obsType].estimate);
2106 return doubleDifferences;
2113 for (
const auto& [satSigId, observation] : observations.signals)
2115 for (
const auto& [obsType, baseObs] : observation.recvObs.at(
Base)->obs)
2117 measKeys[obsType].emplace_back(satSigId,
Rover, obsType);
2118 measKeys[obsType].emplace_back(satSigId,
Base, obsType);
2123 for (
const auto& [obsType, keys] : measKeys)
2128 for (
const auto& [satSigId, observation] : observations.signals)
2130 const auto& baseObservations = observation.recvObs.at(
Base)->obs;
2131 const auto& roverObservations = observation.recvObs.at(
Rover)->obs;
2133 for (
const auto& [obsType, baseObs] : baseObservations)
2135 const auto& roverObs = roverObservations.at(obsType);
2140 Rtilde.at(obsType)(roverKey, roverKey) = roverObs.measVar;
2141 Rtilde.at(obsType)(baseKey, baseKey) = baseObs.measVar;
2158 std::vector<Meas::MeasKeyTypes> measKeys;
2159 measKeys.reserve(doubleDifferences.size() *
_obsFilter.getUsedObservationTypes().size());
2164 for (
const auto& [satSigId, doubleDiff] : doubleDifferences)
2166 for (
const auto& [obsType, diff] : doubleDiff)
2168 if (obsType == oType)
2194 for (
const auto& [obsType, R] : Rtilde)
2196 std::vector<Meas::MeasKeyTypes> obsTypeMeasKeys;
2197 for (
const auto& key : measKeys)
2199 if (std::holds_alternative<Meas::PsrDD>(key) && obsType ==
GnssObs::Pseudorange) { obsTypeMeasKeys.push_back(key); }
2200 else if (std::holds_alternative<Meas::CarrierDD>(key) && obsType ==
GnssObs::Carrier) { obsTypeMeasKeys.push_back(key); }
2201 else if (std::holds_alternative<Meas::DopplerDD>(key) && obsType ==
GnssObs::Doppler) { obsTypeMeasKeys.push_back(key); }
2204 obsTypeMeasKeys, R.rowKeys());
2206 for (
const auto& rowKey : obsTypeMeasKeys)
2212 satSigId_s = std::get<Meas::PsrDD>(rowKey).satSigId;
2215 satSigId_s = std::get<Meas::CarrierDD>(rowKey).satSigId;
2218 satSigId_s = std::get<Meas::DopplerDD>(rowKey).satSigId;
2238 for (
const auto& [satSigId_s, doubleDiff] : doubleDifferences)
2240 const auto& obs_s = observations.
signals.at(satSigId_s);
2242 double lambda_j =
InsConst::C / satSigId_s.freq().getFrequency(obs_s.freqNum());
2246 for (
const auto& [obsType, obs] : doubleDiff)
2248 const auto& satSigId_1 =
_pivotSatellites.at({ satSigId_s.code, obsType }).satSigId;
2249 const auto& obs_1 = observations.
signals.at(satSigId_1);
2270 -e_vLOS_1.x() * e_pLOS_1.x() * e_pLOS_1.x() + e_vLOS_1.x() + e_vLOS_s.x() * e_pLOS_s.x() * e_pLOS_s.x() - e_vLOS_s.x() - e_vLOS_1.y() * e_pLOS_1.x() * e_pLOS_1.y() + e_vLOS_s.y() * e_pLOS_s.x() * e_pLOS_s.y() - e_vLOS_1.z() * e_pLOS_1.x() * e_pLOS_1.z() + e_vLOS_s.z() * e_pLOS_s.x() * e_pLOS_s.z(),
2271 -e_vLOS_1.x() * e_pLOS_1.x() * e_pLOS_1.y() + e_vLOS_s.x() * e_pLOS_s.x() * e_pLOS_s.y() - e_vLOS_1.y() * e_pLOS_1.y() * e_pLOS_1.y() + e_vLOS_1.y() + e_vLOS_s.y() * e_pLOS_s.y() * e_pLOS_s.y() - e_vLOS_s.y() - e_vLOS_1.z() * e_pLOS_1.y() * e_pLOS_1.z() + e_vLOS_s.z() * e_pLOS_s.y() * e_pLOS_s.z(),
2272 -e_vLOS_1.x() * e_pLOS_1.x() * e_pLOS_1.z() + e_vLOS_s.x() * e_pLOS_s.x() * e_pLOS_s.z() - e_vLOS_1.y() * e_pLOS_1.y() * e_pLOS_1.z() + e_vLOS_s.y() * e_pLOS_s.y() * e_pLOS_s.z() - e_vLOS_1.z() * e_pLOS_1.z() * e_pLOS_1.z() + e_vLOS_1.z() + e_vLOS_s.z() * e_pLOS_s.z() * e_pLOS_s.z() - e_vLOS_s.z());
2293 if (normInno(
all).hasNaN())
2295 LOG_ERROR(
"{}: NIS check has NaN values. Skipping check this epoch.",
nameId());
2301 LOG_DATA(
"{}: normInno = \n{}",
nameId(), normInno.transposed());
2310 auto code = pivotCodeObsType.first;
2311 auto obsType = pivotCodeObsType.second;
2315 if (
const auto* meas = std::get_if<Meas::PsrDD>(&key))
2318 satSigId = meas->satSigId;
2320 else if (
const auto* meas = std::get_if<Meas::CarrierDD>(&key))
2323 satSigId = meas->satSigId;
2325 else if (
const auto* meas = std::get_if<Meas::DopplerDD>(&key))
2328 satSigId = meas->satSigId;
2330 return satSigId.
code != code || obsType2 != obsType;
2334 for (
const auto& key :
_kalmanFilter.savedPreUpdate().z.rowKeys())
2336 if (filterMeasurements(key)) {
continue; }
2340 if (amount <= 1) {
continue; }
2341 mean /=
static_cast<double>(amount);
2343 double variance = 0.0;
2344 for (
const auto& key :
_kalmanFilter.savedPreUpdate().z.rowKeys())
2346 if (filterMeasurements(key)) {
continue; }
2347 variance += std::pow(std::abs(
_kalmanFilter.savedPreUpdate().z(key) - mean), 2);
2349 variance *= 1.0 / (
static_cast<double>(amount) - 1.0);
2351 LOG_DATA(
"{}: pivot [{}][{}]: {:.3f} / {:.3f} = {:.3f}",
nameId(), code, obsType, mean, std::sqrt(variance), mean / std::sqrt(variance));
2353 if (std::abs(mean / std::sqrt(variance)) > 5.0)
2370 faultyMeas.
satSigId = pivot.satSigId;
2379 normInno(
all).maxCoeff(&maxIdx);
2380 faultyMeas.
key = normInno.
rowKeys().at(
static_cast<size_t>(maxIdx));
2381 LOG_DATA(
"{}: Largest post-fit innovation: {} ({:.1f})",
nameId(), faultyMeas.
key, normInno(faultyMeas.
key));
2383 if (
const auto* meas = std::get_if<Meas::PsrDD>(&faultyMeas.
key))
2386 faultyMeas.
satSigId = meas->satSigId;
2388 else if (
const auto* meas = std::get_if<Meas::CarrierDD>(&faultyMeas.
key))
2391 faultyMeas.
satSigId = meas->satSigId;
2393 else if (
const auto* meas = std::get_if<Meas::DopplerDD>(&faultyMeas.
key))
2396 faultyMeas.
satSigId = meas->satSigId;
2401 if (pivot.satSigId == faultyMeas.
satSigId)
2412 addEventToGui(rtkSol,
"Stopped doing NIS check, as minimum amount of pseudorange observables reached.");
2416 std::vector<RealTimeKinematic::OutlierInfo> faulty{ faultyMeas };
2421 faulty.push_back(faultyMeas);
2428 faulty.push_back(faultyMeas);
2430 faulty.back().key =
Meas::PsrDD{ faulty.back().satSigId };
2432 if (faulty.size() == 2)
2434 if (
auto pivotKey = std::make_pair(faulty.back().satSigId.code, faulty.back().obsType);
2441 faulty.back().pivot.reset();
2446 for (
const auto& faultyMeas : faulty)
2450 if (faultyMeas.
pivot)
2465 addEventToGui(rtkSol, fmt::format(
"State [{}] removed (because outlier detected in carrier measurement [{}])", ambStateKey, faultyMeas.
key));
2476 addEventToGui(rtkSol, fmt::format(
"Erasing receiver observation [{}][{}], because outlier (NIS check)", faultyMeas.
satSigId, faultyMeas.
obsType));
2479 rtkSol->nisRemovedCnt++;
2490 solutionValid.
valid =
false;
2495 solutionValid.
threshold = std::max({ 50.0,
2512 if (solutionValid.
valid)
2523 ? std::string(
"Update rejected - no observations left")
2524 : fmt::format(
"Update rejected - position change due to update is {:.2f} [m] > {:.2f} [m]", solutionValid.
dx, solutionValid.
threshold);
2531 return solutionValid;
2536 std::vector<States::StateKeyType> ambKeys;
2537 std::vector<Meas::MeasKeyTypes> ambMeasKeys;
2540 if (
const auto* ambDD = std::get_if<States::AmbiguityDD>(&
_kalmanFilter.x.rowKeys().at(i)))
2542 ambKeys.emplace_back(*ambDD);
2543 ambMeasKeys.emplace_back(*ambDD);
2546 size_t nAmbs = ambKeys.size();
2550 LOG_TRACE(
"{}: [{}] Not fixing ambiguities because only {} satellites but minimum {} needed.",
2554 addEventToGui(rtkSol, fmt::format(
"Not fixing ambiguities anymore because\nonly {} satellites but minimum {} needed.", nCarrierMeasUniqueSatellite,
_nMinSatForAmbFix));
2563 addEventToGui(rtkSol, fmt::format(
"Resuming ambiguity fixing as\nminimum amount of satellites reached."));
2569 LOG_TRACE(
"{}: [{}] Not fixing ambiguities because position variance is {:.4f}m which is higher than {:.4f}m",
2573 addEventToGui(rtkSol, fmt::format(
"Not fixing ambiguities anymore because\nposition variance is {:.4f}m^2 which is higher than {:.4f}m^2.", posVar,
_maxPosVar));
2581 addEventToGui(rtkSol, fmt::format(
"Resuming ambiguity fixing as\nposition variance smaller than limit."));
2584 if (ambKeys.empty()) {
return false; }
2588#if LOG_LEVEL <= LOG_LEVEL_DATA
2590 std::set<SatSigId> ambStates;
2591 for (
const auto& key : ambKeys)
2593 auto amb = std::get<States::AmbiguityDD>(key);
2594 if (ambStates.contains(amb.satSigId))
2598 ambStates.insert(amb.satSigId);
2600 LOG_DATA(
"{}: ambKeys: [{}]",
nameId(), fmt::join(ambStates,
", "));
2602 std::set<SatSigId> ambHold;
2605 if (ambHold.contains(hold.first))
2609 ambHold.insert(hold.first);
2611 LOG_DATA(
"{}: ambHold: [{}]",
nameId(), fmt::join(ambHold,
", "));
2613 if (ambStates != ambHold)
2615 std::vector<SatSigId> diff;
2616 std::set_difference(ambStates.begin(), ambStates.end(), ambHold.begin(), ambHold.end(), std::back_inserter(diff));
2617 LOG_DATA(
"{}: Amb not in hold: [{}]",
nameId(), fmt::join(diff,
", "));
2619 std::set_difference(ambHold.begin(), ambHold.end(), ambStates.begin(), ambStates.end(), std::back_inserter(diff));
2620 LOG_DATA(
"{}: Amb not in states: [{}]",
nameId(), fmt::join(diff,
", "));
2624 Eigen::VectorXd fixedAmb = Eigen::VectorXd::Zero(
static_cast<int>(ambKeys.size()));
2626 for (
size_t k = 0; k < ambKeys.size(); k++)
2628 const auto& key = ambKeys.at(k);
2629 auto satSigId = std::get<States::AmbiguityDD>(key).satSigId;
2634 rtkSol->nAmbiguitiesFixed = ambKeys.size() + 1;
2640 addEventToGui(rtkSol, fmt::format(
"Resuming ambiguity holding as\nminimum amount of satellites reached."));
2647 LOG_TRACE(
"{}: [{}] Not holding ambiguities because only {} satellites but minimum {} needed.",
2651 addEventToGui(rtkSol, fmt::format(
"Not holding ambiguities anymore because\nonly {} satellites but minimum {} needed.", nCarrierMeasUniqueSatellite,
_nMinSatForAmbHold));
2658 size_t partialFixTries = 0;
2666 rtkSol->ambiguityResolutionFailure = fixed.failure;
2667 rtkSol->ambiguityCriticalValueRatio = fixed.ambiguityCriticalValueRatio;
2688 rtkSol->ambiguityCriticalValueRatio = fixed.ambiguityCriticalValueRatio;
2689 rtkSol->nAmbiguitiesFixed = fixed.nFixed + 1;
2691 for (
const auto& key_ : ambKeys)
2693 const auto key = std::get<States::AmbiguityDD>(key_);
2698 && fixed.nFixed == nAmbs && partialFixTries == 0)
2700 LOG_WARN(
"{}: Ambiguity [{}] changed from {} to {} (despite being FixAndHold)",
nameId(), key.satSigId,
2706 return partialFixTries == 0;
2708 LOG_DATA(
"{}: Fixing failed. partialFixTries = {}",
nameId(), partialFixTries);
2713 _kalmanFilter.P(ambKeys, ambKeys).diagonal().maxCoeff(&maxAmb);
2715 LOG_DATA(
"{}: [{}] Trying partial fixing by not fixing [{}] with highest variance of {}",
2717 ambKeys.at(
static_cast<size_t>(maxAmb)),
2718 _kalmanFilter.P(ambKeys.at(
static_cast<size_t>(maxAmb)), ambKeys.at(
static_cast<size_t>(maxAmb))));
2719 floatKeys.push_back(ambKeys.at(
static_cast<size_t>(maxAmb)));
2720 ambKeys.erase(std::next(ambKeys.begin(), maxAmb));
2721 ambMeasKeys.erase(std::next(ambMeasKeys.begin(), maxAmb));
2737#if LOG_LEVEL <= LOG_LEVEL_DATA
2738 if (((
_kalmanFilter.x(ambKeys).array() * 1e2).round() * 1e-2).matrix() != fixedAmb)
2741 (Eigen::MatrixXd(
static_cast<int>(ambKeys.size()), 2) <<
_kalmanFilter.x(ambKeys), fixedAmb).finished(),
2742 ambKeys, std::vector<States::StateKeyType>{ States::AmbiguityDD(SatSigId(Code::G1C, 200)), States::AmbiguityDD(SatSigId(Code::G1C, 201)) });
2743 LOG_DATA(
"{}: [{}] Ambiguity estimate changed (relative to pivot) (200 = prev, 201 = new)\n{}",
nameId(),
_receiver[
Rover].gnssObs->insTime.toYMDHMS(
GPST), ambPrint.transposed());
Ambiguity resolution algorithms.
#define INS_ASSERT_USER_ERROR(_EXP, _MSG)
Assert function with message.
Combination of different cycle-slip detection algorithms.
Ambiguity decorrelation algorithms.
Combo representing an enumeration.
nlohmann::json json
json namespace
Frequency definition for different satellite systems.
Keys for the RTK algorithm for use inside the KeyedMatrices.
GNSS Observation messages.
Text Help Marker (?) with Tooltip.
The class is responsible for all time-related tasks.
void from_json(const json &j, ImColor &color)
Converts the provided json object into a color.
void to_json(json &j, const ImColor &color)
Converts the provided color into a json object.
Defines how to save certain datatypes to json.
Least squares with keyed states.
Utility class for logging to console and file.
#define LOG_CRITICAL(...)
Critical Event, which causes the program to work entirely and throws an exception.
#define LOG_DEBUG
Debug information. Should not be called on functions which receive observations (spamming)
#define LOG_DATA
All output which occurs repeatedly every time observations are received.
#define LOG_ERROR
Error occurred, which stops part of the program to work, but not everything.
#define LOG_WARN
Error occurred, but a fallback option exists and program continues to work normally.
#define LOG_TRACE
Detailled info to trace the execution of the program. Should not be called on functions which receive...
Observation data used for calculations.
Real-Time Kinematic (RTK) carrier-phase DGNSS.
RTK Node/Algorithm output.
Structs identifying a unique satellite.
Ambiguity Search algorithms.
Values with an uncertainty (Standard Deviation)
std::unordered_map< Key, T > unordered_map
Unordered map type.
Vector Utility functions.
Enumerate for GNSS Codes.
bool isEnabled(const Detector &detector) const
Is the cycle-slip detector enabled?
void resetSignal(const SatSigId &satSigId)
Resets all data related to the provided signal.
@ LLI
Loss-of-Lock Indicator check.
@ SingleFrequency
Single frequency detector.
@ DualFrequency
Dual frequency detector.
static std::string type()
Returns the type of the data class.
ObservationType
Observation types.
@ Doppler
Doppler (Pseudorange rate)
@ ObservationType_COUNT
Count.
@ Pseudorange
Pseudorange.
static std::string type()
Returns the type of the data class.
static constexpr double C
Speed of light [m/s].
ImVec2 _guiConfigDefaultWindowSize
Node(std::string name)
Constructor.
std::optional< InputPin::IncomingLink::ValueWrapper< T > > getInputValue(size_t portIndex) const
Get Input Value connected on the pin. Only const data types.
std::vector< InputPin > inputPins
List of input pins.
OutputPin * CreateOutputPin(const char *name, Pin::Type pinType, const std::vector< std::string > &dataIdentifier, OutputPin::PinData data=static_cast< void * >(nullptr), int idx=-1)
Create an Output Pin object.
std::string nameId() const
Node name and id.
std::string name
Name of the Node.
InputPin * CreateInputPin(const char *name, Pin::Type pinType, const std::vector< std::string > &dataIdentifier={}, InputPin::Callback callback=static_cast< InputPin::FlowFirableCallbackFunc >(nullptr), InputPin::FlowFirableCheckFunc firable=nullptr, int priority=0, int idx=-1)
Create an Input Pin object.
void invokeCallbacks(size_t portIndex, const std::shared_ptr< const NodeData > &data)
Calls all registered callbacks on the specified output port.
bool DeleteInputPin(size_t pinIndex)
Deletes the input pin. Invalidates the pin reference given.
bool _hasConfig
Flag if the config window should be shown.
@ DoubleDifference
Double Difference.
void disableFilter()
Opens all settings to the maximum, disabling the filter.
void selectObservationsForCalculation(ReceiverType receiverType, const Eigen::MatrixBase< DerivedPe > &e_posMarker, const Eigen::MatrixBase< DerivedPn > &lla_posMarker, const std::shared_ptr< const GnssObs > &gnssObs, const std::vector< const GnssNavInfo * > &gnssNavInfos, Observations &observations, Filtered *filtered, const std::string &nameId, bool ignoreElevationMask=false)
Returns a list of satellites and observations filtered by GUI settings & NAV data available & ....
static std::string type()
Returns the type of the data class.
ObservationFilter _obsFilter
Observation Filter.
double _dataInterval
Data interval [s].
StdevAccelUnits _gui_stdevAccelUnits
Gui selection for the Unit of the input stdev_accel parameter for the StDev due to acceleration due t...
void updateKalmanFilterAmbiguitiesForPivotChange(const SatSigId &newPivotSatSigId, const SatSigId &oldPivotSatSigId, bool oldPivotObservedInEpoch, const std::shared_ptr< RtkSolution > &rtkSol)
Adapts the Kalman Filter if a pivot satellite signal was changed.
StdevAmbiguityUnits _gui_stdevAmbiguityFloatUnits
Gui selection for the Unit of the input for the StDev of the ambiguities (while float solution)
void recvRoverGnssObs(InputPin::NodeDataQueue &queue, size_t pinIdx)
Receive Function for the RoverGNSS Observations.
void printPivotSatellites()
Prints all pivot satellites.
std::vector< std::pair< InsTime, std::vector< std::string > > > _events
List of event texts (pivot changes, cycle-slips)
size_t nFixSolutions
Percentage of fix solutions.
bool _outputStateEvents
Whether to output state change events.
size_t _nCycleSlipsDual
Cycle-slip detection count (Dual)
void guiConfig() override
ImGui config window which is shown on double click.
static std::string category()
String representation of the Class Category.
std::shared_ptr< RtkSolution > calcFallbackSppSolution()
Calculates a SPP solution as fallback in case no base data is available.
bool initialize() override
Initialize the node.
size_t _outlierMinSat
Minumum amount of satellites for doing a NIS check.
KeyedKalmanFilterD< RTK::States::StateKeyType, RTK::Meas::MeasKeyTypes > _kalmanFilter
Kalman Filter representation.
bool _maxPosVarTriggered
Trigger state of the check.
std::unordered_map< SatSigId, double > _ambiguitiesHold
Ambiguities which are fixed and hold. Values in [cycles].
void calcKalmanUpdateMatrices(const Observations &observations, const Differences &doubleDifferences, const unordered_map< GnssObs::ObservationType, KeyedMatrixXd< RTK::Meas::SingleObs< ReceiverType > > > &Rtilde)
Calculates the Measurement vector 𝐳, Measurement sensitivity matrix 𝐇 and Measurement noise covarianc...
StdevAmbiguityUnits _gui_ambFixUpdateStdDevUnits
Gui selection for the Unit of the input for the StDev of the ambiguities (while float solution)
std::vector< OutlierInfo > removeOutlier(Observations &observations, const std::shared_ptr< RtkSolution > &rtkSol)
Performs the NIS check.
double _ambiguityFloatProcessNoiseVariance
Process noise (variance) for the ambiguities (while float solution) in [cycles^2] (Value used for cal...
std::array< double, 2 > _gui_stdevAccel
GUI selection for the Standard deviation of the acceleration 𝜎_a due to user motion in horizontal and...
double _ambFixUpdateVariance
Measurement noise variance used when fixing ambiguities in [cycles^2] (Value used for calculation)
double _maxPosVar
Do not attempt to fix if the position variance is above the threshold.
size_t _nMinSatForAmbFix
Minimum amount of satellites with carrier observations to try fixing the solution.
double _maxTimeBetweenBaseRoverForRTK
Maximum time between base and rover observations to calculate the RTK solution.
size_t _nMinSatForAmbHold
Minimum amount of satellites with carrier observations for holding the ambiguities.
AmbiguityResolutionParameters _ambiguityResolutionParameters
Ambiguity resolution algorithms and parameters to use.
static void pinAddCallback(Node *node)
Function to call to add a new pin.
size_t _nPivotChange
Amount of pivot changes performed.
std::array< CycleSlipDetector, ReceiverType_COUNT > _cycleSlipDetector
Cycle-slip detector.
gui::widgets::DynamicInputPins _dynamicInputPins
Dynamic input pins.
double _outlierMaxPosVarStartup
Do not attempt to remove outlier if the position variance is above the threshold in the startup phase...
RtkSolution::SolutionType _lastSolutionStatus
Solution type of last epoch.
std::string _eventFilterRegex
Regex for filtering events.
void addEventToGui(const std::shared_ptr< RtkSolution > &rtkSol, const std::string &text)
Adds the event to the GUI list.
size_t _nCycleSlipsSingle
Cycle-slip detection count (Single)
RealTimeKinematic()
Default constructor.
void calcRealTimeKinematicSolution()
Calculates the RTK solution.
size_t _nCycleSlipsLLI
Cycle-slip detection count (LLI)
void assignSolutionToFilter(const std::shared_ptr< NAV::SppSolution > &sppSol)
Assign the SPP solution to the RTK filter.
unordered_map< SatSigId, unordered_map< GnssObs::ObservationType, Difference > > Differences
Difference storage type.
size_t nFloatSolutions
Percentage of float solutions.
ReceiverType
Receiver Types.
@ ReceiverType_COUNT
Amount of receiver types.
void recvBaseGnssObs(InputPin::NodeDataQueue &queue, size_t pinIdx)
Receive Function for the Base GNSS Observations.
unordered_map< GnssObs::ObservationType, KeyedMatrixXd< RTK::Meas::SingleObs< ReceiverType > > > calcSingleObsMeasurementNoiseMatrices(const Observations &observations) const
Calculate the measurement noise matrices for each observation type.
double _gui_ambiguityFloatProcessNoiseStDev
Process noise (standard deviation) for the ambiguities (while float solution) (Selection in GUI)
StdevAmbiguityUnits _gui_stdevAmbiguityFixUnits
Gui selection for the Unit of the input for the StDev of the ambiguities (while fix solution)
void addOrRemoveKalmanFilterAmbiguities(const Observations &observations, const std::shared_ptr< RtkSolution > &rtkSol)
Adds or remove Ambiguities to/from the Kalman Filter state depending on the received observations.
size_t _nMeasExcludedNIS
Measurement excluded due to NIS test count.
static void pinDeleteCallback(Node *node, size_t pinIdx)
Function to call to delete a pin.
void printObservations(const Observations &observations)
Prints the observations for logging.
std::string type() const override
String representation of the Class Type.
NAV::Receiver< ReceiverType > Receiver
Receiver.
AmbiguityResolutionStrategy _ambiguityResolutionStrategy
Ambiguity resolution strategy.
SPP::Algorithm _sppAlgorithm
SPP algorithm.
bool _calcSPPIfNoBase
Wether to output a SPP solution if no base observations are available.
double _ambiguityFixProcessNoiseVariance
Process noise (standard deviation) for the ambiguities (while fix solution) in [cycles^2] (Value used...
size_t _outlierMinPsrObsKeep
Minumum amount of pseudorange observables to leave when doing a NIS check.
bool _outlierMaxPosVarStartupTriggered
Trigger state of the check.
Differences calcDoubleDifferences(const Observations &observations, const Differences &singleDifferences) const
Calculates the double difference of the measurements and estimates.
void kalmanFilterPrediction()
Does the Kalman Filter prediction.
bool _baseObsReceivedThisEpoch
Flag, whether the observation was received this epoch.
size_t _outlierRemoveEpochs
Amount of epochs to remove an outlier after being found (1 = only current)
size_t nSingleSolutions
Percentage of float solutions.
static constexpr size_t OUTPUT_PORT_INDEX_RTKSOL
Flow (RtkSol)
InsTime _lastUpdate
Last update time.
double _gui_ambiguityFixProcessNoiseStDev
Process noise (standard deviation) for the ambiguities (while fix solution) (Selection in GUI)
ObservationEstimator _obsEstimator
Observation Estimator.
unordered_map< std::pair< Code, GnssObs::ObservationType >, PivotSatellite > _pivotSatellites
Pivot satellites for each constellation.
std::optional< RtkSolution::PivotChange > updatePivotSatellite(Code code, GnssObs::ObservationType obsType, Observations &observations, const std::shared_ptr< RtkSolution > &rtkSol, const RtkSolution::PivotChange::Reason &reason)
Update the specified pivot satellite and also does a pivot change in the Kalman filter state if neces...
bool _nMinSatForAmbHoldTriggered
Trigger state of the check.
double _gui_ambFixUpdateStdDev
Measurement noise standard deviation used when fixing ambiguities in [cycles] (GUI value)
~RealTimeKinematic() override
Destructor.
size_t _maxRemoveOutlier
Maximum amount of outliers to remove per epoch.
bool _applyFixedAmbiguitiesWithUpdate
void recvBasePos(InputPin::NodeDataQueue &queue, size_t pinIdx)
Receive Function for the Base Position.
void deinitialize() override
Deinitialize the node.
UpdateStatus kalmanFilterUpdate(const std::shared_ptr< RtkSolution > &rtkSol)
Does the Kalman Filter update.
static constexpr size_t INPUT_PORT_INDEX_GNSS_NAV_INFO
GnssNavInfo.
void restore(const json &j) override
Restores the node from a json object.
void removeSingleObservations(Observations &observations, ObservationFilter::Filtered *filtered, const std::shared_ptr< RtkSolution > &rtkSol)
Removes observations which do not have a second one to do double differences.
void applyFixedAmbiguities(const Eigen::VectorXd &fixedAmb, const std::vector< RTK::States::StateKeyType > &ambKeys, const std::vector< RTK::Meas::MeasKeyTypes > &ambMeasKeys)
Apply the fixed ambiguities to the kalman filter state.
void checkForCycleSlip(Observations &observations, const std::shared_ptr< RtkSolution > &rtkSol)
Checks for cycle-slips.
Differences calcSingleDifferences(const Observations &observations) const
Calculates the single difference of the measurements and estimates.
bool resolveAmbiguities(size_t nCarrierMeasUniqueSatellite, const std::shared_ptr< RtkSolution > &rtkSol)
Resolves the ambiguities in the Kalman filter and updates the state and covariance matrix.
std::array< Receiver, ReceiverType::ReceiverType_COUNT > _receiver
Receivers.
static std::string typeStatic()
String representation of the Class Type.
void updatePivotSatellites(Observations &observations, const std::shared_ptr< RtkSolution > &rtkSol)
Update the pivot satellites for each constellation.
bool _nMinSatForAmbFixTriggered
Trigger state of the check.
json save() const override
Saves the node into a json object.
@ Predicted
Only predicted by Kalman Filter.
@ RTK_Fixed
RTK solution with fixed ambiguities to integers.
@ RTK_Float
RTK solution with floating point ambiguities.
@ SPP
Solution calculated via SPP algorithm because of missing data for RTK.
static std::string type()
Returns the type of the data class.
auto extract_front()
Returns a copy of the first element in the container and removes it from the container.
static float windowFontRatio()
Ratio to multiply for GUI window elements.
const std::vector< RowKeyType > & rowKeys() const
Returns the row keys.
bool InputIntL(const char *label, int *v, int v_min, int v_max, int step, int step_fast, ImGuiInputTextFlags flags)
Shows a value limited InputText GUI element for 'int'.
bool InputDoubleL(const char *label, double *v, double v_min, double v_max, double step, double step_fast, const char *format, ImGuiInputTextFlags flags)
Shows a value limited InputText GUI element for 'double'.
constexpr std::array< StateKeyType, 3 > Pos
All position keys.
constexpr std::array< StateKeyType, 6 > PosVel
Vector with all position and velocity keys.
constexpr std::array< StateKeyType, 3 > Vel
All velocity keys.
std::variant< PsrDD, CarrierDD, DopplerDD, States::AmbiguityDD > MeasKeyTypes
Alias for the measurement key type.
static const std::vector< StateKeyType > Vel
All velocity keys.
std::variant< KFStates, AmbiguityDD > StateKeyType
Alias for the state key type.
static const std::vector< StateKeyType > Pos
All position keys.
static const std::vector< StateKeyType > PosVel
Vector with all position and velocity state keys.
void ApplyChanges()
Signals that there have been changes to the flow.
Eigen::Vector3< typename Derived::Scalar > ecef2lla_WGS84(const Eigen::MatrixBase< Derived > &e_position)
Converts Earth-centered-Earth-fixed coordinates into latitude, longitude and altitude using WGS84.
Eigen::Quaternion< Scalar > e_Quat_n(const Scalar &latitude, const Scalar &longitude)
Quaternion for rotations from navigation to Earth-fixed frame.
std::string joinToString(const T &container, const char *delimiter=", ", const std::string &elementFormat="")
Joins the container to a string.
std::pair< typename DerivedF::PlainObject, typename DerivedF::PlainObject > calcPhiAndQWithVanLoanMethod(const Eigen::MatrixBase< DerivedF > &F, const Eigen::MatrixBase< DerivedG > &G, const Eigen::MatrixBase< DerivedW > &W, double dt)
Numerical Method to calculate the State transition matrix 𝚽 and System/Process noise covariance matri...
@ FixAndHold
Do not change the ambiguity once it is fixed.
const char * to_string(gui::widgets::PositionWithFrame::ReferenceFrame refFrame)
Converts the enum to a string.
KeyedVectorX< double, RowKeyType > KeyedVectorXd
Dynamic size KeyedVector with double types.
KeyedKalmanFilter< double, StateKeyType, MeasKeyType > KeyedKalmanFilterD
Keyed Kalman Filter class with double as type.
AmbiguityResolutionResult< typename DerivedA::Scalar, DerivedA::RowsAtCompileTime, DerivedB::RowsAtCompileTime > ResolveAmbiguities(const Eigen::MatrixBase< DerivedA > &a, const Eigen::MatrixBase< DerivedQa > &Qa, const Eigen::MatrixBase< DerivedB > &b, const Eigen::MatrixBase< DerivedQb > &Qb, const Eigen::MatrixBase< DerivedQab > &Qab, const Eigen::MatrixBase< DerivedQba > &Qba, const AmbiguityResolutionParameters ¶ms, const std::string &nameId)
Tries resolving the ambiguities.
static const internal::all_t all
Used to request all rows or columns in KeyedMatrices.
KeyedMatrixX< double, RowKeyType, ColKeyType > KeyedMatrixXd
Dynamic size KeyedMatrix with double types.
bool CycleSlipDetectorGui(const char *label, CycleSlipDetector &cycleSlipDetector, float width, bool dualFrequencyAvailable)
Shows a GUI for advanced configuration of the CycleSlipDetector.
constexpr auto rad2deg(const T &rad)
Convert Radians to Degree.
bool GuiAmbiguityResolution(const char *id, AmbiguityResolutionParameters ¶ms, float width)
Shows a ComboBox to select the ambiguity resolution algorithms.
@ None
Disable the search.
Satellite observations ordered per satellite.
std::vector< SatSigId > singleObservation
Only signal for this code/type (relevant for double differences)
Observation storage type.
size_t countObservations(const SatSigId &satSigId, const GnssObs::ObservationType &obsType) const
Counts how many observations for the specified signal ant type exist.
bool removeSignal(const SatSigId &satSigId, const std::string &nameId)
Remove the signal from the observations.
bool removeMeasurementsFor(const Code &code, const GnssObs::ObservationType &obsType, const std::string &nameId)
Remove all measurements for the provided code and observation type.
std::array< size_t, GnssObs::ObservationType_COUNT > nObservables
Number of observables.
void recalcObservableCounts(const std::string &nameId)
Calculates/Recalculates the number of observables.
std::unordered_set< SatId > satellites
Satellites used.
std::array< size_t, GnssObs::ObservationType_COUNT > nObservablesUniqueSatellite
Number of observables (counted once for each satellite)
unordered_map< SatSigId, SignalObservation > signals
Observations and calculated data for each signal.
Double differenced carrier-phase measurement phi_br^1s [m] (one for each satellite signal,...
Double differenced range-rate (doppler) measurement d_br^1s [m/s] (one for each satellite signal,...
Double differenced pseudorange measurement psr_br^1s [m] (one for each satellite signal,...
Double differenced N_br^1s = N_br^s - N_br^1 ambiguity [cycles] (one for each satellite signal,...
std::optional< RtkSolution::PivotChange::Reason > pivot
Pivot change reason, if it is a pivot.
SatSigId satSigId
Satellit signal id.
RTK::Meas::MeasKeyTypes key
Measurement key of the outlier.
GnssObs::ObservationType obsType
Observation type.
Pivot satellite information.
Update Valid information.
bool valid
Flag if the solution is valid.
double dx
Position change due to the update.
double threshold
Allowed position change.
Ambiguity double differences.
@ NIS
Normalized Innovation Squared (NIS)
Pivot Change information.
Reason
Possible reasons for a pivot change.
@ PivotNotObservedInEpoch
Old pivot satellite was not observed this epoch.
@ PivotCycleSlip
The pivot satellite had a cycle-slip.
@ PivotOutlier
Old pivot satellite was flagged as outlier.
@ None
No reason selected yet.
Identifies a satellite signal (satellite frequency and number)
Value with standard deviation.