133 ImGui::TextUnformatted(
"Create combinations of GNSS measurements by adding or subtracting signals.");
135 std::vector<size_t> combToDelete;
140 bool keepCombination =
true;
141 if (ImGui::CollapsingHeader(fmt::format(
"Combination {}##id{}", c,
size_t(
id)).c_str(),
142 _combinations.size() > 1 ? &keepCombination :
nullptr, ImGuiTreeNodeFlags_DefaultOpen))
144 if (ImGui::Button(fmt::format(
"Add Term##id{} c{}",
size_t(
id), c).c_str()))
147 comb.terms.emplace_back();
148 if (comb.terms.size() != 2) { comb.polynomialCycleSlipDetector.setEnabled(
false); }
153 ImGui::SetNextItemWidth(100.0F);
154 if (ImGui::Combo(fmt::format(
"Output unit##id{} c{}",
size_t(
id), c).c_str(), &selected,
"Meters\0Cycles\0\0"))
161 if (ImGui::Button(fmt::format(
"Cycle-slip detector##id{} c{}",
size_t(
id), c).c_str()))
163 ImGui::OpenPopup(fmt::format(
"Cycle-slip detector##Popup - id{} c{}",
size_t(
id), c).c_str());
165 if (ImGui::BeginPopup(fmt::format(
"Cycle-slip detector##Popup - id{} c{}",
size_t(
id), c).c_str()))
167 constexpr float WIDTH = 145.0F;
169 comb.polynomialCycleSlipDetector, WIDTH))
174 ImGui::SetNextItemWidth(WIDTH);
175 if (
double val = comb.polynomialCycleSlipDetectorThresholdPercentage * 100.0;
176 ImGui::DragDouble(fmt::format(
"Threshold##id{} c{}",
size_t(
id), c).c_str(), &val, 1.0F,
177 1.0, std::numeric_limits<double>::max(),
"%.2f %%"))
179 comb.polynomialCycleSlipDetectorThresholdPercentage = val / 100.0;
183 std::string description =
"As percentage of the smallest wavelength of the combination terms.";
185 return a.satSigId.freq().getFrequency(a.freqNum) < b.satSigId.freq().getFrequency(b.freqNum);
187 maxF != comb.terms.end())
189 double lambda =
InsConst::C / maxF->satSigId.freq().getFrequency(maxF->freqNum);
190 double threshold = comb.polynomialCycleSlipDetectorThresholdPercentage * lambda;
191 description += fmt::format(
"\nFor [{} {}] the wavelength is λ = {:.3f} [m].\nThe threshold is then {:.3f} [m].",
192 maxF->satSigId.toSatId().satSys, maxF->satSigId.freq(), lambda, threshold);
196 if (!comb.polynomialCycleSlipDetector.isEnabled()) { ImGui::BeginDisabled(); }
197 if (ImGui::Checkbox(fmt::format(
"Output when insufficient points##id{} c{}",
size_t(
id), c).c_str(), &comb.polynomialCycleSlipDetectorOutputWhenWindowSizeNotReached))
201 if (ImGui::Checkbox(fmt::format(
"Output polynomials##id{} c{}",
size_t(
id), c).c_str(), &comb.polynomialCycleSlipDetectorOutputPolynomials))
205 if (!comb.polynomialCycleSlipDetector.isEnabled()) { ImGui::EndDisabled(); }
210 std::vector<size_t> termToDelete;
211 if (ImGui::BeginTable(fmt::format(
"##Table id{} c{}",
size_t(
id), c).c_str(), 3 *
static_cast<int>(comb.terms.size()) + 1,
212 ImGuiTableFlags_NoHostExtendX | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_ScrollX,
215 ImGui::TableNextRow();
217 for (
size_t t = 0; t < comb.terms.size(); t++)
219 auto& term = comb.terms.at(t);
221 ImGui::TableSetColumnIndex(
static_cast<int>(t) * 3);
222 int selected = term.sign == 1 ? 0 : 1;
223 ImGui::SetNextItemWidth(50.0F);
224 if (ImGui::Combo(fmt::format(
"##Sign id{} c{} t{}",
size_t(
id), c, t).c_str(), &selected,
"+1\0-1\0\0"))
227 term.sign = selected == 0 ? +1 : -1;
230 ImGui::TableSetColumnIndex(
static_cast<int>(t) * 3 + 1);
232 ImGui::SetNextItemWidth(62.0F);
233 if (ImGui::Combo(fmt::format(
"##ObsType id{} c{} t{}",
size_t(
id), c, t).c_str(), &selected, comb.unit ==
Combination::Unit::Cycles ?
"P\0Φ\0\0" :
"p\0φ\0\0"))
239 ImGui::TableSetColumnIndex(
static_cast<int>(t) * 3 + 2);
240 ImGui::SetNextItemWidth(62.0F);
241 if (
ShowCodeSelector(fmt::format(
"##Code id{} c{} t{}",
size_t(
id), c, t).c_str(), term.satSigId.code,
Freq_All,
true))
246 ImGui::Dummy(ImVec2(10.0F, 0.0F));
248 ImGui::TableNextColumn();
249 ImGui::TextUnformatted(
"= Combined Frequency");
251 ImGui::TableNextRow();
252 for (
size_t t = 0; t < comb.terms.size(); t++)
254 auto& term = comb.terms.at(t);
256 ImGui::TableSetColumnIndex(
static_cast<int>(t) * 3);
257 ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 8.0F);
259 ImGui::GetWindowDrawList()->AddCircleFilled(ImGui::GetCursorScreenPos() + ImVec2(radius, ImGui::GetTextLineHeight() / 2.0F + 2.0F), radius,
260 term.receivedDuringRun ? IM_COL32(0, 255, 0, 255) : IM_COL32(255, 0, 0, 255));
261 ImGui::Dummy(ImVec2(radius * 2.0F, ImGui::GetTextLineHeight()));
262 if (ImGui::IsItemHovered()) { ImGui::SetTooltip(term.receivedDuringRun ?
"Signal was received" :
"Signal was not received"); }
264 if (comb.terms.size() > 1)
267 if (ImGui::Button(fmt::format(
"X##id{} c{} t{}",
size_t(
id), c, t).c_str()))
270 termToDelete.push_back(t);
272 if (ImGui::IsItemHovered()) { ImGui::SetTooltip(
"Remove Term?"); }
275 ImGui::TableSetColumnIndex(
static_cast<int>(t) * 3 + 1);
276 double f = term.satSigId.freq().getFrequency(term.freqNum);
277 ImGui::TextUnformatted(fmt::format(
"{:.2f}", f * 1e-6).c_str());
278 if (ImGui::IsItemHovered()) { ImGui::SetTooltip(
"%s", fmt::format(
"Frequency in [Mhz]\nλ = {:.3f}m",
InsConst::C / f).c_str()); }
280 ImGui::TableSetColumnIndex(
static_cast<int>(t) * 3 + 2);
281 ImGui::SetNextItemWidth(62.0F);
282 SatId satId =
SatId(term.satSigId.toSatId().satSys, term.satSigId.satNum);
285 term.satSigId.satNum = satId.
satNum;
289 ImGui::TableNextColumn();
290 double f = comb.calcCombinationFrequency();
291 ImGui::TextUnformatted(fmt::format(
" {:.2f} MHz", f * 1e-6).c_str());
292 if (ImGui::IsItemHovered()) { ImGui::SetTooltip(
"%s", fmt::format(
"λ = {:.3f}m",
InsConst::C / f).c_str()); }
297 for (
const auto& t : termToDelete) { comb.terms.erase(std::next(comb.terms.begin(),
static_cast<std::ptrdiff_t
>(t))); }
300 if (!keepCombination) { combToDelete.push_back(c); }
305 if (ImGui::Button(fmt::format(
"Add Combination##id{}",
size_t(
id)).c_str()))
309 _combinations.back().polynomialCycleSlipDetector.setResetAfterCycleSlip(
false);
366 auto gnssObs = std::static_pointer_cast<const GnssObs>(queue.
extract_front());
367 LOG_DATA(
"{}: Received GnssObs for [{}]",
nameId(), gnssObs->insTime);
369 auto gnssComb = std::make_shared<GnssCombination>();
370 gnssComb->insTime = gnssObs->insTime;
379 if (i == c) {
continue; }
382 combination.
description += fmt::format(
" - {}", c);
389 double lambdaMin = 100.0;
390 size_t termsFound = 0;
391 for (
auto& term : comb.terms)
394 oTerm.
sign = term.sign;
401 double freq = term.satSigId.freq().getFrequency(term.freqNum);
403 lambdaMin = std::min(lambdaMin, lambda);
405 if (
auto obs = (*gnssObs)(term.satSigId))
409 if (
auto psr = obs->get().pseudorange)
411 term.receivedDuringRun =
true;
413 double value = psr->value;
414 result +=
static_cast<double>(term.sign) * value;
426 LOG_DATA(
"{}: Resetting cycle-slip detector as no pseudorange in measurement",
nameId());
427 comb.polynomialCycleSlipDetector.reset(comb.description());
432 if (
auto carrier = obs->get().carrierPhase)
434 term.receivedDuringRun =
true;
436 double value = carrier->value * lambda;
437 result +=
static_cast<double>(term.sign) * value;
449 LOG_DATA(
"{}: Resetting cycle-slip detector as no carrier-phase in measurement",
nameId());
450 comb.polynomialCycleSlipDetector.reset(comb.description());
455 combination.
terms.push_back(oTerm);
457 LOG_DATA(
"{}: Found {}/{}",
nameId(), termsFound, comb.terms.size());
458 if (termsFound == comb.terms.size())
460 auto lambda =
InsConst::C / comb.calcCombinationFrequency();
461 double resultCycles = result / lambda;
464 if (comb.polynomialCycleSlipDetector.isEnabled())
466 auto key = comb.description();
467 LOG_DATA(
"{}: Polynomial {}/{} data points ({} needed for calculation)",
nameId(),
468 comb.polynomialCycleSlipDetector.getDataSize(key), comb.polynomialCycleSlipDetector.getWindowSize(),
469 comb.polynomialCycleSlipDetector.getPolynomialDegree() + 1);
470 combination.
cycleSlipPrediction = comb.polynomialCycleSlipDetector.predictValue(key, gnssComb->insTime);
477 if (comb.polynomialCycleSlipDetectorOutputPolynomials)
479 if (
auto polynomial = comb.polynomialCycleSlipDetector.calcPolynomial(key))
481 comb.polynomials.emplace_back(gnssComb->insTime, *polynomial);
483 if (
auto relTime = comb.polynomialCycleSlipDetector.calcRelativeTime(key, gnssComb->insTime))
485 for (
const auto& poly : comb.polynomials)
487 double value = poly.second.f(*relTime);
488 LOG_DATA(
"f({:.2f}) = {:.2f} ({})", *relTime, value, poly.second.toString());
493 double threshold = comb.polynomialCycleSlipDetectorThresholdPercentage * lambdaMin;
497 combination.
cycleSlipResult = comb.polynomialCycleSlipDetector.checkForCycleSlip(key, gnssComb->insTime, *combination.
result, threshold);
499 if (!comb.polynomialCycleSlipDetectorOutputWhenWindowSizeNotReached
508 gnssComb->combinations.push_back(combination);