11#include <fmt/ranges.h>
48 return "RinexObsFile";
58 return "Data Provider";
64 { ".obs",
".rnx",
"(.+[.]\\d\\d?[oO])" }, size_t(
id),
nameId()))
77 ImGui::Text(
"Supported versions: ");
80 ImGui::Text(
"%0.2f", x);
85 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))");
104 if (j.contains(
"FileReader"))
108 if (j.contains(
"eraseLessPreciseCodes"))
145 auto extHeaderLabel = [](std::string line) {
147 line.erase(std::ranges::find_if(line, [](
int ch) {
return std::iscntrl(ch); }), line.end());
154 auto filestreamHeader = std::ifstream(filepath);
155 if (filestreamHeader.good())
159 std::getline(filestreamHeader, line);
161 if (line.size() != 80)
163 LOG_ERROR(
"{}: Not a valid RINEX OBS file. Lines should be 80 characters long but the file has {}.",
nameId(), line.size() - 1);
167 if (extHeaderLabel(line) !=
"RINEX VERSION / TYPE")
169 LOG_ERROR(
"{}: Not a valid RINEX OBS file. Could not read 'RINEX VERSION / TYPE' line.",
nameId());
176 LOG_ERROR(
"{}: RINEX version {} is not supported. Supported versions are [{}]",
nameId(),
182 if (fileType.at(0) !=
'O')
184 LOG_ERROR(
"{}: Not a valid RINEX OBS file. File type '{}' not recognized.",
nameId(), fileType);
191 LOG_ERROR(
"{}: Not a valid RINEX OBS file. Satellite System '{}' not recognized.",
nameId(), satSystem.at(0));
196 std::getline(filestreamHeader, line);
198 if (extHeaderLabel(line) !=
"PGM / RUN BY / DATE")
200 LOG_ERROR(
"{}: Not a valid RINEX OBS file. Could not read 'PGM / RUN BY / DATE' line.",
nameId());
205 while (std::getline(filestreamHeader, line))
208 if (extHeaderLabel(line) ==
"END OF HEADER")
213 LOG_ERROR(
"{}: Not a valid RINEX NAV file. Could not read 'END OF HEADER' line.",
nameId());
217 LOG_ERROR(
"{}: Could not determine file type because file could not be opened '{}' line.",
nameId(), filepath.string());
236 if (line.size() < 60)
238 LOG_WARN(
"{}: Skipping header line because it does not include a header label: '{}'",
nameId(), line);
242 if (headerLabel ==
"PGM / RUN BY / DATE")
251 else if (headerLabel ==
"COMMENT")
255 else if (headerLabel ==
"MARKER NAME")
258 if (!markerName.empty())
263 else if (headerLabel ==
"MARKER NUMBER")
266 if (!markerNumber.empty())
271 else if (headerLabel ==
"MARKER TYPE")
274 if (!markerType.empty())
279 else if (headerLabel ==
"OBSERVER / AGENCY")
283 if (!observer.empty() || !agency.empty())
285 LOG_DATA(
"{}: Observer '{}', Agency '{}'",
nameId(), observer, agency);
288 else if (headerLabel ==
"REC # / TYPE / VERS")
293 if (!receiverNumber.empty() || !receiverType.empty() || !receiverVersion.empty())
295 LOG_DATA(
"{}: RecNum '{}', recType '{}', recVersion '{}'",
nameId(),
296 receiverNumber, receiverType, receiverVersion);
299 else if (headerLabel ==
"ANT # / TYPE")
303 if (!antennaNumber.empty() || !antennaType.empty())
305 LOG_DATA(
"{}: antNum '{}', antType '{}'",
nameId(), antennaNumber, antennaType);
307 if (!antennaType.empty() || antennaType !=
"Unknown")
312 else if (headerLabel ==
"APPROX POSITION XYZ")
316 Eigen::Vector3d position_xyz{ std::stod(
str::trim_copy(line.substr(0, 14))),
320 LOG_DATA(
"{}: Approx Position XYZ: {} (not used yet)",
nameId(), position_xyz.transpose());
324 else if (headerLabel ==
"ANTENNA: DELTA H/E/N")
327 double antennaHeight = std::stod(
str::trim_copy(line.substr(0, 14)));
329 double antennaEccentricityEast = std::stod(
str::trim_copy(line.substr(14, 14)));
331 double antennaEccentricityNorth = std::stod(
str::trim_copy(line.substr(28, 14)));
333 LOG_DATA(
"{}: Antenna delta H/E/N: {}, {}, {} (not used yet)",
nameId(),
334 antennaHeight, antennaEccentricityEast, antennaEccentricityNorth);
336 _receiverInfo.antennaDeltaNEU = Eigen::Vector3d(antennaEccentricityNorth, antennaEccentricityEast, antennaHeight);
338 else if (headerLabel ==
"ANTENNA: DELTA X/Y/Z")
341 [[maybe_unused]] Eigen::Vector3d antennaDeltaXYZ{ std::stod(
str::trim_copy(line.substr(0, 14))),
345 LOG_DATA(
"{}: Antenna Delta XYZ: {} (not used yet)",
nameId(), antennaDeltaXYZ.transpose());
347 else if (headerLabel ==
"ANTENNA: PHASECENTER")
349 LOG_TRACE(
"{}: '{}' not implemented yet",
nameId(),
"ANTENNA: PHASECENTER");
351 else if (headerLabel ==
"ANTENNA: B.SIGHT XYZ")
353 LOG_TRACE(
"{}: '{}' not implemented yet",
nameId(),
"ANTENNA: B.SIGHT XYZ");
355 else if (headerLabel ==
"ANTENNA: ZERODIR AZI")
357 LOG_TRACE(
"{}: '{}' not implemented yet",
nameId(),
"ANTENNA: ZERODIR AZI");
359 else if (headerLabel ==
"ANTENNA: ZERODIR XYZ")
361 LOG_TRACE(
"{}: '{}' not implemented yet",
nameId(),
"ANTENNA: ZERODIR XYZ");
363 else if (headerLabel ==
"CENTER OF MASS: XYZ")
365 LOG_TRACE(
"{}: '{}' not implemented yet",
nameId(),
"CENTER OF MASS: XYZ");
367 else if (headerLabel ==
"SYS / # / OBS TYPES")
373 size_t numSpecifications = std::stoul(line.substr(3, 3));
375 std::string debugOutput;
376 for (
size_t n = 0, nLine = 1, i = 7; n < numSpecifications; n++, nLine++, i += 4)
382 auto attribute = line.at(i + 2);
383 if (freq ==
B01 && attribute ==
'I') { freq =
B02; }
399 satSys, numSpecifications, debugOutput);
401 else if (headerLabel ==
"SIGNAL STRENGTH UNIT")
403 LOG_TRACE(
"{}: '{}' not implemented yet",
nameId(),
"SIGNAL STRENGTH UNIT");
405 else if (headerLabel ==
"INTERVAL")
409 else if (headerLabel ==
"TIME OF FIRST OBS")
411 [[maybe_unused]]
auto year = std::stoi(line.substr(0, 6));
412 [[maybe_unused]]
auto month = std::stoi(line.substr(6, 6));
413 [[maybe_unused]]
auto day = std::stoi(line.substr(12, 6));
414 [[maybe_unused]]
auto hour = std::stoi(line.substr(18, 6));
415 [[maybe_unused]]
auto min = std::stoi(line.substr(24, 6));
416 [[maybe_unused]]
auto sec = std::stold(line.substr(30, 13));
418 LOG_DATA(
"{}: Time of first obs: {} GPST (originally in '{}' time)",
nameId(),
419 InsTime{
static_cast<uint16_t
>(year),
420 static_cast<uint16_t
>(month),
421 static_cast<uint16_t
>(day),
422 static_cast<uint16_t
>(hour),
423 static_cast<uint16_t
>(min),
429 else if (headerLabel ==
"TIME OF LAST OBS")
431 LOG_TRACE(
"{}: '{}' not implemented yet",
nameId(),
"TIME OF LAST OBS");
433 else if (headerLabel ==
"RCV CLOCK OFFS APPL")
438 LOG_INFO(
"{}: Data (epoch, pseudorange, phase) corrected by the reported clock offset.",
nameId());
442 else if (headerLabel ==
"SYS / DCBS APPLIED")
444 LOG_TRACE(
"{}: '{}' not implemented yet",
nameId(),
"SYS / DCBS APPLIED");
446 else if (headerLabel ==
"SYS / PCVS APPLIED")
448 LOG_TRACE(
"{}: '{}' not implemented yet",
nameId(),
"SYS / PCVS APPLIED");
450 else if (headerLabel ==
"SYS / SCALE FACTOR")
452 LOG_TRACE(
"{}: '{}' not implemented yet",
nameId(),
"SYS / SCALE FACTOR");
454 else if (headerLabel ==
"SYS / PHASE SHIFT")
456 LOG_TRACE(
"{}: '{}' not implemented yet",
nameId(),
"SYS / PHASE SHIFT");
458 else if (headerLabel ==
"GLONASS SLOT / FRQ #")
460 LOG_TRACE(
"{}: '{}' not implemented yet",
nameId(),
"GLONASS SLOT / FRQ #");
462 else if (headerLabel ==
"GLONASS COD/PHS/BIS")
464 LOG_TRACE(
"{}: '{}' not implemented yet",
nameId(),
"GLONASS COD/PHS/BIS");
466 else if (headerLabel ==
"LEAP SECONDS")
470 else if (headerLabel ==
"# OF SATELLITES")
472 LOG_TRACE(
"{}: '{}' not implemented yet",
nameId(),
"# OF SATELLITES");
474 else if (headerLabel ==
"PRN / # OF OBS")
478 else if (headerLabel ==
"END OF HEADER")
484 LOG_WARN(
"{}: Unknown header label '{}' in line '{}'",
nameId(), headerLabel, line);
513 LOG_CRITICAL(
"{}: Could not determine time system of the file because satellite system '{}' has no default.",
533 size_t nSatellites = 0;
534 while (epochFlag != 0 && !
eof() &&
getline(line))
542 if (line.at(0) ==
'>')
544 auto year = std::stoi(line.substr(2, 4));
545 auto month = std::stoi(line.substr(7, 2));
546 auto day = std::stoi(line.substr(10, 2));
547 auto hour = std::stoi(line.substr(13, 2));
548 auto min = std::stoi(line.substr(16, 2));
549 auto sec = std::stold(line.substr(18, 11));
550 nSatellites = std::stoul(line.substr(29 + 3, 3));
552 [[maybe_unused]]
double recClkOffset = 0.0;
555 recClkOffset = line.size() >= 41 + 3 ? std::stod(line.substr(41, 15)) : 0.0;
557 catch (
const std::exception& )
559 LOG_DATA(
"{}: 'recClkOffset' not mentioned in file --> recClkOffset = {}",
nameId(), recClkOffset);
566 epochTime =
InsTime{
static_cast<uint16_t
>(year),
static_cast<uint16_t
>(month),
static_cast<uint16_t
>(day),
567 static_cast<uint16_t
>(hour),
static_cast<uint16_t
>(min), sec,
570 epochFlag = std::stoi(line.substr(31, 1));
572 LOG_DATA(
"{}: {}, epochFlag {}, numSats {}, recClkOffset {}",
nameId(),
573 epochTime.
toYMDHMS(), epochFlag, nSatellites, recClkOffset);
576 if (epochTime.
empty())
581 auto gnssObs = std::make_shared<GnssObs>();
582 gnssObs->insTime = epochTime;
594 auto satNum =
static_cast<uint8_t
>(std::stoi(line.substr(1, 2)));
596 LOG_DATA(
"{}: [{}] {}{}:",
nameId(), gnssObs->insTime.toYMDHMS(
GPST),
char(satSys), satNum);
598 size_t curExtractLoc = 3;
601 if (line.size() < curExtractLoc + 14)
614 double observation{};
617 observation = std::stod(strObs);
619 catch (
const std::exception& e)
621 if ((*gnssObs)({ obsDesc.code, satNum }).pseudorange)
625 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));
629 LOG_WARN(
"{}: observation of satSys = {} contains no doppler.",
nameId(),
char(satSys));
644 if (line.size() > curExtractLoc)
646 char LLIc = line.at(curExtractLoc);
651 LLI =
static_cast<uint8_t
>(LLIc -
'0');
669 if (line.size() > curExtractLoc)
671 char SSIc = line.at(curExtractLoc);
676 SSI =
static_cast<uint8_t
>(SSIc -
'0');
680 switch (obsDesc.type)
683 (*gnssObs)({ obsDesc.code, satNum }).pseudorange = { .value = observation,
687 (*gnssObs)({ obsDesc.code, satNum }).carrierPhase = { .value = observation,
692 (*gnssObs)({ obsDesc.code, satNum }).doppler = observation;
695 (*gnssObs)({ obsDesc.code, satNum }).CN0 = observation;
700 LOG_WARN(
"{}: ObsType {} not supported",
nameId(),
size_t(obsDesc.type));
704 gnssObs->satData(
SatId{ satSys, satNum }).frequencies |= obsDesc.code.getFrequency();
708 observation, LLI, SSI);
713 if (gnssObs->data.back().pseudorange)
715 if (!gnssObs->data.back().carrierPhase)
717 LOG_DATA(
"{}: A data record at epoch {} (plus leap seconds) contains Pseudorange, but is missing carrier phase.",
nameId(), epochTime.
toYMDHMS());
719 if (!gnssObs->data.back().doppler)
721 LOG_DATA(
"{}: A data record at epoch {} (plus leap seconds) contains Pseudorange, but is missing doppler.",
nameId(), epochTime.
toYMDHMS());
723 if (!gnssObs->data.back().CN0)
725 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());
730 if (satCnt != nSatellites)
732 LOG_WARN(
"{}: [{}] {} satellites read, but epoch header specified {} satellites",
nameId(), gnssObs->insTime.toYMDHMS(
GPST), satCnt, nSatellites);
743 auto eraseLessPrecise = [&](
const Code& third,
const Code& second,
const Code& prime) {
744 auto eraseSatDataWithCode = [&](
const Code& code) {
747 return idData.satSigId == SatSigId{ code, satNum };
749 if (iter != gnssObs->data.end())
752 gnssObs->data.erase(iter);
756 if (gnssObs->contains({ prime, satNum }))
758 eraseSatDataWithCode(second);
759 eraseSatDataWithCode(third);
761 else if (gnssObs->contains({ second, satNum }))
763 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.
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.
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.
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.