| 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 | /// @file ObservationEstimator.hpp | ||
| 10 | /// @brief Calculates Observation estimates | ||
| 11 | /// @author T. Topp (topp@ins.uni-stuttgart.de) | ||
| 12 | /// @date 2023-12-21 | ||
| 13 | |||
| 14 | #pragma once | ||
| 15 | |||
| 16 | #include <algorithm> | ||
| 17 | #include <cmath> | ||
| 18 | #include <cstddef> | ||
| 19 | #include <cstdint> | ||
| 20 | #include <memory> | ||
| 21 | #include <unordered_map> | ||
| 22 | #include <utility> | ||
| 23 | #include <vector> | ||
| 24 | |||
| 25 | #include <imgui.h> | ||
| 26 | #include <application.h> | ||
| 27 | #include <imgui_stdlib.h> | ||
| 28 | #include "Navigation/GNSS/Core/Frequency.hpp" | ||
| 29 | #include "Navigation/GNSS/Core/SatelliteIdentifier.hpp" | ||
| 30 | #include "Navigation/GNSS/Positioning/AntexReader.hpp" | ||
| 31 | #include "Navigation/Transformations/Antenna.hpp" | ||
| 32 | #include "Navigation/Transformations/CoordinateFrames.hpp" | ||
| 33 | #include "NodeData/GNSS/GnssObs.hpp" | ||
| 34 | #include "internal/gui/widgets/imgui_ex.hpp" | ||
| 35 | #include "internal/gui/widgets/HelpMarker.hpp" | ||
| 36 | |||
| 37 | #include "Navigation/Atmosphere/Ionosphere/Ionosphere.hpp" | ||
| 38 | #include "Navigation/Atmosphere/Troposphere/Troposphere.hpp" | ||
| 39 | #include "Navigation/GNSS/Errors/MeasurementErrors.hpp" | ||
| 40 | #include "Navigation/GNSS/Positioning/Observation.hpp" | ||
| 41 | #include "Navigation/GNSS/Positioning/Receiver.hpp" | ||
| 42 | #include "Navigation/GNSS/Satellite/Ephemeris/GLONASSEphemeris.hpp" | ||
| 43 | |||
| 44 | #include "util/Container/UncertainValue.hpp" | ||
| 45 | #include "util/Json.hpp" | ||
| 46 | #include <Eigen/src/Core/MatrixBase.h> | ||
| 47 | #include <fmt/core.h> | ||
| 48 | #include <fmt/format.h> | ||
| 49 | #include <fmt/ranges.h> | ||
| 50 | |||
| 51 | namespace NAV | ||
| 52 | { | ||
| 53 | |||
| 54 | /// @brief Calculates Observation estimates | ||
| 55 | class ObservationEstimator | ||
| 56 | { | ||
| 57 | public: | ||
| 58 | /// @brief Constructor | ||
| 59 | /// @param[in] receiverCount Number of receivers | ||
| 60 | 352 | explicit ObservationEstimator(size_t receiverCount) | |
| 61 | 352 | : _receiverCount(receiverCount) | |
| 62 | { | ||
| 63 |
2/2✓ Branch 0 taken 467 times.
✓ Branch 1 taken 352 times.
|
819 | for (size_t i = 0; i < receiverCount; i++) |
| 64 | { | ||
| 65 |
3/8✓ Branch 2 taken 467 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 467 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 467 times.
✗ Branch 9 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
|
467 | _antenna.emplace(i, Antenna{}); |
| 66 | } | ||
| 67 | 352 | } | |
| 68 | |||
| 69 | /// @brief How the observation gets used. Influenced the measurement variance | ||
| 70 | enum ObservationDifference : uint8_t | ||
| 71 | { | ||
| 72 | NoDifference, ///< Estimation is not differenced | ||
| 73 | SingleDifference, ///< Single Difference | ||
| 74 | DoubleDifference, ///< Double Difference | ||
| 75 | }; | ||
| 76 | |||
| 77 | /// @brief Calculates the observation estimates | ||
| 78 | /// @param[in, out] observations List of GNSS observation data used for the calculation of this epoch | ||
| 79 | /// @param[in] ionosphericCorrections Ionospheric correction parameters collected from the Nav data | ||
| 80 | /// @param[in] receiver Receiver | ||
| 81 | /// @param[in] nameId Name and Id of the node used for log messages only | ||
| 82 | /// @param[in] obsDiff Observation Difference type to estimate | ||
| 83 | template<typename ReceiverType> | ||
| 84 | 3643 | void calcObservationEstimates(Observations& observations, | |
| 85 | const Receiver<ReceiverType>& receiver, | ||
| 86 | const std::shared_ptr<const IonosphericCorrections>& ionosphericCorrections, | ||
| 87 | [[maybe_unused]] const std::string& nameId, | ||
| 88 | ObservationDifference obsDiff) const | ||
| 89 | { | ||
| 90 | LOG_DATA("{}: Calculating all observation estimates:", nameId); | ||
| 91 |
2/2✓ Branch 7 taken 160463 times.
✓ Branch 8 taken 3643 times.
|
164106 | for (auto& [satSigId, observation] : observations.signals) |
| 92 | { | ||
| 93 |
1/2✓ Branch 1 taken 160463 times.
✗ Branch 2 not taken.
|
160463 | calcObservationEstimate(satSigId, observation, receiver, ionosphericCorrections, nameId, obsDiff); |
| 94 | } | ||
| 95 | 3643 | } | |
| 96 | |||
| 97 | /// @brief Calculates the observation estimate for the given signal | ||
| 98 | /// @param[in] satSigId Satellite signal identifier | ||
| 99 | /// @param[in, out] observation GNSS observation data used for the calculation of this epoch | ||
| 100 | /// @param[in] ionosphericCorrections Ionospheric correction parameters collected from the Nav data | ||
| 101 | /// @param[in] receiver Receiver | ||
| 102 | /// @param[in] nameId Name and Id of the node used for log messages only | ||
| 103 | /// @param[in] obsDiff Observation Difference type to estimate | ||
| 104 | template<typename ReceiverType> | ||
| 105 | 160463 | void calcObservationEstimate(const SatSigId& satSigId, | |
| 106 | Observations::SignalObservation& observation, | ||
| 107 | const Receiver<ReceiverType>& receiver, | ||
| 108 | const std::shared_ptr<const IonosphericCorrections>& ionosphericCorrections, | ||
| 109 | [[maybe_unused]] const std::string& nameId, | ||
| 110 | ObservationDifference obsDiff) const | ||
| 111 | { | ||
| 112 | LOG_DATA("{}: Calculating observation estimates for [{}]", nameId, satSigId); | ||
| 113 |
1/2✓ Branch 1 taken 160463 times.
✗ Branch 2 not taken.
|
160463 | const Frequency freq = satSigId.freq(); |
| 114 |
1/2✓ Branch 1 taken 160463 times.
✗ Branch 2 not taken.
|
160463 | const SatelliteSystem satSys = freq.getSatSys(); |
| 115 | |||
| 116 |
1/2✓ Branch 1 taken 160463 times.
✗ Branch 2 not taken.
|
160463 | auto& recvObs = observation.recvObs.at(receiver.type); |
| 117 | |||
| 118 | 160463 | Eigen::Vector3d e_recvPosAPC = e_calcRecvPosAPC(freq, | |
| 119 | 160463 | receiver.type, | |
| 120 | 160463 | receiver.e_posMarker, | |
| 121 |
1/2✓ Branch 1 taken 160463 times.
✗ Branch 2 not taken.
|
160463 | receiver.gnssObs, nameId); |
| 122 |
1/2✓ Branch 1 taken 160463 times.
✗ Branch 2 not taken.
|
160463 | Eigen::Vector3d lla_recvPosAPC = trafo::ecef2lla_WGS84(e_recvPosAPC); |
| 123 | |||
| 124 |
1/2✓ Branch 2 taken 160463 times.
✗ Branch 3 not taken.
|
160463 | double elevation = recvObs->satElevation(e_recvPosAPC, lla_recvPosAPC); |
| 125 |
1/2✓ Branch 2 taken 160463 times.
✗ Branch 3 not taken.
|
160463 | double azimuth = recvObs->satAzimuth(e_recvPosAPC, lla_recvPosAPC); |
| 126 |
1/2✓ Branch 2 taken 160463 times.
✗ Branch 3 not taken.
|
160463 | Eigen::Vector3d e_pLOS = recvObs->e_pLOS(e_recvPosAPC); |
| 127 | |||
| 128 | // Receiver-Satellite Range [m] | ||
| 129 |
2/4✓ Branch 3 taken 160463 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 160463 times.
✗ Branch 7 not taken.
|
160463 | double rho_r_s = (recvObs->e_satPos() - e_recvPosAPC).norm(); |
| 130 | 160463 | recvObs->terms.rho_r_s = rho_r_s; | |
| 131 | // Troposphere | ||
| 132 |
1/2✓ Branch 1 taken 160463 times.
✗ Branch 2 not taken.
|
160463 | auto tropo_r_s = calcTroposphericDelayAndMapping(receiver.gnssObs->insTime, lla_recvPosAPC, |
| 133 | 160463 | elevation, azimuth, _troposphereModels, nameId); | |
| 134 | 160463 | recvObs->terms.tropoZenithDelay = tropo_r_s; | |
| 135 | // Estimated troposphere propagation error [m] | ||
| 136 | 160463 | double dpsr_T_r_s = tropo_r_s.ZHD * tropo_r_s.zhdMappingFactor + tropo_r_s.ZWD * tropo_r_s.zwdMappingFactor; | |
| 137 | 160463 | recvObs->terms.dpsr_T_r_s = dpsr_T_r_s; | |
| 138 | // Estimated ionosphere propagation error [m] | ||
| 139 |
2/4✓ Branch 4 taken 160463 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 160463 times.
✗ Branch 8 not taken.
|
320926 | double dpsr_I_r_s = calcIonosphericDelay(static_cast<double>(receiver.gnssObs->insTime.toGPSweekTow().tow), |
| 140 | 160463 | freq, observation.freqNum(), lla_recvPosAPC, elevation, azimuth, | |
| 141 | 160463 | _ionosphereModel, ionosphericCorrections.get()); | |
| 142 | 160463 | recvObs->terms.dpsr_I_r_s = dpsr_I_r_s; | |
| 143 | // Sagnac correction [m] | ||
| 144 |
1/2✓ Branch 3 taken 160463 times.
✗ Branch 4 not taken.
|
160463 | double dpsr_ie_r_s = calcSagnacCorrection(e_recvPosAPC, recvObs->e_satPos()); |
| 145 | 160463 | recvObs->terms.dpsr_ie_r_s = dpsr_ie_r_s; | |
| 146 | |||
| 147 | // Earth's gravitational field causes relativistic signal delay due to space-time curvature (Shapiro effect) [s] | ||
| 148 | // double posNorm = recvObs->e_satPos().norm() + e_recvPosAPC.norm(); | ||
| 149 | // double dt_rel_stc = e_recvPosAPC.norm() > InsConst::WGS84::a / 2.0 ? 2.0 * InsConst::WGS84::MU / std::pow(InsConst::C, 3) * std::log((posNorm + rho_r_s) / (posNorm - rho_r_s)) | ||
| 150 | // : 0.0; | ||
| 151 | |||
| 152 |
1/2✓ Branch 2 taken 160463 times.
✗ Branch 3 not taken.
|
160463 | double cn0 = recvObs->gnssObsData().CN0.value_or(1.0); |
| 153 | |||
| 154 |
1/2✓ Branch 3 taken 160463 times.
✗ Branch 4 not taken.
|
160463 | double pcv = calcPCV(freq, receiver.gnssObs->insTime, elevation, std::nullopt, receiver.type, receiver.gnssObs, nameId); // TODO: use 'azimuth', but need antenna azimuth |
| 155 | |||
| 156 |
2/2✓ Branch 8 taken 375622 times.
✓ Branch 9 taken 160463 times.
|
536085 | for (auto& [obsType, obsData] : recvObs->obs) |
| 157 | { | ||
| 158 | // LOG_DATA("{}: [{}][{:11}][{:5}] Observation estimate", nameId, satSigId, obsType, receiver.type); | ||
| 159 |
3/5✓ Branch 0 taken 160463 times.
✓ Branch 1 taken 55572 times.
✓ Branch 2 taken 159587 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
|
375622 | switch (obsType) |
| 160 | { | ||
| 161 | 160463 | case GnssObs::Pseudorange: | |
| 162 | 160463 | obsData.estimate = rho_r_s | |
| 163 | 160463 | + dpsr_ie_r_s | |
| 164 | 160463 | + dpsr_T_r_s | |
| 165 | 160463 | + dpsr_I_r_s | |
| 166 | 160463 | + InsConst::C | |
| 167 |
1/2✓ Branch 1 taken 160463 times.
✗ Branch 2 not taken.
|
160463 | * (*receiver.recvClk.biasFor(satSys) - recvObs->satClock().bias |
| 168 | // + dt_rel_stc | ||
| 169 |
4/6✓ Branch 1 taken 160463 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 65833 times.
✓ Branch 4 taken 94630 times.
✓ Branch 6 taken 65833 times.
✗ Branch 7 not taken.
|
160463 | + (receiver.interFrequencyBias.contains(freq) ? receiver.interFrequencyBias.at(freq).value : 0.0)) |
| 170 | 160463 | + pcv; | |
| 171 |
1/2✓ Branch 1 taken 160463 times.
✗ Branch 2 not taken.
|
160463 | obsData.measVar = _gnssMeasurementErrorModel.psrMeasErrorVar(satSys, elevation, cn0); |
| 172 | // LOG_DATA("{}: [{}][{:11}][{:5}] {:.4f} [m] Geometrical range", nameId, satSigId, obsType, receiver.type, rho_r_s); | ||
| 173 | // LOG_DATA("{}: [{}][{:11}][{:5}] + {:.4f} [m] Sagnac correction", nameId, satSigId, obsType, receiver.type, dpsr_ie_r_s); | ||
| 174 | // if (dpsr_T_r_s != 0.0) { LOG_DATA("{}: [{}][{:11}][{:5}] + {:.4f} [m] Tropospheric delay", nameId, satSigId, obsType, receiver.type, dpsr_T_r_s); } | ||
| 175 | // if (dpsr_I_r_s != 0.0) { LOG_DATA("{}: [{}][{:11}][{:5}] + {:.4f} [m] Ionospheric delay", nameId, satSigId, obsType, receiver.type, dpsr_I_r_s); } | ||
| 176 | // if (*receiver.recvClk.biasFor(satSys) != 0.0) { LOG_DATA("{}: [{}][{:11}][{:5}] + {:.4f} [m] Receiver clock bias", nameId, satSigId, obsType, receiver.type, InsConst::C * *receiver.recvClk.biasFor(satSys)); } | ||
| 177 | // LOG_DATA("{}: [{}][{:11}][{:5}] - {:.4f} [m] Satellite clock bias", nameId, satSigId, obsType, receiver.type, InsConst::C * recvObs->satClock().bias); | ||
| 178 | // // LOG_DATA("{}: [{}][{:11}][{:5}] + {:.4f} [m] Shapiro effect", nameId, satSigId, obsType, receiver.type, InsConst::C * dt_rel_stc); | ||
| 179 | // if (receiver.interFrequencyBias.contains(freq)) { LOG_DATA("{}: [{}][{:11}][{:5}] + {:.4f} [m] Inter-frequency bias", nameId, satSigId, obsType, receiver.type, InsConst::C * receiver.interFrequencyBias.at(freq).value); } | ||
| 180 | // LOG_DATA("{}: [{}][{:11}][{:5}] = {:.4f} [m] Pseudorange estimate", nameId, satSigId, obsType, receiver.type, obsData.estimate); | ||
| 181 | // LOG_DATA("{}: [{}][{:11}][{:5}] {:.4f} [m] Pseudorange measurement", nameId, satSigId, obsType, receiver.type, obsData.measurement); | ||
| 182 | // LOG_DATA("{}: [{}][{:11}][{:5}] {:.4e} [m] Difference to measurement", nameId, satSigId, obsType, receiver.type, obsData.measurement - obsData.estimate); | ||
| 183 | 160463 | break; | |
| 184 | 55572 | case GnssObs::Carrier: | |
| 185 | 55572 | obsData.estimate = rho_r_s | |
| 186 | 55572 | + dpsr_ie_r_s | |
| 187 | 55572 | + dpsr_T_r_s | |
| 188 | 55572 | - dpsr_I_r_s | |
| 189 | 55572 | + InsConst::C | |
| 190 |
1/2✓ Branch 1 taken 55572 times.
✗ Branch 2 not taken.
|
55572 | * (*receiver.recvClk.biasFor(satSys) - recvObs->satClock().bias |
| 191 | // + dt_rel_stc | ||
| 192 | ) | ||
| 193 | 55572 | + pcv; | |
| 194 |
1/2✓ Branch 1 taken 55572 times.
✗ Branch 2 not taken.
|
55572 | obsData.measVar = _gnssMeasurementErrorModel.carrierMeasErrorVar(satSys, elevation, cn0); |
| 195 | // LOG_DATA("{}: [{}][{:11}][{:5}] {:.4f} [m] Geometrical range", nameId, satSigId, obsType, receiver.type, rho_r_s); | ||
| 196 | // LOG_DATA("{}: [{}][{:11}][{:5}] + {:.4f} [m] Sagnac correction", nameId, satSigId, obsType, receiver.type, dpsr_ie_r_s); | ||
| 197 | // if (dpsr_T_r_s != 0.0) { LOG_DATA("{}: [{}][{:11}][{:5}] + {:.4f} [m] Tropospheric delay", nameId, satSigId, obsType, receiver.type, dpsr_T_r_s); } | ||
| 198 | // if (dpsr_I_r_s != 0.0) { LOG_DATA("{}: [{}][{:11}][{:5}] - {:.4f} [m] Ionospheric delay", nameId, satSigId, obsType, receiver.type, dpsr_I_r_s); } | ||
| 199 | // if (*receiver.recvClk.biasFor(satSys) != 0.0) { LOG_DATA("{}: [{}][{:11}][{:5}] + {:.4f} [m] Receiver clock bias", nameId, satSigId, obsType, receiver.type, InsConst::C * *receiver.recvClk.biasFor(satSys)); } | ||
| 200 | // LOG_DATA("{}: [{}][{:11}][{:5}] - {:.4f} [m] Satellite clock bias", nameId, satSigId, obsType, receiver.type, InsConst::C * recvObs->satClock().bias); | ||
| 201 | // // LOG_DATA("{}: [{}][{:11}][{:5}] + {:.4f} [m] Shapiro effect", nameId, satSigId, obsType, receiver.type, InsConst::C * dt_rel_stc); | ||
| 202 | // LOG_DATA("{}: [{}][{:11}][{:5}] = {:.4f} [m] Carrier-phase estimate", nameId, satSigId, obsType, receiver.type, obsData.estimate); | ||
| 203 | // LOG_DATA("{}: [{}][{:11}][{:5}] {:.4f} [m] Carrier-phase measurement", nameId, satSigId, obsType, receiver.type, obsData.measurement); | ||
| 204 | // LOG_DATA("{}: [{}][{:11}][{:5}] {:.4e} [m] Difference to measurement", nameId, satSigId, obsType, receiver.type, obsData.measurement - obsData.estimate); | ||
| 205 | 55572 | break; | |
| 206 | 159587 | case GnssObs::Doppler: | |
| 207 |
2/4✓ Branch 3 taken 159587 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 159587 times.
✗ Branch 7 not taken.
|
159587 | obsData.estimate = e_pLOS.dot(recvObs->e_satVel() - receiver.e_vel) |
| 208 |
1/2✓ Branch 5 taken 159587 times.
✗ Branch 6 not taken.
|
159587 | - calcSagnacRateCorrection(e_recvPosAPC, recvObs->e_satPos(), receiver.e_vel, recvObs->e_satVel()) |
| 209 | 159587 | + InsConst::C | |
| 210 |
1/2✓ Branch 1 taken 159587 times.
✗ Branch 2 not taken.
|
159587 | * (*receiver.recvClk.driftFor(satSys) |
| 211 | 159587 | - recvObs->satClock().drift); | |
| 212 |
1/2✓ Branch 2 taken 159587 times.
✗ Branch 3 not taken.
|
159587 | obsData.measVar = _gnssMeasurementErrorModel.psrRateMeasErrorVar(freq, observation.freqNum(), elevation, cn0); |
| 213 | // LOG_DATA("{}: [{}][{:11}][{:5}] {:.4f} [m/s] ", nameId, satSigId, obsType, receiver.type, e_pLOS.dot(recvObs->e_satVel() - receiver.e_vel)); | ||
| 214 | // LOG_DATA("{}: [{}][{:11}][{:5}] - {:.4f} [m/s] Sagnac rate correction", nameId, satSigId, obsType, receiver.type, calcSagnacRateCorrection(e_recvPosAPC, recvObs->e_satPos(), receiver.e_vel, recvObs->e_satVel())); | ||
| 215 | // if (*receiver.recvClk.driftFor(satSys) != 0.0) { LOG_DATA("{}: [{}][{:11}][{:5}] + {:.4f} [m/s] Receiver clock drift", nameId, satSigId, obsType, receiver.type, InsConst::C * *receiver.recvClk.driftFor(satSys)); } | ||
| 216 | // LOG_DATA("{}: [{}][{:11}][{:5}] - {:.4f} [m/s] Satellite clock drift", nameId, satSigId, obsType, receiver.type, InsConst::C * recvObs->satClock().drift); | ||
| 217 | // LOG_DATA("{}: [{}][{:11}][{:5}] = {:.4f} [m/s] Doppler estimate", nameId, satSigId, obsType, receiver.type, obsData.estimate); | ||
| 218 | // LOG_DATA("{}: [{}][{:11}][{:5}] {:.4f} [m/s] Doppler measurement", nameId, satSigId, obsType, receiver.type, obsData.measurement); | ||
| 219 | // LOG_DATA("{}: [{}][{:11}][{:5}] {:.4e} [m/s] Difference to measurement", nameId, satSigId, obsType, receiver.type, obsData.measurement - obsData.estimate); | ||
| 220 | 159587 | break; | |
| 221 | ✗ | case GnssObs::ObservationType_COUNT: | |
| 222 | ✗ | break; | |
| 223 | } | ||
| 224 | // LOG_DATA("{}: [{}][{:11}][{:5}] Observation error variance", nameId, satSigId, obsType, receiver.type); | ||
| 225 | // LOG_DATA("{}: [{}][{:11}][{:5}] {:.4g} [{}] Measurement error variance", nameId, satSigId, obsType, receiver.type, obsData.measVar, | ||
| 226 | // obsType == GnssObs::Doppler ? "m^2/s^2" : "m^2"); | ||
| 227 | |||
| 228 |
2/2✓ Branch 0 taken 208906 times.
✓ Branch 1 taken 166716 times.
|
375622 | if (obsDiff == NoDifference) |
| 229 | { | ||
| 230 |
3/4✓ Branch 0 taken 104015 times.
✓ Branch 1 taken 104891 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 104015 times.
|
208906 | if (obsType == GnssObs::Pseudorange || obsType == GnssObs::Carrier) |
| 231 | { | ||
| 232 |
1/2✓ Branch 3 taken 104891 times.
✗ Branch 4 not taken.
|
104891 | obsData.measVar += observation.navData()->calcSatellitePositionVariance() |
| 233 |
1/2✓ Branch 2 taken 104891 times.
✗ Branch 3 not taken.
|
104891 | + ionoErrorVar(dpsr_I_r_s, freq, observation.freqNum()) |
| 234 |
1/2✓ Branch 1 taken 104891 times.
✗ Branch 2 not taken.
|
104891 | + tropoErrorVar(dpsr_T_r_s, elevation); |
| 235 | // LOG_DATA("{}: [{}][{:11}][{:5}] + {:.4f} [m^2] Satellite position variance", nameId, satSigId, obsType, receiver.type, observation.navData()->calcSatellitePositionVariance()); | ||
| 236 | // if (dpsr_I_r_s != 0.0) { LOG_DATA("{}: [{}][{:11}][{:5}] + {:.4f} [m^2] Ionosphere variance", nameId, satSigId, obsType, receiver.type, ionoErrorVar(dpsr_I_r_s, freq, observation.freqNum())); } | ||
| 237 | // if (dpsr_T_r_s != 0.0) { LOG_DATA("{}: [{}][{:11}][{:5}] + {:.4f} [m^2] Troposphere variance", nameId, satSigId, obsType, receiver.type, tropoErrorVar(dpsr_T_r_s, elevation)); } | ||
| 238 | } | ||
| 239 |
2/2✓ Branch 0 taken 104891 times.
✓ Branch 1 taken 104015 times.
|
208906 | if (obsType == GnssObs::Pseudorange) |
| 240 | { | ||
| 241 |
1/2✓ Branch 1 taken 104891 times.
✗ Branch 2 not taken.
|
104891 | obsData.measVar += _gnssMeasurementErrorModel.codeBiasErrorVar(); |
| 242 | // LOG_DATA("{}: [{}][{:11}][{:5}] + {:.4f} [m^2] Code bias variance", nameId, satSigId, obsType, receiver.type, _gnssMeasurementErrorModel.codeBiasErrorVar()); | ||
| 243 | } | ||
| 244 | } | ||
| 245 | // LOG_DATA("{}: [{}][{:11}][{:5}] = {:.4g} [{}] Observation error variance", nameId, satSigId, obsType, receiver.type, obsData.measVar, | ||
| 246 | // obsType == GnssObs::Doppler ? "m^2/s^2" : "m^2"); | ||
| 247 | } | ||
| 248 | 160463 | } | |
| 249 | |||
| 250 | /// Satellite Info used for estimate calculation | ||
| 251 | struct SatelliteInfo | ||
| 252 | { | ||
| 253 | /// @brief Constructor | ||
| 254 | /// @param[in] e_satPos Satellite position in ECEF coordinates [m] | ||
| 255 | /// @param[in] e_satVel Satellite velocity in ECEF frame [m/s] | ||
| 256 | /// @param[in] satClockBias Satellite clock bias [s] | ||
| 257 | /// @param[in] satClockDrift Satellite clock drift [s/s] | ||
| 258 | SatelliteInfo(Eigen::Vector3d e_satPos, Eigen::Vector3d e_satVel, double satClockBias, double satClockDrift) | ||
| 259 | : e_satPos(std::move(e_satPos)), e_satVel(std::move(e_satVel)), satClockBias(satClockBias), satClockDrift(satClockDrift) {} | ||
| 260 | |||
| 261 | Eigen::Vector3d e_satPos; ///< Satellite position in ECEF coordinates [m] | ||
| 262 | Eigen::Vector3d e_satVel; ///< Satellite velocity in ECEF frame [m/s] | ||
| 263 | double satClockBias{}; ///< Satellite clock bias [s] | ||
| 264 | double satClockDrift{}; ///< Satellite clock drift [s/s] | ||
| 265 | }; | ||
| 266 | |||
| 267 | /// @brief Calculates the pseudorange estimate | ||
| 268 | /// @param[in] freq Signal frequency | ||
| 269 | /// @param[in] receiverType Receiver type to select the correct antenna | ||
| 270 | /// @param[in] e_recvPosAPC Receiver antenna phase center position in ECEF coordinates [m] | ||
| 271 | /// @param[in] recvClockBias Receiver clock bias [s] | ||
| 272 | /// @param[in] interFreqBias Receiver inter-frequency bias [s] | ||
| 273 | /// @param[in] dpsr_T_r_s Estimated troposphere propagation error [m] | ||
| 274 | /// @param[in] dpsr_I_r_s Estimated ionosphere propagation error [m] | ||
| 275 | /// @param[in] satInfo Satellite Information (pos, vel, clock) | ||
| 276 | /// @param[in] elevation Elevation of the satellite [rad] | ||
| 277 | /// @param[in] gnssObs GNSS observation | ||
| 278 | /// @param[in] nameId Name and Id of the node used for log messages only | ||
| 279 | /// @return The estimate [m] and the position line-of-sight unit vector (used for the Jacobian) | ||
| 280 | template<typename Derived> | ||
| 281 | auto calcPseudorangeEstimate(Frequency freq, | ||
| 282 | size_t receiverType, | ||
| 283 | const Eigen::MatrixBase<Derived>& e_recvPosAPC, | ||
| 284 | const auto& recvClockBias, | ||
| 285 | const auto& interFreqBias, | ||
| 286 | const auto& dpsr_T_r_s, | ||
| 287 | const auto& dpsr_I_r_s, | ||
| 288 | const std::shared_ptr<const SatelliteInfo>& satInfo, | ||
| 289 | const auto& elevation, | ||
| 290 | const std::shared_ptr<const GnssObs>& gnssObs, | ||
| 291 | [[maybe_unused]] const std::string& nameId) const | ||
| 292 | { | ||
| 293 | // Receiver-Satellite Range [m] | ||
| 294 | auto rho_r_s = (satInfo->e_satPos - e_recvPosAPC).norm(); | ||
| 295 | |||
| 296 | // Sagnac correction [m] | ||
| 297 | auto dpsr_ie_r_s = calcSagnacCorrection(e_recvPosAPC, satInfo->e_satPos); | ||
| 298 | |||
| 299 | // Earth's gravitational field causes relativistic signal delay due to space-time curvature (Shapiro effect) [s] | ||
| 300 | // double posNorm = e_satPos.norm() + e_recvPosAPC.norm(); | ||
| 301 | // double dt_rel_stc = e_recvPosAPC.norm() > InsConst::WGS84::a / 2.0 ? 2.0 * InsConst::WGS84::MU / std::pow(InsConst::C, 3) * std::log((posNorm + rho_r_s) / (posNorm - rho_r_s)) | ||
| 302 | // : 0.0; | ||
| 303 | |||
| 304 | auto pcv = calcPCV(freq, gnssObs->insTime, elevation, std::nullopt, receiverType, gnssObs, nameId); // TODO: use 'azimuth', but need antenna azimuth | ||
| 305 | |||
| 306 | return rho_r_s | ||
| 307 | + dpsr_ie_r_s | ||
| 308 | + dpsr_T_r_s | ||
| 309 | + dpsr_I_r_s | ||
| 310 | + InsConst::C * (recvClockBias - satInfo->satClockBias /* + dt_rel_stc */ + interFreqBias) | ||
| 311 | + pcv; | ||
| 312 | } | ||
| 313 | |||
| 314 | /// @brief Calculates the carrier-phase estimate | ||
| 315 | /// @param[in] freq Signal frequency | ||
| 316 | /// @param[in] freqNum Frequency number. Only used for GLONASS G1 and G2 | ||
| 317 | /// @param[in] receiverType Receiver type to select the correct antenna | ||
| 318 | /// @param[in] e_recvPosMarker Receiver marker position in ECEF coordinates [m] | ||
| 319 | /// @param[in] recvClockBias Receiver clock bias [s] | ||
| 320 | /// @param[in] satInfo Satellite Information (pos, vel, clock) | ||
| 321 | /// @param[in] gnssObs GNSS observation | ||
| 322 | /// @param[in] ionosphericCorrections Ionospheric correction parameters collected from the Nav data | ||
| 323 | /// @param[in] nameId Name and Id of the node used for log messages only | ||
| 324 | /// @return The estimate [m] and the position line-of-sight unit vector (used for the Jacobian) | ||
| 325 | template<typename Derived> | ||
| 326 | std::pair<double, Eigen::Vector3d> | ||
| 327 | calcCarrierEstimate(Frequency freq, | ||
| 328 | int8_t freqNum, | ||
| 329 | size_t receiverType, | ||
| 330 | const Eigen::MatrixBase<Derived>& e_recvPosMarker, | ||
| 331 | double recvClockBias, | ||
| 332 | const std::shared_ptr<const SatelliteInfo>& satInfo, | ||
| 333 | const std::shared_ptr<const GnssObs>& gnssObs, | ||
| 334 | const std::shared_ptr<const IonosphericCorrections>& ionosphericCorrections, | ||
| 335 | [[maybe_unused]] const std::string& nameId) const | ||
| 336 | { | ||
| 337 | Eigen::Vector3d e_recvPosAPC = e_calcRecvPosAPC(freq, receiverType, e_recvPosMarker, gnssObs, nameId); | ||
| 338 | Eigen::Vector3d lla_recvPosAPC = trafo::ecef2lla_WGS84(e_recvPosAPC); | ||
| 339 | |||
| 340 | Eigen::Vector3d e_pLOS = e_calcLineOfSightUnitVector(e_recvPosAPC, satInfo->e_satPos); | ||
| 341 | Eigen::Vector3d n_lineOfSightUnitVector = trafo::n_Quat_e(lla_recvPosAPC(0), lla_recvPosAPC(1)) * e_pLOS; | ||
| 342 | double elevation = calcSatElevation(n_lineOfSightUnitVector); | ||
| 343 | double azimuth = calcSatAzimuth(n_lineOfSightUnitVector); | ||
| 344 | |||
| 345 | // Receiver-Satellite Range [m] | ||
| 346 | double rho_r_s = (satInfo->e_satPos - e_recvPosAPC).norm(); | ||
| 347 | // Estimated troposphere propagation error [m] | ||
| 348 | double dpsr_T_r_s = calculateTroposphericDelay(lla_recvPosAPC, gnssObs, elevation, azimuth, nameId); | ||
| 349 | // Estimated ionosphere propagation error [m] | ||
| 350 | double dpsr_I_r_s = calculateIonosphericDelay(freq, freqNum, lla_recvPosAPC, gnssObs, ionosphericCorrections, elevation, azimuth); | ||
| 351 | // Sagnac correction [m] | ||
| 352 | double dpsr_ie_r_s = calcSagnacCorrection(e_recvPosAPC, satInfo->e_satPos); | ||
| 353 | |||
| 354 | // Earth's gravitational field causes relativistic signal delay due to space-time curvature (Shapiro effect) [s] | ||
| 355 | // double posNorm = e_satPos.norm() + e_recvPosAPC.norm(); | ||
| 356 | // double dt_rel_stc = e_recvPosAPC.norm() > InsConst::WGS84::a / 2.0 ? 2.0 * InsConst::WGS84::MU / std::pow(InsConst::C, 3) * std::log((posNorm + rho_r_s) / (posNorm - rho_r_s)) | ||
| 357 | // : 0.0; | ||
| 358 | |||
| 359 | auto pcv = calcPCV(freq, gnssObs->insTime, elevation, std::nullopt, receiverType, gnssObs, nameId); // TODO: use 'azimuth', but need antenna azimuth | ||
| 360 | |||
| 361 | double estimate = rho_r_s | ||
| 362 | + dpsr_ie_r_s | ||
| 363 | + dpsr_T_r_s | ||
| 364 | - dpsr_I_r_s | ||
| 365 | + InsConst::C * (recvClockBias - satInfo->satClockBias /* + dt_rel_stc */) | ||
| 366 | + pcv; | ||
| 367 | |||
| 368 | return { estimate, e_pLOS }; | ||
| 369 | } | ||
| 370 | |||
| 371 | /// @brief Calculates the doppler estimate | ||
| 372 | /// @param[in] freq Signal frequency | ||
| 373 | /// @param[in] receiverType Receiver type to select the correct antenna | ||
| 374 | /// @param[in] e_recvPosMarker Receiver marker position in ECEF coordinates [m] | ||
| 375 | /// @param[in] e_recvVel Receiver velocity in ECEF frame [m/s] | ||
| 376 | /// @param[in] recvClockDrift Receiver clock drift [s/s] | ||
| 377 | /// @param[in] satInfo Satellite Information (pos, vel, clock) | ||
| 378 | /// @param[in] gnssObs GNSS observation | ||
| 379 | /// @param[in] nameId Name and Id of the node used for log messages only | ||
| 380 | /// @return The estimate [m/s] and the position line-of-sight unit vector (used for the Jacobian) | ||
| 381 | template<typename DerivedPos, typename DerivedVel> | ||
| 382 | auto calcDopplerEstimate(Frequency freq, | ||
| 383 | size_t receiverType, | ||
| 384 | const Eigen::MatrixBase<DerivedPos>& e_recvPosMarker, | ||
| 385 | const Eigen::MatrixBase<DerivedVel>& e_recvVel, | ||
| 386 | const auto& recvClockDrift, | ||
| 387 | const std::shared_ptr<const SatelliteInfo>& satInfo, | ||
| 388 | const std::shared_ptr<const GnssObs>& gnssObs, | ||
| 389 | [[maybe_unused]] const std::string& nameId) const | ||
| 390 | { | ||
| 391 | auto e_recvPosAPC = e_calcRecvPosAPC(freq, receiverType, e_recvPosMarker, gnssObs, nameId); | ||
| 392 | |||
| 393 | auto e_pLOS = e_calcLineOfSightUnitVector(e_recvPosAPC, satInfo->e_satPos); | ||
| 394 | |||
| 395 | return e_pLOS.dot(satInfo->e_satVel - e_recvVel) | ||
| 396 | - calcSagnacRateCorrection(e_recvPosAPC, satInfo->e_satPos, e_recvVel, satInfo->e_satVel) | ||
| 397 | + InsConst::C * (recvClockDrift - satInfo->satClockDrift); | ||
| 398 | } | ||
| 399 | |||
| 400 | /// @brief Calculates the observation variance | ||
| 401 | /// @param[in] freq Signal Frequency | ||
| 402 | /// @param[in] freqNum Frequency number. Only used for GLONASS G1 and G2 | ||
| 403 | /// @param[in] receiverType Receiver type to select the correct antenna | ||
| 404 | /// @param[in] obsType Observation type to calculate the variance for | ||
| 405 | /// @param[in] e_recvPosMarker Receiver marker position in ECEF coordinates [m] | ||
| 406 | /// @param[in] e_satPos Satellite position in ECEF coordinates [m] | ||
| 407 | /// @param[in] cn0 Carrier-to-Noise density [dBHz] | ||
| 408 | /// @param[in] gnssObs GNSS observation | ||
| 409 | /// @param[in] navData Navigation data including this satellite | ||
| 410 | /// @param[in] ionosphericCorrections Ionospheric correction parameters collected from the Nav data | ||
| 411 | /// @param[in] nameId Name and Id of the node used for log messages only | ||
| 412 | /// @param[in] obsDiff Observation Difference type to estimate | ||
| 413 | /// @return Observation variance in [m] (pseudorange, carrier) or [m/s] (doppler) | ||
| 414 | double calcObservationVariance(Frequency freq, | ||
| 415 | int8_t freqNum, | ||
| 416 | size_t receiverType, | ||
| 417 | GnssObs::ObservationType obsType, | ||
| 418 | const Eigen::Vector3d& e_recvPosMarker, | ||
| 419 | const Eigen::Vector3d& e_satPos, | ||
| 420 | double cn0, | ||
| 421 | const std::shared_ptr<const GnssObs>& gnssObs, | ||
| 422 | const std::shared_ptr<const SatNavData>& navData, | ||
| 423 | const std::shared_ptr<const IonosphericCorrections>& ionosphericCorrections, | ||
| 424 | [[maybe_unused]] const std::string& nameId, | ||
| 425 | ObservationDifference obsDiff) const | ||
| 426 | { | ||
| 427 | const SatelliteSystem satSys = freq.getSatSys(); | ||
| 428 | |||
| 429 | Eigen::Vector3d e_recvPosAPC = e_calcRecvPosAPC(freq, receiverType, e_recvPosMarker, gnssObs, nameId); | ||
| 430 | Eigen::Vector3d lla_recvPosAPC = trafo::ecef2lla_WGS84(e_recvPosAPC); | ||
| 431 | |||
| 432 | Eigen::Vector3d e_pLOS = e_calcLineOfSightUnitVector(e_recvPosAPC, e_satPos); | ||
| 433 | Eigen::Vector3d n_lineOfSightUnitVector = trafo::n_Quat_e(lla_recvPosAPC(0), lla_recvPosAPC(1)) * e_pLOS; | ||
| 434 | double elevation = calcSatElevation(n_lineOfSightUnitVector); | ||
| 435 | double azimuth = calcSatAzimuth(n_lineOfSightUnitVector); | ||
| 436 | |||
| 437 | // Estimated troposphere propagation error [m] | ||
| 438 | double dpsr_T_r_s = calculateTroposphericDelay(lla_recvPosAPC, gnssObs, elevation, azimuth, nameId); | ||
| 439 | // Estimated ionosphere propagation error [m] | ||
| 440 | double dpsr_I_r_s = calculateIonosphericDelay(freq, freqNum, lla_recvPosAPC, gnssObs, ionosphericCorrections, elevation, azimuth); | ||
| 441 | |||
| 442 | double variance = 0.0; | ||
| 443 | |||
| 444 | switch (obsType) | ||
| 445 | { | ||
| 446 | case GnssObs::Pseudorange: | ||
| 447 | variance = _gnssMeasurementErrorModel.psrMeasErrorVar(satSys, elevation, cn0); | ||
| 448 | break; | ||
| 449 | case GnssObs::Carrier: | ||
| 450 | variance = _gnssMeasurementErrorModel.carrierMeasErrorVar(satSys, elevation, cn0); | ||
| 451 | break; | ||
| 452 | case GnssObs::Doppler: | ||
| 453 | variance = _gnssMeasurementErrorModel.psrRateMeasErrorVar(freq, freqNum, elevation, cn0); | ||
| 454 | break; | ||
| 455 | case GnssObs::ObservationType_COUNT: | ||
| 456 | break; | ||
| 457 | } | ||
| 458 | |||
| 459 | if (obsDiff == NoDifference) | ||
| 460 | { | ||
| 461 | if (obsType == GnssObs::Pseudorange || obsType == GnssObs::Carrier) | ||
| 462 | { | ||
| 463 | variance += navData->calcSatellitePositionVariance() | ||
| 464 | + ionoErrorVar(dpsr_I_r_s, freq, freqNum) | ||
| 465 | + tropoErrorVar(dpsr_T_r_s, elevation); | ||
| 466 | } | ||
| 467 | if (obsType == GnssObs::Pseudorange) | ||
| 468 | { | ||
| 469 | variance += _gnssMeasurementErrorModel.codeBiasErrorVar(); | ||
| 470 | } | ||
| 471 | } | ||
| 472 | |||
| 473 | return variance; | ||
| 474 | } | ||
| 475 | |||
| 476 | /// @brief Calculates the antenna phase center position for the marker | ||
| 477 | /// @param[in] freq Signal frequency | ||
| 478 | /// @param[in] receiverType Receiver type to select the correct antenna | ||
| 479 | /// @param[in] e_recvPosMarker Receiver marker position in ECEF coordinates [m] | ||
| 480 | /// @param[in] gnssObs GNSS observation | ||
| 481 | /// @param[in] nameId Name and Id of the node used for log messages only | ||
| 482 | template<typename Derived> | ||
| 483 | 160463 | [[nodiscard]] Eigen::Vector3<typename Derived::Scalar> e_calcRecvPosAPC(const Frequency& freq, | |
| 484 | size_t receiverType, | ||
| 485 | const Eigen::MatrixBase<Derived>& e_recvPosMarker, | ||
| 486 | const std::shared_ptr<const GnssObs>& gnssObs, | ||
| 487 | [[maybe_unused]] const std::string& nameId) const | ||
| 488 | { | ||
| 489 | 160463 | const Antenna& antenna = _antenna.at(receiverType); | |
| 490 | |||
| 491 | 160463 | auto e_recvPosAPC = trafo::e_posMarker2ARP(e_recvPosMarker, | |
| 492 | gnssObs, | ||
| 493 | 160463 | antenna.hen_delta); | |
| 494 |
2/2✓ Branch 0 taken 34903 times.
✓ Branch 1 taken 125559 times.
|
160462 | if (antenna.correctPCO) |
| 495 | { | ||
| 496 | 34903 | std::string antennaType; | |
| 497 |
3/6✓ Branch 0 taken 34903 times.
✗ Branch 1 not taken.
✓ Branch 4 taken 34903 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 34903 times.
✗ Branch 7 not taken.
|
34903 | if (antenna.autoDetermine && gnssObs->receiverInfo) |
| 498 | { | ||
| 499 |
1/2✓ Branch 4 taken 34903 times.
✗ Branch 5 not taken.
|
34903 | antennaType = gnssObs->receiverInfo->get().antennaType; |
| 500 | } | ||
| 501 | ✗ | else if (!antenna.autoDetermine) | |
| 502 | { | ||
| 503 | ✗ | antennaType = antenna.name; | |
| 504 | } | ||
| 505 |
1/2✓ Branch 1 taken 34903 times.
✗ Branch 2 not taken.
|
34903 | e_recvPosAPC = trafo::e_posARP2APC(e_recvPosAPC, gnssObs, freq, antennaType, nameId); |
| 506 | 34903 | } | |
| 507 | |||
| 508 | 160462 | return e_recvPosAPC; | |
| 509 | } | ||
| 510 | |||
| 511 | /// @brief Calculates the phase center variation | ||
| 512 | /// @param[in] freq Signal frequency | ||
| 513 | /// @param[in] insTime Time to lookup the PCV pattern | ||
| 514 | /// @param[in] elevation Elevation angle in [rad] | ||
| 515 | /// @param[in] azimuth Azimuth in [rad] or nullopt to use the azimuth independent (NOAZI) | ||
| 516 | /// @param[in] receiverType Receiver type to select the correct antenna | ||
| 517 | /// @param[in] gnssObs GNSS observation | ||
| 518 | /// @param[in] nameId Name and Id of the node used for log messages only | ||
| 519 | /// @return The PCV value in [m] | ||
| 520 | template<typename T> | ||
| 521 | 160461 | [[nodiscard]] T calcPCV(const Frequency& freq, | |
| 522 | const InsTime& insTime, | ||
| 523 | const T& elevation, | ||
| 524 | std::optional<double> azimuth, | ||
| 525 | size_t receiverType, | ||
| 526 | const std::shared_ptr<const GnssObs>& gnssObs, | ||
| 527 | [[maybe_unused]] const std::string& nameId) const | ||
| 528 | { | ||
| 529 | 160461 | if (std::isnan(elevation) | |
| 530 |
4/8✓ Branch 0 taken 160461 times.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 160461 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 1 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 160462 times.
|
160461 | || (azimuth.has_value() && std::isnan(*azimuth))) { return T(0.0); } |
| 531 | 160462 | const Antenna& antenna = _antenna.at(receiverType); | |
| 532 |
2/2✓ Branch 0 taken 34903 times.
✓ Branch 1 taken 125556 times.
|
160459 | if (antenna.correctPCV) |
| 533 | { | ||
| 534 | 34903 | std::string antennaType; | |
| 535 |
3/6✓ Branch 0 taken 34903 times.
✗ Branch 1 not taken.
✓ Branch 4 taken 34903 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 34903 times.
✗ Branch 7 not taken.
|
34903 | if (antenna.autoDetermine && gnssObs->receiverInfo) |
| 536 | { | ||
| 537 |
1/2✓ Branch 4 taken 34903 times.
✗ Branch 5 not taken.
|
34903 | antennaType = gnssObs->receiverInfo->get().antennaType; |
| 538 | } | ||
| 539 | ✗ | else if (!antenna.autoDetermine) | |
| 540 | { | ||
| 541 | ✗ | antennaType = antenna.name; | |
| 542 | } | ||
| 543 |
1/2✓ Branch 3 taken 34903 times.
✗ Branch 4 not taken.
|
34903 | return AntexReader::Get().getAntennaPhaseCenterVariation(antennaType, |
| 544 | Frequency_(freq), | ||
| 545 | insTime, | ||
| 546 | elevation, | ||
| 547 | azimuth, | ||
| 548 | nameId) | ||
| 549 | 34903 | .value_or(T(0.0)); | |
| 550 | 34903 | } | |
| 551 | |||
| 552 | 125556 | return T(0.0); | |
| 553 | } | ||
| 554 | |||
| 555 | /// @brief Calculate the ionospheric delay with the model selected in the GUI | ||
| 556 | /// @param[in] freq Frequency of the signal | ||
| 557 | /// @param[in] freqNum Frequency number. Only used for GLONASS G1 and G2 | ||
| 558 | /// @param[in] lla_recvPosAPC Receiver antenna phase center position in LLA coordinates [rad, rad, m] | ||
| 559 | /// @param[in] gnssObs GNSS observation | ||
| 560 | /// @param[in] ionosphericCorrections Ionospheric correction parameters collected from the Nav data | ||
| 561 | /// @param[in] elevation Satellite elevation [rad] | ||
| 562 | /// @param[in] azimuth Satellite azimuth [rad] | ||
| 563 | /// @return Estimated ionosphere propagation error [m] | ||
| 564 | double calculateIonosphericDelay(const Frequency& freq, | ||
| 565 | int8_t freqNum, | ||
| 566 | const Eigen::Vector3d& lla_recvPosAPC, | ||
| 567 | const std::shared_ptr<const GnssObs>& gnssObs, | ||
| 568 | const std::shared_ptr<const IonosphericCorrections>& ionosphericCorrections, | ||
| 569 | double elevation, | ||
| 570 | double azimuth) const | ||
| 571 | { | ||
| 572 | return calcIonosphericDelay(static_cast<double>(gnssObs->insTime.toGPSweekTow().tow), | ||
| 573 | freq, freqNum, lla_recvPosAPC, | ||
| 574 | elevation, azimuth, | ||
| 575 | _ionosphereModel, ionosphericCorrections.get()); | ||
| 576 | } | ||
| 577 | |||
| 578 | /// @brief Calculate the tropospheric delay with the model selected in the GUI | ||
| 579 | /// @param[in] lla_recvPosAPC Receiver antenna phase center position in LLA coordinates [rad, rad, m] | ||
| 580 | /// @param[in] gnssObs GNSS observation | ||
| 581 | /// @param[in] elevation Satellite elevation [rad] | ||
| 582 | /// @param[in] azimuth Satellite azimuth [rad] | ||
| 583 | /// @param[in] nameId Name and Id of the node used for log messages only | ||
| 584 | /// @return Estimated troposphere propagation error [m] | ||
| 585 | double calculateTroposphericDelay(const Eigen::Vector3d& lla_recvPosAPC, | ||
| 586 | const std::shared_ptr<const GnssObs>& gnssObs, | ||
| 587 | double elevation, | ||
| 588 | double azimuth, | ||
| 589 | [[maybe_unused]] const std::string& nameId) const | ||
| 590 | { | ||
| 591 | auto tropo_r_s = calcTroposphericDelayAndMapping(gnssObs->insTime, lla_recvPosAPC, | ||
| 592 | elevation, azimuth, _troposphereModels, nameId); | ||
| 593 | |||
| 594 | return tropo_r_s.ZHD * tropo_r_s.zhdMappingFactor + tropo_r_s.ZWD * tropo_r_s.zwdMappingFactor; | ||
| 595 | } | ||
| 596 | |||
| 597 | /// @brief Shows the GUI input to select the options | ||
| 598 | /// @param[in] id Unique id for ImGui. | ||
| 599 | /// @param[in] itemWidth Width of the widgets | ||
| 600 | template<typename ReceiverType> | ||
| 601 | ✗ | bool ShowGuiWidgets(const char* id, float itemWidth) | |
| 602 | { | ||
| 603 | ✗ | bool changed = false; | |
| 604 | |||
| 605 | ✗ | ImGui::SetNextItemOpen(true, ImGuiCond_FirstUseEver); | |
| 606 | ✗ | if (ImGui::TreeNode(fmt::format("Compensation models##{}", id).c_str())) | |
| 607 | { | ||
| 608 | ✗ | ImGui::SetNextItemWidth(itemWidth - ImGui::GetStyle().IndentSpacing); | |
| 609 | ✗ | if (ComboIonosphereModel(fmt::format("Ionosphere Model##{}", id).c_str(), _ionosphereModel)) | |
| 610 | { | ||
| 611 | ✗ | LOG_DEBUG("{}: Ionosphere Model changed to {}", id, NAV::to_string(_ionosphereModel)); | |
| 612 | ✗ | changed = true; | |
| 613 | } | ||
| 614 | ✗ | if (ComboTroposphereModel(fmt::format("Troposphere Model##{}", id).c_str(), _troposphereModels, itemWidth - ImGui::GetStyle().IndentSpacing)) | |
| 615 | { | ||
| 616 | ✗ | changed = true; | |
| 617 | } | ||
| 618 | ✗ | ImGui::TreePop(); | |
| 619 | } | ||
| 620 | |||
| 621 | ✗ | ImGui::SetNextItemOpen(true, ImGuiCond_FirstUseEver); | |
| 622 | ✗ | if (ImGui::TreeNode(fmt::format("GNSS Measurement Error##{}", id).c_str())) | |
| 623 | { | ||
| 624 | ✗ | if (_gnssMeasurementErrorModel.ShowGuiWidgets(id, itemWidth - ImGui::GetStyle().IndentSpacing)) | |
| 625 | { | ||
| 626 | ✗ | LOG_DEBUG("{}: GNSS Measurement Error Model changed.", id); | |
| 627 | ✗ | changed = true; | |
| 628 | } | ||
| 629 | ✗ | ImGui::TreePop(); | |
| 630 | } | ||
| 631 | |||
| 632 | ✗ | if (ImGui::TreeNode(fmt::format("Antenna settings##{}", id).c_str())) | |
| 633 | { | ||
| 634 | ✗ | for (size_t i = 0; i < _receiverCount; ++i) | |
| 635 | { | ||
| 636 | ✗ | if (_receiverCount > 1) | |
| 637 | { | ||
| 638 | ✗ | ImGui::SetNextItemOpen(true, ImGuiCond_FirstUseEver); | |
| 639 | ✗ | if (!ImGui::TreeNode(fmt::format("{}", static_cast<ReceiverType>(i)).c_str())) { continue; } | |
| 640 | } | ||
| 641 | |||
| 642 | ✗ | if (ImGui::Checkbox(fmt::format("Correct Phase-center offset##antenna {} {}", id, i).c_str(), &_antenna.at(i).correctPCO)) | |
| 643 | { | ||
| 644 | ✗ | LOG_DEBUG("{}: Antenna {} correctPCO: {}", id, i, _antenna.at(i).correctPCO); | |
| 645 | ✗ | changed = true; | |
| 646 | } | ||
| 647 | ✗ | ImGui::SameLine(); | |
| 648 | ✗ | if (ImGui::Checkbox(fmt::format("Correct Phase-center variation##antenna {} {}", id, i).c_str(), &_antenna.at(i).correctPCV)) | |
| 649 | { | ||
| 650 | ✗ | LOG_DEBUG("{}: Antenna {} correctPCV: {}", id, i, _antenna.at(i).correctPCV); | |
| 651 | ✗ | changed = true; | |
| 652 | } | ||
| 653 | |||
| 654 | ✗ | if (!_antenna.at(i).correctPCO && !_antenna.at(i).correctPCV) { ImGui::BeginDisabled(); } | |
| 655 | ✗ | if (ImGui::Checkbox(fmt::format("Auto determine##antenna {} {}", id, i).c_str(), &_antenna.at(i).autoDetermine)) | |
| 656 | { | ||
| 657 | ✗ | LOG_DEBUG("{}: Antenna {} auto: {}", id, i, _antenna.at(i).autoDetermine); | |
| 658 | ✗ | changed = true; | |
| 659 | } | ||
| 660 | ✗ | ImGui::SameLine(); | |
| 661 | ✗ | if (_antenna.at(i).autoDetermine) { ImGui::BeginDisabled(); } | |
| 662 | ✗ | ImGui::SetNextItemWidth(itemWidth - (1.0F + static_cast<float>(_receiverCount > 1)) * ImGui::GetStyle().IndentSpacing); | |
| 663 | |||
| 664 | ✗ | if (ImGui::BeginCombo(fmt::format("Type##Combo {} {}", id, i).c_str(), _antenna.at(i).name.c_str())) | |
| 665 | { | ||
| 666 | ✗ | ImGui::PushFont(Application::MonoFont()); | |
| 667 | ✗ | if (ImGui::Selectable(fmt::format("##Selectable {} {}", id, i).c_str(), _antenna.at(i).name.empty())) | |
| 668 | { | ||
| 669 | ✗ | _antenna.at(i).name = ""; | |
| 670 | } | ||
| 671 | |||
| 672 | ✗ | for (const auto& iter : AntexReader::Get().antennas()) | |
| 673 | { | ||
| 674 | ✗ | const bool is_selected = (_antenna.at(i).name == iter); | |
| 675 | ✗ | if (ImGui::Selectable(iter.c_str(), is_selected)) | |
| 676 | { | ||
| 677 | ✗ | _antenna.at(i).name = iter; | |
| 678 | } | ||
| 679 | ✗ | if (is_selected) { ImGui::SetItemDefaultFocus(); } | |
| 680 | } | ||
| 681 | ✗ | ImGui::PopFont(); | |
| 682 | ✗ | ImGui::EndCombo(); | |
| 683 | } | ||
| 684 | ✗ | if (_antenna.at(i).autoDetermine) { ImGui::EndDisabled(); } | |
| 685 | ✗ | if (!_antenna.at(i).correctPCO && !_antenna.at(i).correctPCV) { ImGui::EndDisabled(); } | |
| 686 | ✗ | ImGui::SameLine(); | |
| 687 | ✗ | gui::widgets::HelpMarker("Used for lookup in ANTEX file."); | |
| 688 | |||
| 689 | ✗ | ImGui::SetNextItemWidth(itemWidth - (1.0F + static_cast<float>(_receiverCount > 1)) * ImGui::GetStyle().IndentSpacing); | |
| 690 | ✗ | if (ImGui::InputDouble3(fmt::format("Delta H/E/N [m]##{} {}", id, i).c_str(), _antenna.at(i).hen_delta.data(), "%.3f")) | |
| 691 | { | ||
| 692 | ✗ | LOG_DEBUG("{}: Antenna {} delta: {}", id, i, _antenna.at(i).hen_delta.transpose()); | |
| 693 | ✗ | changed = true; | |
| 694 | } | ||
| 695 | ✗ | ImGui::SameLine(); | |
| 696 | ✗ | gui::widgets::HelpMarker("- Antenna height: Height of the antenna reference point (ARP) above the marker\n" | |
| 697 | "- Horizontal eccentricity of ARP relative to the marker (east/north)"); | ||
| 698 | |||
| 699 | ✗ | if (_receiverCount > 1) { ImGui::TreePop(); } | |
| 700 | } | ||
| 701 | ✗ | ImGui::TreePop(); | |
| 702 | } | ||
| 703 | |||
| 704 | ✗ | return changed; | |
| 705 | } | ||
| 706 | |||
| 707 | /// @brief Get the Ionosphere Model selected | ||
| 708 | [[nodiscard]] const IonosphereModel& getIonosphereModel() const { return _ionosphereModel; } | ||
| 709 | /// @brief Get the Troposphere Model selection | ||
| 710 | [[nodiscard]] const TroposphereModelSelection& getTroposphereModels() const { return _troposphereModels; } | ||
| 711 | /// @brief Get the GNSS measurement error model | ||
| 712 | [[nodiscard]] const GnssMeasurementErrorModel& getGnssMeasurementErrorModel() const { return _gnssMeasurementErrorModel; } | ||
| 713 | |||
| 714 | private: | ||
| 715 | IonosphereModel _ionosphereModel = IonosphereModel::Klobuchar; ///< Ionosphere Model used for the calculation | ||
| 716 | TroposphereModelSelection _troposphereModels; ///< Troposphere Models used for the calculation | ||
| 717 | GnssMeasurementErrorModel _gnssMeasurementErrorModel; ///< GNSS measurement error model to use | ||
| 718 | |||
| 719 | size_t _receiverCount; ///< Number of receivers (used for GUI) | ||
| 720 | |||
| 721 | /// Antenna information | ||
| 722 | struct Antenna | ||
| 723 | { | ||
| 724 | bool correctPCO = true; ///< Correct phase center offset | ||
| 725 | bool correctPCV = true; ///< Correct phase center variation | ||
| 726 | bool autoDetermine = true; ///< Try determine antenna type from e.g. RINEX file | ||
| 727 | std::string name; ///< Type of the antenna for the Antex lookup | ||
| 728 | Eigen::Vector3d hen_delta = Eigen::Vector3d::Zero(); ///< Delta values of marker to antenna base | ||
| 729 | }; | ||
| 730 | |||
| 731 | std::unordered_map<size_t, Antenna> _antenna; ///< User antenna selection. Key is receiver type | ||
| 732 | |||
| 733 | /// @brief Converts the provided object into json | ||
| 734 | /// @param[out] j Json object which gets filled with the info | ||
| 735 | /// @param[in] obj Object to convert into json | ||
| 736 | ✗ | friend void to_json(json& j, const ObservationEstimator::Antenna& obj) | |
| 737 | { | ||
| 738 | ✗ | j = json{ | |
| 739 | ✗ | { "correctPCO", obj.correctPCO }, | |
| 740 | ✗ | { "correctPCV", obj.correctPCV }, | |
| 741 | ✗ | { "autoDetermine", obj.autoDetermine }, | |
| 742 | ✗ | { "name", obj.name }, | |
| 743 | ✗ | { "hen_delta", obj.hen_delta }, | |
| 744 | ✗ | }; | |
| 745 | ✗ | } | |
| 746 | /// @brief Converts the provided json object into a node object | ||
| 747 | /// @param[in] j Json object with the needed values | ||
| 748 | /// @param[out] obj Object to fill from the json | ||
| 749 | 9 | friend void from_json(const json& j, ObservationEstimator::Antenna& obj) | |
| 750 | { | ||
| 751 |
2/2✓ Branch 1 taken 3 times.
✓ Branch 2 taken 6 times.
|
9 | if (j.contains("correctPCO")) { j.at("correctPCO").get_to(obj.correctPCO); } |
| 752 |
2/2✓ Branch 1 taken 3 times.
✓ Branch 2 taken 6 times.
|
9 | if (j.contains("correctPCV")) { j.at("correctPCV").get_to(obj.correctPCV); } |
| 753 |
1/2✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
|
9 | if (j.contains("autoDetermine")) { j.at("autoDetermine").get_to(obj.autoDetermine); } |
| 754 |
1/2✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
|
9 | if (j.contains("name")) { j.at("name").get_to(obj.name); } |
| 755 |
1/2✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
|
9 | if (j.contains("hen_delta")) { j.at("hen_delta").get_to(obj.hen_delta); } |
| 756 | 9 | } | |
| 757 | |||
| 758 | /// @brief Converts the provided object into json | ||
| 759 | /// @param[out] j Json object which gets filled with the info | ||
| 760 | /// @param[in] obj Object to convert into json | ||
| 761 | ✗ | friend void to_json(json& j, const ObservationEstimator& obj) | |
| 762 | { | ||
| 763 | ✗ | j = json{ | |
| 764 | ✗ | { "ionosphereModel", obj._ionosphereModel }, | |
| 765 | ✗ | { "troposphereModels", obj._troposphereModels }, | |
| 766 | ✗ | { "gnssMeasurementError", obj._gnssMeasurementErrorModel }, | |
| 767 | ✗ | { "antenna", obj._antenna }, | |
| 768 | ✗ | }; | |
| 769 | ✗ | } | |
| 770 | /// @brief Converts the provided json object into a node object | ||
| 771 | /// @param[in] j Json object with the needed values | ||
| 772 | /// @param[out] obj Object to fill from the json | ||
| 773 | 9 | friend void from_json(const json& j, ObservationEstimator& obj) | |
| 774 | { | ||
| 775 |
1/2✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
|
9 | if (j.contains("ionosphereModel")) { j.at("ionosphereModel").get_to(obj._ionosphereModel); } |
| 776 |
1/2✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
|
9 | if (j.contains("troposphereModels")) { j.at("troposphereModels").get_to(obj._troposphereModels); } |
| 777 |
1/2✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
|
9 | if (j.contains("gnssMeasurementError")) { j.at("gnssMeasurementError").get_to(obj._gnssMeasurementErrorModel); } |
| 778 |
2/2✓ Branch 1 taken 8 times.
✓ Branch 2 taken 1 times.
|
9 | if (j.contains("antenna")) { j.at("antenna").get_to(obj._antenna); } |
| 779 | 9 | } | |
| 780 | }; | ||
| 781 | |||
| 782 | } // namespace NAV | ||
| 783 |