RinexNavigationParser.java

  1. /* Copyright 2002-2025 CS GROUP
  2.  * Licensed to CS GROUP (CS) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * CS licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *   http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.orekit.files.rinex.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.PredefinedGnssSignal;
  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.NavICL1NVNavigationMessage;
  47. import org.orekit.propagation.analytical.gnss.data.NavICLegacyNavigationMessage;
  48. import org.orekit.propagation.analytical.gnss.data.LegacyNavigationMessage;
  49. import org.orekit.propagation.analytical.gnss.data.QZSSCivilianNavigationMessage;
  50. import org.orekit.propagation.analytical.gnss.data.QZSSLegacyNavigationMessage;
  51. import org.orekit.propagation.analytical.gnss.data.SBASNavigationMessage;
  52. import org.orekit.time.AbsoluteDate;
  53. import org.orekit.time.GNSSDate;
  54. import org.orekit.time.TimeScale;
  55. import org.orekit.time.TimeScales;
  56. import org.orekit.utils.Constants;
  57. import org.orekit.utils.units.Unit;

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

  80.     /** Converter for positions. */
  81.     private static final Unit KM = Unit.KILOMETRE;

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

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

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

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

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

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

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

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

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

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

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

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

  106.     /** System initials. */
  107.     private static final String INITIALS = "GRECIJS";

  108.     /** URA index to URA mapping (table 23 of NavIC ICD). */
  109.     // CHECKSTYLE: stop Indentation check
  110.     private static final double[] NAVIC_URA = {
  111.            2.40,    3.40,    4.85,   6.85,
  112.            9.65,   13.65,   24.00,  48.00,
  113.           96.00,  192.00,  384.00, 768.00,
  114.         1536.00, 3072.00, 6144.00, Double.NaN
  115.     };
  116.     // CHECKSTYLE: resume Indentation check

  117.     /** Set of time scales. */
  118.     private final TimeScales timeScales;

  119.     /**
  120.      * Constructor.
  121.      * <p>This constructor uses the {@link DataContext#getDefault() default data context}.</p>
  122.      * @see #RinexNavigationParser(TimeScales)
  123.      *
  124.      */
  125.     @DefaultDataContext
  126.     public RinexNavigationParser() {
  127.         this(DataContext.getDefault().getTimeScales());
  128.     }

  129.     /**
  130.      * Constructor.
  131.      * @param timeScales the set of time scales used for parsing dates.
  132.      */
  133.     public RinexNavigationParser(final TimeScales timeScales) {
  134.         this.timeScales = timeScales;
  135.     }

  136.     /**
  137.      * Parse RINEX navigation messages.
  138.      * @param source source providing the data to parse
  139.      * @return a parsed  RINEX navigation messages file
  140.      * @throws IOException if {@code reader} throws one
  141.      */
  142.     public RinexNavigation parse(final DataSource source) throws IOException {

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

  145.         Iterable<LineParser> candidateParsers = Collections.singleton(LineParser.HEADER_VERSION);
  146.         try (Reader reader = source.getOpener().openReaderOnce();
  147.              BufferedReader br = new BufferedReader(reader)) {
  148.             nextLine:
  149.                 for (String line = br.readLine(); line != null; line = br.readLine()) {
  150.                     ++pi.lineNumber;
  151.                     for (final LineParser candidate : candidateParsers) {
  152.                         if (candidate.canHandle.test(line)) {
  153.                             try {
  154.                                 candidate.parsingMethod.parse(line, pi);
  155.                                 candidateParsers = candidate.allowedNextProvider.apply(pi);
  156.                                 continue nextLine;
  157.                             } catch (StringIndexOutOfBoundsException | NumberFormatException | InputMismatchException e) {
  158.                                 throw new OrekitException(e,
  159.                                                           OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  160.                                                           pi.lineNumber, source.getName(), line);
  161.                             }
  162.                         }
  163.                     }
  164.                     throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  165.                                               pi.lineNumber, source.getName(), line);
  166.                 }
  167.         }

  168.         if (!pi.headerParsed) {
  169.             throw new OrekitException(OrekitMessages.UNEXPECTED_END_OF_FILE, source.getName());
  170.         }

  171.         pi.closePendingMessage();

  172.         return pi.file;

  173.     }

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

  176.         /** Name of the data source. */
  177.         private final String name;

  178.         /** Set of time scales for parsing dates. */
  179.         private final TimeScales timeScales;

  180.         /** The corresponding navigation messages file object. */
  181.         private final RinexNavigation file;

  182.         /** Number of initial spaces in broadcast orbits lines. */
  183.         private int initialSpaces;

  184.         /** Flag indicating header has been completely parsed. */
  185.         private boolean headerParsed;

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

  188.         /** Satellite system line parser. */
  189.         private SatelliteSystemLineParser systemLineParser;

  190.         /** Current global line number. */
  191.         private int lineNumber;

  192.         /** Current line number within the navigation message. */
  193.         private int messageLineNumber;

  194.         /** Container for GPS navigation message. */
  195.         private GPSLegacyNavigationMessage gpsLNav;

  196.         /** Container for GPS navigation message. */
  197.         private GPSCivilianNavigationMessage gpsCNav;

  198.         /** Container for Galileo navigation message. */
  199.         private GalileoNavigationMessage galileoNav;

  200.         /** Container for Beidou navigation message. */
  201.         private BeidouLegacyNavigationMessage beidouLNav;

  202.         /** Container for Beidou navigation message. */
  203.         private BeidouCivilianNavigationMessage beidouCNav;

  204.         /** Container for QZSS navigation message. */
  205.         private QZSSLegacyNavigationMessage qzssLNav;

  206.         /** Container for QZSS navigation message. */
  207.         private QZSSCivilianNavigationMessage qzssCNav;

  208.         /** Container for NavIC navigation message. */
  209.         private NavICLegacyNavigationMessage navicLNav;

  210.         /** Container for NavIC navigation message.
  211.          * @since 13.0
  212.          */
  213.         private NavICL1NVNavigationMessage navicL1NV;

  214.         /** Container for GLONASS navigation message. */
  215.         private GLONASSNavigationMessage glonassNav;

  216.         /** Container for SBAS navigation message. */
  217.         private SBASNavigationMessage sbasNav;

  218.         /** Container for System Time Offset message. */
  219.         private SystemTimeOffsetMessage sto;

  220.         /** Container for Earth Orientation Parameter message. */
  221.         private EarthOrientationParameterMessage eop;

  222.         /** Container for ionosphere Klobuchar message. */
  223.         private IonosphereKlobucharMessage klobuchar;

  224.         /** Container for ionosphere Nequick-G message. */
  225.         private IonosphereNequickGMessage nequickG;

  226.         /** Container for ionosphere BDGIM message. */
  227.         private IonosphereBDGIMMessage bdgim;

  228.         /** Constructor, build the ParseInfo object.
  229.          * @param name name of the data source
  230.          */
  231.         ParseInfo(final String name) {
  232.             // Initialize default values for fields
  233.             this.name                         = name;
  234.             this.timeScales                   = RinexNavigationParser.this.timeScales;
  235.             this.isIonosphereAlphaInitialized = false;
  236.             this.file                         = new RinexNavigation();

  237.         }

  238.         /** Ensure navigation message has been closed.
  239.          */
  240.         void closePendingMessage() {
  241.             if (systemLineParser != null) {
  242.                 systemLineParser.closeMessage(this);
  243.                 systemLineParser = null;
  244.             }

  245.         }

  246.     }

  247.     /** Parsers for specific lines. */
  248.     private enum LineParser {

  249.         /** Parser for version, file type and satellite system. */
  250.         HEADER_VERSION(line -> RinexUtils.matchesLabel(line, "RINEX VERSION / TYPE"),
  251.                        (line, pi) -> {
  252.                            RinexUtils.parseVersionFileTypeSatelliteSystem(line, pi.name, pi.file.getHeader(),
  253.                                                                           2.0, 2.01, 2.10, 2.11,
  254.                                                                           3.01, 3.02, 3.03, 3.04, 3.05,
  255.                                                                           4.00, 4.01, 4.02);
  256.                            pi.initialSpaces = pi.file.getHeader().getFormatVersion() < 3.0 ? 3 : 4;
  257.                        },
  258.                        LineParser::headerNext),

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

  263.         /** Parser for comments. */
  264.         HEADER_COMMENT(line -> RinexUtils.matchesLabel(line, "COMMENT"),
  265.                        (line, pi) -> RinexUtils.parseComment(pi.lineNumber, line, pi.file),
  266.                        LineParser::headerNext),

  267.         /** Parser for ionospheric correction parameters. */
  268.         HEADER_ION_ALPHA(line -> RinexUtils.matchesLabel(line, "ION ALPHA"),
  269.                          (line, pi) -> {

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

  271.                              // Read coefficients
  272.                              final double[] parameters = new double[4];
  273.                              parameters[0] = RinexUtils.parseDouble(line, 2,  12);
  274.                              parameters[1] = RinexUtils.parseDouble(line, 14, 12);
  275.                              parameters[2] = RinexUtils.parseDouble(line, 26, 12);
  276.                              parameters[3] = RinexUtils.parseDouble(line, 38, 12);
  277.                              pi.file.setKlobucharAlpha(parameters);
  278.                              pi.isIonosphereAlphaInitialized = true;

  279.                          },
  280.                          LineParser::headerNext),

  281.         /** Parser for ionospheric correction parameters. */
  282.         HEADER_ION_BETA(line -> RinexUtils.matchesLabel(line, "ION BETA"),
  283.                         (line, pi) -> {

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

  285.                             // Read coefficients
  286.                             final double[] parameters = new double[4];
  287.                             parameters[0] = RinexUtils.parseDouble(line, 2,  12);
  288.                             parameters[1] = RinexUtils.parseDouble(line, 14, 12);
  289.                             parameters[2] = RinexUtils.parseDouble(line, 26, 12);
  290.                             parameters[3] = RinexUtils.parseDouble(line, 38, 12);
  291.                             pi.file.setKlobucharBeta(parameters);

  292.                         },
  293.                         LineParser::headerNext),

  294.         /** Parser for ionospheric correction parameters. */
  295.         HEADER_IONOSPHERIC(line -> RinexUtils.matchesLabel(line, "IONOSPHERIC CORR"),
  296.                            (line, pi) -> {

  297.                                // ionospheric correction type
  298.                                final IonosphericCorrectionType ionoType =
  299.                                                IonosphericCorrectionType.valueOf(RinexUtils.parseString(line, 0, 3));
  300.                                pi.file.getHeader().setIonosphericCorrectionType(ionoType);

  301.                                // Read coefficients
  302.                                final double[] parameters = new double[4];
  303.                                parameters[0] = RinexUtils.parseDouble(line, 5,  12);
  304.                                parameters[1] = RinexUtils.parseDouble(line, 17, 12);
  305.                                parameters[2] = RinexUtils.parseDouble(line, 29, 12);
  306.                                parameters[3] = RinexUtils.parseDouble(line, 41, 12);

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

  309.                                    // We are parsing Galileo ionospheric parameters
  310.                                    pi.file.setNeQuickAlpha(parameters);

  311.                                } else {
  312.                                    // We are parsing Klobuchar ionospheric parameters

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

  315.                                        // Ionospheric "beta" parameters
  316.                                        pi.file.setKlobucharBeta(parameters);

  317.                                    } else {

  318.                                        // Ionospheric "alpha" parameters
  319.                                        pi.file.setKlobucharAlpha(parameters);

  320.                                        // Set the flag to true
  321.                                        pi.isIonosphereAlphaInitialized = true;

  322.                                    }

  323.                                }

  324.                            },
  325.                            LineParser::headerNext),

  326.         /** Parser for corrections to transform the system time to UTC or to other time systems. */
  327.         HEADER_DELTA_UTC(line -> RinexUtils.matchesLabel(line, "DELTA-UTC: A0,A1,T,W"),
  328.                          (line, pi) -> {
  329.                              // Read fields
  330.                              final double a0      = RinexUtils.parseDouble(line, 3,  19);
  331.                              final double a1      = RinexUtils.parseDouble(line, 22, 19);
  332.                              final int    refTime = RinexUtils.parseInt(line, 41, 9);
  333.                              final int    refWeek = RinexUtils.parseInt(line, 50, 9);

  334.                              // convert date
  335.                              final SatelliteSystem satSystem = pi.file.getHeader().getSatelliteSystem();
  336.                              final AbsoluteDate    date      = new GNSSDate(refWeek, refTime, satSystem, pi.timeScales).getDate();

  337.                              // Add to the list
  338.                              final TimeSystemCorrection tsc = new TimeSystemCorrection("GPUT", date, a0, a1);
  339.                              pi.file.getHeader().addTimeSystemCorrections(tsc);
  340.                          },
  341.                          LineParser::headerNext),

  342.         /** Parser for corrections to transform the GLONASS system time to UTC or to other time systems. */
  343.         HEADER_CORR_SYSTEM_TIME(line -> RinexUtils.matchesLabel(line, "CORR TO SYSTEM TIME"),
  344.                          (line, pi) -> {
  345.                              // Read fields
  346.                              final int year        = RinexUtils.parseInt(line,  0, 6);
  347.                              final int month       = RinexUtils.parseInt(line,  6, 6);
  348.                              final int day         = RinexUtils.parseInt(line, 12, 6);
  349.                              final double minusTau = RinexUtils.parseDouble(line, 21, 19);

  350.                              // convert date
  351.                              final SatelliteSystem satSystem = pi.file.getHeader().getSatelliteSystem();
  352.                              final TimeScale       timeScale = satSystem.getObservationTimeScale().getTimeScale(pi.timeScales);
  353.                              final AbsoluteDate    date      = new AbsoluteDate(year, month, day, timeScale);

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

  357.                          },
  358.                          LineParser::headerNext),

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

  362.                         // Read fields
  363.                         final String type    = RinexUtils.parseString(line, 0,  4);
  364.                         final double a0      = RinexUtils.parseDouble(line, 5,  17);
  365.                         final double a1      = RinexUtils.parseDouble(line, 22, 16);
  366.                         final int    refTime = RinexUtils.parseInt(line, 38, 7);
  367.                         final int    refWeek = RinexUtils.parseInt(line, 46, 5);

  368.                         // convert date
  369.                         final SatelliteSystem satSystem = pi.file.getHeader().getSatelliteSystem();
  370.                         final AbsoluteDate    date;
  371.                         if (satSystem == SatelliteSystem.GLONASS) {
  372.                             date = null;
  373.                         } else if (satSystem == SatelliteSystem.BEIDOU) {
  374.                             date = new GNSSDate(refWeek, refTime, satSystem, pi.timeScales).getDate();
  375.                         } else {
  376.                             // all other systems are converted to GPS week in Rinex files!
  377.                             date = new GNSSDate(refWeek, refTime, SatelliteSystem.GPS, pi.timeScales).getDate();
  378.                         }

  379.                         // Add to the list
  380.                         final TimeSystemCorrection tsc = new TimeSystemCorrection(type, date, a0, a1);
  381.                         pi.file.getHeader().addTimeSystemCorrections(tsc);

  382.                     },
  383.                     LineParser::headerNext),

  384.         /** Parser for leap seconds. */
  385.         HEADER_LEAP_SECONDS(line -> RinexUtils.matchesLabel(line, "LEAP SECONDS"),
  386.                             (line, pi) -> pi.file.getHeader().setNumberOfLeapSeconds(RinexUtils.parseInt(line, 0, 6)),
  387.                             LineParser::headerNext),

  388.         /** Parser for DOI.
  389.          * @since 12.0
  390.          */
  391.         HEADER_DOI(line -> RinexUtils.matchesLabel(line, "DOI"),
  392.                    (line, pi) -> pi.file.getHeader().setDoi(RinexUtils.parseString(line, 0, RinexUtils.LABEL_INDEX)),
  393.                    LineParser::headerNext),

  394.         /** Parser for license.
  395.          * @since 12.0
  396.          */
  397.         HEADER_LICENSE(line -> RinexUtils.matchesLabel(line, "LICENSE OF USE"),
  398.                        (line, pi) -> pi.file.getHeader().setLicense(RinexUtils.parseString(line, 0, RinexUtils.LABEL_INDEX)),
  399.                        LineParser::headerNext),

  400.         /** Parser for stationInformation.
  401.          * @since 12.0
  402.          */
  403.         HEADER_STATION_INFORMATION(line -> RinexUtils.matchesLabel(line, "STATION INFORMATION"),
  404.                                    (line, pi) -> pi.file.getHeader().setStationInformation(RinexUtils.parseString(line, 0, RinexUtils.LABEL_INDEX)),
  405.                                    LineParser::headerNext),

  406.         /** Parser for merged files.
  407.          * @since 12.0
  408.          */
  409.         HEADER_MERGED_FILE(line -> RinexUtils.matchesLabel(line, "MERGED FILE"),
  410.                            (line, pi) -> pi.file.getHeader().setMergedFiles(RinexUtils.parseInt(line, 0, 9)),
  411.                            LineParser::headerNext),

  412.        /** Parser for the end of header. */
  413.         HEADER_END(line -> RinexUtils.matchesLabel(line, "END OF HEADER"),
  414.                    (line, pi) -> {
  415.                        // get rinex format version
  416.                        final RinexNavigationHeader header = pi.file.getHeader();
  417.                        final double version = header.getFormatVersion();

  418.                        // check mandatory header fields
  419.                        if (header.getRunByName() == null ||
  420.                            version >= 4 && header.getNumberOfLeapSeconds() < 0) {
  421.                            throw new OrekitException(OrekitMessages.INCOMPLETE_HEADER, pi.name);
  422.                        }

  423.                        pi.headerParsed = true;

  424.                    },
  425.                    LineParser::navigationNext),

  426.         /** Parser for navigation message space vehicle epoch and clock. */
  427.         NAVIGATION_SV_EPOCH_CLOCK_RINEX_2(line -> true,
  428.                                           (line, pi) -> {

  429.                                               // Set the line number to 0
  430.                                               pi.messageLineNumber = 0;

  431.                                               // Initialize parser
  432.                                               pi.closePendingMessage();
  433.                                               pi.systemLineParser = SatelliteSystemLineParser.getParser(pi.file.getHeader().getSatelliteSystem(),
  434.                                                                                                         null, pi, line);

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

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

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

  441.                                       // Set the line number to 0
  442.                                       pi.messageLineNumber = 0;

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

  446.                                           // Initialize parser
  447.                                           pi.closePendingMessage();
  448.                                           pi.systemLineParser = SatelliteSystemLineParser.getParser(system, null, pi, line);
  449.                                       }

  450.                                       // Read first line
  451.                                       pi.systemLineParser.parseSvEpochSvClockLine(line, pi);

  452.                                   },
  453.                                   LineParser::navigationNext),

  454.         /** Parser for navigation message type. */
  455.         EPH_TYPE(line -> line.startsWith("> EPH"),
  456.                  (line, pi) -> {
  457.                      final SatelliteSystem system = SatelliteSystem.parseSatelliteSystem(RinexUtils.parseString(line, 6, 1));
  458.                      final String          type   = RinexUtils.parseString(line, 10, 4);
  459.                      pi.closePendingMessage();
  460.                      pi.systemLineParser = SatelliteSystemLineParser.getParser(system, type, pi, line);
  461.                  },
  462.                  pi -> Collections.singleton(NAVIGATION_SV_EPOCH_CLOCK)),

  463.         /** Parser for broadcast orbit. */
  464.         BROADCAST_ORBIT(line -> line.startsWith("   "),
  465.                         (line, pi) -> {
  466.                             switch (++pi.messageLineNumber) {
  467.                                 case 1: pi.systemLineParser.parseFirstBroadcastOrbit(line, pi);
  468.                                 break;
  469.                                 case 2: pi.systemLineParser.parseSecondBroadcastOrbit(line, pi);
  470.                                 break;
  471.                                 case 3: pi.systemLineParser.parseThirdBroadcastOrbit(line, pi);
  472.                                 break;
  473.                                 case 4: pi.systemLineParser.parseFourthBroadcastOrbit(line, pi);
  474.                                 break;
  475.                                 case 5: pi.systemLineParser.parseFifthBroadcastOrbit(line, pi);
  476.                                 break;
  477.                                 case 6: pi.systemLineParser.parseSixthBroadcastOrbit(line, pi);
  478.                                 break;
  479.                                 case 7: pi.systemLineParser.parseSeventhBroadcastOrbit(line, pi);
  480.                                 break;
  481.                                 case 8: pi.systemLineParser.parseEighthBroadcastOrbit(line, pi);
  482.                                 break;
  483.                                 case 9: pi.systemLineParser.parseNinthBroadcastOrbit(line, pi);
  484.                                 break;
  485.                                 default:
  486.                                     // this should never happen
  487.                                     throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  488.                                                               pi.lineNumber, pi.name, line);
  489.                             }

  490.                         },
  491.                         LineParser::navigationNext),

  492.         /** Parser for system time offset message model. */
  493.         STO_LINE_1(line -> true,
  494.                    (line, pi) -> {
  495.                        pi.sto.setTransmissionTime(Unit.SECOND.toSI(RinexUtils.parseDouble(line,  4, 19)));
  496.                        pi.sto.setA0(Unit.SECOND.toSI(RinexUtils.parseDouble(line, 23, 19)));
  497.                        pi.sto.setA1(S_PER_S.toSI(RinexUtils.parseDouble(line, 42, 19)));
  498.                        pi.sto.setA2(S_PER_S2.toSI(RinexUtils.parseDouble(line, 61, 19)));
  499.                        pi.file.addSystemTimeOffset(pi.sto);
  500.                        pi.sto = null;
  501.                    },
  502.                    LineParser::navigationNext),

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

  506.                                pi.sto.setDefinedTimeSystem(TimeSystem.parseTwoLettersCode(RinexUtils.parseString(line, 24, 2)));
  507.                                pi.sto.setReferenceTimeSystem(TimeSystem.parseTwoLettersCode(RinexUtils.parseString(line, 26, 2)));
  508.                                final String sbas = RinexUtils.parseString(line, 43, 18);
  509.                                pi.sto.setSbasId(!sbas.isEmpty() ? SbasId.valueOf(sbas) : null);
  510.                                final String utc = RinexUtils.parseString(line, 62, 18);
  511.                                pi.sto.setUtcId(!utc.isEmpty() ? UtcId.parseUtcId(utc) : null);

  512.                                // TODO is the reference date relative to one or the other time scale?
  513.                                final int year  = RinexUtils.parseInt(line, 4, 4);
  514.                                final int month = RinexUtils.parseInt(line, 9, 2);
  515.                                final int day   = RinexUtils.parseInt(line, 12, 2);
  516.                                final int hours = RinexUtils.parseInt(line, 15, 2);
  517.                                final int min   = RinexUtils.parseInt(line, 18, 2);
  518.                                final int sec   = RinexUtils.parseInt(line, 21, 2);
  519.                                pi.sto.setReferenceEpoch(new AbsoluteDate(year, month, day, hours, min, sec,
  520.                                                                          pi.sto.getDefinedTimeSystem().getTimeScale(pi.timeScales)));

  521.                            },
  522.                            pi -> Collections.singleton(STO_LINE_1)),

  523.         /** Parser for system time offset message type. */
  524.         STO_TYPE(line -> line.startsWith("> STO"),
  525.                  (line, pi) -> {
  526.                      pi.closePendingMessage();
  527.                      pi.sto = new SystemTimeOffsetMessage(SatelliteSystem.parseSatelliteSystem(RinexUtils.parseString(line, 6, 1)),
  528.                                                           RinexUtils.parseInt(line, 7, 2),
  529.                                                           RinexUtils.parseString(line, 10, 4));
  530.                  },
  531.                  pi -> Collections.singleton(STO_SV_EPOCH_CLOCK)),

  532.         /** Parser for Earth orientation parameter message model. */
  533.         EOP_LINE_2(line -> true,
  534.                    (line, pi) -> {
  535.                        pi.eop.setTransmissionTime(Unit.SECOND.toSI(RinexUtils.parseDouble(line,  4, 19)));
  536.                        pi.eop.setDut1(Unit.SECOND.toSI(RinexUtils.parseDouble(line, 23, 19)));
  537.                        pi.eop.setDut1Dot(S_PER_DAY.toSI(RinexUtils.parseDouble(line, 42, 19)));
  538.                        pi.eop.setDut1DotDot(S_PER_DAY2.toSI(RinexUtils.parseDouble(line, 61, 19)));
  539.                        pi.file.addEarthOrientationParameter(pi.eop);
  540.                        pi.eop = null;
  541.                    },
  542.                    LineParser::navigationNext),

  543.         /** Parser for Earth orientation parameter message model. */
  544.         EOP_LINE_1(line -> true,
  545.                    (line, pi) -> {
  546.                        pi.eop.setYp(Unit.ARC_SECOND.toSI(RinexUtils.parseDouble(line, 23, 19)));
  547.                        pi.eop.setYpDot(AS_PER_DAY.toSI(RinexUtils.parseDouble(line, 42, 19)));
  548.                        pi.eop.setYpDotDot(AS_PER_DAY2.toSI(RinexUtils.parseDouble(line, 61, 19)));
  549.                    },
  550.                    pi -> Collections.singleton(EOP_LINE_2)),

  551.         /** Parser for Earth orientation parameter message model. */
  552.         EOP_LINE_0(line -> true,
  553.                            (line, pi) -> {
  554.                                final int year  = RinexUtils.parseInt(line, 4, 4);
  555.                                final int month = RinexUtils.parseInt(line, 9, 2);
  556.                                final int day   = RinexUtils.parseInt(line, 12, 2);
  557.                                final int hours = RinexUtils.parseInt(line, 15, 2);
  558.                                final int min   = RinexUtils.parseInt(line, 18, 2);
  559.                                final int sec   = RinexUtils.parseInt(line, 21, 2);
  560.                                pi.eop.setReferenceEpoch(new AbsoluteDate(year, month, day, hours, min, sec,
  561.                                                                          pi.eop.getSystem().getObservationTimeScale().getTimeScale(pi.timeScales)));
  562.                                pi.eop.setXp(Unit.ARC_SECOND.toSI(RinexUtils.parseDouble(line, 23, 19)));
  563.                                pi.eop.setXpDot(AS_PER_DAY.toSI(RinexUtils.parseDouble(line, 42, 19)));
  564.                                pi.eop.setXpDotDot(AS_PER_DAY2.toSI(RinexUtils.parseDouble(line, 61, 19)));
  565.                            },
  566.                            pi -> Collections.singleton(EOP_LINE_1)),

  567.         /** Parser for Earth orientation parameter message type. */
  568.         EOP_TYPE(line -> line.startsWith("> EOP"),
  569.                  (line, pi) -> {
  570.                      pi.closePendingMessage();
  571.                      pi.eop = new EarthOrientationParameterMessage(SatelliteSystem.parseSatelliteSystem(RinexUtils.parseString(line, 6, 1)),
  572.                                                                    RinexUtils.parseInt(line, 7, 2),
  573.                                                                    RinexUtils.parseString(line, 10, 4));
  574.                  },
  575.                  pi -> Collections.singleton(EOP_LINE_0)),

  576.         /** Parser for ionosphere Klobuchar message model. */
  577.         KLOBUCHAR_LINE_2(line -> true,
  578.                          (line, pi) -> {
  579.                              pi.klobuchar.setBetaI(3, IonosphereKlobucharMessage.S_PER_SC_N[3].toSI(RinexUtils.parseDouble(line,  4, 19)));
  580.                              pi.klobuchar.setRegionCode(RinexUtils.parseDouble(line, 23, 19) < 0.5 ?
  581.                                                         RegionCode.WIDE_AREA : RegionCode.JAPAN);
  582.                              pi.file.addKlobucharMessage(pi.klobuchar);
  583.                              pi.klobuchar = null;
  584.                          },
  585.                          LineParser::navigationNext),

  586.         /** Parser for ionosphere Klobuchar message model. */
  587.         KLOBUCHAR_LINE_1(line -> true,
  588.                          (line, pi) -> {
  589.                              pi.klobuchar.setAlphaI(3, IonosphereKlobucharMessage.S_PER_SC_N[3].toSI(RinexUtils.parseDouble(line,  4, 19)));
  590.                              pi.klobuchar.setBetaI(0, IonosphereKlobucharMessage.S_PER_SC_N[0].toSI(RinexUtils.parseDouble(line, 23, 19)));
  591.                              pi.klobuchar.setBetaI(1, IonosphereKlobucharMessage.S_PER_SC_N[1].toSI(RinexUtils.parseDouble(line, 42, 19)));
  592.                              pi.klobuchar.setBetaI(2, IonosphereKlobucharMessage.S_PER_SC_N[2].toSI(RinexUtils.parseDouble(line, 61, 19)));
  593.                          },
  594.                          pi -> Collections.singleton(KLOBUCHAR_LINE_2)),

  595.         /** Parser for ionosphere Klobuchar message model. */
  596.         KLOBUCHAR_LINE_0(line -> true,
  597.                          (line, pi) -> {
  598.                              final int year  = RinexUtils.parseInt(line, 4, 4);
  599.                              final int month = RinexUtils.parseInt(line, 9, 2);
  600.                              final int day   = RinexUtils.parseInt(line, 12, 2);
  601.                              final int hours = RinexUtils.parseInt(line, 15, 2);
  602.                              final int min   = RinexUtils.parseInt(line, 18, 2);
  603.                              final int sec   = RinexUtils.parseInt(line, 21, 2);
  604.                              pi.klobuchar.setTransmitTime(new AbsoluteDate(year, month, day, hours, min, sec,
  605.                                                                            pi.klobuchar.getSystem().getObservationTimeScale().getTimeScale(pi.timeScales)));
  606.                              pi.klobuchar.setAlphaI(0, IonosphereKlobucharMessage.S_PER_SC_N[0].toSI(RinexUtils.parseDouble(line, 23, 19)));
  607.                              pi.klobuchar.setAlphaI(1, IonosphereKlobucharMessage.S_PER_SC_N[1].toSI(RinexUtils.parseDouble(line, 42, 19)));
  608.                              pi.klobuchar.setAlphaI(2, IonosphereKlobucharMessage.S_PER_SC_N[2].toSI(RinexUtils.parseDouble(line, 61, 19)));
  609.                          },
  610.                          pi -> Collections.singleton(KLOBUCHAR_LINE_1)),

  611.         /** Parser for ionosphere Nequick-G message model. */
  612.         NEQUICK_LINE_1(line -> true,
  613.                        (line, pi) -> {
  614.                            pi.nequickG.setFlags((int) FastMath.rint(RinexUtils.parseDouble(line, 4, 19)));
  615.                            pi.file.addNequickGMessage(pi.nequickG);
  616.                            pi.nequickG = null;
  617.                        },
  618.                        LineParser::navigationNext),

  619.         /** Parser for ionosphere Nequick-G message model. */
  620.         NEQUICK_LINE_0(line -> true,
  621.                        (line, pi) -> {
  622.                            final int year  = RinexUtils.parseInt(line, 4, 4);
  623.                            final int month = RinexUtils.parseInt(line, 9, 2);
  624.                            final int day   = RinexUtils.parseInt(line, 12, 2);
  625.                            final int hours = RinexUtils.parseInt(line, 15, 2);
  626.                            final int min   = RinexUtils.parseInt(line, 18, 2);
  627.                            final int sec   = RinexUtils.parseInt(line, 21, 2);
  628.                            pi.nequickG.setTransmitTime(new AbsoluteDate(year, month, day, hours, min, sec,
  629.                                                                         pi.nequickG.getSystem().getObservationTimeScale().getTimeScale(pi.timeScales)));
  630.                            pi.nequickG.setAi0(IonosphereNequickGMessage.SFU.toSI(RinexUtils.parseDouble(line, 23, 19)));
  631.                            pi.nequickG.setAi1(IonosphereNequickGMessage.SFU_PER_DEG.toSI(RinexUtils.parseDouble(line, 42, 19)));
  632.                            pi.nequickG.setAi2(IonosphereNequickGMessage.SFU_PER_DEG2.toSI(RinexUtils.parseDouble(line, 61, 19)));
  633.                        },
  634.                        pi -> Collections.singleton(NEQUICK_LINE_1)),

  635.         /** Parser for ionosphere BDGIM message model. */
  636.         BDGIM_LINE_2(line -> true,
  637.                      (line, pi) -> {
  638.                          pi.bdgim.setAlphaI(7, Unit.TOTAL_ELECTRON_CONTENT_UNIT.toSI(RinexUtils.parseDouble(line,  4, 19)));
  639.                          pi.bdgim.setAlphaI(8, Unit.TOTAL_ELECTRON_CONTENT_UNIT.toSI(RinexUtils.parseDouble(line, 23, 19)));
  640.                          pi.file.addBDGIMMessage(pi.bdgim);
  641.                          pi.bdgim = null;
  642.                      },
  643.                      LineParser::navigationNext),

  644.         /** Parser for ionosphere BDGIM message model. */
  645.         BDGIM_LINE_1(line -> true,
  646.                      (line, pi) -> {
  647.                          pi.bdgim.setAlphaI(3, Unit.TOTAL_ELECTRON_CONTENT_UNIT.toSI(RinexUtils.parseDouble(line,  4, 19)));
  648.                          pi.bdgim.setAlphaI(4, Unit.TOTAL_ELECTRON_CONTENT_UNIT.toSI(RinexUtils.parseDouble(line, 23, 19)));
  649.                          pi.bdgim.setAlphaI(5, Unit.TOTAL_ELECTRON_CONTENT_UNIT.toSI(RinexUtils.parseDouble(line, 42, 19)));
  650.                          pi.bdgim.setAlphaI(6, Unit.TOTAL_ELECTRON_CONTENT_UNIT.toSI(RinexUtils.parseDouble(line, 61, 19)));
  651.                      },
  652.                      pi -> Collections.singleton(BDGIM_LINE_2)),

  653.         /** Parser for ionosphere BDGIM message model. */
  654.         BDGIM_LINE_0(line -> true,
  655.                      (line, pi) -> {
  656.                          final int year  = RinexUtils.parseInt(line, 4, 4);
  657.                          final int month = RinexUtils.parseInt(line, 9, 2);
  658.                          final int day   = RinexUtils.parseInt(line, 12, 2);
  659.                          final int hours = RinexUtils.parseInt(line, 15, 2);
  660.                          final int min   = RinexUtils.parseInt(line, 18, 2);
  661.                          final int sec   = RinexUtils.parseInt(line, 21, 2);
  662.                          pi.bdgim.setTransmitTime(new AbsoluteDate(year, month, day, hours, min, sec,
  663.                                                                    pi.bdgim.getSystem().getObservationTimeScale().getTimeScale(pi.timeScales)));
  664.                          pi.bdgim.setAlphaI(0, Unit.TOTAL_ELECTRON_CONTENT_UNIT.toSI(RinexUtils.parseDouble(line, 23, 19)));
  665.                          pi.bdgim.setAlphaI(1, Unit.TOTAL_ELECTRON_CONTENT_UNIT.toSI(RinexUtils.parseDouble(line, 42, 19)));
  666.                          pi.bdgim.setAlphaI(2, Unit.TOTAL_ELECTRON_CONTENT_UNIT.toSI(RinexUtils.parseDouble(line, 61, 19)));
  667.                      },
  668.                      pi -> Collections.singleton(BDGIM_LINE_1)),

  669.         /** Parser for ionosphere message type. */
  670.         IONO_TYPE(line -> line.startsWith("> ION"),
  671.                   (line, pi) -> {
  672.                       pi.closePendingMessage();
  673.                       final SatelliteSystem system = SatelliteSystem.parseSatelliteSystem(RinexUtils.parseString(line, 6, 1));
  674.                       final int             prn    = RinexUtils.parseInt(line, 7, 2);
  675.                       final String          type   = RinexUtils.parseString(line, 10, 4);
  676.                       if (system == SatelliteSystem.GALILEO) {
  677.                           pi.nequickG = new IonosphereNequickGMessage(system, prn, type);
  678.                       } else {
  679.                           // in Rinex 4.00, tables A32 and A34 are ambiguous as both seem to apply
  680.                           // to Beidou CNVX messages, we consider BDGIM is the proper model in this case
  681.                           if (system == SatelliteSystem.BEIDOU && "CNVX".equals(type)) {
  682.                               pi.bdgim = new IonosphereBDGIMMessage(system, prn, type);
  683.                           } else {
  684.                               pi.klobuchar = new IonosphereKlobucharMessage(system, prn, type);
  685.                           }
  686.                       }
  687.                   },
  688.                   pi -> Collections.singleton(pi.nequickG != null ? NEQUICK_LINE_0 : (pi.bdgim != null ? BDGIM_LINE_0 : KLOBUCHAR_LINE_0)));

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

  691.         /** Parsing method. */
  692.         private final ParsingMethod parsingMethod;

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

  695.         /** Simple constructor.
  696.          * @param canHandle predicate for identifying lines that can be parsed
  697.          * @param parsingMethod parsing method
  698.          * @param allowedNextProvider supplier for allowed parsers for next line
  699.          */
  700.         LineParser(final Predicate<String> canHandle, final ParsingMethod parsingMethod,
  701.                    final Function<ParseInfo, Iterable<LineParser>> allowedNextProvider) {
  702.             this.canHandle           = canHandle;
  703.             this.parsingMethod       = parsingMethod;
  704.             this.allowedNextProvider = allowedNextProvider;
  705.         }

  706.         /** Get the allowed parsers for next lines while parsing Rinex header.
  707.          * @param parseInfo holder for transient data
  708.          * @return allowed parsers for next line
  709.          */
  710.         private static Iterable<LineParser> headerNext(final ParseInfo parseInfo) {
  711.             if (parseInfo.file.getHeader().getFormatVersion() < 3) {
  712.                 // Rinex 2.x header entries
  713.                 return Arrays.asList(HEADER_COMMENT, HEADER_PROGRAM,
  714.                                      HEADER_ION_ALPHA, HEADER_ION_BETA,
  715.                                      HEADER_DELTA_UTC, HEADER_CORR_SYSTEM_TIME,
  716.                                      HEADER_LEAP_SECONDS, HEADER_END);
  717.             } else if (parseInfo.file.getHeader().getFormatVersion() < 4) {
  718.                 // Rinex 3.x header entries
  719.                 return Arrays.asList(HEADER_COMMENT, HEADER_PROGRAM,
  720.                                      HEADER_IONOSPHERIC, HEADER_TIME,
  721.                                      HEADER_LEAP_SECONDS, HEADER_END);
  722.             } else {
  723.                 // Rinex 4.x header entries
  724.                 return Arrays.asList(HEADER_COMMENT, HEADER_PROGRAM,
  725.                                      HEADER_DOI, HEADER_LICENSE, HEADER_STATION_INFORMATION, HEADER_MERGED_FILE,
  726.                                      HEADER_LEAP_SECONDS, HEADER_END);
  727.             }
  728.         }

  729.         /** Get the allowed parsers for next lines while parsing navigation date.
  730.          * @param parseInfo holder for transient data
  731.          * @return allowed parsers for next line
  732.          */
  733.         private static Iterable<LineParser> navigationNext(final ParseInfo parseInfo) {
  734.             if (parseInfo.gpsLNav    != null || parseInfo.gpsCNav    != null || parseInfo.galileoNav != null ||
  735.                 parseInfo.beidouLNav != null || parseInfo.beidouCNav != null || parseInfo.qzssLNav   != null ||
  736.                 parseInfo.qzssCNav   != null || parseInfo.navicLNav  != null || parseInfo.navicL1NV  != null ||
  737.                 parseInfo.sbasNav    != null) {
  738.                 return Collections.singleton(BROADCAST_ORBIT);
  739.             } else if (parseInfo.glonassNav != null) {
  740.                 if (parseInfo.messageLineNumber < 3) {
  741.                     return Collections.singleton(BROADCAST_ORBIT);
  742.                 } else {
  743.                     // workaround for some invalid files that should nevertheless be parsed
  744.                     // we have encountered in the wild merged files that claimed to be in 3.05 version
  745.                     // and hence needed at least 4 broadcast GLONASS orbit lines (the fourth line was
  746.                     // introduced in 3.05), but in fact only had 3 broadcast lines. We think they were
  747.                     // merged from files in 3.04 or earlier format. In order to parse these files,
  748.                     // we accept after the third line either another broadcast orbit line or a new message
  749.                     if (parseInfo.file.getHeader().getFormatVersion() < 4) {
  750.                         return Arrays.asList(BROADCAST_ORBIT, NAVIGATION_SV_EPOCH_CLOCK);
  751.                     } else {
  752.                         return Arrays.asList(BROADCAST_ORBIT, EPH_TYPE, STO_TYPE, EOP_TYPE, IONO_TYPE);
  753.                     }
  754.                 }
  755.             } else if (parseInfo.file.getHeader().getFormatVersion() < 3) {
  756.                 return Collections.singleton(NAVIGATION_SV_EPOCH_CLOCK_RINEX_2);
  757.             } else if (parseInfo.file.getHeader().getFormatVersion() < 4) {
  758.                 return Collections.singleton(NAVIGATION_SV_EPOCH_CLOCK);
  759.             } else {
  760.                 return Arrays.asList(EPH_TYPE, STO_TYPE, EOP_TYPE, IONO_TYPE);
  761.             }
  762.         }

  763.     }

  764.     /** Parsers for satellite system specific lines. */
  765.     private enum SatelliteSystemLineParser {

  766.         /** GPS legacy. */
  767.         GPS_LNAV() {

  768.             /** {@inheritDoc} */
  769.             @Override
  770.             public void parseSvEpochSvClockLine(final String line, final ParseInfo pi) {
  771.                 if (pi.file.getHeader().getFormatVersion() < 3.0) {
  772.                     parseSvEpochSvClockLineRinex2(line, pi.timeScales.getGPS(), pi.gpsLNav);
  773.                 } else {
  774.                     parseSvEpochSvClockLine(line, pi.timeScales.getGPS(), pi.gpsLNav);
  775.                 }
  776.             }

  777.             /** {@inheritDoc} */
  778.             @Override
  779.             public void parseFirstBroadcastOrbit(final String line, final ParseInfo pi) {
  780.                 pi.gpsLNav.setIODE(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
  781.                 pi.gpsLNav.setCrs(parseBroadcastDouble2(line,    pi.initialSpaces, Unit.METRE));
  782.                 pi.gpsLNav.setDeltaN0(parseBroadcastDouble3(line, pi.initialSpaces, RAD_PER_S));
  783.                 pi.gpsLNav.setM0(parseBroadcastDouble4(line,     pi.initialSpaces, Unit.RADIAN));
  784.             }

  785.             /** {@inheritDoc} */
  786.             @Override
  787.             public void parseSecondBroadcastOrbit(final String line, final ParseInfo pi) {
  788.                 pi.gpsLNav.setCuc(parseBroadcastDouble1(line, pi.initialSpaces, Unit.RADIAN));
  789.                 pi.gpsLNav.setE(parseBroadcastDouble2(line,     pi.initialSpaces, Unit.NONE));
  790.                 pi.gpsLNav.setCus(parseBroadcastDouble3(line,   pi.initialSpaces, Unit.RADIAN));
  791.                 pi.gpsLNav.setSqrtA(parseBroadcastDouble4(line, pi.initialSpaces, SQRT_M));
  792.             }

  793.             /** {@inheritDoc} */
  794.             @Override
  795.             public void parseThirdBroadcastOrbit(final String line, final ParseInfo pi) {
  796.                 pi.gpsLNav.setTime(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
  797.                 pi.gpsLNav.setCic(parseBroadcastDouble2(line,    pi.initialSpaces, Unit.RADIAN));
  798.                 pi.gpsLNav.setOmega0(parseBroadcastDouble3(line, pi.initialSpaces, Unit.RADIAN));
  799.                 pi.gpsLNav.setCis(parseBroadcastDouble4(line,    pi.initialSpaces, Unit.RADIAN));
  800.             }

  801.             /** {@inheritDoc} */
  802.             @Override
  803.             public void parseFourthBroadcastOrbit(final String line, final ParseInfo pi) {
  804.                 pi.gpsLNav.setI0(parseBroadcastDouble1(line, pi.initialSpaces, Unit.RADIAN));
  805.                 pi.gpsLNav.setCrc(parseBroadcastDouble2(line,      pi.initialSpaces, Unit.METRE));
  806.                 pi.gpsLNav.setPa(parseBroadcastDouble3(line,       pi.initialSpaces, Unit.RADIAN));
  807.                 pi.gpsLNav.setOmegaDot(parseBroadcastDouble4(line, pi.initialSpaces, RAD_PER_S));
  808.             }

  809.             /** {@inheritDoc} */
  810.             @Override
  811.             public void parseFifthBroadcastOrbit(final String line, final ParseInfo pi) {
  812.                 // iDot
  813.                 pi.gpsLNav.setIDot(parseBroadcastDouble1(line, pi.initialSpaces, RAD_PER_S));
  814.                 // Codes on L2 channel (ignored)
  815.                 // RinexUtils.parseDouble(line, 23, 19)
  816.                 // GPS week (to go with Toe)
  817.                 pi.gpsLNav.setWeek((int) RinexUtils.parseDouble(line, 42, 19));
  818.             }

  819.             /** {@inheritDoc} */
  820.             @Override
  821.             public void parseSixthBroadcastOrbit(final String line, final ParseInfo pi) {
  822.                 pi.gpsLNav.setSvAccuracy(parseBroadcastDouble1(line, pi.initialSpaces, Unit.METRE));
  823.                 pi.gpsLNav.setSvHealth(parseBroadcastInt2(line, pi.initialSpaces));
  824.                 pi.gpsLNav.setTGD(parseBroadcastDouble3(line,   pi.initialSpaces, Unit.SECOND));
  825.                 pi.gpsLNav.setIODC(parseBroadcastInt4(line,     pi.initialSpaces));
  826.             }

  827.             /** {@inheritDoc} */
  828.             @Override
  829.             public void parseSeventhBroadcastOrbit(final String line, final ParseInfo pi) {
  830.                 pi.gpsLNav.setTransmissionTime(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
  831.                 pi.gpsLNav.setFitInterval(parseBroadcastInt2(line, pi.initialSpaces));
  832.                 pi.closePendingMessage();
  833.             }

  834.             /** {@inheritDoc} */
  835.             @Override
  836.             public void closeMessage(final ParseInfo pi) {
  837.                 pi.file.addGPSLegacyNavigationMessage(pi.gpsLNav);
  838.                 pi.gpsLNav = null;
  839.             }

  840.         },

  841.         /** GPS civilian.
  842.          * @since 12.0
  843.          */
  844.         GPS_CNAV() {

  845.             /** {@inheritDoc} */
  846.             @Override
  847.             public void parseSvEpochSvClockLine(final String line, final ParseInfo pi) {
  848.                 parseSvEpochSvClockLine(line, pi.timeScales.getGPS(), pi.gpsCNav);
  849.             }

  850.             /** {@inheritDoc} */
  851.             @Override
  852.             public void parseFirstBroadcastOrbit(final String line, final ParseInfo pi) {
  853.                 pi.gpsCNav.setADot(parseBroadcastDouble1(line, pi.initialSpaces, M_PER_S));
  854.                 pi.gpsCNav.setCrs(parseBroadcastDouble2(line,    pi.initialSpaces, Unit.METRE));
  855.                 pi.gpsCNav.setDeltaN0(parseBroadcastDouble3(line, pi.initialSpaces, RAD_PER_S));
  856.                 pi.gpsCNav.setM0(parseBroadcastDouble4(line,     pi.initialSpaces, Unit.RADIAN));
  857.             }

  858.             /** {@inheritDoc} */
  859.             @Override
  860.             public void parseSecondBroadcastOrbit(final String line, final ParseInfo pi) {
  861.                 pi.gpsCNav.setCuc(parseBroadcastDouble1(line,   pi.initialSpaces, Unit.RADIAN));
  862.                 pi.gpsCNav.setE(parseBroadcastDouble2(line,     pi.initialSpaces, Unit.NONE));
  863.                 pi.gpsCNav.setCus(parseBroadcastDouble3(line,   pi.initialSpaces, Unit.RADIAN));
  864.                 pi.gpsCNav.setSqrtA(parseBroadcastDouble4(line, pi.initialSpaces, SQRT_M));
  865.             }

  866.             /** {@inheritDoc} */
  867.             @Override
  868.             public void parseThirdBroadcastOrbit(final String line, final ParseInfo pi) {
  869.                 pi.gpsCNav.setTime(parseBroadcastDouble1(line,   pi.initialSpaces, Unit.SECOND));
  870.                 pi.gpsCNav.setCic(parseBroadcastDouble2(line,    pi.initialSpaces, Unit.RADIAN));
  871.                 pi.gpsCNav.setOmega0(parseBroadcastDouble3(line, pi.initialSpaces, Unit.RADIAN));
  872.                 pi.gpsCNav.setCis(parseBroadcastDouble4(line,    pi.initialSpaces, Unit.RADIAN));
  873.             }

  874.             /** {@inheritDoc} */
  875.             @Override
  876.             public void parseFourthBroadcastOrbit(final String line, final ParseInfo pi) {
  877.                 pi.gpsCNav.setI0(parseBroadcastDouble1(line, pi.initialSpaces, Unit.RADIAN));
  878.                 pi.gpsCNav.setCrc(parseBroadcastDouble2(line,      pi.initialSpaces, Unit.METRE));
  879.                 pi.gpsCNav.setPa(parseBroadcastDouble3(line,       pi.initialSpaces, Unit.RADIAN));
  880.                 pi.gpsCNav.setOmegaDot(parseBroadcastDouble4(line, pi.initialSpaces, RAD_PER_S));
  881.             }

  882.             /** {@inheritDoc} */
  883.             @Override
  884.             public void parseFifthBroadcastOrbit(final String line, final ParseInfo pi) {
  885.                 pi.gpsCNav.setIDot(parseBroadcastDouble1(line, pi.initialSpaces, RAD_PER_S));
  886.                 pi.gpsCNav.setDeltaN0Dot(parseBroadcastDouble2(line, pi.initialSpaces, RAD_PER_S2));
  887.                 pi.gpsCNav.setUraiNed0(parseBroadcastInt3(line, pi.initialSpaces));
  888.                 pi.gpsCNav.setUraiNed1(parseBroadcastInt4(line, pi.initialSpaces));
  889.             }

  890.             /** {@inheritDoc} */
  891.             @Override
  892.             public void parseSixthBroadcastOrbit(final String line, final ParseInfo pi) {
  893.                 pi.gpsCNav.setUraiEd(parseBroadcastInt1(line, pi.initialSpaces));
  894.                 pi.gpsCNav.setSvHealth(parseBroadcastInt2(line, pi.initialSpaces));
  895.                 pi.gpsCNav.setTGD(parseBroadcastDouble3(line, pi.initialSpaces, Unit.SECOND));
  896.                 pi.gpsCNav.setUraiNed2(parseBroadcastInt4(line, pi.initialSpaces));
  897.             }

  898.             /** {@inheritDoc} */
  899.             @Override
  900.             public void parseSeventhBroadcastOrbit(final String line, final ParseInfo pi) {
  901.                 pi.gpsCNav.setIscL1CA(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
  902.                 pi.gpsCNav.setIscL2C(parseBroadcastDouble2(line,  pi.initialSpaces, Unit.SECOND));
  903.                 pi.gpsCNav.setIscL5I5(parseBroadcastDouble3(line, pi.initialSpaces, Unit.SECOND));
  904.                 pi.gpsCNav.setIscL5Q5(parseBroadcastDouble4(line, pi.initialSpaces, Unit.SECOND));
  905.             }

  906.             /** {@inheritDoc} */
  907.             @Override
  908.             public void parseEighthBroadcastOrbit(final String line, final ParseInfo pi) {
  909.                 if (pi.gpsCNav.isCnv2()) {
  910.                     // in CNAV2 messages, there is an additional line for L1 CD and L1 CP inter signal delay
  911.                     pi.gpsCNav.setIscL1CD(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
  912.                     pi.gpsCNav.setIscL1CP(parseBroadcastDouble2(line, pi.initialSpaces, Unit.SECOND));
  913.                 } else {
  914.                     parseTransmissionTimeLine(line, pi);
  915.                 }
  916.             }

  917.             /** {@inheritDoc} */
  918.             @Override
  919.             public void parseNinthBroadcastOrbit(final String line, final ParseInfo pi) {
  920.                 parseTransmissionTimeLine(line, pi);
  921.             }

  922.             /** Parse transmission time line.
  923.              * @param line line to parse
  924.              * @param pi holder for transient data
  925.              */
  926.             private void parseTransmissionTimeLine(final String line, final ParseInfo pi) {
  927.                 pi.gpsCNav.setTransmissionTime(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
  928.                 pi.gpsCNav.setWeek(parseBroadcastInt2(line, pi.initialSpaces));
  929.                 pi.closePendingMessage();
  930.             }

  931.             /** {@inheritDoc} */
  932.             @Override
  933.             public void closeMessage(final ParseInfo pi) {
  934.                 pi.file.addGPSCivilianNavigationMessage(pi.gpsCNav);
  935.                 pi.gpsCNav = null;
  936.             }

  937.         },

  938.         /** Galileo. */
  939.         GALILEO() {

  940.             /** {@inheritDoc} */
  941.             @Override
  942.             public void parseSvEpochSvClockLine(final String line, final ParseInfo pi) {
  943.                 parseSvEpochSvClockLine(line, pi.timeScales.getGPS(), pi.galileoNav);
  944.             }

  945.             /** {@inheritDoc} */
  946.             @Override
  947.             public void parseFirstBroadcastOrbit(final String line, final ParseInfo pi) {
  948.                 pi.galileoNav.setIODNav(parseBroadcastInt1(line, pi.initialSpaces));
  949.                 pi.galileoNav.setCrs(parseBroadcastDouble2(line,       pi.initialSpaces, Unit.METRE));
  950.                 pi.galileoNav.setDeltaN0(parseBroadcastDouble3(line,    pi.initialSpaces, RAD_PER_S));
  951.                 pi.galileoNav.setM0(parseBroadcastDouble4(line,        pi.initialSpaces, Unit.RADIAN));
  952.             }

  953.             /** {@inheritDoc} */
  954.             @Override
  955.             public void parseSecondBroadcastOrbit(final String line, final ParseInfo pi) {
  956.                 pi.galileoNav.setCuc(parseBroadcastDouble1(line,   pi.initialSpaces, Unit.RADIAN));
  957.                 pi.galileoNav.setE(parseBroadcastDouble2(line,     pi.initialSpaces, Unit.NONE));
  958.                 pi.galileoNav.setCus(parseBroadcastDouble3(line,   pi.initialSpaces, Unit.RADIAN));
  959.                 pi.galileoNav.setSqrtA(parseBroadcastDouble4(line, pi.initialSpaces, SQRT_M));
  960.             }

  961.             /** {@inheritDoc} */
  962.             @Override
  963.             public void parseThirdBroadcastOrbit(final String line, final ParseInfo pi) {
  964.                 pi.galileoNav.setTime(parseBroadcastDouble1(line,   pi.initialSpaces, Unit.SECOND));
  965.                 pi.galileoNav.setCic(parseBroadcastDouble2(line,    pi.initialSpaces, Unit.RADIAN));
  966.                 pi.galileoNav.setOmega0(parseBroadcastDouble3(line, pi.initialSpaces, Unit.RADIAN));
  967.                 pi.galileoNav.setCis(parseBroadcastDouble4(line,    pi.initialSpaces, Unit.RADIAN));
  968.             }

  969.             /** {@inheritDoc} */
  970.             @Override
  971.             public void parseFourthBroadcastOrbit(final String line, final ParseInfo pi) {
  972.                 pi.galileoNav.setI0(parseBroadcastDouble1(line, pi.initialSpaces, Unit.RADIAN));
  973.                 pi.galileoNav.setCrc(parseBroadcastDouble2(line,      pi.initialSpaces, Unit.METRE));
  974.                 pi.galileoNav.setPa(parseBroadcastDouble3(line,       pi.initialSpaces, Unit.RADIAN));
  975.                 pi.galileoNav.setOmegaDot(parseBroadcastDouble4(line, pi.initialSpaces, RAD_PER_S));
  976.             }

  977.             /** {@inheritDoc} */
  978.             @Override
  979.             public void parseFifthBroadcastOrbit(final String line, final ParseInfo pi) {
  980.                 // iDot
  981.                 pi.galileoNav.setIDot(parseBroadcastDouble1(line, pi.initialSpaces, RAD_PER_S));
  982.                 pi.galileoNav.setDataSource(parseBroadcastInt2(line, pi.initialSpaces));
  983.                 // GAL week (to go with Toe)
  984.                 pi.galileoNav.setWeek(parseBroadcastInt3(line, pi.initialSpaces));
  985.             }

  986.             /** {@inheritDoc} */
  987.             @Override
  988.             public void parseSixthBroadcastOrbit(final String line, final ParseInfo pi) {
  989.                 pi.galileoNav.setSisa(parseBroadcastDouble1(line, pi.initialSpaces, Unit.METRE));
  990.                 pi.galileoNav.setSvHealth(parseBroadcastDouble2(line, pi.initialSpaces, Unit.NONE));
  991.                 pi.galileoNav.setBGDE1E5a(parseBroadcastDouble3(line, pi.initialSpaces, Unit.SECOND));
  992.                 pi.galileoNav.setBGDE5bE1(parseBroadcastDouble4(line, pi.initialSpaces, Unit.SECOND));
  993.             }

  994.             /** {@inheritDoc} */
  995.             @Override
  996.             public void parseSeventhBroadcastOrbit(final String line, final ParseInfo pi) {
  997.                 pi.galileoNav.setTransmissionTime(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
  998.                 pi.closePendingMessage();
  999.             }

  1000.             /** {@inheritDoc} */
  1001.             @Override
  1002.             public void closeMessage(final ParseInfo pi) {
  1003.                 pi.file.addGalileoNavigationMessage(pi.galileoNav);
  1004.                 pi.galileoNav = null;
  1005.             }

  1006.         },

  1007.         /** Glonass. */
  1008.         GLONASS() {

  1009.             /** {@inheritDoc} */
  1010.             @Override
  1011.             public void parseSvEpochSvClockLine(final String line, final ParseInfo pi) {

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

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

  1014.                     // Toc
  1015.                     final int    year  = RinexUtils.convert2DigitsYear(RinexUtils.parseInt(line,  3, 2));
  1016.                     final int    month = RinexUtils.parseInt(line,  6, 2);
  1017.                     final int    day   = RinexUtils.parseInt(line,  9, 2);
  1018.                     final int    hours = RinexUtils.parseInt(line, 12, 2);
  1019.                     final int    min   = RinexUtils.parseInt(line, 15, 2);
  1020.                     final double sec   = RinexUtils.parseDouble(line, 17, 5);
  1021.                     pi.glonassNav.setEpochToc(new AbsoluteDate(year, month, day, hours, min, sec,
  1022.                                                                pi.timeScales.getUTC()));

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

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

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

  1031.                     // Toc
  1032.                     pi.glonassNav.setEpochToc(parsePrnSvEpochClock(line, pi.timeScales.getUTC()));

  1033.                     // clock
  1034.                     pi.glonassNav.setTauN(-RinexUtils.parseDouble(line, 23, 19));
  1035.                     pi.glonassNav.setGammaN(RinexUtils.parseDouble(line, 42, 19));
  1036.                     pi.glonassNav.setTime(fmod(RinexUtils.parseDouble(line, 61, 19), Constants.JULIAN_DAY));

  1037.                     // Set the ephemeris epoch (same as time of clock epoch)
  1038.                     pi.glonassNav.setDate(pi.glonassNav.getEpochToc());
  1039.                 }

  1040.             }

  1041.             /** {@inheritDoc} */
  1042.             @Override
  1043.             public void parseFirstBroadcastOrbit(final String line, final ParseInfo pi) {
  1044.                 pi.glonassNav.setX(parseBroadcastDouble1(line, pi.initialSpaces, KM));
  1045.                 pi.glonassNav.setXDot(parseBroadcastDouble2(line,    pi.initialSpaces, KM_PER_S));
  1046.                 pi.glonassNav.setXDotDot(parseBroadcastDouble3(line, pi.initialSpaces, KM_PER_S2));
  1047.                 pi.glonassNav.setHealth(parseBroadcastDouble4(line,  pi.initialSpaces, Unit.NONE));
  1048.             }

  1049.             /** {@inheritDoc} */
  1050.             @Override
  1051.             public void parseSecondBroadcastOrbit(final String line, final ParseInfo pi) {
  1052.                 pi.glonassNav.setY(parseBroadcastDouble1(line, pi.initialSpaces, KM));
  1053.                 pi.glonassNav.setYDot(parseBroadcastDouble2(line,            pi.initialSpaces, KM_PER_S));
  1054.                 pi.glonassNav.setYDotDot(parseBroadcastDouble3(line,         pi.initialSpaces, KM_PER_S2));
  1055.                 pi.glonassNav.setFrequencyNumber(parseBroadcastDouble4(line, pi.initialSpaces, Unit.NONE));
  1056.             }

  1057.             /** {@inheritDoc} */
  1058.             @Override
  1059.             public void parseThirdBroadcastOrbit(final String line, final ParseInfo pi) {
  1060.                 pi.glonassNav.setZ(parseBroadcastDouble1(line, pi.initialSpaces, KM));
  1061.                 pi.glonassNav.setZDot(parseBroadcastDouble2(line,    pi.initialSpaces, KM_PER_S));
  1062.                 pi.glonassNav.setZDotDot(parseBroadcastDouble3(line, pi.initialSpaces, KM_PER_S2));
  1063.                 if (pi.file.getHeader().getFormatVersion() < 3.045) {
  1064.                     pi.closePendingMessage();
  1065.                 }
  1066.             }

  1067.             /** {@inheritDoc} */
  1068.             @Override
  1069.             public void parseFourthBroadcastOrbit(final String line, final ParseInfo pi) {
  1070.                 pi.glonassNav.setStatusFlags(parseBroadcastDouble1(line, pi.initialSpaces, Unit.NONE));
  1071.                 pi.glonassNav.setGroupDelayDifference(parseBroadcastDouble2(line, pi.initialSpaces, Unit.NONE));
  1072.                 pi.glonassNav.setURA(parseBroadcastDouble3(line,                  pi.initialSpaces, Unit.NONE));
  1073.                 pi.glonassNav.setHealthFlags(parseBroadcastDouble4(line,          pi.initialSpaces, Unit.NONE));
  1074.                 pi.closePendingMessage();
  1075.             }

  1076.             /** {@inheritDoc} */
  1077.             @Override
  1078.             public void closeMessage(final ParseInfo pi) {
  1079.                 pi.file.addGlonassNavigationMessage(pi.glonassNav);
  1080.                 pi.glonassNav = null;
  1081.             }

  1082.         },

  1083.         /** QZSS legacy. */
  1084.         QZSS_LNAV() {

  1085.             /** {@inheritDoc} */
  1086.             @Override
  1087.             public void parseSvEpochSvClockLine(final String line, final ParseInfo pi) {
  1088.                 parseSvEpochSvClockLine(line, pi.timeScales.getGPS(), pi.qzssLNav);
  1089.             }

  1090.             /** {@inheritDoc} */
  1091.             @Override
  1092.             public void parseFirstBroadcastOrbit(final String line, final ParseInfo pi) {
  1093.                 pi.qzssLNav.setIODE(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
  1094.                 pi.qzssLNav.setCrs(parseBroadcastDouble2(line,    pi.initialSpaces, Unit.METRE));
  1095.                 pi.qzssLNav.setDeltaN0(parseBroadcastDouble3(line, pi.initialSpaces, RAD_PER_S));
  1096.                 pi.qzssLNav.setM0(parseBroadcastDouble4(line,     pi.initialSpaces, Unit.RADIAN));
  1097.             }

  1098.             /** {@inheritDoc} */
  1099.             @Override
  1100.             public void parseSecondBroadcastOrbit(final String line, final ParseInfo pi) {
  1101.                 pi.qzssLNav.setCuc(parseBroadcastDouble1(line, pi.initialSpaces, Unit.RADIAN));
  1102.                 pi.qzssLNav.setE(parseBroadcastDouble2(line,     pi.initialSpaces, Unit.NONE));
  1103.                 pi.qzssLNav.setCus(parseBroadcastDouble3(line,   pi.initialSpaces, Unit.RADIAN));
  1104.                 pi.qzssLNav.setSqrtA(parseBroadcastDouble4(line, pi.initialSpaces, SQRT_M));
  1105.             }

  1106.             /** {@inheritDoc} */
  1107.             @Override
  1108.             public void parseThirdBroadcastOrbit(final String line, final ParseInfo pi) {
  1109.                 pi.qzssLNav.setTime(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
  1110.                 pi.qzssLNav.setCic(parseBroadcastDouble2(line,    pi.initialSpaces, Unit.RADIAN));
  1111.                 pi.qzssLNav.setOmega0(parseBroadcastDouble3(line, pi.initialSpaces, Unit.RADIAN));
  1112.                 pi.qzssLNav.setCis(parseBroadcastDouble4(line,    pi.initialSpaces, Unit.RADIAN));
  1113.             }

  1114.             /** {@inheritDoc} */
  1115.             @Override
  1116.             public void parseFourthBroadcastOrbit(final String line, final ParseInfo pi) {
  1117.                 pi.qzssLNav.setI0(parseBroadcastDouble1(line, pi.initialSpaces, Unit.RADIAN));
  1118.                 pi.qzssLNav.setCrc(parseBroadcastDouble2(line,      pi.initialSpaces, Unit.METRE));
  1119.                 pi.qzssLNav.setPa(parseBroadcastDouble3(line,       pi.initialSpaces, Unit.RADIAN));
  1120.                 pi.qzssLNav.setOmegaDot(parseBroadcastDouble4(line, pi.initialSpaces, RAD_PER_S));
  1121.             }

  1122.             /** {@inheritDoc} */
  1123.             @Override
  1124.             public void parseFifthBroadcastOrbit(final String line, final ParseInfo pi) {
  1125.                 // iDot
  1126.                 pi.qzssLNav.setIDot(parseBroadcastDouble1(line, pi.initialSpaces, RAD_PER_S));
  1127.                 // Codes on L2 channel (ignored)
  1128.                 // RinexUtils.parseDouble(line, 23, 19)
  1129.                 // GPS week (to go with Toe)
  1130.                 pi.qzssLNav.setWeek(parseBroadcastInt3(line, pi.initialSpaces));
  1131.             }

  1132.             /** {@inheritDoc} */
  1133.             @Override
  1134.             public void parseSixthBroadcastOrbit(final String line, final ParseInfo pi) {
  1135.                 pi.qzssLNav.setSvAccuracy(parseBroadcastDouble1(line,  pi.initialSpaces, Unit.METRE));
  1136.                 pi.qzssLNav.setSvHealth(parseBroadcastInt2(line, pi.initialSpaces));
  1137.                 pi.qzssLNav.setTGD(parseBroadcastDouble3(line,   pi.initialSpaces, Unit.SECOND));
  1138.                 pi.qzssLNav.setIODC(parseBroadcastInt4(line,     pi.initialSpaces));
  1139.             }

  1140.             /** {@inheritDoc} */
  1141.             @Override
  1142.             public void parseSeventhBroadcastOrbit(final String line, final ParseInfo pi) {
  1143.                 pi.qzssLNav.setTransmissionTime(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
  1144.                 pi.qzssLNav.setFitInterval(parseBroadcastInt2(line, pi.initialSpaces));
  1145.                 pi.closePendingMessage();
  1146.             }

  1147.             /** {@inheritDoc} */
  1148.             @Override
  1149.             public void closeMessage(final ParseInfo pi) {
  1150.                 pi.file.addQZSSLegacyNavigationMessage(pi.qzssLNav);
  1151.                 pi.qzssLNav = null;
  1152.             }

  1153.         },

  1154.         /** QZSS civilian.
  1155.          * @since 12.0
  1156.          */
  1157.         QZSS_CNAV() {

  1158.             /** {@inheritDoc} */
  1159.             @Override
  1160.             public void parseSvEpochSvClockLine(final String line, final ParseInfo pi) {
  1161.                 parseSvEpochSvClockLine(line, pi.timeScales.getGPS(), pi.qzssCNav);
  1162.             }

  1163.             /** {@inheritDoc} */
  1164.             @Override
  1165.             public void parseFirstBroadcastOrbit(final String line, final ParseInfo pi) {
  1166.                 pi.qzssCNav.setADot(parseBroadcastDouble1(line, pi.initialSpaces, M_PER_S));
  1167.                 pi.qzssCNav.setCrs(parseBroadcastDouble2(line,    pi.initialSpaces, Unit.METRE));
  1168.                 pi.qzssCNav.setDeltaN0(parseBroadcastDouble3(line, pi.initialSpaces, RAD_PER_S));
  1169.                 pi.qzssCNav.setM0(parseBroadcastDouble4(line,     pi.initialSpaces, Unit.RADIAN));
  1170.             }

  1171.             /** {@inheritDoc} */
  1172.             @Override
  1173.             public void parseSecondBroadcastOrbit(final String line, final ParseInfo pi) {
  1174.                 pi.qzssCNav.setCuc(parseBroadcastDouble1(line, pi.initialSpaces, Unit.RADIAN));
  1175.                 pi.qzssCNav.setE(parseBroadcastDouble2(line,     pi.initialSpaces, Unit.NONE));
  1176.                 pi.qzssCNav.setCus(parseBroadcastDouble3(line,   pi.initialSpaces, Unit.RADIAN));
  1177.                 pi.qzssCNav.setSqrtA(parseBroadcastDouble4(line, pi.initialSpaces, SQRT_M));
  1178.             }

  1179.             /** {@inheritDoc} */
  1180.             @Override
  1181.             public void parseThirdBroadcastOrbit(final String line, final ParseInfo pi) {
  1182.                 pi.qzssCNav.setTime(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
  1183.                 pi.qzssCNav.setCic(parseBroadcastDouble2(line,    pi.initialSpaces, Unit.RADIAN));
  1184.                 pi.qzssCNav.setOmega0(parseBroadcastDouble3(line, pi.initialSpaces, Unit.RADIAN));
  1185.                 pi.qzssCNav.setCis(parseBroadcastDouble4(line,    pi.initialSpaces, Unit.RADIAN));
  1186.             }

  1187.             /** {@inheritDoc} */
  1188.             @Override
  1189.             public void parseFourthBroadcastOrbit(final String line, final ParseInfo pi) {
  1190.                 pi.qzssCNav.setI0(parseBroadcastDouble1(line, pi.initialSpaces, Unit.RADIAN));
  1191.                 pi.qzssCNav.setCrc(parseBroadcastDouble2(line,      pi.initialSpaces, Unit.METRE));
  1192.                 pi.qzssCNav.setPa(parseBroadcastDouble3(line,       pi.initialSpaces, Unit.RADIAN));
  1193.                 pi.qzssCNav.setOmegaDot(parseBroadcastDouble4(line, pi.initialSpaces, RAD_PER_S));
  1194.             }

  1195.             /** {@inheritDoc} */
  1196.             @Override
  1197.             public void parseFifthBroadcastOrbit(final String line, final ParseInfo pi) {
  1198.                 pi.qzssCNav.setIDot(parseBroadcastDouble1(line, pi.initialSpaces, RAD_PER_S));
  1199.                 pi.qzssCNav.setDeltaN0Dot(parseBroadcastDouble2(line, pi.initialSpaces, RAD_PER_S2));
  1200.                 pi.qzssCNav.setUraiNed0(parseBroadcastInt3(line, pi.initialSpaces));
  1201.                 pi.qzssCNav.setUraiNed1(parseBroadcastInt4(line, pi.initialSpaces));
  1202.             }

  1203.             /** {@inheritDoc} */
  1204.             @Override
  1205.             public void parseSixthBroadcastOrbit(final String line, final ParseInfo pi) {
  1206.                 pi.qzssCNav.setUraiEd(parseBroadcastInt1(line, pi.initialSpaces));
  1207.                 pi.qzssCNav.setSvHealth(parseBroadcastInt2(line, pi.initialSpaces));
  1208.                 pi.qzssCNav.setTGD(parseBroadcastDouble3(line, pi.initialSpaces, Unit.SECOND));
  1209.                 pi.qzssCNav.setUraiNed2(parseBroadcastInt4(line, pi.initialSpaces));
  1210.             }

  1211.             /** {@inheritDoc} */
  1212.             @Override
  1213.             public void parseSeventhBroadcastOrbit(final String line, final ParseInfo pi) {
  1214.                 pi.qzssCNav.setIscL1CA(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
  1215.                 pi.qzssCNav.setIscL2C(parseBroadcastDouble2(line,  pi.initialSpaces, Unit.SECOND));
  1216.                 pi.qzssCNav.setIscL5I5(parseBroadcastDouble3(line, pi.initialSpaces, Unit.SECOND));
  1217.                 pi.qzssCNav.setIscL5Q5(parseBroadcastDouble4(line, pi.initialSpaces, Unit.SECOND));
  1218.             }

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

  1230.             /** {@inheritDoc} */
  1231.             @Override
  1232.             public void parseNinthBroadcastOrbit(final String line, final ParseInfo pi) {
  1233.                 parseTransmissionTimeLine(line, pi);
  1234.             }

  1235.             /** Parse transmission time line.
  1236.              * @param line line to parse
  1237.              * @param pi holder for transient data
  1238.              */
  1239.             private void parseTransmissionTimeLine(final String line, final ParseInfo pi) {
  1240.                 pi.qzssCNav.setTransmissionTime(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
  1241.                 pi.qzssCNav.setWeek(parseBroadcastInt2(line, pi.initialSpaces));
  1242.                 pi.closePendingMessage();
  1243.             }

  1244.             /** {@inheritDoc} */
  1245.             @Override
  1246.             public void closeMessage(final ParseInfo pi) {
  1247.                 pi.file.addQZSSCivilianNavigationMessage(pi.qzssCNav);
  1248.                 pi.qzssCNav = null;
  1249.             }

  1250.         },

  1251.         /** Beidou legacy. */
  1252.         BEIDOU_D1_D2() {

  1253.             /** {@inheritDoc} */
  1254.             @Override
  1255.             public void parseSvEpochSvClockLine(final String line, final ParseInfo pi) {
  1256.                 parseSvEpochSvClockLine(line, pi.timeScales.getBDT(), pi.beidouLNav);
  1257.             }

  1258.             /** {@inheritDoc} */
  1259.             @Override
  1260.             public void parseFirstBroadcastOrbit(final String line, final ParseInfo pi) {
  1261.                 pi.beidouLNav.setAODE(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
  1262.                 pi.beidouLNav.setCrs(parseBroadcastDouble2(line,    pi.initialSpaces, Unit.METRE));
  1263.                 pi.beidouLNav.setDeltaN0(parseBroadcastDouble3(line, pi.initialSpaces, RAD_PER_S));
  1264.                 pi.beidouLNav.setM0(parseBroadcastDouble4(line,     pi.initialSpaces, Unit.RADIAN));
  1265.             }

  1266.             /** {@inheritDoc} */
  1267.             @Override
  1268.             public void parseSecondBroadcastOrbit(final String line, final ParseInfo pi) {
  1269.                 pi.beidouLNav.setCuc(parseBroadcastDouble1(line,   pi.initialSpaces, Unit.RADIAN));
  1270.                 pi.beidouLNav.setE(parseBroadcastDouble2(line,     pi.initialSpaces, Unit.NONE));
  1271.                 pi.beidouLNav.setCus(parseBroadcastDouble3(line,   pi.initialSpaces, Unit.RADIAN));
  1272.                 pi.beidouLNav.setSqrtA(parseBroadcastDouble4(line, pi.initialSpaces, SQRT_M));
  1273.             }

  1274.             /** {@inheritDoc} */
  1275.             @Override
  1276.             public void parseThirdBroadcastOrbit(final String line, final ParseInfo pi) {
  1277.                 pi.beidouLNav.setTime(parseBroadcastDouble1(line,   pi.initialSpaces, Unit.SECOND));
  1278.                 pi.beidouLNav.setCic(parseBroadcastDouble2(line,    pi.initialSpaces, Unit.RADIAN));
  1279.                 pi.beidouLNav.setOmega0(parseBroadcastDouble3(line, pi.initialSpaces, Unit.RADIAN));
  1280.                 pi.beidouLNav.setCis(parseBroadcastDouble4(line,    pi.initialSpaces, Unit.RADIAN));
  1281.             }

  1282.             /** {@inheritDoc} */
  1283.             @Override
  1284.             public void parseFourthBroadcastOrbit(final String line, final ParseInfo pi) {
  1285.                 pi.beidouLNav.setI0(parseBroadcastDouble1(line,       pi.initialSpaces, Unit.RADIAN));
  1286.                 pi.beidouLNav.setCrc(parseBroadcastDouble2(line,      pi.initialSpaces, Unit.METRE));
  1287.                 pi.beidouLNav.setPa(parseBroadcastDouble3(line,       pi.initialSpaces, Unit.RADIAN));
  1288.                 pi.beidouLNav.setOmegaDot(parseBroadcastDouble4(line, pi.initialSpaces, RAD_PER_S));
  1289.             }

  1290.             /** {@inheritDoc} */
  1291.             @Override
  1292.             public void parseFifthBroadcastOrbit(final String line, final ParseInfo pi) {
  1293.                 // iDot
  1294.                 pi.beidouLNav.setIDot(parseBroadcastDouble1(line, pi.initialSpaces, RAD_PER_S));
  1295.                 // BDT week (to go with Toe)
  1296.                 pi.beidouLNav.setWeek(parseBroadcastInt3(line, pi.initialSpaces));
  1297.             }

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

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

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

  1319.         },

  1320.         /** Beidou-3 CNAV. */
  1321.         BEIDOU_CNV_123() {

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

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

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

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

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

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

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

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

  1408.             /** {@inheritDoc} */
  1409.             @Override
  1410.             public void parseEighthBroadcastOrbit(final String line, final ParseInfo pi) {
  1411.                 if (pi.beidouCNav.getRadioWave().closeTo(PredefinedGnssSignal.B2B)) {
  1412.                     pi.beidouCNav.setTransmissionTime(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
  1413.                     pi.closePendingMessage();
  1414.                 } else {
  1415.                     parseSismaiHealthIntegrity(line, pi);
  1416.                 }
  1417.             }

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

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

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

  1444.         },

  1445.         /** SBAS. */
  1446.         SBAS() {

  1447.             /** {@inheritDoc} */
  1448.             @Override
  1449.             public void parseSvEpochSvClockLine(final String line, final ParseInfo pi) {

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

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

  1455.                 pi.sbasNav.setEpochToc(parsePrnSvEpochClock(line, timeScale));
  1456.                 pi.sbasNav.setAGf0(parseBroadcastDouble2(line, pi.initialSpaces, Unit.SECOND));
  1457.                 pi.sbasNav.setAGf1(parseBroadcastDouble3(line, pi.initialSpaces, S_PER_S));
  1458.                 pi.sbasNav.setTime(parseBroadcastDouble4(line, pi.initialSpaces, Unit.SECOND));

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

  1461.             }

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

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

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

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

  1493.         },

  1494.         /** NavIC. */
  1495.         NAVIC_LNAV() {

  1496.             /** {@inheritDoc} */
  1497.             @Override
  1498.             public void parseSvEpochSvClockLine(final String line, final ParseInfo pi) {
  1499.                 parseSvEpochSvClockLine(line, pi.timeScales.getNavIC(), pi.navicLNav);
  1500.             }

  1501.             /** {@inheritDoc} */
  1502.             @Override
  1503.             public void parseFirstBroadcastOrbit(final String line, final ParseInfo pi) {
  1504.                 pi.navicLNav.setIODC(parseBroadcastInt1(line, pi.initialSpaces));
  1505.                 pi.navicLNav.setCrs(parseBroadcastDouble2(line, pi.initialSpaces, Unit.METRE));
  1506.                 pi.navicLNav.setDeltaN0(parseBroadcastDouble3(line, pi.initialSpaces, RAD_PER_S));
  1507.                 pi.navicLNav.setM0(parseBroadcastDouble4(line, pi.initialSpaces, Unit.RADIAN));
  1508.             }

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

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

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

  1533.             /** {@inheritDoc} */
  1534.             @Override
  1535.             public void parseFifthBroadcastOrbit(final String line, final ParseInfo pi) {
  1536.                 // iDot
  1537.                 pi.navicLNav.setIDot(parseBroadcastDouble1(line, pi.initialSpaces, RAD_PER_S));
  1538.                 // NavIC week (to go with Toe)
  1539.                 pi.navicLNav.setWeek(parseBroadcastInt3(line, pi.initialSpaces));
  1540.             }

  1541.             /** {@inheritDoc} */
  1542.             @Override
  1543.             public void parseSixthBroadcastOrbit(final String line, final ParseInfo pi) {
  1544.                 final int uraIndex = parseBroadcastInt1(line, pi.initialSpaces);
  1545.                 pi.navicLNav.setSvAccuracy(NAVIC_URA[FastMath.min(uraIndex, NAVIC_URA.length - 1)]);
  1546.                 pi.navicLNav.setSvHealth(parseBroadcastInt2(line, pi.initialSpaces));
  1547.                 pi.navicLNav.setTGD(parseBroadcastDouble3(line, pi.initialSpaces, Unit.SECOND));
  1548.             }

  1549.             /** {@inheritDoc} */
  1550.             @Override
  1551.             public void parseSeventhBroadcastOrbit(final String line, final ParseInfo pi) {
  1552.                 pi.navicLNav.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.addNavICLegacyNavigationMessage(pi.navicLNav);
  1559.                 pi.navicLNav = null;
  1560.             }

  1561.         },

  1562.         /** NavIC L1NV.
  1563.          * @since 12.0
  1564.          */
  1565.         NAVIC_L1NV() {

  1566.             /** {@inheritDoc} */
  1567.             @Override
  1568.             public void parseSvEpochSvClockLine(final String line, final ParseInfo pi) {
  1569.                 parseSvEpochSvClockLine(line, pi.timeScales.getGPS(), pi.navicL1NV);
  1570.             }

  1571.             /** {@inheritDoc} */
  1572.             @Override
  1573.             public void parseFirstBroadcastOrbit(final String line, final ParseInfo pi) {
  1574.                 pi.navicL1NV.setADot(parseBroadcastDouble1(line,    pi.initialSpaces, M_PER_S));
  1575.                 pi.navicL1NV.setCrs(parseBroadcastDouble2(line,     pi.initialSpaces, Unit.METRE));
  1576.                 pi.navicL1NV.setDeltaN0(parseBroadcastDouble3(line, pi.initialSpaces, RAD_PER_S));
  1577.                 pi.navicL1NV.setM0(parseBroadcastDouble4(line,      pi.initialSpaces, Unit.RADIAN));
  1578.             }

  1579.             /** {@inheritDoc} */
  1580.             @Override
  1581.             public void parseSecondBroadcastOrbit(final String line, final ParseInfo pi) {
  1582.                 pi.navicL1NV.setCuc(parseBroadcastDouble1(line,   pi.initialSpaces, Unit.RADIAN));
  1583.                 pi.navicL1NV.setE(parseBroadcastDouble2(line,     pi.initialSpaces, Unit.NONE));
  1584.                 pi.navicL1NV.setCus(parseBroadcastDouble3(line,   pi.initialSpaces, Unit.RADIAN));
  1585.                 pi.navicL1NV.setSqrtA(parseBroadcastDouble4(line, pi.initialSpaces, SQRT_M));
  1586.             }

  1587.             /** {@inheritDoc} */
  1588.             @Override
  1589.             public void parseThirdBroadcastOrbit(final String line, final ParseInfo pi) {
  1590.                 pi.navicL1NV.setTime(parseBroadcastDouble1(line,   pi.initialSpaces, Unit.SECOND));
  1591.                 pi.navicL1NV.setCic(parseBroadcastDouble2(line,    pi.initialSpaces, Unit.RADIAN));
  1592.                 pi.navicL1NV.setOmega0(parseBroadcastDouble3(line, pi.initialSpaces, Unit.RADIAN));
  1593.                 pi.navicL1NV.setCis(parseBroadcastDouble4(line,    pi.initialSpaces, Unit.RADIAN));
  1594.             }

  1595.             /** {@inheritDoc} */
  1596.             @Override
  1597.             public void parseFourthBroadcastOrbit(final String line, final ParseInfo pi) {
  1598.                 pi.navicL1NV.setI0(parseBroadcastDouble1(line,       pi.initialSpaces, Unit.RADIAN));
  1599.                 pi.navicL1NV.setCrc(parseBroadcastDouble2(line,      pi.initialSpaces, Unit.METRE));
  1600.                 pi.navicL1NV.setPa(parseBroadcastDouble3(line,       pi.initialSpaces, Unit.RADIAN));
  1601.                 pi.navicL1NV.setOmegaDot(parseBroadcastDouble4(line, pi.initialSpaces, RAD_PER_S));
  1602.             }

  1603.             /** {@inheritDoc} */
  1604.             @Override
  1605.             public void parseFifthBroadcastOrbit(final String line, final ParseInfo pi) {
  1606.                 pi.navicL1NV.setIDot(parseBroadcastDouble1(line,             pi.initialSpaces, RAD_PER_S));
  1607.                 pi.navicL1NV.setDeltaN0Dot(parseBroadcastDouble2(line,       pi.initialSpaces, RAD_PER_S2));
  1608.                 pi.navicL1NV.setReferenceSignalFlag(parseBroadcastInt4(line, pi.initialSpaces));
  1609.             }

  1610.             /** {@inheritDoc} */
  1611.             @Override
  1612.             public void parseSixthBroadcastOrbit(final String line, final ParseInfo pi) {
  1613.                 final int uraIndex = parseBroadcastInt1(line, pi.initialSpaces);
  1614.                 pi.navicL1NV.setSvAccuracy(NAVIC_URA[FastMath.min(uraIndex, NAVIC_URA.length - 1)]);
  1615.                 pi.navicL1NV.setSvHealth(parseBroadcastInt2(line, pi.initialSpaces));
  1616.                 pi.navicL1NV.setTGD(parseBroadcastDouble3(line,   pi.initialSpaces, Unit.SECOND));
  1617.                 pi.navicL1NV.setTGDSL5(parseBroadcastDouble4(line, pi.initialSpaces, Unit.SECOND));
  1618.             }

  1619.             /** {@inheritDoc} */
  1620.             @Override
  1621.             public void parseSeventhBroadcastOrbit(final String line, final ParseInfo pi) {
  1622.                 pi.navicL1NV.setIscSL1P(parseBroadcastDouble1(line,   pi.initialSpaces, Unit.SECOND));
  1623.                 pi.navicL1NV.setIscL1DL1P(parseBroadcastDouble2(line, pi.initialSpaces, Unit.SECOND));
  1624.                 pi.navicL1NV.setIscL1PS(parseBroadcastDouble3(line,   pi.initialSpaces, Unit.SECOND));
  1625.                 pi.navicL1NV.setIscL1DS(parseBroadcastDouble4(line,   pi.initialSpaces, Unit.SECOND));
  1626.             }

  1627.             /** {@inheritDoc} */
  1628.             @Override
  1629.             public void parseEighthBroadcastOrbit(final String line, final ParseInfo pi) {
  1630.                 pi.navicL1NV.setTransmissionTime(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
  1631.                 pi.navicL1NV.setWeek(parseBroadcastInt2(line, pi.initialSpaces));
  1632.                 pi.closePendingMessage();
  1633.             }

  1634.             /** {@inheritDoc} */
  1635.             @Override
  1636.             public void closeMessage(final ParseInfo pi) {
  1637.                 pi.file.addNavICL1NVNavigationMessage(pi.navicL1NV);
  1638.                 pi.navicL1NV = null;
  1639.             }

  1640.         };

  1641.         /** Get the parse for navigation message.
  1642.          * @param system satellite system
  1643.          * @param type message type (null for Rinex 3.x)
  1644.          * @param parseInfo container for transient data
  1645.          * @param line line being parsed
  1646.          * @return the satellite system line parser
  1647.          */
  1648.         private static SatelliteSystemLineParser getParser(final SatelliteSystem system, final String type,
  1649.                                                            final ParseInfo parseInfo, final String line) {
  1650.             switch (system) {
  1651.                 case GPS :
  1652.                     if (type == null || type.equals(LegacyNavigationMessage.LNAV)) {
  1653.                         // in Rinex, week number is aligned to GPS week!
  1654.                         parseInfo.gpsLNav = new GPSLegacyNavigationMessage(parseInfo.timeScales,
  1655.                                                                            SatelliteSystem.GPS);
  1656.                         return GPS_LNAV;
  1657.                     } else if (type.equals(CivilianNavigationMessage.CNAV)) {
  1658.                         // in Rinex, week number is aligned to GPS week!
  1659.                         parseInfo.gpsCNav = new GPSCivilianNavigationMessage(false,
  1660.                                                                              parseInfo.timeScales,
  1661.                                                                              SatelliteSystem.GPS);
  1662.                         return GPS_CNAV;
  1663.                     } else if (type.equals(CivilianNavigationMessage.CNV2)) {
  1664.                         // in Rinex, week number is aligned to GPS week!
  1665.                         parseInfo.gpsCNav = new GPSCivilianNavigationMessage(true,
  1666.                                                                              parseInfo.timeScales,
  1667.                                                                              SatelliteSystem.GPS);
  1668.                         return GPS_CNAV;
  1669.                     }
  1670.                     break;
  1671.                 case GALILEO :
  1672.                     if (type == null || type.equals("INAV") || type.equals("FNAV")) {
  1673.                         // in Rinex, week number is aligned to GPS week!
  1674.                         parseInfo.galileoNav = new GalileoNavigationMessage(parseInfo.timeScales,
  1675.                                                                             SatelliteSystem.GPS);
  1676.                         return GALILEO;
  1677.                     }
  1678.                     break;
  1679.                 case GLONASS :
  1680.                     if (type == null || type.equals("FDMA")) {
  1681.                         parseInfo.glonassNav = new GLONASSNavigationMessage();
  1682.                         return GLONASS;
  1683.                     }
  1684.                     break;
  1685.                 case QZSS :
  1686.                     if (type == null || type.equals(LegacyNavigationMessage.LNAV)) {
  1687.                         // in Rinex, week number is aligned to GPS week!
  1688.                         parseInfo.qzssLNav = new QZSSLegacyNavigationMessage(parseInfo.timeScales,
  1689.                                                                              SatelliteSystem.GPS);
  1690.                         return QZSS_LNAV;
  1691.                     } else if (type.equals(CivilianNavigationMessage.CNAV)) {
  1692.                         // in Rinex, week number is aligned to GPS week!
  1693.                         parseInfo.qzssCNav = new QZSSCivilianNavigationMessage(false,
  1694.                                                                                parseInfo.timeScales,
  1695.                                                                                SatelliteSystem.GPS);
  1696.                         return QZSS_CNAV;
  1697.                     } else if (type.equals(CivilianNavigationMessage.CNV2)) {
  1698.                         // in Rinex, week number is aligned to GPS week!
  1699.                         parseInfo.qzssCNav = new QZSSCivilianNavigationMessage(true,
  1700.                                                                                parseInfo.timeScales,
  1701.                                                                                SatelliteSystem.GPS);
  1702.                         return QZSS_CNAV;
  1703.                     }
  1704.                     break;
  1705.                 case BEIDOU :
  1706.                     if (type == null ||
  1707.                         type.equals(BeidouLegacyNavigationMessage.D1) ||
  1708.                         type.equals(BeidouLegacyNavigationMessage.D2)) {
  1709.                         // in Rinex, week number for Beidou is really aligned to Beidou week!
  1710.                         parseInfo.beidouLNav = new BeidouLegacyNavigationMessage(parseInfo.timeScales,
  1711.                                                                                  SatelliteSystem.BEIDOU);
  1712.                         return BEIDOU_D1_D2;
  1713.                     } else if (type.equals(BeidouCivilianNavigationMessage.CNV1)) {
  1714.                         // in Rinex, week number for Beidou is really aligned to Beidou week!
  1715.                         parseInfo.beidouCNav = new BeidouCivilianNavigationMessage(PredefinedGnssSignal.B1C,
  1716.                                                                                    parseInfo.timeScales,
  1717.                                                                                    SatelliteSystem.BEIDOU);
  1718.                         return BEIDOU_CNV_123;
  1719.                     } else if (type.equals(BeidouCivilianNavigationMessage.CNV2)) {
  1720.                         // in Rinex, week number for Beidou is really aligned to Beidou week!
  1721.                         parseInfo.beidouCNav = new BeidouCivilianNavigationMessage(PredefinedGnssSignal.B2A,
  1722.                                                                                    parseInfo.timeScales,
  1723.                                                                                    SatelliteSystem.BEIDOU);
  1724.                         return BEIDOU_CNV_123;
  1725.                     } else if (type.equals(BeidouCivilianNavigationMessage.CNV3)) {
  1726.                         // in Rinex, week number for Beidou is really aligned to Beidou week!
  1727.                         parseInfo.beidouCNav = new BeidouCivilianNavigationMessage(PredefinedGnssSignal.B2B,
  1728.                                                                                    parseInfo.timeScales,
  1729.                                                                                    SatelliteSystem.BEIDOU);
  1730.                         return BEIDOU_CNV_123;
  1731.                     }
  1732.                     break;
  1733.                 case NAVIC:
  1734.                     if (type == null || type.equals("LNAV")) {
  1735.                         // in Rinex, week number is aligned to GPS week!
  1736.                         parseInfo.navicLNav = new NavICLegacyNavigationMessage(parseInfo.timeScales,
  1737.                                                                                SatelliteSystem.GPS);
  1738.                         return NAVIC_LNAV;
  1739.                     } else if (type.equals(CivilianNavigationMessage.L1NV)) {
  1740.                         // in Rinex, week number is aligned to GPS week!
  1741.                         parseInfo.navicL1NV = new NavICL1NVNavigationMessage(parseInfo.timeScales,
  1742.                                                                              SatelliteSystem.GPS);
  1743.                         return NAVIC_L1NV;
  1744.                     }
  1745.                     break;
  1746.                 case SBAS :
  1747.                     if (type == null || type.equals("SBAS")) {
  1748.                         parseInfo.sbasNav = new SBASNavigationMessage();
  1749.                         return SBAS;
  1750.                     }
  1751.                     break;
  1752.                 default:
  1753.                     // do nothing, handle error after the switch
  1754.             }
  1755.             throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  1756.                                       parseInfo.lineNumber, parseInfo.name, line);
  1757.         }

  1758.         /**
  1759.          * Parse the SV/Epoch/Sv clock of the navigation message.
  1760.          * @param line line to read
  1761.          * @param timeScale time scale to use
  1762.          * @param message navigation message
  1763.          */
  1764.         protected void parseSvEpochSvClockLineRinex2(final String line, final TimeScale timeScale,
  1765.                                                      final AbstractNavigationMessage<?> message) {
  1766.             // PRN
  1767.             message.setPRN(RinexUtils.parseInt(line, 0, 2));

  1768.             // Toc
  1769.             final int    year  = RinexUtils.convert2DigitsYear(RinexUtils.parseInt(line,  2, 3));
  1770.             final int    month = RinexUtils.parseInt(line,  5, 3);
  1771.             final int    day   = RinexUtils.parseInt(line,  8, 3);
  1772.             final int    hours = RinexUtils.parseInt(line, 11, 3);
  1773.             final int    min   = RinexUtils.parseInt(line, 14, 3);
  1774.             final double sec   = RinexUtils.parseDouble(line, 17, 5);
  1775.             message.setEpochToc(new AbsoluteDate( year, month, day, hours, min, sec, timeScale));

  1776.             // clock
  1777.             message.setAf0(RinexUtils.parseDouble(line, 22, 19));
  1778.             message.setAf1(RinexUtils.parseDouble(line, 41, 19));
  1779.             message.setAf2(RinexUtils.parseDouble(line, 60, 19));

  1780.         }

  1781.         /**
  1782.          * Parse the SV/Epoch/Sv clock of the navigation message.
  1783.          * @param line line to read
  1784.          * @param timeScale time scale to use
  1785.          * @param message navigation message
  1786.          */
  1787.         protected void parseSvEpochSvClockLine(final String line, final TimeScale timeScale,
  1788.                                                final AbstractNavigationMessage<?> message) {
  1789.             // PRN
  1790.             message.setPRN(RinexUtils.parseInt(line, 1, 2));

  1791.             // Toc
  1792.             message.setEpochToc(parsePrnSvEpochClock(line, timeScale));

  1793.             // clock
  1794.             message.setAf0(RinexUtils.parseDouble(line, 23, 19));
  1795.             message.setAf1(RinexUtils.parseDouble(line, 42, 19));
  1796.             message.setAf2(RinexUtils.parseDouble(line, 61, 19));

  1797.         }

  1798.         /** Parse epoch field of a Sv/epoch/clock line.
  1799.          * @param line line to parse
  1800.          * @param timeScale time scale to use
  1801.          * @return parsed field
  1802.          */
  1803.         protected AbsoluteDate parsePrnSvEpochClock(final String line, final TimeScale timeScale) {
  1804.             final int year  = RinexUtils.parseInt(line, 4, 4);
  1805.             final int month = RinexUtils.parseInt(line, 9, 2);
  1806.             final int day   = RinexUtils.parseInt(line, 12, 2);
  1807.             final int hours = RinexUtils.parseInt(line, 15, 2);
  1808.             final int min   = RinexUtils.parseInt(line, 18, 2);
  1809.             final int sec   = RinexUtils.parseInt(line, 21, 2);
  1810.             return new AbsoluteDate(year, month, day, hours, min, sec, timeScale);
  1811.         }

  1812.         /** Parse double field 1 of a broadcast orbit line.
  1813.          * @param line line to parse
  1814.          * @param initialSpaces number of initial spaces in the line
  1815.          * @param unit unit to used for parsing the field
  1816.          * @return parsed field
  1817.          */
  1818.         protected double parseBroadcastDouble1(final String line, final int initialSpaces, final Unit unit) {
  1819.             return unit.toSI(RinexUtils.parseDouble(line, initialSpaces, 19));
  1820.         }

  1821.         /** Parse integer field 1 of a broadcast orbit line.
  1822.          * @param line line to parse
  1823.          * @param initialSpaces number of initial spaces in the line
  1824.          * @return parsed field
  1825.          */
  1826.         protected int parseBroadcastInt1(final String line, final int initialSpaces) {
  1827.             return (int) FastMath.rint(RinexUtils.parseDouble(line, initialSpaces, 19));
  1828.         }

  1829.         /** Parse double field 2 of a broadcast orbit line.
  1830.          * @param line line to parse
  1831.          * @param initialSpaces number of initial spaces in the line
  1832.          * @param unit unit to used for parsing the field
  1833.          * @return parsed field
  1834.          */
  1835.         protected double parseBroadcastDouble2(final String line, final int initialSpaces, final Unit unit) {
  1836.             return unit.toSI(RinexUtils.parseDouble(line, initialSpaces + 19, 19));
  1837.         }

  1838.         /** Parse integer field 2 of a broadcast orbit line.
  1839.          * @param line line to parse
  1840.          * @param initialSpaces number of initial spaces in the line
  1841.          * @return parsed field
  1842.          */
  1843.         protected int parseBroadcastInt2(final String line, final int initialSpaces) {
  1844.             return (int) FastMath.rint(RinexUtils.parseDouble(line, initialSpaces + 19, 19));
  1845.         }

  1846.         /** Parse double field 3 of a broadcast orbit line.
  1847.          * @param line line to parse
  1848.          * @param initialSpaces number of initial spaces in the line
  1849.          * @param unit unit to used for parsing the field
  1850.          * @return parsed field
  1851.          */
  1852.         protected double parseBroadcastDouble3(final String line, final int initialSpaces, final Unit unit) {
  1853.             return unit.toSI(RinexUtils.parseDouble(line, initialSpaces + 38, 19));
  1854.         }

  1855.         /** Parse integer field 3 of a broadcast orbit line.
  1856.          * @param line line to parse
  1857.          * @param initialSpaces number of initial spaces in the line
  1858.          * @return parsed field
  1859.          */
  1860.         protected int parseBroadcastInt3(final String line, final int initialSpaces) {
  1861.             return (int) FastMath.rint(RinexUtils.parseDouble(line, initialSpaces + 38, 19));
  1862.         }

  1863.         /** Parse double field 4 of a broadcast orbit line.
  1864.          * @param line line to parse
  1865.          * @param initialSpaces number of initial spaces in the line
  1866.          * @param unit unit to used for parsing the field
  1867.          * @return parsed field
  1868.          */
  1869.         protected double parseBroadcastDouble4(final String line, final int initialSpaces, final Unit unit) {
  1870.             return unit.toSI(RinexUtils.parseDouble(line, initialSpaces + 57, 19));
  1871.         }

  1872.         /** Parse integer field 4 of a broadcast orbit line.
  1873.          * @param line line to parse
  1874.          * @param initialSpaces number of initial spaces in the line
  1875.          * @return parsed field
  1876.          */
  1877.         protected int parseBroadcastInt4(final String line, final int initialSpaces) {
  1878.             return (int) FastMath.rint(RinexUtils.parseDouble(line, initialSpaces + 57, 19));
  1879.         }

  1880.         /**
  1881.          * Parse the SV/Epoch/Sv clock of the navigation message.
  1882.          * @param line line to read
  1883.          * @param pi holder for transient data
  1884.          */
  1885.         public abstract void parseSvEpochSvClockLine(String line, ParseInfo pi);

  1886.         /**
  1887.          * Parse the "BROADCASTORBIT - 1" line.
  1888.          * @param line line to read
  1889.          * @param pi holder for transient data
  1890.          */
  1891.         public abstract void parseFirstBroadcastOrbit(String line, ParseInfo pi);

  1892.         /**
  1893.          * Parse the "BROADCASTORBIT - 2" line.
  1894.          * @param line line to read
  1895.          * @param pi holder for transient data
  1896.          */
  1897.         public abstract void parseSecondBroadcastOrbit(String line, ParseInfo pi);

  1898.         /**
  1899.          * Parse the "BROADCASTORBIT - 3" line.
  1900.          * @param line line to read
  1901.          * @param pi holder for transient data
  1902.          */
  1903.         public abstract void parseThirdBroadcastOrbit(String line, ParseInfo pi);

  1904.         /**
  1905.          * Parse the "BROADCASTORBIT - 4" line.
  1906.          * @param line line to read
  1907.          * @param pi holder for transient data
  1908.          */
  1909.         public void parseFourthBroadcastOrbit(final String line, final ParseInfo pi) {
  1910.             // this should never be called (except by some tests that use reflection)
  1911.             throw new OrekitInternalError(null);
  1912.         }

  1913.         /**
  1914.          * Parse the "BROADCASTORBIT - 5" line.
  1915.          * @param line line to read
  1916.          * @param pi holder for transient data
  1917.          */
  1918.         public void parseFifthBroadcastOrbit(final String line, final ParseInfo pi) {
  1919.             // this should never be called (except by some tests that use reflection)
  1920.             throw new OrekitInternalError(null);
  1921.         }

  1922.         /**
  1923.          * Parse the "BROADCASTORBIT - 6" line.
  1924.          * @param line line to read
  1925.          * @param pi holder for transient data
  1926.          */
  1927.         public void parseSixthBroadcastOrbit(final String line, final ParseInfo pi) {
  1928.             // this should never be called (except by some tests that use reflection)
  1929.             throw new OrekitInternalError(null);
  1930.         }

  1931.         /**
  1932.          * Parse the "BROADCASTORBIT - 7" line.
  1933.          * @param line line to read
  1934.          * @param pi holder for transient data
  1935.          */
  1936.         public void parseSeventhBroadcastOrbit(final String line, final ParseInfo pi) {
  1937.             // this should never be called (except by some tests that use reflection)
  1938.             throw new OrekitInternalError(null);
  1939.         }

  1940.         /**
  1941.          * Parse the "BROADCASTORBIT - 8" line.
  1942.          * @param line line to read
  1943.          * @param pi holder for transient data
  1944.          */
  1945.         public void parseEighthBroadcastOrbit(final String line, final ParseInfo pi) {
  1946.             // this should never be called (except by some tests that use reflection)
  1947.             throw new OrekitInternalError(null);
  1948.         }

  1949.         /**
  1950.          * Parse the "BROADCASTORBIT - 9" line.
  1951.          * @param line line to read
  1952.          * @param pi holder for transient data
  1953.          */
  1954.         public void parseNinthBroadcastOrbit(final String line, final ParseInfo pi) {
  1955.             // this should never be called (except by some tests that use reflection)
  1956.             throw new OrekitInternalError(null);
  1957.         }

  1958.         /**
  1959.          * Close a message as last line was parsed.
  1960.          * @param pi holder for transient data
  1961.          */
  1962.         public abstract void closeMessage(ParseInfo pi);

  1963.         /**
  1964.          * Calculates the floating-point remainder of a / b.
  1965.          * <p>
  1966.          * fmod = a - x * b
  1967.          * where x = (int) a / b
  1968.          * </p>
  1969.          * @param a numerator
  1970.          * @param b denominator
  1971.          * @return the floating-point remainder of a / b
  1972.          */
  1973.         private static double fmod(final double a, final double b) {
  1974.             final double x = (int) (a / b);
  1975.             return a - x * b;
  1976.         }

  1977.     }

  1978.     /** Parsing method. */
  1979.     @FunctionalInterface
  1980.     private interface ParsingMethod {
  1981.         /** Parse a line.
  1982.          * @param line line to parse
  1983.          * @param parseInfo holder for transient data
  1984.          */
  1985.         void parse(String line, ParseInfo parseInfo);
  1986.     }

  1987. }