11#include <fmt/ranges.h>
50 return "RinexObsFile";
60 return "Data Provider";
66 { ".obs",
".rnx",
"(.+[.]\\d\\d?[oO])" }, size_t(
id),
nameId()))
79 ImGui::Text(
"Supported versions: ");
82 ImGui::Text(
"%0.2f", x);
87 gui::widgets::HelpMarker(
"Whether to remove less precise codes (e.g. if G1X (L1C combined) is present, don't use G1L (L1C pilot) and G1S (L1C data))");
106 if (j.contains(
"FileReader"))
110 if (j.contains(
"eraseLessPreciseCodes"))
147 auto extHeaderLabel = [](std::string line) {
149 line.erase(std::ranges::find_if(line, [](
int ch) {
return std::iscntrl(ch); }), line.end());
156 auto filestreamHeader = std::ifstream(filepath);
157 if (filestreamHeader.good())
161 std::getline(filestreamHeader, line);
163 if (line.size() != 80)
165 LOG_ERROR(
"{}: Not a valid RINEX OBS file. Lines should be 80 characters long but the file has {}.",
nameId(), line.size() - 1);
169 if (extHeaderLabel(line) !=
"RINEX VERSION / TYPE")
171 LOG_ERROR(
"{}: Not a valid RINEX OBS file. Could not read 'RINEX VERSION / TYPE' line.",
nameId());
178 LOG_ERROR(
"{}: RINEX version {} is not supported. Supported versions are [{}]",
nameId(),
184 if (fileType.at(0) !=
'O')
186 LOG_ERROR(
"{}: Not a valid RINEX OBS file. File type '{}' not recognized.",
nameId(), fileType);
193 LOG_ERROR(
"{}: Not a valid RINEX OBS file. Satellite System '{}' not recognized.",
nameId(), satSystem.at(0));
198 std::getline(filestreamHeader, line);
200 if (extHeaderLabel(line) !=
"PGM / RUN BY / DATE")
202 LOG_ERROR(
"{}: Not a valid RINEX OBS file. Could not read 'PGM / RUN BY / DATE' line.",
nameId());
207 while (std::getline(filestreamHeader, line))
210 if (extHeaderLabel(line) ==
"END OF HEADER")
215 LOG_ERROR(
"{}: Not a valid RINEX NAV file. Could not read 'END OF HEADER' line.",
nameId());
219 LOG_ERROR(
"{}: Could not determine file type because file could not be opened '{}' line.",
nameId(), filepath.string());
238 if (line.size() < 60)
240 LOG_WARN(
"{}: Skipping header line because it does not include a header label: '{}'",
nameId(), line);
244 if (headerLabel ==
"PGM / RUN BY / DATE")
253 else if (headerLabel ==
"COMMENT")
257 else if (headerLabel ==
"MARKER NAME")
260 if (!markerName.empty())
265 else if (headerLabel ==
"MARKER NUMBER")
268 if (!markerNumber.empty())
273 else if (headerLabel ==
"MARKER TYPE")
276 if (!markerType.empty())
281 else if (headerLabel ==
"OBSERVER / AGENCY")
285 if (!observer.empty() || !agency.empty())
287 LOG_DATA(
"{}: Observer '{}', Agency '{}'",
nameId(), observer, agency);
290 else if (headerLabel ==
"REC # / TYPE / VERS")
295 if (!receiverNumber.empty() || !receiverType.empty() || !receiverVersion.empty())
297 LOG_DATA(
"{}: RecNum '{}', recType '{}', recVersion '{}'",
nameId(),
298 receiverNumber, receiverType, receiverVersion);
301 else if (headerLabel ==
"ANT # / TYPE")
305 if (!antennaNumber.empty() || !antennaType.empty())
307 LOG_DATA(
"{}: antNum '{}', antType '{}'",
nameId(), antennaNumber, antennaType);
309 if (!antennaType.empty() || antennaType !=
"Unknown")
314 else if (headerLabel ==
"APPROX POSITION XYZ")
318 Eigen::Vector3d position_xyz{ std::stod(
str::trim_copy(line.substr(0, 14))),
322 LOG_DATA(
"{}: Approx Position XYZ: {} (not used yet)",
nameId(), position_xyz.transpose());
326 else if (headerLabel ==
"ANTENNA: DELTA H/E/N")
329 double antennaHeight = std::stod(
str::trim_copy(line.substr(0, 14)));
331 double antennaEccentricityEast = std::stod(
str::trim_copy(line.substr(14, 14)));
333 double antennaEccentricityNorth = std::stod(
str::trim_copy(line.substr(28, 14)));
335 LOG_DATA(
"{}: Antenna delta H/E/N: {}, {}, {} (not used yet)",
nameId(),
336 antennaHeight, antennaEccentricityEast, antennaEccentricityNorth);
338 _receiverInfo.antennaDeltaNEU = Eigen::Vector3d(antennaEccentricityNorth, antennaEccentricityEast, antennaHeight);
340 else if (headerLabel ==
"ANTENNA: DELTA X/Y/Z")
343 [[maybe_unused]] Eigen::Vector3d antennaDeltaXYZ{ std::stod(
str::trim_copy(line.substr(0, 14))),
347 LOG_DATA(
"{}: Antenna Delta XYZ: {} (not used yet)",
nameId(), antennaDeltaXYZ.transpose());
349 else if (headerLabel ==
"ANTENNA: PHASECENTER")
351 LOG_TRACE(
"{}: '{}' not implemented yet",
nameId(),
"ANTENNA: PHASECENTER");
353 else if (headerLabel ==
"ANTENNA: B.SIGHT XYZ")
355 LOG_TRACE(
"{}: '{}' not implemented yet",
nameId(),
"ANTENNA: B.SIGHT XYZ");
357 else if (headerLabel ==
"ANTENNA: ZERODIR AZI")
359 LOG_TRACE(
"{}: '{}' not implemented yet",
nameId(),
"ANTENNA: ZERODIR AZI");
361 else if (headerLabel ==
"ANTENNA: ZERODIR XYZ")
363 LOG_TRACE(
"{}: '{}' not implemented yet",
nameId(),
"ANTENNA: ZERODIR XYZ");
365 else if (headerLabel ==
"CENTER OF MASS: XYZ")
367 LOG_TRACE(
"{}: '{}' not implemented yet",
nameId(),
"CENTER OF MASS: XYZ");
369 else if (headerLabel ==
"SYS / # / OBS TYPES")
375 size_t numSpecifications = std::stoul(line.substr(3, 3));
377 std::string debugOutput;
378 for (
size_t n = 0, nLine = 1, i = 7; n < numSpecifications; n++, nLine++, i += 4)
384 auto attribute = line.at(i + 2);
385 if (freq ==
B01 && attribute ==
'I') { freq =
B02; }
401 satSys, numSpecifications, debugOutput);
403 else if (headerLabel ==
"SIGNAL STRENGTH UNIT")
405 LOG_TRACE(
"{}: '{}' not implemented yet",
nameId(),
"SIGNAL STRENGTH UNIT");
407 else if (headerLabel ==
"INTERVAL")
411 else if (headerLabel ==
"TIME OF FIRST OBS")
413 [[maybe_unused]]
auto year = std::stoi(line.substr(0, 6));
414 [[maybe_unused]]
auto month = std::stoi(line.substr(6, 6));
415 [[maybe_unused]]
auto day = std::stoi(line.substr(12, 6));
416 [[maybe_unused]]
auto hour = std::stoi(line.substr(18, 6));
417 [[maybe_unused]]
auto min = std::stoi(line.substr(24, 6));
418 [[maybe_unused]]
auto sec = std::stold(line.substr(30, 13));
420 LOG_DATA(
"{}: Time of first obs: {} GPST (originally in '{}' time)",
nameId(),
421 InsTime{
static_cast<uint16_t
>(year),
422 static_cast<uint16_t
>(month),
423 static_cast<uint16_t
>(day),
424 static_cast<uint16_t
>(hour),
425 static_cast<uint16_t
>(min),
431 else if (headerLabel ==
"TIME OF LAST OBS")
433 LOG_TRACE(
"{}: '{}' not implemented yet",
nameId(),
"TIME OF LAST OBS");
435 else if (headerLabel ==
"RCV CLOCK OFFS APPL")
440 LOG_INFO(
"{}: Data (epoch, pseudorange, phase) corrected by the reported clock offset.",
nameId());
444 else if (headerLabel ==
"SYS / DCBS APPLIED")
446 LOG_TRACE(
"{}: '{}' not implemented yet",
nameId(),
"SYS / DCBS APPLIED");
448 else if (headerLabel ==
"SYS / PCVS APPLIED")
450 LOG_TRACE(
"{}: '{}' not implemented yet",
nameId(),
"SYS / PCVS APPLIED");
452 else if (headerLabel ==
"SYS / SCALE FACTOR")
454 LOG_TRACE(
"{}: '{}' not implemented yet",
nameId(),
"SYS / SCALE FACTOR");
456 else if (headerLabel ==
"SYS / PHASE SHIFT")
458 LOG_TRACE(
"{}: '{}' not implemented yet",
nameId(),
"SYS / PHASE SHIFT");
460 else if (headerLabel ==
"GLONASS SLOT / FRQ #")
462 LOG_TRACE(
"{}: '{}' not implemented yet",
nameId(),
"GLONASS SLOT / FRQ #");
464 else if (headerLabel ==
"GLONASS COD/PHS/BIS")
466 LOG_TRACE(
"{}: '{}' not implemented yet",
nameId(),
"GLONASS COD/PHS/BIS");
468 else if (headerLabel ==
"LEAP SECONDS")
472 else if (headerLabel ==
"# OF SATELLITES")
474 LOG_TRACE(
"{}: '{}' not implemented yet",
nameId(),
"# OF SATELLITES");
476 else if (headerLabel ==
"PRN / # OF OBS")
480 else if (headerLabel ==
"END OF HEADER")
486 LOG_WARN(
"{}: Unknown header label '{}' in line '{}'",
nameId(), headerLabel, line);
515 LOG_CRITICAL(
"{}: Could not determine time system of the file because satellite system '{}' has no default.",
535 size_t nSatellites = 0;
536 while (epochFlag != 0 && !
eof() &&
getline(line))
544 if (line.at(0) ==
'>')
546 auto year = std::stoi(line.substr(2, 4));
547 auto month = std::stoi(line.substr(7, 2));
548 auto day = std::stoi(line.substr(10, 2));
549 auto hour = std::stoi(line.substr(13, 2));
550 auto min = std::stoi(line.substr(16, 2));
551 auto sec = std::stold(line.substr(18, 11));
552 nSatellites = std::stoul(line.substr(29 + 3, 3));
554 [[maybe_unused]]
double recClkOffset = 0.0;
557 recClkOffset = line.size() >= 41 + 3 ? std::stod(line.substr(41, 15)) : 0.0;
559 catch (
const std::exception& )
561 LOG_DATA(
"{}: 'recClkOffset' not mentioned in file --> recClkOffset = {}",
nameId(), recClkOffset);
568 epochTime =
InsTime{
static_cast<uint16_t
>(year),
static_cast<uint16_t
>(month),
static_cast<uint16_t
>(day),
569 static_cast<uint16_t
>(hour),
static_cast<uint16_t
>(min), sec,
572 epochFlag = std::stoi(line.substr(31, 1));
574 LOG_DATA(
"{}: {}, epochFlag {}, numSats {}, recClkOffset {}",
nameId(),
575 epochTime.
toYMDHMS(), epochFlag, nSatellites, recClkOffset);
578 if (epochTime.
empty())
583 auto gnssObs = std::make_shared<GnssObs>();
584 gnssObs->insTime = epochTime;
596 auto satNum =
static_cast<uint8_t
>(std::stoi(line.substr(1, 2)));
598 LOG_DATA(
"{}: [{}] {}{}:",
nameId(), gnssObs->insTime.toYMDHMS(
GPST),
char(satSys), satNum);
600 size_t curExtractLoc = 3;
603 if (line.size() < curExtractLoc + 14)
616 double observation{};
619 observation = std::stod(strObs);
621 catch (
const std::exception& e)
623 if ((*gnssObs)({ obsDesc.code, satNum }).pseudorange)
627 LOG_WARN(
"{}: observation of satSys = {} contains no carrier phase. This happens if the CN0 is so small that the PLL could not lock, even if the DLL has locked (= pseudorange available). The observation is still valid.",
nameId(),
char(satSys));
631 LOG_WARN(
"{}: observation of satSys = {} contains no doppler.",
nameId(),
char(satSys));
646 if (line.size() > curExtractLoc)
648 char LLIc = line.at(curExtractLoc);
653 LLI =
static_cast<uint8_t
>(LLIc -
'0');
671 if (line.size() > curExtractLoc)
673 char SSIc = line.at(curExtractLoc);
678 SSI =
static_cast<uint8_t
>(SSIc -
'0');
682 switch (obsDesc.type)
685 (*gnssObs)({ obsDesc.code, satNum }).pseudorange = { .value = observation,
689 (*gnssObs)({ obsDesc.code, satNum }).carrierPhase = { .value = observation,
694 (*gnssObs)({ obsDesc.code, satNum }).doppler = observation;
697 (*gnssObs)({ obsDesc.code, satNum }).CN0 = observation;
702 LOG_WARN(
"{}: ObsType {} not supported",
nameId(),
size_t(obsDesc.type));
706 gnssObs->satData(
SatId{ satSys, satNum }).frequencies |= obsDesc.code.getFrequency();
710 observation, LLI, SSI);
715 if (gnssObs->data.back().pseudorange)
717 if (!gnssObs->data.back().carrierPhase)
719 LOG_DATA(
"{}: A data record at epoch {} (plus leap seconds) contains Pseudorange, but is missing carrier phase.",
nameId(), epochTime.
toYMDHMS());
721 if (!gnssObs->data.back().doppler)
723 LOG_DATA(
"{}: A data record at epoch {} (plus leap seconds) contains Pseudorange, but is missing doppler.",
nameId(), epochTime.
toYMDHMS());
725 if (!gnssObs->data.back().CN0)
727 LOG_DATA(
"{}: A data record at epoch {} (plus leap seconds) contains Pseudorange, but is missing raw signal strength(carrier to noise ratio).",
nameId(), epochTime.
toYMDHMS());
732 if (satCnt != nSatellites)
734 LOG_WARN(
"{}: [{}] {} satellites read, but epoch header specified {} satellites",
nameId(), gnssObs->insTime.toYMDHMS(
GPST), satCnt, nSatellites);
745 auto eraseLessPrecise = [&](
const Code& third,
const Code& second,
const Code& prime) {
746 auto eraseSatDataWithCode = [&](
const Code& code) {
749 return idData.satSigId == SatSigId{ code, satNum };
751 if (iter != gnssObs->data.end())
754 gnssObs->data.erase(iter);
758 if (gnssObs->contains({ prime, satNum }))
760 eraseSatDataWithCode(second);
761 eraseSatDataWithCode(third);
763 else if (gnssObs->contains({ second, satNum }))
765 eraseSatDataWithCode(third);
nlohmann::json json
json namespace
GNSS Observation messages.
Text Help Marker (?) with Tooltip.
Utility class for logging to console and file.
#define LOG_CRITICAL(...)
Critical Event, which causes the program to work entirely and throws an exception.
#define LOG_DEBUG
Debug information. Should not be called on functions which receive observations (spamming)
#define LOG_DATA
All output which occurs repeatedly every time observations are received.
#define LOG_ERROR
Error occurred, which stops part of the program to work, but not everything.
#define LOG_WARN
Error occurred, but a fallback option exists and program continues to work normally.
#define LOG_INFO
Info to the user on the state of the program.
#define LOG_TRACE
Detailled info to trace the execution of the program. Should not be called on functions which receive...
Functions to work with RINEX.
File reader for RINEX Observation messages.
Utility functions for working with std::strings.
Enumerate for GNSS Codes.
@ B7I
BeiDou B2b (BDS-2) - B2I(OS)
@ R6A
GLO G2a - L2CSI (data)
@ J2L
QZSS L2 - L2C-code (long)
@ G1L
GPS L1 - L1C-P (pilot)
@ I5X
IRNSS L5 - RS (combined)
@ J6L
QZSS L6 - L6P LEX signal (long)
@ B5P
BeiDou B2a - Pilot(P)
@ B7D
BeiDou B2b (BDS-3) - Data (D)
@ G1X
GPS L1 - L1C-(D+P) (combined)
@ G2X
GPS L2 - L2C(M+L) (combined)
@ J6X
QZSS L6 - L6(D+P) LEX signal (combined)
@ E6X
GAL E6 - Combined (B+C)
@ J1L
QZSS L1 - L1C (pilot)
@ J1S
QZSS L1 - L1C (data)
@ I5C
IRNSS L5 - RS (pilot)
@ I5B
IRNSS L5 - RS (data)
@ J1X
QZSS L1 - L1C (combined)
@ E8X
GAL E5(a+b) - AltBOC (combined)
@ B1P
BeiDou B1 - Pilot(P)
@ J2X
QZSS L2 - L2C-code (combined)
@ B1D
BeiDou B1 - Data (D)
@ G2L
GPS L2 - L2C(L) (long)
@ R6B
GLO G2a - L2OCp (pilot)
@ I9X
IRNSS S - RS (combined)
@ B8X
BeiDou B2 (B2a+B2b) - D+P.
@ R4X
GLO G1a - L1OCd+L1OCp (combined)
@ B8D
BeiDou B2 (B2a+B2b) - Data (D)
@ E8I
GAL E5(a+b) - AltBOC (data)
@ B7P
BeiDou B2b (BDS-3) - Pilot(P)
@ E8Q
GAL E5(a+b) - AltBOC (pilot)
@ B5D
BeiDou B2a - Data (D)
@ J6S
QZSS L6 - L6D LEX signal (short)
@ R4B
GLO G1a - L1OCp (pilot)
@ B6X
BeiDou B3 - B3I, B3Q, combined.
@ B7X
BeiDou B2b (BDS-2) - B2I(OS), B2Q, combined.
@ E1X
GAL E1 - OS(B+C) (combined)
@ B2I
BeiDou B1-2 - B1I(OS)
@ B7Q
BeiDou B2b (BDS-2) - B2Q.
@ R6X
GLO G2a - L2CSI+L2OCp (combined)
@ G1S
GPS L1 - L1C-D (data)
@ I9C
IRNSS S - RS (pilot)
@ B2X
BeiDou B1-2 - B1I(OS), B1Q, combined.
@ B7Z
BeiDou B2b (BDS-3) - D+P.
@ B8P
BeiDou B2 (B2a+B2b) - Pilot(P)
@ G2S
GPS L2 - L2C(M) (medium)
@ J2S
QZSS L2 - L2C-code (medium)
@ R4A
GLO G1a - L1OCd (data)
static Code fromFreqAttr(Frequency freq, char attribute)
Generates a Code from frequency and attribute.
bool initialize()
Initialize the file reader.
void restore(const json &j)
Restores the node from a json object.
auto peek()
Looking ahead in the stream.
std::string _path
Path to the file.
FileType
File Type Enumeration.
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.
Frequency definition for different satellite systems.
static std::string type()
Returns the type of the data class.
The class is responsible for all time-related tasks.
constexpr InsTime_YMDHMS toYMDHMS(TimeSystem timesys=UTC, int digits=-1) const
Converts this time object into a different format.
constexpr bool empty() const
Checks if the Time object has a value.
bool doDeinitialize(bool wait=false)
Asks the node worker to deinitialize the node.
ImVec2 _guiConfigDefaultWindowSize
Node(std::string name)
Constructor.
std::string nameId() const
Node name and id.
std::string name
Name of the Node.
bool doReinitialize(bool wait=false)
Asks the node worker to reinitialize the node.
void invokeCallbacks(size_t portIndex, const std::shared_ptr< const NodeData > &data)
Calls all registered callbacks on the specified output port.
bool _hasConfig
Flag if the config window should be shown.
void eraseLessPreciseCodes(const std::shared_ptr< GnssObs > &gnssObs, const Frequency &freq, uint16_t satNum)
Removes less precise codes (e.g. if G1X (L1C combined) is present, don't use G1L (L1C pilot) and G1S ...
static const std::set< double > _supportedVersions
Supported RINEX versions.
double _version
Version of the RINEX file.
void restore(const json &j) override
Restores the node from a json object.
void deinitialize() override
Deinitialize the node.
std::string type() const override
String representation of the Class Type.
TimeSystem _timeSystem
Time system of all observations in the file.
bool _rcvClockOffsAppl
Receiver clock offset app.
json save() const override
Saves the node into a json object.
void guiConfig() override
ImGui config window which is shown on double click.
RinexObsFile()
Default constructor.
bool initialize() override
Initialize the node.
static std::string category()
String representation of the Class Category.
static constexpr size_t OUTPUT_PORT_INDEX_GNSS_OBS
Flow (GnssObs)
FileType determineFileType() override
Determines the type of the file.
GnssObs::ReceiverInfo _receiverInfo
Receiver Info transmitted with the observation.
~RinexObsFile() override
Destructor.
bool resetNode() override
Resets the node. Moves the read cursor to the start.
bool _eraseLessPreciseCodes
Whether to remove less precise codes (e.g. if G1X (L1C combined) is present, don't use G1L (L1C pilot...
void readHeader() override
Read the Header of the file.
std::shared_ptr< const NodeData > pollData()
Polls the data from the file.
static std::string typeStatic()
String representation of the Class Type.
std::unordered_map< SatelliteSystem, std::vector< NAV::vendor::RINEX::ObservationDescription > > _obsDescription
Observation description. [Key]: Satellite System, [Value]: List with descriptions.
static TimeSystem fromString(const std::string &typeString)
Construct new object from std::string.
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.
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 void trim(std::string &s)
Trim from both ends (in place)
ObsType
Observation types of the 'SYS / # / OBS TYPES' header.
@ X
Receiver channel numbers.
@ S
Raw signal strength(carrier to noise ratio)
@ I
Ionosphere phase delay.
char obsTypeToChar(ObsType type)
Converts an ObsType to char.
Frequency getFrequencyFromBand(SatelliteSystem satSys, int band)
Get the Frequency from the provided satellite system and band in the 'SYS / # / OBS TYPES' header.
ObsType obsTypeFromChar(char c)
Converts a character to an ObsType.
@ IRNSST
Indian Regional Navigation Satellite System Time.
@ GLNT
GLONASS Time (GLONASST)
@ TimeSys_None
No Time system.
@ QZSST
Quasi-Zenith Satellite System Time.
@ UTC
Coordinated Universal Time.
@ B02
Beidou B1-2 (B1I) (1561.098 MHz).
@ B01
Beidou B1 (1575.42 MHz).
SatelliteSystem_
Satellite System enumeration.
@ GPS
Global Positioning System.
@ QZSS
Quasi-Zenith Satellite System.
@ GLO
Globalnaja nawigazionnaja sputnikowaja sistema (GLONASS)
@ SBAS
Satellite Based Augmentation System.
@ SatSys_None
No Satellite system.
@ IRNSS
Indian Regional Navigation Satellite System.
Stores the satellites observations.
Identifies a satellite (satellite system and number)
static SatelliteSystem fromChar(char typeChar)
Construct new object from char.
Description of the observations from the 'SYS / # / OBS TYPES' header.