INSTINCT Code Coverage Report


Directory: src/
File: NodeData/GNSS/GnssObs.hpp
Date: 2025-02-07 16:54:41
Exec Total Coverage
Lines: 38 101 37.6%
Functions: 13 21 61.9%
Branches: 13 143 9.1%

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 GnssObs.hpp
10 /// @brief GNSS Observation messages
11 /// @author T. Topp (topp@ins.uni-stuttgart.de)
12 /// @date 2022-04-26
13
14 #pragma once
15
16 #include <cstdint>
17 #include <limits>
18 #include <optional>
19 #include <vector>
20 #include <algorithm>
21 #include <Eigen/Core>
22
23 #include "NodeData/NodeData.hpp"
24
25 #include "Navigation/GNSS/Core/SatelliteIdentifier.hpp"
26 #include "Navigation/GNSS/Core/Code.hpp"
27 #include "util/Assert.h"
28
29 namespace NAV
30 {
31 /// GNSS Observation message information
32 class GnssObs : public NodeData
33 {
34 public:
35 /// @brief Observation types
36 enum ObservationType : uint8_t
37 {
38 Pseudorange, ///< Pseudorange
39 Carrier, ///< Carrier-Phase
40 Doppler, ///< Doppler (Pseudorange rate)
41 ObservationType_COUNT, ///< Count
42 };
43
44 /// @brief Stores the satellites observations
45 struct ObservationData
46 {
47 /// Pseudorange
48 struct Pseudorange
49 {
50 /// Pseudorange measurement [m]
51 double value = 0.0;
52
53 /// @brief Signal Strength Indicator (SSI) projected into interval 1-9
54 ///
55 /// Carrier to Noise ratio(dbHz) | Carrier to Noise ratio(RINEX)
56 /// :-: | ---
57 /// - | 0 or blank: not known, don't care
58 /// < 12 | 1 (minimum possible signal strength)
59 /// 12-17 | 2
60 /// 18-23 | 3
61 /// 24-29 | 4
62 /// 30-35 | 5 (threshold for good tracking)
63 /// 36-41 | 6
64 /// 42-47 | 7
65 /// 48-53 | 8
66 /// ≥ 54 | 9 (maximum possible signal strength)
67 uint8_t SSI = 0;
68 };
69
70 /// Carrier phase
71 struct CarrierPhase
72 {
73 /// Carrier phase measurement [cycles]
74 double value = 0.0;
75
76 /// @brief Signal Strength Indicator (SSI) projected into interval 1-9
77 ///
78 /// Carrier to Noise ratio(dbHz) | Carrier to Noise ratio(RINEX)
79 /// :-: | ---
80 /// - | 0 or blank: not known, don't care
81 /// < 12 | 1 (minimum possible signal strength)
82 /// 12-17 | 2
83 /// 18-23 | 3
84 /// 24-29 | 4
85 /// 30-35 | 5 (threshold for good tracking)
86 /// 36-41 | 6
87 /// 42-47 | 7
88 /// 48-53 | 8
89 /// ≥ 54 | 9 (maximum possible signal strength)
90 uint8_t SSI = 0;
91
92 /// Loss of Lock Indicator [0...6] (only associated with the phase observation)
93 uint8_t LLI = 0;
94 };
95
96 /// @brief Constructor
97 /// @param[in] satSigId Satellite signal identifier (frequency and satellite number)
98 522246 explicit ObservationData(const SatSigId& satSigId) : satSigId(satSigId) {}
99
100 #ifdef TESTING
101 /// @brief Constructor
102 /// @param[in] satSigId Satellite signal identifier (frequency and satellite number)
103 /// @param[in] pseudorange Pseudorange measurement [m] and Signal Strength Indicator (SSI)
104 /// @param[in] carrierPhase Carrier phase measurement [cycles], Signal Strength Indicator (SSI) and Loss of Lock Indicator (LLI)
105 /// @param[in] doppler Doppler measurement [Hz]
106 /// @param[in] CN0 Carrier-to-Noise density [dBHz]
107 23672 ObservationData(const SatSigId& satSigId,
108 std::optional<Pseudorange> pseudorange,
109 std::optional<CarrierPhase> carrierPhase,
110 std::optional<double> doppler,
111 std::optional<double> CN0)
112 23672 : satSigId(satSigId),
113 23672 pseudorange(pseudorange),
114 23672 carrierPhase(carrierPhase),
115 23672 doppler(doppler),
116 23672 CN0(CN0)
117 23672 {}
118 #endif
119
120 SatSigId satSigId = { Code::None, 0 }; ///< SignalId and satellite number
121 std::optional<Pseudorange> pseudorange; ///< Pseudorange measurement [m]
122 std::optional<CarrierPhase> carrierPhase; ///< Carrier phase measurement [cycles]
123 std::optional<double> doppler; ///< Doppler measurement [Hz]
124 std::optional<double> CN0; ///< Carrier-to-Noise density [dBHz]
125 };
126
127 /// @brief Useful information of the satellites
128 struct SatelliteData
129 {
130 SatId satId; ///< Satellite identifier
131 Frequency frequencies = Freq_None; ///< Frequencies transmitted by this satellite
132 };
133
134 #ifdef TESTING
135 /// Default constructor
136 9045 GnssObs() = default;
137
138 /// @brief Constructor
139 /// @param[in] insTime Epoch time
140 /// @param[in] data Observation data
141 /// @param[in] satData Satellite data
142 807 GnssObs(const InsTime& insTime, std::vector<ObservationData> data, std::vector<SatelliteData> satData)
143 807 : data(std::move(data)), _satData(std::move(satData))
144 {
145 807 this->insTime = insTime;
146 807 }
147 #endif
148 /// @brief Returns the type of the data class
149 /// @return The data type
150 583140 [[nodiscard]] static std::string type()
151 {
152
1/2
✓ Branch 1 taken 583140 times.
✗ Branch 2 not taken.
1166280 return "GnssObs";
153 }
154
155 /// @brief Returns the type of the data class
156 /// @return The data type
157 [[nodiscard]] std::string getType() const override { return type(); }
158
159 /// @brief Returns the parent types of the data class
160 /// @return The parent data types
161 112 [[nodiscard]] static std::vector<std::string> parentTypes()
162 {
163
3/6
✓ Branch 1 taken 112 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 112 times.
✓ Branch 4 taken 112 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
336 return { NodeData::type() };
164 112 }
165
166 /// @brief Satellite observations
167 std::vector<ObservationData> data;
168
169 /// @brief Access or insert the satellite data
170 /// @param satId Satellite identifier
171 /// @return The satellite data
172 1875890 SatelliteData& satData(const SatId& satId)
173 {
174
1/2
✓ Branch 1 taken 1875923 times.
✗ Branch 2 not taken.
1875890 auto iter = std::ranges::find_if(_satData, [&satId](const SatelliteData& sat) {
175 33280141 return sat.satId == satId;
176 });
177
2/2
✓ Branch 2 taken 1618027 times.
✓ Branch 3 taken 258004 times.
1875923 if (iter != _satData.end())
178 {
179 1618027 return *iter;
180 }
181
182
1/2
✓ Branch 1 taken 257985 times.
✗ Branch 2 not taken.
258004 _satData.emplace_back();
183 257985 _satData.back().satId = satId;
184 257990 return _satData.back();
185 }
186
187 /// @brief Access the satellite data
188 /// @param satId Satellite identifier
189 /// @return The satellite data if in the list
190 [[nodiscard]] std::optional<std::reference_wrapper<const SatelliteData>> satData(const SatId& satId) const
191 {
192 auto iter = std::ranges::find_if(_satData, [&satId](const SatelliteData& sat) {
193 return sat.satId == satId;
194 });
195 if (iter != _satData.end())
196 {
197 return *iter;
198 }
199 return std::nullopt;
200 }
201
202 /// @brief Checks if an element with the identifier exists
203 /// @param[in] satSigId Signal id
204 /// @return True if the element exists
205 14221972 [[nodiscard]] bool contains(const SatSigId& satSigId) const
206 {
207
1/2
✓ Branch 1 taken 14221300 times.
✗ Branch 2 not taken.
14221972 auto iter = std::ranges::find_if(data, [&satSigId](const ObservationData& idData) {
208 700897311 return idData.satSigId == satSigId;
209 });
210 14221300 return iter != data.end();
211 }
212
213 /// @brief Return the element with the identifier or a newly constructed one if it did not exist
214 /// @param[in] satSigId Signal id
215 /// @return The element found in the observations or a newly constructed one
216 1846786 ObservationData& operator()(const SatSigId& satSigId)
217 {
218
1/2
✓ Branch 1 taken 1847268 times.
✗ Branch 2 not taken.
1846786 auto iter = std::ranges::find_if(data, [&satSigId](const ObservationData& idData) {
219 86940988 return idData.satSigId == satSigId;
220 });
221
2/2
✓ Branch 2 taken 1353799 times.
✓ Branch 3 taken 493590 times.
1847268 if (iter != data.end())
222 {
223 1353799 return *iter;
224 }
225
226
1/2
✓ Branch 1 taken 493559 times.
✗ Branch 2 not taken.
493590 data.emplace_back(satSigId);
227 493559 return data.back();
228 }
229
230 /// @brief Return the element with the identifier
231 /// @param[in] satSigId Signal id
232 /// @return The element found in the observations
233 [[nodiscard]] std::optional<std::reference_wrapper<const ObservationData>> operator()(const SatSigId& satSigId) const
234 {
235 auto iter = std::ranges::find_if(data, [&satSigId](const ObservationData& idData) {
236 return idData.satSigId == satSigId;
237 });
238
239 if (iter != data.end())
240 {
241 return *iter;
242 }
243 return std::nullopt;
244 }
245
246 /// @brief Useful information of the satellites
247 270766 [[nodiscard]] const std::vector<SatelliteData>& getSatData() const { return _satData; }
248
249 /// @brief Returns a vector of data descriptors for the dynamic data
250 [[nodiscard]] std::vector<std::string> dynamicDataDescriptors() const override
251 {
252 std::vector<std::string> descriptors;
253 descriptors.reserve(data.size() * 7);
254
255 for (const auto& obsData : data)
256 {
257 descriptors.push_back(fmt::format("{} Pseudorange [m]", obsData.satSigId));
258 descriptors.push_back(fmt::format("{} Pseudorange SSI", obsData.satSigId));
259
260 descriptors.push_back(fmt::format("{} Carrier-phase [cycles]", obsData.satSigId));
261 descriptors.push_back(fmt::format("{} Carrier-phase SSI", obsData.satSigId));
262 descriptors.push_back(fmt::format("{} Carrier-phase LLI", obsData.satSigId));
263
264 descriptors.push_back(fmt::format("{} Doppler [Hz]", obsData.satSigId));
265 descriptors.push_back(fmt::format("{} Carrier-to-Noise density [dBHz]", obsData.satSigId));
266 }
267
268 return descriptors;
269 }
270
271 /// @brief Get the value for the descriptor
272 /// @return Value if in the observation
273 [[nodiscard]] std::optional<double> getDynamicDataAt(const std::string& descriptor) const override
274 {
275 for (const auto& obsData : data)
276 {
277 if (descriptor == fmt::format("{} Pseudorange [m]", obsData.satSigId) && obsData.pseudorange)
278 {
279 return obsData.pseudorange->value;
280 }
281 if (descriptor == fmt::format("{} Pseudorange SSI", obsData.satSigId) && obsData.pseudorange)
282 {
283 return obsData.pseudorange->SSI;
284 }
285 if (descriptor == fmt::format("{} Carrier-phase [cycles]", obsData.satSigId) && obsData.carrierPhase)
286 {
287 return obsData.carrierPhase->value;
288 }
289 if (descriptor == fmt::format("{} Carrier-phase SSI", obsData.satSigId) && obsData.carrierPhase)
290 {
291 return obsData.carrierPhase->SSI;
292 }
293 if (descriptor == fmt::format("{} Carrier-phase LLI", obsData.satSigId) && obsData.carrierPhase)
294 {
295 return obsData.carrierPhase->LLI;
296 }
297 if (descriptor == fmt::format("{} Doppler [Hz]", obsData.satSigId))
298 {
299 return obsData.doppler;
300 }
301 if (descriptor == fmt::format("{} Carrier-to-Noise density [dBHz]", obsData.satSigId))
302 {
303 return obsData.CN0;
304 }
305 }
306 return std::nullopt;
307 }
308
309 /// @brief Returns a vector of data descriptors and values for the dynamic data
310 [[nodiscard]] std::vector<std::pair<std::string, double>> getDynamicData() const override
311 {
312 std::vector<std::pair<std::string, double>> dynData;
313 dynData.reserve(data.size() * 7);
314 for (const auto& obsData : data)
315 {
316 if (obsData.pseudorange) { dynData.emplace_back(fmt::format("{} Pseudorange [m]", obsData.satSigId), obsData.pseudorange->value); }
317 if (obsData.pseudorange) { dynData.emplace_back(fmt::format("{} Pseudorange SSI", obsData.satSigId), obsData.pseudorange->SSI); }
318
319 if (obsData.carrierPhase) { dynData.emplace_back(fmt::format("{} Carrier-phase [cycles]", obsData.satSigId), obsData.carrierPhase->value); }
320 if (obsData.carrierPhase) { dynData.emplace_back(fmt::format("{} Carrier-phase SSI", obsData.satSigId), obsData.carrierPhase->SSI); }
321 if (obsData.carrierPhase) { dynData.emplace_back(fmt::format("{} Carrier-phase LLI", obsData.satSigId), obsData.carrierPhase->LLI); }
322
323 if (obsData.doppler) { dynData.emplace_back(fmt::format("{} Doppler [Hz]", obsData.satSigId), obsData.doppler.value()); }
324
325 if (obsData.CN0) { dynData.emplace_back(fmt::format("{} Carrier-to-Noise density [dBHz]", obsData.satSigId), obsData.CN0.value()); }
326 }
327 return dynData;
328 }
329
330 /// Receiver Information, e.g. from RINEX header
331 struct ReceiverInfo
332 {
333 ///< Approximate receiver position in [m], e.g. from RINEX header
334 std::optional<Eigen::Vector3d> e_approxPos;
335
336 /// Antenna Type. Empty if unknown
337 std::string antennaType;
338
339 /// @brief Antenna Delta (North, East, Up) in [m]
340 ///
341 /// - Horizontal eccentricity of ARP relative to the marker (north/east)
342 /// - Height of the antenna reference point (ARP) above the marker
343 Eigen::Vector3d antennaDeltaNEU = Eigen::Vector3d::Zero();
344 };
345
346 /// Optional Receiver Information, e.g. from RINEX header
347 std::optional<std::reference_wrapper<ReceiverInfo>> receiverInfo;
348
349 private:
350 /// @brief Useful information of the satellites
351 std::vector<SatelliteData> _satData;
352 };
353
354 /// @brief Converts the enum to a string
355 /// @param[in] obsType Enum value to convert into text
356 /// @return String representation of the enum
357 constexpr const char* to_string(GnssObs::ObservationType obsType)
358 {
359 switch (obsType)
360 {
361 case GnssObs::Pseudorange:
362 return "Pseudorange";
363 case GnssObs::Carrier:
364 return "Carrier";
365 case GnssObs::Doppler:
366 return "Doppler";
367 case GnssObs::ObservationType_COUNT:
368 return "COUNT";
369 }
370 return "";
371 }
372
373 } // namespace NAV
374
375 #ifndef DOXYGEN_IGNORE
376
377 template<>
378 struct fmt::formatter<NAV::GnssObs::ObservationType> : fmt::formatter<const char*>
379 {
380 /// @brief Defines how to format Frequency structs
381 /// @param[in] obsType Struct to format
382 /// @param[in, out] ctx Format context
383 /// @return Output iterator
384 template<typename FormatContext>
385 auto format(const NAV::GnssObs::ObservationType& obsType, FormatContext& ctx) const
386 {
387 return fmt::formatter<const char*>::format(to_string(obsType), ctx);
388 }
389 };
390
391 #endif
392
393 /// @brief Stream insertion operator overload
394 /// @param[in, out] os Output stream object to stream the time into
395 /// @param[in] obj Object to print
396 /// @return Returns the output stream object in order to chain stream insertions
397 std::ostream& operator<<(std::ostream& os, const NAV::GnssObs::ObservationType& obj);
398