20#include <unordered_map>
21#include <unordered_set>
28#include <spdlog/common.h>
29#include <spdlog/spdlog.h>
58#include <fmt/format.h>
99 return "RealTimeKinematic - RTK";
109 return "Data Processor";
114 auto nSatColumnContent = [&](
size_t pinIndex) ->
bool {
117 size_t usedSatNum = 0;
118 std::string usedSats;
121 std::string filler =
", ";
122 for (
const auto& satellite : gnssNavInfo->v->satellites())
124 if (
_obsFilter.isSatelliteAllowed(satellite.first))
127 usedSats += (allSats.empty() ?
"" : filler) + fmt::format(
"{}", satellite.first);
129 allSats += (allSats.empty() ?
"" : filler) + fmt::format(
"{}", satellite.first);
131 ImGui::TextUnformatted(fmt::format(
"{} / {}", usedSatNum, gnssNavInfo->v->nSatellites()).c_str());
132 if (ImGui::IsItemHovered())
134 ImGui::SetTooltip(
"Used satellites: %s\n"
135 "Avail satellites: %s",
136 usedSats.c_str(), allSats.c_str());
149 ImGui::PushFont(Application::MonoFont());
150 auto totalSolutions = nFixSolutions + nFloatSolutions + nSingleSolutions;
151 ImGui::SetNextItemOpen(
true, ImGuiCond_Always);
152 if (ImGui::TreeNode(
"Solution statistics:"))
154 ImGui::BulletText(
"Fix: %6.2f%% (%zu)", totalSolutions > 0 ?
static_cast<double>(nFixSolutions) /
static_cast<double>(totalSolutions) * 100.0 : 0.0, nFixSolutions);
155 ImGui::BulletText(
"Float: %6.2f%% (%zu)", totalSolutions > 0 ?
static_cast<double>(nFloatSolutions) /
static_cast<double>(totalSolutions) * 100.0 : 0.0, nFloatSolutions);
156 ImGui::BulletText(
"Single: %6.2f%% (%zu)", totalSolutions > 0 ?
static_cast<double>(nSingleSolutions) /
static_cast<double>(totalSolutions) * 100.0 : 0.0, nSingleSolutions);
159 if (ImGui::TreeNode(fmt::format(
"Cycle-slips: {}", _nCycleSlipsLLI + _nCycleSlipsSingle + _nCycleSlipsDual).c_str()))
161 ImGui::BulletText(
"LLI: %zu", _nCycleSlipsLLI);
162 ImGui::BulletText(
"Single Freq: %zu", _nCycleSlipsSingle);
164 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.");
165 ImGui::BulletText(
"Dual Freq: %zu", _nCycleSlipsDual);
167 gui::widgets::HelpMarker(
"Only cycle-slips which were not recognized by\nthe single frequency detector or had the LLI flag set.");
170 ImGui::BulletText(
"Pivot changes: %zu", _nPivotChange);
171 if (_kalmanFilter.isNISenabled()) { ImGui::BulletText(
"NIS triggered: %zu", _nMeasExcludedNIS); }
177 const float cWidth = itemWidth - ImGui::GetStyle().IndentSpacing;
179 if (_obsFilter.ShowGuiWidgets<ReceiverType>(nameId().c_str(), itemWidth))
184 if (_obsEstimator.ShowGuiWidgets<ReceiverType>(nameId().c_str(), itemWidth))
189 ImGui::SetNextItemOpen(
true, ImGuiCond_FirstUseEver);
190 if (ImGui::TreeNode(fmt::format(
"Kalman Filter##{}", nameId()).c_str()))
192 if (_kalmanFilter.showKalmanFilterGUI(nameId().c_str(), itemWidth))
197 if (_kalmanFilter.isNISenabled())
199 auto tmp =
static_cast<int>(_maxRemoveOutlier);
200 ImGui::SetNextItemWidth(cWidth);
201 if (
ImGui::InputIntL(fmt::format(
"Max. remove per epoch##{}", nameId()).c_str(), &tmp, 0))
203 _maxRemoveOutlier =
static_cast<size_t>(tmp);
209 tmp =
static_cast<int>(_outlierRemoveEpochs);
210 ImGui::SetNextItemWidth(cWidth);
211 if (
ImGui::InputIntL(fmt::format(
"Epochs to exclude outlier##{}", nameId()).c_str(), &tmp, 1))
213 _outlierRemoveEpochs =
static_cast<size_t>(tmp);
219 tmp =
static_cast<int>(_outlierMinSat);
220 ImGui::SetNextItemWidth(cWidth);
221 if (
ImGui::InputIntL(fmt::format(
"Min. satellites for check##{}", nameId()).c_str(), &tmp, 1))
223 _outlierMinSat =
static_cast<size_t>(tmp);
229 tmp =
static_cast<int>(_outlierMinPsrObsKeep);
230 ImGui::SetNextItemWidth(cWidth);
231 if (
ImGui::InputIntL(fmt::format(
"# of pseudorange obs to keep##{}", nameId()).c_str(), &tmp, 1))
233 _outlierMinPsrObsKeep =
static_cast<size_t>(tmp);
239 ImGui::SetNextItemWidth(cWidth);
240 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"))
245 gui::widgets::HelpMarker(
"Maximum position variance\nin order to attempt outlier removal during the startup phase.");
247 ImGui::SetNextItemOpen(
false, ImGuiCond_FirstUseEver);
248 if (ImGui::TreeNode(fmt::format(
"Matrices##Kalman Filter {}", nameId()).c_str()))
250 _kalmanFilter.showKalmanFilterMatrixViews(nameId().c_str());
256 ImGui::SetNextItemOpen(
true, ImGuiCond_FirstUseEver);
257 if (ImGui::TreeNode(fmt::format(
"System/Process noise (Standard Deviations)##{}",
size_t(
id)).c_str()))
259 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))
261 LOG_DEBUG(
"{}: stdevAccel changed to horizontal {} and vertical {}", nameId(), _gui_stdevAccel.at(0), _gui_stdevAccel.at(1));
262 LOG_DEBUG(
"{}: stdevAccelNoiseUnits changed to {}", nameId(), fmt::underlying(_gui_stdevAccelUnits));
266 cWidth, unitWidth, &_gui_ambiguityFloatProcessNoiseStDev,
267 _gui_stdevAmbiguityFloatUnits,
"cycle\0\0",
268 0.0, 0.0,
"%.2e", ImGuiInputTextFlags_CharsScientific))
270 LOG_DEBUG(
"{}: ambiguityFloatProcessNoiseStDev changed to {}", nameId(), _gui_ambiguityFloatProcessNoiseStDev);
271 LOG_DEBUG(
"{}: stdevAmbiguityFloatUnits changed to {}", nameId(), fmt::underlying(_gui_stdevAmbiguityFloatUnits));
273 switch (_gui_stdevAmbiguityFloatUnits)
275 case StdevAmbiguityUnits::Cycle:
276 _ambiguityFloatProcessNoiseVariance = std::pow(_gui_ambiguityFloatProcessNoiseStDev, 2);
283 cWidth, unitWidth, &_gui_ambiguityFixProcessNoiseStDev,
284 _gui_stdevAmbiguityFixUnits,
"cycle\0\0",
285 0.0, 0.0,
"%.2e", ImGuiInputTextFlags_CharsScientific))
287 LOG_DEBUG(
"{}: ambiguityFixProcessNoiseStDev changed to {}", nameId(), _gui_ambiguityFixProcessNoiseStDev);
288 LOG_DEBUG(
"{}: stdevAmbiguityFixUnits changed to {}", nameId(), fmt::underlying(_gui_stdevAmbiguityFixUnits));
290 switch (_gui_stdevAmbiguityFixUnits)
292 case StdevAmbiguityUnits::Cycle:
293 _ambiguityFixProcessNoiseVariance = std::pow(_gui_ambiguityFixProcessNoiseStDev, 2);
303 ImGui::SetNextItemOpen(
true, ImGuiCond_FirstUseEver);
304 if (ImGui::TreeNode(fmt::format(
"Ambiguity resolution##{}",
size_t(
id)).c_str()))
306 if (!_obsFilter.isObsTypeUsed(
GnssObs::Carrier)) { ImGui::BeginDisabled(); }
308 ImGui::SetNextItemWidth(cWidth);
311 LOG_DEBUG(
"{}: Ambiguity resolution strategy changed to {}", nameId(),
NAV::to_string(_ambiguityResolutionStrategy));
315 if (
GuiAmbiguityResolution(fmt::format(
"Ambiguity resolution##{}",
size_t(
id)).c_str(), _ambiguityResolutionParameters, itemWidth))
317 LOG_DEBUG(
"{}: Ambiguity resolution parameters changed to [{}][{}]", nameId(),
318 NAV::to_string(_ambiguityResolutionParameters.decorrelationAlgorithm),
323 ImGui::SetNextItemWidth(cWidth);
324 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"))
331 auto tmp =
static_cast<int>(_nMinSatForAmbFix);
332 ImGui::SetNextItemWidth(cWidth);
333 if (
ImGui::InputIntL(fmt::format(
"Min Sat for Fix##{}",
size_t(
id)).c_str(), &tmp, 0))
335 _nMinSatForAmbFix =
static_cast<size_t>(tmp);
339 gui::widgets::HelpMarker(
"Minimum amount of satellites with carrier observations\nin order to attempt ambiguity fixing.");
342 tmp =
static_cast<int>(_nMinSatForAmbHold);
343 ImGui::SetNextItemWidth(cWidth);
344 if (
ImGui::InputIntL(fmt::format(
"Min Sat for Hold##{}",
size_t(
id)).c_str(), &tmp, 0))
346 _nMinSatForAmbHold =
static_cast<size_t>(tmp);
350 gui::widgets::HelpMarker(
"Minimum amount of satellites with carrier observations\nin order to attempt ambiguity holding.");
352 if (ImGui::Checkbox(fmt::format(
"Apply fixed ambiguities with KF update##{}",
size_t(
id)).c_str(), &_applyFixedAmbiguitiesWithUpdate))
358 "Otherwise apply via\n"
360 "- b = b_float - Q_ba * Q_aa^-1 (a_fix - a_float)");
362 if (_applyFixedAmbiguitiesWithUpdate
364 cWidth, unitWidth, &_gui_ambFixUpdateStdDev,
365 _gui_ambFixUpdateStdDevUnits,
"cycle\0\0",
366 0.0, 0.0,
"%.2e", ImGuiInputTextFlags_CharsScientific))
368 LOG_DEBUG(
"{}: ambFixUpdateStdDev changed to {}", nameId(), _gui_ambFixUpdateStdDev);
369 LOG_DEBUG(
"{}: ambFixUpdateStdDevUnits changed to {}", nameId(), fmt::underlying(_gui_ambFixUpdateStdDevUnits));
371 switch (_gui_ambFixUpdateStdDevUnits)
373 case StdevAmbiguityUnits::Cycle:
374 _ambFixUpdateVariance = std::pow(_gui_ambFixUpdateStdDev, 2);
381 ImGui::SetNextItemOpen(
true, ImGuiCond_FirstUseEver);
382 if (ImGui::TreeNode(fmt::format(
"Cycle-slip detection##Tree{}",
size_t(
id)).c_str()))
384 ImGui::SetNextItemOpen(
true, ImGuiCond_FirstUseEver);
385 if (ImGui::TreeNode(fmt::format(
"Base##Tree{}",
size_t(
id)).c_str()))
387 if (
CycleSlipDetectorGui(fmt::format(
"Cycle-slip detector Base##{}",
size_t(
id)).c_str(), _cycleSlipDetector[Base], 145.0F))
393 ImGui::SetNextItemOpen(
true, ImGuiCond_FirstUseEver);
394 if (ImGui::TreeNode(fmt::format(
"Rover##Tree{}",
size_t(
id)).c_str()))
396 if (
CycleSlipDetectorGui(fmt::format(
"Cycle-slip detector Rover##{}",
size_t(
id)).c_str(), _cycleSlipDetector[Rover], 145.0F))
409 if (ImGui::TreeNode(fmt::format(
"Misc##{}",
size_t(
id)).c_str()))
411 if (ImGui::Checkbox(fmt::format(
"Output a SPP solution if no base observation available##{}",
size_t(
id)).c_str(), &_calcSPPIfNoBase))
415 ImGui::SetNextItemWidth(cWidth);
416 if (
ImGui::InputDoubleL(fmt::format(
"Max Time between rover & base obs##{}",
size_t(
id)).c_str(), &_maxTimeBetweenBaseRoverForRTK, 1e-3))
424 ImGui::SetNextItemOpen(
true, ImGuiCond_FirstUseEver);
425 if (ImGui::TreeNode(fmt::format(
"Events##{}",
size_t(
id)).c_str()))
427 if (ImGui::Checkbox(fmt::format(
"Output State Change Events##{}",
size_t(
id)).c_str(), &_outputStateEvents))
431 ImGui::SetNextItemWidth(cWidth);
432 if (ImGui::InputText(fmt::format(
"Events Filter Regex##{}",
size_t(
id)).c_str(), &_eventFilterRegex))
436 if (!_events.empty())
438 std::optional<std::regex> filter;
441 filter = std::regex(_eventFilterRegex, std::regex_constants::ECMAScript | std::regex_constants::icase);
446 if (ImGui::BeginChild(fmt::format(
"Events list {}",
size_t(
id)).c_str(), ImVec2(0.0F, 600.0F),
true))
448 ImGui::PushFont(Application::MonoFont());
449 for (
size_t i = 0; i < _events.size(); i++)
451 if (!std::any_of(_events.at(i).second.begin(), _events.at(i).second.end(), [&](
const std::string& text) {
452 return _eventFilterRegex.empty() || (filter && std::regex_search(text, *filter));
458 if (i != 0) { ImGui::Separator(); }
459 ImGui::SetNextItemOpen(
true, ImGuiCond_Always);
460 if (ImGui::TreeNode(fmt::format(
"{} GPST", _events.at(i).first.toYMDHMS(
GPST)).c_str()))
462 for (
const auto& text : _events.at(i).second)
464 if (_eventFilterRegex.empty() || (filter && std::regex_search(text, *filter)))
466 ImGui::BulletText(
"%s", text.c_str());
528 if (j.contains(
"obsFilter")) { j.at(
"obsFilter").get_to(
_obsFilter); }
529 if (j.contains(
"obsEstimator")) { j.at(
"obsEstimator").get_to(
_obsEstimator); }
530 if (j.contains(
"kalmanFilter")) { j.at(
"kalmanFilter").get_to(
_kalmanFilter); }
531 if (j.contains(
"maxRemoveOutlier")) { j.at(
"maxRemoveOutlier").get_to(
_maxRemoveOutlier); }
532 if (j.contains(
"outlierRemoveEpochs")) { j.at(
"outlierRemoveEpochs").get_to(
_outlierRemoveEpochs); }
533 if (j.contains(
"outlierMinSat")) { j.at(
"outlierMinSat").get_to(
_outlierMinSat); }
534 if (j.contains(
"outlierMinPsrObsKeep")) { j.at(
"outlierMinPsrObsKeep").get_to(
_outlierMinPsrObsKeep); }
536 if (j.contains(
"calcSPPIfNoBase")) { j.at(
"calcSPPIfNoBase").get_to(
_calcSPPIfNoBase); }
541 if (j.contains(
"nMinSatForAmbFix")) { j.at(
"nMinSatForAmbFix").get_to(
_nMinSatForAmbFix); }
542 if (j.contains(
"nMinSatForAmbHold")) { j.at(
"nMinSatForAmbHold").get_to(
_nMinSatForAmbHold); }
543 if (j.contains(
"maxPosVar")) { j.at(
"maxPosVar").get_to(
_maxPosVar); }
546 if (j.contains(
"ambFixUpdateStdDev"))
557 if (j.contains(
"cycleSlipDetector")) { j.at(
"cycleSlipDetector").get_to(
_cycleSlipDetector); }
558 if (j.contains(
"eventFilterRegex")) { j.at(
"eventFilterRegex").get_to(
_eventFilterRegex); }
559 if (j.contains(
"outputStateEvents")) { j.at(
"outputStateEvents").get_to(
_outputStateEvents); }
562 if (j.contains(
"stdevAccel")) {
_gui_stdevAccel = j.at(
"stdevAccel"); }
565 if (j.contains(
"ambiguityFloatProcessNoiseStDev"))
577 if (j.contains(
"ambiguityFixProcessNoiseStDev"))
595 LOG_ERROR(
"{}: You need to connect a GNSS NavigationInfo provider",
nameId());
640 std::array<double, 2> varAccel{};
647 LOG_DATA(
" sigma2_accel = h: {}, v: {} [m^2 / s^3]",
nameId(), varAccel.at(0), varAccel.at(1));
650 LOG_DEBUG(
"RealTimeKinematic initialized");
672 auto iter = std::ranges::find_if(
_events, [&](
const auto& event) {
return event.first == rtkSol->insTime; });
675 _events.emplace_back(rtkSol->insTime, std::vector{ text });
679 iter->second.emplace_back(text);
681 rtkSol->addEvent(text);
686#if LOG_LEVEL <= LOG_LEVEL_DATA
689 for (
const auto& obs : observations.signals)
691 satData[obs.first.toSatId()].first |= obs.first.freq();
692 satData[obs.first.toSatId()].second |= obs.first.code;
695 if (std::ranges::all_of(obs.second.recvObs, [&obsType](
const auto& recvObs) {
696 return recvObs.second->obs.contains(static_cast<GnssObs::ObservationType>(obsType));
703 for ([[maybe_unused]]
const auto& [satId, freqCode] : satData)
705 LOG_DATA(
"{}: [{}] on frequencies [{}] with codes [{}]",
nameId(), satId, freqCode.first, freqCode.second);
706 for (
const auto& [satSigId, obs] : sigData)
708 if (satSigId.toSatId() != satId) {
continue; }
710 for (
const auto& o : obs)
712 if (!
str.empty()) {
str +=
", "; }
713 str += fmt::format(
"{}", o);
723 auto gnssObs = std::static_pointer_cast<const Pos>(queue.
extract_front());
724 LOG_DATA(
"{}: Received Base Position for [{}]",
nameId(), gnssObs->insTime.toYMDHMS(
GPST));
751 LOG_TRACE(
"{}: Data gap between Base [{}] and Rover [{}] is {:.1f}s. Falling back to SPP for gaps larger than 0.5s",
nameId(),
776 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.",
800 if (!sppSol->e_velocity().hasNaN())
808 if (sppSol->e_CovarianceMatrix().has_value())
828 std::vector<InputPin::IncomingLink::ValueWrapper<GnssNavInfo>> gnssNavInfoWrappers;
829 std::vector<const GnssNavInfo*> gnssNavInfos;
834 gnssNavInfoWrappers.push_back(*gnssNavInfo);
835 gnssNavInfos.push_back(gnssNavInfo->v);
838 if (!gnssNavInfos.empty())
840 auto rtkSol = std::make_shared<RtkSolution>();
852 std::vector<States::StateKeyType> ambKeys;
855 if (
const auto* ambDD = std::get_if<States::AmbiguityDD>(&
_kalmanFilter.x.rowKeys().at(i)))
857 ambKeys.emplace_back(*ambDD);
864 std::string text = fmt::format(
"Outage of {:.1f} [s] (interval {:.1f} [s]). Reinitializing with SPP solution.", dt,
_dataInterval);
871 std::vector<States::StateKeyType> ambKeys;
874 if (
const auto* ambDD = std::get_if<States::AmbiguityDD>(&
_kalmanFilter.x.rowKeys().at(i)))
876 ambKeys.emplace_back(*ambDD);
881 std::string text = fmt::format(
"Outage of {:.1f} [s] (interval {:.1f} [s]). Resetting ambiguities.", dt,
_dataInterval);
894 LOG_DEBUG(
"{}: Initial base position: {}°, {}°, {}m (ECEF {} {} {} [m])",
nameId(),
897 LOG_DEBUG(
"{}: Initial rover position: {}°, {}°, {}m (ECEF {} {} {} [m])",
nameId(),
904 LOG_DATA(
"{}: Could not calculate initial position solution.",
nameId());
910 else if (startupPhase)
921 auto ionosphericCorrections = std::make_shared<const IonosphericCorrections>(gnssNavInfos);
926 auto logLevel = spdlog::get_level();
927 spdlog::set_level(spdlog::level::debug);
938 observations,
nullptr,
nameId() +
" no filter");
944 observations,
nullptr,
nameId() +
" no filter");
947 for (
const auto& [satSigId, sigObs] : observations.
signals)
949 auto satId = satSigId.toSatId();
950 if (std::ranges::find_if(rtkSol->satData, [&satId](
const auto& sat) {
951 return satId == sat.first;
953 != rtkSol->satData.end())
957 rtkSol->satData.emplace_back(satSigId.toSatId(),
958 RtkSolution::SatData{ .satElevation = sigObs.recvObs.at(Rover)->satElevation(_receiver[Rover].e_posMarker, _receiver[Rover].lla_posMarker),
959 .satAzimuth = sigObs.recvObs.at(Rover)->satElevation(_receiver[Rover].e_posMarker, _receiver[Rover].lla_posMarker) });
961 for (
const auto& [satSigId, sigObs] : observations.
signals)
963 for (
const auto& obs : sigObs.recvObs.at(
Rover)->obs)
965 rtkSol->observableReceived.emplace(satSigId, obs.first);
968 spdlog::set_level(logLevel);
978 observations, &filtered,
nameId());
984 observations, &filtered,
nameId());
986 rtkSol->filtered = filtered;
988 for (
const auto& [satSigId, sigObs] : observations.
signals)
990 for (
const auto& obs : sigObs.recvObs.at(
Rover)->obs)
992 rtkSol->observableFiltered.emplace(satSigId, obs.first);
1008 bool nisEnabled = !startupPhase &&
_kalmanFilter.isNISenabled();
1011 bool pivotChanged =
true;
1016 for (
size_t i = 0; i < maxIter; i++)
1027 pivotChanged =
false;
1030 if (nisEnabled && !startupPhase)
1034 if (!rtkSol->nisResultInitial) { rtkSol->nisResultInitial = nisResult; }
1035 rtkSol->nisResultFinal = nisResult;
1038 nisResult.triggered =
false;
1039 addEventToGui(rtkSol,
"Stopped doing NIS check, as minimum amount of satellites reached.");
1041 if (nisResult.triggered)
1049 for (
const auto& outlier : outliers)
1051 pivotChanged |= outlier.pivot.has_value();
1069 LOG_DATA(
"{}: Final NIS = {} < {} = r2. {} removed",
nameId(), rtkSol->nisResultFinal->NIS, rtkSol->nisResultFinal->r2, rtkSol->nisRemovedCnt);
1091 LOG_TRACE(
"{}: No observations found. Check that your base and rover file match and reconfigure the filter.",
nameId());
1102 rtkSol->nSatellites = observations.
satellites.size();
1112 if (
const auto* ambDD = std::get_if<States::AmbiguityDD>(&
_kalmanFilter.x.rowKeys().at(i)))
1118 .satSigId = ambDD->satSigId,
1122 rtkSol->ambiguityDD_br.push_back(ambDDSol);
1127 rtkSol->pivots.emplace(pivot.second.satSigId, pivot.first.second);
1129 for (
const auto& [satSigId, sigObs] : observations.
signals)
1131 for (
const auto& obs : sigObs.recvObs.at(
Rover)->obs)
1133 rtkSol->observableUsed.emplace(satSigId, obs.first);
1143 _receiver[Rover].gnssObs =
nullptr;
1144 _baseObsReceivedThisEpoch =
false;
1152 std::vector<InputPin::IncomingLink::ValueWrapper<GnssNavInfo>> gnssNavInfoWrappers;
1153 std::vector<const GnssNavInfo*> gnssNavInfos;
1158 gnssNavInfoWrappers.push_back(*gnssNavInfo);
1159 gnssNavInfos.push_back(gnssNavInfo->v);
1165 auto rtkSol = std::make_shared<RtkSolution>();
1166 rtkSol->insTime = sppSol->insTime;
1168 rtkSol->nSatellites = sppSol->nSatellites;
1176 if (sppSol->e_CovarianceMatrix().has_value())
1180 rtkSol->setPosVelAndCov_e(sppSol->e_position(), sppSol->e_velocity(),
1185 rtkSol->setPositionAndCov_e(sppSol->e_position(),
1191 rtkSol->setPosition_e(sppSol->e_position());
1192 rtkSol->setVelocity_e(sppSol->e_velocity());
1231 if (
const auto* ambDD = std::get_if<States::AmbiguityDD>(&
_kalmanFilter.x.rowKeys().at(i)))
1266 std::array<std::vector<CycleSlipDetector::SatelliteObservation>,
ReceiverType_COUNT> cycleSlipObservations;
1268 for (
const auto& satId : observations.
satellites)
1271 for (
const auto& [satSigId, signalObs] : observations.
signals)
1273 if (satSigId.toSatId() != satId) {
continue; }
1289 for (
const auto& receiverObs :
_receiver.at(recvType).gnssObs->data)
1291 if (receiverObs.satSigId.toSatId() == satId && receiverObs.carrierPhase)
1293 [[maybe_unused]]
auto lambda =
InsConst::C / receiverObs.satSigId.freq().getFrequency(signalObs.freqNum());
1294 LOG_DATA(
"[{}] [{}] Added to cycle-slip detection: {:.1f} [m]", receiverObs.satSigId, recvType, receiverObs.carrierPhase->value * lambda);
1299 auto sat = std::ranges::find_if(cycleSlipObservations.at(recvType),
1300 [&satId](
const auto& satObs) { return satObs.satId == satId; });
1302 if (sat == cycleSlipObservations.at(recvType).end())
1305 .signals = { signal },
1306 .freqNum = signalObs.freqNum() };
1307 cycleSlipObservations.at(recvType).push_back(satObs);
1311 sat->signals.push_back(signal);
1317 if (added) {
break; }
1321 std::vector<NAV::Code> pivotsToChange;
1322 std::vector<States::AmbiguityDD> removedStates;
1323 auto removeSatSig = [&](
const SatSigId& satSigId, [[maybe_unused]]
const std::string& reason) ->
bool {
1327 LOG_TRACE(
"{}: [{}] Cycle-slip detected in [{}], due to {}. Removing state: {}",
nameId(),
1336 LOG_TRACE(
"{}: [{}] Cycle-slip detected in pivot satellite [{}], due to {}",
nameId(),
1338 pivotsToChange.push_back(satSigId.
code);
1342 LOG_DATA(
"{}: [{}] Cycle-slip detected in [{}], due to {}. Not yet estimated (no action taken).",
nameId(),
1349 for (
size_t i = 0; i < cycleSlipObservations.size(); ++i)
1351 std::string receiverName = fmt::format(
"{}",
static_cast<ReceiverType>(i));
1355 for (
const auto& cycleSlip : cycleSlips)
1357 if (
const auto* s = std::get_if<CycleSlipDetector::CycleSlipLossOfLockIndicator>(&cycleSlip))
1359 if (s->signal.code &
_obsFilter.getCodeFilter()
1360 && removeSatSig(s->signal,
"LLI set in " + receiverName))
1362 rtkSol->cycleSlipDetectorResult.emplace_back(cycleSlip, receiverName);
1365 else if (
const auto* s = std::get_if<CycleSlipDetector::CycleSlipSingleFrequency>(&cycleSlip))
1367 if (s->signal.code &
_obsFilter.getCodeFilter()
1368 && removeSatSig(s->signal,
"single frequency check in " + receiverName))
1370 rtkSol->cycleSlipDetectorResult.emplace_back(cycleSlip, receiverName);
1373 else if (
const auto* s = std::get_if<CycleSlipDetector::CycleSlipDualFrequency>(&cycleSlip))
1376 for (
const auto& signal : s->signals)
1379 && removeSatSig(signal,
"dual frequency check in " + receiverName)
1382 rtkSol->cycleSlipDetectorResult.emplace_back(cycleSlip, receiverName);
1390 for (
const auto& code : pivotsToChange)
1396 for (
const auto& cycleSlip : rtkSol->cycleSlipDetectorResult)
1398 switch (cycleSlip.first.index())
1412 auto text = fmt::format(
"{}: {}", cycleSlip.second, cycleSlip.first);
1416 for (
const auto& state : removedStates)
1418 auto text = fmt::format(
"State [{}] removed, because cycle-slip detected.", state);
1426 LOG_DATA(
"{}: Checking for observations to remove when only one observable per code for double difference",
nameId());
1428 std::unordered_map<std::pair<Code, GnssObs::ObservationType>, uint16_t> signalCount;
1429 for (
const auto& [satSigId, sigObs] : observations.
signals)
1431 const auto& recvObs = sigObs.recvObs.at(
Base);
1432 for (
const auto& obs : recvObs->obs)
1434 signalCount[std::make_pair(satSigId.code, obs.first)]++;
1438 std::vector<SatSigId> sigToRemove;
1439 for (
auto& [satSigId, sigObs] : observations.
signals)
1441 for (
auto& recvObs : sigObs.recvObs)
1443 std::vector<GnssObs::ObservationType> obsToRemove;
1444 for (
const auto& obs : recvObs.second->obs)
1446 if (signalCount[std::make_pair(satSigId.code, obs.first)] < 2)
1448 obsToRemove.push_back(obs.first);
1454 recvObs.second->obs.erase(obs);
1457 if (recvObs.second->obs.empty())
1459 sigToRemove.push_back(satSigId);
1465 for (
const auto& satSigId : sigToRemove)
1467 LOG_DATA(
"{}: Removing [{}], because no more observations left for this signal",
nameId(), satSigId);
1471 for (
const auto& obsType :
_obsFilter.getUsedObservationTypes())
1473 auto key = std::make_pair(satSigId.code, obsType);
1476 LOG_DATA(
"{}: Pivot [{}][{}] does not have enough signals anymore",
nameId(), satSigId.code, obsType);
1487 Observations& observations,
const std::shared_ptr<RtkSolution>& rtkSol,
1491 std::optional<RtkSolution::PivotChange> pivotChange;
1492 if (reason != Reason::None)
1495 pivotChange->
reason = reason;
1496 pivotChange->obsType = obsType;
1501 if (observations.
signals.contains(pivot.satSigId))
1503 auto oldPivotObs = observations.
signals.at(pivot.satSigId).recvObs.begin();
1504 pivotChange->oldPivotSat = pivot.satSigId;
1511 auto sumRecvElevation = [&](
double el,
const auto& recvData) {
1515 for (
const auto& [satSigId, observation] : observations.
signals)
1517 if (satSigId.code != code) {
continue; }
1519 bool anySignalOnThisCodeWasEstimatedYet = std::ranges::any_of(observations.
signals,
1520 [&](
const auto& obs) { return obs.first.code == code
1521 && _kalmanFilter.x.hasRow(States::AmbiguityDD(obs.first)); });
1523 [&code](
const auto& amb) {
return amb.first.code == code; });
1525 LOG_DATA(
"{}: [{}] Checking [{}] for new pivot and obsType [{}]",
1529 for (
const auto& recvObs : observation.recvObs)
1531 if (recvObs.second->obs.contains(obsType)) { nFound++; }
1533 if (nFound != observation.recvObs.size()) {
continue; }
1535#if LOG_LEVEL <= LOG_LEVEL_DATA
1538 std::string ambHold;
1542 ambHold += fmt::format(
"[{} = {}]", amb.first, amb.second);
1544 LOG_DATA(
"{}: [{}] anySignalOnThisCodeWasEstimatedYet = {}, anySignalOnThisCodeWithFix = {}, ambHold = {}",
1545 nameId(),
_receiver[
Rover].gnssObs->insTime.toYMDHMS(
GPST), anySignalOnThisCodeWasEstimatedYet, anySignalOnThisCodeWithFix, ambHold);
1550 if (pivotChange->oldPivotSat == satSigId) {
continue; }
1554 && (anySignalOnThisCodeWasEstimatedYet ||
_pivotSatellites.contains({ code, GnssObs::Carrier }))
1557 && (anySignalOnThisCodeWithFix ||
_pivotSatellites.contains({ code, GnssObs::Carrier }))
1563 double satElevation = std::accumulate(observation.recvObs.begin(), observation.recvObs.end(), 0.0, sumRecvElevation)
1564 /
static_cast<double>(observation.recvObs.size());
1568 _pivotSatellites.insert(std::make_pair(std::make_pair(code, obsType), satSigId));
1571 LOG_DATA(
"{}: [{}] Setting [{}] as new pivot for obsType [{}] (elevation {:.4}°, no previous pivot on this code)",
nameId(),
1576 .oldPivotElevation = 0.0,
1577 .newPivotSat = satSigId,
1578 .newPivotElevation = satElevation };
1582 LOG_DATA(
"{}: [{}] Setting [{}] as new pivot for obsType [{}] (elevation {:.4}°)",
nameId(),
1584 pivotChange->newPivotSat = satSigId;
1585 pivotChange->newPivotElevation = satElevation;
1592 if (!observations.
signals.contains(pivotSat.satSigId))
1594 LOG_DATA(
"{}: [{}] Setting [{}] as new pivot for obsType [{}] (elevation {:.4}°, old pivot [{}] not observed this epoch)",
nameId(),
1599 .oldPivotElevation = 0.0,
1600 .newPivotSat = satSigId,
1601 .newPivotElevation = satElevation };
1606 const auto& pivotObs = observations.
signals.at(pivotSat.satSigId);
1607 double pivElevation = std::accumulate(pivotObs.recvObs.begin(), pivotObs.recvObs.end(), 0.0, sumRecvElevation)
1608 /
static_cast<double>(pivotObs.recvObs.size());
1610 if (satElevation > pivElevation)
1612 LOG_DATA(
"{}: [{}] Setting [{}] as new pivot for obsType [{}] (elevation {:.4}°, larger than previous pivot [{}] elevation {:.4}°)",
nameId(),
1619 .oldPivotElevation = pivElevation,
1620 .newPivotSat = satSigId,
1621 .newPivotElevation = satElevation };
1625 pivotChange->newPivotSat = satSigId;
1626 pivotChange->newPivotElevation = satElevation;
1637 ||
_pivotSatellites.at({ code, obsType }).satSigId == pivotChange->newPivotSat,
1638 "The new pivot satellite and current one must match.");
1640 || pivotChange->newPivotSat ==
SatSigId{},
1641 "Old and new pivot satellite have to be different.");
1645 rtkSol->changedPivotSatellites.emplace(std::make_pair(code, obsType), *pivotChange);
1649 if (pivotChange->newPivotSat ==
SatSigId{})
1657 if (
const auto* ambDD = std::get_if<States::AmbiguityDD>(&key))
1659 if (ambDD->satSigId.code != pivotChange->oldPivotSat.code) {
continue; }
1661 LOG_DATA(
"{}: [{}] Removing Amibguity as no pivot anymore {}",
nameId(), ambDD->satSigId, key);
1670 else if (pivotChange->reason != Reason::NewCode && obsType ==
GnssObs::Carrier)
1673 pivotChange->reason != Reason::PivotNotObservedInEpoch
1674 && pivotChange->reason != Reason::PivotCycleSlip
1675 && pivotChange->reason != Reason::PivotOutlier,
1687 LOG_DATA(
"{}: [{} - {}]: {}",
nameId(), key.first, key.second, pivot.satSigId);
1692 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);
1701 std::set<Code> codes;
1702 for (
const auto& [satSigId, observation] : observations.
signals)
1704 codes.insert(satSigId.code);
1706 bool anyPivotChanged =
false;
1707 for (
const auto& code : codes)
1709 for (
const auto& obsType :
_obsFilter.getUsedObservationTypes())
1716 const auto& pivotSatSigId = pivotSat.satSigId;
1717 auto obs = std::ranges::find_if(observations.
signals, [&](
const auto& obs) {
1719 for (const auto& recvObs : obs.second.recvObs)
1721 if (recvObs.second->obs.contains(obsType)) { nFound++; }
1723 return obs.first == pivotSatSigId && nFound == obs.second.recvObs.size();
1725 if (obs == observations.
signals.end())
1727 LOG_DATA(
"{}: [{}] Removing pivot satellite [{}] on observable [{}] because not observed this epoch.",
nameId(),
1733 anyPivotChanged |=
updatePivotSatellite(code, obsType, observations, rtkSol, reason).has_value();
1737 LOG_DATA(
"{}: {}", nameId(), anyPivotChanged ?
"Pivot satellite changed" :
"No pivot satellite change");
1738 if (anyPivotChanged)
1740 removeSingleObservations(observations, &rtkSol->filtered, rtkSol);
1741 printPivotSatellites();
1746 bool oldPivotObservedInEpoch,
const std::shared_ptr<RtkSolution>& rtkSol)
1751 std::vector<States::StateKeyType> ambiguitiesToChange;
1754 const auto* ambDD = std::get_if<States::AmbiguityDD>(&
_kalmanFilter.x.rowKeys().at(i));
1755 if (ambDD && newPivotSatSigId.
code == ambDD->satSigId.code
1756 && (oldPivotObservedInEpoch || newPivotSatSigId != ambDD->satSigId))
1758 ambiguitiesToChange.emplace_back(*ambDD);
1762 newPivotSatSigId, fmt::join(ambiguitiesToChange,
", "));
1764 auto nStates =
static_cast<int>(
_kalmanFilter.x.rowKeys().size());
1768 D(ambiguitiesToChange, newPivotKey).setConstant(-1.0);
1769 if (!oldPivotObservedInEpoch) { D(newPivotKey, newPivotKey) = 0.0; }
1780#if LOG_LEVEL <= LOG_LEVEL_DATA
1782 std::string ambHold;
1784 for (
const auto& amb : ambiguitiesToChange)
1786 if (
const auto* ambDD = std::get_if<States::AmbiguityDD>(&amb))
1790 ambHold += fmt::format(
"[{} = {}]", ambDD->satSigId,
_ambiguitiesHold.at(ambDD->satSigId));
1798 for (
const auto& amb : ambiguitiesToChange)
1800 if (
const auto* ambDD = std::get_if<States::AmbiguityDD>(&amb))
1809 if (oldPivotObservedInEpoch)
1821 addEventToGui(rtkSol, fmt::format(
"State [{}] replaced with [{}] (because [{}] is new pivot satellite)", newPivotKey, oldPivotKey, newPivotSatSigId));
1823 LOG_TRACE(
"{}: [{}] State [{}] replaced with [{}] (because [{}] is new pivot satellite)",
nameId(),
1824 _receiver[
Rover].gnssObs->insTime.toYMDHMS(
GPST), newPivotKey, oldPivotKey, newPivotSatSigId);
1832 detector.resetSignal(newPivotSatSigId);
1833 detector.resetSignal(oldPivotSatSigId);
1837 addEventToGui(rtkSol, fmt::format(
"State [{}] removed (because new pivot satellite)", newPivotKey));
1844#if LOG_LEVEL <= LOG_LEVEL_DATA
1846 std::erase_if(ambiguitiesToChange, [&newPivotSatSigId](
const auto& amb) {
1847 if (
const auto* ambDD = std::get_if<States::AmbiguityDD>(&amb))
1849 return ambDD->satSigId == newPivotSatSigId;
1853 if (oldPivotObservedInEpoch)
1858 std::string ambHold;
1860 for (
const auto& amb : ambiguitiesToChange)
1862 if (
const auto* ambDD = std::get_if<States::AmbiguityDD>(&amb))
1866 ambHold += fmt::format(
"[{} = {}]", ambDD->satSigId,
_ambiguitiesHold.at(ambDD->satSigId));
1878 bool anyStateChanged =
false;
1880 auto addEvent = [&](
const std::string& text) {
1884 std::vector<States::StateKeyType> newAddedAmbiguities;
1886 for (
const auto& [satSigId, observation] : observations.
signals)
1894 &&
_pivotSatellites.at({ satSigId.code, GnssObs::Carrier }).satSigId != satSigId)
1896 anyStateChanged =
true;
1899 newAddedAmbiguities.emplace_back(key);
1900 if (std::ranges::find(rtkSol->newEstimatedAmbiguity, satSigId)
1901 == rtkSol->newEstimatedAmbiguity.end())
1903 rtkSol->newEstimatedAmbiguity.push_back(satSigId);
1914 LOG_DATA(
"{}: {} - pivot {}",
nameId(), satSigId, pivotSat.satSigId);
1915 if (!observations.
signals.contains(pivotSat.satSigId))
1917 LOG_CRITICAL(
"{}: [{}] The pivot satellite [{}] does not have a carrier measurement. It should have been switched before.",
1920 const auto& pivotObs = observations.
signals.at(pivotSat.satSigId);
1923 double lambda_j =
InsConst::C / satSigId.freq().getFrequency(observation.freqNum());
1931 double phi_br_s_meas = phi_r_s.measurement - phi_b_s.measurement;
1932 double phi_br_1_meas = phi_r_1.measurement - phi_b_1.measurement;
1933 double phi_br_1s_meas = phi_br_s_meas - phi_br_1_meas;
1935 double varCarrierPseudorange = std::numeric_limits<double>::infinity();
1936 bool forceCarrierPseudorange =
false;
1944 if (pivotSatPsr.satSigId == satSigId)
1946 const auto& pivotObsPsr = observations.
signals.at(pivotSatPsr.satSigId);
1952 varCarrierPseudorange = phi_r_s.measVar + p_r_s.measVar + phi_b_s.measVar + p_b_s.measVar
1953 + phi_r_1.measVar + p_r_1.measVar + phi_b_1.measVar + p_b_1.measVar;
1956 forceCarrierPseudorange = posUncertanty > 0.06;
1960 Eigen::Vector3d e_pLOS_1s = pivotObs.recvObs.at(
Rover)->e_pLOS(
_receiver[
Rover].e_posMarker)
1962 const double varCarrierGeometry = phi_r_s.measVar + phi_b_s.measVar + phi_r_1.measVar + phi_b_1.measVar
1964 LOG_DATA(
"{}: varCarrierGeometry = {:.2g} [m], varCarrierPseudorange = {:.2g} [m]",
nameId(), varCarrierGeometry, varCarrierPseudorange);
1966 if (varCarrierPseudorange < varCarrierGeometry || forceCarrierPseudorange)
1973 double p_br_s_meas = p_r_s.measurement - p_b_s.measurement;
1974 double p_br_1_meas = p_r_1.measurement - p_b_1.measurement;
1975 double p_br_1s_meas = p_br_s_meas - p_br_1_meas;
1977 _kalmanFilter.x(key) = (phi_br_1s_meas - p_br_1s_meas) / lambda_j;
1979 _kalmanFilter.P(key, key) = varCarrierPseudorange * 3.0 / std::pow(lambda_j, 2);
1980 auto msg = fmt::format(
"ddCP({:.1f}m) - ddPR({:.1f}m) = {:.1f} [cycles], std = {:.2f} [cycles]",
1982 LOG_DATA(
"{}: [{}] Init Amibguity: {}",
nameId(), satSigId, msg);
1984 addEvent(fmt::format(
"State [{}] added ({})", key, msg));
1988 double phi_br_s_est = phi_r_s.estimate - phi_b_s.estimate;
1989 double phi_br_1_est = phi_r_1.estimate - phi_b_1.estimate;
1990 double phi_br_1s_est = phi_br_s_est - phi_br_1_est;
1992 _kalmanFilter.x(key) = (phi_br_1s_meas - phi_br_1s_est) / lambda_j;
1994 _kalmanFilter.P(key, key) = varCarrierGeometry * 3.0 / std::pow(lambda_j, 2);
1995 auto msg = fmt::format(
"ddCP({:.1f}m) - ddEst({:.1f}m) = {:.1f} [cycles], std = {:.2f} [cycles]",
1997 LOG_DATA(
"{}: [{}] Init Amibguity: {}",
nameId(), satSigId, msg);
1999 addEvent(fmt::format(
"State [{}] added ({})", key, msg));
2007 if (
const auto* ambDD = std::get_if<States::AmbiguityDD>(&key))
2011 LOG_DATA(
"{}: [{}] Removing Ambiguity as not observed anymore {}",
nameId(), ambDD->satSigId, key);
2013 addEvent(fmt::format(
"State [{}] removed (not observed anymore)", key));
2019 if (anyStateChanged)
2031 singleDifferences.reserve(observations.signals.size());
2033 LOG_DATA(
"{}: Calculating single differences (rover - base):",
nameId());
2034 for (
const auto& [satSigId, observation] : observations.signals)
2036 const auto& baseObservations = observation.recvObs.at(
Base)->obs;
2037 const auto& roverObservations = observation.recvObs.at(
Rover)->obs;
2039 for (
const auto& [obsType, baseObs] : baseObservations)
2041 const auto& roverObs = roverObservations.at(obsType);
2043 singleDifferences[satSigId][obsType].estimate = roverObs.estimate - baseObs.estimate;
2044 singleDifferences[satSigId].at(obsType).measurement = roverObs.measurement - baseObs.measurement;
2046 LOG_DATA(
"{}: [{}][{:11}][Meas] r {:.3f} - b {:.3f} = {:.3f}",
nameId(), satSigId, obsType,
2047 roverObs.measurement, baseObs.measurement, singleDifferences[satSigId][obsType].measurement);
2048 LOG_DATA(
"{}: [{}][{:11}][Est ] r {:.3f} - b {:.3f} = {:.3f} (diff to meas {:.3e})",
nameId(), satSigId, obsType,
2049 roverObs.estimate, baseObs.estimate, singleDifferences[satSigId][obsType].estimate,
2050 singleDifferences[satSigId][obsType].measurement - singleDifferences[satSigId][obsType].estimate);
2054 return singleDifferences;
2060 if (singleDifferences.empty()) {
return doubleDifferences; }
2061 doubleDifferences.reserve(singleDifferences.size() - 1);
2064 for (
const auto& [satSigId_s, singleDiff_s] : singleDifferences)
2066 for (
const auto& [obsType, sDiff_s] : singleDiff_s)
2070 LOG_DATA(
"{}: [{}] Not calculating double difference for [{} {}] because no pivot satellite.",
nameId(),
_receiver[
Rover].gnssObs->insTime.toYMDHMS(
GPST), satSigId_s.code, obsType);
2073 const auto& satSigId_1 =
_pivotSatellites.at({ satSigId_s.code, obsType }).satSigId;
2074 if (satSigId_s == satSigId_1) {
continue; }
2076 if (!singleDifferences.contains(satSigId_1))
2081 if (!singleDifferences.at(satSigId_1).contains(obsType))
2083 LOG_WARN(
"{}: [{}] Pivot [{}] has no observation type [{}]. This is a bug.",
nameId(),
_receiver[
Rover].gnssObs->insTime.toYMDHMS(
GPST), satSigId_1, obsType);
2087 const auto& singleDiff_1 = singleDifferences.at(satSigId_1);
2089 const auto& sDiff_1 = singleDiff_1.at(obsType);
2091 doubleDifferences[satSigId_s][obsType].estimate = sDiff_s.estimate - sDiff_1.estimate;
2092 doubleDifferences[satSigId_s].at(obsType).measurement = sDiff_s.measurement - sDiff_1.measurement;
2093 LOG_DATA(
"{}: [{} - {}][{:11}][Meas] sat {:.3f} - piv {:.3f} = {:.3f}",
nameId(), satSigId_s, satSigId_1, obsType,
2094 sDiff_s.measurement, sDiff_1.measurement, doubleDifferences[satSigId_s][obsType].measurement);
2098 double lambda_j_1 =
InsConst::C / satSigId_1.freq().getFrequency(observations.
signals.at(satSigId_1).freqNum());
2100 doubleDifferences[satSigId_s].at(obsType).estimate += lambda_j_1 * N_br_1s;
2102 LOG_DATA(
"{}: [{} - {}][{:11}][Est ] sat {:.3f} - piv {:.3f} = {:.3f} (diff to meas {:.3e})",
nameId(), satSigId_s, satSigId_1, obsType,
2103 sDiff_s.estimate, sDiff_1.estimate, doubleDifferences[satSigId_s][obsType].estimate,
2104 doubleDifferences[satSigId_s][obsType].measurement - doubleDifferences[satSigId_s][obsType].estimate);
2108 return doubleDifferences;
2115 for (
const auto& [satSigId, observation] : observations.signals)
2117 for (
const auto& [obsType, baseObs] : observation.recvObs.at(
Base)->obs)
2119 measKeys[obsType].emplace_back(satSigId,
Rover, obsType);
2120 measKeys[obsType].emplace_back(satSigId,
Base, obsType);
2125 for (
const auto& [obsType, keys] : measKeys)
2130 for (
const auto& [satSigId, observation] : observations.signals)
2132 const auto& baseObservations = observation.recvObs.at(
Base)->obs;
2133 const auto& roverObservations = observation.recvObs.at(
Rover)->obs;
2135 for (
const auto& [obsType, baseObs] : baseObservations)
2137 const auto& roverObs = roverObservations.at(obsType);
2142 Rtilde.at(obsType)(roverKey, roverKey) = roverObs.measVar;
2143 Rtilde.at(obsType)(baseKey, baseKey) = baseObs.measVar;
2160 std::vector<Meas::MeasKeyTypes> measKeys;
2161 measKeys.reserve(doubleDifferences.size() *
_obsFilter.getUsedObservationTypes().size());
2166 for (
const auto& [satSigId, doubleDiff] : doubleDifferences)
2168 for (
const auto& [obsType, diff] : doubleDiff)
2170 if (obsType == oType)
2196 for (
const auto& [obsType, R] : Rtilde)
2198 std::vector<Meas::MeasKeyTypes> obsTypeMeasKeys;
2199 for (
const auto& key : measKeys)
2201 if (std::holds_alternative<Meas::PsrDD>(key) && obsType ==
GnssObs::Pseudorange) { obsTypeMeasKeys.push_back(key); }
2202 else if (std::holds_alternative<Meas::CarrierDD>(key) && obsType ==
GnssObs::Carrier) { obsTypeMeasKeys.push_back(key); }
2203 else if (std::holds_alternative<Meas::DopplerDD>(key) && obsType ==
GnssObs::Doppler) { obsTypeMeasKeys.push_back(key); }
2206 obsTypeMeasKeys, R.rowKeys());
2208 for (
const auto& rowKey : obsTypeMeasKeys)
2214 satSigId_s = std::get<Meas::PsrDD>(rowKey).satSigId;
2217 satSigId_s = std::get<Meas::CarrierDD>(rowKey).satSigId;
2220 satSigId_s = std::get<Meas::DopplerDD>(rowKey).satSigId;
2240 for (
const auto& [satSigId_s, doubleDiff] : doubleDifferences)
2242 const auto& obs_s = observations.
signals.at(satSigId_s);
2244 double lambda_j =
InsConst::C / satSigId_s.freq().getFrequency(obs_s.freqNum());
2248 for (
const auto& [obsType, obs] : doubleDiff)
2250 const auto& satSigId_1 =
_pivotSatellites.at({ satSigId_s.code, obsType }).satSigId;
2251 const auto& obs_1 = observations.
signals.at(satSigId_1);
2272 -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(),
2273 -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(),
2274 -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());
2295 if (normInno(
all).hasNaN())
2297 LOG_ERROR(
"{}: NIS check has NaN values. Skipping check this epoch.",
nameId());
2303 LOG_DATA(
"{}: normInno = \n{}",
nameId(), normInno.transposed());
2312 auto code = pivotCodeObsType.first;
2313 auto obsType = pivotCodeObsType.second;
2317 if (
const auto* meas = std::get_if<Meas::PsrDD>(&key))
2320 satSigId = meas->satSigId;
2322 else if (
const auto* meas = std::get_if<Meas::CarrierDD>(&key))
2325 satSigId = meas->satSigId;
2327 else if (
const auto* meas = std::get_if<Meas::DopplerDD>(&key))
2330 satSigId = meas->satSigId;
2332 return satSigId.
code != code || obsType2 != obsType;
2336 for (
const auto& key :
_kalmanFilter.savedPreUpdate().z.rowKeys())
2338 if (filterMeasurements(key)) {
continue; }
2342 if (amount <= 1) {
continue; }
2343 mean /=
static_cast<double>(amount);
2345 double variance = 0.0;
2346 for (
const auto& key :
_kalmanFilter.savedPreUpdate().z.rowKeys())
2348 if (filterMeasurements(key)) {
continue; }
2349 variance += std::pow(std::abs(
_kalmanFilter.savedPreUpdate().z(key) - mean), 2);
2351 variance *= 1.0 / (
static_cast<double>(amount) - 1.0);
2353 LOG_DATA(
"{}: pivot [{}][{}]: {:.3f} / {:.3f} = {:.3f}",
nameId(), code, obsType, mean, std::sqrt(variance), mean / std::sqrt(variance));
2355 if (std::abs(mean / std::sqrt(variance)) > 5.0)
2372 faultyMeas.
satSigId = pivot.satSigId;
2381 normInno(
all).maxCoeff(&maxIdx);
2382 faultyMeas.
key = normInno.
rowKeys().at(
static_cast<size_t>(maxIdx));
2383 LOG_DATA(
"{}: Largest post-fit innovation: {} ({:.1f})",
nameId(), faultyMeas.
key, normInno(faultyMeas.
key));
2385 if (
const auto* meas = std::get_if<Meas::PsrDD>(&faultyMeas.
key))
2388 faultyMeas.
satSigId = meas->satSigId;
2390 else if (
const auto* meas = std::get_if<Meas::CarrierDD>(&faultyMeas.
key))
2393 faultyMeas.
satSigId = meas->satSigId;
2395 else if (
const auto* meas = std::get_if<Meas::DopplerDD>(&faultyMeas.
key))
2398 faultyMeas.
satSigId = meas->satSigId;
2403 if (pivot.satSigId == faultyMeas.
satSigId)
2414 addEventToGui(rtkSol,
"Stopped doing NIS check, as minimum amount of pseudorange observables reached.");
2418 std::vector<RealTimeKinematic::OutlierInfo> faulty{ faultyMeas };
2423 faulty.push_back(faultyMeas);
2430 faulty.push_back(faultyMeas);
2432 faulty.back().key =
Meas::PsrDD{ faulty.back().satSigId };
2434 if (faulty.size() == 2)
2436 if (
auto pivotKey = std::make_pair(faulty.back().satSigId.code, faulty.back().obsType);
2443 faulty.back().pivot.reset();
2448 for (
const auto& faultyMeas : faulty)
2452 if (faultyMeas.
pivot)
2467 addEventToGui(rtkSol, fmt::format(
"State [{}] removed (because outlier detected in carrier measurement [{}])", ambStateKey, faultyMeas.
key));
2478 addEventToGui(rtkSol, fmt::format(
"Erasing receiver observation [{}][{}], because outlier (NIS check)", faultyMeas.
satSigId, faultyMeas.
obsType));
2481 rtkSol->nisRemovedCnt++;
2492 solutionValid.
valid =
false;
2497 solutionValid.
threshold = std::max({ 50.0,
2514 if (solutionValid.
valid)
2525 ? std::string(
"Update rejected - no observations left")
2526 : fmt::format(
"Update rejected - position change due to update is {:.2f} [m] > {:.2f} [m]", solutionValid.
dx, solutionValid.
threshold);
2533 return solutionValid;
2538 std::vector<States::StateKeyType> ambKeys;
2539 std::vector<Meas::MeasKeyTypes> ambMeasKeys;
2542 if (
const auto* ambDD = std::get_if<States::AmbiguityDD>(&
_kalmanFilter.x.rowKeys().at(i)))
2544 ambKeys.emplace_back(*ambDD);
2545 ambMeasKeys.emplace_back(*ambDD);
2548 size_t nAmbs = ambKeys.size();
2552 LOG_TRACE(
"{}: [{}] Not fixing ambiguities because only {} satellites but minimum {} needed.",
2556 addEventToGui(rtkSol, fmt::format(
"Not fixing ambiguities anymore because\nonly {} satellites but minimum {} needed.", nCarrierMeasUniqueSatellite,
_nMinSatForAmbFix));
2565 addEventToGui(rtkSol, fmt::format(
"Resuming ambiguity fixing as\nminimum amount of satellites reached."));
2571 LOG_TRACE(
"{}: [{}] Not fixing ambiguities because position variance is {:.4f}m which is higher than {:.4f}m",
2575 addEventToGui(rtkSol, fmt::format(
"Not fixing ambiguities anymore because\nposition variance is {:.4f}m^2 which is higher than {:.4f}m^2.", posVar,
_maxPosVar));
2583 addEventToGui(rtkSol, fmt::format(
"Resuming ambiguity fixing as\nposition variance smaller than limit."));
2586 if (ambKeys.empty()) {
return false; }
2590#if LOG_LEVEL <= LOG_LEVEL_DATA
2592 std::set<SatSigId> ambStates;
2593 for (
const auto& key : ambKeys)
2595 auto amb = std::get<States::AmbiguityDD>(key);
2596 if (ambStates.contains(amb.satSigId))
2600 ambStates.insert(amb.satSigId);
2602 LOG_DATA(
"{}: ambKeys: [{}]",
nameId(), fmt::join(ambStates,
", "));
2604 std::set<SatSigId> ambHold;
2607 if (ambHold.contains(hold.first))
2611 ambHold.insert(hold.first);
2613 LOG_DATA(
"{}: ambHold: [{}]",
nameId(), fmt::join(ambHold,
", "));
2615 if (ambStates != ambHold)
2617 std::vector<SatSigId> diff;
2618 std::set_difference(ambStates.begin(), ambStates.end(), ambHold.begin(), ambHold.end(), std::back_inserter(diff));
2619 LOG_DATA(
"{}: Amb not in hold: [{}]",
nameId(), fmt::join(diff,
", "));
2621 std::set_difference(ambHold.begin(), ambHold.end(), ambStates.begin(), ambStates.end(), std::back_inserter(diff));
2622 LOG_DATA(
"{}: Amb not in states: [{}]",
nameId(), fmt::join(diff,
", "));
2626 Eigen::VectorXd fixedAmb = Eigen::VectorXd::Zero(
static_cast<int>(ambKeys.size()));
2628 for (
size_t k = 0; k < ambKeys.size(); k++)
2630 const auto& key = ambKeys.at(k);
2631 auto satSigId = std::get<States::AmbiguityDD>(key).satSigId;
2636 rtkSol->nAmbiguitiesFixed = ambKeys.size() + 1;
2642 addEventToGui(rtkSol, fmt::format(
"Resuming ambiguity holding as\nminimum amount of satellites reached."));
2649 LOG_TRACE(
"{}: [{}] Not holding ambiguities because only {} satellites but minimum {} needed.",
2653 addEventToGui(rtkSol, fmt::format(
"Not holding ambiguities anymore because\nonly {} satellites but minimum {} needed.", nCarrierMeasUniqueSatellite,
_nMinSatForAmbHold));
2660 size_t partialFixTries = 0;
2668 rtkSol->ambiguityResolutionFailure = fixed.failure;
2669 rtkSol->ambiguityCriticalValueRatio = fixed.ambiguityCriticalValueRatio;
2690 rtkSol->ambiguityCriticalValueRatio = fixed.ambiguityCriticalValueRatio;
2691 rtkSol->nAmbiguitiesFixed = fixed.nFixed + 1;
2693 for (
const auto& key_ : ambKeys)
2695 const auto key = std::get<States::AmbiguityDD>(key_);
2700 && fixed.nFixed == nAmbs && partialFixTries == 0)
2702 LOG_WARN(
"{}: Ambiguity [{}] changed from {} to {} (despite being FixAndHold)",
nameId(), key.satSigId,
2708 return partialFixTries == 0;
2710 LOG_DATA(
"{}: Fixing failed. partialFixTries = {}",
nameId(), partialFixTries);
2715 _kalmanFilter.P(ambKeys, ambKeys).diagonal().maxCoeff(&maxAmb);
2717 LOG_DATA(
"{}: [{}] Trying partial fixing by not fixing [{}] with highest variance of {}",
2719 ambKeys.at(
static_cast<size_t>(maxAmb)),
2720 _kalmanFilter.P(ambKeys.at(
static_cast<size_t>(maxAmb)), ambKeys.at(
static_cast<size_t>(maxAmb))));
2721 floatKeys.push_back(ambKeys.at(
static_cast<size_t>(maxAmb)));
2722 ambKeys.erase(std::next(ambKeys.begin(), maxAmb));
2723 ambMeasKeys.erase(std::next(ambMeasKeys.begin(), maxAmb));
2739#if LOG_LEVEL <= LOG_LEVEL_DATA
2740 if (((
_kalmanFilter.x(ambKeys).array() * 1e2).round() * 1e-2).matrix() != fixedAmb)
2743 (Eigen::MatrixXd(
static_cast<int>(ambKeys.size()), 2) <<
_kalmanFilter.x(ambKeys), fixedAmb).finished(),
2744 ambKeys, std::vector<States::StateKeyType>{ States::AmbiguityDD(SatSigId(Code::G1C, 200)), States::AmbiguityDD(SatSigId(Code::G1C, 201)) });
2745 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)
ankerl::unordered_dense::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.
std::string nameId() const
Node name and id.
std::string name
Name of the Node.
void invokeCallbacks(size_t portIndex, const std::shared_ptr< const NodeData > &data)
Calls all registered callbacks on the specified output port.
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.
OutputPin * CreateOutputPin(Node *node, 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.
bool DeleteInputPin(InputPin &pin)
Deletes the input pin. Invalidates the pin reference given.
InputPin * CreateInputPin(Node *node, 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.
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.