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