Skip to content

RINEX 4.02 Plan

This plan scoped the RINEX 4 work before implementation. The original iter1 intent was to record parser state, isolate the RINEX 4 surface area, and add a compile-time skeleton; the completed Phase 1 and Phase 2 slices now add the documented reader behavior incrementally.

Reference Sources

  • IGS RINEX 4.02 specification: https://files.igs.org/pub/data/format/rinex_4.02.pdf
  • IGS release note for RINEX 4.02: https://igs.org/news/rinex-4-02/
  • IGS formats page: https://igs.org/formats-and-standards/
  • GSI RNXCMP / CompactRINEX note: https://terras.gsi.go.jp/ja/crx2rnx.html

Current Parser State

The active reader/writer lives in:

  • include/libgnss++/io/rinex.hpp
  • src/io/rinex.cpp

Current behavior is mostly RINEX 2.x and 3.x:

  • RINEXReader::readHeader() reads fixed 80-column header lines until END OF HEADER.
  • parseHeaderLine() detects the file version with std::stod(line.substr(0, 9)).
  • File type is inferred from line[20], with observation, navigation, meteorological, and clock mapped to FileType.
  • Observation headers support RINEX 2 # / TYPES OF OBSERV and RINEX 3 SYS / # / OBS TYPES.
  • The RINEX 3 observation type parser currently reads only the first line for a system, up to 13 observation types. Continuation lines are not accumulated.
  • readObservationEpoch() dispatches version < 3.0 to the RINEX 2 epoch parser and every version >= 3.0 to the RINEX 3 epoch parser.
  • parseObservationEpochV3() assumes fixed epoch fields: year at columns 2-5, seconds in substr(18, 13), flag at columns 31-32, and satellite count at columns 33-35.
  • RINEX 3 satellite observation rows are parsed as one line per satellite, with 16-character observation fields starting at column 4.
  • readNavigationData() treats every version >= 3.0 navigation file as a RINEX 3 navigation file.
  • Navigation record detection expects the first character of an ephemeris data line to be a constellation identifier.
  • supportsBroadcastNavigationSystem() accepts GPS, GLONASS, Galileo, BeiDou, and QZSS for broadcast navigation parsing. SBAS and NavIC are not accepted.
  • parseNavigationMessage() handles RINEX 2 GPS-style records and RINEX 3 GPS/Galileo/BeiDou/QZSS eight-line Kepler records, plus four-line GLONASS FDMA records.
  • Header-level RINEX 3 IONOSPHERIC CORR parsing is limited to GPSA/GPSB.
  • RINEXWriter can write simple RINEX 3-style headers and broadcast navigation records, but it has no RINEX 4 data-record header support.

The important conclusion is that RINEX 4 observation files may look close to RINEX 3, but RINEX 4 navigation files do not fit the current navigation parser. The current version >= 3.0 branch is not a sufficient RINEX 4 compatibility story.

RINEX 4.02 Deltas

The RINEX 4 line starts at RINEX 4.00:

  • Navigation files use a new data record header line beginning with >.
  • Navigation record types include EPH, STO, EOP, and ION.
  • The header line carries a data source and a navigation message type, for example LNAV, FDMA, FNAV, INAV, CNAV, CNV1, CNV2, CNV3, L1NV, L1OC, and L3OC.
  • System records such as STO, EOP, and ION are first-class data records, not just legacy header fields.
  • RINEX 4.00 made system-dependent observation code lists required for observation files.

The RINEX 4.02 update adds or clarifies:

  • Observation epoch time tagging may carry extra second digits up to picosecond resolution.
  • The observation epoch record can include an optional receiver clock offset estimate.
  • NavIC L1 L1NV navigation messages are included.
  • GLONASS L1OC and L3OC CDMA navigation messages are included.
  • Navigation message subtypes are present in the navigation data record header.
  • ION subtypes are defined for QZSS and NavIC dual ionosphere models: QZSS CNVX WIDE, QZSS CNVX JAPN, NavIC L1NV KLOB, and NavIC L1NV NEQN.

CompactRINEX / CRINEX is separate from the RINEX syntax itself:

  • Hatanaka CompactRINEX is a compressed representation for RINEX observation files.
  • GSI RNXCMP advertises conversion for RINEX 2.xx, 3.xx, and 4.xx observation files.
  • This repo currently has no native CRINEX reader. Iter2 should treat CRINEX as an input-preparation concern unless a native decompressor is explicitly chosen.

Impact On Current Code

Observation file impact:

  • Current RINEX 3 epoch parsing will not safely parse picosecond-width seconds, because flag and satellite-count columns are fixed after a 13-character seconds field.
  • The parser needs token-based epoch parsing after the > marker while still preserving optional receiver clock offset parsing.
  • System observation type continuation lines must be supported before claiming robust RINEX 4 observation support.
  • Time precision should be stored as double for now because GNSSTime::tow is a double; picosecond text may be parsed but not represented losslessly.

Navigation file impact:

  • Current RINEX 4 navigation files will begin records with > EPH ..., > STO ..., > EOP ..., or > ION ...; the current parser will not treat those lines as ephemeris starts.
  • Record metadata must be parsed before record-body dispatch.
  • Existing ephemeris parsing can be reused for selected EPH bodies after the RINEX 4 header line is consumed, but the selected message type must be kept in the resulting object or at least respected for line count and time-system interpretation.
  • STO/EOP/ION require new data containers or an explicit skip policy. Silent misparse is not acceptable.
  • NavIC and GLONASS CDMA navigation messages need either new Ephemeris fields or a generic navigation-message container.

Writer impact:

  • RINEX 4 navigation writing must emit data record header lines.
  • Observation writing must preserve the expanded epoch second precision and the optional receiver clock offset field.
  • Writer support can lag reader support; it should not block initial reader acceptance.

Proposed Skeleton Ownership

The new files introduced in iter1 are:

  • include/libgnss++/io/rinex4.hpp
  • src/io/rinex4.cpp

They should remain small and separate until the compatibility boundary is clear. The existing RINEXReader should remain the production path for RINEX 2/3. RINEX 4 additions can later be folded into RINEXReader once the tests prove that no RINEX 2/3 behavior regressed.

Roadmap

Phase 0, iter1 foundation:

  • Add empty RINEX 4 skeleton files and compile them.
  • Document current parser assumptions.
  • Do not change behavior in src/io/rinex.cpp.

Phase 1, reader detection and safe dispatch:

  • Add explicit isRinex4() checks rather than relying on version >= 3.0.
  • Add tests with minimal RINEX 4.02 observation and navigation fixtures.
  • For RINEX 4 navigation records, parse the > data record header and route unsupported record types to a deliberate skip path.
  • Keep RINEX 2/3 tests unchanged.

Phase 2, observation support:

  • Replace fixed-column epoch parsing with token-based parsing for RINEX 4 epoch lines.
  • Preserve optional receiver clock offset estimate when present.
  • Handle expanded second precision text without shifting flag/count parsing.
  • Accumulate SYS / # / OBS TYPES continuation lines.
  • Add CRINEX policy tests that verify .crx/.crx.gz is rejected with a clear message or preprocessed through an external converter.

DONE in the Phase 2 observation slice. RINEX 4 epoch metadata is parsed token-wise, including the Table A3 receiver-clock and optional five-digit picosecond extension fields. GPS and QZSS rows reuse the RINEX 3 selection policy, continuation headers are covered, and event/cycle-slip records are consumed before scanning for the next normal epoch. Event records do not produce ObservationData epochs, while a valid flag 0/1 epoch with zero satellites is returned as an intentionally empty epoch; malformed or truncated normal records fail instead of returning partial data. Native CRINEX decompression remains out of scope and matching suffixes are rejected explicitly.

Phase 3, EPH navigation metadata and compatible-body support (DONE):

  • Parse RINEX 4 EPH data record headers and preserve typed message provenance on Ephemeris. DONE in the Phase 3 slice.
  • Reuse current GPS/Galileo/BeiDou/QZSS/GLONASS FDMA bodies where compatible. DONE for the compatible message types; global ephemeris selection is unchanged.
  • Reject nonblank EPH subtypes and trailing header tokens, skip unsupported EPH bodies at the next > boundary, and validate Galileo FNAV/INAV clock source bits. DONE in the Phase 3 slice.

Phase 4, system data records (DONE):

  • Add internal containers for STO, EOP, and ION. DONE in the Phase 4 slice as a RINEX-specific RINEXReader sidecar, leaving the existing NavigationData selection and legacy ionosphere model unchanged.
  • Parse the Table A33/A34 STO/EOP records and the A35--A40 Klobuchar, NeQuick-G, BDGIM, NavIC KLOB/NEQN, and GLONASS CDMA ION models with strict fixed-field validation. DONE in the Phase 4 slice.
  • Reject unsupported message/type/subtype combinations and malformed bodies transactionally while preserving the next > boundary. The LEG STO spelling is retained only for the explicit RINEX 4.02 Table A41 example; Table 21 remains the normative message-type matrix.

Phase 5, new navigation messages:

  • Implement GPS/QZSS CNAV/CNV2, BeiDou CNV1/CNV2/CNV3, and SBAS navigation body models.
  • Implement NavIC L1 L1NV parsing.
  • Add conformance fixtures from public RINEX 4.02 files before enabling these records in production flows.

Phase 5a, GLONASS CDMA navigation (DONE):

  • Parse > EPH R.. L1OC/L3OC A16/A17 bodies with a strict, transactional nine-line parser, including required integer/range checks and the required 19-character blank spare field (A19 format).
  • Preserve the typed message provenance and optional CDMA payload on Ephemeris; map the broadcast state vector from km to metres and convert UTC toc/t_tm to GPST with nearest-week handling.
  • Keep the existing GLONASS FDMA path and ephemeris selection policy unchanged; malformed CDMA records recover at the next explicit > boundary.

The remaining Phase 5 work is GPS/QZSS CNAV/CNV2, BeiDou CNV1/CNV2/CNV3, SBAS, and NavIC L1NV body models. Those records remain deliberately unsupported.

Phase 6, writer support:

  • Emit RINEX 4 navigation data record headers.
  • Add writer round-trip tests only after reader behavior is stable.

End-State Acceptance Gates

These are end-state gates for the complete roadmap, not just the current safe-dispatch and observation slices. The reader gates completed by Phases 1 and 2 are marked by the corresponding status sections below.

  • Existing RINEX 2/3 tests continue to pass.
  • RINEX 4 observation fixture parses header, epoch time, satellite count, and at least GPS/QZSS observation rows. MET by Phase 2.
  • RINEX 4 navigation fixture with > EPH G.. LNAV parses at parity with the equivalent RINEX 3 record.
  • Supported RINEX 4 STO/EOP/ION records are retained in the reader sidecar; unsupported records fail or skip deterministically with diagnostics.
  • CRINEX input policy is explicit and tested.

Safe-Dispatch, Observation, and System-Record Slice Non-Goals

  • No complete RINEX 4 observation model: event metadata is intentionally not retained in ObservationData, and cycle-slip records are consumed rather than exposed.
  • No native CRINEX decompressor.
  • No GPS/QZSS CNAV/CNV2, BeiDou CNV1/CNV2/CNV3, SBAS, or NavIC L1NV EPH body implementation; GLONASS CDMA L1OC/L3OC is covered by Phase 5a.
  • No writer behavior changes.
  • No conversion of system-record epochs into a common GPS time scale or application of the parsed system data to navigation selection.

Phase 1 Implementation Status: DONE

The safe-dispatch slice is implemented in the reader:

  • RINEXReader::isRinex4() identifies versions 4.00 through 4.99 explicitly; RINEX 2 and 3 continue through their existing paths.
  • RINEX 4 observation epochs are dispatched to the dedicated Phase 2 parser; RINEX 2 and 3 continue through their existing paths.
  • RINEX 4 navigation data-record headers are parsed in rinex4.hpp/cpp. Compatible GPS/QZSS LNAV, GLONASS FDMA, Galileo FNAV/INAV, and BeiDou D1/D2 EPH bodies reuse the existing ephemeris parser; GLONASS CDMA L1OC/L3OC uses the dedicated Phase 5a parser.
  • STO/EOP/ION records and unsupported EPH message types are skipped at the next > record boundary with diagnostics, preventing silent body misparse.

Phase 2 Implementation Status: DONE

The observation slice now handles token-based RINEX 4 epoch fields, fixed Table A3 receiver-clock offsets, optional extra second digits, GPS/QZSS observation rows, continuation headers, deterministic event/cycle-slip skipping (with zero-satellite normal epochs preserved), strict truncation checks, and explicit CRINEX suffix rejection. Event metadata retention, richer cycle-slip data models, and the remaining new EPH navigation message models remain deferred as listed above.

Phase 4 Implementation Status: DONE

RINEX 4 STO, EOP, and ION records are parsed into the RINEXReader::rinex4SystemData() sidecar. The implementation covers the Table A33/A34 fixed records and the A35--A40 ION payload models, validates system/message/subtype combinations and numeric/date fields, and appends only complete records. Unsupported or malformed records are diagnosed and discarded at the next explicit > boundary without changing RINEX 2/3 or legacy NavigationData behavior.