RinexNavigationParser.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.navigation;

  18. import java.io.BufferedReader;
  19. import java.io.IOException;
  20. import java.io.Reader;
  21. import java.util.Arrays;
  22. import java.util.Collections;
  23. import java.util.InputMismatchException;
  24. import java.util.function.Function;
  25. import java.util.function.Predicate;

  26. import org.hipparchus.util.FastMath;
  27. import org.orekit.annotation.DefaultDataContext;
  28. import org.orekit.data.DataContext;
  29. import org.orekit.data.DataSource;
  30. import org.orekit.errors.OrekitException;
  31. import org.orekit.errors.OrekitInternalError;
  32. import org.orekit.errors.OrekitMessages;
  33. import org.orekit.files.rinex.utils.parsing.RinexUtils;
  34. import org.orekit.gnss.Frequency;
  35. import org.orekit.gnss.SatelliteSystem;
  36. import org.orekit.gnss.TimeSystem;
  37. import org.orekit.propagation.analytical.gnss.data.AbstractNavigationMessage;
  38. import org.orekit.propagation.analytical.gnss.data.BeidouCivilianNavigationMessage;
  39. import org.orekit.propagation.analytical.gnss.data.BeidouLegacyNavigationMessage;
  40. import org.orekit.propagation.analytical.gnss.data.BeidouSatelliteType;
  41. import org.orekit.propagation.analytical.gnss.data.CivilianNavigationMessage;
  42. import org.orekit.propagation.analytical.gnss.data.GLONASSNavigationMessage;
  43. import org.orekit.propagation.analytical.gnss.data.GPSCivilianNavigationMessage;
  44. import org.orekit.propagation.analytical.gnss.data.GPSLegacyNavigationMessage;
  45. import org.orekit.propagation.analytical.gnss.data.GalileoNavigationMessage;
  46. import org.orekit.propagation.analytical.gnss.data.IRNSSNavigationMessage;
  47. import org.orekit.propagation.analytical.gnss.data.LegacyNavigationMessage;
  48. import org.orekit.propagation.analytical.gnss.data.QZSSCivilianNavigationMessage;
  49. import org.orekit.propagation.analytical.gnss.data.QZSSLegacyNavigationMessage;
  50. import org.orekit.propagation.analytical.gnss.data.SBASNavigationMessage;
  51. import org.orekit.time.AbsoluteDate;
  52. import org.orekit.time.GNSSDate;
  53. import org.orekit.time.TimeScale;
  54. import org.orekit.time.TimeScales;
  55. import org.orekit.utils.Constants;
  56. import org.orekit.utils.units.Unit;

  57. /**
  58.  * Parser for RINEX navigation messages files.
  59.  * <p>
  60.  * This parser handles RINEX version from 2 to 4.00.
  61.  * </p>
  62.  * @see <a href="https://files.igs.org/pub/data/format/rinex2.txt">rinex 2.0</a>
  63.  * @see <a href="https://files.igs.org/pub/data/format/rinex210.txt">rinex 2.10</a>
  64.  * @see <a href="https://files.igs.org/pub/data/format/rinex211.pdf">rinex 2.11</a>
  65.  * @see <a href="https://files.igs.org/pub/data/format/rinex301.pdf"> 3.01 navigation messages file format</a>
  66.  * @see <a href="https://files.igs.org/pub/data/format/rinex302.pdf"> 3.02 navigation messages file format</a>
  67.  * @see <a href="https://files.igs.org/pub/data/format/rinex303.pdf"> 3.03 navigation messages file format</a>
  68.  * @see <a href="https://files.igs.org/pub/data/format/rinex304.pdf"> 3.04 navigation messages file format</a>
  69.  * @see <a href="https://files.igs.org/pub/data/format/rinex305.pdf"> 3.05 navigation messages file format</a>
  70.  * @see <a href="https://files.igs.org/pub/data/format/rinex_4.00.pdf"> 4.00 navigation messages file format</a>
  71.  *
  72.  * @author Bryan Cazabonne
  73.  * @since 11.0
  74.  *
  75.  */
  76. public class RinexNavigationParser {

  77.     /** Converter for positions. */
  78.     private static final Unit KM = Unit.KILOMETRE;

  79.     /** Converter for velocities. */
  80.     private static final Unit KM_PER_S = Unit.parse("km/s");

  81.     /** Converter for accelerations. */
  82.     private static final Unit KM_PER_S2 = Unit.parse("km/s²");;

  83.     /** Converter for velocities. */
  84.     private static final Unit M_PER_S = Unit.parse("m/s");

  85.     /** Converter for clock drift. */
  86.     private static final Unit S_PER_S = Unit.parse("s/s");

  87.     /** Converter for clock drift rate. */
  88.     private static final Unit S_PER_S2 = Unit.parse("s/s²");

  89.     /** Converter for ΔUT₁ first derivative. */
  90.     private static final Unit S_PER_DAY = Unit.parse("s/d");

  91.     /** Converter for ΔUT₁ second derivative. */
  92.     private static final Unit S_PER_DAY2 = Unit.parse("s/d²");

  93.     /** Converter for square root of semi-major axis. */
  94.     private static final Unit SQRT_M = Unit.parse("√m");

  95.     /** Converter for angular rates. */
  96.     private static final Unit RAD_PER_S = Unit.parse("rad/s");;

  97.     /** Converter for angular accelerations. */
  98.     private static final Unit RAD_PER_S2 = Unit.parse("rad/s²");;

  99.     /** Converter for rates of small angle. */
  100.     private static final Unit AS_PER_DAY = Unit.parse("as/d");;

  101.     /** Converter for accelerations of small angles. */
  102.     private static final Unit AS_PER_DAY2 = Unit.parse("as/d²");;

  103.     /** System initials. */
  104.     private static final String INITIALS = "GRECIJS";

  105.     /** Set of time scales. */
  106.     private final TimeScales timeScales;

  107.     /**
  108.      * Constructor.
  109.      * <p>This constructor uses the {@link DataContext#getDefault() default data context}.</p>
  110.      * @see #RinexNavigationParser(TimeScales)
  111.      *
  112.      */
  113.     @DefaultDataContext
  114.     public RinexNavigationParser() {
  115.         this(DataContext.getDefault().getTimeScales());
  116.     }

  117.     /**
  118.      * Constructor.
  119.      * @param timeScales the set of time scales used for parsing dates.
  120.      */
  121.     public RinexNavigationParser(final TimeScales timeScales) {
  122.         this.timeScales = timeScales;
  123.     }

  124.     /**
  125.      * Parse RINEX navigation messages.
  126.      * @param source source providing the data to parse
  127.      * @return a parsed  RINEX navigation messages file
  128.      * @throws IOException if {@code reader} throws one
  129.      */
  130.     public RinexNavigation parse(final DataSource source) throws IOException {

  131.         // initialize internal data structures
  132.         final ParseInfo pi = new ParseInfo(source.getName());

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

  156.         if (!pi.headerParsed) {
  157.             throw new OrekitException(OrekitMessages.UNEXPECTED_END_OF_FILE, source.getName());
  158.         }

  159.         pi.closePendingMessage();

  160.         return pi.file;

  161.     }

  162.     /** Transient data used for parsing a RINEX navigation messages file. */
  163.     private class ParseInfo {

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

  166.         /** Set of time scales for parsing dates. */
  167.         private final TimeScales timeScales;

  168.         /** The corresponding navigation messages file object. */
  169.         private RinexNavigation file;

  170.         /** Number of initial spaces in broadcase orbits lines. */
  171.         private int initialSpaces;

  172.         /** Flag indicating header has been completely parsed. */
  173.         private boolean headerParsed;

  174.         /** Flag indicating the distinction between "alpha" and "beta" ionospheric coefficients. */
  175.         private boolean isIonosphereAlphaInitialized;

  176.         /** Satellite system line parser. */
  177.         private SatelliteSystemLineParser systemLineParser;

  178.         /** Current global line number. */
  179.         private int lineNumber;

  180.         /** Current line number within the navigation message. */
  181.         private int messageLineNumber;

  182.         /** Container for GPS navigation message. */
  183.         private GPSLegacyNavigationMessage gpsLNav;

  184.         /** Container for GPS navigation message. */
  185.         private GPSCivilianNavigationMessage gpsCNav;

  186.         /** Container for Galileo navigation message. */
  187.         private GalileoNavigationMessage galileoNav;

  188.         /** Container for Beidou navigation message. */
  189.         private BeidouLegacyNavigationMessage beidouLNav;

  190.         /** Container for Beidou navigation message. */
  191.         private BeidouCivilianNavigationMessage beidouCNav;

  192.         /** Container for QZSS navigation message. */
  193.         private QZSSLegacyNavigationMessage qzssLNav;

  194.         /** Container for QZSS navigation message. */
  195.         private QZSSCivilianNavigationMessage qzssCNav;

  196.         /** Container for IRNSS navigation message. */
  197.         private IRNSSNavigationMessage irnssNav;

  198.         /** Container for GLONASS navigation message. */
  199.         private GLONASSNavigationMessage glonassNav;

  200.         /** Container for SBAS navigation message. */
  201.         private SBASNavigationMessage sbasNav;

  202.         /** Container for System Time Offset message. */
  203.         private SystemTimeOffsetMessage sto;

  204.         /** Container for Earth Orientation Parameter message. */
  205.         private EarthOrientationParameterMessage eop;

  206.         /** Container for ionosphere Klobuchar message. */
  207.         private IonosphereKlobucharMessage klobuchar;

  208.         /** Container for ionosphere Nequick-G message. */
  209.         private IonosphereNequickGMessage nequickG;

  210.         /** Container for ionosphere BDGIM message. */
  211.         private IonosphereBDGIMMessage bdgim;

  212.         /** Constructor, build the ParseInfo object.
  213.          * @param name name of the data source
  214.          */
  215.         ParseInfo(final String name) {
  216.             // Initialize default values for fields
  217.             this.name                         = name;
  218.             this.timeScales                   = RinexNavigationParser.this.timeScales;
  219.             this.isIonosphereAlphaInitialized = false;
  220.             this.file                         = new RinexNavigation();

  221.         }

  222.         /** Ensure navigation message has been closed.
  223.          */
  224.         void closePendingMessage() {
  225.             if (systemLineParser != null) {
  226.                 systemLineParser.closeMessage(this);
  227.                 systemLineParser = null;
  228.             }

  229.         }

  230.     }

  231.     /** Parsers for specific lines. */
  232.     private enum LineParser {

  233.         /** Parser for version, file type and satellite system. */
  234.         HEADER_VERSION(line -> RinexUtils.matchesLabel(line, "RINEX VERSION / TYPE"),
  235.                        (line, pi) -> {
  236.                            RinexUtils.parseVersionFileTypeSatelliteSystem(line, pi.name, pi.file.getHeader(),
  237.                                                                                     2.0, 2.01, 2.10, 2.11,
  238.                                                                                     3.01, 3.02, 3.03, 3.04, 3.05,
  239.                                                                                     4.00);
  240.                            pi.initialSpaces = pi.file.getHeader().getFormatVersion() < 3.0 ? 3 : 4;
  241.                        },
  242.                        LineParser::headerNext),

  243.         /** Parser for generating program and emitting agency. */
  244.         HEADER_PROGRAM(line -> RinexUtils.matchesLabel(line, "PGM / RUN BY / DATE"),
  245.                        (line, pi) -> RinexUtils.parseProgramRunByDate(line, pi.lineNumber, pi.name, pi.timeScales, pi.file.getHeader()),
  246.                        LineParser::headerNext),

  247.         /** Parser for comments. */
  248.         HEADER_COMMENT(line -> RinexUtils.matchesLabel(line, "COMMENT"),
  249.                        (line, pi) -> RinexUtils.parseComment(pi.lineNumber, line, pi.file),
  250.                        LineParser::headerNext),

  251.         /** Parser for ionospheric correction parameters. */
  252.         HEADER_ION_ALPHA(line -> RinexUtils.matchesLabel(line, "ION ALPHA"),
  253.                          (line, pi) -> {

  254.                              pi.file.getHeader().setIonosphericCorrectionType(IonosphericCorrectionType.GPS);

  255.                              // Read coefficients
  256.                              final double[] parameters = new double[4];
  257.                              parameters[0] = RinexUtils.parseDouble(line, 2,  12);
  258.                              parameters[1] = RinexUtils.parseDouble(line, 14, 12);
  259.                              parameters[2] = RinexUtils.parseDouble(line, 26, 12);
  260.                              parameters[3] = RinexUtils.parseDouble(line, 38, 12);
  261.                              pi.file.setKlobucharAlpha(parameters);
  262.                              pi.isIonosphereAlphaInitialized = true;

  263.                          },
  264.                          LineParser::headerNext),

  265.         /** Parser for ionospheric correction parameters. */
  266.         HEADER_ION_BETA(line -> RinexUtils.matchesLabel(line, "ION BETA"),
  267.                         (line, pi) -> {

  268.                             pi.file.getHeader().setIonosphericCorrectionType(IonosphericCorrectionType.GPS);

  269.                             // Read coefficients
  270.                             final double[] parameters = new double[4];
  271.                             parameters[0] = RinexUtils.parseDouble(line, 2,  12);
  272.                             parameters[1] = RinexUtils.parseDouble(line, 14, 12);
  273.                             parameters[2] = RinexUtils.parseDouble(line, 26, 12);
  274.                             parameters[3] = RinexUtils.parseDouble(line, 38, 12);
  275.                             pi.file.setKlobucharBeta(parameters);

  276.                         },
  277.                         LineParser::headerNext),

  278.         /** Parser for ionospheric correction parameters. */
  279.         HEADER_IONOSPHERIC(line -> RinexUtils.matchesLabel(line, "IONOSPHERIC CORR"),
  280.                            (line, pi) -> {

  281.                                // ionospheric correction type
  282.                                final IonosphericCorrectionType ionoType =
  283.                                                IonosphericCorrectionType.valueOf(RinexUtils.parseString(line, 0, 3));
  284.                                pi.file.getHeader().setIonosphericCorrectionType(ionoType);

  285.                                // Read coefficients
  286.                                final double[] parameters = new double[4];
  287.                                parameters[0] = RinexUtils.parseDouble(line, 5,  12);
  288.                                parameters[1] = RinexUtils.parseDouble(line, 17, 12);
  289.                                parameters[2] = RinexUtils.parseDouble(line, 29, 12);
  290.                                parameters[3] = RinexUtils.parseDouble(line, 41, 12);

  291.                                // Verify if we are parsing Galileo ionospheric parameters
  292.                                if (ionoType == IonosphericCorrectionType.GAL) {

  293.                                    // We are parsing Galileo ionospheric parameters
  294.                                    pi.file.setNeQuickAlpha(parameters);

  295.                                } else {
  296.                                    // We are parsing Klobuchar ionospheric parameters

  297.                                    // Verify if we are parsing "alpha" or "beta" ionospheric parameters
  298.                                    if (pi.isIonosphereAlphaInitialized) {

  299.                                        // Ionospheric "beta" parameters
  300.                                        pi.file.setKlobucharBeta(parameters);

  301.                                    } else {

  302.                                        // Ionospheric "alpha" parameters
  303.                                        pi.file.setKlobucharAlpha(parameters);

  304.                                        // Set the flag to true
  305.                                        pi.isIonosphereAlphaInitialized = true;

  306.                                    }

  307.                                }

  308.                            },
  309.                            LineParser::headerNext),

  310.         /** Parser for corrections to transform the system time to UTC or to other time systems. */
  311.         HEADER_DELTA_UTC(line -> RinexUtils.matchesLabel(line, "DELTA-UTC: A0,A1,T,W"),
  312.                          (line, pi) -> {
  313.                              // Read fields
  314.                              final double a0      = RinexUtils.parseDouble(line, 3,  19);
  315.                              final double a1      = RinexUtils.parseDouble(line, 22, 19);
  316.                              final int    refTime = RinexUtils.parseInt(line, 41, 9);
  317.                              final int    refWeek = RinexUtils.parseInt(line, 50, 9);

  318.                              // convert date
  319.                              final SatelliteSystem satSystem = pi.file.getHeader().getSatelliteSystem();
  320.                              final AbsoluteDate    date      = new GNSSDate(refWeek, refTime, satSystem, pi.timeScales).getDate();

  321.                              // Add to the list
  322.                              final TimeSystemCorrection tsc = new TimeSystemCorrection("GPUT", date, a0, a1);
  323.                              pi.file.getHeader().addTimeSystemCorrections(tsc);
  324.                          },
  325.                          LineParser::headerNext),

  326.         /** Parser for corrections to transform the GLONASS system time to UTC or to other time systems. */
  327.         HEADER_CORR_SYSTEM_TIME(line -> RinexUtils.matchesLabel(line, "CORR TO SYSTEM TIME"),
  328.                          (line, pi) -> {
  329.                              // Read fields
  330.                              final int year        = RinexUtils.parseInt(line,  0, 6);
  331.                              final int month       = RinexUtils.parseInt(line,  6, 6);
  332.                              final int day         = RinexUtils.parseInt(line, 12, 6);
  333.                              final double minusTau = RinexUtils.parseDouble(line, 21, 19);

  334.                              // convert date
  335.                              final SatelliteSystem satSystem = pi.file.getHeader().getSatelliteSystem();
  336.                              final TimeScale       timeScale = satSystem.getObservationTimeScale().getTimeScale(pi.timeScales);
  337.                              final AbsoluteDate    date      = new AbsoluteDate(year, month, day, timeScale);

  338.                              // Add to the list
  339.                              final TimeSystemCorrection tsc = new TimeSystemCorrection("GLUT", date, minusTau, 0.0);
  340.                              pi.file.getHeader().addTimeSystemCorrections(tsc);

  341.                          },
  342.                          LineParser::headerNext),

  343.         /** Parser for corrections to transform the system time to UTC or to other time systems. */
  344.         HEADER_TIME(line -> RinexUtils.matchesLabel(line, "TIME SYSTEM CORR"),
  345.                     (line, pi) -> {

  346.                         // Read fields
  347.                         final String type    = RinexUtils.parseString(line, 0,  4);
  348.                         final double a0      = RinexUtils.parseDouble(line, 5,  17);
  349.                         final double a1      = RinexUtils.parseDouble(line, 22, 16);
  350.                         final int    refTime = RinexUtils.parseInt(line, 38, 7);
  351.                         final int    refWeek = RinexUtils.parseInt(line, 46, 5);

  352.                         // convert date
  353.                         final SatelliteSystem satSystem = pi.file.getHeader().getSatelliteSystem();
  354.                         final AbsoluteDate    date;
  355.                         if (satSystem == SatelliteSystem.GLONASS) {
  356.                             date = null;
  357.                         } else if (satSystem == SatelliteSystem.BEIDOU) {
  358.                             date = new GNSSDate(refWeek, refTime, satSystem, pi.timeScales).getDate();
  359.                         } else {
  360.                             // all other systems are converted to GPS week in Rinex files!
  361.                             date = new GNSSDate(refWeek, refTime, SatelliteSystem.GPS, pi.timeScales).getDate();
  362.                         }

  363.                         // Add to the list
  364.                         final TimeSystemCorrection tsc = new TimeSystemCorrection(type, date, a0, a1);
  365.                         pi.file.getHeader().addTimeSystemCorrections(tsc);

  366.                     },
  367.                     LineParser::headerNext),

  368.         /** Parser for leap seconds. */
  369.         HEADER_LEAP_SECONDS(line -> RinexUtils.matchesLabel(line, "LEAP SECONDS"),
  370.                             (line, pi) -> pi.file.getHeader().setNumberOfLeapSeconds(RinexUtils.parseInt(line, 0, 6)),
  371.                             LineParser::headerNext),

  372.         /** Parser for DOI.
  373.          * @since 12.0
  374.          */
  375.         HEADER_DOI(line -> RinexUtils.matchesLabel(line, "DOI"),
  376.                    (line, pi) -> pi.file.getHeader().setDoi(RinexUtils.parseString(line, 0, RinexUtils.LABEL_INDEX)),
  377.                    LineParser::headerNext),

  378.         /** Parser for license.
  379.          * @since 12.0
  380.          */
  381.         HEADER_LICENSE(line -> RinexUtils.matchesLabel(line, "LICENSE OF USE"),
  382.                        (line, pi) -> pi.file.getHeader().setLicense(RinexUtils.parseString(line, 0, RinexUtils.LABEL_INDEX)),
  383.                        LineParser::headerNext),

  384.         /** Parser for stationInformation.
  385.          * @since 12.0
  386.          */
  387.         HEADER_STATION_INFORMATION(line -> RinexUtils.matchesLabel(line, "STATION INFORMATION"),
  388.                                    (line, pi) -> pi.file.getHeader().setStationInformation(RinexUtils.parseString(line, 0, RinexUtils.LABEL_INDEX)),
  389.                                    LineParser::headerNext),

  390.         /** Parser for merged files.
  391.          * @since 12.0
  392.          */
  393.         HEADER_MERGED_FILE(line -> RinexUtils.matchesLabel(line, "MERGED FILE"),
  394.                            (line, pi) -> pi.file.getHeader().setMergedFiles(RinexUtils.parseInt(line, 0, 9)),
  395.                            LineParser::headerNext),

  396.        /** Parser for the end of header. */
  397.         HEADER_END(line -> RinexUtils.matchesLabel(line, "END OF HEADER"),
  398.                    (line, pi) -> {
  399.                        // get rinex format version
  400.                        final RinexNavigationHeader header = pi.file.getHeader();
  401.                        final double version = header.getFormatVersion();

  402.                        // check mandatory header fields
  403.                        if (header.getRunByName() == null ||
  404.                            version >= 4 && header.getNumberOfLeapSeconds() < 0) {
  405.                            throw new OrekitException(OrekitMessages.INCOMPLETE_HEADER, pi.name);
  406.                        }

  407.                        pi.headerParsed = true;

  408.                    },
  409.                    LineParser::navigationNext),

  410.         /** Parser for navigation message space vehicle epoch and clock. */
  411.         NAVIGATION_SV_EPOCH_CLOCK_RINEX_2(line -> true,
  412.                                           (line, pi) -> {

  413.                                               // Set the line number to 0
  414.                                               pi.messageLineNumber = 0;

  415.                                               // Initialize parser
  416.                                               pi.closePendingMessage();
  417.                                               pi.systemLineParser = SatelliteSystemLineParser.getParser(pi.file.getHeader().getSatelliteSystem(),
  418.                                                                                                         null, pi, line);

  419.                                               pi.systemLineParser.parseSvEpochSvClockLine(line, pi);

  420.                                           },
  421.                                           LineParser::navigationNext),

  422.         /** Parser for navigation message space vehicle epoch and clock. */
  423.         NAVIGATION_SV_EPOCH_CLOCK(line -> INITIALS.indexOf(line.charAt(0)) >= 0,
  424.                                   (line, pi) -> {

  425.                                       // Set the line number to 0
  426.                                       pi.messageLineNumber = 0;

  427.                                       if (pi.file.getHeader().getFormatVersion() < 4) {
  428.                                           // Current satellite system
  429.                                           final SatelliteSystem system = SatelliteSystem.parseSatelliteSystem(RinexUtils.parseString(line, 0, 1));

  430.                                           // Initialize parser
  431.                                           pi.closePendingMessage();
  432.                                           pi.systemLineParser = SatelliteSystemLineParser.getParser(system, null, pi, line);
  433.                                       }

  434.                                       // Read first line
  435.                                       pi.systemLineParser.parseSvEpochSvClockLine(line, pi);

  436.                                   },
  437.                                   LineParser::navigationNext),

  438.         /** Parser for navigation message type. */
  439.         EPH_TYPE(line -> line.startsWith("> EPH"),
  440.                  (line, pi) -> {
  441.                      final SatelliteSystem system = SatelliteSystem.parseSatelliteSystem(RinexUtils.parseString(line, 6, 1));
  442.                      final String          type   = RinexUtils.parseString(line, 10, 4);
  443.                      pi.closePendingMessage();
  444.                      pi.systemLineParser = SatelliteSystemLineParser.getParser(system, type, pi, line);
  445.                  },
  446.                  pi -> Collections.singleton(NAVIGATION_SV_EPOCH_CLOCK)),

  447.         /** Parser for broadcast orbit. */
  448.         BROADCAST_ORBIT(line -> line.startsWith("   "),
  449.                         (line, pi) -> {
  450.                             switch (++pi.messageLineNumber) {
  451.                                 case 1: pi.systemLineParser.parseFirstBroadcastOrbit(line, pi);
  452.                                 break;
  453.                                 case 2: pi.systemLineParser.parseSecondBroadcastOrbit(line, pi);
  454.                                 break;
  455.                                 case 3: pi.systemLineParser.parseThirdBroadcastOrbit(line, pi);
  456.                                 break;
  457.                                 case 4: pi.systemLineParser.parseFourthBroadcastOrbit(line, pi);
  458.                                 break;
  459.                                 case 5: pi.systemLineParser.parseFifthBroadcastOrbit(line, pi);
  460.                                 break;
  461.                                 case 6: pi.systemLineParser.parseSixthBroadcastOrbit(line, pi);
  462.                                 break;
  463.                                 case 7: pi.systemLineParser.parseSeventhBroadcastOrbit(line, pi);
  464.                                 break;
  465.                                 case 8: pi.systemLineParser.parseEighthBroadcastOrbit(line, pi);
  466.                                 break;
  467.                                 case 9: pi.systemLineParser.parseNinthBroadcastOrbit(line, pi);
  468.                                 break;
  469.                                 default:
  470.                                     // this should never happen
  471.                                     throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  472.                                                               pi.lineNumber, pi.name, line);
  473.                             }

  474.                         },
  475.                         LineParser::navigationNext),

  476.         /** Parser for system time offset message model. */
  477.         STO_LINE_1(line -> true,
  478.                    (line, pi) -> {
  479.                        pi.sto.setTransmissionTime(Unit.SECOND.toSI(RinexUtils.parseDouble(line,  4, 19)));
  480.                        pi.sto.setA0(Unit.SECOND.toSI(RinexUtils.parseDouble(line, 23, 19)));
  481.                        pi.sto.setA1(S_PER_S.toSI(RinexUtils.parseDouble(line, 42, 19)));
  482.                        pi.sto.setA2(S_PER_S2.toSI(RinexUtils.parseDouble(line, 61, 19)));
  483.                        pi.file.addSystemTimeOffset(pi.sto);
  484.                        pi.sto = null;
  485.                    },
  486.                    LineParser::navigationNext),

  487.         /** Parser for system time offset message space vehicle epoch and clock. */
  488.         STO_SV_EPOCH_CLOCK(line -> true,
  489.                            (line, pi) -> {

  490.                                pi.sto.setDefinedTimeSystem(TimeSystem.parseTwoLettersCode(RinexUtils.parseString(line, 24, 2)));
  491.                                pi.sto.setReferenceTimeSystem(TimeSystem.parseTwoLettersCode(RinexUtils.parseString(line, 26, 2)));
  492.                                final String sbas = RinexUtils.parseString(line, 43, 18);
  493.                                pi.sto.setSbasId(sbas.length() > 0 ? SbasId.valueOf(sbas) : null);
  494.                                final String utc = RinexUtils.parseString(line, 62, 18);
  495.                                pi.sto.setUtcId(utc.length() > 0 ? UtcId.parseUtcId(utc) : null);

  496.                                // TODO is the reference date relative to one or the other time scale?
  497.                                final int year  = RinexUtils.parseInt(line, 4, 4);
  498.                                final int month = RinexUtils.parseInt(line, 9, 2);
  499.                                final int day   = RinexUtils.parseInt(line, 12, 2);
  500.                                final int hours = RinexUtils.parseInt(line, 15, 2);
  501.                                final int min   = RinexUtils.parseInt(line, 18, 2);
  502.                                final int sec   = RinexUtils.parseInt(line, 21, 2);
  503.                                pi.sto.setReferenceEpoch(new AbsoluteDate(year, month, day, hours, min, sec,
  504.                                                                          pi.sto.getDefinedTimeSystem().getTimeScale(pi.timeScales)));

  505.                            },
  506.                            pi -> Collections.singleton(STO_LINE_1)),

  507.         /** Parser for system time offset message type. */
  508.         STO_TYPE(line -> line.startsWith("> STO"),
  509.                  (line, pi) -> {
  510.                      pi.closePendingMessage();
  511.                      pi.sto = new SystemTimeOffsetMessage(SatelliteSystem.parseSatelliteSystem(RinexUtils.parseString(line, 6, 1)),
  512.                                                           RinexUtils.parseInt(line, 7, 2),
  513.                                                           RinexUtils.parseString(line, 10, 4));
  514.                  },
  515.                  pi -> Collections.singleton(STO_SV_EPOCH_CLOCK)),

  516.         /** Parser for Earth orientation parameter message model. */
  517.         EOP_LINE_2(line -> true,
  518.                    (line, pi) -> {
  519.                        pi.eop.setTransmissionTime(Unit.SECOND.toSI(RinexUtils.parseDouble(line,  4, 19)));
  520.                        pi.eop.setDut1(Unit.SECOND.toSI(RinexUtils.parseDouble(line, 23, 19)));
  521.                        pi.eop.setDut1Dot(S_PER_DAY.toSI(RinexUtils.parseDouble(line, 42, 19)));
  522.                        pi.eop.setDut1DotDot(S_PER_DAY2.toSI(RinexUtils.parseDouble(line, 61, 19)));
  523.                        pi.file.addEarthOrientationParameter(pi.eop);
  524.                        pi.eop = null;
  525.                    },
  526.                    LineParser::navigationNext),

  527.         /** Parser for Earth orientation parameter message model. */
  528.         EOP_LINE_1(line -> true,
  529.                    (line, pi) -> {
  530.                        pi.eop.setYp(Unit.ARC_SECOND.toSI(RinexUtils.parseDouble(line, 23, 19)));
  531.                        pi.eop.setYpDot(AS_PER_DAY.toSI(RinexUtils.parseDouble(line, 42, 19)));
  532.                        pi.eop.setYpDotDot(AS_PER_DAY2.toSI(RinexUtils.parseDouble(line, 61, 19)));
  533.                    },
  534.                    pi -> Collections.singleton(EOP_LINE_2)),

  535.         /** Parser for Earth orientation parameter message space vehicle epoch and clock. */
  536.         EOP_SV_EPOCH_CLOCK(line -> true,
  537.                            (line, pi) -> {
  538.                                final int year  = RinexUtils.parseInt(line, 4, 4);
  539.                                final int month = RinexUtils.parseInt(line, 9, 2);
  540.                                final int day   = RinexUtils.parseInt(line, 12, 2);
  541.                                final int hours = RinexUtils.parseInt(line, 15, 2);
  542.                                final int min   = RinexUtils.parseInt(line, 18, 2);
  543.                                final int sec   = RinexUtils.parseInt(line, 21, 2);
  544.                                pi.eop.setReferenceEpoch(new AbsoluteDate(year, month, day, hours, min, sec,
  545.                                                                          pi.eop.getSystem().getObservationTimeScale().getTimeScale(pi.timeScales)));
  546.                                pi.eop.setXp(Unit.ARC_SECOND.toSI(RinexUtils.parseDouble(line, 23, 19)));
  547.                                pi.eop.setXpDot(AS_PER_DAY.toSI(RinexUtils.parseDouble(line, 42, 19)));
  548.                                pi.eop.setXpDotDot(AS_PER_DAY2.toSI(RinexUtils.parseDouble(line, 61, 19)));
  549.                            },
  550.                            pi -> Collections.singleton(EOP_LINE_1)),

  551.         /** Parser for Earth orientation parameter message type. */
  552.         EOP_TYPE(line -> line.startsWith("> EOP"),
  553.                  (line, pi) -> {
  554.                      pi.closePendingMessage();
  555.                      pi.eop = new EarthOrientationParameterMessage(SatelliteSystem.parseSatelliteSystem(RinexUtils.parseString(line, 6, 1)),
  556.                                                                    RinexUtils.parseInt(line, 7, 2),
  557.                                                                    RinexUtils.parseString(line, 10, 4));
  558.                  },
  559.                  pi -> Collections.singleton(EOP_SV_EPOCH_CLOCK)),

  560.         /** Parser for ionosphere Klobuchar message model. */
  561.         KLOBUCHAR_LINE_2(line -> true,
  562.                          (line, pi) -> {
  563.                              pi.klobuchar.setBetaI(3, IonosphereKlobucharMessage.S_PER_SC_N[3].toSI(RinexUtils.parseDouble(line,  4, 19)));
  564.                              pi.klobuchar.setRegionCode(RinexUtils.parseDouble(line, 23, 19) < 0.5 ?
  565.                                                         RegionCode.WIDE_AREA : RegionCode.JAPAN);
  566.                              pi.file.addKlobucharMessage(pi.klobuchar);
  567.                              pi.klobuchar = null;
  568.                          },
  569.                          LineParser::navigationNext),

  570.         /** Parser for ionosphere Klobuchar message model. */
  571.         KLOBUCHAR_LINE_1(line -> true,
  572.                          (line, pi) -> {
  573.                              pi.klobuchar.setAlphaI(3, IonosphereKlobucharMessage.S_PER_SC_N[3].toSI(RinexUtils.parseDouble(line,  4, 19)));
  574.                              pi.klobuchar.setBetaI(0, IonosphereKlobucharMessage.S_PER_SC_N[0].toSI(RinexUtils.parseDouble(line, 23, 19)));
  575.                              pi.klobuchar.setBetaI(1, IonosphereKlobucharMessage.S_PER_SC_N[1].toSI(RinexUtils.parseDouble(line, 42, 19)));
  576.                              pi.klobuchar.setBetaI(2, IonosphereKlobucharMessage.S_PER_SC_N[2].toSI(RinexUtils.parseDouble(line, 61, 19)));
  577.                          },
  578.                          pi -> Collections.singleton(KLOBUCHAR_LINE_2)),

  579.         /** Parser for ionosphere Klobuchar message model. */
  580.         KLOBUCHAR_LINE_0(line -> true,
  581.                          (line, pi) -> {
  582.                              final int year  = RinexUtils.parseInt(line, 4, 4);
  583.                              final int month = RinexUtils.parseInt(line, 9, 2);
  584.                              final int day   = RinexUtils.parseInt(line, 12, 2);
  585.                              final int hours = RinexUtils.parseInt(line, 15, 2);
  586.                              final int min   = RinexUtils.parseInt(line, 18, 2);
  587.                              final int sec   = RinexUtils.parseInt(line, 21, 2);
  588.                              pi.klobuchar.setTransmitTime(new AbsoluteDate(year, month, day, hours, min, sec,
  589.                                                                            pi.klobuchar.getSystem().getObservationTimeScale().getTimeScale(pi.timeScales)));
  590.                              pi.klobuchar.setAlphaI(0, IonosphereKlobucharMessage.S_PER_SC_N[0].toSI(RinexUtils.parseDouble(line, 23, 19)));
  591.                              pi.klobuchar.setAlphaI(1, IonosphereKlobucharMessage.S_PER_SC_N[1].toSI(RinexUtils.parseDouble(line, 42, 19)));
  592.                              pi.klobuchar.setAlphaI(2, IonosphereKlobucharMessage.S_PER_SC_N[2].toSI(RinexUtils.parseDouble(line, 61, 19)));
  593.                          },
  594.                          pi -> Collections.singleton(KLOBUCHAR_LINE_1)),

  595.         /** Parser for ionosphere Nequick-G message model. */
  596.         NEQUICK_LINE_1(line -> true,
  597.                        (line, pi) -> {
  598.                            pi.nequickG.setFlags((int) FastMath.rint(RinexUtils.parseDouble(line, 4, 19)));
  599.                            pi.file.addNequickGMessage(pi.nequickG);
  600.                            pi.nequickG = null;
  601.                        },
  602.                        LineParser::navigationNext),

  603.         /** Parser for ionosphere Nequick-G message model. */
  604.         NEQUICK_LINE_0(line -> true,
  605.                        (line, pi) -> {
  606.                            final int year  = RinexUtils.parseInt(line, 4, 4);
  607.                            final int month = RinexUtils.parseInt(line, 9, 2);
  608.                            final int day   = RinexUtils.parseInt(line, 12, 2);
  609.                            final int hours = RinexUtils.parseInt(line, 15, 2);
  610.                            final int min   = RinexUtils.parseInt(line, 18, 2);
  611.                            final int sec   = RinexUtils.parseInt(line, 21, 2);
  612.                            pi.nequickG.setTransmitTime(new AbsoluteDate(year, month, day, hours, min, sec,
  613.                                                                         pi.nequickG.getSystem().getObservationTimeScale().getTimeScale(pi.timeScales)));
  614.                            pi.nequickG.setAi0(IonosphereNequickGMessage.SFU.toSI(RinexUtils.parseDouble(line, 23, 19)));
  615.                            pi.nequickG.setAi1(IonosphereNequickGMessage.SFU_PER_DEG.toSI(RinexUtils.parseDouble(line, 42, 19)));
  616.                            pi.nequickG.setAi2(IonosphereNequickGMessage.SFU_PER_DEG2.toSI(RinexUtils.parseDouble(line, 61, 19)));
  617.                        },
  618.                        pi -> Collections.singleton(NEQUICK_LINE_1)),

  619.         /** Parser for ionosphere BDGIM message model. */
  620.         BDGIM_LINE_2(line -> true,
  621.                      (line, pi) -> {
  622.                          pi.bdgim.setAlphaI(7, Unit.TOTAL_ELECTRON_CONTENT_UNIT.toSI(RinexUtils.parseDouble(line,  4, 19)));
  623.                          pi.bdgim.setAlphaI(8, Unit.TOTAL_ELECTRON_CONTENT_UNIT.toSI(RinexUtils.parseDouble(line, 23, 19)));
  624.                          pi.file.addBDGIMMessage(pi.bdgim);
  625.                          pi.bdgim = null;
  626.                      },
  627.                      LineParser::navigationNext),

  628.         /** Parser for ionosphere BDGIM message model. */
  629.         BDGIM_LINE_1(line -> true,
  630.                      (line, pi) -> {
  631.                          pi.bdgim.setAlphaI(3, Unit.TOTAL_ELECTRON_CONTENT_UNIT.toSI(RinexUtils.parseDouble(line,  4, 19)));
  632.                          pi.bdgim.setAlphaI(4, Unit.TOTAL_ELECTRON_CONTENT_UNIT.toSI(RinexUtils.parseDouble(line, 23, 19)));
  633.                          pi.bdgim.setAlphaI(5, Unit.TOTAL_ELECTRON_CONTENT_UNIT.toSI(RinexUtils.parseDouble(line, 42, 19)));
  634.                          pi.bdgim.setAlphaI(6, Unit.TOTAL_ELECTRON_CONTENT_UNIT.toSI(RinexUtils.parseDouble(line, 61, 19)));
  635.                      },
  636.                      pi -> Collections.singleton(BDGIM_LINE_2)),

  637.         /** Parser for ionosphere BDGIM message model. */
  638.         BDGIM_LINE_0(line -> true,
  639.                      (line, pi) -> {
  640.                          final int year  = RinexUtils.parseInt(line, 4, 4);
  641.                          final int month = RinexUtils.parseInt(line, 9, 2);
  642.                          final int day   = RinexUtils.parseInt(line, 12, 2);
  643.                          final int hours = RinexUtils.parseInt(line, 15, 2);
  644.                          final int min   = RinexUtils.parseInt(line, 18, 2);
  645.                          final int sec   = RinexUtils.parseInt(line, 21, 2);
  646.                          pi.bdgim.setTransmitTime(new AbsoluteDate(year, month, day, hours, min, sec,
  647.                                                                    pi.bdgim.getSystem().getObservationTimeScale().getTimeScale(pi.timeScales)));
  648.                          pi.bdgim.setAlphaI(0, Unit.TOTAL_ELECTRON_CONTENT_UNIT.toSI(RinexUtils.parseDouble(line, 23, 19)));
  649.                          pi.bdgim.setAlphaI(1, Unit.TOTAL_ELECTRON_CONTENT_UNIT.toSI(RinexUtils.parseDouble(line, 42, 19)));
  650.                          pi.bdgim.setAlphaI(2, Unit.TOTAL_ELECTRON_CONTENT_UNIT.toSI(RinexUtils.parseDouble(line, 61, 19)));
  651.                      },
  652.                      pi -> Collections.singleton(BDGIM_LINE_1)),

  653.         /** Parser for ionosphere message type. */
  654.         IONO_TYPE(line -> line.startsWith("> ION"),
  655.                   (line, pi) -> {
  656.                       pi.closePendingMessage();
  657.                       final SatelliteSystem system = SatelliteSystem.parseSatelliteSystem(RinexUtils.parseString(line, 6, 1));
  658.                       final int             prn    = RinexUtils.parseInt(line, 7, 2);
  659.                       final String          type   = RinexUtils.parseString(line, 10, 4);
  660.                       if (system == SatelliteSystem.GALILEO) {
  661.                           pi.nequickG = new IonosphereNequickGMessage(system, prn, type);
  662.                       } else {
  663.                           // in Rinex 4.00, tables A32 and A34 are ambiguous as both seem to apply
  664.                           // to Beidou CNVX messages, we consider BDGIM is the proper model in this case
  665.                           if (system == SatelliteSystem.BEIDOU && "CNVX".equals(type)) {
  666.                               pi.bdgim = new IonosphereBDGIMMessage(system, prn, type);
  667.                           } else {
  668.                               pi.klobuchar = new IonosphereKlobucharMessage(system, prn, type);
  669.                           }
  670.                       }
  671.                   },
  672.                   pi -> Collections.singleton(pi.nequickG != null ? NEQUICK_LINE_0 : (pi.bdgim != null ? BDGIM_LINE_0 : KLOBUCHAR_LINE_0)));

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

  675.         /** Parsing method. */
  676.         private final ParsingMethod parsingMethod;

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

  679.         /** Simple constructor.
  680.          * @param canHandle predicate for identifying lines that can be parsed
  681.          * @param parsingMethod parsing method
  682.          * @param allowedNextProvider supplier for allowed parsers for next line
  683.          */
  684.         LineParser(final Predicate<String> canHandle, final ParsingMethod parsingMethod,
  685.                    final Function<ParseInfo, Iterable<LineParser>> allowedNextProvider) {
  686.             this.canHandle           = canHandle;
  687.             this.parsingMethod       = parsingMethod;
  688.             this.allowedNextProvider = allowedNextProvider;
  689.         }

  690.         /** Get the allowed parsers for next lines while parsing Rinex header.
  691.          * @param parseInfo holder for transient data
  692.          * @return allowed parsers for next line
  693.          */
  694.         private static Iterable<LineParser> headerNext(final ParseInfo parseInfo) {
  695.             if (parseInfo.file.getHeader().getFormatVersion() < 3) {
  696.                 // Rinex 2.x header entries
  697.                 return Arrays.asList(HEADER_COMMENT, HEADER_PROGRAM,
  698.                                      HEADER_ION_ALPHA, HEADER_ION_BETA,
  699.                                      HEADER_DELTA_UTC, HEADER_CORR_SYSTEM_TIME,
  700.                                      HEADER_LEAP_SECONDS, HEADER_END);
  701.             } else if (parseInfo.file.getHeader().getFormatVersion() < 4) {
  702.                 // Rinex 3.x header entries
  703.                 return Arrays.asList(HEADER_COMMENT, HEADER_PROGRAM,
  704.                                      HEADER_IONOSPHERIC, HEADER_TIME,
  705.                                      HEADER_LEAP_SECONDS, HEADER_END);
  706.             } else {
  707.                 // Rinex 4.x header entries
  708.                 return Arrays.asList(HEADER_COMMENT, HEADER_PROGRAM,
  709.                                      HEADER_DOI, HEADER_LICENSE, HEADER_STATION_INFORMATION, HEADER_MERGED_FILE,
  710.                                      HEADER_LEAP_SECONDS, HEADER_END);
  711.             }
  712.         }

  713.         /** Get the allowed parsers for next lines while parsing navigation date.
  714.          * @param parseInfo holder for transient data
  715.          * @return allowed parsers for next line
  716.          */
  717.         private static Iterable<LineParser> navigationNext(final ParseInfo parseInfo) {
  718.             if (parseInfo.gpsLNav    != null || parseInfo.gpsCNav    != null || parseInfo.galileoNav != null ||
  719.                 parseInfo.beidouLNav != null || parseInfo.beidouCNav != null || parseInfo.qzssLNav   != null ||
  720.                 parseInfo.qzssCNav   != null || parseInfo.irnssNav   != null || parseInfo.sbasNav    != null) {
  721.                 return Collections.singleton(BROADCAST_ORBIT);
  722.             } else if (parseInfo.glonassNav != null) {
  723.                 if (parseInfo.messageLineNumber < 3) {
  724.                     return Collections.singleton(BROADCAST_ORBIT);
  725.                 } else {
  726.                     // workaround for some invalid files that should nevertheless be parsed
  727.                     // we have encountered in the wild merged files that claimed to be in 3.05 version
  728.                     // and hence needed at least 4 broadcast GLONASS orbit lines (the fourth line was
  729.                     // introduced in 3.05), but in fact only had 3 broadcast lines. We think they were
  730.                     // merged from files in 3.04 or earlier format. In order to parse these files,
  731.                     // we accept after the third line either another broadcast orbit line or a new message
  732.                     if (parseInfo.file.getHeader().getFormatVersion() < 4) {
  733.                         return Arrays.asList(BROADCAST_ORBIT, NAVIGATION_SV_EPOCH_CLOCK);
  734.                     } else {
  735.                         return Arrays.asList(BROADCAST_ORBIT, EPH_TYPE, STO_TYPE, EOP_TYPE, IONO_TYPE);
  736.                     }
  737.                 }
  738.             } else if (parseInfo.file.getHeader().getFormatVersion() < 3) {
  739.                 return Collections.singleton(NAVIGATION_SV_EPOCH_CLOCK_RINEX_2);
  740.             } else if (parseInfo.file.getHeader().getFormatVersion() < 4) {
  741.                 return Collections.singleton(NAVIGATION_SV_EPOCH_CLOCK);
  742.             } else {
  743.                 return Arrays.asList(EPH_TYPE, STO_TYPE, EOP_TYPE, IONO_TYPE);
  744.             }
  745.         }

  746.     }

  747.     /** Parsers for satellite system specific lines. */
  748.     private enum SatelliteSystemLineParser {

  749.         /** GPS legacy. */
  750.         GPS_LNAV() {

  751.             /** {@inheritDoc} */
  752.             @Override
  753.             public void parseSvEpochSvClockLine(final String line, final ParseInfo pi) {
  754.                 if (pi.file.getHeader().getFormatVersion() < 3.0) {
  755.                     parseSvEpochSvClockLineRinex2(line, pi.timeScales.getGPS(), pi.gpsLNav);
  756.                 } else {
  757.                     parseSvEpochSvClockLine(line, pi.timeScales.getGPS(), pi.gpsLNav);
  758.                 }
  759.             }

  760.             /** {@inheritDoc} */
  761.             @Override
  762.             public void parseFirstBroadcastOrbit(final String line, final ParseInfo pi) {
  763.                 pi.gpsLNav.setIODE(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
  764.                 pi.gpsLNav.setCrs(parseBroadcastDouble2(line,    pi.initialSpaces, Unit.METRE));
  765.                 pi.gpsLNav.setDeltaN(parseBroadcastDouble3(line, pi.initialSpaces, RAD_PER_S));
  766.                 pi.gpsLNav.setM0(parseBroadcastDouble4(line,     pi.initialSpaces, Unit.RADIAN));
  767.             }

  768.             /** {@inheritDoc} */
  769.             @Override
  770.             public void parseSecondBroadcastOrbit(final String line, final ParseInfo pi) {
  771.                 pi.gpsLNav.setCuc(parseBroadcastDouble1(line, pi.initialSpaces, Unit.RADIAN));
  772.                 pi.gpsLNav.setE(parseBroadcastDouble2(line,     pi.initialSpaces, Unit.NONE));
  773.                 pi.gpsLNav.setCus(parseBroadcastDouble3(line,   pi.initialSpaces, Unit.RADIAN));
  774.                 pi.gpsLNav.setSqrtA(parseBroadcastDouble4(line, pi.initialSpaces, SQRT_M));
  775.             }

  776.             /** {@inheritDoc} */
  777.             @Override
  778.             public void parseThirdBroadcastOrbit(final String line, final ParseInfo pi) {
  779.                 pi.gpsLNav.setTime(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
  780.                 pi.gpsLNav.setCic(parseBroadcastDouble2(line,    pi.initialSpaces, Unit.RADIAN));
  781.                 pi.gpsLNav.setOmega0(parseBroadcastDouble3(line, pi.initialSpaces, Unit.RADIAN));
  782.                 pi.gpsLNav.setCis(parseBroadcastDouble4(line,    pi.initialSpaces, Unit.RADIAN));
  783.             }

  784.             /** {@inheritDoc} */
  785.             @Override
  786.             public void parseFourthBroadcastOrbit(final String line, final ParseInfo pi) {
  787.                 pi.gpsLNav.setI0(parseBroadcastDouble1(line, pi.initialSpaces, Unit.RADIAN));
  788.                 pi.gpsLNav.setCrc(parseBroadcastDouble2(line,      pi.initialSpaces, Unit.METRE));
  789.                 pi.gpsLNav.setPa(parseBroadcastDouble3(line,       pi.initialSpaces, Unit.RADIAN));
  790.                 pi.gpsLNav.setOmegaDot(parseBroadcastDouble4(line, pi.initialSpaces, RAD_PER_S));
  791.             }

  792.             /** {@inheritDoc} */
  793.             @Override
  794.             public void parseFifthBroadcastOrbit(final String line, final ParseInfo pi) {
  795.                 // iDot
  796.                 pi.gpsLNav.setIDot(parseBroadcastDouble1(line, pi.initialSpaces, RAD_PER_S));
  797.                 // Codes on L2 channel (ignored)
  798.                 // RinexUtils.parseDouble(line, 23, 19)
  799.                 // GPS week (to go with Toe)
  800.                 pi.gpsLNav.setWeek((int) RinexUtils.parseDouble(line, 42, 19));
  801.                 pi.gpsLNav.setDate(new GNSSDate(pi.gpsLNav.getWeek(),
  802.                                                pi.gpsLNav.getTime(),
  803.                                                SatelliteSystem.GPS,
  804.                                                pi.timeScales).getDate());
  805.             }

  806.             /** {@inheritDoc} */
  807.             @Override
  808.             public void parseSixthBroadcastOrbit(final String line, final ParseInfo pi) {
  809.                 pi.gpsLNav.setSvAccuracy(parseBroadcastDouble1(line, pi.initialSpaces, Unit.METRE));
  810.                 pi.gpsLNav.setSvHealth(parseBroadcastInt2(line, pi.initialSpaces));
  811.                 pi.gpsLNav.setTGD(parseBroadcastDouble3(line,   pi.initialSpaces, Unit.SECOND));
  812.                 pi.gpsLNav.setIODC(parseBroadcastInt4(line,     pi.initialSpaces));
  813.             }

  814.             /** {@inheritDoc} */
  815.             @Override
  816.             public void parseSeventhBroadcastOrbit(final String line, final ParseInfo pi) {
  817.                 pi.gpsLNav.setTransmissionTime(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
  818.                 pi.gpsLNav.setFitInterval(parseBroadcastInt2(line, pi.initialSpaces));
  819.                 pi.closePendingMessage();
  820.             }

  821.             /** {@inheritDoc} */
  822.             @Override
  823.             public void closeMessage(final ParseInfo pi) {
  824.                 pi.file.addGPSLegacyNavigationMessage(pi.gpsLNav);
  825.                 pi.gpsLNav = null;
  826.             }

  827.         },

  828.         /** GPS civilian.
  829.          * @since 12.0
  830.          */
  831.         GPS_CNAV() {

  832.             /** {@inheritDoc} */
  833.             @Override
  834.             public void parseSvEpochSvClockLine(final String line, final ParseInfo pi) {
  835.                 parseSvEpochSvClockLine(line, pi.timeScales.getGPS(), pi.gpsCNav);
  836.             }

  837.             /** {@inheritDoc} */
  838.             @Override
  839.             public void parseFirstBroadcastOrbit(final String line, final ParseInfo pi) {
  840.                 pi.gpsCNav.setADot(parseBroadcastDouble1(line, pi.initialSpaces, M_PER_S));
  841.                 pi.gpsCNav.setCrs(parseBroadcastDouble2(line,    pi.initialSpaces, Unit.METRE));
  842.                 pi.gpsCNav.setDeltaN(parseBroadcastDouble3(line, pi.initialSpaces, RAD_PER_S));
  843.                 pi.gpsCNav.setM0(parseBroadcastDouble4(line,     pi.initialSpaces, Unit.RADIAN));
  844.             }

  845.             /** {@inheritDoc} */
  846.             @Override
  847.             public void parseSecondBroadcastOrbit(final String line, final ParseInfo pi) {
  848.                 pi.gpsCNav.setCuc(parseBroadcastDouble1(line,   pi.initialSpaces, Unit.RADIAN));
  849.                 pi.gpsCNav.setE(parseBroadcastDouble2(line,     pi.initialSpaces, Unit.NONE));
  850.                 pi.gpsCNav.setCus(parseBroadcastDouble3(line,   pi.initialSpaces, Unit.RADIAN));
  851.                 pi.gpsCNav.setSqrtA(parseBroadcastDouble4(line, pi.initialSpaces, SQRT_M));
  852.             }

  853.             /** {@inheritDoc} */
  854.             @Override
  855.             public void parseThirdBroadcastOrbit(final String line, final ParseInfo pi) {
  856.                 pi.gpsCNav.setTime(parseBroadcastDouble1(line,   pi.initialSpaces, Unit.SECOND));
  857.                 pi.gpsCNav.setCic(parseBroadcastDouble2(line,    pi.initialSpaces, Unit.RADIAN));
  858.                 pi.gpsCNav.setOmega0(parseBroadcastDouble3(line, pi.initialSpaces, Unit.RADIAN));
  859.                 pi.gpsCNav.setCis(parseBroadcastDouble4(line,    pi.initialSpaces, Unit.RADIAN));
  860.             }

  861.             /** {@inheritDoc} */
  862.             @Override
  863.             public void parseFourthBroadcastOrbit(final String line, final ParseInfo pi) {
  864.                 pi.gpsCNav.setI0(parseBroadcastDouble1(line, pi.initialSpaces, Unit.RADIAN));
  865.                 pi.gpsCNav.setCrc(parseBroadcastDouble2(line,      pi.initialSpaces, Unit.METRE));
  866.                 pi.gpsCNav.setPa(parseBroadcastDouble3(line,       pi.initialSpaces, Unit.RADIAN));
  867.                 pi.gpsCNav.setOmegaDot(parseBroadcastDouble4(line, pi.initialSpaces, RAD_PER_S));
  868.             }

  869.             /** {@inheritDoc} */
  870.             @Override
  871.             public void parseFifthBroadcastOrbit(final String line, final ParseInfo pi) {
  872.                 pi.gpsCNav.setIDot(parseBroadcastDouble1(line, pi.initialSpaces, RAD_PER_S));
  873.                 pi.gpsCNav.setDeltaN0Dot(parseBroadcastDouble2(line, pi.initialSpaces, RAD_PER_S2));
  874.                 pi.gpsCNav.setUraiNed0(parseBroadcastInt3(line, pi.initialSpaces));
  875.                 pi.gpsCNav.setUraiNed1(parseBroadcastInt4(line, pi.initialSpaces));
  876.             }

  877.             /** {@inheritDoc} */
  878.             @Override
  879.             public void parseSixthBroadcastOrbit(final String line, final ParseInfo pi) {
  880.                 pi.gpsCNav.setUraiEd(parseBroadcastInt1(line, pi.initialSpaces));
  881.                 pi.gpsCNav.setSvHealth(parseBroadcastInt2(line, pi.initialSpaces));
  882.                 pi.gpsCNav.setTGD(parseBroadcastDouble3(line, pi.initialSpaces, Unit.SECOND));
  883.                 pi.gpsCNav.setUraiNed2(parseBroadcastInt4(line, pi.initialSpaces));
  884.             }

  885.             /** {@inheritDoc} */
  886.             @Override
  887.             public void parseSeventhBroadcastOrbit(final String line, final ParseInfo pi) {
  888.                 pi.gpsCNav.setIscL1CA(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
  889.                 pi.gpsCNav.setIscL2C(parseBroadcastDouble2(line,  pi.initialSpaces, Unit.SECOND));
  890.                 pi.gpsCNav.setIscL5I5(parseBroadcastDouble3(line, pi.initialSpaces, Unit.SECOND));
  891.                 pi.gpsCNav.setIscL5Q5(parseBroadcastDouble4(line, pi.initialSpaces, Unit.SECOND));
  892.             }

  893.             /** {@inheritDoc} */
  894.             @Override
  895.             public void parseEighthBroadcastOrbit(final String line, final ParseInfo pi) {
  896.                 if (pi.gpsCNav.isCnv2()) {
  897.                     // in CNAV2 messages, there is an additional line for L1 CD and L1 CP inter signal delay
  898.                     pi.gpsCNav.setIscL1CD(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
  899.                     pi.gpsCNav.setIscL1CP(parseBroadcastDouble2(line, pi.initialSpaces, Unit.SECOND));
  900.                 } else {
  901.                     parseTransmissionTimeLine(line, pi);
  902.                 }
  903.             }

  904.             /** {@inheritDoc} */
  905.             @Override
  906.             public void parseNinthBroadcastOrbit(final String line, final ParseInfo pi) {
  907.                 parseTransmissionTimeLine(line, pi);
  908.             }

  909.             /** Parse transmission time line.
  910.              * @param line line to parse
  911.              * @param pi holder for transient data
  912.              */
  913.             private void parseTransmissionTimeLine(final String line, final ParseInfo pi) {
  914.                 pi.gpsCNav.setTransmissionTime(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
  915.                 pi.closePendingMessage();
  916.             }

  917.             /** {@inheritDoc} */
  918.             @Override
  919.             public void closeMessage(final ParseInfo pi) {
  920.                 pi.file.addGPSLegacyNavigationMessage(pi.gpsCNav);
  921.                 pi.gpsCNav = null;
  922.             }

  923.         },

  924.         /** Galileo. */
  925.         GALILEO() {

  926.             /** {@inheritDoc} */
  927.             @Override
  928.             public void parseSvEpochSvClockLine(final String line, final ParseInfo pi) {
  929.                 parseSvEpochSvClockLine(line, pi.timeScales.getGPS(), pi.galileoNav);
  930.             }

  931.             /** {@inheritDoc} */
  932.             @Override
  933.             public void parseFirstBroadcastOrbit(final String line, final ParseInfo pi) {
  934.                 pi.galileoNav.setIODNav(parseBroadcastInt1(line, pi.initialSpaces));
  935.                 pi.galileoNav.setCrs(parseBroadcastDouble2(line,       pi.initialSpaces, Unit.METRE));
  936.                 pi.galileoNav.setDeltaN(parseBroadcastDouble3(line,    pi.initialSpaces, RAD_PER_S));
  937.                 pi.galileoNav.setM0(parseBroadcastDouble4(line,        pi.initialSpaces, Unit.RADIAN));
  938.             }

  939.             /** {@inheritDoc} */
  940.             @Override
  941.             public void parseSecondBroadcastOrbit(final String line, final ParseInfo pi) {
  942.                 pi.galileoNav.setCuc(parseBroadcastDouble1(line,   pi.initialSpaces, Unit.RADIAN));
  943.                 pi.galileoNav.setE(parseBroadcastDouble2(line,     pi.initialSpaces, Unit.NONE));
  944.                 pi.galileoNav.setCus(parseBroadcastDouble3(line,   pi.initialSpaces, Unit.RADIAN));
  945.                 pi.galileoNav.setSqrtA(parseBroadcastDouble4(line, pi.initialSpaces, SQRT_M));
  946.             }

  947.             /** {@inheritDoc} */
  948.             @Override
  949.             public void parseThirdBroadcastOrbit(final String line, final ParseInfo pi) {
  950.                 pi.galileoNav.setTime(parseBroadcastDouble1(line,   pi.initialSpaces, Unit.SECOND));
  951.                 pi.galileoNav.setCic(parseBroadcastDouble2(line,    pi.initialSpaces, Unit.RADIAN));
  952.                 pi.galileoNav.setOmega0(parseBroadcastDouble3(line, pi.initialSpaces, Unit.RADIAN));
  953.                 pi.galileoNav.setCis(parseBroadcastDouble4(line,    pi.initialSpaces, Unit.RADIAN));
  954.             }

  955.             /** {@inheritDoc} */
  956.             @Override
  957.             public void parseFourthBroadcastOrbit(final String line, final ParseInfo pi) {
  958.                 pi.galileoNav.setI0(parseBroadcastDouble1(line, pi.initialSpaces, Unit.RADIAN));
  959.                 pi.galileoNav.setCrc(parseBroadcastDouble2(line,      pi.initialSpaces, Unit.METRE));
  960.                 pi.galileoNav.setPa(parseBroadcastDouble3(line,       pi.initialSpaces, Unit.RADIAN));
  961.                 pi.galileoNav.setOmegaDot(parseBroadcastDouble4(line, pi.initialSpaces, RAD_PER_S));
  962.             }

  963.             /** {@inheritDoc} */
  964.             @Override
  965.             public void parseFifthBroadcastOrbit(final String line, final ParseInfo pi) {
  966.                 // iDot
  967.                 pi.galileoNav.setIDot(parseBroadcastDouble1(line, pi.initialSpaces, RAD_PER_S));
  968.                 pi.galileoNav.setDataSource(parseBroadcastInt2(line, pi.initialSpaces));
  969.                 // GAL week (to go with Toe)
  970.                 pi.galileoNav.setWeek(parseBroadcastInt3(line, pi.initialSpaces));
  971.                 pi.galileoNav.setDate(new GNSSDate(pi.galileoNav.getWeek(),
  972.                                                    pi.galileoNav.getTime(),
  973.                                                    SatelliteSystem.GPS, // in Rinex files, week number is aligned to GPS week!
  974.                                                    pi.timeScales).getDate());
  975.             }

  976.             /** {@inheritDoc} */
  977.             @Override
  978.             public void parseSixthBroadcastOrbit(final String line, final ParseInfo pi) {
  979.                 pi.galileoNav.setSisa(parseBroadcastDouble1(line, pi.initialSpaces, Unit.METRE));
  980.                 pi.galileoNav.setSvHealth(parseBroadcastDouble2(line, pi.initialSpaces, Unit.NONE));
  981.                 pi.galileoNav.setBGDE1E5a(parseBroadcastDouble3(line, pi.initialSpaces, Unit.SECOND));
  982.                 pi.galileoNav.setBGDE5bE1(parseBroadcastDouble4(line, pi.initialSpaces, Unit.SECOND));
  983.             }

  984.             /** {@inheritDoc} */
  985.             @Override
  986.             public void parseSeventhBroadcastOrbit(final String line, final ParseInfo pi) {
  987.                 pi.galileoNav.setTransmissionTime(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
  988.                 pi.closePendingMessage();
  989.             }

  990.             /** {@inheritDoc} */
  991.             @Override
  992.             public void closeMessage(final ParseInfo pi) {
  993.                 pi.file.addGalileoNavigationMessage(pi.galileoNav);
  994.                 pi.galileoNav = null;
  995.             }

  996.         },

  997.         /** Glonass. */
  998.         GLONASS() {

  999.             /** {@inheritDoc} */
  1000.             @Override
  1001.             public void parseSvEpochSvClockLine(final String line, final ParseInfo pi) {

  1002.                 if (pi.file.getHeader().getFormatVersion() < 3.0) {

  1003.                     pi.glonassNav.setPRN(RinexUtils.parseInt(line, 0, 2));

  1004.                     // Toc
  1005.                     final int    year  = RinexUtils.convert2DigitsYear(RinexUtils.parseInt(line,  3, 2));
  1006.                     final int    month = RinexUtils.parseInt(line,  6, 2);
  1007.                     final int    day   = RinexUtils.parseInt(line,  9, 2);
  1008.                     final int    hours = RinexUtils.parseInt(line, 12, 2);
  1009.                     final int    min   = RinexUtils.parseInt(line, 15, 2);
  1010.                     final double sec   = RinexUtils.parseDouble(line, 17, 5);
  1011.                     pi.glonassNav.setEpochToc(new AbsoluteDate(year, month, day, hours, min, sec,
  1012.                                                                pi.timeScales.getUTC()));

  1013.                     // clock
  1014.                     pi.glonassNav.setTauN(-RinexUtils.parseDouble(line, 22, 19));
  1015.                     pi.glonassNav.setGammaN(RinexUtils.parseDouble(line, 41, 19));
  1016.                     pi.glonassNav.setTime(fmod(RinexUtils.parseDouble(line, 60, 19), Constants.JULIAN_DAY));

  1017.                     // Set the ephemeris epoch (same as time of clock epoch)
  1018.                     pi.glonassNav.setDate(pi.glonassNav.getEpochToc());

  1019.                 } else {
  1020.                     pi.glonassNav.setPRN(RinexUtils.parseInt(line, 1, 2));

  1021.                     // Toc
  1022.                     pi.glonassNav.setEpochToc(parsePrnSvEpochClock(line, pi.timeScales.getUTC()));

  1023.                     // clock
  1024.                     pi.glonassNav.setTauN(-RinexUtils.parseDouble(line, 23, 19));
  1025.                     pi.glonassNav.setGammaN(RinexUtils.parseDouble(line, 42, 19));
  1026.                     pi.glonassNav.setTime(fmod(RinexUtils.parseDouble(line, 61, 19), Constants.JULIAN_DAY));

  1027.                     // Set the ephemeris epoch (same as time of clock epoch)
  1028.                     pi.glonassNav.setDate(pi.glonassNav.getEpochToc());
  1029.                 }

  1030.             }

  1031.             /** {@inheritDoc} */
  1032.             @Override
  1033.             public void parseFirstBroadcastOrbit(final String line, final ParseInfo pi) {
  1034.                 pi.glonassNav.setX(parseBroadcastDouble1(line, pi.initialSpaces, KM));
  1035.                 pi.glonassNav.setXDot(parseBroadcastDouble2(line,    pi.initialSpaces, KM_PER_S));
  1036.                 pi.glonassNav.setXDotDot(parseBroadcastDouble3(line, pi.initialSpaces, KM_PER_S2));
  1037.                 pi.glonassNav.setHealth(parseBroadcastDouble4(line,  pi.initialSpaces, Unit.NONE));
  1038.             }

  1039.             /** {@inheritDoc} */
  1040.             @Override
  1041.             public void parseSecondBroadcastOrbit(final String line, final ParseInfo pi) {
  1042.                 pi.glonassNav.setY(parseBroadcastDouble1(line, pi.initialSpaces, KM));
  1043.                 pi.glonassNav.setYDot(parseBroadcastDouble2(line,            pi.initialSpaces, KM_PER_S));
  1044.                 pi.glonassNav.setYDotDot(parseBroadcastDouble3(line,         pi.initialSpaces, KM_PER_S2));
  1045.                 pi.glonassNav.setFrequencyNumber(parseBroadcastDouble4(line, pi.initialSpaces, Unit.NONE));
  1046.             }

  1047.             /** {@inheritDoc} */
  1048.             @Override
  1049.             public void parseThirdBroadcastOrbit(final String line, final ParseInfo pi) {
  1050.                 pi.glonassNav.setZ(parseBroadcastDouble1(line, pi.initialSpaces, KM));
  1051.                 pi.glonassNav.setZDot(parseBroadcastDouble2(line,    pi.initialSpaces, KM_PER_S));
  1052.                 pi.glonassNav.setZDotDot(parseBroadcastDouble3(line, pi.initialSpaces, KM_PER_S2));
  1053.                 if (pi.file.getHeader().getFormatVersion() < 3.045) {
  1054.                     pi.closePendingMessage();
  1055.                 }
  1056.             }

  1057.             /** {@inheritDoc} */
  1058.             @Override
  1059.             public void parseFourthBroadcastOrbit(final String line, final ParseInfo pi) {
  1060.                 pi.glonassNav.setStatusFlags(parseBroadcastDouble1(line, pi.initialSpaces, Unit.NONE));
  1061.                 pi.glonassNav.setGroupDelayDifference(parseBroadcastDouble2(line, pi.initialSpaces, Unit.NONE));
  1062.                 pi.glonassNav.setURA(parseBroadcastDouble3(line,                  pi.initialSpaces, Unit.NONE));
  1063.                 pi.glonassNav.setHealthFlags(parseBroadcastDouble4(line,          pi.initialSpaces, Unit.NONE));
  1064.                 pi.closePendingMessage();
  1065.             }

  1066.             /** {@inheritDoc} */
  1067.             @Override
  1068.             public void closeMessage(final ParseInfo pi) {
  1069.                 pi.file.addGlonassNavigationMessage(pi.glonassNav);
  1070.                 pi.glonassNav = null;
  1071.             }

  1072.         },

  1073.         /** QZSS legacy. */
  1074.         QZSS_LNAV() {

  1075.             /** {@inheritDoc} */
  1076.             @Override
  1077.             public void parseSvEpochSvClockLine(final String line, final ParseInfo pi) {
  1078.                 parseSvEpochSvClockLine(line, pi.timeScales.getGPS(), pi.qzssLNav);
  1079.             }

  1080.             /** {@inheritDoc} */
  1081.             @Override
  1082.             public void parseFirstBroadcastOrbit(final String line, final ParseInfo pi) {
  1083.                 pi.qzssLNav.setIODE(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
  1084.                 pi.qzssLNav.setCrs(parseBroadcastDouble2(line,    pi.initialSpaces, Unit.METRE));
  1085.                 pi.qzssLNav.setDeltaN(parseBroadcastDouble3(line, pi.initialSpaces, RAD_PER_S));
  1086.                 pi.qzssLNav.setM0(parseBroadcastDouble4(line,     pi.initialSpaces, Unit.RADIAN));
  1087.             }

  1088.             /** {@inheritDoc} */
  1089.             @Override
  1090.             public void parseSecondBroadcastOrbit(final String line, final ParseInfo pi) {
  1091.                 pi.qzssLNav.setCuc(parseBroadcastDouble1(line, pi.initialSpaces, Unit.RADIAN));
  1092.                 pi.qzssLNav.setE(parseBroadcastDouble2(line,     pi.initialSpaces, Unit.NONE));
  1093.                 pi.qzssLNav.setCus(parseBroadcastDouble3(line,   pi.initialSpaces, Unit.RADIAN));
  1094.                 pi.qzssLNav.setSqrtA(parseBroadcastDouble4(line, pi.initialSpaces, SQRT_M));
  1095.             }

  1096.             /** {@inheritDoc} */
  1097.             @Override
  1098.             public void parseThirdBroadcastOrbit(final String line, final ParseInfo pi) {
  1099.                 pi.qzssLNav.setTime(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
  1100.                 pi.qzssLNav.setCic(parseBroadcastDouble2(line,    pi.initialSpaces, Unit.RADIAN));
  1101.                 pi.qzssLNav.setOmega0(parseBroadcastDouble3(line, pi.initialSpaces, Unit.RADIAN));
  1102.                 pi.qzssLNav.setCis(parseBroadcastDouble4(line,    pi.initialSpaces, Unit.RADIAN));
  1103.             }

  1104.             /** {@inheritDoc} */
  1105.             @Override
  1106.             public void parseFourthBroadcastOrbit(final String line, final ParseInfo pi) {
  1107.                 pi.qzssLNav.setI0(parseBroadcastDouble1(line, pi.initialSpaces, Unit.RADIAN));
  1108.                 pi.qzssLNav.setCrc(parseBroadcastDouble2(line,      pi.initialSpaces, Unit.METRE));
  1109.                 pi.qzssLNav.setPa(parseBroadcastDouble3(line,       pi.initialSpaces, Unit.RADIAN));
  1110.                 pi.qzssLNav.setOmegaDot(parseBroadcastDouble4(line, pi.initialSpaces, RAD_PER_S));
  1111.             }

  1112.             /** {@inheritDoc} */
  1113.             @Override
  1114.             public void parseFifthBroadcastOrbit(final String line, final ParseInfo pi) {
  1115.                 // iDot
  1116.                 pi.qzssLNav.setIDot(parseBroadcastDouble1(line, pi.initialSpaces, RAD_PER_S));
  1117.                 // Codes on L2 channel (ignored)
  1118.                 // RinexUtils.parseDouble(line, 23, 19)
  1119.                 // GPS week (to go with Toe)
  1120.                 pi.qzssLNav.setWeek(parseBroadcastInt3(line, pi.initialSpaces));
  1121.                 pi.qzssLNav.setDate(new GNSSDate(pi.qzssLNav.getWeek(),
  1122.                                                  pi.qzssLNav.getTime(),
  1123.                                                  SatelliteSystem.GPS, // in Rinex files, week number is aligned to GPS week!
  1124.                                                  pi.timeScales).getDate());
  1125.             }

  1126.             /** {@inheritDoc} */
  1127.             @Override
  1128.             public void parseSixthBroadcastOrbit(final String line, final ParseInfo pi) {
  1129.                 pi.qzssLNav.setSvAccuracy(parseBroadcastDouble1(line,  pi.initialSpaces, Unit.METRE));
  1130.                 pi.qzssLNav.setSvHealth(parseBroadcastInt2(line, pi.initialSpaces));
  1131.                 pi.qzssLNav.setTGD(parseBroadcastDouble3(line,   pi.initialSpaces, Unit.SECOND));
  1132.                 pi.qzssLNav.setIODC(parseBroadcastInt4(line,     pi.initialSpaces));
  1133.             }

  1134.             /** {@inheritDoc} */
  1135.             @Override
  1136.             public void parseSeventhBroadcastOrbit(final String line, final ParseInfo pi) {
  1137.                 pi.qzssLNav.setTransmissionTime(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
  1138.                 pi.qzssLNav.setFitInterval(parseBroadcastInt2(line, pi.initialSpaces));
  1139.                 pi.closePendingMessage();
  1140.             }

  1141.             /** {@inheritDoc} */
  1142.             @Override
  1143.             public void closeMessage(final ParseInfo pi) {
  1144.                 pi.file.addQZSSLegacyNavigationMessage(pi.qzssLNav);
  1145.                 pi.qzssLNav = null;
  1146.             }

  1147.         },

  1148.         /** QZSS civilian.
  1149.          * @since 12.0
  1150.          */
  1151.         QZSS_CNAV() {

  1152.             /** {@inheritDoc} */
  1153.             @Override
  1154.             public void parseSvEpochSvClockLine(final String line, final ParseInfo pi) {
  1155.                 parseSvEpochSvClockLine(line, pi.timeScales.getGPS(), pi.qzssCNav);
  1156.             }

  1157.             /** {@inheritDoc} */
  1158.             @Override
  1159.             public void parseFirstBroadcastOrbit(final String line, final ParseInfo pi) {
  1160.                 pi.qzssCNav.setADot(parseBroadcastDouble1(line, pi.initialSpaces, M_PER_S));
  1161.                 pi.qzssCNav.setCrs(parseBroadcastDouble2(line,    pi.initialSpaces, Unit.METRE));
  1162.                 pi.qzssCNav.setDeltaN(parseBroadcastDouble3(line, pi.initialSpaces, RAD_PER_S));
  1163.                 pi.qzssCNav.setM0(parseBroadcastDouble4(line,     pi.initialSpaces, Unit.RADIAN));
  1164.             }

  1165.             /** {@inheritDoc} */
  1166.             @Override
  1167.             public void parseSecondBroadcastOrbit(final String line, final ParseInfo pi) {
  1168.                 pi.qzssCNav.setCuc(parseBroadcastDouble1(line, pi.initialSpaces, Unit.RADIAN));
  1169.                 pi.qzssCNav.setE(parseBroadcastDouble2(line,     pi.initialSpaces, Unit.NONE));
  1170.                 pi.qzssCNav.setCus(parseBroadcastDouble3(line,   pi.initialSpaces, Unit.RADIAN));
  1171.                 pi.qzssCNav.setSqrtA(parseBroadcastDouble4(line, pi.initialSpaces, SQRT_M));
  1172.             }

  1173.             /** {@inheritDoc} */
  1174.             @Override
  1175.             public void parseThirdBroadcastOrbit(final String line, final ParseInfo pi) {
  1176.                 pi.qzssCNav.setTime(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
  1177.                 pi.qzssCNav.setCic(parseBroadcastDouble2(line,    pi.initialSpaces, Unit.RADIAN));
  1178.                 pi.qzssCNav.setOmega0(parseBroadcastDouble3(line, pi.initialSpaces, Unit.RADIAN));
  1179.                 pi.qzssCNav.setCis(parseBroadcastDouble4(line,    pi.initialSpaces, Unit.RADIAN));
  1180.             }

  1181.             /** {@inheritDoc} */
  1182.             @Override
  1183.             public void parseFourthBroadcastOrbit(final String line, final ParseInfo pi) {
  1184.                 pi.qzssCNav.setI0(parseBroadcastDouble1(line, pi.initialSpaces, Unit.RADIAN));
  1185.                 pi.qzssCNav.setCrc(parseBroadcastDouble2(line,      pi.initialSpaces, Unit.METRE));
  1186.                 pi.qzssCNav.setPa(parseBroadcastDouble3(line,       pi.initialSpaces, Unit.RADIAN));
  1187.                 pi.qzssCNav.setOmegaDot(parseBroadcastDouble4(line, pi.initialSpaces, RAD_PER_S));
  1188.             }

  1189.             /** {@inheritDoc} */
  1190.             @Override
  1191.             public void parseFifthBroadcastOrbit(final String line, final ParseInfo pi) {
  1192.                 pi.qzssCNav.setIDot(parseBroadcastDouble1(line, pi.initialSpaces, RAD_PER_S));
  1193.                 pi.qzssCNav.setDeltaN0Dot(parseBroadcastDouble2(line, pi.initialSpaces, RAD_PER_S2));
  1194.                 pi.qzssCNav.setUraiNed0(parseBroadcastInt3(line, pi.initialSpaces));
  1195.                 pi.qzssCNav.setUraiNed1(parseBroadcastInt4(line, pi.initialSpaces));
  1196.             }

  1197.             /** {@inheritDoc} */
  1198.             @Override
  1199.             public void parseSixthBroadcastOrbit(final String line, final ParseInfo pi) {
  1200.                 pi.qzssCNav.setUraiEd(parseBroadcastInt1(line, pi.initialSpaces));
  1201.                 pi.qzssCNav.setSvHealth(parseBroadcastInt2(line, pi.initialSpaces));
  1202.                 pi.qzssCNav.setTGD(parseBroadcastDouble3(line, pi.initialSpaces, Unit.SECOND));
  1203.                 pi.qzssCNav.setUraiNed2(parseBroadcastInt4(line, pi.initialSpaces));
  1204.             }

  1205.             /** {@inheritDoc} */
  1206.             @Override
  1207.             public void parseSeventhBroadcastOrbit(final String line, final ParseInfo pi) {
  1208.                 pi.qzssCNav.setIscL1CA(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
  1209.                 pi.qzssCNav.setIscL2C(parseBroadcastDouble2(line,  pi.initialSpaces, Unit.SECOND));
  1210.                 pi.qzssCNav.setIscL5I5(parseBroadcastDouble3(line, pi.initialSpaces, Unit.SECOND));
  1211.                 pi.qzssCNav.setIscL5Q5(parseBroadcastDouble4(line, pi.initialSpaces, Unit.SECOND));
  1212.             }

  1213.             /** {@inheritDoc} */
  1214.             @Override
  1215.             public void parseEighthBroadcastOrbit(final String line, final ParseInfo pi) {
  1216.                 if (pi.qzssCNav.isCnv2()) {
  1217.                     // in CNAV2 messages, there is an additional line for L1 CD and L1 CP inter signal delay
  1218.                     pi.qzssCNav.setIscL1CD(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
  1219.                     pi.qzssCNav.setIscL1CP(parseBroadcastDouble2(line, pi.initialSpaces, Unit.SECOND));
  1220.                 } else {
  1221.                     parseTransmissionTimeLine(line, pi);
  1222.                 }
  1223.             }

  1224.             /** {@inheritDoc} */
  1225.             @Override
  1226.             public void parseNinthBroadcastOrbit(final String line, final ParseInfo pi) {
  1227.                 parseTransmissionTimeLine(line, pi);
  1228.             }

  1229.             /** Parse transmission time line.
  1230.              * @param line line to parse
  1231.              * @param pi holder for transient data
  1232.              */
  1233.             private void parseTransmissionTimeLine(final String line, final ParseInfo pi) {
  1234.                 pi.qzssCNav.setTransmissionTime(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
  1235.                 pi.closePendingMessage();
  1236.             }

  1237.             /** {@inheritDoc} */
  1238.             @Override
  1239.             public void closeMessage(final ParseInfo pi) {
  1240.                 pi.file.addQZSSCivilianNavigationMessage(pi.qzssCNav);
  1241.                 pi.qzssCNav = null;
  1242.             }

  1243.         },

  1244.         /** Beidou legacy. */
  1245.         BEIDOU_D1_D2() {

  1246.             /** {@inheritDoc} */
  1247.             @Override
  1248.             public void parseSvEpochSvClockLine(final String line, final ParseInfo pi) {
  1249.                 parseSvEpochSvClockLine(line, pi.timeScales.getBDT(), pi.beidouLNav);
  1250.             }

  1251.             /** {@inheritDoc} */
  1252.             @Override
  1253.             public void parseFirstBroadcastOrbit(final String line, final ParseInfo pi) {
  1254.                 pi.beidouLNav.setAODE(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
  1255.                 pi.beidouLNav.setCrs(parseBroadcastDouble2(line,    pi.initialSpaces, Unit.METRE));
  1256.                 pi.beidouLNav.setDeltaN(parseBroadcastDouble3(line, pi.initialSpaces, RAD_PER_S));
  1257.                 pi.beidouLNav.setM0(parseBroadcastDouble4(line,     pi.initialSpaces, Unit.RADIAN));
  1258.             }

  1259.             /** {@inheritDoc} */
  1260.             @Override
  1261.             public void parseSecondBroadcastOrbit(final String line, final ParseInfo pi) {
  1262.                 pi.beidouLNav.setCuc(parseBroadcastDouble1(line,   pi.initialSpaces, Unit.RADIAN));
  1263.                 pi.beidouLNav.setE(parseBroadcastDouble2(line,     pi.initialSpaces, Unit.NONE));
  1264.                 pi.beidouLNav.setCus(parseBroadcastDouble3(line,   pi.initialSpaces, Unit.RADIAN));
  1265.                 pi.beidouLNav.setSqrtA(parseBroadcastDouble4(line, pi.initialSpaces, SQRT_M));
  1266.             }

  1267.             /** {@inheritDoc} */
  1268.             @Override
  1269.             public void parseThirdBroadcastOrbit(final String line, final ParseInfo pi) {
  1270.                 pi.beidouLNav.setTime(parseBroadcastDouble1(line,   pi.initialSpaces, Unit.SECOND));
  1271.                 pi.beidouLNav.setCic(parseBroadcastDouble2(line,    pi.initialSpaces, Unit.RADIAN));
  1272.                 pi.beidouLNav.setOmega0(parseBroadcastDouble3(line, pi.initialSpaces, Unit.RADIAN));
  1273.                 pi.beidouLNav.setCis(parseBroadcastDouble4(line,    pi.initialSpaces, Unit.RADIAN));
  1274.             }

  1275.             /** {@inheritDoc} */
  1276.             @Override
  1277.             public void parseFourthBroadcastOrbit(final String line, final ParseInfo pi) {
  1278.                 pi.beidouLNav.setI0(parseBroadcastDouble1(line,       pi.initialSpaces, Unit.RADIAN));
  1279.                 pi.beidouLNav.setCrc(parseBroadcastDouble2(line,      pi.initialSpaces, Unit.METRE));
  1280.                 pi.beidouLNav.setPa(parseBroadcastDouble3(line,       pi.initialSpaces, Unit.RADIAN));
  1281.                 pi.beidouLNav.setOmegaDot(parseBroadcastDouble4(line, pi.initialSpaces, RAD_PER_S));
  1282.             }

  1283.             /** {@inheritDoc} */
  1284.             @Override
  1285.             public void parseFifthBroadcastOrbit(final String line, final ParseInfo pi) {
  1286.                 // iDot
  1287.                 pi.beidouLNav.setIDot(parseBroadcastDouble1(line, pi.initialSpaces, RAD_PER_S));
  1288.                 // BDT week (to go with Toe)
  1289.                 pi.beidouLNav.setWeek(parseBroadcastInt3(line, pi.initialSpaces));
  1290.                 pi.beidouLNav.setDate(new GNSSDate(pi.beidouLNav.getWeek(),
  1291.                                                    pi.beidouLNav.getTime(),
  1292.                                                    SatelliteSystem.BEIDOU,
  1293.                                                    pi.timeScales).getDate());
  1294.             }

  1295.             /** {@inheritDoc} */
  1296.             @Override
  1297.             public void parseSixthBroadcastOrbit(final String line, final ParseInfo pi) {
  1298.                 pi.beidouLNav.setSvAccuracy(parseBroadcastDouble1(line, pi.initialSpaces, Unit.METRE));
  1299.                 // TODO SatH1
  1300.                 pi.beidouLNav.setTGD1(parseBroadcastDouble3(line,       pi.initialSpaces, Unit.SECOND));
  1301.                 pi.beidouLNav.setTGD2(parseBroadcastDouble4(line,       pi.initialSpaces, Unit.SECOND));
  1302.             }

  1303.             /** {@inheritDoc} */
  1304.             @Override
  1305.             public void parseSeventhBroadcastOrbit(final String line, final ParseInfo pi) {
  1306.                 pi.beidouLNav.setTransmissionTime(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
  1307.                 pi.beidouLNav.setAODC(parseBroadcastDouble2(line,             pi.initialSpaces, Unit.SECOND));
  1308.                 pi.closePendingMessage();
  1309.             }

  1310.             /** {@inheritDoc} */
  1311.             @Override
  1312.             public void closeMessage(final ParseInfo pi) {
  1313.                 pi.file.addBeidouLegacyNavigationMessage(pi.beidouLNav);
  1314.                 pi.beidouLNav = null;
  1315.             }

  1316.         },

  1317.         /** Beidou-3 CNAV. */
  1318.         BEIDOU_CNV_123() {

  1319.             /** {@inheritDoc} */
  1320.             @Override
  1321.             public void parseSvEpochSvClockLine(final String line, final ParseInfo pi) {
  1322.                 parseSvEpochSvClockLine(line, pi.timeScales.getBDT(), pi.beidouCNav);
  1323.             }

  1324.             /** {@inheritDoc} */
  1325.             @Override
  1326.             public void parseFirstBroadcastOrbit(final String line, final ParseInfo pi) {
  1327.                 pi.beidouCNav.setADot(parseBroadcastDouble1(line, pi.initialSpaces, M_PER_S));
  1328.                 pi.beidouCNav.setCrs(parseBroadcastDouble2(line,    pi.initialSpaces, Unit.METRE));
  1329.                 pi.beidouCNav.setDeltaN(parseBroadcastDouble3(line, pi.initialSpaces, RAD_PER_S));
  1330.                 pi.beidouCNav.setM0(parseBroadcastDouble4(line,     pi.initialSpaces, Unit.RADIAN));
  1331.             }

  1332.             /** {@inheritDoc} */
  1333.             @Override
  1334.             public void parseSecondBroadcastOrbit(final String line, final ParseInfo pi) {
  1335.                 pi.beidouCNav.setCuc(parseBroadcastDouble1(line, pi.initialSpaces, Unit.RADIAN));
  1336.                 pi.beidouCNav.setE(parseBroadcastDouble2(line,     pi.initialSpaces, Unit.NONE));
  1337.                 pi.beidouCNav.setCus(parseBroadcastDouble3(line,   pi.initialSpaces, Unit.RADIAN));
  1338.                 pi.beidouCNav.setSqrtA(parseBroadcastDouble4(line, pi.initialSpaces, SQRT_M));
  1339.             }

  1340.             /** {@inheritDoc} */
  1341.             @Override
  1342.             public void parseThirdBroadcastOrbit(final String line, final ParseInfo pi) {
  1343.                 pi.beidouCNav.setTime(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
  1344.                 pi.beidouCNav.setCic(parseBroadcastDouble2(line,    pi.initialSpaces, Unit.RADIAN));
  1345.                 pi.beidouCNav.setOmega0(parseBroadcastDouble3(line, pi.initialSpaces, Unit.RADIAN));
  1346.                 pi.beidouCNav.setCis(parseBroadcastDouble4(line,    pi.initialSpaces, Unit.RADIAN));
  1347.             }

  1348.             /** {@inheritDoc} */
  1349.             @Override
  1350.             public void parseFourthBroadcastOrbit(final String line, final ParseInfo pi) {
  1351.                 pi.beidouCNav.setI0(parseBroadcastDouble1(line,       pi.initialSpaces, Unit.RADIAN));
  1352.                 pi.beidouCNav.setCrc(parseBroadcastDouble2(line,      pi.initialSpaces, Unit.METRE));
  1353.                 pi.beidouCNav.setPa(parseBroadcastDouble3(line,       pi.initialSpaces, Unit.RADIAN));
  1354.                 pi.beidouCNav.setOmegaDot(parseBroadcastDouble4(line, pi.initialSpaces, RAD_PER_S));
  1355.             }

  1356.             /** {@inheritDoc} */
  1357.             @Override
  1358.             public void parseFifthBroadcastOrbit(final String line, final ParseInfo pi) {
  1359.                 pi.beidouCNav.setIDot(parseBroadcastDouble1(line, pi.initialSpaces, RAD_PER_S));
  1360.                 pi.beidouCNav.setDeltaN0Dot(parseBroadcastDouble2(line, pi.initialSpaces, RAD_PER_S2));
  1361.                 switch (parseBroadcastInt3(line, pi.initialSpaces)) {
  1362.                     case 0 :
  1363.                         pi.beidouCNav.setSatelliteType(BeidouSatelliteType.RESERVED);
  1364.                         break;
  1365.                     case 1 :
  1366.                         pi.beidouCNav.setSatelliteType(BeidouSatelliteType.GEO);
  1367.                         break;
  1368.                     case 2 :
  1369.                         pi.beidouCNav.setSatelliteType(BeidouSatelliteType.IGSO);
  1370.                         break;
  1371.                     case 3 :
  1372.                         pi.beidouCNav.setSatelliteType(BeidouSatelliteType.MEO);
  1373.                         break;
  1374.                     default:
  1375.                         throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  1376.                                                   pi.lineNumber, pi.name, line);
  1377.                 }
  1378.                 pi.beidouCNav.setTime(parseBroadcastDouble4(line,     pi.initialSpaces, Unit.SECOND));
  1379.             }

  1380.             /** {@inheritDoc} */
  1381.             @Override
  1382.             public void parseSixthBroadcastOrbit(final String line, final ParseInfo pi) {
  1383.                 pi.beidouCNav.setSisaiOe(parseBroadcastInt1(line, pi.initialSpaces));
  1384.                 pi.beidouCNav.setSisaiOcb(parseBroadcastInt2(line, pi.initialSpaces));
  1385.                 pi.beidouCNav.setSisaiOc1(parseBroadcastInt3(line, pi.initialSpaces));
  1386.                 pi.beidouCNav.setSisaiOc2(parseBroadcastInt4(line, pi.initialSpaces));
  1387.             }

  1388.             /** {@inheritDoc} */
  1389.             @Override
  1390.             public void parseSeventhBroadcastOrbit(final String line, final ParseInfo pi) {
  1391.                 if (pi.beidouCNav.getSignal() == Frequency.B1C) {
  1392.                     pi.beidouCNav.setIscB1CD(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
  1393.                     // field 2 is spare
  1394.                     pi.beidouCNav.setTgdB1Cp(parseBroadcastDouble3(line, pi.initialSpaces, Unit.SECOND));
  1395.                     pi.beidouCNav.setTgdB2ap(parseBroadcastDouble4(line, pi.initialSpaces, Unit.SECOND));
  1396.                 } else if (pi.beidouCNav.getSignal() == Frequency.B2A) {
  1397.                     // field 1 is spare
  1398.                     pi.beidouCNav.setIscB2AD(parseBroadcastDouble2(line, pi.initialSpaces, Unit.SECOND));
  1399.                     pi.beidouCNav.setTgdB1Cp(parseBroadcastDouble3(line, pi.initialSpaces, Unit.SECOND));
  1400.                     pi.beidouCNav.setTgdB2ap(parseBroadcastDouble4(line, pi.initialSpaces, Unit.SECOND));
  1401.                 } else {
  1402.                     parseSismaiHealthIntegrity(line, pi);
  1403.                 }
  1404.             }

  1405.             /** {@inheritDoc} */
  1406.             @Override
  1407.             public void parseEighthBroadcastOrbit(final String line, final ParseInfo pi) {
  1408.                 if (pi.beidouCNav.getSignal() == Frequency.B2B) {
  1409.                     pi.beidouCNav.setTransmissionTime(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
  1410.                     pi.closePendingMessage();
  1411.                 } else {
  1412.                     parseSismaiHealthIntegrity(line, pi);
  1413.                 }
  1414.             }

  1415.             /** {@inheritDoc} */
  1416.             @Override
  1417.             public void parseNinthBroadcastOrbit(final String line, final ParseInfo pi) {
  1418.                 pi.beidouCNav.setTransmissionTime(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
  1419.                 // field 2 is spare
  1420.                 // field 3 is spare
  1421.                 pi.beidouCNav.setIODE(parseBroadcastInt4(line, pi.initialSpaces));
  1422.                 pi.closePendingMessage();
  1423.             }

  1424.             /** {@inheritDoc} */
  1425.             @Override
  1426.             public void closeMessage(final ParseInfo pi) {
  1427.                 pi.file.addBeidouCivilianNavigationMessage(pi.beidouCNav);
  1428.                 pi.beidouCNav = null;
  1429.             }

  1430.             /**
  1431.              * Parse the SISMAI/Health/integrity line.
  1432.              * @param line line to read
  1433.              * @param pi holder for transient data
  1434.              */
  1435.             private void parseSismaiHealthIntegrity(final String line, final ParseInfo pi) {
  1436.                 pi.beidouCNav.setSismai(parseBroadcastInt1(line, pi.initialSpaces));
  1437.                 pi.beidouCNav.setHealth(parseBroadcastInt2(line, pi.initialSpaces));
  1438.                 pi.beidouCNav.setIntegrityFlags(parseBroadcastInt3(line, pi.initialSpaces));
  1439.                 pi.beidouCNav.setIODC(parseBroadcastInt4(line, pi.initialSpaces));
  1440.             }

  1441.         },

  1442.         /** SBAS. */
  1443.         SBAS() {

  1444.             /** {@inheritDoc} */
  1445.             @Override
  1446.             public void parseSvEpochSvClockLine(final String line, final ParseInfo pi) {

  1447.                 // parse PRN
  1448.                 pi.sbasNav.setPRN(RinexUtils.parseInt(line, 1, 2));

  1449.                 // Time scale (UTC for Rinex 3.01 and GPS for other RINEX versions)
  1450.                 final int       version100 = (int) FastMath.rint(pi.file.getHeader().getFormatVersion() * 100);
  1451.                 final TimeScale timeScale  = (version100 == 301) ? pi.timeScales.getUTC() : pi.timeScales.getGPS();

  1452.                 pi.sbasNav.setEpochToc(parsePrnSvEpochClock(line, timeScale));
  1453.                 pi.sbasNav.setAGf0(parseBroadcastDouble2(line, pi.initialSpaces, Unit.SECOND));
  1454.                 pi.sbasNav.setAGf1(parseBroadcastDouble3(line, pi.initialSpaces, S_PER_S));
  1455.                 pi.sbasNav.setTime(parseBroadcastDouble4(line, pi.initialSpaces, Unit.SECOND));

  1456.                 // Set the ephemeris epoch (same as time of clock epoch)
  1457.                 pi.sbasNav.setDate(pi.sbasNav.getEpochToc());

  1458.             }

  1459.             /** {@inheritDoc} */
  1460.             @Override
  1461.             public void parseFirstBroadcastOrbit(final String line, final ParseInfo pi) {
  1462.                 pi.sbasNav.setX(parseBroadcastDouble1(line, pi.initialSpaces, KM));
  1463.                 pi.sbasNav.setXDot(parseBroadcastDouble2(line,    pi.initialSpaces, KM_PER_S));
  1464.                 pi.sbasNav.setXDotDot(parseBroadcastDouble3(line, pi.initialSpaces, KM_PER_S2));
  1465.                 pi.sbasNav.setHealth(parseBroadcastDouble4(line,  pi.initialSpaces, Unit.NONE));
  1466.             }

  1467.             /** {@inheritDoc} */
  1468.             @Override
  1469.             public void parseSecondBroadcastOrbit(final String line, final ParseInfo pi) {
  1470.                 pi.sbasNav.setY(parseBroadcastDouble1(line, pi.initialSpaces, KM));
  1471.                 pi.sbasNav.setYDot(parseBroadcastDouble2(line,    pi.initialSpaces, KM_PER_S));
  1472.                 pi.sbasNav.setYDotDot(parseBroadcastDouble3(line, pi.initialSpaces, KM_PER_S2));
  1473.                 pi.sbasNav.setURA(parseBroadcastDouble4(line,     pi.initialSpaces, Unit.NONE));
  1474.             }

  1475.             /** {@inheritDoc} */
  1476.             @Override
  1477.             public void parseThirdBroadcastOrbit(final String line, final ParseInfo pi) {
  1478.                 pi.sbasNav.setZ(parseBroadcastDouble1(line, pi.initialSpaces, KM));
  1479.                 pi.sbasNav.setZDot(parseBroadcastDouble2(line,    pi.initialSpaces, KM_PER_S));
  1480.                 pi.sbasNav.setZDotDot(parseBroadcastDouble3(line, pi.initialSpaces, KM_PER_S2));
  1481.                 pi.sbasNav.setIODN(parseBroadcastDouble4(line,    pi.initialSpaces, Unit.NONE));
  1482.                 pi.closePendingMessage();
  1483.             }

  1484.             /** {@inheritDoc} */
  1485.             @Override
  1486.             public void closeMessage(final ParseInfo pi) {
  1487.                 pi.file.addSBASNavigationMessage(pi.sbasNav);
  1488.                 pi.sbasNav = null;
  1489.             }

  1490.         },

  1491.         /** IRNSS. */
  1492.         IRNSS() {

  1493.             /** {@inheritDoc} */
  1494.             @Override
  1495.             public void parseSvEpochSvClockLine(final String line, final ParseInfo pi) {
  1496.                 parseSvEpochSvClockLine(line, pi.timeScales.getIRNSS(), pi.irnssNav);
  1497.             }

  1498.             /** {@inheritDoc} */
  1499.             @Override
  1500.             public void parseFirstBroadcastOrbit(final String line, final ParseInfo pi) {
  1501.                 pi.irnssNav.setIODEC(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
  1502.                 pi.irnssNav.setCrs(parseBroadcastDouble2(line,     pi.initialSpaces, Unit.METRE));
  1503.                 pi.irnssNav.setDeltaN(parseBroadcastDouble3(line,  pi.initialSpaces, RAD_PER_S));
  1504.                 pi.irnssNav.setM0(parseBroadcastDouble4(line,      pi.initialSpaces, Unit.RADIAN));
  1505.             }

  1506.             /** {@inheritDoc} */
  1507.             @Override
  1508.             public void parseSecondBroadcastOrbit(final String line, final ParseInfo pi) {
  1509.                 pi.irnssNav.setCuc(parseBroadcastDouble1(line, pi.initialSpaces, Unit.RADIAN));
  1510.                 pi.irnssNav.setE(parseBroadcastDouble2(line,     pi.initialSpaces, Unit.NONE));
  1511.                 pi.irnssNav.setCus(parseBroadcastDouble3(line,   pi.initialSpaces, Unit.RADIAN));
  1512.                 pi.irnssNav.setSqrtA(parseBroadcastDouble4(line, pi.initialSpaces, SQRT_M));
  1513.             }

  1514.             /** {@inheritDoc} */
  1515.             @Override
  1516.             public void parseThirdBroadcastOrbit(final String line, final ParseInfo pi) {
  1517.                 pi.irnssNav.setTime(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
  1518.                 pi.irnssNav.setCic(parseBroadcastDouble2(line,    pi.initialSpaces, Unit.RADIAN));
  1519.                 pi.irnssNav.setOmega0(parseBroadcastDouble3(line, pi.initialSpaces, Unit.RADIAN));
  1520.                 pi.irnssNav.setCis(parseBroadcastDouble4(line,    pi.initialSpaces, Unit.RADIAN));
  1521.             }

  1522.             /** {@inheritDoc} */
  1523.             @Override
  1524.             public void parseFourthBroadcastOrbit(final String line, final ParseInfo pi) {
  1525.                 pi.irnssNav.setI0(parseBroadcastDouble1(line, pi.initialSpaces, Unit.RADIAN));
  1526.                 pi.irnssNav.setCrc(parseBroadcastDouble2(line,      pi.initialSpaces, Unit.METRE));
  1527.                 pi.irnssNav.setPa(parseBroadcastDouble3(line,       pi.initialSpaces, Unit.RADIAN));
  1528.                 pi.irnssNav.setOmegaDot(parseBroadcastDouble4(line, pi.initialSpaces, RAD_PER_S));
  1529.             }

  1530.             /** {@inheritDoc} */
  1531.             @Override
  1532.             public void parseFifthBroadcastOrbit(final String line, final ParseInfo pi) {
  1533.                 // iDot
  1534.                 pi.irnssNav.setIDot(parseBroadcastDouble1(line, pi.initialSpaces, RAD_PER_S));
  1535.                 // IRNSS week (to go with Toe)
  1536.                 pi.irnssNav.setWeek(parseBroadcastInt3(line, pi.initialSpaces));
  1537.                 pi.irnssNav.setDate(new GNSSDate(pi.irnssNav.getWeek(),
  1538.                                                  pi.irnssNav.getTime(),
  1539.                                                  SatelliteSystem.GPS, // in Rinex files, week number is aligned to GPS week!
  1540.                                                  pi.timeScales).getDate());
  1541.             }

  1542.             /** {@inheritDoc} */
  1543.             @Override
  1544.             public void parseSixthBroadcastOrbit(final String line, final ParseInfo pi) {
  1545.                 pi.irnssNav.setURA(parseBroadcastDouble1(line,      pi.initialSpaces, Unit.METRE));
  1546.                 pi.irnssNav.setSvHealth(parseBroadcastDouble2(line, pi.initialSpaces, Unit.NONE));
  1547.                 pi.irnssNav.setTGD(parseBroadcastDouble3(line,      pi.initialSpaces, Unit.SECOND));
  1548.             }

  1549.             /** {@inheritDoc} */
  1550.             @Override
  1551.             public void parseSeventhBroadcastOrbit(final String line, final ParseInfo pi) {
  1552.                 pi.irnssNav.setTransmissionTime(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
  1553.                 pi.closePendingMessage();
  1554.             }

  1555.             /** {@inheritDoc} */
  1556.             @Override
  1557.             public void closeMessage(final ParseInfo pi) {
  1558.                 pi.file.addIRNSSNavigationMessage(pi.irnssNav);
  1559.                 pi.irnssNav = null;
  1560.             }

  1561.         };

  1562.         /** Get the parse for navigation message.
  1563.          * @param system satellite system
  1564.          * @param type message type (null for Rinex 3.x)
  1565.          * @param parseInfo container for transient data
  1566.          * @param line line being parsed
  1567.          * @return the satellite system line parser
  1568.          */
  1569.         private static SatelliteSystemLineParser getParser(final SatelliteSystem system, final String type,
  1570.                                                            final ParseInfo parseInfo, final String line) {
  1571.             switch (system) {
  1572.                 case GPS :
  1573.                     if (type == null || type.equals(LegacyNavigationMessage.LNAV)) {
  1574.                         parseInfo.gpsLNav = new GPSLegacyNavigationMessage();
  1575.                         return GPS_LNAV;
  1576.                     } else if (type.equals(CivilianNavigationMessage.CNAV)) {
  1577.                         parseInfo.gpsCNav = new GPSCivilianNavigationMessage(false);
  1578.                         return GPS_CNAV;
  1579.                     } else if (type.equals(CivilianNavigationMessage.CNV2)) {
  1580.                         parseInfo.gpsCNav = new GPSCivilianNavigationMessage(true);
  1581.                         return GPS_CNAV;
  1582.                     }
  1583.                     break;
  1584.                 case GALILEO :
  1585.                     if (type == null || type.equals("INAV") || type.equals("FNAV")) {
  1586.                         parseInfo.galileoNav = new GalileoNavigationMessage();
  1587.                         return GALILEO;
  1588.                     }
  1589.                     break;
  1590.                 case GLONASS :
  1591.                     if (type == null || type.equals("FDMA")) {
  1592.                         parseInfo.glonassNav = new GLONASSNavigationMessage();
  1593.                         return GLONASS;
  1594.                     }
  1595.                     break;
  1596.                 case QZSS :
  1597.                     if (type == null || type.equals(LegacyNavigationMessage.LNAV)) {
  1598.                         parseInfo.qzssLNav = new QZSSLegacyNavigationMessage();
  1599.                         return QZSS_LNAV;
  1600.                     } else if (type.equals(CivilianNavigationMessage.CNAV)) {
  1601.                         parseInfo.qzssCNav = new QZSSCivilianNavigationMessage(false);
  1602.                         return QZSS_CNAV;
  1603.                     } else if (type.equals(CivilianNavigationMessage.CNV2)) {
  1604.                         parseInfo.qzssCNav = new QZSSCivilianNavigationMessage(true);
  1605.                         return QZSS_CNAV;
  1606.                     }
  1607.                     break;
  1608.                 case BEIDOU :
  1609.                     if (type == null ||
  1610.                         type.equals(BeidouLegacyNavigationMessage.D1) ||
  1611.                         type.equals(BeidouLegacyNavigationMessage.D2)) {
  1612.                         parseInfo.beidouLNav = new BeidouLegacyNavigationMessage();
  1613.                         return BEIDOU_D1_D2;
  1614.                     } else if (type.equals(BeidouCivilianNavigationMessage.CNV1)) {
  1615.                         parseInfo.beidouCNav = new BeidouCivilianNavigationMessage(Frequency.B1C);
  1616.                         return BEIDOU_CNV_123;
  1617.                     } else if (type.equals(BeidouCivilianNavigationMessage.CNV2)) {
  1618.                         parseInfo.beidouCNav = new BeidouCivilianNavigationMessage(Frequency.B2A);
  1619.                         return BEIDOU_CNV_123;
  1620.                     } else if (type.equals(BeidouCivilianNavigationMessage.CNV3)) {
  1621.                         parseInfo.beidouCNav = new BeidouCivilianNavigationMessage(Frequency.B2B);
  1622.                         return BEIDOU_CNV_123;
  1623.                     }
  1624.                     break;
  1625.                 case IRNSS :
  1626.                     if (type == null || type.equals("LNAV")) {
  1627.                         parseInfo.irnssNav = new IRNSSNavigationMessage();
  1628.                         return IRNSS;
  1629.                     }
  1630.                     break;
  1631.                 case SBAS :
  1632.                     if (type == null || type.equals("SBAS")) {
  1633.                         parseInfo.sbasNav = new SBASNavigationMessage();
  1634.                         return SBAS;
  1635.                     }
  1636.                     break;
  1637.                 default:
  1638.                     // do nothing, handle error after the switch
  1639.             }
  1640.             throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  1641.                                       parseInfo.lineNumber, parseInfo.name, line);
  1642.         }

  1643.         /**
  1644.          * Parse the SV/Epoch/Sv clock of the navigation message.
  1645.          * @param line line to read
  1646.          * @param timeScale time scale to use
  1647.          * @param message navigation message
  1648.          */
  1649.         protected void parseSvEpochSvClockLineRinex2(final String line, final TimeScale timeScale,
  1650.                                                      final AbstractNavigationMessage message) {
  1651.             // PRN
  1652.             message.setPRN(RinexUtils.parseInt(line, 0, 2));

  1653.             // Toc
  1654.             final int    year  = RinexUtils.convert2DigitsYear(RinexUtils.parseInt(line,  2, 3));
  1655.             final int    month = RinexUtils.parseInt(line,  5, 3);
  1656.             final int    day   = RinexUtils.parseInt(line,  8, 3);
  1657.             final int    hours = RinexUtils.parseInt(line, 11, 3);
  1658.             final int    min   = RinexUtils.parseInt(line, 14, 3);
  1659.             final double sec   = RinexUtils.parseDouble(line, 17, 5);
  1660.             message.setEpochToc(new AbsoluteDate( year, month, day, hours, min, sec, timeScale));

  1661.             // clock
  1662.             message.setAf0(RinexUtils.parseDouble(line, 22, 19));
  1663.             message.setAf1(RinexUtils.parseDouble(line, 41, 19));
  1664.             message.setAf2(RinexUtils.parseDouble(line, 60, 19));

  1665.         }

  1666.         /**
  1667.          * Parse the SV/Epoch/Sv clock of the navigation message.
  1668.          * @param line line to read
  1669.          * @param timeScale time scale to use
  1670.          * @param message navigation message
  1671.          */
  1672.         protected void parseSvEpochSvClockLine(final String line, final TimeScale timeScale,
  1673.                                                final AbstractNavigationMessage message) {
  1674.             // PRN
  1675.             message.setPRN(RinexUtils.parseInt(line, 1, 2));

  1676.             // Toc
  1677.             message.setEpochToc(parsePrnSvEpochClock(line, timeScale));

  1678.             // clock
  1679.             message.setAf0(RinexUtils.parseDouble(line, 23, 19));
  1680.             message.setAf1(RinexUtils.parseDouble(line, 42, 19));
  1681.             message.setAf2(RinexUtils.parseDouble(line, 61, 19));

  1682.         }

  1683.         /** Parse epoch field of a Sv/epoch/clock line.
  1684.          * @param line line to parse
  1685.          * @param timeScale time scale to use
  1686.          * @return parsed field
  1687.          */
  1688.         protected AbsoluteDate parsePrnSvEpochClock(final String line, final TimeScale timeScale) {
  1689.             final int year  = RinexUtils.parseInt(line, 4, 4);
  1690.             final int month = RinexUtils.parseInt(line, 9, 2);
  1691.             final int day   = RinexUtils.parseInt(line, 12, 2);
  1692.             final int hours = RinexUtils.parseInt(line, 15, 2);
  1693.             final int min   = RinexUtils.parseInt(line, 18, 2);
  1694.             final int sec   = RinexUtils.parseInt(line, 21, 2);
  1695.             return new AbsoluteDate(year, month, day, hours, min, sec, timeScale);
  1696.         }

  1697.         /** Parse double field 1 of a broadcast orbit line.
  1698.          * @param line line to parse
  1699.          * @param initialSpaces number of initial spaces in the line
  1700.          * @param unit unit to used for parsing the field
  1701.          * @return parsed field
  1702.          */
  1703.         protected double parseBroadcastDouble1(final String line, final int initialSpaces, final Unit unit) {
  1704.             return unit.toSI(RinexUtils.parseDouble(line, initialSpaces, 19));
  1705.         }

  1706.         /** Parse integer field 1 of a broadcast orbit line.
  1707.          * @param line line to parse
  1708.          * @param initialSpaces number of initial spaces in the line
  1709.          * @return parsed field
  1710.          */
  1711.         protected int parseBroadcastInt1(final String line, final int initialSpaces) {
  1712.             return (int) FastMath.rint(RinexUtils.parseDouble(line, initialSpaces, 19));
  1713.         }

  1714.         /** Parse double field 2 of a broadcast orbit line.
  1715.          * @param line line to parse
  1716.          * @param initialSpaces number of initial spaces in the line
  1717.          * @param unit unit to used for parsing the field
  1718.          * @return parsed field
  1719.          */
  1720.         protected double parseBroadcastDouble2(final String line, final int initialSpaces, final Unit unit) {
  1721.             return unit.toSI(RinexUtils.parseDouble(line, initialSpaces + 19, 19));
  1722.         }

  1723.         /** Parse integer field 2 of a broadcast orbit line.
  1724.          * @param line line to parse
  1725.          * @param initialSpaces number of initial spaces in the line
  1726.          * @return parsed field
  1727.          */
  1728.         protected int parseBroadcastInt2(final String line, final int initialSpaces) {
  1729.             return (int) FastMath.rint(RinexUtils.parseDouble(line, initialSpaces + 19, 19));
  1730.         }

  1731.         /** Parse double field 3 of a broadcast orbit line.
  1732.          * @param line line to parse
  1733.          * @param initialSpaces number of initial spaces in the line
  1734.          * @param unit unit to used for parsing the field
  1735.          * @return parsed field
  1736.          */
  1737.         protected double parseBroadcastDouble3(final String line, final int initialSpaces, final Unit unit) {
  1738.             return unit.toSI(RinexUtils.parseDouble(line, initialSpaces + 38, 19));
  1739.         }

  1740.         /** Parse integer field 3 of a broadcast orbit line.
  1741.          * @param line line to parse
  1742.          * @param initialSpaces number of initial spaces in the line
  1743.          * @return parsed field
  1744.          */
  1745.         protected int parseBroadcastInt3(final String line, final int initialSpaces) {
  1746.             return (int) FastMath.rint(RinexUtils.parseDouble(line, initialSpaces + 38, 19));
  1747.         }

  1748.         /** Parse double field 4 of a broadcast orbit line.
  1749.          * @param line line to parse
  1750.          * @param initialSpaces number of initial spaces in the line
  1751.          * @param unit unit to used for parsing the field
  1752.          * @return parsed field
  1753.          */
  1754.         protected double parseBroadcastDouble4(final String line, final int initialSpaces, final Unit unit) {
  1755.             return unit.toSI(RinexUtils.parseDouble(line, initialSpaces + 57, 19));
  1756.         }

  1757.         /** Parse integer field 4 of a broadcast orbit line.
  1758.          * @param line line to parse
  1759.          * @param initialSpaces number of initial spaces in the line
  1760.          * @return parsed field
  1761.          */
  1762.         protected int parseBroadcastInt4(final String line, final int initialSpaces) {
  1763.             return (int) FastMath.rint(RinexUtils.parseDouble(line, initialSpaces + 57, 19));
  1764.         }

  1765.         /**
  1766.          * Parse the SV/Epoch/Sv clock of the navigation message.
  1767.          * @param line line to read
  1768.          * @param pi holder for transient data
  1769.          */
  1770.         public abstract void parseSvEpochSvClockLine(String line, ParseInfo pi);

  1771.         /**
  1772.          * Parse the "BROADCASTORBIT - 1" line.
  1773.          * @param line line to read
  1774.          * @param pi holder for transient data
  1775.          */
  1776.         public abstract void parseFirstBroadcastOrbit(String line, ParseInfo pi);

  1777.         /**
  1778.          * Parse the "BROADCASTORBIT - 2" line.
  1779.          * @param line line to read
  1780.          * @param pi holder for transient data
  1781.          */
  1782.         public abstract void parseSecondBroadcastOrbit(String line, ParseInfo pi);

  1783.         /**
  1784.          * Parse the "BROADCASTORBIT - 3" line.
  1785.          * @param line line to read
  1786.          * @param pi holder for transient data
  1787.          */
  1788.         public abstract void parseThirdBroadcastOrbit(String line, ParseInfo pi);

  1789.         /**
  1790.          * Parse the "BROADCASTORBIT - 4" line.
  1791.          * @param line line to read
  1792.          * @param pi holder for transient data
  1793.          */
  1794.         public void parseFourthBroadcastOrbit(final String line, final ParseInfo pi) {
  1795.             // this should never be called (except by some tests that use reflection)
  1796.             throw new OrekitInternalError(null);
  1797.         }

  1798.         /**
  1799.          * Parse the "BROADCASTORBIT - 5" line.
  1800.          * @param line line to read
  1801.          * @param pi holder for transient data
  1802.          */
  1803.         public void parseFifthBroadcastOrbit(final String line, final ParseInfo pi) {
  1804.             // this should never be called (except by some tests that use reflection)
  1805.             throw new OrekitInternalError(null);
  1806.         }

  1807.         /**
  1808.          * Parse the "BROADCASTORBIT - 6" line.
  1809.          * @param line line to read
  1810.          * @param pi holder for transient data
  1811.          */
  1812.         public void parseSixthBroadcastOrbit(final String line, final ParseInfo pi) {
  1813.             // this should never be called (except by some tests that use reflection)
  1814.             throw new OrekitInternalError(null);
  1815.         }

  1816.         /**
  1817.          * Parse the "BROADCASTORBIT - 7" line.
  1818.          * @param line line to read
  1819.          * @param pi holder for transient data
  1820.          */
  1821.         public void parseSeventhBroadcastOrbit(final String line, final ParseInfo pi) {
  1822.             // this should never be called (except by some tests that use reflection)
  1823.             throw new OrekitInternalError(null);
  1824.         }

  1825.         /**
  1826.          * Parse the "BROADCASTORBIT - 8" line.
  1827.          * @param line line to read
  1828.          * @param pi holder for transient data
  1829.          */
  1830.         public void parseEighthBroadcastOrbit(final String line, final ParseInfo pi) {
  1831.             // this should never be called (except by some tests that use reflection)
  1832.             throw new OrekitInternalError(null);
  1833.         }

  1834.         /**
  1835.          * Parse the "BROADCASTORBIT - 9" line.
  1836.          * @param line line to read
  1837.          * @param pi holder for transient data
  1838.          */
  1839.         public void parseNinthBroadcastOrbit(final String line, final ParseInfo pi) {
  1840.             // this should never be called (except by some tests that use reflection)
  1841.             throw new OrekitInternalError(null);
  1842.         }

  1843.         /**
  1844.          * Close a message as last line was parsed.
  1845.          * @param pi holder for transient data
  1846.          */
  1847.         public abstract void closeMessage(ParseInfo pi);

  1848.         /**
  1849.          * Calculates the floating-point remainder of a / b.
  1850.          * <p>
  1851.          * fmod = a - x * b
  1852.          * where x = (int) a / b
  1853.          * </p>
  1854.          * @param a numerator
  1855.          * @param b denominator
  1856.          * @return the floating-point remainder of a / b
  1857.          */
  1858.         private static double fmod(final double a, final double b) {
  1859.             final double x = (int) (a / b);
  1860.             return a - x * b;
  1861.         }

  1862.     }

  1863.     /** Parsing method. */
  1864.     @FunctionalInterface
  1865.     private interface ParsingMethod {
  1866.         /** Parse a line.
  1867.          * @param line line to parse
  1868.          * @param parseInfo holder for transient data
  1869.          */
  1870.         void parse(String line, ParseInfo parseInfo);
  1871.     }

  1872. }