| 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 |