INSTINCT Code Coverage Report


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