INSTINCT Code Coverage Report


Directory: src/
File: Nodes/DataProcessor/GNSS/GnssAnalyzer.cpp
Date: 2025-11-25 23:34:18
Exec Total Coverage
Lines: 13 264 4.9%
Functions: 4 16 25.0%
Branches: 15 478 3.1%

Line Branch Exec Source
1 // This file is part of INSTINCT, the INS Toolkit for Integrated
2 // Navigation Concepts and Training by the Institute of Navigation of
3 // the University of Stuttgart, Germany.
4 //
5 // This Source Code Form is subject to the terms of the Mozilla Public
6 // License, v. 2.0. If a copy of the MPL was not distributed with this
7 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
8
9 #include "GnssAnalyzer.hpp"
10
11 #include <imgui_internal.h>
12
13 #include "util/Logger.hpp"
14
15 #include "internal/FlowManager.hpp"
16
17 #include "internal/gui/widgets/HelpMarker.hpp"
18
19 #include "Navigation/Constants.hpp"
20 #include "Navigation/GNSS/Core/Code.hpp"
21 #include "Navigation/GNSS/Core/SatelliteIdentifier.hpp"
22
23 #include "NodeData/GNSS/GnssObs.hpp"
24 #include "NodeData/GNSS/GnssCombination.hpp"
25
26 namespace NAV
27 {
28
29 /// @brief Write info to a json object
30 /// @param[out] j Json output
31 /// @param[in] data Object to read info from
32 ✗ void to_json(json& j, const GnssAnalyzer::Combination::Term& data)
33 {
34 ✗ j = json{
35 ✗ { "obsType", data.obsType },
36 ✗ { "satSigId", data.satSigId },
37 ✗ { "sign", data.sign },
38 ✗ };
39 ✗ }
40 /// @brief Read info from a json object
41 /// @param[in] j Json variable to read info from
42 /// @param[out] data Output object
43 ✗ void from_json(const json& j, GnssAnalyzer::Combination::Term& data)
44 {
45 ✗ if (j.contains("obsType")) { j.at("obsType").get_to(data.obsType); }
46 ✗ if (j.contains("satSigId")) { j.at("satSigId").get_to(data.satSigId); }
47 ✗ if (j.contains("sign")) { j.at("sign").get_to(data.sign); }
48 ✗ }
49
50 /// @brief Write info to a json object
51 /// @param[out] j Json output
52 /// @param[in] data Object to read info from
53 ✗ void to_json(json& j, const GnssAnalyzer::Combination& data)
54 {
55 ✗ j = json{
56 ✗ { "description", data.description() },
57 ✗ { "terms", data.terms },
58 ✗ { "unit", data.unit },
59 ✗ { "polynomialCycleSlipDetector", data.polynomialCycleSlipDetector },
60 ✗ { "polynomialCycleSlipDetector.thresholdPercentage", data.polynomialCycleSlipDetectorThresholdPercentage },
61 ✗ { "polynomialCycleSlipDetector.outputWhenWindowSizeNotReached", data.polynomialCycleSlipDetectorOutputWhenWindowSizeNotReached },
62 ✗ { "polynomialCycleSlipDetector.outputPolynomials", data.polynomialCycleSlipDetectorOutputPolynomials },
63 ✗ };
64 ✗ }
65 /// @brief Read info from a json object
66 /// @param[in] j Json variable to read info from
67 /// @param[out] data Output object
68 ✗ void from_json(const json& j, GnssAnalyzer::Combination& data)
69 {
70 ✗ if (j.contains("terms"))
71 {
72 ✗ j.at("terms").get_to(data.terms);
73 }
74 ✗ if (j.contains("unit"))
75 {
76 ✗ j.at("unit").get_to(data.unit);
77 }
78 ✗ if (j.contains("polynomialCycleSlipDetector"))
79 {
80 ✗ j.at("polynomialCycleSlipDetector").get_to(data.polynomialCycleSlipDetector);
81 }
82 ✗ if (j.contains("polynomialCycleSlipDetector.thresholdPercentage"))
83 {
84 ✗ j.at("polynomialCycleSlipDetector.thresholdPercentage").get_to(data.polynomialCycleSlipDetectorThresholdPercentage);
85 }
86 ✗ if (j.contains("polynomialCycleSlipDetector.outputWhenWindowSizeNotReached"))
87 {
88 ✗ j.at("polynomialCycleSlipDetector.outputWhenWindowSizeNotReached").get_to(data.polynomialCycleSlipDetectorOutputWhenWindowSizeNotReached);
89 }
90 ✗ if (j.contains("polynomialCycleSlipDetector.outputPolynomials"))
91 {
92 ✗ j.at("polynomialCycleSlipDetector.outputPolynomials").get_to(data.polynomialCycleSlipDetectorOutputPolynomials);
93 }
94 ✗ }
95
96 } // namespace NAV
97
98 114 NAV::GnssAnalyzer::GnssAnalyzer()
99
5/10
✓ Branch 1 taken 114 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 114 times.
✗ Branch 5 not taken.
✓ Branch 8 taken 114 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 114 times.
✓ Branch 11 taken 114 times.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
456 : Node(typeStatic())
100 {
101 LOG_TRACE("{}: called", name);
102
103 114 _hasConfig = true;
104 114 _guiConfigDefaultWindowSize = { 630, 410 };
105
106
4/8
✓ Branch 2 taken 114 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 114 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 114 times.
✓ Branch 10 taken 114 times.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
456 CreateOutputPin("GnssComb", Pin::Type::Flow, { NAV::GnssCombination::type() });
107
108
4/8
✓ Branch 1 taken 114 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 114 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 114 times.
✓ Branch 9 taken 114 times.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
342 CreateInputPin("GnssObs", Pin::Type::Flow, { NAV::GnssObs::type() }, &GnssAnalyzer::receiveGnssObs);
109 456 }
110
111 228 NAV::GnssAnalyzer::~GnssAnalyzer()
112 {
113 LOG_TRACE("{}: called", nameId());
114 228 }
115
116 228 std::string NAV::GnssAnalyzer::typeStatic()
117 {
118
1/2
✓ Branch 1 taken 228 times.
✗ Branch 2 not taken.
456 return "Gnss Analyzer";
119 }
120
121 ✗ std::string NAV::GnssAnalyzer::type() const
122 {
123 ✗ return typeStatic();
124 }
125
126 114 std::string NAV::GnssAnalyzer::category()
127 {
128
1/2
✓ Branch 1 taken 114 times.
✗ Branch 2 not taken.
228 return "Data Processor";
129 }
130
131 ✗ void NAV::GnssAnalyzer::guiConfig()
132 {
133 ✗ ImGui::TextUnformatted("Create combinations of GNSS measurements by adding or subtracting signals.");
134
135 ✗ std::vector<size_t> combToDelete;
136 ✗ for (size_t c = 0; c < _combinations.size(); c++)
137 {
138 ✗ auto& comb = _combinations.at(c);
139
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))
143 {
144 ✗ if (ImGui::Button(fmt::format("Add Term##id{} c{}", size_t(id), c).c_str()))
145 {
146 ✗ flow::ApplyChanges();
147 ✗ comb.terms.emplace_back();
148 ✗ if (comb.terms.size() != 2) { comb.polynomialCycleSlipDetector.setEnabled(false); }
149 }
150
151 ✗ ImGui::SameLine();
152 ✗ int selected = comb.unit == Combination::Unit::Meters ? 0 : 1;
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"))
155 {
156 ✗ flow::ApplyChanges();
157 ✗ comb.unit = selected == 0 ? Combination::Unit::Meters : Combination::Unit::Cycles;
158 }
159
160 ✗ ImGui::SameLine();
161 ✗ if (ImGui::Button(fmt::format("Cycle-slip detector##id{} c{}", size_t(id), c).c_str()))
162 {
163 ✗ ImGui::OpenPopup(fmt::format("Cycle-slip detector##Popup - id{} c{}", size_t(id), c).c_str());
164 }
165 ✗ if (ImGui::BeginPopup(fmt::format("Cycle-slip detector##Popup - id{} c{}", size_t(id), c).c_str()))
166 {
167 ✗ constexpr float WIDTH = 145.0F;
168 ✗ if (PolynomialCycleSlipDetectorGui(fmt::format("Cycle-slip detector id{} c{}", size_t(id), c).c_str(),
169 ✗ comb.polynomialCycleSlipDetector, WIDTH))
170 {
171 ✗ flow::ApplyChanges();
172 }
173
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 %%"))
178 {
179 ✗ comb.polynomialCycleSlipDetectorThresholdPercentage = val / 100.0;
180 ✗ flow::ApplyChanges();
181 }
182 ✗ ImGui::SameLine();
183 ✗ std::string description = "As percentage of the smallest wavelength of the combination terms.";
184 ✗ if (auto maxF = std::ranges::max_element(comb.terms, [](const Combination::Term& a, const Combination::Term& b) {
185 ✗ return a.satSigId.freq().getFrequency(a.freqNum) < b.satSigId.freq().getFrequency(b.freqNum);
186 });
187 ✗ maxF != comb.terms.end())
188 {
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);
193 }
194 ✗ gui::widgets::HelpMarker(description.c_str());
195
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))
198 {
199 ✗ flow::ApplyChanges();
200 }
201 ✗ if (ImGui::Checkbox(fmt::format("Output polynomials##id{} c{}", size_t(id), c).c_str(), &comb.polynomialCycleSlipDetectorOutputPolynomials))
202 {
203 ✗ flow::ApplyChanges();
204 }
205 ✗ if (!comb.polynomialCycleSlipDetector.isEnabled()) { ImGui::EndDisabled(); }
206
207 ✗ ImGui::EndPopup();
208 ✗ }
209
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,
213 ✗ ImVec2(0, 70.0F)))
214 {
215 ✗ ImGui::TableNextRow();
216
217 ✗ for (size_t t = 0; t < comb.terms.size(); t++)
218 {
219 ✗ auto& term = comb.terms.at(t);
220
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"))
225 {
226 ✗ flow::ApplyChanges();
227 ✗ term.sign = selected == 0 ? +1 : -1;
228 }
229
230 ✗ ImGui::TableSetColumnIndex(static_cast<int>(t) * 3 + 1);
231 ✗ selected = term.obsType == Combination::Term::ObservationType::Pseudorange ? 0 : 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"))
234 {
235 ✗ flow::ApplyChanges();
236 ✗ term.obsType = selected == 0 ? Combination::Term::ObservationType::Pseudorange : Combination::Term::ObservationType::Carrier;
237 }
238
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))
242 {
243 ✗ flow::ApplyChanges();
244 }
245 ✗ ImGui::SameLine();
246 ✗ ImGui::Dummy(ImVec2(10.0F, 0.0F));
247 }
248 ✗ ImGui::TableNextColumn();
249 ✗ ImGui::TextUnformatted("= Combined Frequency");
250
251 ✗ ImGui::TableNextRow();
252 ✗ for (size_t t = 0; t < comb.terms.size(); t++)
253 {
254 ✗ auto& term = comb.terms.at(t);
255
256 ✗ ImGui::TableSetColumnIndex(static_cast<int>(t) * 3);
257 ✗ ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 8.0F);
258 ✗ float radius = 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"); }
263
264 ✗ if (comb.terms.size() > 1)
265 {
266 ✗ ImGui::SameLine();
267 ✗ if (ImGui::Button(fmt::format("X##id{} c{} t{}", size_t(id), c, t).c_str()))
268 {
269 ✗ flow::ApplyChanges();
270 ✗ termToDelete.push_back(t);
271 }
272 ✗ if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Remove Term?"); }
273 }
274
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()); }
279
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);
283 ✗ if (ShowSatelliteSelector(fmt::format("##SatNum id{} c{} t{}", size_t(id), c, t).c_str(), satId, satId.satSys, true))
284 {
285 ✗ term.satSigId.satNum = satId.satNum;
286 ✗ flow::ApplyChanges();
287 }
288 }
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()); }
293
294 ✗ ImGui::EndTable();
295 }
296
297 ✗ for (const auto& t : termToDelete) { comb.terms.erase(std::next(comb.terms.begin(), static_cast<std::ptrdiff_t>(t))); }
298 ✗ }
299
300 ✗ if (!keepCombination) { combToDelete.push_back(c); }
301 }
302 ✗ for (const auto& c : combToDelete) { _combinations.erase(std::next(_combinations.begin(), static_cast<std::ptrdiff_t>(c))); }
303
304 ✗ ImGui::Separator();
305 ✗ if (ImGui::Button(fmt::format("Add Combination##id{}", size_t(id)).c_str()))
306 {
307 ✗ flow::ApplyChanges();
308 ✗ _combinations.emplace_back();
309 ✗ _combinations.back().polynomialCycleSlipDetector.setResetAfterCycleSlip(false);
310 }
311 ✗ }
312
313 ✗ [[nodiscard]] json NAV::GnssAnalyzer::save() const
314 {
315 LOG_TRACE("{}: called", nameId());
316
317 ✗ json j;
318
319 ✗ j["combinations"] = _combinations;
320
321 ✗ return j;
322 ✗ }
323
324 ✗ void NAV::GnssAnalyzer::restore(json const& j)
325 {
326 LOG_TRACE("{}: called", nameId());
327
328 ✗ if (j.contains("combinations"))
329 {
330 ✗ j.at("combinations").get_to(_combinations);
331 }
332 ✗ }
333
334 ✗ bool NAV::GnssAnalyzer::initialize()
335 {
336 LOG_TRACE("{}: called", nameId());
337
338 ✗ for (auto& comb : _combinations)
339 {
340 ✗ comb.polynomialCycleSlipDetector.clear();
341 ✗ comb.polynomials.clear();
342 ✗ for (auto& term : comb.terms)
343 {
344 ✗ term.receivedDuringRun = false;
345 }
346 }
347
348 ✗ return true;
349 }
350
351 ✗ void NAV::GnssAnalyzer::deinitialize()
352 {
353 LOG_TRACE("{}: called", nameId());
354
355 ✗ for (auto& comb : _combinations)
356 {
357 ✗ for (auto& term : comb.terms)
358 {
359 ✗ term.receivedDuringRun = false;
360 }
361 }
362 ✗ }
363
364 ✗ void NAV::GnssAnalyzer::receiveGnssObs(NAV::InputPin::NodeDataQueue& queue, size_t /* pinIdx */)
365 {
366 ✗ auto gnssObs = std::static_pointer_cast<const GnssObs>(queue.extract_front());
367 LOG_DATA("{}: Received GnssObs for [{}]", nameId(), gnssObs->insTime);
368
369 ✗ auto gnssComb = std::make_shared<GnssCombination>();
370 ✗ gnssComb->insTime = gnssObs->insTime;
371
372 ✗ for (size_t c = 0; c < _combinations.size(); c++)
373 {
374 ✗ auto& comb = _combinations.at(c);
375 ✗ GnssCombination::Combination combination;
376 ✗ combination.description = comb.description();
377 ✗ for (size_t i = 0; i < _combinations.size(); i++)
378 {
379 ✗ if (i == c) { continue; }
380 ✗ if (combination.description == _combinations.at(i).description())
381 {
382 ✗ combination.description += fmt::format(" - {}", c);
383 ✗ break;
384 }
385 }
386 LOG_DATA("{}: {}", nameId(), combination.description);
387
388 ✗ double result = 0.0;
389 ✗ double lambdaMin = 100.0;
390 ✗ size_t termsFound = 0;
391 ✗ for (auto& term : comb.terms)
392 {
393 ✗ GnssCombination::Combination::Term oTerm;
394 ✗ oTerm.sign = term.sign;
395 ✗ oTerm.satSigId = term.satSigId;
396 ✗ oTerm.obsType = term.obsType == Combination::Term::ObservationType::Pseudorange
397 ✗ ? GnssObs::ObservationType::Pseudorange
398 : GnssObs::ObservationType::Carrier;
399 LOG_DATA("{}: {}[{}][{}]", nameId(), oTerm.sign == 1 ? "+" : "-", oTerm.satSigId, oTerm.obsType);
400
401 ✗ double freq = term.satSigId.freq().getFrequency(term.freqNum);
402 ✗ double lambda = InsConst::C / freq;
403 ✗ lambdaMin = std::min(lambdaMin, lambda);
404
405 ✗ if (auto obs = (*gnssObs)(term.satSigId))
406 {
407 ✗ if (term.obsType == Combination::Term::ObservationType::Pseudorange)
408 {
409 ✗ if (auto psr = obs->get().pseudorange)
410 {
411 ✗ term.receivedDuringRun = true;
412
413 ✗ double value = psr->value;
414 ✗ result += static_cast<double>(term.sign) * value;
415
416 ✗ if (comb.unit == Combination::Unit::Cycles)
417 {
418 ✗ value /= lambda;
419 }
420 ✗ oTerm.value = value;
421
422 ✗ termsFound++;
423 }
424 else
425 {
426 LOG_DATA("{}: Resetting cycle-slip detector as no pseudorange in measurement", nameId());
427 ✗ comb.polynomialCycleSlipDetector.reset(comb.description());
428 }
429 }
430 else // if (term.obsType == Combination::Term::ObservationType::Carrier)
431 {
432 ✗ if (auto carrier = obs->get().carrierPhase)
433 {
434 ✗ term.receivedDuringRun = true;
435
436 ✗ double value = carrier->value * lambda;
437 ✗ result += static_cast<double>(term.sign) * value;
438
439 ✗ if (comb.unit == Combination::Unit::Cycles)
440 {
441 ✗ value /= lambda;
442 }
443 ✗ oTerm.value = value;
444
445 ✗ termsFound++;
446 }
447 else
448 {
449 LOG_DATA("{}: Resetting cycle-slip detector as no carrier-phase in measurement", nameId());
450 ✗ comb.polynomialCycleSlipDetector.reset(comb.description());
451 }
452 }
453 }
454
455 ✗ combination.terms.push_back(oTerm);
456 }
457 LOG_DATA("{}: Found {}/{}", nameId(), termsFound, comb.terms.size());
458 ✗ if (termsFound == comb.terms.size())
459 {
460 ✗ auto lambda = InsConst::C / comb.calcCombinationFrequency();
461 ✗ double resultCycles = result / lambda;
462 ✗ combination.result = comb.unit == Combination::Unit::Cycles ? resultCycles : result;
463
464 ✗ if (comb.polynomialCycleSlipDetector.isEnabled())
465 {
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);
471 ✗ if (combination.cycleSlipPrediction.has_value())
472 {
473 ✗ combination.cycleSlipMeasMinPred = *combination.result - *combination.cycleSlipPrediction;
474 LOG_DATA("{}: Predicting: meas - pred = {}", nameId(), *combination.cycleSlipMeasMinPred);
475 }
476
477 ✗ if (comb.polynomialCycleSlipDetectorOutputPolynomials)
478 {
479 ✗ if (auto polynomial = comb.polynomialCycleSlipDetector.calcPolynomial(key))
480 {
481 ✗ comb.polynomials.emplace_back(gnssComb->insTime, *polynomial);
482 ✗ }
483 ✗ if (auto relTime = comb.polynomialCycleSlipDetector.calcRelativeTime(key, gnssComb->insTime))
484 {
485 ✗ for (const auto& poly : comb.polynomials)
486 {
487 ✗ double value = poly.second.f(*relTime);
488 LOG_DATA("f({:.2f}) = {:.2f} ({})", *relTime, value, poly.second.toString());
489 ✗ combination.cycleSlipPolynomials.emplace_back(poly.first, poly.second, value);
490 }
491 }
492 }
493 ✗ double threshold = comb.polynomialCycleSlipDetectorThresholdPercentage * lambdaMin;
494 ✗ if (comb.unit == Combination::Unit::Cycles) { threshold /= lambda; }
495
496 // This adds the measurement to the polynomial
497 ✗ combination.cycleSlipResult = comb.polynomialCycleSlipDetector.checkForCycleSlip(key, gnssComb->insTime, *combination.result, threshold);
498
499 ✗ if (!comb.polynomialCycleSlipDetectorOutputWhenWindowSizeNotReached
500 ✗ && *combination.cycleSlipResult == PolynomialCycleSlipDetectorResult::LessDataThanWindowSize)
501 {
502 ✗ combination.cycleSlipPrediction.reset();
503 ✗ combination.cycleSlipMeasMinPred.reset();
504 }
505 ✗ }
506 }
507
508 ✗ gnssComb->combinations.push_back(combination);
509 ✗ }
510
511 ✗ invokeCallbacks(OUTPUT_PORT_INDEX_GNSS_COMBINATION, gnssComb);
512 ✗ }
513