INSTINCT Code Coverage Report


Directory: src/
File: Navigation/GNSS/Positioning/ObservationEstimator.hpp
Date: 2025-02-07 16:54:41
Exec Total Coverage
Lines: 103 198 52.0%
Functions: 7 10 70.0%
Branches: 77 359 21.4%

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