0.4.1
Loading...
Searching...
No Matches
RinexNavFile.cpp
Go to the documentation of this file.
1// This file is part of INSTINCT, the INS Toolkit for Integrated
2// Navigation Concepts and Training by the Institute of Navigation of
3// the University of Stuttgart, Germany.
4//
5// This Source Code Form is subject to the terms of the Mozilla Public
6// License, v. 2.0. If a copy of the MPL was not distributed with this
7// file, You can obtain one at https://mozilla.org/MPL/2.0/.
8
9#include "RinexNavFile.hpp"
10
11#include <bitset>
12#include <fmt/ranges.h>
13
15namespace nm = NAV::NodeManager;
17
18#include "util/StringUtil.hpp"
19
27
28namespace NAV
29{
40
42{
43 LOG_TRACE("{}: called", nameId());
44}
45
47{
48 return "RinexNavFile";
49}
50
51std::string RinexNavFile::type() const
52{
53 return typeStatic();
54}
55
57{
58 return "Data Provider";
59}
60
62{
63 if (auto res = FileReader::guiConfig(R"(Rinex Nav (.nav .rnx .gal .geo .glo .*N .*P){.nav,.rnx,.gal,.geo,.glo,(.+[.]\d\d?[Nn]),(.+[.]\d\d?[Ll]),(.+[.]\d\d?[Pp])},.*)",
64 { ".nav", ".rnx", ".gal", ".geo", ".glo", "(.+[.]\\d\\d?[Nn])", "(.+[.]\\d\\d?[Ll])", "(.+[.]\\d\\d?[Pp])" }, size_t(id), nameId()))
65 {
66 LOG_DEBUG("{}: Path changed to {}", nameId(), _path);
68 if (res == FileReader::PATH_CHANGED)
69 {
71 }
72 else
73 {
75 }
76 }
77 ImGui::Text("Supported versions: ");
78 std::ranges::for_each(_supportedVersions, [](double x) {
79 ImGui::SameLine();
80 ImGui::Text("%0.2f", x);
81 });
82}
83
84[[nodiscard]] json RinexNavFile::save() const
85{
86 LOG_TRACE("{}: called", nameId());
87
88 json j;
89
90 j["FileReader"] = FileReader::save();
91
92 return j;
93}
94
96{
97 LOG_TRACE("{}: called", nameId());
98
99 if (j.contains("FileReader"))
100 {
101 FileReader::restore(j.at("FileReader"));
102 }
103}
104
106{
107 LOG_TRACE("{}: called", nameId());
108
109 {
110 // The guards needs to be released before FileReader::initialize()
112 _gnssNavInfo.reset();
113 }
114 _version = 0.0;
116
118 {
119 return false;
120 }
121
122 readOrbits();
123
124 return true;
125}
126
128{
129 LOG_TRACE("{}: called", nameId());
130
132}
133
135{
136 LOG_TRACE("{}: called", nameId());
137
139
140 return true;
141}
142
144{
145 auto extHeaderLabel = [](const std::string& line) {
146 return str::trim_copy(std::string_view(line).substr(60, 20));
147 };
148
149 std::filesystem::path filepath = getFilepath();
150
151 auto filestreamHeader = std::ifstream(filepath);
152 if (filestreamHeader.good())
153 {
154 std::string line;
155 // --------------------------------------- RINEX VERSION / TYPE ------------------------------------------
156 while (line.find("RINEX VERSION / TYPE") == std::string::npos && !filestreamHeader.eof())
157 {
158 std::getline(filestreamHeader, line);
159 }
160 if (filestreamHeader.eof())
161 {
162 LOG_ERROR("{}: Not a valid RINEX NAV file. Could not read 'RINEX VERSION / TYPE' line.", nameId());
164 }
165 str::rtrim(line);
166 if (str::rtrim_copy(line).size() != 80)
167 {
168 LOG_ERROR("{}: Not a valid RINEX NAV file. Lines should be 80 characters long but the file has {}.", nameId(), line.size() - 1);
170 }
171
172 if (extHeaderLabel(line) != "RINEX VERSION / TYPE")
173 {
174 LOG_ERROR("{}: Not a valid RINEX NAV file. Could not read 'RINEX VERSION / TYPE' line.", nameId());
176 }
177
178 _version = std::stod(str::trim_copy(line.substr(0, 20))); // FORMAT: F9.2,11X
179 if (!_supportedVersions.contains(_version))
180 {
181 LOG_ERROR("{}: RINEX version {} is not supported. Supported versions are [{}]", nameId(),
182 _version, fmt::join(_supportedVersions.begin(), _supportedVersions.end(), ", "));
183 _version = 0.0;
185 }
186
187 std::string fileType = str::trim_copy(line.substr(20, 20)); // FORMAT: A1,19X
188 if (fileType.at(0) != 'N' // N: GNSS NAV DATA
189 && fileType != "NAVIGATION DATA"
190 && fileType != "GLONASS NAV DATA")
191 {
192 LOG_ERROR("{}: Not a valid RINEX NAV file. File type '{}' not recognized.", nameId(), fileType);
195 }
196 std::string satSystem;
197 if (_version < 3.0)
198 {
199 if (fileType == "NAVIGATION DATA")
200 {
201 satSystem = 'G';
202 }
203 else if (fileType == "GLONASS NAV DATA")
204 {
205 satSystem = 'R';
206 }
207 else if (fileType.at(0) == 'N')
208 {
209 satSystem = fileType.substr(3, 3);
210 }
211 }
212 else
213 {
214 satSystem = str::trim_copy(line.substr(40, 20)); // FORMAT: A1,19X
215 }
216 if (satSystem.empty() || (SatelliteSystem::fromChar(satSystem.at(0)) == SatSys_None && satSystem.at(0) != 'M'))
217 {
218 LOG_ERROR("{}: Not a valid RINEX NAV file. Satellite System '{}' not recognized.", nameId(), satSystem);
221 }
222 // ---------------------------------------- PGM / RUN BY / DATE ------------------------------------------
223 std::getline(filestreamHeader, line);
224 str::rtrim(line);
225 if (extHeaderLabel(line) != "PGM / RUN BY / DATE")
226 {
227 LOG_ERROR("{}: Not a valid RINEX NAV file. Could not read 'PGM / RUN BY / DATE' line.", nameId());
229 }
230
231 // ----------------------------------------- END OF HEADER -------------------------------------------
232 while (std::getline(filestreamHeader, line) && !filestreamHeader.eof())
233 {
234 str::rtrim(line);
235 if (extHeaderLabel(line) == "END OF HEADER")
236 {
238 }
239 }
240 LOG_ERROR("{}: Not a valid RINEX NAV file. Could not read 'END OF HEADER' line.", nameId());
242 }
243
244 LOG_ERROR("{}: Could not determine file type because file could not be opened '{}' line.", nameId(), filepath.string());
246}
247
248namespace
249{
250
251std::string_view extHeaderLabel(const std::string& line)
252{
253 return line.size() >= 60 ? str::trim_copy(std::string_view(line).substr(60, 20))
254 : std::string_view{};
255};
256
257} // namespace
258
260{
261 if (version < 3.0)
262 {
263 parseHeader2();
264 }
265 else if (version < 4.0)
266 {
267 parseHeader3();
268 }
269 else if (version == 4.0)
270 {
271 parseHeader4();
272 }
273 else
274 {
275 LOG_ERROR("{}: Unsupported RINEX version {}.", nameId(), version);
276 // return nullptr;
277 }
278}
279
281{
282 if (version < 3.0)
283 {
284 parseOrbit2();
285 }
286 else if (version < 4.0)
287 {
288 parseOrbit3();
289 }
290 else if (version == 4.0)
291 {
292 parseOrbit4();
293 }
294 else
295 {
296 LOG_ERROR("{}: Unsupported RINEX version {}.", nameId(), version);
297 // return nullptr;
298 }
299}
300
302{
303 std::string line;
304 // --------------------------------------- RINEX VERSION / TYPE ------------------------------------------
305 while (line.find("RINEX VERSION / TYPE") == std::string::npos && !eof())
306 {
307 getline(line);
308 }
310 LOG_DEBUG("{}: Version: {:3.2f}", nameId(), _version); // FORMAT: F9.2,11X
311 if ((line.substr(20, 15) == "N: GPS NAV DATA" || line.substr(20, 15) == "NAVIGATION DATA")
312 && (line.substr(40, 3) == "GPS" || str::trim_copy(line.substr(40, 3)).empty()))
313 {
314 LOG_DEBUG("{}: SatSys : {}", nameId(), "G: GPS"); // FORMAT: A1,19X
315 satSys = GPS;
316 _gnssNavInfo.satelliteSystems |= satSys;
317 }
318 else if (line.substr(20, 16) == "GLONASS NAV DATA")
319 {
320 LOG_DEBUG("{}: SatSys : {}", nameId(), "R: GLONASS"); // FORMAT: A1,19X
321 satSys = GLO;
322 _gnssNavInfo.satelliteSystems |= satSys;
323 }
324 // #######################################################################################################
325 while (getline(line) && !eof())
326 {
327 str::rtrim(line);
328 auto headerLabel = extHeaderLabel(line);
329 if (headerLabel == "PGM / RUN BY / DATE")
330 {
331 // Name of program creating current file
332 LOG_DATA("{}: Program: {}", nameId(), str::trim_copy(line.substr(0, 20))); // FORMAT: A20
333 // Name of agency creating current file
334 LOG_DATA("{}: Run by : {}", nameId(), str::trim_copy(line.substr(20, 20))); // FORMAT: A20
335 // Date and time of file creation
336 LOG_DATA("{}: Date : {}", nameId(), str::trim_copy(line.substr(40, 20))); // FORMAT: A20
337 }
338 else if (headerLabel == "COMMENT")
339 {
340 LOG_DATA("{}: Comment: {}", nameId(), line.substr(0, 60)); // FORMAT: A60
341 }
342 else if (headerLabel == "IONOSPHERIC CORR")
343 {
344 auto correctionType = str::trim_copy(line.substr(0, 4)); // FORMAT: A4,1X,
345 std::array<double, 4> values{};
346 values[0] = str::stod(str::replaceAll_copy(line.substr(5, 12), "d", "e", str::IgnoreCase), 0.0); // FORMAT: 4D12.4
347 values[1] = str::stod(str::replaceAll_copy(line.substr(17, 12), "d", "e", str::IgnoreCase), 0.0);
348 values[2] = str::stod(str::replaceAll_copy(line.substr(29, 12), "d", "e", str::IgnoreCase), 0.0);
349 values[3] = str::stod(str::replaceAll_copy(line.substr(41, 12), "d", "e", str::IgnoreCase), 0.0);
350
351 auto satSys = SatelliteSystem::fromString(correctionType.substr(0, 3));
352 IonosphericCorrections::AlphaBeta alphaBeta = (correctionType.size() == 4 && correctionType.at(3) == 'B')
355 _gnssNavInfo.ionosphericCorrections.insert(satSys, alphaBeta, values);
356 LOG_DATA("{}: Ionospheric Correction: {}-{}: [{}]", nameId(),
357 std::string(satSys), alphaBeta == IonosphericCorrections::Alpha ? "Alpha" : "Beta",
358 fmt::join(values.begin(), values.end(), ", "));
359 }
360 else if (headerLabel == "ION ALPHA" || headerLabel == "ION BETA")
361 {
362 auto correctionType = headerLabel.substr(4, 1);
363 auto valuesStr = str::split_wo_empty(str::replaceAll_copy(line.substr(4, 60 - 4), "d", "e", str::IgnoreCase), " ");
364 std::array<double, 4> values{};
365 for (size_t i = 0; i < valuesStr.size(); i++)
366 {
367 values.at(i) = std::stod(valuesStr.at(i));
368 }
369 IonosphericCorrections::AlphaBeta alphaBeta = correctionType == "B" ? IonosphericCorrections::Beta
371 _gnssNavInfo.ionosphericCorrections.insert(satSys, alphaBeta, values);
372 LOG_DATA("{}: Ionospheric Correction: {}-{}: [{}]", nameId(),
373 std::string(satSys), alphaBeta == IonosphericCorrections::Alpha ? "Alpha" : "Beta",
374 fmt::join(values.begin(), values.end(), ", "));
375 }
376 else if (headerLabel == "TIME SYSTEM CORR"
377 || headerLabel == "CORR TO SYSTEM TIME")
378 {
379 auto tau_c = str::stod(str::replaceAll_copy(line.substr(21, 19), "d", "e", str::IgnoreCase), 0.0);
380 LOG_DATA("{}: tau_c {}", nameId(), tau_c);
381 if (tau_c != 0)
382 {
383 _gnssNavInfo.timeSysCorr[{ satSys.getTimeSystem(), UTC }] = { .a0 = tau_c, .a1 = 0 };
384 }
385 }
386 else if (headerLabel == "DELTA-UTC: A0,A1,T,W")
387 {
388 auto a0 = std::stod(str::replaceAll_copy(line.substr(3, 19), "d", "e", str::IgnoreCase));
389 LOG_DATA("{}: a0 {}", nameId(), a0);
390 auto a1 = std::stod(str::replaceAll_copy(line.substr(22, 19), "d", "e", str::IgnoreCase));
391 LOG_DATA("{}: a1 {}", nameId(), a1);
392
393 if (a0 != 0 || a1 != 0)
394 {
395 _gnssNavInfo.timeSysCorr[{ satSys.getTimeSystem(), UTC }] = { .a0 = a0, .a1 = a1 };
396 }
397 }
398 else if (headerLabel == "LEAP SECONDS")
399 {
400 LOG_DATA("{}: Leap seconds: {}", nameId(), std::stoi(line.substr(0, 6)));
401 }
402 else if (headerLabel == "END OF HEADER")
403 {
404 break;
405 }
406 }
407}
408
410{
411 std::string line;
412 // --------------------------------------- RINEX VERSION / TYPE ------------------------------------------
413 while (line.find("RINEX VERSION / TYPE") == std::string::npos && !eof())
414 {
415 getline(line);
416 }
418 LOG_DEBUG("{}: Version: {:3.2f}", nameId(), _version); // FORMAT: F9.2,11X
419 LOG_DEBUG("{}: SatSys : {}", nameId(), str::trim_copy(line.substr(40, 20))); // FORMAT: A1,19X
420 // #######################################################################################################
421 while (getline(line) && !eof())
422 {
423 str::rtrim(line);
424 auto headerLabel = extHeaderLabel(line);
425 if (headerLabel == "PGM / RUN BY / DATE")
426 {
427 // Name of program creating current file
428 LOG_DATA("{}: Program: {}", nameId(), str::trim_copy(line.substr(0, 20))); // FORMAT: A20
429 // Name of agency creating current file
430 LOG_DATA("{}: Run by : {}", nameId(), str::trim_copy(line.substr(20, 20))); // FORMAT: A20
431 // Date and time of file creation
432 LOG_DATA("{}: Date : {}", nameId(), str::trim_copy(line.substr(40, 20))); // FORMAT: A20
433 }
434 else if (headerLabel == "COMMENT")
435 {
436 LOG_DATA("{}: Comment: {}", nameId(), line.substr(0, 60)); // FORMAT: A60
437 }
438 else if (headerLabel == "IONOSPHERIC CORR")
439 {
440 auto correctionType = str::trim_copy(line.substr(0, 4)); // FORMAT: A4,1X,
441 std::array<double, 4> values{};
442 values[0] = str::stod(str::replaceAll_copy(line.substr(5, 12), "d", "e", str::IgnoreCase), 0.0); // FORMAT: 4D12.4
443 values[1] = str::stod(str::replaceAll_copy(line.substr(17, 12), "d", "e", str::IgnoreCase), 0.0);
444 values[2] = str::stod(str::replaceAll_copy(line.substr(29, 12), "d", "e", str::IgnoreCase), 0.0);
445 values[3] = str::stod(str::replaceAll_copy(line.substr(41, 12), "d", "e", str::IgnoreCase), 0.0);
446
447 auto satSys = SatelliteSystem::fromString(correctionType.substr(0, 3));
448 IonosphericCorrections::AlphaBeta alphaBeta = (correctionType.size() == 4 && correctionType.at(3) == 'B')
451 _gnssNavInfo.ionosphericCorrections.insert(satSys, alphaBeta, values);
452 LOG_DATA("{}: Ionospheric Correction: {}-{}: [{}]", nameId(),
453 std::string(satSys), alphaBeta == IonosphericCorrections::Alpha ? "Alpha" : "Beta",
454 fmt::join(values.begin(), values.end(), ", "));
455 }
456 else if (headerLabel == "ION ALPHA" || headerLabel == "ION BETA")
457 {
458 auto correctionType = headerLabel.substr(4, 1);
459 auto valuesStr = str::split_wo_empty(str::replaceAll_copy(line.substr(4, 60 - 4), "d", "e", str::IgnoreCase), " ");
460 std::array<double, 4> values{};
461 for (size_t i = 0; i < valuesStr.size(); i++)
462 {
463 values.at(i) = std::stod(valuesStr.at(i));
464 }
465 IonosphericCorrections::AlphaBeta alphaBeta = correctionType == "B" ? IonosphericCorrections::Beta
467 _gnssNavInfo.ionosphericCorrections.insert(satSys, alphaBeta, values);
468 LOG_DATA("{}: Ionospheric Correction: {}-{}: [{}]", nameId(),
469 std::string(satSys), alphaBeta == IonosphericCorrections::Alpha ? "Alpha" : "Beta",
470 fmt::join(values.begin(), values.end(), ", "));
471 }
472 else if (headerLabel == "TIME SYSTEM CORR"
473 || headerLabel == "CORR TO SYSTEM TIME")
474 {
475 auto correctionType = str::trim_copy(line.substr(0, 4)); // FORMAT: A4,1X,
476 LOG_DATA("{}: Time System Correction: {}", nameId(), correctionType);
477
478 std::array<size_t, 2> x{ 5, 22 };
479 std::array<size_t, 2> n{ 17, 16 };
480
481 if (correctionType == "SBUT")
482 {
483 x = { 7, 26 };
484 n = { 19, 19 };
485 }
486 else if (correctionType == "GLUT")
487 {
488 x = { 5, 24 };
489 n = { 19, 19 };
490 }
491
492 auto a0 = str::stod(str::replaceAll_copy(line.substr(x[0], n[0]), "d", "e", str::IgnoreCase), std::nan(""));
493 LOG_DATA("{}: a0 {}", nameId(), a0);
494 auto a1 = str::stod(str::replaceAll_copy(line.substr(x[1], n[1]), "d", "e", str::IgnoreCase), std::nan(""));
495 LOG_DATA("{}: a1 {}", nameId(), a1);
496
497 if (!std::isnan(a0) && !std::isnan(a1))
498 {
499 std::pair<TimeSystem, TimeSystem> timeSystems;
500 if (correctionType == "GPUT") { timeSystems = { GPST, UTC }; }
501 else if (correctionType == "GLUT") { timeSystems = { GLNT, UTC }; }
502 else if (correctionType == "GAUT") { timeSystems = { GST, UTC }; }
503 else if (correctionType == "BDUT") { timeSystems = { BDT, UTC }; }
504 else if (correctionType == "QZUT") { timeSystems = { QZSST, UTC }; }
505 else if (correctionType == "IRUT") { timeSystems = { IRNSST, UTC }; }
506 // else if (correctionType == "SBUT") { timeSystems = { SBAST, UTC }; }
507 else if (correctionType == "GLGP") { timeSystems = { GLNT, GPST }; }
508 else if (correctionType == "GAGP") { timeSystems = { GST, GPST }; }
509 else if (correctionType == "GPGA") { timeSystems = { GST, GPST }; } // Pre RINEX 3.04
510 else if (correctionType == "QZGP") { timeSystems = { QZSST, GPST }; }
511 else if (correctionType == "IRGP") { timeSystems = { IRNSST, GPST }; }
512 else
513 {
514 LOG_TRACE("{}: Time System Correction '{}' not implemented yet", nameId(), correctionType);
515 continue;
516 }
517 _gnssNavInfo.timeSysCorr[timeSystems] = { .a0 = a0, .a1 = a1 };
518 }
519 }
520 else if (headerLabel == "DELTA-UTC: A0,A1,T,W")
521 {
522 auto a0 = std::stod(str::replaceAll_copy(line.substr(3, 19), "d", "e", str::IgnoreCase));
523 LOG_DATA("{}: a0 {}", nameId(), a0);
524 auto a1 = std::stod(str::replaceAll_copy(line.substr(22, 19), "d", "e", str::IgnoreCase));
525 LOG_DATA("{}: a1 {}", nameId(), a1);
526
527 if (a0 != 0 || a1 != 0)
528 {
529 _gnssNavInfo.timeSysCorr[{ satSys.getTimeSystem(), UTC }] = { .a0 = a0, .a1 = a1 };
530 }
531 }
532 else if (headerLabel == "LEAP SECONDS")
533 {
534 LOG_DATA("{}: Leap seconds: {}", nameId(), std::stoi(line.substr(0, 6)));
535 }
536 else if (headerLabel == "END OF HEADER")
537 {
538 break;
539 }
540 }
541}
542
544{
545 std::string line;
546 // --------------------------------------- RINEX VERSION / TYPE ------------------------------------------
547 while (line.find("RINEX VERSION / TYPE") == std::string::npos && !eof())
548 {
549 getline(line);
550 }
551 // SatelliteSystem satSys = SatSys_None;
552 LOG_DEBUG("{}: Version: {:3.2f}", nameId(), _version); // FORMAT: F9.2,11X
553 LOG_DEBUG("{}: SatSys : {}", nameId(), str::trim_copy(line.substr(40, 20))); // FORMAT: A1,19X
554 // #######################################################################################################
555 while (getline(line) && !eof())
556 {
557 str::rtrim(line);
558 auto headerLabel = extHeaderLabel(line);
559 if (headerLabel == "PGM / RUN BY / DATE")
560 {
561 // Name of program creating current file
562 LOG_DATA("{}: Program: {}", nameId(), str::trim_copy(line.substr(0, 20))); // FORMAT: A20
563 // Name of agency creating current file
564 LOG_DATA("{}: Run by : {}", nameId(), str::trim_copy(line.substr(20, 20))); // FORMAT: A20
565 // Date and time of file creation
566 LOG_DATA("{}: Date : {}", nameId(), str::trim_copy(line.substr(40, 20))); // FORMAT: A20
567 }
568 else if (headerLabel == "COMMENT")
569 {
570 LOG_DATA("{}: Comment: {}", nameId(), line.substr(0, 60)); // FORMAT: A60
571 }
572 else if (headerLabel == "REC # / TYPE / VERS") // Not in mixed station files
573 {
574 // Receiver number of station
575 LOG_DATA("{}: Receiver number : {}", nameId(), str::trim_copy(line.substr(0, 20))); // FORMAT: A20
576 // Receiver type of station
577 LOG_DATA("{}: Receiver type : {}", nameId(), str::trim_copy(line.substr(20, 20))); // FORMAT: A20
578 // Receiver software version of station
579 LOG_DATA("{}: Receiver version : {}", nameId(), str::trim_copy(line.substr(40, 20))); // FORMAT: A20
580 }
581 else if (headerLabel == "Merged File")
582 {
583 if (!str::trim_copy(line.substr(0, 9)).empty())
584 {
585 // Numbers of files merged
586 LOG_DATA("{}: Number of files merged : {}", nameId(), str::trim_copy(line.substr(0, 9))); // FORMAT: I9
587 }
588 }
589 else if (headerLabel == "DOI")
590 {
591 // Digital Object Identifier
592 LOG_DATA("{}: Comment: {}", nameId(), line.substr(0, 60)); // FORMAT: A60
593 }
594 else if (headerLabel == "LICENSE OF USE")
595 {
596 // License of Use
597 LOG_DATA("{}: Comment: {}", nameId(), line.substr(0, 60)); // FORMAT: A60
598 }
599 else if (headerLabel == "LICENSE OF USE")
600 {
601 // License of Use
602 LOG_DATA("{}: Comment: {}", nameId(), line.substr(0, 60)); // FORMAT: A60
603 }
604 else if (headerLabel == "LEAP SECONDS")
605 {
606 LOG_DATA("{}: Leap seconds: {}", nameId(), std::stoi(line.substr(0, 6))); // I6
607 }
608 else if (headerLabel == "END OF HEADER")
609 {
610 break;
611 }
612 }
613}
614
616{
617 std::string line;
618
619 try
620 {
621 while (getline(line) && !eof())
622 {
623 if (line.size() > 82)
624 {
625 abortReading();
626 return;
627 } // 80 + \n\r
628
629 // -------------------------------------- SV / EPOCH / SV CLK ----------------------------------------
630
631 // Satellite system (G), sat number (PRN) - A1,I2.2,
632 // Satellite system (E), satellite number - A1,I2.2,
633 // Satellite system (R), satellite number (slot number in sat. constellation) - A1,I2.2,
634 // Satellite system (J), Satellite PRN-192 - A1,I2,
635 // Satellite system (C), sat number (PRN) - A1,I2.2,
636 // Satellite system (S), satellite number (slot number in sat. constellation) - A1,I2.2,
637 // Satellite system (I), sat number (PRN) - A1,I2.2,
639
640 // Offset for RINEX 2.xx versions. Has only 3 leading spaces
641 satSys = _gnssNavInfo.satelliteSystems;
642
643 if (satSys == SatSys_None) { continue; } // To intercept comment lines
644
645 auto satNumStr = line.substr(0, 2);
646 if (satNumStr == " ") { continue; }
647
648 auto satNum = static_cast<uint8_t>(str::stoi(satNumStr, 0));
649 if (satNum == 0) { continue; }
650 // Epoch: Toc - Time of Clock (GPS) year (4 digits) - 1X,I4,
651 // month, day, hour, minute, second - 5(1X,I2.2),
652 auto timeSplit = str::split_wo_empty(line.substr(2, 20), " ");
653 auto timeSystem = satSys == GLO ? UTC : satSys.getTimeSystem();
654
655 int year = std::stoi(timeSplit.at(0));
656
657 // RINEX 2.xx has 2-Digit Years
658 year += (year >= 80 ? 1900 : 2000);
659
660 InsTime epoch{ static_cast<uint16_t>(year),
661 static_cast<uint16_t>(std::stoi(timeSplit.at(1))),
662 static_cast<uint16_t>(std::stoi(timeSplit.at(2))),
663 static_cast<uint16_t>(std::stoi(timeSplit.at(3))),
664 static_cast<uint16_t>(std::stoi(timeSplit.at(4))),
665 std::stold(timeSplit.at(5)),
666 timeSystem };
667
668 LOG_DATA("{}: {}-{} {} (read as {} time)", nameId(), satSys, satNum, epoch.toYMDHMS(), std::string(timeSystem));
669
670 if (satSys == GPS || satSys == GAL || satSys == QZSS || satSys == BDS) // NOLINT(misc-redundant-expression) // bugged warning
671 {
672 // Polynomial coefficients for clock correction
673 std::array<double, 3> a{};
674 // SV clock bias [seconds]
675 a[0] = std::stod(str::replaceAll_copy(line.substr(22, 19), "d", "e", str::IgnoreCase));
676 // SV clock drift [sec/sec]
677 a[1] = std::stod(str::replaceAll_copy(line.substr(41, 19), "d", "e", str::IgnoreCase));
678 // SV clock drift rate [sec/sec^2]
679 a[2] = std::stod(str::replaceAll_copy(line.substr(60, 19), "d", "e", str::IgnoreCase));
680
681 LOG_DATA("{}: clkBias {}, clkDrift {}, clkDriftRate {}", nameId(), a[0], a[1], a[2]);
682
683 // ------------------------------------ BROADCAST ORBIT - 1 --------------------------------------
684 getline(line);
685 if (line.size() > 82)
686 {
687 abortReading();
688 return;
689 } // 80 + \n\r
690
691 // GPS/QZSS: Issue of Data, Ephemeris (IODE)
692 // GAL: IODnav Issue of Data of the nav batch
693 // BDS: AODE Age of Data, Ephemeris (as specified in BeiDou ICD Table Section 5.2.4.11 Table 5-8)
694 // and field range is: 0-31.
695 // IRNSS: IODEC Issue of Data, Ephemeris and Clock
696 auto IODE_IODnav_AODE_IODEC = static_cast<size_t>(std::stod(str::replaceAll_copy(line.substr(3, 19), "d", "e", str::IgnoreCase)));
697 // Crs (meters)
698 double Crs = std::stod(str::replaceAll_copy(line.substr(22, 19), "d", "e", str::IgnoreCase));
699 // Delta n (radians/sec)
700 double delta_n = std::stod(str::replaceAll_copy(line.substr(41, 19), "d", "e", str::IgnoreCase));
701 // M0 (radians)
702 double M_0 = std::stod(str::replaceAll_copy(line.substr(60, 19), "d", "e", str::IgnoreCase));
703
704 LOG_DATA("{}: IODE_IODnav_AODE_IODEC {}, Crs {}, Delta_n {}, M0 {}", nameId(), IODE_IODnav_AODE_IODEC, Crs, delta_n, M_0);
705
706 // ------------------------------------ BROADCAST ORBIT - 2 --------------------------------------
707 getline(line);
708 if (line.size() > 82)
709 {
710 abortReading();
711 return;
712 } // 80 + \n\r
713
714 // Cuc (radians)
715 double Cuc = std::stod(str::replaceAll_copy(line.substr(3, 19), "d", "e", str::IgnoreCase));
716 // e Eccentricity
717 double e = std::stod(str::replaceAll_copy(line.substr(22, 19), "d", "e", str::IgnoreCase));
718 // Cus (radians)
719 double Cus = std::stod(str::replaceAll_copy(line.substr(41, 19), "d", "e", str::IgnoreCase));
720 // sqrt(A) (sqrt(m))
721 double sqrt_A = std::stod(str::replaceAll_copy(line.substr(60, 19), "d", "e", str::IgnoreCase));
722
723 LOG_DATA("{}: Cuc {}, e {}, Cus {}, sqrt_A {}", nameId(), Cuc, e, Cus, sqrt_A);
724
725 // ------------------------------------ BROADCAST ORBIT - 3 --------------------------------------
726 getline(line);
727 if (line.size() > 82)
728 {
729 abortReading();
730 return;
731 } // 80 + \n\r
732
733 // Toe Time of Ephemeris (sec of GPS week)
734 double toeSec = std::stod(str::replaceAll_copy(line.substr(3, 19), "d", "e", str::IgnoreCase));
735 // Cic (radians)
736 double Cic = std::stod(str::replaceAll_copy(line.substr(22, 19), "d", "e", str::IgnoreCase));
737 // OMEGA0 (radians)
738 double Omega_0 = std::stod(str::replaceAll_copy(line.substr(41, 19), "d", "e", str::IgnoreCase));
739 // Cis (radians)
740 double Cis = std::stod(str::replaceAll_copy(line.substr(60, 19), "d", "e", str::IgnoreCase));
741
742 LOG_DATA("{}: toe {}, Cic {}, Omega_0 {}, Cis {}", nameId(), toeSec, Cic, Omega_0, Cis);
743
744 // ------------------------------------ BROADCAST ORBIT - 4 --------------------------------------
745 getline(line);
746 if (line.size() > 82)
747 {
748 abortReading();
749 return;
750 } // 80 + \n\r
751
752 // i0 (radians)
753 double i_0 = std::stod(str::replaceAll_copy(line.substr(3, 19), "d", "e", str::IgnoreCase));
754 // Crc (meters)
755 double Crc = std::stod(str::replaceAll_copy(line.substr(22, 19), "d", "e", str::IgnoreCase));
756 // omega (radians)
757 double omega = std::stod(str::replaceAll_copy(line.substr(41, 19), "d", "e", str::IgnoreCase));
758 // OMEGA DOT (radians/sec)
759 double Omega_dot = std::stod(str::replaceAll_copy(line.substr(60, 19), "d", "e", str::IgnoreCase));
760
761 LOG_DATA("{}: i_0 {}, Crc {}, omega {}, Omega_dot {}", nameId(), i_0, Crc, omega, Omega_dot);
762
763 // ------------------------------------ BROADCAST ORBIT - 5 --------------------------------------
764 getline(line);
765 if (line.size() > 82)
766 {
767 abortReading();
768 return;
769 } // 80 + \n\r
770
771 // IDOT (radians/sec)
772 double i_dot = std::stod(str::replaceAll_copy(line.substr(3, 19), "d", "e", str::IgnoreCase));
773 // GPS: Codes on L2 channel
774 // QZSS: Codes on L2 channel (fixed to 2, see IS-QZSS-PNT 4.1.2.7)
775 // GAL: Data sources (FLOAT --> INTEGER)
776 // Bit 0 set: I/NAV E1-B
777 // Bit 1 set: F/NAV E5a-I
778 // Bit 2 set: I/NAV E5b-I
779 // Bits 0 and 2 : Both can be set if the navigation messages were merged, however, bits 0-2
780 // cannot all be set, as the I/NAV and F/NAV messages contain different information
781 // Bit 3 reserved for Galileo internal use
782 // Bit 4 reserved for Galileo internal use
783 // Bit 8 set: af0-af2, Toc, SISA are for E5a,E1
784 // Bit 9 set: af0-af2, Toc, SISA are for E5b,E1
785 // Bits 8-9 : exclusive (only one bit can be set)
786 // BDS/IRNSS: Spare
787 auto codesOnL2Channel_dataSources = static_cast<uint16_t>(str::stod(str::replaceAll_copy(line.substr(22, 19), "d", "e", str::IgnoreCase), 0));
788
789 // GPS Week # (to go with TOE) Continuous number, not mod(1024)!
790 auto gpsWeek = static_cast<int32_t>(str::stod(str::replaceAll_copy(line.substr(41, 19), "d", "e", str::IgnoreCase), epoch.toGPSweekTow().gpsWeek));
791 if (satSys == BDS) { gpsWeek += InsTimeUtil::DIFF_BDT_WEEK_TO_GPST_WEEK; }
792 auto toe = InsTime(0, gpsWeek, toeSec, satSys.getTimeSystem());
793
794 // GPS: L2 P data flag
795 // QZSS: L2P data flag set to 1 since QZSS does not track L2P
796 // GAL/BDS/IRNSS: Spare
797 bool L2PdataFlag = static_cast<bool>(str::stod(str::replaceAll_copy(line.substr(60, 19), "d", "e", str::IgnoreCase), 0.0));
798
799 LOG_DATA("{}: i_dot {}, codesOnL2Channel/dataSources {} ({}), gpsWeek {}, L2PdataFlag {}", nameId(),
800 i_dot, codesOnL2Channel_dataSources, std::bitset<10>(codesOnL2Channel_dataSources).to_string(), gpsWeek, L2PdataFlag);
801
802 // ------------------------------------ BROADCAST ORBIT - 6 --------------------------------------
803 getline(line);
804 if (line.size() > 82)
805 {
806 abortReading();
807 return;
808 } // 80 + \n\r
809
810 // GPS: SV accuracy (meters) See GPS ICD 200H Section 20.3.3.3.1.3 use specified equations to define
811 // nominal values, N = 0-6: use 2(1+N/2) (round to one decimal place i.e. 2.8, 5.7 and 11.3) ,
812 // N= 7-15:use 2 (N-2), 8192 specifies use at own risk
813 // QZSS: SV accuracy (meters) (IS -QZSS-PNT, Section 5.4.3.1) which refers to: IS GPS 200H Section 20.3.3.3.1.3
814 // use specified equations to define nominal values, N = 0-6: use 2(1+N/2) (round to one decimal
815 // place i.e. 2.8, 5.7 and 11.3) , N= 7-15:use 2 (N-2), 8192 specifies use at own risk
816 // IRNSS: User Range Accuracy(m), See IRNSS ICD Section 6.2.1.4 , use specified equations to define
817 // nominal values, N = 0-6: use 2(1+N/2) (round to one decimal place i.e. 2.8, 5.7 and 11.3) ,
818 // N= 7-15:use 2 (N-2), 8192 specifies use at own risk
819 double signalAccuracy = std::stod(str::replaceAll_copy(line.substr(3, 19), "d", "e", str::IgnoreCase));
820 // GPS/QZSS: SV health (bits 17-22 w 3 sf 1)
821 // GAL: SV health (FLOAT converted to INTEGER) See Galileo ICD Section 5.1.9.3
822 // Bit 0: E1B DVS Bits 1-2: E1B HS Bit 3: E5a DVS
823 // Bits 4-5: E5a HS Bit 6: E5b DVS Bits 7-8: E5b HS
824 // BDS: SatH1
825 // IRNSS: Health (Sub frame 1,bits 155(most significant) and 156(least significant)),
826 // where 0 = L5 and S healthy, 1 = L5 healthy and S unhealthy, 2= L5 unhealthy
827 // and S healthy, 3= both L5 and S unhealthy
828 auto svHealth = static_cast<uint16_t>(std::stod(str::replaceAll_copy(line.substr(22, 19), "d", "e", str::IgnoreCase)));
829 // GPS: TGD (seconds)
830 // QZSS: TGD (seconds) The QZSS ICD specifies a do not use bit pattern "10000000" this condition is represented by a blank field.
831 // GAL: BGD E5a/E1 (seconds)
832 // BDS: TGD1 B1/B3 (seconds)
833 double tgd_bgd5a_TGD1 = str::stod(str::replaceAll_copy(line.substr(41, 19), "d", "e", str::IgnoreCase), 0.0);
834
835 // GPS/QZSS: IODC Issue of Data, Clock
836 // GAL: BGD E5b/E1 (seconds)
837 // BDS: TGD2 B2/B3 (seconds)
838 // IRNSS: Blank
839 double IODC_bgd5b_TGD2 = str::stod(str::replaceAll_copy(line.substr(60, 19), "d", "e", str::IgnoreCase), 0.0);
840
841 LOG_DATA("{}: svAccuracy {}, svHealth {}, TGD {}, IODC {}", nameId(), signalAccuracy, svHealth, tgd_bgd5a_TGD1, IODC_bgd5b_TGD2);
842
843 // ------------------------------------ BROADCAST ORBIT - 7 --------------------------------------
844 getline(line);
845 if (line.size() > 82)
846 {
847 abortReading();
848 return;
849 } // 80 + \n\r
850
851 // Transmission time of message (sec of GPS week, derived e.g.from Z-count in Hand Over Word (HOW))
852 [[maybe_unused]] auto transmissionTime = str::stod(str::replaceAll_copy(line.substr(3, 19), "d", "e", str::IgnoreCase), 0.0);
853 // GPS/QZSS: Fit Interval in hours see section 6.11. (BNK).
854 // GAL: Spare
855 // BDS: AODC Age of Data Clock (as specified in BeiDou ICD Table Section 5.2.4.9 Table 5-6) and field range is: 0-31.
856 auto fitInterval_AODC = str::stod(str::replaceAll_copy(line.substr(22, 19), "d", "e", str::IgnoreCase), 0.0);
857 // Spare
858 // std::stod(str::replaceAll_copy(line.substr(41, 19), "d", "e", str::IgnoreCase));
859 // Spare
860 // std::stod(str::replaceAll_copy(line.substr(60, 19), "d", "e", str::IgnoreCase));
861
862 LOG_DATA("{}: transmissionTime {}, fitInterval/AODC {}", nameId(), transmissionTime, fitInterval_AODC);
863
864 if (satSys == GPS)
865 {
866 _gnssNavInfo.addSatelliteNavData({ satSys, satNum }, std::make_shared<GPSEphemeris>(epoch, toe,
867 IODE_IODnav_AODE_IODEC, IODC_bgd5b_TGD2, a,
868 sqrt_A, e, i_0, Omega_0, omega, M_0,
869 delta_n, Omega_dot, i_dot, Cus, Cuc,
870 Cis, Cic, Crs, Crc,
871 signalAccuracy, svHealth,
872 codesOnL2Channel_dataSources, L2PdataFlag,
873 tgd_bgd5a_TGD1, fitInterval_AODC));
874 }
875 else if (satSys == GAL)
876 {
877 // The same satellite can appear multiple times with different dataSource bits
878 // We want to prefer 'I/NAV E1-B' (Bit 0 set) over 'F/NAV E5a-I' (Bit 1 set) or 'I/NAV E5b-I' (Bit 2 set)
879
880 if (_gnssNavInfo.satellites().contains({ satSys, satNum }) // We have this satellite already
881 && !std::bitset<10>(codesOnL2Channel_dataSources)[0]) // This message is not 'I/NAV E1-B'
882 {
883 const auto& navData = _gnssNavInfo.satellites().at({ satSys, satNum }).getNavigationData();
884 auto existingEph = std::ranges::find_if(navData, [&](const std::shared_ptr<SatNavData>& satNavData) {
885 return satNavData->type == SatNavData::GalileoEphemeris && satNavData->refTime == epoch
886 && std::dynamic_pointer_cast<GalileoEphemeris>(satNavData)->dataSource[0];
887 });
888 if (existingEph != navData.end()) // There is already a 'I/NAV E1-B' message
889 {
890 LOG_DATA("{}: Skipping ephemeris data because of dataSource priority", nameId());
891 continue;
892 }
893 }
894
895 GalileoEphemeris::SvHealth health = { .E5a_DataValidityStatus = static_cast<GalileoEphemeris::SvHealth::DataValidityStatus>((svHealth & 0b000001000) >> 3),
896 .E5b_DataValidityStatus = static_cast<GalileoEphemeris::SvHealth::DataValidityStatus>((svHealth & 0b001000000) >> 6),
897 .E1B_DataValidityStatus = static_cast<GalileoEphemeris::SvHealth::DataValidityStatus>((svHealth & 0b000000001) >> 0),
898 .E5a_SignalHealthStatus = static_cast<GalileoEphemeris::SvHealth::SignalHealthStatus>((svHealth & 0b000110000) >> 4),
899 .E5b_SignalHealthStatus = static_cast<GalileoEphemeris::SvHealth::SignalHealthStatus>((svHealth & 0b110000000) >> 7),
900 .E1B_SignalHealthStatus = static_cast<GalileoEphemeris::SvHealth::SignalHealthStatus>((svHealth & 0b000000110) >> 1) };
901 _gnssNavInfo.addSatelliteNavData({ satSys, satNum }, std::make_shared<GalileoEphemeris>(epoch, toe, IODE_IODnav_AODE_IODEC, a,
902 sqrt_A, e, i_0, Omega_0, omega, M_0,
903 delta_n, Omega_dot, i_dot, Cus, Cuc,
904 Cis, Cic, Crs, Crc,
905 codesOnL2Channel_dataSources, signalAccuracy, health,
906 tgd_bgd5a_TGD1, IODC_bgd5b_TGD2));
907 }
908 else if (satSys == BDS)
909 {
910 _gnssNavInfo.addSatelliteNavData({ satSys, satNum }, std::make_shared<BDSEphemeris>(satNum, epoch, toe,
911 IODE_IODnav_AODE_IODEC, fitInterval_AODC, a,
912 sqrt_A, e, i_0, Omega_0, omega, M_0,
913 delta_n, Omega_dot, i_dot, Cus, Cuc,
914 Cis, Cic, Crs, Crc,
915 signalAccuracy, svHealth,
916 tgd_bgd5a_TGD1, IODC_bgd5b_TGD2));
917 }
918 else if (satSys == QZSS)
919 {
920 _gnssNavInfo.addSatelliteNavData({ satSys, satNum }, std::make_shared<QZSSEphemeris>(epoch, toe,
921 IODE_IODnav_AODE_IODEC, IODC_bgd5b_TGD2, a,
922 sqrt_A, e, i_0, Omega_0, omega, M_0,
923 delta_n, Omega_dot, i_dot, Cus, Cuc,
924 Cis, Cic, Crs, Crc,
925 signalAccuracy, svHealth,
926 codesOnL2Channel_dataSources, L2PdataFlag,
927 tgd_bgd5a_TGD1, fitInterval_AODC));
928 }
929 }
930 else if (satSys == GLO || satSys == SBAS) // NOLINT(misc-redundant-expression) // bugged warning
931 {
932 // TODO: Offset
933
934 Eigen::Vector3d pos;
935 Eigen::Vector3d vel;
936 Eigen::Vector3d accelLuniSolar;
937
938 double tau_c{};
939 // Coefficient of linear polynomial of time system difference [s]
940 if (_gnssNavInfo.timeSysCorr.contains({ satSys.getTimeSystem(), UTC }))
941 {
942 tau_c = _gnssNavInfo.timeSysCorr.at({ satSys.getTimeSystem(), UTC }).a0;
943 }
944
945 // GLO: SV clock bias (sec) (-TauN)
946 // SBAS: SV clock bias (sec) (aGf0)
947 double m_tau_n = std::stod(str::replaceAll_copy(line.substr(22, 19), "d", "e", str::IgnoreCase));
948 // GLO: SV relative frequency bias (+GammaN)
949 // SBAS: SV relative frequency bias (aGf1)
950 double gamma_n = std::stod(str::replaceAll_copy(line.substr(41, 19), "d", "e", str::IgnoreCase));
951 // GLO: Message frame time (tk+nd*86400) in seconds of the UTC week
952 // SBAS: Transmission time of message (start of the message) in GPS seconds of the week
953 [[maybe_unused]] auto msgFrameTime = std::stod(str::replaceAll_copy(line.substr(60, 19), "d", "e", str::IgnoreCase));
954 LOG_DATA("{}: clkBias {}, relFreqBias {}, msgFrameTime {}", nameId(), m_tau_n, gamma_n, msgFrameTime);
955
956 // ------------------------------------ BROADCAST ORBIT - 1 --------------------------------------
957 getline(line);
958 if (line.size() > 82)
959 {
960 abortReading();
961 return;
962 } // 80 + \n\r
963
964 // Satellite position X (km)
965 pos.x() = std::stod(str::replaceAll_copy(line.substr(3, 19), "d", "e", str::IgnoreCase)) * 1e3;
966 // velocity X dot (km/sec)
967 vel.x() = std::stod(str::replaceAll_copy(line.substr(22, 19), "d", "e", str::IgnoreCase)) * 1e3;
968 // X acceleration (km/sec2)
969 accelLuniSolar.x() = std::stod(str::replaceAll_copy(line.substr(41, 19), "d", "e", str::IgnoreCase)) * 1e3;
970 // GLO: health (0=healthy, 1=unhealthy) (MSB of 3-bit Bn)
971 // SBAS: Health: SBAS: See Section 8.3.3 bit mask for: health, health availability and User Range Accuracy.
972 [[maybe_unused]] auto health = std::stod(str::replaceAll_copy(line.substr(60, 19), "d", "e", str::IgnoreCase));
973
974 LOG_DATA("{}: satPosX {}, velX {}, accelX {}, health {}", nameId(), pos.x(), vel.x(), accelLuniSolar.x(), health);
975
976 // ------------------------------------ BROADCAST ORBIT - 2 --------------------------------------
977 getline(line);
978 if (line.size() > 82)
979 {
980 abortReading();
981 return;
982 } // 80 + \n\r
983
984 // Satellite position Y (km)
985 pos.y() = std::stod(str::replaceAll_copy(line.substr(3, 19), "d", "e", str::IgnoreCase)) * 1e3;
986 // velocity Y dot (km/sec)
987 vel.y() = std::stod(str::replaceAll_copy(line.substr(22, 19), "d", "e", str::IgnoreCase)) * 1e3;
988 // Y acceleration (km/sec2)
989 accelLuniSolar.y() = std::stod(str::replaceAll_copy(line.substr(41, 19), "d", "e", str::IgnoreCase)) * 1e3;
990 // GLO: frequency number(-7...+13) (-7...+6 ICD 5.1)
991 // SBAS: Accuracy code (URA, meters)
992 double frequencyNumber_accuracyCode = std::stod(str::replaceAll_copy(line.substr(60, 19), "d", "e", str::IgnoreCase));
993
994 LOG_DATA("{}: satPosY {}, velY {}, accelY {}, freqNum/accuracyCode {}", nameId(), pos.y(), vel.y(), accelLuniSolar.y(), frequencyNumber_accuracyCode);
995
996 // ------------------------------------ BROADCAST ORBIT - 3 --------------------------------------
997 getline(line);
998 if (line.size() > 82)
999 {
1000 abortReading();
1001 return;
1002 } // 80 + \n\r
1003
1004 // Satellite position Z (km)
1005 pos.z() = std::stod(str::replaceAll_copy(line.substr(3, 19), "d", "e", str::IgnoreCase)) * 1e3;
1006 // velocity Z dot (km/sec)
1007 vel.z() = std::stod(str::replaceAll_copy(line.substr(22, 19), "d", "e", str::IgnoreCase)) * 1e3;
1008 // Z acceleration (km/sec2)
1009 accelLuniSolar.z() = std::stod(str::replaceAll_copy(line.substr(41, 19), "d", "e", str::IgnoreCase)) * 1e3;
1010 // GLO: Age of oper. information (days) (E)
1011 // SBAS: IODN (Issue of Data Navigation, DO229, 8 first bits after Message Type if MT9)
1012 [[maybe_unused]] auto ageOfOperation_IODN = str::stod(str::replaceAll_copy(line.substr(60, 19), "d", "e", str::IgnoreCase), 0.0);
1013
1014 LOG_DATA("{}: satPosZ {}, velZ {}, accelZ {}, ageOfOperation/IODN {}", nameId(), pos.z(), vel.z(), accelLuniSolar.z(), ageOfOperation_IODN);
1015
1016 if (satSys == GLO)
1017 {
1018 _gnssNavInfo.addSatelliteNavData({ satSys, satNum }, std::make_shared<GLONASSEphemeris>(epoch, tau_c,
1019 -m_tau_n, gamma_n, static_cast<bool>(health),
1020 pos, vel, accelLuniSolar,
1021 frequencyNumber_accuracyCode));
1022 }
1023 else if (satSys == SBAS && !_sbasNotSupportedWarned)
1024 {
1025 LOG_WARN("SBAS is not yet supported. Therefore the Navigation file data will be skipped.");
1027 }
1028 }
1029 }
1030 }
1031 catch (const std::exception& e)
1032 {
1033 abortReading();
1034 }
1035}
1036
1038{
1039 std::string line;
1040 try
1041 {
1042 while (getline(line) && !eof())
1043 {
1044 if (line.size() > 82)
1045 {
1046 abortReading();
1047 return;
1048 } // 80 + \n\r
1049
1050 // -------------------------------------- SV / EPOCH / SV CLK ----------------------------------------
1051
1052 // Satellite system (G), sat number (PRN) - A1,I2.2,
1053 // Satellite system (E), satellite number - A1,I2.2,
1054 // Satellite system (R), satellite number (slot number in sat. constellation) - A1,I2.2,
1055 // Satellite system (J), Satellite PRN-192 - A1,I2,
1056 // Satellite system (C), sat number (PRN) - A1,I2.2,
1057 // Satellite system (S), satellite number (slot number in sat. constellation) - A1,I2.2,
1058 // Satellite system (I), sat number (PRN) - A1,I2.2,
1060
1061 satSys = SatelliteSystem::fromChar(line.at(0));
1062 _gnssNavInfo.satelliteSystems |= satSys;
1063
1064 if (satSys == SatSys_None) { continue; } // To intercept comment lines
1065
1066 auto satNumStr = line.substr(1, 2);
1067 if (satNumStr == " ") { continue; }
1068
1069 auto satNum = static_cast<uint8_t>(str::stoi(satNumStr, 0));
1070 if (satNum == 0) { continue; }
1071
1072 if (parseEphemeris(line, satSys, satNum))
1073 {
1074 continue;
1075 }
1076 }
1077 }
1078 catch (const std::exception& e)
1079 {
1080 abortReading();
1081 }
1082}
1083
1085{
1086 std::string line;
1087 try
1088 {
1089 while (getline(line) && !eof())
1090 {
1091 if (line.size() > 82)
1092 {
1093 abortReading();
1094 return;
1095 } // 80 + \n\r
1096
1097 if (line.at(0) == '>')
1098 {
1099 // -------------------------------------- Type / SV / MSSG ----------------------------------------
1100
1101 // Satellite system (G), sat number (PRN) - A1,I2.2,
1102 // Satellite system (E), satellite number - A1,I2.2,
1103 // Satellite system (R), satellite number (slot number in sat. constellation) - A1,I2.2,
1104 // Satellite system (J), Satellite PRN-192 - A1,I2,
1105 // Satellite system (C), sat number (PRN) - A1,I2.2,
1106 // Satellite system (S), satellite number (slot number in sat. constellation) - A1,I2.2,
1107 // Satellite system (I), sat number (PRN) - A1,I2.2,
1108
1110 satSys = SatelliteSystem::fromChar(line.at(6));
1111 _gnssNavInfo.satelliteSystems |= satSys;
1112 if (satSys == SatSys_None) { continue; } // To intercept comment lines
1113
1114 auto satNumStr = line.substr(7, 2);
1115 if (satNumStr == " ") { continue; }
1116
1117 auto satNum = static_cast<uint8_t>(str::stoi(satNumStr, 0));
1118 if (satNum == 0) { continue; }
1119
1120 std::string msgType = line.substr(2, 3);
1121 NavMsgType navMsgType = getNavMsgType(msgType);
1122
1123 // std::string satMsgType = line.substr(10, 4);
1124
1125 switch (navMsgType)
1126 {
1127 case NavMsgType::EPH:
1128 {
1129 // Code for EPH
1130 getline(line);
1131 if (line.size() > 82)
1132 {
1133 abortReading();
1134 return;
1135 }
1136
1137 if (satSys != SatelliteSystem::fromChar(line.at(0)) || satNumStr != line.substr(1, 2)) { continue; }
1138
1139 if (parseEphemeris(line, satSys, satNum))
1140 {
1141 continue;
1142 }
1143 }
1144 break;
1145 case NavMsgType::STO:
1146 {
1147 // Code for STO
1148 getline(line);
1149 if (line.size() > 82)
1150 {
1151 abortReading();
1152 return;
1153 }
1154
1155 // Epoch: Toc - Time of Clock (GPS) year (4 digits) - 4X,I4,
1156 // month, day, hour, minute, second - 5(1X,I2.2),
1157 auto timeSplit = str::split_wo_empty(line.substr(4, 20), " ");
1158 auto timeSystem = satSys == GLO ? UTC : satSys.getTimeSystem();
1159
1160 int year = std::stoi(timeSplit.at(0));
1161
1162 InsTime epoch{ static_cast<uint16_t>(year),
1163 static_cast<uint16_t>(std::stoi(timeSplit.at(1))),
1164 static_cast<uint16_t>(std::stoi(timeSplit.at(2))),
1165 static_cast<uint16_t>(std::stoi(timeSplit.at(3))),
1166 static_cast<uint16_t>(std::stoi(timeSplit.at(4))),
1167 std::stold(timeSplit.at(5)),
1168 timeSystem };
1169
1170 LOG_DATA("{}: {}-{} {} (read as {} time)", nameId(), satSys, satNum, epoch.toYMDHMS(), std::string(timeSystem));
1171 auto correctionType = str::trim_copy(line.substr(24, 4)); // FORMAT: 1X,A18
1172 LOG_DATA("{}: Time System Correction: {}", nameId(), correctionType);
1173
1174 getline(line);
1175 if (line.size() > 82)
1176 {
1177 abortReading();
1178 return;
1179 }
1180
1181 // auto t_tm = str::stod(str::trim_copy(line.substr(4, 19)), std::nan(""));
1182 // LOG_DATA("{}: t_tm {}", nameId(), t_tm);
1183 auto a0 = str::stod(str::trim_copy(line.substr(23, 19)), std::nan(""));
1184 LOG_DATA("{}: a0 {}", nameId(), a0);
1185 auto a1 = str::stod(str::trim_copy(line.substr(42, 19)), std::nan(""));
1186 LOG_DATA("{}: a1 {}", nameId(), a1);
1187 // auto a2 = str::stod(str::trim_copy(line.substr(61, 19)), std::nan(""));
1188 // LOG_DATA("{}: a2 {}", nameId(), a2);
1189
1190 if (!std::isnan(a0) && !std::isnan(a1))
1191 {
1192 std::pair<TimeSystem, TimeSystem> timeSystems;
1193 if (correctionType == "GPUT")
1194 {
1195 timeSystems = { GPST, UTC };
1196 }
1197 else if (correctionType == "GLUT")
1198 {
1199 timeSystems = { GLNT, UTC };
1200 }
1201 else if (correctionType == "GAUT")
1202 {
1203 timeSystems = { GST, UTC };
1204 }
1205 else if (correctionType == "BDUT")
1206 {
1207 timeSystems = { BDT, UTC };
1208 }
1209 else if (correctionType == "QZUT")
1210 {
1211 timeSystems = { QZSST, UTC };
1212 }
1213 else if (correctionType == "IRUT")
1214 {
1215 timeSystems = { IRNSST, UTC };
1216 }
1217 // else if (correctionType == "SBUT") { timeSystems = { SBAST, UTC }; }
1218 else if (correctionType == "GLGP")
1219 {
1220 timeSystems = { GLNT, GPST };
1221 }
1222 else if (correctionType == "GAGP")
1223 {
1224 timeSystems = { GST, GPST };
1225 }
1226 else if (correctionType == "GPGA")
1227 {
1228 timeSystems = { GST, GPST };
1229 } // Pre RINEX 3.04
1230 else if (correctionType == "QZGP")
1231 {
1232 timeSystems = { QZSST, GPST };
1233 }
1234 else if (correctionType == "IRGP")
1235 {
1236 timeSystems = { IRNSST, GPST };
1237 }
1238 else
1239 {
1240 LOG_TRACE("{}: Time System Correction '{}' not implemented yet", nameId(), correctionType);
1241 continue;
1242 }
1243 _gnssNavInfo.timeSysCorr[timeSystems] = { .a0 = a0, .a1 = a1 };
1244 }
1245 }
1246 break;
1247 case NavMsgType::EOP:
1248 // Code for EOP
1249 // TODO: Implement EOP
1250 break;
1251 case NavMsgType::ION:
1252 {
1253 // Code for ION
1254 getline(line);
1255 if (line.size() > 82)
1256 {
1257 abortReading();
1258 return;
1259 }
1260
1261 // Epoch: t_tm - Transmission timne of ION data, year (4 digits) - 4X,I4,
1262 // month, day, hour, minute, second - 5(1X,I2.2),
1263 auto timeSplit = str::split_wo_empty(line.substr(4, 20), " ");
1264 auto timeSystem = satSys == GLO ? UTC : satSys.getTimeSystem();
1265
1266 int year = std::stoi(timeSplit.at(0));
1267
1268 InsTime t_tm{ static_cast<uint16_t>(year),
1269 static_cast<uint16_t>(std::stoi(timeSplit.at(1))),
1270 static_cast<uint16_t>(std::stoi(timeSplit.at(2))),
1271 static_cast<uint16_t>(std::stoi(timeSplit.at(3))),
1272 static_cast<uint16_t>(std::stoi(timeSplit.at(4))),
1273 std::stold(timeSplit.at(5)),
1274 timeSystem };
1275
1276 LOG_DATA("{}: {}-{} {} (read as {} time)", nameId(), satSys, satNum, t_tm.toYMDHMS(), std::string(timeSystem));
1277
1278 // TODO: Destinction of BDS unclear in RINEX versin 4.00 BDGIM or Klobuchar model
1279 // Klobuchar
1280 if (satSys == GPS || satSys == QZSS || satSys == IRNSS || satSys == BDS) // NOLINT(misc-redundant-expression) // bugged warning
1281 {
1282 std::array<double, 4> alpha{};
1283 std::array<double, 4> beta{};
1284 alpha[0] = str::stod(str::trim_copy(line.substr(23, 19)), 0.0);
1285 alpha[1] = str::stod(str::trim_copy(line.substr(42, 19)), 0.0);
1286 alpha[2] = str::stod(str::trim_copy(line.substr(61, 19)), 0.0);
1287 getline(line);
1288 if (line.size() > 82)
1289 {
1290 abortReading();
1291 return;
1292 }
1293 alpha[3] = str::stod(str::trim_copy(line.substr(4, 19)), 0.0);
1294 beta[0] = str::stod(str::trim_copy(line.substr(23, 19)), 0.0);
1295 beta[1] = str::stod(str::trim_copy(line.substr(42, 19)), 0.0);
1296 beta[2] = str::stod(str::trim_copy(line.substr(61, 19)), 0.0);
1297 getline(line);
1298 if (line.size() > 82)
1299 {
1300 abortReading();
1301 return;
1302 }
1303 beta[3] = str::stod(str::trim_copy(line.substr(4, 19)), 0.0);
1304 _gnssNavInfo.ionosphericCorrections.insert(satSys, IonosphericCorrections::Alpha, alpha);
1305 _gnssNavInfo.ionosphericCorrections.insert(satSys, IonosphericCorrections::Beta, beta);
1306 LOG_DATA("{}: Ionospheric Correction: {}-Alpha: [{}]", nameId(),
1307 std::string(satSys), fmt::join(alpha.begin(), alpha.end(), ", "));
1308 LOG_DATA("{}: Ionospheric Correction: {}-Beta: [{}]", nameId(),
1309 std::string(satSys), fmt::join(beta.begin(), beta.end(), ", "));
1310 [[maybe_unused]] auto regionCode = str::stod(str::trim_copy(line.substr(23, 19)), std::nan(""));
1311 }
1312 else if (satSys == GAL) // Nequick-G
1313 {
1314 std::array<double, 4> alpha{};
1315 alpha[0] = str::stod(str::trim_copy(line.substr(23, 19)), 0.0);
1316 alpha[1] = str::stod(str::trim_copy(line.substr(42, 19)), 0.0);
1317 alpha[2] = str::stod(str::trim_copy(line.substr(61, 19)), 0.0);
1318 getline(line);
1319 if (line.size() > 82)
1320 {
1321 abortReading();
1322 return;
1323 }
1324 // TODO: Change Correction format to support Nequick-G
1325 // This is a hack this data is not an alpha value but a disturbance flag
1326 alpha[3] = str::stod(str::trim_copy(line.substr(4, 19)), 0.0);
1327 _gnssNavInfo.ionosphericCorrections.insert(satSys, IonosphericCorrections::Alpha, alpha);
1328 LOG_DATA("{}: Ionospheric Correction: {}-Alpha: [{}]", nameId(),
1329 std::string(satSys), fmt::join(alpha.begin(), alpha.end(), ", "));
1330 } /*
1331 else if (satSys == BDS) // BDGIM
1332 {
1333 // TODO: Implement Beidou ION message
1334 }*/
1335 else
1336 {
1337 continue;
1338 }
1339 }
1340 break;
1341 default:
1342 // Abort
1343 break;
1344 }
1345 }
1346 }
1347 }
1348 catch (const std::exception& e)
1349 {
1350 abortReading();
1351 }
1352}
1353
1354bool RinexNavFile::parseEphemeris(std::string& line, SatelliteSystem satSys, uint8_t satNum)
1355{
1356 // Epoch: Toc - Time of Clock (GPS) year (4 digits) - 1X,I4,
1357 // month, day, hour, minute, second - 5(1X,I2.2),
1358 auto timeSplit = str::split_wo_empty(line.substr(3, 20), " ");
1359 auto timeSystem = satSys == GLO ? UTC : satSys.getTimeSystem();
1360
1361 int year = std::stoi(timeSplit.at(0));
1362
1363 InsTime epoch{ static_cast<uint16_t>(year),
1364 static_cast<uint16_t>(std::stoi(timeSplit.at(1))),
1365 static_cast<uint16_t>(std::stoi(timeSplit.at(2))),
1366 static_cast<uint16_t>(std::stoi(timeSplit.at(3))),
1367 static_cast<uint16_t>(std::stoi(timeSplit.at(4))),
1368 std::stold(timeSplit.at(5)),
1369 timeSystem };
1370
1371 LOG_DATA("{}: {}-{} {} (read as {} time)", nameId(), satSys, satNum, epoch.toYMDHMS(), std::string(timeSystem));
1372
1373 if (satSys == GPS || satSys == GAL || satSys == QZSS || satSys == BDS) // NOLINT(misc-redundant-expression) // bugged warning
1374 {
1375 // Polynomial coefficients for clock correction
1376 std::array<double, 3> a{};
1377 // SV clock bias [seconds]
1378 a[0] = std::stod(str::replaceAll_copy(line.substr(23, 19), "d", "e", str::IgnoreCase));
1379 // SV clock drift [sec/sec]
1380 a[1] = std::stod(str::replaceAll_copy(line.substr(42, 19), "d", "e", str::IgnoreCase));
1381 // SV clock drift rate [sec/sec^2]
1382 a[2] = std::stod(str::replaceAll_copy(line.substr(61, 19), "d", "e", str::IgnoreCase));
1383
1384 LOG_DATA("{}: clkBias {}, clkDrift {}, clkDriftRate {}", nameId(), a[0], a[1], a[2]);
1385
1386 // ------------------------------------ BROADCAST ORBIT - 1 --------------------------------------
1387 getline(line);
1388 if (line.size() > 82)
1389 {
1390 abortReading();
1391 return false;
1392 } // 80 + \n\r
1393
1394 // GPS/QZSS: Issue of Data, Ephemeris (IODE)
1395 // GAL: IODnav Issue of Data of the nav batch
1396 // BDS: AODE Age of Data, Ephemeris (as specified in BeiDou ICD Table Section 5.2.4.11 Table 5-8)
1397 // and field range is: 0-31.
1398 // IRNSS: IODEC Issue of Data, Ephemeris and Clock
1399 auto IODE_IODnav_AODE_IODEC = static_cast<size_t>(std::stod(str::replaceAll_copy(line.substr(4, 19), "d", "e", str::IgnoreCase)));
1400 // Crs (meters)
1401 double Crs = std::stod(str::replaceAll_copy(line.substr(23, 19), "d", "e", str::IgnoreCase));
1402 // Delta n (radians/sec)
1403 double delta_n = std::stod(str::replaceAll_copy(line.substr(42, 19), "d", "e", str::IgnoreCase));
1404 // M0 (radians)
1405 double M_0 = std::stod(str::replaceAll_copy(line.substr(61, 19), "d", "e", str::IgnoreCase));
1406
1407 LOG_DATA("{}: IODE_IODnav_AODE_IODEC {}, Crs {}, Delta_n {}, M0 {}", nameId(), IODE_IODnav_AODE_IODEC, Crs, delta_n, M_0);
1408
1409 // ------------------------------------ BROADCAST ORBIT - 2 --------------------------------------
1410 getline(line);
1411 if (line.size() > 82)
1412 {
1413 abortReading();
1414 return false;
1415 } // 80 + \n\r
1416
1417 // Cuc (radians)
1418 double Cuc = std::stod(str::replaceAll_copy(line.substr(4, 19), "d", "e", str::IgnoreCase));
1419 // e Eccentricity
1420 double e = std::stod(str::replaceAll_copy(line.substr(23, 19), "d", "e", str::IgnoreCase));
1421 // Cus (radians)
1422 double Cus = std::stod(str::replaceAll_copy(line.substr(42, 19), "d", "e", str::IgnoreCase));
1423 // sqrt(A) (sqrt(m))
1424 double sqrt_A = std::stod(str::replaceAll_copy(line.substr(61, 19), "d", "e", str::IgnoreCase));
1425
1426 LOG_DATA("{}: Cuc {}, e {}, Cus {}, sqrt_A {}", nameId(), Cuc, e, Cus, sqrt_A);
1427
1428 // ------------------------------------ BROADCAST ORBIT - 3 --------------------------------------
1429 getline(line);
1430 if (line.size() > 82)
1431 {
1432 abortReading();
1433 return false;
1434 } // 80 + \n\r
1435
1436 // Toe Time of Ephemeris (sec of GPS week)
1437 double toeSec = std::stod(str::replaceAll_copy(line.substr(4, 19), "d", "e", str::IgnoreCase));
1438 // Cic (radians)
1439 double Cic = std::stod(str::replaceAll_copy(line.substr(23, 19), "d", "e", str::IgnoreCase));
1440 // OMEGA0 (radians)
1441 double Omega_0 = std::stod(str::replaceAll_copy(line.substr(42, 19), "d", "e", str::IgnoreCase));
1442 // Cis (radians)
1443 double Cis = std::stod(str::replaceAll_copy(line.substr(61, 19), "d", "e", str::IgnoreCase));
1444
1445 LOG_DATA("{}: toe {}, Cic {}, Omega_0 {}, Cis {}", nameId(), toeSec, Cic, Omega_0, Cis);
1446
1447 // ------------------------------------ BROADCAST ORBIT - 4 --------------------------------------
1448 getline(line);
1449 if (line.size() > 82)
1450 {
1451 abortReading();
1452 return false;
1453 } // 80 + \n\r
1454
1455 // i0 (radians)
1456 double i_0 = std::stod(str::replaceAll_copy(line.substr(4, 19), "d", "e", str::IgnoreCase));
1457 // Crc (meters)
1458 double Crc = std::stod(str::replaceAll_copy(line.substr(23, 19), "d", "e", str::IgnoreCase));
1459 // omega (radians)
1460 double omega = std::stod(str::replaceAll_copy(line.substr(42, 19), "d", "e", str::IgnoreCase));
1461 // OMEGA DOT (radians/sec)
1462 double Omega_dot = std::stod(str::replaceAll_copy(line.substr(61, 19), "d", "e", str::IgnoreCase));
1463
1464 LOG_DATA("{}: i_0 {}, Crc {}, omega {}, Omega_dot {}", nameId(), i_0, Crc, omega, Omega_dot);
1465
1466 // ------------------------------------ BROADCAST ORBIT - 5 --------------------------------------
1467 getline(line);
1468 if (line.size() > 82)
1469 {
1470 abortReading();
1471 return false;
1472 } // 80 + \n\r
1473
1474 // IDOT (radians/sec)
1475 double i_dot = std::stod(str::replaceAll_copy(line.substr(4, 19), "d", "e", str::IgnoreCase));
1476 // GPS: Codes on L2 channel
1477 // QZSS: Codes on L2 channel (fixed to 2, see IS-QZSS-PNT 4.1.2.7)
1478 // GAL: Data sources (FLOAT --> INTEGER)
1479 // Bit 0 set: I/NAV E1-B
1480 // Bit 1 set: F/NAV E5a-I
1481 // Bit 2 set: I/NAV E5b-I
1482 // Bits 0 and 2 : Both can be set if the navigation messages were merged, however, bits 0-2
1483 // cannot all be set, as the I/NAV and F/NAV messages contain different information
1484 // Bit 3 reserved for Galileo internal use
1485 // Bit 4 reserved for Galileo internal use
1486 // Bit 8 set: af0-af2, Toc, SISA are for E5a,E1
1487 // Bit 9 set: af0-af2, Toc, SISA are for E5b,E1
1488 // Bits 8-9 : exclusive (only one bit can be set)
1489 // BDS/IRNSS: Spare
1490 auto codesOnL2Channel_dataSources = static_cast<uint16_t>(str::stod(str::replaceAll_copy(line.substr(23, 19), "d", "e", str::IgnoreCase), 0));
1491
1492 // GPS Week # (to go with TOE) Continuous number, not mod(1024)!
1493 auto gpsWeek = static_cast<int32_t>(str::stod(str::replaceAll_copy(line.substr(42, 19), "d", "e", str::IgnoreCase), epoch.toGPSweekTow().gpsWeek));
1494 if (satSys == BDS) { gpsWeek += InsTimeUtil::DIFF_BDT_WEEK_TO_GPST_WEEK; }
1495 auto toe = InsTime(0, gpsWeek, toeSec, satSys.getTimeSystem());
1496
1497 // GPS: L2 P data flag
1498 // QZSS: L2P data flag set to 1 since QZSS does not track L2P
1499 // GAL/BDS/IRNSS: Spare
1500 bool L2PdataFlag = static_cast<bool>(str::stod(str::replaceAll_copy(line.substr(61, 19), "d", "e", str::IgnoreCase), 0.0));
1501
1502 LOG_DATA("{}: i_dot {}, codesOnL2Channel/dataSources {} ({}), gpsWeek {}, L2PdataFlag {}", nameId(),
1503 i_dot, codesOnL2Channel_dataSources, std::bitset<10>(codesOnL2Channel_dataSources).to_string(), gpsWeek, L2PdataFlag);
1504
1505 // ------------------------------------ BROADCAST ORBIT - 6 --------------------------------------
1506 getline(line);
1507 if (line.size() > 82)
1508 {
1509 abortReading();
1510 return false;
1511 } // 80 + \n\r
1512
1513 // GPS: SV accuracy (meters) See GPS ICD 200H Section 20.3.3.3.1.3 use specified equations to define
1514 // nominal values, N = 0-6: use 2(1+N/2) (round to one decimal place i.e. 2.8, 5.7 and 11.3) ,
1515 // N= 7-15:use 2 (N-2), 8192 specifies use at own risk
1516 // QZSS: SV accuracy (meters) (IS -QZSS-PNT, Section 5.4.3.1) which refers to: IS GPS 200H Section 20.3.3.3.1.3
1517 // use specified equations to define nominal values, N = 0-6: use 2(1+N/2) (round to one decimal
1518 // place i.e. 2.8, 5.7 and 11.3) , N= 7-15:use 2 (N-2), 8192 specifies use at own risk
1519 // IRNSS: User Range Accuracy(m), See IRNSS ICD Section 6.2.1.4 , use specified equations to define
1520 // nominal values, N = 0-6: use 2(1+N/2) (round to one decimal place i.e. 2.8, 5.7 and 11.3) ,
1521 // N= 7-15:use 2 (N-2), 8192 specifies use at own risk
1522 double signalAccuracy = std::stod(str::replaceAll_copy(line.substr(4, 19), "d", "e", str::IgnoreCase));
1523 // GPS/QZSS: SV health (bits 17-22 w 3 sf 1)
1524 // GAL: SV health (FLOAT converted to INTEGER) See Galileo ICD Section 5.1.9.3
1525 // Bit 0: E1B DVS Bits 1-2: E1B HS Bit 3: E5a DVS
1526 // Bits 4-5: E5a HS Bit 6: E5b DVS Bits 7-8: E5b HS
1527 // BDS: SatH1
1528 // IRNSS: Health (Sub frame 1,bits 155(most significant) and 156(least significant)),
1529 // where 0 = L5 and S healthy, 1 = L5 healthy and S unhealthy, 2= L5 unhealthy
1530 // and S healthy, 3= both L5 and S unhealthy
1531 auto svHealth = static_cast<uint16_t>(std::stod(str::replaceAll_copy(line.substr(23, 19), "d", "e", str::IgnoreCase)));
1532 // GPS: TGD (seconds)
1533 // QZSS: TGD (seconds) The QZSS ICD specifies a do not use bit pattern "10000000" this condition is represented by a blank field.
1534 // GAL: BGD E5a/E1 (seconds)
1535 // BDS: TGD1 B1/B3 (seconds)
1536 double tgd_bgd5a_TGD1 = str::stod(str::replaceAll_copy(line.substr(42, 19), "d", "e", str::IgnoreCase), 0.0);
1537
1538 // GPS/QZSS: IODC Issue of Data, Clock
1539 // GAL: BGD E5b/E1 (seconds)
1540 // BDS: TGD2 B2/B3 (seconds)
1541 // IRNSS: Blank
1542 double IODC_bgd5b_TGD2 = str::stod(str::replaceAll_copy(line.substr(61, 19), "d", "e", str::IgnoreCase), 0.0);
1543
1544 LOG_DATA("{}: svAccuracy {}, svHealth {}, TGD {}, IODC {}", nameId(), signalAccuracy, svHealth, tgd_bgd5a_TGD1, IODC_bgd5b_TGD2);
1545
1546 // ------------------------------------ BROADCAST ORBIT - 7 --------------------------------------
1547 getline(line);
1548 if (line.size() > 82)
1549 {
1550 abortReading();
1551 return false;
1552 } // 80 + \n\r
1553
1554 // Transmission time of message (sec of GPS week, derived e.g.from Z-count in Hand Over Word (HOW))
1555 [[maybe_unused]] auto transmissionTime = str::stod(str::replaceAll_copy(line.substr(4, 19), "d", "e", str::IgnoreCase), 0.0);
1556 // GPS/QZSS: Fit Interval in hours see section 6.11. (BNK).
1557 // GAL: Spare
1558 // BDS: AODC Age of Data Clock (as specified in BeiDou ICD Table Section 5.2.4.9 Table 5-6) and field range is: 0-31.
1559 auto fitInterval_AODC = str::stod(str::replaceAll_copy(line.substr(23, 19), "d", "e", str::IgnoreCase), 0.0);
1560 // Spare
1561 // std::stod(str::replaceAll_copy(line.substr(42, 19), "d", "e", str::IgnoreCase));
1562 // Spare
1563 // std::stod(str::replaceAll_copy(line.substr(61, 19), "d", "e", str::IgnoreCase));
1564
1565 LOG_DATA("{}: transmissionTime {}, fitInterval/AODC {}", nameId(), transmissionTime, fitInterval_AODC);
1566
1567 if (satSys == GPS)
1568 {
1569 _gnssNavInfo.addSatelliteNavData({ satSys, satNum }, std::make_shared<GPSEphemeris>(epoch, toe,
1570 IODE_IODnav_AODE_IODEC, IODC_bgd5b_TGD2, a,
1571 sqrt_A, e, i_0, Omega_0, omega, M_0,
1572 delta_n, Omega_dot, i_dot, Cus, Cuc,
1573 Cis, Cic, Crs, Crc,
1574 signalAccuracy, svHealth,
1575 codesOnL2Channel_dataSources, L2PdataFlag,
1576 tgd_bgd5a_TGD1, fitInterval_AODC));
1577 }
1578 else if (satSys == GAL)
1579 {
1580 // The same satellite can appear multiple times with different dataSource bits
1581 // We want to prefer 'I/NAV E1-B' (Bit 0 set) over 'F/NAV E5a-I' (Bit 1 set) or 'I/NAV E5b-I' (Bit 2 set)
1582
1583 if (_gnssNavInfo.satellites().contains({ satSys, satNum }) // We have this satellite already
1584 && !std::bitset<10>(codesOnL2Channel_dataSources)[0]) // This message is not 'I/NAV E1-B'
1585 {
1586 const auto& navData = _gnssNavInfo.satellites().at({ satSys, satNum }).getNavigationData();
1587 auto existingEph = std::ranges::find_if(navData, [&](const std::shared_ptr<SatNavData>& satNavData) {
1588 return satNavData->type == SatNavData::GalileoEphemeris && satNavData->refTime == epoch
1589 && std::dynamic_pointer_cast<GalileoEphemeris>(satNavData)->dataSource[0];
1590 });
1591 if (existingEph != navData.end()) // There is already a 'I/NAV E1-B' message
1592 {
1593 LOG_DATA("{}: Skipping ephemeris data because of dataSource priority", nameId());
1594 return false;
1595 }
1596 }
1597
1598 GalileoEphemeris::SvHealth health = { .E5a_DataValidityStatus = static_cast<GalileoEphemeris::SvHealth::DataValidityStatus>((svHealth & 0b000001000) >> 3),
1599 .E5b_DataValidityStatus = static_cast<GalileoEphemeris::SvHealth::DataValidityStatus>((svHealth & 0b001000000) >> 6),
1600 .E1B_DataValidityStatus = static_cast<GalileoEphemeris::SvHealth::DataValidityStatus>((svHealth & 0b000000001) >> 0),
1601 .E5a_SignalHealthStatus = static_cast<GalileoEphemeris::SvHealth::SignalHealthStatus>((svHealth & 0b000110000) >> 4),
1602 .E5b_SignalHealthStatus = static_cast<GalileoEphemeris::SvHealth::SignalHealthStatus>((svHealth & 0b110000000) >> 7),
1603 .E1B_SignalHealthStatus = static_cast<GalileoEphemeris::SvHealth::SignalHealthStatus>((svHealth & 0b000000110) >> 1) };
1604 _gnssNavInfo.addSatelliteNavData({ satSys, satNum }, std::make_shared<GalileoEphemeris>(epoch, toe, IODE_IODnav_AODE_IODEC, a,
1605 sqrt_A, e, i_0, Omega_0, omega, M_0,
1606 delta_n, Omega_dot, i_dot, Cus, Cuc,
1607 Cis, Cic, Crs, Crc,
1608 codesOnL2Channel_dataSources, signalAccuracy, health,
1609 tgd_bgd5a_TGD1, IODC_bgd5b_TGD2));
1610 }
1611 else if (satSys == BDS)
1612 {
1613 _gnssNavInfo.addSatelliteNavData({ satSys, satNum }, std::make_shared<BDSEphemeris>(satNum, epoch, toe,
1614 IODE_IODnav_AODE_IODEC, fitInterval_AODC, a,
1615 sqrt_A, e, i_0, Omega_0, omega, M_0,
1616 delta_n, Omega_dot, i_dot, Cus, Cuc,
1617 Cis, Cic, Crs, Crc,
1618 signalAccuracy, svHealth,
1619 tgd_bgd5a_TGD1, IODC_bgd5b_TGD2));
1620 }
1621 else if (satSys == QZSS)
1622 {
1623 _gnssNavInfo.addSatelliteNavData({ satSys, satNum }, std::make_shared<QZSSEphemeris>(epoch, toe,
1624 IODE_IODnav_AODE_IODEC, IODC_bgd5b_TGD2, a,
1625 sqrt_A, e, i_0, Omega_0, omega, M_0,
1626 delta_n, Omega_dot, i_dot, Cus, Cuc,
1627 Cis, Cic, Crs, Crc,
1628 signalAccuracy, svHealth,
1629 codesOnL2Channel_dataSources, L2PdataFlag,
1630 tgd_bgd5a_TGD1, fitInterval_AODC));
1631 }
1632 }
1633 else if (satSys == GLO || satSys == SBAS) // NOLINT(misc-redundant-expression) // bugged warning
1634 {
1635 // TODO: Offset
1636
1637 Eigen::Vector3d pos;
1638 Eigen::Vector3d vel;
1639 Eigen::Vector3d accelLuniSolar;
1640
1641 double tau_c{};
1642 // Coefficient of linear polynomial of time system difference [s]
1643 if (_gnssNavInfo.timeSysCorr.contains({ satSys.getTimeSystem(), UTC }))
1644 {
1645 tau_c = _gnssNavInfo.timeSysCorr.at({ satSys.getTimeSystem(), UTC }).a0;
1646 }
1647
1648 // GLO: SV clock bias (sec) (-TauN)
1649 // SBAS: SV clock bias (sec) (aGf0)
1650 double m_tau_n = std::stod(str::replaceAll_copy(line.substr(23, 19), "d", "e", str::IgnoreCase));
1651 // GLO: SV relative frequency bias (+GammaN)
1652 // SBAS: SV relative frequency bias (aGf1)
1653 double gamma_n = std::stod(str::replaceAll_copy(line.substr(42, 19), "d", "e", str::IgnoreCase));
1654 // GLO: Message frame time (tk+nd*86400) in seconds of the UTC week
1655 // SBAS: Transmission time of message (start of the message) in GPS seconds of the week
1656 [[maybe_unused]] auto msgFrameTime = std::stod(str::replaceAll_copy(line.substr(61, 19), "d", "e", str::IgnoreCase));
1657 LOG_DATA("{}: clkBias {}, relFreqBias {}, msgFrameTime {}", nameId(), m_tau_n, gamma_n, msgFrameTime);
1658
1659 // ------------------------------------ BROADCAST ORBIT - 1 --------------------------------------
1660 getline(line);
1661 if (line.size() > 82)
1662 {
1663 abortReading();
1664 return false;
1665 } // 80 + \n\r
1666
1667 // Satellite position X (km)
1668 pos.x() = std::stod(str::replaceAll_copy(line.substr(4, 19), "d", "e", str::IgnoreCase)) * 1e3;
1669 // velocity X dot (km/sec)
1670 vel.x() = std::stod(str::replaceAll_copy(line.substr(23, 19), "d", "e", str::IgnoreCase)) * 1e3;
1671 // X acceleration (km/sec2)
1672 accelLuniSolar.x() = std::stod(str::replaceAll_copy(line.substr(42, 19), "d", "e", str::IgnoreCase)) * 1e3;
1673 // GLO: health (0=healthy, 1=unhealthy) (MSB of 3-bit Bn)
1674 // SBAS: Health: SBAS: See Section 8.3.3 bit mask for: health, health availability and User Range Accuracy.
1675 [[maybe_unused]] auto health = std::stod(str::replaceAll_copy(line.substr(61, 19), "d", "e", str::IgnoreCase));
1676
1677 LOG_DATA("{}: satPosX {}, velX {}, accelX {}, health {}", nameId(), pos.x(), vel.x(), accelLuniSolar.x(), health);
1678
1679 // ------------------------------------ BROADCAST ORBIT - 2 --------------------------------------
1680 getline(line);
1681 if (line.size() > 82)
1682 {
1683 abortReading();
1684 return false;
1685 } // 80 + \n\r
1686
1687 // Satellite position Y (km)
1688 pos.y() = std::stod(str::replaceAll_copy(line.substr(4, 19), "d", "e", str::IgnoreCase)) * 1e3;
1689 // velocity Y dot (km/sec)
1690 vel.y() = std::stod(str::replaceAll_copy(line.substr(23, 19), "d", "e", str::IgnoreCase)) * 1e3;
1691 // Y acceleration (km/sec2)
1692 accelLuniSolar.y() = std::stod(str::replaceAll_copy(line.substr(42, 19), "d", "e", str::IgnoreCase)) * 1e3;
1693 // GLO: frequency number(-7...+13) (-7...+6 ICD 5.1)
1694 // SBAS: Accuracy code (URA, meters)
1695 double frequencyNumber_accuracyCode = std::stod(str::replaceAll_copy(line.substr(61, 19), "d", "e", str::IgnoreCase));
1696
1697 LOG_DATA("{}: satPosY {}, velY {}, accelY {}, freqNum/accuracyCode {}", nameId(), pos.y(), vel.y(), accelLuniSolar.y(), frequencyNumber_accuracyCode);
1698
1699 // ------------------------------------ BROADCAST ORBIT - 3 --------------------------------------
1700 getline(line);
1701 if (line.size() > 82)
1702 {
1703 abortReading();
1704 return false;
1705 } // 80 + \n\r
1706
1707 // Satellite position Z (km)
1708 pos.z() = std::stod(str::replaceAll_copy(line.substr(4, 19), "d", "e", str::IgnoreCase)) * 1e3;
1709 // velocity Z dot (km/sec)
1710 vel.z() = std::stod(str::replaceAll_copy(line.substr(23, 19), "d", "e", str::IgnoreCase)) * 1e3;
1711 // Z acceleration (km/sec2)
1712 accelLuniSolar.z() = std::stod(str::replaceAll_copy(line.substr(42, 19), "d", "e", str::IgnoreCase)) * 1e3;
1713 // GLO: Age of oper. information (days) (E)
1714 // SBAS: IODN (Issue of Data Navigation, DO229, 8 first bits after Message Type if MT9)
1715 [[maybe_unused]] auto ageOfOperation_IODN = str::stod(str::replaceAll_copy(line.substr(61, 19), "d", "e", str::IgnoreCase), 0.0);
1716
1717 LOG_DATA("{}: satPosZ {}, velZ {}, accelZ {}, ageOfOperation/IODN {}", nameId(), pos.z(), vel.z(), accelLuniSolar.z(), ageOfOperation_IODN);
1718
1719 // ------------------------------------ BROADCAST ORBIT - 4 --------------------------------------
1720 // since version 3.05 GLONASS has an additional line
1721 if (satSys == GLO && _version >= 3.05)
1722 {
1723 getline(line);
1724 if (line.size() > 82)
1725 {
1726 abortReading();
1727 return false;
1728 } // 80 + \n\r
1729
1730 // status flags (FLOAT converted to INTEGER) (can be blank)
1731 // M Bit 7-8: GLO type indicator (00=GLO, 01=GLO-M/K)
1732 // P4 Bit 6: GLO-M/K only, 1=data updated, 0=data not updated
1733 // P3 Bit 5: num of satellites in current frame alamanc (0=4 sats, 1=5 sats)
1734 // P2 Bit 4: indicate even (0) or odd (1) of time interval
1735 // P1 Bit 2-3: update and validity interval (00=0 min, 01=30 min, 10=45 min, 11=60 min)
1736 // P Bit 0-1: GLO-M/K only, time offset parameters tau_c, tau_GPS source (00=ground, 01 = tau_c ground, tau_GPS on-board, 10 = tau_c on-board, tau_GPS ground, 11 = on-board)
1737 [[maybe_unused]] double statusFlags = str::stod(str::replaceAll_copy(str::trim_copy(line.substr(4, 19)), "d", "e", str::IgnoreCase), std::nan(""));
1738 // L1/L2 group delay difference Δτ [sec]
1739 [[maybe_unused]] double L1L2groupDelayDifference = std::stod(str::replaceAll_copy(line.substr(23, 19), "d", "e", str::IgnoreCase));
1740 // URAI raw accuracy index F_T (GLO-M/K only)r
1741 [[maybe_unused]] double URAI = std::stod(str::replaceAll_copy(line.substr(42, 19), "d", "e", str::IgnoreCase));
1742 // health flags (FLOAT converted to INTEGER) (can be blank)
1743 // l(3) Bit 2: GLO-M/K only, health bit of string 3
1744 // A_C Bit 1: 1=almanac health reported in ephemerides record, 0=not reported
1745 // C Bit 0: almanac health bit (1=healthy, 0=not healthy)
1746 [[maybe_unused]] double healthFlags = str::stod(str::replaceAll_copy(str::trim_copy(line.substr(61, 19)), "d", "e", str::IgnoreCase), std::nan(""));
1747
1748 LOG_DATA("{}: statusFlags {}, L1L2groupDelayDifference {}, URAI {}, healthFlags {}", nameId(), statusFlags, L1L2groupDelayDifference, URAI, healthFlags);
1749 }
1750
1751 if (satSys == GLO)
1752 {
1753 _gnssNavInfo.addSatelliteNavData({ satSys, satNum }, std::make_shared<GLONASSEphemeris>(epoch, tau_c,
1754 -m_tau_n, gamma_n, static_cast<bool>(health),
1755 pos, vel, accelLuniSolar,
1756 frequencyNumber_accuracyCode));
1757 }
1758 else if (satSys == SBAS && !_sbasNotSupportedWarned)
1759 {
1760 LOG_WARN("SBAS is not yet supported. Therefore the Navigation file data will be skipped.");
1762 }
1763 }
1764 return true;
1765}
1766
1773
1780
1781} // namespace NAV
BDS Ephemeris information.
Save/Load the Nodes.
nlohmann::json json
json namespace
Galileo Ephemeris information.
GPS Ephemeris information.
Galileo Ephemeris information.
Ionospheric Correction data.
#define LOG_DEBUG
Debug information. Should not be called on functions which receive observations (spamming)
Definition Logger.hpp:67
#define LOG_DATA
All output which occurs repeatedly every time observations are received.
Definition Logger.hpp:29
#define LOG_ERROR
Error occurred, which stops part of the program to work, but not everything.
Definition Logger.hpp:73
#define LOG_WARN
Error occurred, but a fallback option exists and program continues to work normally.
Definition Logger.hpp:71
#define LOG_TRACE
Detailled info to trace the execution of the program. Should not be called on functions which receive...
Definition Logger.hpp:65
Manages all Nodes.
QZSS Ephemeris information.
File reader for RINEX Navigation messages.
GNSS Satellite System.
Utility functions for working with std::strings.
bool initialize()
Initialize the file reader.
void restore(const json &j)
Restores the node from a json object.
std::string _path
Path to the file.
FileType
File Type Enumeration.
@ ASCII
Ascii text data.
@ NONE
Not specified.
auto eof() const
Check whether the end of file is reached.
std::filesystem::path getFilepath()
Returns the path of the file.
@ PATH_CHANGED
The path changed and exists.
GuiResult guiConfig(const char *vFilters, const std::vector< std::string > &extensions, size_t id, const std::string &nameId)
ImGui config.
void resetReader()
Moves the read cursor to the start.
auto & getline(std::string &str)
Reads a line from the filestream.
json save() const
Saves the node into a json object.
void deinitialize()
Deinitialize the file reader.
static std::string type()
Returns the type of the data class.
The class is responsible for all time-related tasks.
Definition InsTime.hpp:710
@ Beta
Coefficients of a cubic equation representing the period of the model.
@ Alpha
Coefficients of a cubic equation representing the amplitude of the vertical delay.
bool doDeinitialize(bool wait=false)
Asks the node worker to deinitialize the node.
Definition Node.cpp:395
ImVec2 _guiConfigDefaultWindowSize
Definition Node.hpp:410
Node(std::string name)
Constructor.
Definition Node.cpp:30
std::string nameId() const
Node name and id.
Definition Node.cpp:253
std::string name
Name of the Node.
Definition Node.hpp:395
bool doReinitialize(bool wait=false)
Asks the node worker to reinitialize the node.
Definition Node.cpp:350
std::scoped_lock< std::mutex > requestOutputValueLock(size_t pinIdx)
Blocks the thread till the output values was read by all connected nodes.
Definition Node.cpp:133
bool _hasConfig
Flag if the config window should be shown.
Definition Node.hpp:413
void parseOrbit2()
Parses RINEX version 2.* messages.
void deinitialize() override
Deinitialize the node.
void parseHeader3()
Parses RINEX version 3.* headers.
static NavMsgType getNavMsgType(const std::string &type)
Converts RINEX navigation message string to enum type.
void restore(const json &j) override
Restores the node from a json object.
void parseHeader4()
Parses RINEX version 4.00 headers.
std::string type() const override
String representation of the Class Type.
static const std::set< double > _supportedVersions
Supported RINEX versions.
void abortReading()
Aborts RINEX file reading and deinitializes node.
void executeHeaderParser(double version)
Read the header of the file with correct version.
~RinexNavFile() override
Destructor.
void executeOrbitParser(double version)
Read the messages of the file with correct version.
NavMsgType
RINEX navigation message types enumeration with continuous range.
@ EOP
Earth Orientation Parameter.
void parseHeader2()
Parses RINEX version 2.* headers.
void parseOrbit4()
Parses RINEX version 4.00 messages.
double _version
Version of the RINEX file.
RinexNavFile()
Default constructor.
void parseOrbit3()
Parses RINEX version 3.* messages.
static std::string typeStatic()
String representation of the Class Type.
static constexpr size_t OUTPUT_PORT_INDEX_GNSS_NAV_INFO
Object (GnssNavInfo)
void guiConfig() override
ImGui config window which is shown on double click.
FileType determineFileType() override
Determines the type of the file.
static std::string category()
String representation of the Class Category.
bool initialize() override
Initialize the node.
bool _sbasNotSupportedWarned
Only warn once.
void readOrbits()
Read the orbit information.
json save() const override
Saves the node into a json object.
void readHeader() override
Read the Header of the file.
bool parseEphemeris(std::string &line, SatelliteSystem satSys, uint8_t satNum)
Parses ephemeris message since version 3.
GnssNavInfo _gnssNavInfo
Data object to share over the output pin.
bool resetNode() override
Resets the node. Moves the read cursor to the start.
@ GalileoEphemeris
Galileo Broadcast Ephemeris.
constexpr int32_t DIFF_BDT_WEEK_TO_GPST_WEEK
BeiDou starts zero at 1-Jan-2006 and GPS starts 6-Jan-1980.
Definition InsTime.hpp:45
OutputPin * CreateOutputPin(Node *node, const char *name, Pin::Type pinType, const std::vector< std::string > &dataIdentifier, OutputPin::PinData data=static_cast< void * >(nullptr), int idx=-1)
Create an Output Pin object.
void ApplyChanges()
Signals that there have been changes to the flow.
int stoi(const String &str, int default_value, std::size_t *pos=nullptr, int base=10) noexcept
Interprets a value in the string str.
double stod(const String &str, double default_value, std::size_t *pos=nullptr) noexcept
Interprets a value in the string str.
static std::string trim_copy(std::string s)
Trim from both ends (copying)
static void rtrim(std::string &s)
Trim from end (in place)
static std::string replaceAll_copy(std::string str, const std::string &from, const std::string &to, CaseSensitivity cs)
Replaces all occurrence of a search pattern with another sequence.
static std::string rtrim_copy(std::string s)
Trim from end (copying)
static std::vector< std::string > split_wo_empty(const std::string &str, const std::string &delimiter)
Splits a string into parts at a delimiter and removes empty entries.
@ IgnoreCase
Ignore case.
@ IRNSST
Indian Regional Navigation Satellite System Time.
@ GST
Galileo System Time.
@ BDT
BeiDou Time.
@ GLNT
GLONASS Time (GLONASST)
@ QZSST
Quasi-Zenith Satellite System Time.
@ GPST
GPS Time.
@ UTC
Coordinated Universal Time.
const char * to_string(gui::widgets::PositionWithFrame::ReferenceFrame refFrame)
Converts the enum to a string.
@ GPS
Global Positioning System.
@ QZSS
Quasi-Zenith Satellite System.
@ GLO
Globalnaja nawigazionnaja sputnikowaja sistema (GLONASS)
@ GAL
Galileo.
@ SBAS
Satellite Based Augmentation System.
@ BDS
Beidou.
@ SatSys_None
No Satellite system.
@ IRNSS
Indian Regional Navigation Satellite System.
Navigation Data Validity and Signal Health Status.
DataValidityStatus
Navigation Data Validity.
SignalHealthStatus
Signal Health Status.
@ Object
Generic Object.
Definition Pin.hpp:57
Satellite System type.
TimeSystem getTimeSystem() const
Get the Time System of this Satellite System.
static SatelliteSystem fromString(const std::string &typeString)
Construct new object from std::string.
static SatelliteSystem fromChar(char typeChar)
Construct new object from char.