RinexObservationParser.java

  1. /* Copyright 2002-2024 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.Function;
  26. import java.util.function.Predicate;

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

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

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

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

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

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

  75.     /** Set of time scales. */
  76.     private final TimeScales timeScales;

  77.     /** Simple constructor.
  78.      * <p>
  79.      * This constructor uses the {@link DataContext#getDefault() default data context}.
  80.      * </p>
  81.      */
  82.     @DefaultDataContext
  83.     public RinexObservationParser() {
  84.         this(DataContext.getDefault().getTimeScales());
  85.     }

  86.     /**
  87.      * Create a RINEX loader/parser with the given source of RINEX auxiliary data files.
  88.      * @param timeScales the set of time scales to use when parsing dates.
  89.      * @since 12.0
  90.      */
  91.     public RinexObservationParser(final TimeScales timeScales) {
  92.         this.timeScales = timeScales;
  93.     }

  94.     /**
  95.      * Parse RINEX observations messages.
  96.      * @param source source providing the data to parse
  97.      * @return parsed observations file
  98.      */
  99.     public RinexObservation parse(final DataSource source) {

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

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

  103.         try (Reader reader = source.getOpener().openReaderOnce();
  104.              BufferedReader br = new BufferedReader(reader)) {
  105.             ++parseInfo.lineNumber;
  106.             nextLine:
  107.                 for (String line = br.readLine(); line != null; line = br.readLine()) {
  108.                     for (final LineParser candidate : candidateParsers) {
  109.                         if (candidate.canHandle.test(line)) {
  110.                             try {
  111.                                 candidate.parsingMethod.parse(line, parseInfo);
  112.                                 ++parseInfo.lineNumber;
  113.                                 candidateParsers = candidate.allowedNextProvider.apply(parseInfo);
  114.                                 continue nextLine;
  115.                             } catch (StringIndexOutOfBoundsException | NumberFormatException e) {
  116.                                 throw new OrekitException(e,
  117.                                                           OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  118.                                                           parseInfo.lineNumber, source.getName(), line);
  119.                             }
  120.                         }
  121.                     }

  122.                     // no parsers found for this line
  123.                     throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  124.                                               parseInfo.lineNumber, source.getName(), line);

  125.                 }
  126.         } catch (IOException ioe) {
  127.             throw new OrekitException(ioe, LocalizedCoreFormats.SIMPLE_MESSAGE, ioe.getLocalizedMessage());
  128.         }

  129.         return parseInfo.file;

  130.     }

  131.     /** Transient data used for parsing a RINEX observation messages file.
  132.      * @since 12.0
  133.      */
  134.     private class ParseInfo {

  135.         /** Name of the data source. */
  136.         private final String name;

  137.         /** Set of time scales for parsing dates. */
  138.         private final TimeScales timeScales;

  139.         /** Current line number of the navigation message. */
  140.         private int lineNumber;

  141.         /** Rinex file. */
  142.         private final RinexObservation file;

  143.         /** Date of the observation. */
  144.         private AbsoluteDate tObs;

  145.         /** Receiver clock offset (seconds). */
  146.         private double rcvrClkOffset;

  147.         /** time scale for parsing dates. */
  148.         private TimeScale timeScale;

  149.         /** Number of observation types. */
  150.         private int nbTypes;

  151.         /** Number of satellites in the current observations block. */
  152.         private int nbSatObs;

  153.         /** Number of scaling factors. */
  154.         private int nbObsScaleFactor;

  155.         /** Index of satellite in current observation. */
  156.         private int indexObsSat;

  157.         /** Line number of start of next observation. */
  158.         private int nextObsStartLineNumber;

  159.         /** Current satellite system. */
  160.         private SatelliteSystem currentSystem;

  161.         /** Number of satellites affected by phase shifts. */
  162.         private int phaseShiftNbSat;

  163.         /** Number of GLONASS satellites. */
  164.         private int nbGlonass;

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

  167.         /** Type of observation affected by phase shift. */
  168.         private ObservationType phaseShiftTypeObs;

  169.         /** Phase shift correction. */
  170.         private double corrPhaseShift;

  171.         /** Indicator for completed header. */
  172.         private boolean headerCompleted;

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

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

  177.         /** Event flag. */
  178.         private int eventFlag;

  179.         /** Scaling factors. */
  180.         private final List<ObservationType> typesObsScaleFactor;

  181.         /** Types of observations. */
  182.         private final List<ObservationType> typesObs;

  183.         /** Observations. */
  184.         private final List<ObservationData> observations;

  185.         /** Satellites in current observation. */
  186.         private final List<SatInSystem> satObs;

  187.         /** Current satellite. */
  188.         private SatInSystem currentSat;

  189.         /** Constructor, build the ParseInfo object.
  190.          * @param name name of the data source
  191.          */
  192.         ParseInfo(final String name) {
  193.             // Initialize default values for fields
  194.             this.name                   = name;
  195.             this.timeScales             = RinexObservationParser.this.timeScales;
  196.             this.file                   = new RinexObservation();
  197.             this.lineNumber             = 0;
  198.             this.tObs                   = AbsoluteDate.PAST_INFINITY;
  199.             this.timeScale              = null;
  200.             this.nbTypes                = -1;
  201.             this.nbSatObs               = -1;
  202.             this.nbGlonass              = -1;
  203.             this.phaseShiftNbSat        = -1;
  204.             this.nbObsScaleFactor       = -1;
  205.             this.nextObsStartLineNumber = -1;
  206.             this.typesObs               = new ArrayList<>();
  207.             this.observations           = new ArrayList<>();
  208.             this.satPhaseShift          = new ArrayList<>();
  209.             this.typesObsScaleFactor    = new ArrayList<>();
  210.             this.satObs                 = new ArrayList<>();
  211.         }

  212.     }

  213.     /** Parsers for specific lines. */
  214.     private enum LineParser {

  215.         /** Parser for version, file type and satellite system. */
  216.         VERSION(line -> RinexLabels.VERSION.matches(RinexUtils.getLabel(line)),
  217.                 (line, parseInfo) ->  RinexUtils.parseVersionFileTypeSatelliteSystem(line, parseInfo.name, parseInfo.file.getHeader(),
  218.                                                                                      2.00, 2.10, 2.11, 2.12, 2.20,
  219.                                                                                      3.00, 3.01, 3.02, 3.03, 3.04, 3.05, 4.00),
  220.                 LineParser::headerNext),

  221.         /** Parser for generating program and emiting agency. */
  222.         PROGRAM(line -> RinexLabels.PROGRAM.matches(RinexUtils.getLabel(line)),
  223.                 (line, parseInfo) -> RinexUtils.parseProgramRunByDate(line, parseInfo.lineNumber, parseInfo.name,
  224.                                                                       parseInfo.timeScales, parseInfo.file.getHeader()),
  225.                 LineParser::headerNext),

  226.         /** Parser for comments. */
  227.         COMMENT(line -> RinexLabels.COMMENT.matches(RinexUtils.getLabel(line)),
  228.                        (line, parseInfo) -> RinexUtils.parseComment(parseInfo.lineNumber, line, parseInfo.file),
  229.                        LineParser::commentNext),

  230.         /** Parser for marker name. */
  231.         MARKER_NAME(line -> RinexLabels.MARKER_NAME.matches(RinexUtils.getLabel(line)),
  232.                     (line, parseInfo) ->  parseInfo.file.getHeader().setMarkerName(RinexUtils.parseString(line, 0, RinexUtils.LABEL_INDEX)),
  233.                     LineParser::headerNext),

  234.         /** Parser for marker number. */
  235.         MARKER_NUMBER(line -> RinexLabels.MARKER_NUMBER.matches(RinexUtils.getLabel(line)),
  236.                       (line, parseInfo) -> parseInfo.file.getHeader().setMarkerNumber(RinexUtils.parseString(line, 0, 20)),
  237.                       LineParser::headerNext),

  238.         /** Parser for marker type. */
  239.         MARKER_TYPE(line -> RinexLabels.MARKER_TYPE.matches(RinexUtils.getLabel(line)),
  240.                     (line, parseInfo) -> parseInfo.file.getHeader().setMarkerType(RinexUtils.parseString(line, 0, 20)),
  241.                     LineParser::headerNext),

  242.         /** Parser for observer agency. */
  243.         OBSERVER_AGENCY(line -> RinexLabels.OBSERVER_AGENCY.matches(RinexUtils.getLabel(line)),
  244.                         (line, parseInfo) -> {
  245.                             parseInfo.file.getHeader().setObserverName(RinexUtils.parseString(line, 0, 20));
  246.                             parseInfo.file.getHeader().setAgencyName(RinexUtils.parseString(line, 20, 40));
  247.                         },
  248.                         LineParser::headerNext),

  249.         /** Parser for receiver number, type and version. */
  250.         REC_NB_TYPE_VERS(line -> RinexLabels.REC_NB_TYPE_VERS.matches(RinexUtils.getLabel(line)),
  251.                          (line, parseInfo) -> {
  252.                              parseInfo.file.getHeader().setReceiverNumber(RinexUtils.parseString(line, 0, 20));
  253.                              parseInfo.file.getHeader().setReceiverType(RinexUtils.parseString(line, 20, 20));
  254.                              parseInfo.file.getHeader().setReceiverVersion(RinexUtils.parseString(line, 40, 20));
  255.                          },
  256.                          LineParser::headerNext),

  257.         /** Parser for antenna number and type. */
  258.         ANT_NB_TYPE(line -> RinexLabels.ANT_NB_TYPE.matches(RinexUtils.getLabel(line)),
  259.                     (line, parseInfo) -> {
  260.                         parseInfo.file.getHeader().setAntennaNumber(RinexUtils.parseString(line, 0, 20));
  261.                         parseInfo.file.getHeader().setAntennaType(RinexUtils.parseString(line, 20, 20));
  262.                     },
  263.                     LineParser::headerNext),

  264.         /** Parser for approximative position. */
  265.         APPROX_POSITION_XYZ(line -> RinexLabels.APPROX_POSITION_XYZ.matches(RinexUtils.getLabel(line)),
  266.                             (line, parseInfo) -> {
  267.                                 parseInfo.file.getHeader().setApproxPos(new Vector3D(RinexUtils.parseDouble(line, 0, 14),
  268.                                                                                      RinexUtils.parseDouble(line, 14, 14),
  269.                                                                                      RinexUtils.parseDouble(line, 28, 14)));
  270.                             },
  271.                             LineParser::headerNext),

  272.         /** Parser for antenna reference point. */
  273.         ANTENNA_DELTA_H_E_N(line -> RinexLabels.ANTENNA_DELTA_H_E_N.matches(RinexUtils.getLabel(line)),
  274.                             (line, parseInfo) -> {
  275.                                 parseInfo.file.getHeader().setAntennaHeight(RinexUtils.parseDouble(line, 0, 14));
  276.                                 parseInfo.file.getHeader().setEccentricities(new Vector2D(RinexUtils.parseDouble(line, 14, 14),
  277.                                                                                           RinexUtils.parseDouble(line, 28, 14)));
  278.                             },
  279.                             LineParser::headerNext),

  280.         /** Parser for antenna reference point. */
  281.         ANTENNA_DELTA_X_Y_Z(line -> RinexLabels.ANTENNA_DELTA_X_Y_Z.matches(RinexUtils.getLabel(line)),
  282.                             (line, parseInfo) -> {
  283.                                 parseInfo.file.getHeader().setAntennaReferencePoint(new Vector3D(RinexUtils.parseDouble(line,  0, 14),
  284.                                                                                                  RinexUtils.parseDouble(line, 14, 14),
  285.                                                                                                  RinexUtils.parseDouble(line, 28, 14)));
  286.                             },
  287.                             LineParser::headerNext),

  288.         /** Parser for antenna phase center. */
  289.         ANTENNA_PHASE_CENTER(line -> RinexLabels.ANTENNA_PHASE_CENTER.matches(RinexUtils.getLabel(line)),
  290.                              (line, parseInfo) -> {
  291.                                  parseInfo.file.getHeader().setPhaseCenterSystem(SatelliteSystem.parseSatelliteSystem(RinexUtils.parseString(line, 0, 1)));
  292.                                  parseInfo.file.getHeader().setObservationCode(RinexUtils.parseString(line, 2, 3));
  293.                                  parseInfo.file.getHeader().setAntennaPhaseCenter(new Vector3D(RinexUtils.parseDouble(line, 5, 9),
  294.                                                                                                RinexUtils.parseDouble(line, 14, 14),
  295.                                                                                                RinexUtils.parseDouble(line, 28, 14)));
  296.                              },
  297.                              LineParser::headerNext),

  298.         /** Parser for antenna bore sight. */
  299.         ANTENNA_B_SIGHT_XYZ(line -> RinexLabels.ANTENNA_B_SIGHT_XYZ.matches(RinexUtils.getLabel(line)),
  300.                             (line, parseInfo) -> {
  301.                                 parseInfo.file.getHeader().setAntennaBSight(new Vector3D(RinexUtils.parseDouble(line,  0, 14),
  302.                                                                                          RinexUtils.parseDouble(line, 14, 14),
  303.                                                                                          RinexUtils.parseDouble(line, 28, 14)));
  304.                             },
  305.                             LineParser::headerNext),

  306.         /** Parser for antenna zero direction. */
  307.         ANTENNA_ZERODIR_AZI(line -> RinexLabels.ANTENNA_ZERODIR_AZI.matches(RinexUtils.getLabel(line)),
  308.                             (line, parseInfo) -> parseInfo.file.getHeader().setAntennaAzimuth(FastMath.toRadians(RinexUtils.parseDouble(line, 0, 14))),
  309.                             LineParser::headerNext),

  310.         /** Parser for antenna zero direction. */
  311.         ANTENNA_ZERODIR_XYZ(line -> RinexLabels.ANTENNA_ZERODIR_XYZ.matches(RinexUtils.getLabel(line)),
  312.                             (line, parseInfo) -> parseInfo.file.getHeader().setAntennaZeroDirection(new Vector3D(RinexUtils.parseDouble(line, 0, 14),
  313.                                                                                                                  RinexUtils.parseDouble(line, 14, 14),
  314.                                                                                                                  RinexUtils.parseDouble(line, 28, 14))),
  315.                             LineParser::headerNext),

  316.         /** Parser for wavelength factors. */
  317.         WAVELENGTH_FACT_L1_2(line -> RinexLabels.WAVELENGTH_FACT_L1_2.matches(RinexUtils.getLabel(line)),
  318.                              (line, parseInfo) -> {
  319.                                  // optional line in Rinex 2 header, not stored for now
  320.                              },
  321.                              LineParser::headerNext),

  322.         /** Parser for observations scale factor. */
  323.         OBS_SCALE_FACTOR(line -> RinexLabels.OBS_SCALE_FACTOR.matches(RinexUtils.getLabel(line)),
  324.                          (line, parseInfo) -> {
  325.                              final int scaleFactor      = FastMath.max(1, RinexUtils.parseInt(line, 0,  6));
  326.                              final int nbObsScaleFactor = RinexUtils.parseInt(line, 6, 6);
  327.                              final List<ObservationType> types = new ArrayList<>(nbObsScaleFactor);
  328.                              for (int i = 0; i < nbObsScaleFactor; i++) {
  329.                                  types.add(ObservationType.valueOf(RinexUtils.parseString(line, 16 + (6 * i), 2)));
  330.                              }
  331.                              parseInfo.file.getHeader().addScaleFactorCorrection(parseInfo.file.getHeader().getSatelliteSystem(),
  332.                                                                                  new ScaleFactorCorrection(scaleFactor, types));
  333.                          },
  334.                          LineParser::headerNext),

  335.         /** Parser for center of mass. */
  336.         CENTER_OF_MASS_XYZ(line -> RinexLabels.CENTER_OF_MASS_XYZ.matches(RinexUtils.getLabel(line)),
  337.                            (line, parseInfo) -> {
  338.                                parseInfo.file.getHeader().setCenterMass(new Vector3D(RinexUtils.parseDouble(line,  0, 14),
  339.                                                                                      RinexUtils.parseDouble(line, 14, 14),
  340.                                                                                      RinexUtils.parseDouble(line, 28, 14)));
  341.                            },
  342.                            LineParser::headerNext),

  343.         /** Parser for DOI.
  344.          * @since 12.0
  345.          */
  346.         DOI(line -> RinexLabels.DOI.matches(RinexUtils.getLabel(line)),
  347.             (line, parseInfo) -> parseInfo.file.getHeader().setDoi(RinexUtils.parseString(line, 0, RinexUtils.LABEL_INDEX)),
  348.             LineParser::headerNext),

  349.         /** Parser for license.
  350.          * @since 12.0
  351.          */
  352.         LICENSE(line -> RinexLabels.LICENSE.matches(RinexUtils.getLabel(line)),
  353.                 (line, parseInfo) -> parseInfo.file.getHeader().setLicense(RinexUtils.parseString(line, 0, RinexUtils.LABEL_INDEX)),
  354.                 LineParser::headerNext),

  355.         /** Parser for station information.
  356.          * @since 12.0
  357.          */
  358.         STATION_INFORMATION(line -> RinexLabels.STATION_INFORMATION.matches(RinexUtils.getLabel(line)),
  359.                             (line, parseInfo) -> parseInfo.file.getHeader().setStationInformation(RinexUtils.parseString(line, 0, RinexUtils.LABEL_INDEX)),
  360.                             LineParser::headerNext),

  361.         /** Parser for number and types of observations. */
  362.         SYS_NB_TYPES_OF_OBSERV(line -> RinexLabels.SYS_NB_TYPES_OF_OBSERV.matches(RinexUtils.getLabel(line)) ||
  363.                                        RinexLabels.NB_TYPES_OF_OBSERV.matches(RinexUtils.getLabel(line)),
  364.                                (line, parseInfo) -> {
  365.                                    final double version = parseInfo.file.getHeader().getFormatVersion();
  366.                                    if (parseInfo.nbTypes < 0) {
  367.                                        // first line of types of observations
  368.                                        if (version < 3) {
  369.                                            // Rinex 2 has only one system
  370.                                            parseInfo.currentSystem = parseInfo.file.getHeader().getSatelliteSystem();
  371.                                            parseInfo.nbTypes       = RinexUtils.parseInt(line, 0, 6);
  372.                                        } else {
  373.                                            // Rinex 3 and above allow mixed systems
  374.                                            parseInfo.currentSystem = SatelliteSystem.parseSatelliteSystem(RinexUtils.parseString(line, 0, 1));
  375.                                            parseInfo.nbTypes       = RinexUtils.parseInt(line, 3, 3);
  376.                                            if (parseInfo.currentSystem != parseInfo.file.getHeader().getSatelliteSystem() &&
  377.                                                parseInfo.file.getHeader().getSatelliteSystem() != SatelliteSystem.MIXED) {
  378.                                                throw new OrekitException(OrekitMessages.INCONSISTENT_SATELLITE_SYSTEM,
  379.                                                                          parseInfo.lineNumber, parseInfo.name,
  380.                                                                          parseInfo.file.getHeader().getSatelliteSystem(),
  381.                                                                          parseInfo.currentSystem);
  382.                                            }
  383.                                        }
  384.                                    }

  385.                                    final int firstIndex = version < 3 ? 10 : 7;
  386.                                    final int increment  = version < 3 ?  6 : 4;
  387.                                    final int size       = version < 3 ?  2 : 3;
  388.                                    for (int i = firstIndex;
  389.                                                    (i + size) <= RinexUtils.LABEL_INDEX && parseInfo.typesObs.size() < parseInfo.nbTypes;
  390.                                                    i += increment) {
  391.                                        final String type = RinexUtils.parseString(line, i, size);
  392.                                        try {
  393.                                            parseInfo.typesObs.add(ObservationType.valueOf(type));
  394.                                        } catch (IllegalArgumentException iae) {
  395.                                            throw new OrekitException(iae, OrekitMessages.UNKNOWN_RINEX_FREQUENCY,
  396.                                                                      type, parseInfo.name, parseInfo.lineNumber);
  397.                                        }
  398.                                    }

  399.                                    if (parseInfo.typesObs.size() == parseInfo.nbTypes) {
  400.                                        // we have completed the list
  401.                                        parseInfo.file.getHeader().setTypeObs(parseInfo.currentSystem, parseInfo.typesObs);
  402.                                        parseInfo.typesObs.clear();
  403.                                        parseInfo.nbTypes = -1;
  404.                                    }

  405.                                },
  406.                                LineParser::headerNbTypesObs),

  407.         /** Parser for unit of signal strength. */
  408.         SIGNAL_STRENGTH_UNIT(line -> RinexLabels.SIGNAL_STRENGTH_UNIT.matches(RinexUtils.getLabel(line)),
  409.                              (line, parseInfo) -> parseInfo.file.getHeader().setSignalStrengthUnit(RinexUtils.parseString(line, 0, 20)),
  410.                              LineParser::headerNext),

  411.         /** Parser for observation interval. */
  412.         INTERVAL(line -> RinexLabels.INTERVAL.matches(RinexUtils.getLabel(line)),
  413.                  (line, parseInfo) -> parseInfo.file.getHeader().setInterval(RinexUtils.parseDouble(line, 0, 10)),
  414.                  LineParser::headerNext),

  415.         /** Parser for time of first observation. */
  416.         TIME_OF_FIRST_OBS(line -> RinexLabels.TIME_OF_FIRST_OBS.matches(RinexUtils.getLabel(line)),
  417.                           (line, parseInfo) -> {
  418.                               if (parseInfo.file.getHeader().getSatelliteSystem() == SatelliteSystem.MIXED) {
  419.                                   // in case of mixed data, time scale must be specified in the Time of First Observation line
  420.                                   try {
  421.                                       parseInfo.timeScale = ObservationTimeScale.
  422.                                                             valueOf(RinexUtils.parseString(line, 48, 3)).
  423.                                                             getTimeScale(parseInfo.timeScales);
  424.                                   } catch (IllegalArgumentException iae) {
  425.                                       throw new OrekitException(iae,
  426.                                                                 OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  427.                                                                 parseInfo.lineNumber, parseInfo.name, line);
  428.                                   }
  429.                               } else {
  430.                                   final ObservationTimeScale observationTimeScale = parseInfo.file.getHeader().getSatelliteSystem().getObservationTimeScale();
  431.                                   if (observationTimeScale == null) {
  432.                                       throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  433.                                                                 parseInfo.lineNumber, parseInfo.name, line);
  434.                                   }
  435.                                   parseInfo.timeScale = observationTimeScale.getTimeScale(parseInfo.timeScales);
  436.                               }
  437.                               parseInfo.file.getHeader().setTFirstObs(new AbsoluteDate(RinexUtils.parseInt(line, 0, 6),
  438.                                                                                        RinexUtils.parseInt(line, 6, 6),
  439.                                                                                        RinexUtils.parseInt(line, 12, 6),
  440.                                                                                        RinexUtils.parseInt(line, 18, 6),
  441.                                                                                        RinexUtils.parseInt(line, 24, 6),
  442.                                                                                        RinexUtils.parseDouble(line, 30, 13),
  443.                                                                                        parseInfo.timeScale));
  444.                           },
  445.                           LineParser::headerNext),

  446.         /** Parser for time of last observation. */
  447.         TIME_OF_LAST_OBS(line -> RinexLabels.TIME_OF_LAST_OBS.matches(RinexUtils.getLabel(line)),
  448.                          (line, parseInfo) -> {
  449.                              parseInfo.file.getHeader().setTLastObs(new AbsoluteDate(RinexUtils.parseInt(line, 0, 6),
  450.                                                                                      RinexUtils.parseInt(line, 6, 6),
  451.                                                                                      RinexUtils.parseInt(line, 12, 6),
  452.                                                                                      RinexUtils.parseInt(line, 18, 6),
  453.                                                                                      RinexUtils.parseInt(line, 24, 6),
  454.                                                                                      RinexUtils.parseDouble(line, 30, 13),
  455.                                                                                      parseInfo.timeScale));
  456.                          },
  457.                          LineParser::headerNext),

  458.         /** Parser for indicator of receiver clock offset application. */
  459.         RCV_CLOCK_OFFS_APPL(line -> RinexLabels.RCV_CLOCK_OFFS_APPL.matches(RinexUtils.getLabel(line)),
  460.                             (line, parseInfo) -> parseInfo.file.getHeader().setClkOffset(RinexUtils.parseInt(line, 0, 6)),
  461.                             LineParser::headerNext),

  462.         /** Parser for differential code bias corrections. */
  463.         SYS_DCBS_APPLIED(line -> RinexLabels.SYS_DCBS_APPLIED.matches(RinexUtils.getLabel(line)),
  464.                          (line, parseInfo) -> parseInfo.file.getHeader().addAppliedDCBS(new AppliedDCBS(SatelliteSystem.parseSatelliteSystem(RinexUtils.parseString(line, 0, 1)),
  465.                                                                                                         RinexUtils.parseString(line, 2, 17),
  466.                                                                                                         RinexUtils.parseString(line, 20, 40))),
  467.                          LineParser::headerNext),

  468.         /** Parser for phase center variations corrections. */
  469.         SYS_PCVS_APPLIED(line -> RinexLabels.SYS_PCVS_APPLIED.matches(RinexUtils.getLabel(line)),
  470.                          (line, parseInfo) -> parseInfo.file.getHeader().addAppliedPCVS(new AppliedPCVS(SatelliteSystem.parseSatelliteSystem(RinexUtils.parseString(line, 0, 1)),
  471.                                                                                                         RinexUtils.parseString(line, 2, 17),
  472.                                                                                                         RinexUtils.parseString(line, 20, 40))),
  473.                          LineParser::headerNext),

  474.         /** Parser for scale factor. */
  475.         SYS_SCALE_FACTOR(line -> RinexLabels.SYS_SCALE_FACTOR.matches(RinexUtils.getLabel(line)),
  476.                          (line, parseInfo) -> {

  477.                              int scaleFactor = 1;
  478.                              if (parseInfo.nbObsScaleFactor < 0) {
  479.                                  // first line of scale factor
  480.                                  parseInfo.currentSystem    = SatelliteSystem.parseSatelliteSystem(RinexUtils.parseString(line, 0, 1));
  481.                                  scaleFactor                = RinexUtils.parseInt(line, 2, 4);
  482.                                  parseInfo.nbObsScaleFactor = RinexUtils.parseInt(line, 8, 2);
  483.                              }

  484.                              if (parseInfo.nbObsScaleFactor == 0) {
  485.                                  parseInfo.typesObsScaleFactor.addAll(parseInfo.file.getHeader().getTypeObs().get(parseInfo.currentSystem));
  486.                              } else {
  487.                                  for (int i = 11; i < RinexUtils.LABEL_INDEX && parseInfo.typesObsScaleFactor.size() < parseInfo.nbObsScaleFactor; i += 4) {
  488.                                      parseInfo.typesObsScaleFactor.add(ObservationType.valueOf(RinexUtils.parseString(line, i, 3)));
  489.                                  }
  490.                              }

  491.                              if (parseInfo.typesObsScaleFactor.size() >= parseInfo.nbObsScaleFactor) {
  492.                                  // we have completed the list
  493.                                  parseInfo.file.getHeader().addScaleFactorCorrection(parseInfo.currentSystem,
  494.                                                                            new ScaleFactorCorrection(scaleFactor,
  495.                                                                                                      new ArrayList<>(parseInfo.typesObsScaleFactor)));
  496.                                  parseInfo.nbObsScaleFactor = -1;
  497.                                  parseInfo.typesObsScaleFactor.clear();
  498.                              }

  499.                          },
  500.                          LineParser::headerNext),

  501.         /** Parser for phase shift. */
  502.         SYS_PHASE_SHIFT(line -> RinexLabels.SYS_PHASE_SHIFT.matches(RinexUtils.getLabel(line)),
  503.                         (line, parseInfo) -> {

  504.                             if (parseInfo.phaseShiftNbSat < 0) {
  505.                                 // first line of phase shift
  506.                                 parseInfo.currentSystem     = SatelliteSystem.parseSatelliteSystem(RinexUtils.parseString(line, 0, 1));
  507.                                 final String to             = RinexUtils.parseString(line, 2, 3);
  508.                                 parseInfo.phaseShiftTypeObs = to.isEmpty() ? null : ObservationType.valueOf(to.length() < 3 ? "L" + to : to);
  509.                                 parseInfo.corrPhaseShift    = RinexUtils.parseDouble(line, 6, 8);
  510.                                 parseInfo.phaseShiftNbSat   = RinexUtils.parseInt(line, 16, 2);
  511.                             }

  512.                             for (int i = 19; i + 3 < RinexUtils.LABEL_INDEX && parseInfo.satPhaseShift.size() < parseInfo.phaseShiftNbSat; i += 4) {
  513.                                 final SatelliteSystem system = line.charAt(i) == ' ' ?
  514.                                                                parseInfo.currentSystem :
  515.                                                                SatelliteSystem.parseSatelliteSystem(RinexUtils.parseString(line, i, 1));
  516.                                 final int             prn    = RinexUtils.parseInt(line, i + 1, 2);
  517.                                 parseInfo.satPhaseShift.add(new SatInSystem(system,
  518.                                                                             system == SatelliteSystem.SBAS ?
  519.                                                                             prn + 100 :
  520.                                                                             (system == SatelliteSystem.QZSS ? prn + 192 : prn)));
  521.                             }

  522.                             if (parseInfo.satPhaseShift.size() == parseInfo.phaseShiftNbSat) {
  523.                                 // we have completed the list
  524.                                 parseInfo.file.getHeader().addPhaseShiftCorrection(new PhaseShiftCorrection(parseInfo.currentSystem,
  525.                                                                                                             parseInfo.phaseShiftTypeObs,
  526.                                                                                                             parseInfo.corrPhaseShift,
  527.                                                                                                             new ArrayList<>(parseInfo.satPhaseShift)));
  528.                                 parseInfo.phaseShiftNbSat = -1;
  529.                                 parseInfo.satPhaseShift.clear();
  530.                             }

  531.                         },
  532.                         LineParser::headerPhaseShift),

  533.         /** Parser for GLONASS slot and frequency number. */
  534.         GLONASS_SLOT_FRQ_NB(line -> RinexLabels.GLONASS_SLOT_FRQ_NB.matches(RinexUtils.getLabel(line)),
  535.                             (line, parseInfo) -> {

  536.                                 if (parseInfo.nbGlonass < 0) {
  537.                                     // first line of GLONASS satellite/frequency association
  538.                                     parseInfo.nbGlonass = RinexUtils.parseInt(line, 0, 3);
  539.                                 }

  540.                                 for (int i = 4;
  541.                                      i < RinexUtils.LABEL_INDEX && parseInfo.file.getHeader().getGlonassChannels().size() < parseInfo.nbGlonass;
  542.                                      i += 7) {
  543.                                     final SatelliteSystem system = SatelliteSystem.parseSatelliteSystem(RinexUtils.parseString(line, i, 1));
  544.                                     final int             prn    = RinexUtils.parseInt(line, i + 1, 2);
  545.                                     final int             k      = RinexUtils.parseInt(line, i + 4, 2);
  546.                                     parseInfo.file.getHeader().addGlonassChannel(new GlonassSatelliteChannel(new SatInSystem(system, prn), k));
  547.                                 }

  548.                             },
  549.                             LineParser::headerNext),

  550.         /** Parser for GLONASS phase bias corrections. */
  551.         GLONASS_COD_PHS_BIS(line -> RinexLabels.GLONASS_COD_PHS_BIS.matches(RinexUtils.getLabel(line)),
  552.                             (line, parseInfo) -> {

  553.                                 // C1C signal
  554.                                 final String c1c = RinexUtils.parseString(line, 1, 3);
  555.                                 if (c1c.length() > 0) {
  556.                                     parseInfo.file.getHeader().setC1cCodePhaseBias(RinexUtils.parseDouble(line, 5, 8));
  557.                                 }

  558.                                 // C1P signal
  559.                                 final String c1p = RinexUtils.parseString(line, 14, 3);
  560.                                 if (c1p.length() > 0) {
  561.                                     parseInfo.file.getHeader().setC1pCodePhaseBias(RinexUtils.parseDouble(line, 18, 8));
  562.                                 }

  563.                                 // C2C signal
  564.                                 final String c2c = RinexUtils.parseString(line, 27, 3);
  565.                                 if (c2c.length() > 0) {
  566.                                     parseInfo.file.getHeader().setC2cCodePhaseBias(RinexUtils.parseDouble(line, 31, 8));
  567.                                 }

  568.                                 // C2P signal
  569.                                 final String c2p = RinexUtils.parseString(line, 40, 3);
  570.                                 if (c2p.length() > 0) {
  571.                                     parseInfo.file.getHeader().setC2pCodePhaseBias(RinexUtils.parseDouble(line, 44, 8));
  572.                                 }

  573.                             },
  574.                             LineParser::headerNext),

  575.         /** Parser for leap seconds. */
  576.         LEAP_SECONDS(line -> RinexLabels.LEAP_SECONDS.matches(RinexUtils.getLabel(line)),
  577.                      (line, parseInfo) -> {
  578.                          parseInfo.file.getHeader().setLeapSeconds(RinexUtils.parseInt(line, 0, 6));
  579.                          if (parseInfo.file.getHeader().getFormatVersion() >= 3.0) {
  580.                              parseInfo.file.getHeader().setLeapSecondsFuture(RinexUtils.parseInt(line, 6, 6));
  581.                              parseInfo.file.getHeader().setLeapSecondsWeekNum(RinexUtils.parseInt(line, 12, 6));
  582.                              parseInfo.file.getHeader().setLeapSecondsDayNum(RinexUtils.parseInt(line, 18, 6));
  583.                          }
  584.                      },
  585.                      LineParser::headerNext),

  586.         /** Parser for number of satellites. */
  587.         NB_OF_SATELLITES(line -> RinexLabels.NB_OF_SATELLITES.matches(RinexUtils.getLabel(line)),
  588.                          (line, parseInfo) -> parseInfo.file.getHeader().setNbSat(RinexUtils.parseInt(line, 0, 6)),
  589.                          LineParser::headerNext),

  590.         /** Parser for PRN and number of observations . */
  591.         PRN_NB_OF_OBS(line -> RinexLabels.PRN_NB_OF_OBS.matches(RinexUtils.getLabel(line)),
  592.                       (line, parseInfo) ->  {
  593.                           final String systemName = RinexUtils.parseString(line, 3, 1);
  594.                           if (systemName.length() > 0) {
  595.                               final SatelliteSystem system = SatelliteSystem.parseSatelliteSystem(systemName);
  596.                               final int             prn    = RinexUtils.parseInt(line, 4, 2);
  597.                               parseInfo.currentSat         = new SatInSystem(system,
  598.                                                                              system == SatelliteSystem.SBAS ?
  599.                                                                              prn + 100 :
  600.                                                                              (system == SatelliteSystem.QZSS ? prn + 192 : prn));
  601.                               parseInfo.nbTypes            = 0;
  602.                           }
  603.                           final List<ObservationType> types = parseInfo.file.getHeader().getTypeObs().get(parseInfo.currentSat.getSystem());

  604.                           final int firstIndex = 6;
  605.                           final int increment  = 6;
  606.                           final int size       = 6;
  607.                           for (int i = firstIndex;
  608.                                (i + size) <= RinexUtils.LABEL_INDEX && parseInfo.nbTypes < types.size();
  609.                                i += increment) {
  610.                               final String nb = RinexUtils.parseString(line, i, size);
  611.                               if (nb.length() > 0) {
  612.                                   parseInfo.file.getHeader().setNbObsPerSatellite(parseInfo.currentSat, types.get(parseInfo.nbTypes),
  613.                                                                         RinexUtils.parseInt(line, i, size));
  614.                               }
  615.                               ++parseInfo.nbTypes;
  616.                           }

  617.                       },
  618.                       LineParser::headerNext),

  619.         /** Parser for the end of header. */
  620.         END(line -> RinexLabels.END.matches(RinexUtils.getLabel(line)),
  621.             (line, parseInfo) -> {

  622.                 parseInfo.headerCompleted = true;

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

  625.                 // check mandatory header fields
  626.                 if (version < 3) {
  627.                     if (parseInfo.file.getHeader().getMarkerName()                  == null ||
  628.                         parseInfo.file.getHeader().getObserverName()                == null ||
  629.                         parseInfo.file.getHeader().getReceiverNumber()              == null ||
  630.                         parseInfo.file.getHeader().getAntennaNumber()               == null ||
  631.                         parseInfo.file.getHeader().getTFirstObs()                   == null ||
  632.                         version < 2.20 && parseInfo.file.getHeader().getApproxPos() == null ||
  633.                         version < 2.20 && Double.isNaN(parseInfo.file.getHeader().getAntennaHeight()) ||
  634.                         parseInfo.file.getHeader().getTypeObs().isEmpty()) {
  635.                         throw new OrekitException(OrekitMessages.INCOMPLETE_HEADER, parseInfo.name);
  636.                     }

  637.                 } else {
  638.                     if (parseInfo.file.getHeader().getMarkerName()           == null ||
  639.                         parseInfo.file.getHeader().getObserverName()         == null ||
  640.                         parseInfo.file.getHeader().getReceiverNumber()       == null ||
  641.                         parseInfo.file.getHeader().getAntennaNumber()        == null ||
  642.                         Double.isNaN(parseInfo.file.getHeader().getAntennaHeight()) &&
  643.                                 parseInfo.file.getHeader().getAntennaReferencePoint() == null  ||
  644.                         parseInfo.file.getHeader().getTFirstObs()            == null ||
  645.                         parseInfo.file.getHeader().getTypeObs().isEmpty()) {
  646.                         throw new OrekitException(OrekitMessages.INCOMPLETE_HEADER, parseInfo.name);
  647.                     }
  648.                 }
  649.             },
  650.             LineParser::headerEndNext),

  651.         /** Parser for Rinex 2 data list of satellites. */
  652.         RINEX_2_DATA_SAT_LIST(line -> true,
  653.                               (line, parseInfo) -> {
  654.                                   for (int index = 32; parseInfo.satObs.size() < parseInfo.nbSatObs && index < 68; index += 3) {
  655.                                       // add one PRN to the list of observed satellites
  656.                                       final SatelliteSystem system = line.charAt(index) == ' ' ?
  657.                                                                      parseInfo.file.getHeader().getSatelliteSystem() :
  658.                                                                      SatelliteSystem.parseSatelliteSystem(RinexUtils.parseString(line, index, 1));
  659.                                       if (system != parseInfo.file.getHeader().getSatelliteSystem() &&
  660.                                           parseInfo.file.getHeader().getSatelliteSystem() != SatelliteSystem.MIXED) {
  661.                                           throw new OrekitException(OrekitMessages.INCONSISTENT_SATELLITE_SYSTEM,
  662.                                                                     parseInfo.lineNumber, parseInfo.name,
  663.                                                                     parseInfo.file.getHeader().getSatelliteSystem(),
  664.                                                                     system);
  665.                                       }
  666.                                       final int             prn       = RinexUtils.parseInt(line, index + 1, 2);
  667.                                       final SatInSystem     satellite = new SatInSystem(system,
  668.                                                                                         system == SatelliteSystem.SBAS ? prn + 100 : prn);
  669.                                       parseInfo.satObs.add(satellite);
  670.                                       // note that we *must* use parseInfo.file.getHeader().getSatelliteSystem() as it was used to set up parseInfo.mapTypeObs
  671.                                       // and it may be MIXED to be applied to all satellites systems
  672.                                       final int nbObservables = parseInfo.file.getHeader().getTypeObs().get(parseInfo.file.getHeader().getSatelliteSystem()).size();
  673.                                       final int nbLines       = (nbObservables + MAX_OBS_PER_RINEX_2_LINE - 1) / MAX_OBS_PER_RINEX_2_LINE;
  674.                                       parseInfo.nextObsStartLineNumber += nbLines;
  675.                                   }
  676.                               },
  677.                               LineParser::first2),

  678.         /** Parser for Rinex 2 data first line. */
  679.         RINEX_2_DATA_FIRST(line -> true,
  680.                            (line, parseInfo) -> {

  681.                                // flag
  682.                                parseInfo.eventFlag = RinexUtils.parseInt(line, 28, 1);

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

  686.                                if (parseInfo.eventFlag < 2) {
  687.                                    // regular observation
  688.                                    parseInfo.specialRecord = false;
  689.                                    parseInfo.cycleSlip     = false;
  690.                                    final int nbSat         = parseInfo.file.getHeader().getNbSat();
  691.                                    if (nbSat != -1 && parseInfo.nbSatObs > nbSat) {
  692.                                        // we check that the number of Sat in the observation is consistent
  693.                                        throw new OrekitException(OrekitMessages.INCONSISTENT_NUMBER_OF_SATS,
  694.                                                                  parseInfo.lineNumber, parseInfo.name,
  695.                                                                  parseInfo.nbSatObs, nbSat);
  696.                                    }
  697.                                    parseInfo.nextObsStartLineNumber = parseInfo.lineNumber + nbLinesSat;

  698.                                    // read the Receiver Clock offset, if present
  699.                                    parseInfo.rcvrClkOffset = RinexUtils.parseDouble(line, 68, 12);
  700.                                    if (Double.isNaN(parseInfo.rcvrClkOffset)) {
  701.                                        parseInfo.rcvrClkOffset = 0.0;
  702.                                    }

  703.                                } else if (parseInfo.eventFlag < 6) {
  704.                                    // moving antenna / new site occupation / header information / external event
  705.                                    // here, number of sats means number of lines to skip
  706.                                    parseInfo.specialRecord = true;
  707.                                    parseInfo.cycleSlip     = false;
  708.                                    parseInfo.nextObsStartLineNumber = parseInfo.lineNumber + parseInfo.nbSatObs + 1;
  709.                                } else if (parseInfo.eventFlag == 6) {
  710.                                    // cycle slip, we will ignore it during observations parsing
  711.                                    parseInfo.specialRecord = false;
  712.                                    parseInfo.cycleSlip     = true;
  713.                                    parseInfo.nextObsStartLineNumber = parseInfo.lineNumber + nbLinesSat;
  714.                                } else {
  715.                                    // unknown event flag
  716.                                    throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  717.                                                              parseInfo.lineNumber, parseInfo.name, line);
  718.                                }

  719.                                // parse the list of satellites observed
  720.                                parseInfo.satObs.clear();
  721.                                if (!parseInfo.specialRecord) {

  722.                                    // observations epoch
  723.                                    parseInfo.tObs = new AbsoluteDate(RinexUtils.convert2DigitsYear(RinexUtils.parseInt(line, 1, 2)),
  724.                                                                      RinexUtils.parseInt(line,  4, 2),
  725.                                                                      RinexUtils.parseInt(line,  7, 2),
  726.                                                                      RinexUtils.parseInt(line, 10, 2),
  727.                                                                      RinexUtils.parseInt(line, 13, 2),
  728.                                                                      RinexUtils.parseDouble(line, 15, 11),
  729.                                                                      parseInfo.timeScale);

  730.                                    // satellites list
  731.                                    RINEX_2_DATA_SAT_LIST.parsingMethod.parse(line, parseInfo);

  732.                                }

  733.                                // prepare handling of observations for current epoch
  734.                                parseInfo.indexObsSat = 0;
  735.                                parseInfo.observations.clear();

  736.                            },
  737.                            LineParser::first2),

  738.         /** Parser for Rinex 2 special record. */
  739.         RINEX_2_IGNORED_SPECIAL_RECORD(line -> true,
  740.                            (line, parseInfo) -> {
  741.                                // nothing to do
  742.                            },
  743.                            LineParser::ignore2),

  744.         /** Parser for Rinex 2 observation line. */
  745.         RINEX_2_OBSERVATION(line -> true,
  746.                             (line, parseInfo) -> {
  747.                                 final List<ObservationType> types = parseInfo.file.getHeader().getTypeObs().get(parseInfo.file.getHeader().getSatelliteSystem());
  748.                                 for (int index = 0;
  749.                                      parseInfo.observations.size() < types.size() && index < 80;
  750.                                      index += 16) {
  751.                                     final ObservationData observationData;
  752.                                     if (parseInfo.cycleSlip) {
  753.                                         // we are in a cycle slip data block (eventFlag = 6), we just ignore everything
  754.                                         observationData = null;
  755.                                     } else {
  756.                                         // this is a regular observation line
  757.                                         final ObservationType type    = types.get(parseInfo.observations.size());
  758.                                         final double          scaling = getScaling(parseInfo, type, parseInfo.currentSystem);
  759.                                         observationData = new ObservationData(type,
  760.                                                                               scaling * RinexUtils.parseDouble(line, index, 14),
  761.                                                                               RinexUtils.parseInt(line, index + 14, 1),
  762.                                                                               RinexUtils.parseInt(line, index + 15, 1));
  763.                                     }
  764.                                     parseInfo.observations.add(observationData);
  765.                                 }

  766.                                 if (parseInfo.observations.size() == types.size()) {
  767.                                     // we have finished handling observations/cycle slips for one satellite
  768.                                     if (!parseInfo.cycleSlip) {
  769.                                         parseInfo.file.addObservationDataSet(new ObservationDataSet(parseInfo.satObs.get(parseInfo.indexObsSat),
  770.                                                                                                     parseInfo.tObs,
  771.                                                                                                     parseInfo.eventFlag,
  772.                                                                                                     parseInfo.rcvrClkOffset,
  773.                                                                                                     new ArrayList<>(parseInfo.observations)));
  774.                                     }
  775.                                     parseInfo.indexObsSat++;
  776.                                     parseInfo.observations.clear();
  777.                                 }

  778.                             },
  779.                             LineParser::observation2),

  780.         /** Parser for Rinex 3 observation line. */
  781.         RINEX_3_OBSERVATION(line -> true,
  782.                             (line, parseInfo) -> {
  783.                                 final SatelliteSystem system = SatelliteSystem.parseSatelliteSystem(RinexUtils.parseString(line, 0, 1));
  784.                                 final int             prn    = RinexUtils.parseInt(line, 1, 2);
  785.                                 final SatInSystem sat = new SatInSystem(system,
  786.                                                                         system == SatelliteSystem.SBAS ?
  787.                                                                         prn + 100 :
  788.                                                                         (system == SatelliteSystem.QZSS ? prn + 192 : prn));
  789.                                 final List<ObservationType> types = parseInfo.file.getHeader().getTypeObs().get(sat.getSystem());
  790.                                 for (int index = 3;
  791.                                      parseInfo.observations.size() < types.size();
  792.                                      index += 16) {
  793.                                     final ObservationData observationData;
  794.                                     if (parseInfo.specialRecord || parseInfo.cycleSlip) {
  795.                                         // we are in a special record (eventFlag < 6) or in a cycle slip data block (eventFlag = 6), we just ignore everything
  796.                                         observationData = null;
  797.                                     } else {
  798.                                         // this is a regular observation line
  799.                                         final ObservationType type    = types.get(parseInfo.observations.size());
  800.                                         final double          scaling = getScaling(parseInfo, type, sat.getSystem());
  801.                                         observationData = new ObservationData(type,
  802.                                                                               scaling * RinexUtils.parseDouble(line, index, 14),
  803.                                                                               RinexUtils.parseInt(line, index + 14, 1),
  804.                                                                               RinexUtils.parseInt(line, index + 15, 1));
  805.                                     }
  806.                                     parseInfo.observations.add(observationData);
  807.                                 }

  808.                                 if (!(parseInfo.specialRecord || parseInfo.cycleSlip)) {
  809.                                     parseInfo.file.addObservationDataSet(new ObservationDataSet(sat,
  810.                                                                                                 parseInfo.tObs,
  811.                                                                                                 parseInfo.eventFlag,
  812.                                                                                                 parseInfo.rcvrClkOffset,
  813.                                                                                                 new ArrayList<>(parseInfo.observations)));
  814.                                 }
  815.                                 parseInfo.observations.clear();

  816.                             },
  817.                             LineParser::observation3),

  818.         /** Parser for Rinex 3 data first line. */
  819.         RINEX_3_DATA_FIRST(line -> line.startsWith(">"),
  820.                            (line, parseInfo) -> {

  821.                                // flag
  822.                                parseInfo.eventFlag = RinexUtils.parseInt(line, 31, 1);

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

  825.                                if (parseInfo.eventFlag < 2) {
  826.                                    // regular observation
  827.                                    parseInfo.specialRecord = false;
  828.                                    parseInfo.cycleSlip     = false;
  829.                                    final int nbSat         = parseInfo.file.getHeader().getNbSat();
  830.                                    if (nbSat != -1 && parseInfo.nbSatObs > nbSat) {
  831.                                        // we check that the number of Sat in the observation is consistent
  832.                                        throw new OrekitException(OrekitMessages.INCONSISTENT_NUMBER_OF_SATS,
  833.                                                                  parseInfo.lineNumber, parseInfo.name,
  834.                                                                  parseInfo.nbSatObs, nbSat);
  835.                                    }
  836.                                    parseInfo.nextObsStartLineNumber = parseInfo.lineNumber + parseInfo.nbSatObs + 1;

  837.                                    // read the Receiver Clock offset, if present
  838.                                    parseInfo.rcvrClkOffset = RinexUtils.parseDouble(line, 41, 15);
  839.                                    if (Double.isNaN(parseInfo.rcvrClkOffset)) {
  840.                                        parseInfo.rcvrClkOffset = 0.0;
  841.                                    }

  842.                                } else if (parseInfo.eventFlag < 6) {
  843.                                    // moving antenna / new site occupation / header information / external event
  844.                                    // here, number of sats means number of lines to skip
  845.                                    parseInfo.specialRecord = true;
  846.                                    parseInfo.cycleSlip     = false;
  847.                                    parseInfo.nextObsStartLineNumber = parseInfo.lineNumber + parseInfo.nbSatObs + 1;
  848.                                } else if (parseInfo.eventFlag == 6) {
  849.                                    // cycle slip, we will ignore it during observations parsing
  850.                                    parseInfo.specialRecord = false;
  851.                                    parseInfo.cycleSlip     = true;
  852.                                    parseInfo.nextObsStartLineNumber = parseInfo.lineNumber + parseInfo.nbSatObs + 1;
  853.                                } else {
  854.                                    // unknown event flag
  855.                                    throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  856.                                                              parseInfo.lineNumber, parseInfo.name, line);
  857.                                }

  858.                                // parse the list of satellites observed
  859.                                parseInfo.satObs.clear();
  860.                                if (!parseInfo.specialRecord) {

  861.                                    // observations epoch
  862.                                    parseInfo.tObs = new AbsoluteDate(RinexUtils.parseInt(line,  2, 4),
  863.                                                                      RinexUtils.parseInt(line,  7, 2),
  864.                                                                      RinexUtils.parseInt(line, 10, 2),
  865.                                                                      RinexUtils.parseInt(line, 13, 2),
  866.                                                                      RinexUtils.parseInt(line, 16, 2),
  867.                                                                      RinexUtils.parseDouble(line, 18, 11),
  868.                                                                      parseInfo.timeScale);

  869.                                }

  870.                                // prepare handling of observations for current epoch
  871.                                parseInfo.observations.clear();

  872.                            },
  873.                            parseInfo -> Collections.singleton(RINEX_3_OBSERVATION));


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

  876.         /** Parsing method. */
  877.         private final ParsingMethod parsingMethod;

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

  880.         /** Simple constructor.
  881.          * @param canHandle predicate for identifying lines that can be parsed
  882.          * @param parsingMethod parsing method
  883.          * @param allowedNextProvider supplier for allowed parsers for next line
  884.          */
  885.         LineParser(final Predicate<String> canHandle, final ParsingMethod parsingMethod,
  886.                    final Function<ParseInfo, Iterable<LineParser>> allowedNextProvider) {
  887.             this.canHandle           = canHandle;
  888.             this.parsingMethod       = parsingMethod;
  889.             this.allowedNextProvider = allowedNextProvider;
  890.         }

  891.         /** Get the allowed parsers for next lines while parsing comments.
  892.          * @param parseInfo holder for transient data
  893.          * @return allowed parsers for next line
  894.          */
  895.         private static Iterable<LineParser> commentNext(final ParseInfo parseInfo) {
  896.             return parseInfo.headerCompleted ? headerEndNext(parseInfo) : headerNext(parseInfo);
  897.         }

  898.         /** Get the allowed parsers for next lines while parsing Rinex header.
  899.          * @param parseInfo holder for transient data
  900.          * @return allowed parsers for next line
  901.          */
  902.         private static Iterable<LineParser> headerNext(final ParseInfo parseInfo) {
  903.             if (parseInfo.file.getHeader().getFormatVersion() < 3) {
  904.                 // Rinex 2.x header entries
  905.                 return Arrays.asList(PROGRAM, COMMENT, MARKER_NAME, MARKER_NUMBER, MARKER_TYPE, OBSERVER_AGENCY,
  906.                                      REC_NB_TYPE_VERS, ANT_NB_TYPE, APPROX_POSITION_XYZ, ANTENNA_DELTA_H_E_N,
  907.                                      ANTENNA_DELTA_X_Y_Z, ANTENNA_B_SIGHT_XYZ, WAVELENGTH_FACT_L1_2, OBS_SCALE_FACTOR,
  908.                                      CENTER_OF_MASS_XYZ, SYS_NB_TYPES_OF_OBSERV, INTERVAL, TIME_OF_FIRST_OBS, TIME_OF_LAST_OBS,
  909.                                      RCV_CLOCK_OFFS_APPL, LEAP_SECONDS, NB_OF_SATELLITES, PRN_NB_OF_OBS, END);
  910.             } else if (parseInfo.file.getHeader().getFormatVersion() < 4) {
  911.                 // Rinex 3.x header entries
  912.                 return Arrays.asList(PROGRAM, COMMENT, MARKER_NAME, MARKER_NUMBER, MARKER_TYPE, OBSERVER_AGENCY,
  913.                                      REC_NB_TYPE_VERS, ANT_NB_TYPE, APPROX_POSITION_XYZ, ANTENNA_DELTA_H_E_N,
  914.                                      ANTENNA_DELTA_X_Y_Z, ANTENNA_PHASE_CENTER, ANTENNA_B_SIGHT_XYZ, ANTENNA_ZERODIR_AZI,
  915.                                      ANTENNA_ZERODIR_XYZ, CENTER_OF_MASS_XYZ, SYS_NB_TYPES_OF_OBSERV, SIGNAL_STRENGTH_UNIT,
  916.                                      INTERVAL, TIME_OF_FIRST_OBS, TIME_OF_LAST_OBS, RCV_CLOCK_OFFS_APPL,
  917.                                      SYS_DCBS_APPLIED, SYS_PCVS_APPLIED, SYS_SCALE_FACTOR, SYS_PHASE_SHIFT,
  918.                                      GLONASS_SLOT_FRQ_NB, GLONASS_COD_PHS_BIS, LEAP_SECONDS, NB_OF_SATELLITES,
  919.                                      PRN_NB_OF_OBS, END);
  920.             } else {
  921.                 // Rinex 4.x header entries
  922.                 return Arrays.asList(PROGRAM, COMMENT, MARKER_NAME, MARKER_NUMBER, MARKER_TYPE, OBSERVER_AGENCY,
  923.                                      REC_NB_TYPE_VERS, ANT_NB_TYPE, APPROX_POSITION_XYZ, ANTENNA_DELTA_H_E_N,
  924.                                      ANTENNA_DELTA_X_Y_Z, ANTENNA_PHASE_CENTER, ANTENNA_B_SIGHT_XYZ, ANTENNA_ZERODIR_AZI,
  925.                                      ANTENNA_ZERODIR_XYZ, CENTER_OF_MASS_XYZ, DOI, LICENSE, STATION_INFORMATION,
  926.                                      SYS_NB_TYPES_OF_OBSERV, SIGNAL_STRENGTH_UNIT, INTERVAL, TIME_OF_FIRST_OBS, TIME_OF_LAST_OBS,
  927.                                      RCV_CLOCK_OFFS_APPL, SYS_DCBS_APPLIED, SYS_PCVS_APPLIED, SYS_SCALE_FACTOR, SYS_PHASE_SHIFT,
  928.                                      GLONASS_SLOT_FRQ_NB, GLONASS_COD_PHS_BIS, LEAP_SECONDS, NB_OF_SATELLITES,
  929.                                      PRN_NB_OF_OBS, END);
  930.             }
  931.         }

  932.         /** Get the allowed parsers for next lines while parsing header end.
  933.          * @param parseInfo holder for transient data
  934.          * @return allowed parsers for next line
  935.          */
  936.         private static Iterable<LineParser> headerEndNext(final ParseInfo parseInfo) {
  937.             return Collections.singleton(parseInfo.file.getHeader().getFormatVersion() < 3 ?
  938.                                          RINEX_2_DATA_FIRST : RINEX_3_DATA_FIRST);
  939.         }

  940.         /** Get the allowed parsers for next lines while parsing types of observations.
  941.          * @param parseInfo holder for transient data
  942.          * @return allowed parsers for next line
  943.          */
  944.         private static Iterable<LineParser> headerNbTypesObs(final ParseInfo parseInfo) {
  945.             if (parseInfo.typesObs.size() < parseInfo.nbTypes) {
  946.                 return Arrays.asList(COMMENT, SYS_NB_TYPES_OF_OBSERV);
  947.             } else {
  948.                 return headerNext(parseInfo);
  949.             }
  950.         }

  951.         /** Get the allowed parsers for next lines while parsing phase shifts.
  952.          * @param parseInfo holder for transient data
  953.          * @return allowed parsers for next line
  954.          */
  955.         private static Iterable<LineParser> headerPhaseShift(final ParseInfo parseInfo) {
  956.             if (parseInfo.satPhaseShift.size() < parseInfo.phaseShiftNbSat) {
  957.                 return Arrays.asList(COMMENT, SYS_PHASE_SHIFT);
  958.             } else {
  959.                 return headerNext(parseInfo);
  960.             }
  961.         }

  962.         /** Get the allowed parsers for next lines while parsing Rinex 2 observations first lines.
  963.          * @param parseInfo holder for transient data
  964.          * @return allowed parsers for next line
  965.          */
  966.         private static Iterable<LineParser> first2(final ParseInfo parseInfo) {
  967.             if (parseInfo.specialRecord) {
  968.                 return Collections.singleton(RINEX_2_IGNORED_SPECIAL_RECORD);
  969.             } else if (parseInfo.satObs.size() < parseInfo.nbSatObs) {
  970.                 return Collections.singleton(RINEX_2_DATA_SAT_LIST);
  971.             } else {
  972.                 return Collections.singleton(RINEX_2_OBSERVATION);
  973.             }
  974.         }

  975.         /** Get the allowed parsers for next lines while parsing Rinex 2 ignored special records.
  976.          * @param parseInfo holder for transient data
  977.          * @return allowed parsers for next line
  978.          */
  979.         private static Iterable<LineParser> ignore2(final ParseInfo parseInfo) {
  980.             if (parseInfo.lineNumber < parseInfo.nextObsStartLineNumber) {
  981.                 return Collections.singleton(RINEX_2_IGNORED_SPECIAL_RECORD);
  982.             } else {
  983.                 return Arrays.asList(COMMENT, RINEX_2_DATA_FIRST);
  984.             }
  985.         }

  986.         /** Get the allowed parsers for next lines while parsing Rinex 2 observations per se.
  987.          * @param parseInfo holder for transient data
  988.          * @return allowed parsers for next line
  989.          */
  990.         private static Iterable<LineParser> observation2(final ParseInfo parseInfo) {
  991.             if (parseInfo.lineNumber < parseInfo.nextObsStartLineNumber) {
  992.                 return Collections.singleton(RINEX_2_OBSERVATION);
  993.             } else {
  994.                 return Arrays.asList(COMMENT, RINEX_2_DATA_FIRST);
  995.             }
  996.         }

  997.         /** Get the allowed parsers for next lines while parsing Rinex 3 observations.
  998.          * @param parseInfo holder for transient data
  999.          * @return allowed parsers for next line
  1000.          */
  1001.         private static Iterable<LineParser> observation3(final ParseInfo parseInfo) {
  1002.             if (parseInfo.lineNumber < parseInfo.nextObsStartLineNumber) {
  1003.                 return Collections.singleton(RINEX_3_OBSERVATION);
  1004.             } else {
  1005.                 return Arrays.asList(COMMENT, RINEX_3_DATA_FIRST);
  1006.             }
  1007.         }

  1008.         /** Get the scaling factor for an observation.
  1009.          * @param parseInfo holder for transient data
  1010.          * @param type type of observation
  1011.          * @param system satellite system for the observation
  1012.          * @return scaling factor
  1013.          */
  1014.         private static double getScaling(final ParseInfo parseInfo, final ObservationType type,
  1015.                                          final SatelliteSystem system) {

  1016.             for (final ScaleFactorCorrection scaleFactorCorrection :
  1017.                 parseInfo.file.getHeader().getScaleFactorCorrections(system)) {
  1018.                 // check if the next Observation Type to read needs to be scaled
  1019.                 if (scaleFactorCorrection.getTypesObsScaled().contains(type)) {
  1020.                     return 1.0 / scaleFactorCorrection.getCorrection();
  1021.                 }
  1022.             }

  1023.             // no scaling
  1024.             return 1.0;

  1025.         }

  1026.     }

  1027.     /** Parsing method. */
  1028.     @FunctionalInterface
  1029.     private interface ParsingMethod {
  1030.         /** Parse a line.
  1031.          * @param line line to parse
  1032.          * @param parseInfo holder for transient data
  1033.          */
  1034.         void parse(String line, ParseInfo parseInfo);
  1035.     }

  1036. }