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