135 ImGui::TextUnformatted(
"Create combinations of GNSS measurements by adding or subtracting signals.");
137 std::vector<size_t> combToDelete;
142 bool keepCombination =
true;
143 if (ImGui::CollapsingHeader(fmt::format(
"Combination {}##id{}", c,
size_t(
id)).c_str(),
144 _combinations.size() > 1 ? &keepCombination :
nullptr, ImGuiTreeNodeFlags_DefaultOpen))
146 if (ImGui::Button(fmt::format(
"Add Term##id{} c{}",
size_t(
id), c).c_str()))
149 comb.terms.emplace_back();
150 if (comb.terms.size() != 2) { comb.polynomialCycleSlipDetector.setEnabled(
false); }
155 ImGui::SetNextItemWidth(100.0F);
156 if (ImGui::Combo(fmt::format(
"Output unit##id{} c{}",
size_t(
id), c).c_str(), &selected,
"Meters\0Cycles\0\0"))
163 if (ImGui::Button(fmt::format(
"Cycle-slip detector##id{} c{}",
size_t(
id), c).c_str()))
165 ImGui::OpenPopup(fmt::format(
"Cycle-slip detector##Popup - id{} c{}",
size_t(
id), c).c_str());
167 if (ImGui::BeginPopup(fmt::format(
"Cycle-slip detector##Popup - id{} c{}",
size_t(
id), c).c_str()))
169 constexpr float WIDTH = 145.0F;
171 comb.polynomialCycleSlipDetector, WIDTH))
176 ImGui::SetNextItemWidth(WIDTH);
177 if (
double val = comb.polynomialCycleSlipDetectorThresholdPercentage * 100.0;
178 ImGui::DragDouble(fmt::format(
"Threshold##id{} c{}",
size_t(
id), c).c_str(), &val, 1.0F,
179 1.0, std::numeric_limits<double>::max(),
"%.2f %%"))
181 comb.polynomialCycleSlipDetectorThresholdPercentage = val / 100.0;
185 std::string description =
"As percentage of the smallest wavelength of the combination terms.";
187 return a.satSigId.freq().getFrequency(a.freqNum) < b.satSigId.freq().getFrequency(b.freqNum);
189 maxF != comb.terms.end())
191 double lambda =
InsConst::C / maxF->satSigId.freq().getFrequency(maxF->freqNum);
192 double threshold = comb.polynomialCycleSlipDetectorThresholdPercentage * lambda;
193 description += fmt::format(
"\nFor [{} {}] the wavelength is λ = {:.3f} [m].\nThe threshold is then {:.3f} [m].",
194 maxF->satSigId.toSatId().satSys, maxF->satSigId.freq(), lambda, threshold);
198 if (!comb.polynomialCycleSlipDetector.isEnabled()) { ImGui::BeginDisabled(); }
199 if (ImGui::Checkbox(fmt::format(
"Output when insufficient points##id{} c{}",
size_t(
id), c).c_str(), &comb.polynomialCycleSlipDetectorOutputWhenWindowSizeNotReached))
203 if (ImGui::Checkbox(fmt::format(
"Output polynomials##id{} c{}",
size_t(
id), c).c_str(), &comb.polynomialCycleSlipDetectorOutputPolynomials))
207 if (!comb.polynomialCycleSlipDetector.isEnabled()) { ImGui::EndDisabled(); }
212 std::vector<size_t> termToDelete;
213 if (ImGui::BeginTable(fmt::format(
"##Table id{} c{}",
size_t(
id), c).c_str(), 3 *
static_cast<int>(comb.terms.size()) + 1,
214 ImGuiTableFlags_NoHostExtendX | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_ScrollX,
217 ImGui::TableNextRow();
219 for (
size_t t = 0; t < comb.terms.size(); t++)
221 auto& term = comb.terms.at(t);
223 ImGui::TableSetColumnIndex(
static_cast<int>(t) * 3);
224 int selected = term.sign == 1 ? 0 : 1;
225 ImGui::SetNextItemWidth(50.0F);
226 if (ImGui::Combo(fmt::format(
"##Sign id{} c{} t{}",
size_t(
id), c, t).c_str(), &selected,
"+1\0-1\0\0"))
229 term.sign = selected == 0 ? +1 : -1;
232 ImGui::TableSetColumnIndex(
static_cast<int>(t) * 3 + 1);
234 ImGui::SetNextItemWidth(62.0F);
235 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"))
241 ImGui::TableSetColumnIndex(
static_cast<int>(t) * 3 + 2);
242 ImGui::SetNextItemWidth(62.0F);
243 if (
ShowCodeSelector(fmt::format(
"##Code id{} c{} t{}",
size_t(
id), c, t).c_str(), term.satSigId.code,
Freq_All,
true))
248 ImGui::Dummy(ImVec2(10.0F, 0.0F));
250 ImGui::TableNextColumn();
251 ImGui::TextUnformatted(
"= Combined Frequency");
253 ImGui::TableNextRow();
254 for (
size_t t = 0; t < comb.terms.size(); t++)
256 auto& term = comb.terms.at(t);
258 ImGui::TableSetColumnIndex(
static_cast<int>(t) * 3);
259 ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 8.0F);
261 ImGui::GetWindowDrawList()->AddCircleFilled(ImGui::GetCursorScreenPos() + ImVec2(radius, ImGui::GetTextLineHeight() / 2.0F + 2.0F), radius,
262 term.receivedDuringRun ? IM_COL32(0, 255, 0, 255) : IM_COL32(255, 0, 0, 255));
263 ImGui::Dummy(ImVec2(radius * 2.0F, ImGui::GetTextLineHeight()));
264 if (ImGui::IsItemHovered()) { ImGui::SetTooltip(term.receivedDuringRun ?
"Signal was received" :
"Signal was not received"); }
266 if (comb.terms.size() > 1)
269 if (ImGui::Button(fmt::format(
"X##id{} c{} t{}",
size_t(
id), c, t).c_str()))
272 termToDelete.push_back(t);
274 if (ImGui::IsItemHovered()) { ImGui::SetTooltip(
"Remove Term?"); }
277 ImGui::TableSetColumnIndex(
static_cast<int>(t) * 3 + 1);
278 double f = term.satSigId.freq().getFrequency(term.freqNum);
279 ImGui::TextUnformatted(fmt::format(
"{:.2f}", f * 1e-6).c_str());
280 if (ImGui::IsItemHovered()) { ImGui::SetTooltip(
"%s", fmt::format(
"Frequency in [Mhz]\nλ = {:.3f}m",
InsConst::C / f).c_str()); }
282 ImGui::TableSetColumnIndex(
static_cast<int>(t) * 3 + 2);
283 ImGui::SetNextItemWidth(62.0F);
284 SatId satId =
SatId(term.satSigId.toSatId().satSys, term.satSigId.satNum);
287 term.satSigId.satNum = satId.
satNum;
291 ImGui::TableNextColumn();
292 double f = comb.calcCombinationFrequency();
293 ImGui::TextUnformatted(fmt::format(
" {:.2f} MHz", f * 1e-6).c_str());
294 if (ImGui::IsItemHovered()) { ImGui::SetTooltip(
"%s", fmt::format(
"λ = {:.3f}m",
InsConst::C / f).c_str()); }
299 for (
const auto& t : termToDelete) { comb.terms.erase(std::next(comb.terms.begin(),
static_cast<std::ptrdiff_t
>(t))); }
302 if (!keepCombination) { combToDelete.push_back(c); }
307 if (ImGui::Button(fmt::format(
"Add Combination##id{}",
size_t(
id)).c_str()))
367 auto gnssObs = std::static_pointer_cast<const GnssObs>(queue.
extract_front());
368 LOG_DATA(
"{}: Received GnssObs for [{}]",
nameId(), gnssObs->insTime);
370 auto gnssComb = std::make_shared<GnssCombination>();
371 gnssComb->insTime = gnssObs->insTime;
380 if (i == c) {
continue; }
383 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;
400 double freq = term.satSigId.freq().getFrequency(term.freqNum);
402 lambdaMin = std::min(lambdaMin, lambda);
404 if (
auto obs = (*gnssObs)(term.satSigId))
408 if (
auto psr = obs->get().pseudorange)
410 term.receivedDuringRun =
true;
412 double value = psr->value;
413 result +=
static_cast<double>(term.sign) * value;
425 comb.polynomialCycleSlipDetector.reset(comb.description());
430 if (
auto carrier = obs->get().carrierPhase)
432 term.receivedDuringRun =
true;
434 double value = carrier->value * lambda;
435 result +=
static_cast<double>(term.sign) * value;
447 comb.polynomialCycleSlipDetector.reset(comb.description());
452 combination.
terms.push_back(oTerm);
454 if (termsFound == comb.terms.size())
456 auto lambda =
InsConst::C / comb.calcCombinationFrequency();
457 double resultCycles = result / lambda;
460 if (comb.polynomialCycleSlipDetector.isEnabled())
462 auto key = comb.description();
463 combination.
cycleSlipPrediction = comb.polynomialCycleSlipDetector.predictValue(key, gnssComb->insTime);
469 if (comb.polynomialCycleSlipDetectorOutputPolynomials)
471 if (
auto polynomial = comb.polynomialCycleSlipDetector.calcPolynomial(key))
473 comb.polynomials.emplace_back(gnssComb->insTime, *polynomial);
475 if (
auto relTime = comb.polynomialCycleSlipDetector.calcRelativeTime(key, gnssComb->insTime))
477 for (
const auto& poly : comb.polynomials)
479 double value = poly.second.f(*relTime);
480 LOG_DATA(
"f({:.2f}) = {:.2f} ({})", *relTime, value, poly.second.toString());
485 double threshold = comb.polynomialCycleSlipDetectorThresholdPercentage * lambdaMin;
488 combination.
cycleSlipResult = comb.polynomialCycleSlipDetector.checkForCycleSlip(key, gnssComb->insTime, *combination.
result, threshold);
489 if (!comb.polynomialCycleSlipDetectorOutputWhenWindowSizeNotReached
498 gnssComb->combinations.push_back(combination);