RinexObservationParser.java

  1. /* Copyright 2002-2025 CS GROUP
  2.  * Licensed to CS GROUP (CS) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * CS licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *   http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.orekit.files.rinex.observation;
  18. import java.io.BufferedReader;
  19. import java.io.IOException;
  20. import java.io.Reader;
  21. import java.util.ArrayList;
  22. import java.util.Arrays;
  23. import java.util.Collections;
  24. import java.util.List;
  25. import java.util.function.BiFunction;
  26. import java.util.function.Function;
  27. import java.util.function.Predicate;

  28. import org.hipparchus.exception.LocalizedCoreFormats;
  29. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  30. import org.hipparchus.geometry.euclidean.twod.Vector2D;
  31. import org.hipparchus.util.FastMath;
  32. import org.orekit.annotation.DefaultDataContext;
  33. import org.orekit.data.DataContext;
  34. import org.orekit.data.DataSource;
  35. import org.orekit.errors.OrekitException;
  36. import org.orekit.errors.OrekitMessages;
  37. import org.orekit.files.rinex.AppliedDCBS;
  38. import org.orekit.files.rinex.AppliedPCVS;
  39. import org.orekit.files.rinex.section.RinexLabels;
  40. import org.orekit.files.rinex.utils.parsing.RinexUtils;
  41. import org.orekit.gnss.ObservationTimeScale;
  42. import org.orekit.gnss.ObservationType;
  43. import org.orekit.gnss.PredefinedObservationType;
  44. import org.orekit.gnss.SatInSystem;
  45. import org.orekit.gnss.SatelliteSystem;
  46. import org.orekit.time.AbsoluteDate;
  47. import org.orekit.time.TimeScale;
  48. import org.orekit.time.TimeScales;
  49. import org.orekit.utils.units.Unit;

  50. /** Parser for Rinex measurements files.
  51.  * <p>
  52.  * Supported versions are: 2.00, 2.10, 2.11, 2.12 (unofficial), 2.20 (unofficial),
  53.  * 3.00, 3.01, 3.02, 3.03, 3.04, 3.05, 4.00, 4.01, and 4.02.
  54.  * </p>
  55.  * @see <a href="https://files.igs.org/pub/data/format/rinex2.txt">rinex 2.0</a>
  56.  * @see <a href="https://files.igs.org/pub/data/format/rinex210.txt">rinex 2.10</a>
  57.  * @see <a href="https://files.igs.org/pub/data/format/rinex211.pdf">rinex 2.11</a>
  58.  * @see <a href="http://www.aiub.unibe.ch/download/rinex/rinex212.txt">unofficial rinex 2.12</a>
  59.  * @see <a href="http://www.aiub.unibe.ch/download/rinex/rnx_leo.txt">unofficial rinex 2.20</a>
  60.  * @see <a href="https://files.igs.org/pub/data/format/rinex300.pdf">rinex 3.00</a>
  61.  * @see <a href="https://files.igs.org/pub/data/format/rinex301.pdf">rinex 3.01</a>
  62.  * @see <a href="https://files.igs.org/pub/data/format/rinex302.pdf">rinex 3.02</a>
  63.  * @see <a href="https://files.igs.org/pub/data/format/rinex303.pdf">rinex 3.03</a>
  64.  * @see <a href="https://files.igs.org/pub/data/format/rinex304.pdf">rinex 3.04</a>
  65.  * @see <a href="https://files.igs.org/pub/data/format/rinex305.pdf">rinex 3.05</a>
  66.  * @see <a href="https://files.igs.org/pub/data/format/rinex_4.00.pdf">rinex 4.00</a>
  67.  * @see <a href="https://files.igs.org/pub/data/format/rinex_4.01.pdf">rinex 4.01</a>
  68.  * @see <a href="https://files.igs.org/pub/data/format/rinex_4.02.pdf">rinex 4.02</a>
  69.  * @since 12.0
  70.  */
  71. public class RinexObservationParser {

  72.     /** Default name pattern for rinex 2 observation files. */
  73.     public static final String DEFAULT_RINEX_2_NAMES = "^\\w{4}\\d{3}[0a-x](?:\\d{2})?\\.\\d{2}[oO]$";

  74.     /** Default name pattern for rinex 3 observation files. */
  75.     public static final String DEFAULT_RINEX_3_NAMES = "^\\w{9}_\\w{1}_\\d{11}_\\d{2}\\w_\\d{2}\\w{1}_\\w{2}\\.rnx$";

  76.     /** Maximum number of satellites per line in Rinex 2 format . */
  77.     private static final int MAX_SAT_PER_RINEX_2_LINE = 12;

  78.     /** Maximum number of observations per line in Rinex 2 format. */
  79.     private static final int MAX_OBS_PER_RINEX_2_LINE = 5;

  80.     /** Pico seconds. */
  81.     private static final Unit PICO_SECOND = Unit.parse("ps");

  82.     /** Set of time scales. */
  83.     private final TimeScales timeScales;

  84.     /** Mapper from string to observation type.
  85.      * @since 13.0
  86.      */
  87.     private final Function<? super String, ? extends ObservationType> typeBuilder;

  88.     /** Mapper from satellite system to time scales.
  89.      * @since 13.0
  90.      */
  91.     private final BiFunction<SatelliteSystem, TimeScales, ? extends TimeScale> timeScaleBuilder;

  92.     /** Simple constructor.
  93.      * <p>
  94.      * This constructor uses the {@link DataContext#getDefault() default data context}
  95.      * and recognizes only {@link PredefinedObservationType} and {@link SatelliteSystem}
  96.      * with non-null {@link SatelliteSystem#getObservationTimeScale() time scales}
  97.      * (i.e. neither user-defined, nor {@link SatelliteSystem#SBAS}, nor {@link SatelliteSystem#MIXED}).
  98.      * </p>
  99.      * @see #RinexObservationParser(Function, BiFunction, TimeScales)
  100.      */
  101.     @DefaultDataContext
  102.     public RinexObservationParser() {
  103.         this(PredefinedObservationType::valueOf,
  104.              (system, ts) -> system.getObservationTimeScale() == null ?
  105.                              null :
  106.                              system.getObservationTimeScale().getTimeScale(ts),
  107.              DataContext.getDefault().getTimeScales());
  108.     }

  109.     /**
  110.      * Create a RINEX loader/parser with the given source of RINEX auxiliary data files.
  111.      * @param typeBuilder mapper from string to observation type
  112.      * @param timeScaleBuilder mapper from satellite system to time scales (useful for user-defined satellite systems)
  113.      * @param timeScales the set of time scales to use when parsing dates
  114.      * @since 13.0
  115.      */
  116.     public RinexObservationParser(final Function<? super String, ? extends ObservationType> typeBuilder,
  117.                                   final BiFunction<SatelliteSystem, TimeScales, ? extends TimeScale> timeScaleBuilder,
  118.                                   final TimeScales timeScales) {
  119.         this.typeBuilder      = typeBuilder;
  120.         this.timeScaleBuilder = timeScaleBuilder;
  121.         this.timeScales       = timeScales;
  122.     }

  123.     /**
  124.      * Parse RINEX observations messages.
  125.      * @param source source providing the data to parse
  126.      * @return parsed observations file
  127.      */
  128.     public RinexObservation parse(final DataSource source) {

  129.         Iterable<LineParser> candidateParsers = Collections.singleton(LineParser.VERSION);

  130.         // placeholders for parsed data
  131.         final ParseInfo parseInfo = new ParseInfo(source.getName());

  132.         try (Reader reader = source.getOpener().openReaderOnce();
  133.              BufferedReader br = new BufferedReader(reader)) {
  134.             ++parseInfo.lineNumber;
  135.             nextLine:
  136.                 for (String line = br.readLine(); line != null; line = br.readLine()) {
  137.                     for (final LineParser candidate : candidateParsers) {
  138.                         if (candidate.canHandle.test(line)) {
  139.                             try {
  140.                                 candidate.parsingMethod.parse(line, parseInfo);
  141.                                 ++parseInfo.lineNumber;
  142.                                 candidateParsers = candidate.allowedNextProvider.apply(parseInfo);
  143.                                 continue nextLine;
  144.                             } catch (StringIndexOutOfBoundsException | NumberFormatException e) {
  145.                                 throw new OrekitException(e,
  146.                                                           OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  147.                                                           parseInfo.lineNumber, source.getName(), line);
  148.                             }
  149.                         }
  150.                     }

  151.                     // no parsers found for this line
  152.                     throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  153.                                               parseInfo.lineNumber, source.getName(), line);

  154.                 }

  155.         } catch (IOException ioe) {
  156.             throw new OrekitException(ioe, LocalizedCoreFormats.SIMPLE_MESSAGE, ioe.getLocalizedMessage());
  157.         }

  158.         return parseInfo.file;

  159.     }

  160.     /** Transient data used for parsing a RINEX observation messages file.
  161.      * @since 12.0
  162.      */
  163.     private class ParseInfo {

  164.         /** Name of the data source. */
  165.         private final String name;

  166.         /** Mapper from satellite system to time scales.
  167.          * @since 13.0
  168.          */
  169.         private final BiFunction<SatelliteSystem, TimeScales, ? extends TimeScale> timeScaleBuilder;

  170.         /** Set of time scales for parsing dates. */
  171.         private final TimeScales timeScales;

  172.         /** Current line number of the navigation message. */
  173.         private int lineNumber;

  174.         /** Rinex file. */
  175.         private final RinexObservation file;

  176.         /** Date of the observation. */
  177.         private AbsoluteDate tObs;

  178.         /** Indicator that time of first observation was already fixed. */
  179.         private boolean tFirstFixed;

  180.         /** Indicator that time of last observation was already fixed. */
  181.         private boolean tLastFixed;

  182.         /** Receiver clock offset (seconds). */
  183.         private double rcvrClkOffset;

  184.         /** time scale for parsing dates. */
  185.         private TimeScale timeScale;

  186.         /** Number of observation types. */
  187.         private int nbTypes;

  188.         /** Number of satellites in the current observations block. */
  189.         private int nbSatObs;

  190.         /** Number of scaling factors. */
  191.         private int nbObsScaleFactor;

  192.         /** Index of satellite in current observation. */
  193.         private int indexObsSat;

  194.         /** Line number of start of next observation. */
  195.         private int nextObsStartLineNumber;

  196.         /** Current satellite system. */
  197.         private SatelliteSystem currentSystem;

  198.         /** Number of satellites affected by phase shifts. */
  199.         private int phaseShiftNbSat;

  200.         /** Number of GLONASS satellites. */
  201.         private int nbGlonass;

  202.         /** Satellites affected by phase shift. */
  203.         private final List<SatInSystem> satPhaseShift;

  204.         /** Type of observation affected by phase shift. */
  205.         private ObservationType phaseShiftTypeObs;

  206.         /** Phase shift correction. */
  207.         private double corrPhaseShift;

  208.         /** Indicator for completed header. */
  209.         private boolean headerCompleted;

  210.         /** Indicator for skipping special records (eventFlag from 2 to 5). */
  211.         private boolean specialRecord;

  212.         /** Indicator for skipping cyckle slip records (enventFlag == 6). */
  213.         private boolean cycleSlip;

  214.         /** Event flag. */
  215.         private int eventFlag;

  216.         /** Scaling factors. */
  217.         private final List<ObservationType> typesObsScaleFactor;

  218.         /** Types of observations. */
  219.         private final List<ObservationType> typesObs;

  220.         /** Observations. */
  221.         private final List<ObservationData> observations;

  222.         /** Satellites in current observation. */
  223.         private final List<SatInSystem> satObs;

  224.         /** Current satellite. */
  225.         private SatInSystem currentSat;

  226.         /** Constructor, build the ParseInfo object.
  227.          * @param name name of the data source
  228.          */
  229.         ParseInfo(final String name) {
  230.             // Initialize default values for fields
  231.             this.name                   = name;
  232.             this.timeScales             = RinexObservationParser.this.timeScales;
  233.             this.timeScaleBuilder       = RinexObservationParser.this.timeScaleBuilder;
  234.             this.file                   = new RinexObservation();
  235.             this.lineNumber             = 0;
  236.             this.tObs                   = AbsoluteDate.PAST_INFINITY;
  237.             this.tFirstFixed            = false;
  238.             this.tLastFixed             = false;
  239.             this.timeScale              = null;
  240.             this.nbTypes                = -1;
  241.             this.nbSatObs               = -1;
  242.             this.nbGlonass              = -1;
  243.             this.phaseShiftNbSat        = -1;
  244.             this.nbObsScaleFactor       = -1;
  245.             this.nextObsStartLineNumber = -1;
  246.             this.typesObs               = new ArrayList<>();
  247.             this.observations           = new ArrayList<>();
  248.             this.satPhaseShift          = new ArrayList<>();
  249.             this.typesObsScaleFactor    = new ArrayList<>();
  250.             this.satObs                 = new ArrayList<>();
  251.         }

  252.         /** Set observation date, taking care of receiver/absolute time scales.
  253.          * @param rawDate date as parsed, prior to any time scale modification
  254.          */
  255.         private void setTObs(final AbsoluteDate rawDate) {
  256.             final RinexObservationHeader header = file.getHeader();
  257.             if (header.getClockOffsetApplied()) {
  258.                 // date was already in an absolute time scale
  259.                 tObs = rawDate;
  260.             } else {
  261.                 // the epoch was expressed in receiver clock
  262.                 // we need to convert it to absolute date
  263.                 if (FastMath.abs(rawDate.durationFrom(header.getTFirstObs())) < 1.0e-6 &&
  264.                     !tFirstFixed) {
  265.                     // we need to fix the first date in the header too
  266.                     header.setTFirstObs(header.getTFirstObs().shiftedBy(-rcvrClkOffset));
  267.                     tFirstFixed = true;
  268.                 }
  269.                 if (FastMath.abs(rawDate.durationFrom(header.getTLastObs())) < 1.0e-6 &&
  270.                     !tLastFixed) {
  271.                     // we need to fix the last date in the header too
  272.                     header.setTLastObs(header.getTLastObs().shiftedBy(-rcvrClkOffset));
  273.                     tLastFixed = true;
  274.                 }
  275.                 tObs = rawDate.shiftedBy(-rcvrClkOffset);
  276.             }
  277.         }

  278.         /** Build one observation type.
  279.          * @param type type to add
  280.          * @return built type
  281.          * @since 13.0
  282.          */
  283.         ObservationType buildType(final String type) {
  284.             return RinexObservationParser.this.typeBuilder.apply(type);
  285.         }

  286.     }

  287.     /** Parsers for specific lines. */
  288.     private enum LineParser {

  289.         /** Parser for version, file type and satellite system. */
  290.         VERSION(line -> RinexLabels.VERSION.matches(RinexUtils.getLabel(line)),
  291.                 (line, parseInfo) ->  RinexUtils.parseVersionFileTypeSatelliteSystem(line, parseInfo.name, parseInfo.file.getHeader(),
  292.                                                                                      2.00, 2.10, 2.11, 2.12, 2.20,
  293.                                                                                      3.00, 3.01, 3.02, 3.03, 3.04, 3.05,
  294.                                                                                      4.00, 4.01, 4.02),
  295.                 LineParser::headerNext),

  296.         /** Parser for generating program and emitting agency. */
  297.         PROGRAM(line -> RinexLabels.PROGRAM.matches(RinexUtils.getLabel(line)),
  298.                 (line, parseInfo) -> RinexUtils.parseProgramRunByDate(line, parseInfo.lineNumber, parseInfo.name,
  299.                                                                       parseInfo.timeScales, parseInfo.file.getHeader()),
  300.                 LineParser::headerNext),

  301.         /** Parser for comments. */
  302.         COMMENT(line -> RinexLabels.COMMENT.matches(RinexUtils.getLabel(line)),
  303.                        (line, parseInfo) -> RinexUtils.parseComment(parseInfo.lineNumber, line, parseInfo.file),
  304.                        LineParser::commentNext),

  305.         /** Parser for marker name. */
  306.         MARKER_NAME(line -> RinexLabels.MARKER_NAME.matches(RinexUtils.getLabel(line)),
  307.                     (line, parseInfo) ->  parseInfo.file.getHeader().setMarkerName(RinexUtils.parseString(line, 0, RinexUtils.LABEL_INDEX)),
  308.                     LineParser::headerNext),

  309.         /** Parser for marker number. */
  310.         MARKER_NUMBER(line -> RinexLabels.MARKER_NUMBER.matches(RinexUtils.getLabel(line)),
  311.                       (line, parseInfo) -> parseInfo.file.getHeader().setMarkerNumber(RinexUtils.parseString(line, 0, 20)),
  312.                       LineParser::headerNext),

  313.         /** Parser for marker type. */
  314.         MARKER_TYPE(line -> RinexLabels.MARKER_TYPE.matches(RinexUtils.getLabel(line)),
  315.                     (line, parseInfo) -> parseInfo.file.getHeader().setMarkerType(RinexUtils.parseString(line, 0, 20)),
  316.                     LineParser::headerNext),

  317.         /** Parser for observer agency. */
  318.         OBSERVER_AGENCY(line -> RinexLabels.OBSERVER_AGENCY.matches(RinexUtils.getLabel(line)),
  319.                         (line, parseInfo) -> {
  320.                             parseInfo.file.getHeader().setObserverName(RinexUtils.parseString(line, 0, 20));
  321.                             parseInfo.file.getHeader().setAgencyName(RinexUtils.parseString(line, 20, 40));
  322.                         },
  323.                         LineParser::headerNext),

  324.         /** Parser for receiver number, type and version. */
  325.         REC_NB_TYPE_VERS(line -> RinexLabels.REC_NB_TYPE_VERS.matches(RinexUtils.getLabel(line)),
  326.                          (line, parseInfo) -> {
  327.                              parseInfo.file.getHeader().setReceiverNumber(RinexUtils.parseString(line, 0, 20));
  328.                              parseInfo.file.getHeader().setReceiverType(RinexUtils.parseString(line, 20, 20));
  329.                              parseInfo.file.getHeader().setReceiverVersion(RinexUtils.parseString(line, 40, 20));
  330.                          },
  331.                          LineParser::headerNext),

  332.         /** Parser for antenna number and type. */
  333.         ANT_NB_TYPE(line -> RinexLabels.ANT_NB_TYPE.matches(RinexUtils.getLabel(line)),
  334.                     (line, parseInfo) -> {
  335.                         parseInfo.file.getHeader().setAntennaNumber(RinexUtils.parseString(line, 0, 20));
  336.                         parseInfo.file.getHeader().setAntennaType(RinexUtils.parseString(line, 20, 20));
  337.                     },
  338.                     LineParser::headerNext),

  339.         /** Parser for approximative position. */
  340.         APPROX_POSITION_XYZ(line -> RinexLabels.APPROX_POSITION_XYZ.matches(RinexUtils.getLabel(line)),
  341.                             (line, parseInfo) -> parseInfo.file.getHeader().setApproxPos(new Vector3D(RinexUtils.parseDouble(line, 0, 14),
  342.                                                                                                   RinexUtils.parseDouble(line, 14, 14),
  343.                                                                                                   RinexUtils.parseDouble(line, 28, 14))),
  344.                             LineParser::headerNext),

  345.         /** Parser for antenna reference point. */
  346.         ANTENNA_DELTA_H_E_N(line -> RinexLabels.ANTENNA_DELTA_H_E_N.matches(RinexUtils.getLabel(line)),
  347.                             (line, parseInfo) -> {
  348.                                 parseInfo.file.getHeader().setAntennaHeight(RinexUtils.parseDouble(line, 0, 14));
  349.                                 parseInfo.file.getHeader().setEccentricities(new Vector2D(RinexUtils.parseDouble(line, 14, 14),
  350.                                                                                           RinexUtils.parseDouble(line, 28, 14)));
  351.                             },
  352.                             LineParser::headerNext),

  353.         /** Parser for antenna reference point. */
  354.         ANTENNA_DELTA_X_Y_Z(line -> RinexLabels.ANTENNA_DELTA_X_Y_Z.matches(RinexUtils.getLabel(line)),
  355.                             (line, parseInfo) -> parseInfo.file.getHeader().setAntennaReferencePoint(new Vector3D(RinexUtils.parseDouble(line, 0, 14),
  356.                                                                                                                   RinexUtils.parseDouble(line, 14, 14),
  357.                                                                                                                   RinexUtils.parseDouble(line, 28, 14))),
  358.                             LineParser::headerNext),

  359.         /** Parser for antenna phase center. */
  360.         ANTENNA_PHASE_CENTER(line -> RinexLabels.ANTENNA_PHASE_CENTER.matches(RinexUtils.getLabel(line)),
  361.                              (line, parseInfo) -> {
  362.                                  parseInfo.file.getHeader().setPhaseCenterSystem(SatelliteSystem.parseSatelliteSystem(RinexUtils.parseString(line, 0, 1)));
  363.                                  parseInfo.file.getHeader().setObservationCode(RinexUtils.parseString(line, 2, 3));
  364.                                  parseInfo.file.getHeader().setAntennaPhaseCenter(new Vector3D(RinexUtils.parseDouble(line, 5, 9),
  365.                                                                                                RinexUtils.parseDouble(line, 14, 14),
  366.                                                                                                RinexUtils.parseDouble(line, 28, 14)));
  367.                              },
  368.                              LineParser::headerNext),

  369.         /** Parser for antenna bore sight. */
  370.         ANTENNA_B_SIGHT_XYZ(line -> RinexLabels.ANTENNA_B_SIGHT_XYZ.matches(RinexUtils.getLabel(line)),
  371.                             (line, parseInfo) -> parseInfo.file.getHeader().setAntennaBSight(new Vector3D(RinexUtils.parseDouble(line, 0, 14),
  372.                                                                                                           RinexUtils.parseDouble(line, 14, 14),
  373.                                                                                                           RinexUtils.parseDouble(line, 28, 14))),
  374.                             LineParser::headerNext),

  375.         /** Parser for antenna zero direction. */
  376.         ANTENNA_ZERODIR_AZI(line -> RinexLabels.ANTENNA_ZERODIR_AZI.matches(RinexUtils.getLabel(line)),
  377.                             (line, parseInfo) -> parseInfo.file.getHeader().setAntennaAzimuth(FastMath.toRadians(RinexUtils.parseDouble(line, 0, 14))),
  378.                             LineParser::headerNext),

  379.         /** Parser for antenna zero direction. */
  380.         ANTENNA_ZERODIR_XYZ(line -> RinexLabels.ANTENNA_ZERODIR_XYZ.matches(RinexUtils.getLabel(line)),
  381.                             (line, parseInfo) -> parseInfo.file.getHeader().setAntennaZeroDirection(new Vector3D(RinexUtils.parseDouble(line, 0, 14),
  382.                                                                                                                  RinexUtils.parseDouble(line, 14, 14),
  383.                                                                                                                  RinexUtils.parseDouble(line, 28, 14))),
  384.                             LineParser::headerNext),

  385.         /** Parser for wavelength factors. */
  386.         WAVELENGTH_FACT_L1_2(line -> RinexLabels.WAVELENGTH_FACT_L1_2.matches(RinexUtils.getLabel(line)),
  387.                              (line, parseInfo) -> {
  388.                                  // optional line in Rinex 2 header, not stored for now
  389.                              },
  390.                              LineParser::headerNext),

  391.         /** Parser for observations scale factor. */
  392.         OBS_SCALE_FACTOR(line -> RinexLabels.OBS_SCALE_FACTOR.matches(RinexUtils.getLabel(line)),
  393.                          (line, parseInfo) -> {
  394.                              final int scaleFactor      = FastMath.max(1, RinexUtils.parseInt(line, 0,  6));
  395.                              final int nbObsScaleFactor = RinexUtils.parseInt(line, 6, 6);
  396.                              final List<ObservationType> types = new ArrayList<>(nbObsScaleFactor);
  397.                              for (int i = 0; i < nbObsScaleFactor; i++) {
  398.                                  types.add(parseInfo.buildType(RinexUtils.parseString(line, 16 + (6 * i), 2)));
  399.                              }
  400.                              parseInfo.file.getHeader().addScaleFactorCorrection(parseInfo.file.getHeader().getSatelliteSystem(),
  401.                                                                                  new ScaleFactorCorrection(scaleFactor, types));
  402.                          },
  403.                          LineParser::headerNext),

  404.         /** Parser for center of mass. */
  405.         CENTER_OF_MASS_XYZ(line -> RinexLabels.CENTER_OF_MASS_XYZ.matches(RinexUtils.getLabel(line)),
  406.                            (line, parseInfo) -> parseInfo.file.getHeader().setCenterMass(new Vector3D(RinexUtils.parseDouble(line, 0, 14),
  407.                                                                                                       RinexUtils.parseDouble(line, 14, 14),
  408.                                                                                                       RinexUtils.parseDouble(line, 28, 14))),
  409.                            LineParser::headerNext),

  410.         /** Parser for DOI.
  411.          * @since 12.0
  412.          */
  413.         DOI(line -> RinexLabels.DOI.matches(RinexUtils.getLabel(line)),
  414.             (line, parseInfo) -> parseInfo.file.getHeader().setDoi(RinexUtils.parseString(line, 0, RinexUtils.LABEL_INDEX)),
  415.             LineParser::headerNext),

  416.         /** Parser for license.
  417.          * @since 12.0
  418.          */
  419.         LICENSE(line -> RinexLabels.LICENSE.matches(RinexUtils.getLabel(line)),
  420.                 (line, parseInfo) -> parseInfo.file.getHeader().setLicense(RinexUtils.parseString(line, 0, RinexUtils.LABEL_INDEX)),
  421.                 LineParser::headerNext),

  422.         /** Parser for station information.
  423.          * @since 12.0
  424.          */
  425.         STATION_INFORMATION(line -> RinexLabels.STATION_INFORMATION.matches(RinexUtils.getLabel(line)),
  426.                             (line, parseInfo) -> parseInfo.file.getHeader().setStationInformation(RinexUtils.parseString(line, 0, RinexUtils.LABEL_INDEX)),
  427.                             LineParser::headerNext),

  428.         /** Parser for number and types of observations. */
  429.         SYS_NB_TYPES_OF_OBSERV(line -> RinexLabels.SYS_NB_TYPES_OF_OBSERV.matches(RinexUtils.getLabel(line)) ||
  430.                                        RinexLabels.NB_TYPES_OF_OBSERV.matches(RinexUtils.getLabel(line)),
  431.                                (line, parseInfo) -> {
  432.                                    final double version = parseInfo.file.getHeader().getFormatVersion();
  433.                                    if (parseInfo.nbTypes < 0) {
  434.                                        // first line of types of observations
  435.                                        if (version < 3) {
  436.                                            // Rinex 2 has only one system
  437.                                            parseInfo.currentSystem = parseInfo.file.getHeader().getSatelliteSystem();
  438.                                            parseInfo.nbTypes       = RinexUtils.parseInt(line, 0, 6);
  439.                                        } else {
  440.                                            // Rinex 3 and above allow mixed systems
  441.                                            parseInfo.currentSystem = SatelliteSystem.parseSatelliteSystem(RinexUtils.parseString(line, 0, 1));
  442.                                            parseInfo.nbTypes       = RinexUtils.parseInt(line, 3, 3);
  443.                                            if (parseInfo.currentSystem != parseInfo.file.getHeader().getSatelliteSystem() &&
  444.                                                parseInfo.file.getHeader().getSatelliteSystem() != SatelliteSystem.MIXED) {
  445.                                                throw new OrekitException(OrekitMessages.INCONSISTENT_SATELLITE_SYSTEM,
  446.                                                                          parseInfo.lineNumber, parseInfo.name,
  447.                                                                          parseInfo.file.getHeader().getSatelliteSystem(),
  448.                                                                          parseInfo.currentSystem);
  449.                                            }
  450.                                        }
  451.                                    }

  452.                                    final int firstIndex = version < 3 ? 10 : 7;
  453.                                    final int increment  = version < 3 ?  6 : 4;
  454.                                    final int size       = version < 3 ?  2 : 3;
  455.                                    for (int i = firstIndex;
  456.                                                    (i + size) <= RinexUtils.LABEL_INDEX && parseInfo.typesObs.size() < parseInfo.nbTypes;
  457.                                                    i += increment) {
  458.                                        final String type = RinexUtils.parseString(line, i, size);
  459.                                        try {
  460.                                            parseInfo.typesObs.add(parseInfo.buildType(type));
  461.                                        } catch (IllegalArgumentException iae) {
  462.                                            throw new OrekitException(iae, OrekitMessages.UNKNOWN_RINEX_FREQUENCY,
  463.                                                                      type, parseInfo.name, parseInfo.lineNumber);
  464.                                        }
  465.                                    }

  466.                                    if (parseInfo.typesObs.size() == parseInfo.nbTypes) {
  467.                                        // we have completed the list
  468.                                        parseInfo.file.getHeader().setTypeObs(parseInfo.currentSystem, parseInfo.typesObs);
  469.                                        parseInfo.typesObs.clear();
  470.                                        parseInfo.nbTypes = -1;
  471.                                    }

  472.                                },
  473.                                LineParser::headerNbTypesObs),

  474.         /** Parser for unit of signal strength. */
  475.         SIGNAL_STRENGTH_UNIT(line -> RinexLabels.SIGNAL_STRENGTH_UNIT.matches(RinexUtils.getLabel(line)),
  476.                              (line, parseInfo) -> parseInfo.file.getHeader().setSignalStrengthUnit(RinexUtils.parseString(line, 0, 20)),
  477.                              LineParser::headerNext),

  478.         /** Parser for observation interval. */
  479.         INTERVAL(line -> RinexLabels.INTERVAL.matches(RinexUtils.getLabel(line)),
  480.                  (line, parseInfo) -> parseInfo.file.getHeader().setInterval(RinexUtils.parseDouble(line, 0, 10)),
  481.                  LineParser::headerNext),

  482.         /** Parser for time of first observation. */
  483.         TIME_OF_FIRST_OBS(line -> RinexLabels.TIME_OF_FIRST_OBS.matches(RinexUtils.getLabel(line)),
  484.                           (line, parseInfo) -> {
  485.                               try {
  486.                                   // general case: TIME OF FIRST OBS specifies the time scale
  487.                                   parseInfo.timeScale = ObservationTimeScale.
  488.                                                         valueOf(RinexUtils.parseString(line, 48, 3)).
  489.                                                         getTimeScale(parseInfo.timeScales);
  490.                               } catch (IllegalArgumentException iae) {
  491.                                   if (parseInfo.file.getHeader().getSatelliteSystem() != SatelliteSystem.MIXED) {
  492.                                       // use the default from time system header
  493.                                       parseInfo.timeScale = parseInfo.timeScaleBuilder.apply(parseInfo.file.getHeader().getSatelliteSystem(),
  494.                                                                                              parseInfo.timeScales);
  495.                                       if (parseInfo.timeScale == null) {
  496.                                           throw new OrekitException(iae, OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  497.                                                                     parseInfo.lineNumber, parseInfo.name, line);
  498.                                       }
  499.                                   } else {
  500.                                       // in case of mixed data, time scale must be specified in the Time of First Observation line
  501.                                       throw new OrekitException(iae, OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  502.                                                                 parseInfo.lineNumber, parseInfo.name, line);
  503.                                   }
  504.                               }
  505.                               parseInfo.file.getHeader().setTFirstObs(new AbsoluteDate(RinexUtils.parseInt(line, 0, 6),
  506.                                                                                        RinexUtils.parseInt(line, 6, 6),
  507.                                                                                        RinexUtils.parseInt(line, 12, 6),
  508.                                                                                        RinexUtils.parseInt(line, 18, 6),
  509.                                                                                        RinexUtils.parseInt(line, 24, 6),
  510.                                                                                        RinexUtils.parseDouble(line, 30, 13),
  511.                                                                                        parseInfo.timeScale));
  512.                           },
  513.                           LineParser::headerNext),

  514.         /** Parser for time of last observation. */
  515.         TIME_OF_LAST_OBS(line -> RinexLabels.TIME_OF_LAST_OBS.matches(RinexUtils.getLabel(line)),
  516.                          (line, parseInfo) -> parseInfo.file.getHeader().setTLastObs(new AbsoluteDate(RinexUtils.parseInt(line, 0, 6),
  517.                                                                                                       RinexUtils.parseInt(line, 6, 6),
  518.                                                                                                       RinexUtils.parseInt(line, 12, 6),
  519.                                                                                                       RinexUtils.parseInt(line, 18, 6),
  520.                                                                                                       RinexUtils.parseInt(line, 24, 6),
  521.                                                                                                       RinexUtils.parseDouble(line, 30, 13),
  522.                                                                                                       parseInfo.timeScale)),
  523.                          LineParser::headerNext),

  524.         /** Parser for indicator of receiver clock offset application. */
  525.         RCV_CLOCK_OFFS_APPL(line -> RinexLabels.RCV_CLOCK_OFFS_APPL.matches(RinexUtils.getLabel(line)),
  526.                             (line, parseInfo) -> parseInfo.file.getHeader().setClockOffsetApplied(RinexUtils.parseInt(line, 0, 6) > 0),
  527.                             LineParser::headerNext),

  528.         /** Parser for differential code bias corrections. */
  529.         SYS_DCBS_APPLIED(line -> RinexLabels.SYS_DCBS_APPLIED.matches(RinexUtils.getLabel(line)),
  530.                          (line, parseInfo) -> parseInfo.file.getHeader().addAppliedDCBS(new AppliedDCBS(SatelliteSystem.parseSatelliteSystem(RinexUtils.parseString(line, 0, 1)),
  531.                                                                                                         RinexUtils.parseString(line, 2, 17),
  532.                                                                                                         RinexUtils.parseString(line, 20, 40))),
  533.                          LineParser::headerNext),

  534.         /** Parser for phase center variations corrections. */
  535.         SYS_PCVS_APPLIED(line -> RinexLabels.SYS_PCVS_APPLIED.matches(RinexUtils.getLabel(line)),
  536.                          (line, parseInfo) -> parseInfo.file.getHeader().addAppliedPCVS(new AppliedPCVS(SatelliteSystem.parseSatelliteSystem(RinexUtils.parseString(line, 0, 1)),
  537.                                                                                                         RinexUtils.parseString(line, 2, 17),
  538.                                                                                                         RinexUtils.parseString(line, 20, 40))),
  539.                          LineParser::headerNext),

  540.         /** Parser for scale factor. */
  541.         SYS_SCALE_FACTOR(line -> RinexLabels.SYS_SCALE_FACTOR.matches(RinexUtils.getLabel(line)),
  542.                          (line, parseInfo) -> {

  543.                              int scaleFactor = 1;
  544.                              if (parseInfo.nbObsScaleFactor < 0) {
  545.                                  // first line of scale factor
  546.                                  parseInfo.currentSystem    = SatelliteSystem.parseSatelliteSystem(RinexUtils.parseString(line, 0, 1));
  547.                                  scaleFactor                = RinexUtils.parseInt(line, 2, 4);
  548.                                  parseInfo.nbObsScaleFactor = RinexUtils.parseInt(line, 8, 2);
  549.                              }

  550.                              if (parseInfo.nbObsScaleFactor == 0) {
  551.                                  parseInfo.typesObsScaleFactor.addAll(parseInfo.file.getHeader().getTypeObs().get(parseInfo.currentSystem));
  552.                              } else {
  553.                                  for (int i = 11; i < RinexUtils.LABEL_INDEX && parseInfo.typesObsScaleFactor.size() < parseInfo.nbObsScaleFactor; i += 4) {
  554.                                      parseInfo.typesObsScaleFactor.add(parseInfo.buildType(RinexUtils.parseString(line, i, 3)));
  555.                                  }
  556.                              }

  557.                              if (parseInfo.typesObsScaleFactor.size() >= parseInfo.nbObsScaleFactor) {
  558.                                  // we have completed the list
  559.                                  parseInfo.file.getHeader().addScaleFactorCorrection(parseInfo.currentSystem,
  560.                                                                            new ScaleFactorCorrection(scaleFactor,
  561.                                                                                                      new ArrayList<>(parseInfo.typesObsScaleFactor)));
  562.                                  parseInfo.nbObsScaleFactor = -1;
  563.                                  parseInfo.typesObsScaleFactor.clear();
  564.                              }

  565.                          },
  566.                          LineParser::headerNext),

  567.         /** Parser for phase shift. */
  568.         SYS_PHASE_SHIFT(line -> RinexLabels.SYS_PHASE_SHIFT.matches(RinexUtils.getLabel(line)),
  569.                         (line, parseInfo) -> {

  570.                             if (parseInfo.phaseShiftNbSat < 0) {
  571.                                 // first line of phase shift
  572.                                 parseInfo.currentSystem     = SatelliteSystem.parseSatelliteSystem(RinexUtils.parseString(line, 0, 1));
  573.                                 final String to             = RinexUtils.parseString(line, 2, 3);
  574.                                 parseInfo.phaseShiftTypeObs = to.isEmpty() ? null : parseInfo.buildType(to.length() < 3 ? "L" + to : to);
  575.                                 parseInfo.corrPhaseShift    = RinexUtils.parseDouble(line, 6, 8);
  576.                                 parseInfo.phaseShiftNbSat   = RinexUtils.parseInt(line, 16, 2);
  577.                             }

  578.                             for (int i = 19; i + 3 < RinexUtils.LABEL_INDEX && parseInfo.satPhaseShift.size() < parseInfo.phaseShiftNbSat; i += 4) {
  579.                                 final String satSpec = line.charAt(i) == ' ' ?
  580.                                                        parseInfo.currentSystem.getKey() + line.substring(i + 1, i + 3) :
  581.                                                        line.substring(i, i + 3);
  582.                                 parseInfo.satPhaseShift.add(new SatInSystem(satSpec));
  583.                             }

  584.                             if (parseInfo.satPhaseShift.size() == parseInfo.phaseShiftNbSat) {
  585.                                 // we have completed the list
  586.                                 parseInfo.file.getHeader().addPhaseShiftCorrection(new PhaseShiftCorrection(parseInfo.currentSystem,
  587.                                                                                                             parseInfo.phaseShiftTypeObs,
  588.                                                                                                             parseInfo.corrPhaseShift,
  589.                                                                                                             new ArrayList<>(parseInfo.satPhaseShift)));
  590.                                 parseInfo.phaseShiftNbSat = -1;
  591.                                 parseInfo.satPhaseShift.clear();
  592.                             }

  593.                         },
  594.                         LineParser::headerPhaseShift),

  595.         /** Parser for GLONASS slot and frequency number. */
  596.         GLONASS_SLOT_FRQ_NB(line -> RinexLabels.GLONASS_SLOT_FRQ_NB.matches(RinexUtils.getLabel(line)),
  597.                             (line, parseInfo) -> {

  598.                                 if (parseInfo.nbGlonass < 0) {
  599.                                     // first line of GLONASS satellite/frequency association
  600.                                     parseInfo.nbGlonass = RinexUtils.parseInt(line, 0, 3);
  601.                                 }

  602.                                 for (int i = 4;
  603.                                      i < RinexUtils.LABEL_INDEX && parseInfo.file.getHeader().getGlonassChannels().size() < parseInfo.nbGlonass;
  604.                                      i += 7) {
  605.                                     final int k = RinexUtils.parseInt(line, i + 4, 2);
  606.                                     parseInfo.file.getHeader().addGlonassChannel(new GlonassSatelliteChannel(new SatInSystem(line.substring(i, i + 3)), k));
  607.                                 }

  608.                             },
  609.                             LineParser::headerNext),

  610.         /** Parser for GLONASS phase bias corrections. */
  611.         GLONASS_COD_PHS_BIS(line -> RinexLabels.GLONASS_COD_PHS_BIS.matches(RinexUtils.getLabel(line)),
  612.                             (line, parseInfo) -> {

  613.                                 // C1C signal
  614.                                 final String c1c = RinexUtils.parseString(line, 1, 3);
  615.                                 if (!c1c.isEmpty()) {
  616.                                     parseInfo.file.getHeader().setC1cCodePhaseBias(RinexUtils.parseDouble(line, 5, 8));
  617.                                 }

  618.                                 // C1P signal
  619.                                 final String c1p = RinexUtils.parseString(line, 14, 3);
  620.                                 if (!c1p.isEmpty()) {
  621.                                     parseInfo.file.getHeader().setC1pCodePhaseBias(RinexUtils.parseDouble(line, 18, 8));
  622.                                 }

  623.                                 // C2C signal
  624.                                 final String c2c = RinexUtils.parseString(line, 27, 3);
  625.                                 if (!c2c.isEmpty()) {
  626.                                     parseInfo.file.getHeader().setC2cCodePhaseBias(RinexUtils.parseDouble(line, 31, 8));
  627.                                 }

  628.                                 // C2P signal
  629.                                 final String c2p = RinexUtils.parseString(line, 40, 3);
  630.                                 if (!c2p.isEmpty()) {
  631.                                     parseInfo.file.getHeader().setC2pCodePhaseBias(RinexUtils.parseDouble(line, 44, 8));
  632.                                 }

  633.                             },
  634.                             LineParser::headerNext),

  635.         /** Parser for leap seconds. */
  636.         LEAP_SECONDS(line -> RinexLabels.LEAP_SECONDS.matches(RinexUtils.getLabel(line)),
  637.                      (line, parseInfo) -> {
  638.                          parseInfo.file.getHeader().setLeapSeconds(RinexUtils.parseInt(line, 0, 6));
  639.                          if (parseInfo.file.getHeader().getFormatVersion() >= 3.0) {
  640.                              parseInfo.file.getHeader().setLeapSecondsFuture(RinexUtils.parseInt(line, 6, 6));
  641.                              parseInfo.file.getHeader().setLeapSecondsWeekNum(RinexUtils.parseInt(line, 12, 6));
  642.                              parseInfo.file.getHeader().setLeapSecondsDayNum(RinexUtils.parseInt(line, 18, 6));
  643.                          }
  644.                      },
  645.                      LineParser::headerNext),

  646.         /** Parser for number of satellites. */
  647.         NB_OF_SATELLITES(line -> RinexLabels.NB_OF_SATELLITES.matches(RinexUtils.getLabel(line)),
  648.                          (line, parseInfo) -> parseInfo.file.getHeader().setNbSat(RinexUtils.parseInt(line, 0, 6)),
  649.                          LineParser::headerNext),

  650.         /** Parser for PRN and number of observations . */
  651.         PRN_NB_OF_OBS(line -> RinexLabels.PRN_NB_OF_OBS.matches(RinexUtils.getLabel(line)),
  652.                       (line, parseInfo) ->  {
  653.                           final String systemName = RinexUtils.parseString(line, 3, 1);
  654.                           if (!systemName.isEmpty()) {
  655.                               parseInfo.currentSat = new SatInSystem(line.substring(3, 6));
  656.                               parseInfo.nbTypes    = 0;
  657.                           }
  658.                           final List<ObservationType> types = parseInfo.file.getHeader().getTypeObs().get(parseInfo.currentSat.getSystem());

  659.                           final int firstIndex = 6;
  660.                           final int increment  = 6;
  661.                           final int size       = 6;
  662.                           for (int i = firstIndex;
  663.                                (i + size) <= RinexUtils.LABEL_INDEX && parseInfo.nbTypes < types.size();
  664.                                i += increment) {
  665.                               final String nb = RinexUtils.parseString(line, i, size);
  666.                               if (!nb.isEmpty()) {
  667.                                   parseInfo.file.getHeader().setNbObsPerSatellite(parseInfo.currentSat, types.get(parseInfo.nbTypes),
  668.                                                                         RinexUtils.parseInt(line, i, size));
  669.                               }
  670.                               ++parseInfo.nbTypes;
  671.                           }

  672.                       },
  673.                       LineParser::headerNext),

  674.         /** Parser for the end of header. */
  675.         END(line -> RinexLabels.END.matches(RinexUtils.getLabel(line)),
  676.             (line, parseInfo) -> {

  677.                 parseInfo.headerCompleted = true;

  678.                 // get rinex format version
  679.                 final double version = parseInfo.file.getHeader().getFormatVersion();

  680.                 // check mandatory header fields
  681.                 if (version < 3) {
  682.                     if (parseInfo.file.getHeader().getMarkerName()                  == null ||
  683.                         parseInfo.file.getHeader().getObserverName()                == null ||
  684.                         parseInfo.file.getHeader().getReceiverNumber()              == null ||
  685.                         parseInfo.file.getHeader().getAntennaNumber()               == null ||
  686.                         parseInfo.file.getHeader().getTFirstObs()                   == null ||
  687.                         version < 2.20 && parseInfo.file.getHeader().getApproxPos() == null ||
  688.                         version < 2.20 && Double.isNaN(parseInfo.file.getHeader().getAntennaHeight()) ||
  689.                         parseInfo.file.getHeader().getTypeObs().isEmpty()) {
  690.                         throw new OrekitException(OrekitMessages.INCOMPLETE_HEADER, parseInfo.name);
  691.                     }

  692.                 } else {
  693.                     if (parseInfo.file.getHeader().getMarkerName()           == null ||
  694.                         parseInfo.file.getHeader().getObserverName()         == null ||
  695.                         parseInfo.file.getHeader().getReceiverNumber()       == null ||
  696.                         parseInfo.file.getHeader().getAntennaNumber()        == null ||
  697.                         Double.isNaN(parseInfo.file.getHeader().getAntennaHeight()) &&
  698.                         parseInfo.file.getHeader().getAntennaReferencePoint() == null  ||
  699.                         parseInfo.file.getHeader().getTFirstObs()            == null ||
  700.                         parseInfo.file.getHeader().getTypeObs().isEmpty()) {
  701.                         throw new OrekitException(OrekitMessages.INCOMPLETE_HEADER, parseInfo.name);
  702.                     }
  703.                 }
  704.             },
  705.             LineParser::headerEndNext),

  706.         /** Parser for Rinex 2 data list of satellites. */
  707.         RINEX_2_DATA_SAT_LIST(line -> true,
  708.                               (line, parseInfo) -> {
  709.                                   for (int index = 32; parseInfo.satObs.size() < parseInfo.nbSatObs && index < 68; index += 3) {
  710.                                       // add one PRN to the list of observed satellites
  711.                                       final String satSpec =
  712.                                               line.charAt(index) == ' ' ?
  713.                                               parseInfo.file.getHeader().getSatelliteSystem().getKey() + line.substring(index + 1, index + 3) :
  714.                                               line.substring(index, index + 3);
  715.                                       final SatInSystem satellite = new SatInSystem(satSpec);
  716.                                       if (satellite.getSystem() != parseInfo.file.getHeader().getSatelliteSystem() &&
  717.                                           parseInfo.file.getHeader().getSatelliteSystem() != SatelliteSystem.MIXED) {
  718.                                           throw new OrekitException(OrekitMessages.INCONSISTENT_SATELLITE_SYSTEM,
  719.                                                                     parseInfo.lineNumber, parseInfo.name,
  720.                                                                     parseInfo.file.getHeader().getSatelliteSystem(),
  721.                                                                     satellite.getSystem());
  722.                                       }
  723.                                       parseInfo.satObs.add(satellite);
  724.                                       // note that we *must* use parseInfo.file.getHeader().getSatelliteSystem() as it was used to set up parseInfo.mapTypeObs
  725.                                       // and it may be MIXED to be applied to all satellites systems
  726.                                       final int nbObservables = parseInfo.file.getHeader().getTypeObs().get(parseInfo.file.getHeader().getSatelliteSystem()).size();
  727.                                       final int nbLines       = (nbObservables + MAX_OBS_PER_RINEX_2_LINE - 1) / MAX_OBS_PER_RINEX_2_LINE;
  728.                                       parseInfo.nextObsStartLineNumber += nbLines;
  729.                                   }
  730.                               },
  731.                               LineParser::first2),

  732.         /** Parser for Rinex 2 data first line. */
  733.         RINEX_2_DATA_FIRST(line -> true,
  734.                            (line, parseInfo) -> {

  735.                                // flag
  736.                                parseInfo.eventFlag = RinexUtils.parseInt(line, 28, 1);

  737.                                // number of sats
  738.                                parseInfo.nbSatObs   = RinexUtils.parseInt(line, 29, 3);
  739.                                final int nbLinesSat = (parseInfo.nbSatObs + MAX_SAT_PER_RINEX_2_LINE - 1) / MAX_SAT_PER_RINEX_2_LINE;

  740.                                if (parseInfo.eventFlag < 2) {
  741.                                    // regular observation
  742.                                    parseInfo.specialRecord = false;
  743.                                    parseInfo.cycleSlip     = false;
  744.                                    final int nbSat         = parseInfo.file.getHeader().getNbSat();
  745.                                    if (nbSat != -1 && parseInfo.nbSatObs > nbSat) {
  746.                                        // we check that the number of Sat in the observation is consistent
  747.                                        throw new OrekitException(OrekitMessages.INCONSISTENT_NUMBER_OF_SATS,
  748.                                                                  parseInfo.lineNumber, parseInfo.name,
  749.                                                                  parseInfo.nbSatObs, nbSat);
  750.                                    }
  751.                                    parseInfo.nextObsStartLineNumber = parseInfo.lineNumber + nbLinesSat;

  752.                                    // read the Receiver Clock offset, if present
  753.                                    parseInfo.rcvrClkOffset = RinexUtils.parseDouble(line, 68, 12);
  754.                                    if (Double.isNaN(parseInfo.rcvrClkOffset)) {
  755.                                        parseInfo.rcvrClkOffset = 0.0;
  756.                                    }

  757.                                } else if (parseInfo.eventFlag < 6) {
  758.                                    // moving antenna / new site occupation / header information / external event
  759.                                    // here, number of sats means number of lines to skip
  760.                                    parseInfo.specialRecord = true;
  761.                                    parseInfo.cycleSlip     = false;
  762.                                    parseInfo.nextObsStartLineNumber = parseInfo.lineNumber + parseInfo.nbSatObs + 1;
  763.                                } else if (parseInfo.eventFlag == 6) {
  764.                                    // cycle slip, we will ignore it during observations parsing
  765.                                    parseInfo.specialRecord = false;
  766.                                    parseInfo.cycleSlip     = true;
  767.                                    parseInfo.nextObsStartLineNumber = parseInfo.lineNumber + nbLinesSat;
  768.                                } else {
  769.                                    // unknown event flag
  770.                                    throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  771.                                                              parseInfo.lineNumber, parseInfo.name, line);
  772.                                }

  773.                                // parse the list of satellites observed
  774.                                parseInfo.satObs.clear();
  775.                                if (!parseInfo.specialRecord) {

  776.                                    // observations epoch
  777.                                    parseInfo.setTObs(new AbsoluteDate(RinexUtils.convert2DigitsYear(RinexUtils.parseInt(line, 1, 2)),
  778.                                                                       RinexUtils.parseInt(line,  4, 2),
  779.                                                                       RinexUtils.parseInt(line,  7, 2),
  780.                                                                       RinexUtils.parseInt(line, 10, 2),
  781.                                                                       RinexUtils.parseInt(line, 13, 2),
  782.                                                                       RinexUtils.parseDouble(line, 15, 11),
  783.                                                                       parseInfo.timeScale));

  784.                                    // satellites list
  785.                                    RINEX_2_DATA_SAT_LIST.parsingMethod.parse(line, parseInfo);

  786.                                }

  787.                                // prepare handling of observations for current epoch
  788.                                parseInfo.indexObsSat = 0;
  789.                                parseInfo.observations.clear();

  790.                            },
  791.                            LineParser::first2),

  792.         /** Parser for Rinex 2 special record. */
  793.         RINEX_2_IGNORED_SPECIAL_RECORD(line -> true,
  794.                            (line, parseInfo) -> {
  795.                                // nothing to do
  796.                            },
  797.                            LineParser::ignore2),

  798.         /** Parser for Rinex 2 observation line. */
  799.         RINEX_2_OBSERVATION(line -> true,
  800.                             (line, parseInfo) -> {
  801.                                 final List<ObservationType> types = parseInfo.file.getHeader().getTypeObs().get(parseInfo.file.getHeader().getSatelliteSystem());
  802.                                 for (int index = 0;
  803.                                      parseInfo.observations.size() < types.size() && index < 80;
  804.                                      index += 16) {
  805.                                     final ObservationData observationData;
  806.                                     if (parseInfo.cycleSlip) {
  807.                                         // we are in a cycle slip data block (eventFlag = 6), we just ignore everything
  808.                                         observationData = null;
  809.                                     } else {
  810.                                         // this is a regular observation line
  811.                                         final ObservationType type    = types.get(parseInfo.observations.size());
  812.                                         final double          scaling = getScaling(parseInfo, type, parseInfo.currentSystem);
  813.                                         observationData = new ObservationData(type,
  814.                                                                               scaling * RinexUtils.parseDouble(line, index, 14),
  815.                                                                               RinexUtils.parseInt(line, index + 14, 1),
  816.                                                                               RinexUtils.parseInt(line, index + 15, 1));
  817.                                     }
  818.                                     parseInfo.observations.add(observationData);
  819.                                 }

  820.                                 if (parseInfo.observations.size() == types.size()) {
  821.                                     // we have finished handling observations/cycle slips for one satellite
  822.                                     if (!parseInfo.cycleSlip) {
  823.                                         parseInfo.file.addObservationDataSet(new ObservationDataSet(parseInfo.satObs.get(parseInfo.indexObsSat),
  824.                                                                                                     parseInfo.tObs,
  825.                                                                                                     parseInfo.eventFlag,
  826.                                                                                                     parseInfo.rcvrClkOffset,
  827.                                                                                                     new ArrayList<>(parseInfo.observations)));
  828.                                     }
  829.                                     parseInfo.indexObsSat++;
  830.                                     parseInfo.observations.clear();
  831.                                 }

  832.                             },
  833.                             LineParser::observation2),

  834.         /** Parser for Rinex 3 observation line. */
  835.         RINEX_3_OBSERVATION(line -> true,
  836.                             (line, parseInfo) -> {
  837.                                 final SatInSystem sat = new SatInSystem(line.substring(0, 3));
  838.                                 final List<ObservationType> types = parseInfo.file.getHeader().getTypeObs().get(sat.getSystem());
  839.                                 for (int index = 3;
  840.                                      parseInfo.observations.size() < types.size();
  841.                                      index += 16) {
  842.                                     final ObservationData observationData;
  843.                                     if (parseInfo.specialRecord || parseInfo.cycleSlip) {
  844.                                         // we are in a special record (eventFlag < 6) or in a cycle slip data block (eventFlag = 6), we just ignore everything
  845.                                         observationData = null;
  846.                                     } else {
  847.                                         // this is a regular observation line
  848.                                         final ObservationType type    = types.get(parseInfo.observations.size());
  849.                                         final double          scaling = getScaling(parseInfo, type, sat.getSystem());
  850.                                         observationData = new ObservationData(type,
  851.                                                                               scaling * RinexUtils.parseDouble(line, index, 14),
  852.                                                                               RinexUtils.parseInt(line, index + 14, 1),
  853.                                                                               RinexUtils.parseInt(line, index + 15, 1));
  854.                                     }
  855.                                     parseInfo.observations.add(observationData);
  856.                                 }

  857.                                 if (!(parseInfo.specialRecord || parseInfo.cycleSlip)) {
  858.                                     parseInfo.file.addObservationDataSet(new ObservationDataSet(sat,
  859.                                                                                                 parseInfo.tObs,
  860.                                                                                                 parseInfo.eventFlag,
  861.                                                                                                 parseInfo.rcvrClkOffset,
  862.                                                                                                 new ArrayList<>(parseInfo.observations)));
  863.                                 }
  864.                                 parseInfo.observations.clear();

  865.                             },
  866.                             LineParser::observation3),

  867.         /** Parser for Rinex 3 data first line. */
  868.         RINEX_3_DATA_FIRST(line -> line.startsWith(">"),
  869.                            (line, parseInfo) -> {

  870.                                // flag
  871.                                parseInfo.eventFlag = RinexUtils.parseInt(line, 31, 1);

  872.                                // number of sats
  873.                                parseInfo.nbSatObs   = RinexUtils.parseInt(line, 32, 3);

  874.                                if (parseInfo.eventFlag < 2) {
  875.                                    // regular observation
  876.                                    parseInfo.specialRecord = false;
  877.                                    parseInfo.cycleSlip     = false;
  878.                                    final int nbSat         = parseInfo.file.getHeader().getNbSat();
  879.                                    if (nbSat != -1 && parseInfo.nbSatObs > nbSat) {
  880.                                        // we check that the number of Sat in the observation is consistent
  881.                                        throw new OrekitException(OrekitMessages.INCONSISTENT_NUMBER_OF_SATS,
  882.                                                                  parseInfo.lineNumber, parseInfo.name,
  883.                                                                  parseInfo.nbSatObs, nbSat);
  884.                                    }
  885.                                    parseInfo.nextObsStartLineNumber = parseInfo.lineNumber + parseInfo.nbSatObs + 1;

  886.                                    // read the Receiver Clock offset, if present
  887.                                    parseInfo.rcvrClkOffset = RinexUtils.parseDouble(line, 41, 15);
  888.                                    if (Double.isNaN(parseInfo.rcvrClkOffset)) {
  889.                                        parseInfo.rcvrClkOffset = 0.0;
  890.                                    }

  891.                                } else if (parseInfo.eventFlag < 6) {
  892.                                    // moving antenna / new site occupation / header information / external event
  893.                                    // here, number of sats means number of lines to skip
  894.                                    parseInfo.specialRecord = true;
  895.                                    parseInfo.cycleSlip     = false;
  896.                                    parseInfo.nextObsStartLineNumber = parseInfo.lineNumber + parseInfo.nbSatObs + 1;
  897.                                } else if (parseInfo.eventFlag == 6) {
  898.                                    // cycle slip, we will ignore it during observations parsing
  899.                                    parseInfo.specialRecord = false;
  900.                                    parseInfo.cycleSlip     = true;
  901.                                    parseInfo.nextObsStartLineNumber = parseInfo.lineNumber + parseInfo.nbSatObs + 1;
  902.                                } else {
  903.                                    // unknown event flag
  904.                                    throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  905.                                                              parseInfo.lineNumber, parseInfo.name, line);
  906.                                }

  907.                                // parse the list of satellites observed
  908.                                parseInfo.satObs.clear();
  909.                                if (!parseInfo.specialRecord) {

  910.                                    // observations epoch
  911.                                    parseInfo.setTObs(new AbsoluteDate(RinexUtils.parseInt(line,  2, 4),
  912.                                                                       RinexUtils.parseInt(line,  7, 2),
  913.                                                                       RinexUtils.parseInt(line, 10, 2),
  914.                                                                       RinexUtils.parseInt(line, 13, 2),
  915.                                                                       RinexUtils.parseInt(line, 16, 2),
  916.                                                                       RinexUtils.parseDouble(line, 18, 11) +
  917.                                                                       PICO_SECOND.toSI(RinexUtils.parseInt(line, 57, 5)),
  918.                                                                       parseInfo.timeScale));

  919.                                }

  920.                                // prepare handling of observations for current epoch
  921.                                parseInfo.observations.clear();

  922.                            },
  923.                            parseInfo -> Collections.singleton(RINEX_3_OBSERVATION));


  924.         /** Predicate for identifying lines that can be parsed. */
  925.         private final Predicate<String> canHandle;

  926.         /** Parsing method. */
  927.         private final ParsingMethod parsingMethod;

  928.         /** Provider for next line parsers. */
  929.         private final Function<ParseInfo, Iterable<LineParser>> allowedNextProvider;

  930.         /** Simple constructor.
  931.          * @param canHandle predicate for identifying lines that can be parsed
  932.          * @param parsingMethod parsing method
  933.          * @param allowedNextProvider supplier for allowed parsers for next line
  934.          */
  935.         LineParser(final Predicate<String> canHandle, final ParsingMethod parsingMethod,
  936.                    final Function<ParseInfo, Iterable<LineParser>> allowedNextProvider) {
  937.             this.canHandle           = canHandle;
  938.             this.parsingMethod       = parsingMethod;
  939.             this.allowedNextProvider = allowedNextProvider;
  940.         }

  941.         /** Get the allowed parsers for next lines while parsing comments.
  942.          * @param parseInfo holder for transient data
  943.          * @return allowed parsers for next line
  944.          */
  945.         private static Iterable<LineParser> commentNext(final ParseInfo parseInfo) {
  946.             return parseInfo.headerCompleted ? headerEndNext(parseInfo) : headerNext(parseInfo);
  947.         }

  948.         /** Get the allowed parsers for next lines while parsing Rinex header.
  949.          * @param parseInfo holder for transient data
  950.          * @return allowed parsers for next line
  951.          */
  952.         private static Iterable<LineParser> headerNext(final ParseInfo parseInfo) {
  953.             if (parseInfo.file.getHeader().getFormatVersion() < 3) {
  954.                 // Rinex 2.x header entries
  955.                 return Arrays.asList(PROGRAM, COMMENT, MARKER_NAME, MARKER_NUMBER, MARKER_TYPE, OBSERVER_AGENCY,
  956.                                      REC_NB_TYPE_VERS, ANT_NB_TYPE, APPROX_POSITION_XYZ, ANTENNA_DELTA_H_E_N,
  957.                                      ANTENNA_DELTA_X_Y_Z, ANTENNA_B_SIGHT_XYZ, WAVELENGTH_FACT_L1_2, OBS_SCALE_FACTOR,
  958.                                      CENTER_OF_MASS_XYZ, SYS_NB_TYPES_OF_OBSERV, INTERVAL, TIME_OF_FIRST_OBS, TIME_OF_LAST_OBS,
  959.                                      RCV_CLOCK_OFFS_APPL, LEAP_SECONDS, NB_OF_SATELLITES, PRN_NB_OF_OBS, END);
  960.             } else if (parseInfo.file.getHeader().getFormatVersion() < 4) {
  961.                 // Rinex 3.x header entries
  962.                 return Arrays.asList(PROGRAM, COMMENT, MARKER_NAME, MARKER_NUMBER, MARKER_TYPE, OBSERVER_AGENCY,
  963.                                      REC_NB_TYPE_VERS, ANT_NB_TYPE, APPROX_POSITION_XYZ, ANTENNA_DELTA_H_E_N,
  964.                                      ANTENNA_DELTA_X_Y_Z, ANTENNA_PHASE_CENTER, ANTENNA_B_SIGHT_XYZ, ANTENNA_ZERODIR_AZI,
  965.                                      ANTENNA_ZERODIR_XYZ, CENTER_OF_MASS_XYZ, SYS_NB_TYPES_OF_OBSERV, SIGNAL_STRENGTH_UNIT,
  966.                                      INTERVAL, TIME_OF_FIRST_OBS, TIME_OF_LAST_OBS, RCV_CLOCK_OFFS_APPL,
  967.                                      SYS_DCBS_APPLIED, SYS_PCVS_APPLIED, SYS_SCALE_FACTOR, SYS_PHASE_SHIFT,
  968.                                      GLONASS_SLOT_FRQ_NB, GLONASS_COD_PHS_BIS, LEAP_SECONDS, NB_OF_SATELLITES,
  969.                                      PRN_NB_OF_OBS, END);
  970.             } else {
  971.                 // Rinex 4.x header entries
  972.                 return Arrays.asList(PROGRAM, COMMENT, MARKER_NAME, MARKER_NUMBER, MARKER_TYPE, OBSERVER_AGENCY,
  973.                                      REC_NB_TYPE_VERS, ANT_NB_TYPE, APPROX_POSITION_XYZ, ANTENNA_DELTA_H_E_N,
  974.                                      ANTENNA_DELTA_X_Y_Z, ANTENNA_PHASE_CENTER, ANTENNA_B_SIGHT_XYZ, ANTENNA_ZERODIR_AZI,
  975.                                      ANTENNA_ZERODIR_XYZ, CENTER_OF_MASS_XYZ, DOI, LICENSE, STATION_INFORMATION,
  976.                                      SYS_NB_TYPES_OF_OBSERV, SIGNAL_STRENGTH_UNIT, INTERVAL, TIME_OF_FIRST_OBS, TIME_OF_LAST_OBS,
  977.                                      RCV_CLOCK_OFFS_APPL, SYS_DCBS_APPLIED, SYS_PCVS_APPLIED, SYS_SCALE_FACTOR, SYS_PHASE_SHIFT,
  978.                                      GLONASS_SLOT_FRQ_NB, GLONASS_COD_PHS_BIS, LEAP_SECONDS, NB_OF_SATELLITES,
  979.                                      PRN_NB_OF_OBS, END);
  980.             }
  981.         }

  982.         /** Get the allowed parsers for next lines while parsing header end.
  983.          * @param parseInfo holder for transient data
  984.          * @return allowed parsers for next line
  985.          */
  986.         private static Iterable<LineParser> headerEndNext(final ParseInfo parseInfo) {
  987.             return Collections.singleton(parseInfo.file.getHeader().getFormatVersion() < 3 ?
  988.                                          RINEX_2_DATA_FIRST : RINEX_3_DATA_FIRST);
  989.         }

  990.         /** Get the allowed parsers for next lines while parsing types of observations.
  991.          * @param parseInfo holder for transient data
  992.          * @return allowed parsers for next line
  993.          */
  994.         private static Iterable<LineParser> headerNbTypesObs(final ParseInfo parseInfo) {
  995.             if (parseInfo.typesObs.size() < parseInfo.nbTypes) {
  996.                 return Arrays.asList(COMMENT, SYS_NB_TYPES_OF_OBSERV);
  997.             } else {
  998.                 return headerNext(parseInfo);
  999.             }
  1000.         }

  1001.         /** Get the allowed parsers for next lines while parsing phase shifts.
  1002.          * @param parseInfo holder for transient data
  1003.          * @return allowed parsers for next line
  1004.          */
  1005.         private static Iterable<LineParser> headerPhaseShift(final ParseInfo parseInfo) {
  1006.             if (parseInfo.satPhaseShift.size() < parseInfo.phaseShiftNbSat) {
  1007.                 return Arrays.asList(COMMENT, SYS_PHASE_SHIFT);
  1008.             } else {
  1009.                 return headerNext(parseInfo);
  1010.             }
  1011.         }

  1012.         /** Get the allowed parsers for next lines while parsing Rinex 2 observations first lines.
  1013.          * @param parseInfo holder for transient data
  1014.          * @return allowed parsers for next line
  1015.          */
  1016.         private static Iterable<LineParser> first2(final ParseInfo parseInfo) {
  1017.             if (parseInfo.specialRecord) {
  1018.                 return Collections.singleton(RINEX_2_IGNORED_SPECIAL_RECORD);
  1019.             } else if (parseInfo.satObs.size() < parseInfo.nbSatObs) {
  1020.                 return Collections.singleton(RINEX_2_DATA_SAT_LIST);
  1021.             } else {
  1022.                 return Collections.singleton(RINEX_2_OBSERVATION);
  1023.             }
  1024.         }

  1025.         /** Get the allowed parsers for next lines while parsing Rinex 2 ignored special records.
  1026.          * @param parseInfo holder for transient data
  1027.          * @return allowed parsers for next line
  1028.          */
  1029.         private static Iterable<LineParser> ignore2(final ParseInfo parseInfo) {
  1030.             if (parseInfo.lineNumber < parseInfo.nextObsStartLineNumber) {
  1031.                 return Collections.singleton(RINEX_2_IGNORED_SPECIAL_RECORD);
  1032.             } else {
  1033.                 return Arrays.asList(COMMENT, RINEX_2_DATA_FIRST);
  1034.             }
  1035.         }

  1036.         /** Get the allowed parsers for next lines while parsing Rinex 2 observations per se.
  1037.          * @param parseInfo holder for transient data
  1038.          * @return allowed parsers for next line
  1039.          */
  1040.         private static Iterable<LineParser> observation2(final ParseInfo parseInfo) {
  1041.             if (parseInfo.lineNumber < parseInfo.nextObsStartLineNumber) {
  1042.                 return Collections.singleton(RINEX_2_OBSERVATION);
  1043.             } else {
  1044.                 return Arrays.asList(COMMENT, RINEX_2_DATA_FIRST);
  1045.             }
  1046.         }

  1047.         /** Get the allowed parsers for next lines while parsing Rinex 3 observations.
  1048.          * @param parseInfo holder for transient data
  1049.          * @return allowed parsers for next line
  1050.          */
  1051.         private static Iterable<LineParser> observation3(final ParseInfo parseInfo) {
  1052.             if (parseInfo.lineNumber < parseInfo.nextObsStartLineNumber) {
  1053.                 return Collections.singleton(RINEX_3_OBSERVATION);
  1054.             } else {
  1055.                 return Arrays.asList(COMMENT, RINEX_3_DATA_FIRST);
  1056.             }
  1057.         }

  1058.         /** Get the scaling factor for an observation.
  1059.          * @param parseInfo holder for transient data
  1060.          * @param type type of observation
  1061.          * @param system satellite system for the observation
  1062.          * @return scaling factor
  1063.          */
  1064.         private static double getScaling(final ParseInfo parseInfo, final ObservationType type,
  1065.                                          final SatelliteSystem system) {

  1066.             for (final ScaleFactorCorrection scaleFactorCorrection :
  1067.                 parseInfo.file.getHeader().getScaleFactorCorrections(system)) {
  1068.                 // check if the next Observation Type to read needs to be scaled
  1069.                 if (scaleFactorCorrection.getTypesObsScaled().contains(type)) {
  1070.                     return 1.0 / scaleFactorCorrection.getCorrection();
  1071.                 }
  1072.             }

  1073.             // no scaling
  1074.             return 1.0;

  1075.         }

  1076.     }

  1077.     /** Parsing method. */
  1078.     @FunctionalInterface
  1079.     private interface ParsingMethod {
  1080.         /** Parse a line.
  1081.          * @param line line to parse
  1082.          * @param parseInfo holder for transient data
  1083.          */
  1084.         void parse(String line, ParseInfo parseInfo);
  1085.     }

  1086. }