1   /* Copyright 2002-2024 CS GROUP
2    * Licensed to CS GROUP (CS) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * CS licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.orekit.files.rinex.navigation;
18  
19  import java.io.BufferedReader;
20  import java.io.IOException;
21  import java.io.Reader;
22  import java.util.Arrays;
23  import java.util.Collections;
24  import java.util.InputMismatchException;
25  import java.util.function.Function;
26  import java.util.function.Predicate;
27  
28  import org.hipparchus.util.FastMath;
29  import org.orekit.annotation.DefaultDataContext;
30  import org.orekit.data.DataContext;
31  import org.orekit.data.DataSource;
32  import org.orekit.errors.OrekitException;
33  import org.orekit.errors.OrekitInternalError;
34  import org.orekit.errors.OrekitMessages;
35  import org.orekit.files.rinex.utils.parsing.RinexUtils;
36  import org.orekit.gnss.Frequency;
37  import org.orekit.gnss.SatelliteSystem;
38  import org.orekit.gnss.TimeSystem;
39  import org.orekit.propagation.analytical.gnss.data.AbstractNavigationMessage;
40  import org.orekit.propagation.analytical.gnss.data.BeidouCivilianNavigationMessage;
41  import org.orekit.propagation.analytical.gnss.data.BeidouLegacyNavigationMessage;
42  import org.orekit.propagation.analytical.gnss.data.BeidouSatelliteType;
43  import org.orekit.propagation.analytical.gnss.data.CivilianNavigationMessage;
44  import org.orekit.propagation.analytical.gnss.data.GLONASSNavigationMessage;
45  import org.orekit.propagation.analytical.gnss.data.GPSCivilianNavigationMessage;
46  import org.orekit.propagation.analytical.gnss.data.GPSLegacyNavigationMessage;
47  import org.orekit.propagation.analytical.gnss.data.GalileoNavigationMessage;
48  import org.orekit.propagation.analytical.gnss.data.IRNSSNavigationMessage;
49  import org.orekit.propagation.analytical.gnss.data.LegacyNavigationMessage;
50  import org.orekit.propagation.analytical.gnss.data.QZSSCivilianNavigationMessage;
51  import org.orekit.propagation.analytical.gnss.data.QZSSLegacyNavigationMessage;
52  import org.orekit.propagation.analytical.gnss.data.SBASNavigationMessage;
53  import org.orekit.time.AbsoluteDate;
54  import org.orekit.time.GNSSDate;
55  import org.orekit.time.TimeScale;
56  import org.orekit.time.TimeScales;
57  import org.orekit.utils.Constants;
58  import org.orekit.utils.units.Unit;
59  
60  /**
61   * Parser for RINEX navigation messages files.
62   * <p>
63   * This parser handles RINEX version from 2 to 4.00.
64   * </p>
65   * @see <a href="https://files.igs.org/pub/data/format/rinex2.txt">rinex 2.0</a>
66   * @see <a href="https://files.igs.org/pub/data/format/rinex210.txt">rinex 2.10</a>
67   * @see <a href="https://files.igs.org/pub/data/format/rinex211.pdf">rinex 2.11</a>
68   * @see <a href="https://files.igs.org/pub/data/format/rinex301.pdf"> 3.01 navigation messages file format</a>
69   * @see <a href="https://files.igs.org/pub/data/format/rinex302.pdf"> 3.02 navigation messages file format</a>
70   * @see <a href="https://files.igs.org/pub/data/format/rinex303.pdf"> 3.03 navigation messages file format</a>
71   * @see <a href="https://files.igs.org/pub/data/format/rinex304.pdf"> 3.04 navigation messages file format</a>
72   * @see <a href="https://files.igs.org/pub/data/format/rinex305.pdf"> 3.05 navigation messages file format</a>
73   * @see <a href="https://files.igs.org/pub/data/format/rinex_4.00.pdf"> 4.00 navigation messages file format</a>
74   *
75   * @author Bryan Cazabonne
76   * @since 11.0
77   *
78   */
79  public class RinexNavigationParser {
80  
81      /** Converter for positions. */
82      private static final Unit KM = Unit.KILOMETRE;
83  
84      /** Converter for velocities. */
85      private static final Unit KM_PER_S = Unit.parse("km/s");
86  
87      /** Converter for accelerations. */
88      private static final Unit KM_PER_S2 = Unit.parse("km/s²");;
89  
90      /** Converter for velocities. */
91      private static final Unit M_PER_S = Unit.parse("m/s");
92  
93      /** Converter for clock drift. */
94      private static final Unit S_PER_S = Unit.parse("s/s");
95  
96      /** Converter for clock drift rate. */
97      private static final Unit S_PER_S2 = Unit.parse("s/s²");
98  
99      /** Converter for ΔUT₁ first derivative. */
100     private static final Unit S_PER_DAY = Unit.parse("s/d");
101 
102     /** Converter for ΔUT₁ second derivative. */
103     private static final Unit S_PER_DAY2 = Unit.parse("s/d²");
104 
105     /** Converter for square root of semi-major axis. */
106     private static final Unit SQRT_M = Unit.parse("√m");
107 
108     /** Converter for angular rates. */
109     private static final Unit RAD_PER_S = Unit.parse("rad/s");;
110 
111     /** Converter for angular accelerations. */
112     private static final Unit RAD_PER_S2 = Unit.parse("rad/s²");;
113 
114     /** Converter for rates of small angle. */
115     private static final Unit AS_PER_DAY = Unit.parse("as/d");;
116 
117     /** Converter for accelerations of small angles. */
118     private static final Unit AS_PER_DAY2 = Unit.parse("as/d²");;
119 
120     /** System initials. */
121     private static final String INITIALS = "GRECIJS";
122 
123     /** Set of time scales. */
124     private final TimeScales timeScales;
125 
126     /**
127      * Constructor.
128      * <p>This constructor uses the {@link DataContext#getDefault() default data context}.</p>
129      * @see #RinexNavigationParser(TimeScales)
130      *
131      */
132     @DefaultDataContext
133     public RinexNavigationParser() {
134         this(DataContext.getDefault().getTimeScales());
135     }
136 
137     /**
138      * Constructor.
139      * @param timeScales the set of time scales used for parsing dates.
140      */
141     public RinexNavigationParser(final TimeScales timeScales) {
142         this.timeScales = timeScales;
143     }
144 
145     /**
146      * Parse RINEX navigation messages.
147      * @param source source providing the data to parse
148      * @return a parsed  RINEX navigation messages file
149      * @throws IOException if {@code reader} throws one
150      */
151     public RinexNavigation parse(final DataSource source) throws IOException {
152 
153         // initialize internal data structures
154         final ParseInfo pi = new ParseInfo(source.getName());
155 
156         Iterable<LineParser> candidateParsers = Collections.singleton(LineParser.HEADER_VERSION);
157         try (Reader reader = source.getOpener().openReaderOnce();
158              BufferedReader br = new BufferedReader(reader)) {
159             nextLine:
160                 for (String line = br.readLine(); line != null; line = br.readLine()) {
161                     ++pi.lineNumber;
162                     for (final LineParser candidate : candidateParsers) {
163                         if (candidate.canHandle.test(line)) {
164                             try {
165                                 candidate.parsingMethod.parse(line, pi);
166                                 candidateParsers = candidate.allowedNextProvider.apply(pi);
167                                 continue nextLine;
168                             } catch (StringIndexOutOfBoundsException | NumberFormatException | InputMismatchException e) {
169                                 throw new OrekitException(e,
170                                                           OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
171                                                           pi.lineNumber, source.getName(), line);
172                             }
173                         }
174                     }
175                     throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
176                                               pi.lineNumber, source.getName(), line);
177                 }
178         }
179 
180         if (!pi.headerParsed) {
181             throw new OrekitException(OrekitMessages.UNEXPECTED_END_OF_FILE, source.getName());
182         }
183 
184         pi.closePendingMessage();
185 
186         return pi.file;
187 
188     }
189 
190     /** Transient data used for parsing a RINEX navigation messages file. */
191     private class ParseInfo {
192 
193         /** Name of the data source. */
194         private final String name;
195 
196         /** Set of time scales for parsing dates. */
197         private final TimeScales timeScales;
198 
199         /** The corresponding navigation messages file object. */
200         private RinexNavigation file;
201 
202         /** Number of initial spaces in broadcase orbits lines. */
203         private int initialSpaces;
204 
205         /** Flag indicating header has been completely parsed. */
206         private boolean headerParsed;
207 
208         /** Flag indicating the distinction between "alpha" and "beta" ionospheric coefficients. */
209         private boolean isIonosphereAlphaInitialized;
210 
211         /** Satellite system line parser. */
212         private SatelliteSystemLineParser systemLineParser;
213 
214         /** Current global line number. */
215         private int lineNumber;
216 
217         /** Current line number within the navigation message. */
218         private int messageLineNumber;
219 
220         /** Container for GPS navigation message. */
221         private GPSLegacyNavigationMessage gpsLNav;
222 
223         /** Container for GPS navigation message. */
224         private GPSCivilianNavigationMessage gpsCNav;
225 
226         /** Container for Galileo navigation message. */
227         private GalileoNavigationMessage galileoNav;
228 
229         /** Container for Beidou navigation message. */
230         private BeidouLegacyNavigationMessage beidouLNav;
231 
232         /** Container for Beidou navigation message. */
233         private BeidouCivilianNavigationMessage beidouCNav;
234 
235         /** Container for QZSS navigation message. */
236         private QZSSLegacyNavigationMessage qzssLNav;
237 
238         /** Container for QZSS navigation message. */
239         private QZSSCivilianNavigationMessage qzssCNav;
240 
241         /** Container for IRNSS navigation message. */
242         private IRNSSNavigationMessage irnssNav;
243 
244         /** Container for GLONASS navigation message. */
245         private GLONASSNavigationMessage glonassNav;
246 
247         /** Container for SBAS navigation message. */
248         private SBASNavigationMessage sbasNav;
249 
250         /** Container for System Time Offset message. */
251         private SystemTimeOffsetMessage sto;
252 
253         /** Container for Earth Orientation Parameter message. */
254         private EarthOrientationParameterMessage eop;
255 
256         /** Container for ionosphere Klobuchar message. */
257         private IonosphereKlobucharMessage klobuchar;
258 
259         /** Container for ionosphere Nequick-G message. */
260         private IonosphereNequickGMessage nequickG;
261 
262         /** Container for ionosphere BDGIM message. */
263         private IonosphereBDGIMMessage bdgim;
264 
265         /** Constructor, build the ParseInfo object.
266          * @param name name of the data source
267          */
268         ParseInfo(final String name) {
269             // Initialize default values for fields
270             this.name                         = name;
271             this.timeScales                   = RinexNavigationParser.this.timeScales;
272             this.isIonosphereAlphaInitialized = false;
273             this.file                         = new RinexNavigation();
274 
275         }
276 
277         /** Ensure navigation message has been closed.
278          */
279         void closePendingMessage() {
280             if (systemLineParser != null) {
281                 systemLineParser.closeMessage(this);
282                 systemLineParser = null;
283             }
284 
285         }
286 
287     }
288 
289     /** Parsers for specific lines. */
290     private enum LineParser {
291 
292         /** Parser for version, file type and satellite system. */
293         HEADER_VERSION(line -> RinexUtils.matchesLabel(line, "RINEX VERSION / TYPE"),
294                        (line, pi) -> {
295                            RinexUtils.parseVersionFileTypeSatelliteSystem(line, pi.name, pi.file.getHeader(),
296                                                                                     2.0, 2.01, 2.10, 2.11,
297                                                                                     3.01, 3.02, 3.03, 3.04, 3.05,
298                                                                                     4.00);
299                            pi.initialSpaces = pi.file.getHeader().getFormatVersion() < 3.0 ? 3 : 4;
300                        },
301                        LineParser::headerNext),
302 
303         /** Parser for generating program and emitting agency. */
304         HEADER_PROGRAM(line -> RinexUtils.matchesLabel(line, "PGM / RUN BY / DATE"),
305                        (line, pi) -> RinexUtils.parseProgramRunByDate(line, pi.lineNumber, pi.name, pi.timeScales, pi.file.getHeader()),
306                        LineParser::headerNext),
307 
308         /** Parser for comments. */
309         HEADER_COMMENT(line -> RinexUtils.matchesLabel(line, "COMMENT"),
310                        (line, pi) -> RinexUtils.parseComment(pi.lineNumber, line, pi.file),
311                        LineParser::headerNext),
312 
313         /** Parser for ionospheric correction parameters. */
314         HEADER_ION_ALPHA(line -> RinexUtils.matchesLabel(line, "ION ALPHA"),
315                          (line, pi) -> {
316 
317                              pi.file.getHeader().setIonosphericCorrectionType(IonosphericCorrectionType.GPS);
318 
319                              // Read coefficients
320                              final double[] parameters = new double[4];
321                              parameters[0] = RinexUtils.parseDouble(line, 2,  12);
322                              parameters[1] = RinexUtils.parseDouble(line, 14, 12);
323                              parameters[2] = RinexUtils.parseDouble(line, 26, 12);
324                              parameters[3] = RinexUtils.parseDouble(line, 38, 12);
325                              pi.file.setKlobucharAlpha(parameters);
326                              pi.isIonosphereAlphaInitialized = true;
327 
328                          },
329                          LineParser::headerNext),
330 
331         /** Parser for ionospheric correction parameters. */
332         HEADER_ION_BETA(line -> RinexUtils.matchesLabel(line, "ION BETA"),
333                         (line, pi) -> {
334 
335                             pi.file.getHeader().setIonosphericCorrectionType(IonosphericCorrectionType.GPS);
336 
337                             // Read coefficients
338                             final double[] parameters = new double[4];
339                             parameters[0] = RinexUtils.parseDouble(line, 2,  12);
340                             parameters[1] = RinexUtils.parseDouble(line, 14, 12);
341                             parameters[2] = RinexUtils.parseDouble(line, 26, 12);
342                             parameters[3] = RinexUtils.parseDouble(line, 38, 12);
343                             pi.file.setKlobucharBeta(parameters);
344 
345                         },
346                         LineParser::headerNext),
347 
348         /** Parser for ionospheric correction parameters. */
349         HEADER_IONOSPHERIC(line -> RinexUtils.matchesLabel(line, "IONOSPHERIC CORR"),
350                            (line, pi) -> {
351 
352                                // ionospheric correction type
353                                final IonosphericCorrectionType ionoType =
354                                                IonosphericCorrectionType.valueOf(RinexUtils.parseString(line, 0, 3));
355                                pi.file.getHeader().setIonosphericCorrectionType(ionoType);
356 
357                                // Read coefficients
358                                final double[] parameters = new double[4];
359                                parameters[0] = RinexUtils.parseDouble(line, 5,  12);
360                                parameters[1] = RinexUtils.parseDouble(line, 17, 12);
361                                parameters[2] = RinexUtils.parseDouble(line, 29, 12);
362                                parameters[3] = RinexUtils.parseDouble(line, 41, 12);
363 
364                                // Verify if we are parsing Galileo ionospheric parameters
365                                if (ionoType == IonosphericCorrectionType.GAL) {
366 
367                                    // We are parsing Galileo ionospheric parameters
368                                    pi.file.setNeQuickAlpha(parameters);
369 
370                                } else {
371                                    // We are parsing Klobuchar ionospheric parameters
372 
373                                    // Verify if we are parsing "alpha" or "beta" ionospheric parameters
374                                    if (pi.isIonosphereAlphaInitialized) {
375 
376                                        // Ionospheric "beta" parameters
377                                        pi.file.setKlobucharBeta(parameters);
378 
379                                    } else {
380 
381                                        // Ionospheric "alpha" parameters
382                                        pi.file.setKlobucharAlpha(parameters);
383 
384                                        // Set the flag to true
385                                        pi.isIonosphereAlphaInitialized = true;
386 
387                                    }
388 
389                                }
390 
391                            },
392                            LineParser::headerNext),
393 
394         /** Parser for corrections to transform the system time to UTC or to other time systems. */
395         HEADER_DELTA_UTC(line -> RinexUtils.matchesLabel(line, "DELTA-UTC: A0,A1,T,W"),
396                          (line, pi) -> {
397                              // Read fields
398                              final double a0      = RinexUtils.parseDouble(line, 3,  19);
399                              final double a1      = RinexUtils.parseDouble(line, 22, 19);
400                              final int    refTime = RinexUtils.parseInt(line, 41, 9);
401                              final int    refWeek = RinexUtils.parseInt(line, 50, 9);
402 
403                              // convert date
404                              final SatelliteSystem satSystem = pi.file.getHeader().getSatelliteSystem();
405                              final AbsoluteDate    date      = new GNSSDate(refWeek, refTime, satSystem, pi.timeScales).getDate();
406 
407                              // Add to the list
408                              final TimeSystemCorrection tsc = new TimeSystemCorrection("GPUT", date, a0, a1);
409                              pi.file.getHeader().addTimeSystemCorrections(tsc);
410                          },
411                          LineParser::headerNext),
412 
413         /** Parser for corrections to transform the GLONASS system time to UTC or to other time systems. */
414         HEADER_CORR_SYSTEM_TIME(line -> RinexUtils.matchesLabel(line, "CORR TO SYSTEM TIME"),
415                          (line, pi) -> {
416                              // Read fields
417                              final int year        = RinexUtils.parseInt(line,  0, 6);
418                              final int month       = RinexUtils.parseInt(line,  6, 6);
419                              final int day         = RinexUtils.parseInt(line, 12, 6);
420                              final double minusTau = RinexUtils.parseDouble(line, 21, 19);
421 
422                              // convert date
423                              final SatelliteSystem satSystem = pi.file.getHeader().getSatelliteSystem();
424                              final TimeScale       timeScale = satSystem.getObservationTimeScale().getTimeScale(pi.timeScales);
425                              final AbsoluteDate    date      = new AbsoluteDate(year, month, day, timeScale);
426 
427                              // Add to the list
428                              final TimeSystemCorrection tsc = new TimeSystemCorrection("GLUT", date, minusTau, 0.0);
429                              pi.file.getHeader().addTimeSystemCorrections(tsc);
430 
431                          },
432                          LineParser::headerNext),
433 
434         /** Parser for corrections to transform the system time to UTC or to other time systems. */
435         HEADER_TIME(line -> RinexUtils.matchesLabel(line, "TIME SYSTEM CORR"),
436                     (line, pi) -> {
437 
438                         // Read fields
439                         final String type    = RinexUtils.parseString(line, 0,  4);
440                         final double a0      = RinexUtils.parseDouble(line, 5,  17);
441                         final double a1      = RinexUtils.parseDouble(line, 22, 16);
442                         final int    refTime = RinexUtils.parseInt(line, 38, 7);
443                         final int    refWeek = RinexUtils.parseInt(line, 46, 5);
444 
445                         // convert date
446                         final SatelliteSystem satSystem = pi.file.getHeader().getSatelliteSystem();
447                         final AbsoluteDate    date;
448                         if (satSystem == SatelliteSystem.GLONASS) {
449                             date = null;
450                         } else if (satSystem == SatelliteSystem.BEIDOU) {
451                             date = new GNSSDate(refWeek, refTime, satSystem, pi.timeScales).getDate();
452                         } else {
453                             // all other systems are converted to GPS week in Rinex files!
454                             date = new GNSSDate(refWeek, refTime, SatelliteSystem.GPS, pi.timeScales).getDate();
455                         }
456 
457                         // Add to the list
458                         final TimeSystemCorrection tsc = new TimeSystemCorrection(type, date, a0, a1);
459                         pi.file.getHeader().addTimeSystemCorrections(tsc);
460 
461                     },
462                     LineParser::headerNext),
463 
464         /** Parser for leap seconds. */
465         HEADER_LEAP_SECONDS(line -> RinexUtils.matchesLabel(line, "LEAP SECONDS"),
466                             (line, pi) -> pi.file.getHeader().setNumberOfLeapSeconds(RinexUtils.parseInt(line, 0, 6)),
467                             LineParser::headerNext),
468 
469         /** Parser for DOI.
470          * @since 12.0
471          */
472         HEADER_DOI(line -> RinexUtils.matchesLabel(line, "DOI"),
473                    (line, pi) -> pi.file.getHeader().setDoi(RinexUtils.parseString(line, 0, RinexUtils.LABEL_INDEX)),
474                    LineParser::headerNext),
475 
476         /** Parser for license.
477          * @since 12.0
478          */
479         HEADER_LICENSE(line -> RinexUtils.matchesLabel(line, "LICENSE OF USE"),
480                        (line, pi) -> pi.file.getHeader().setLicense(RinexUtils.parseString(line, 0, RinexUtils.LABEL_INDEX)),
481                        LineParser::headerNext),
482 
483         /** Parser for stationInformation.
484          * @since 12.0
485          */
486         HEADER_STATION_INFORMATION(line -> RinexUtils.matchesLabel(line, "STATION INFORMATION"),
487                                    (line, pi) -> pi.file.getHeader().setStationInformation(RinexUtils.parseString(line, 0, RinexUtils.LABEL_INDEX)),
488                                    LineParser::headerNext),
489 
490         /** Parser for merged files.
491          * @since 12.0
492          */
493         HEADER_MERGED_FILE(line -> RinexUtils.matchesLabel(line, "MERGED FILE"),
494                            (line, pi) -> pi.file.getHeader().setMergedFiles(RinexUtils.parseInt(line, 0, 9)),
495                            LineParser::headerNext),
496 
497        /** Parser for the end of header. */
498         HEADER_END(line -> RinexUtils.matchesLabel(line, "END OF HEADER"),
499                    (line, pi) -> {
500                        // get rinex format version
501                        final RinexNavigationHeader header = pi.file.getHeader();
502                        final double version = header.getFormatVersion();
503 
504                        // check mandatory header fields
505                        if (header.getRunByName() == null ||
506                            version >= 4 && header.getNumberOfLeapSeconds() < 0) {
507                            throw new OrekitException(OrekitMessages.INCOMPLETE_HEADER, pi.name);
508                        }
509 
510                        pi.headerParsed = true;
511 
512                    },
513                    LineParser::navigationNext),
514 
515         /** Parser for navigation message space vehicle epoch and clock. */
516         NAVIGATION_SV_EPOCH_CLOCK_RINEX_2(line -> true,
517                                           (line, pi) -> {
518 
519                                               // Set the line number to 0
520                                               pi.messageLineNumber = 0;
521 
522                                               // Initialize parser
523                                               pi.closePendingMessage();
524                                               pi.systemLineParser = SatelliteSystemLineParser.getParser(pi.file.getHeader().getSatelliteSystem(),
525                                                                                                         null, pi, line);
526 
527                                               pi.systemLineParser.parseSvEpochSvClockLine(line, pi);
528 
529                                           },
530                                           LineParser::navigationNext),
531 
532         /** Parser for navigation message space vehicle epoch and clock. */
533         NAVIGATION_SV_EPOCH_CLOCK(line -> INITIALS.indexOf(line.charAt(0)) >= 0,
534                                   (line, pi) -> {
535 
536                                       // Set the line number to 0
537                                       pi.messageLineNumber = 0;
538 
539                                       if (pi.file.getHeader().getFormatVersion() < 4) {
540                                           // Current satellite system
541                                           final SatelliteSystem system = SatelliteSystem.parseSatelliteSystem(RinexUtils.parseString(line, 0, 1));
542 
543                                           // Initialize parser
544                                           pi.closePendingMessage();
545                                           pi.systemLineParser = SatelliteSystemLineParser.getParser(system, null, pi, line);
546                                       }
547 
548                                       // Read first line
549                                       pi.systemLineParser.parseSvEpochSvClockLine(line, pi);
550 
551                                   },
552                                   LineParser::navigationNext),
553 
554         /** Parser for navigation message type. */
555         EPH_TYPE(line -> line.startsWith("> EPH"),
556                  (line, pi) -> {
557                      final SatelliteSystem system = SatelliteSystem.parseSatelliteSystem(RinexUtils.parseString(line, 6, 1));
558                      final String          type   = RinexUtils.parseString(line, 10, 4);
559                      pi.closePendingMessage();
560                      pi.systemLineParser = SatelliteSystemLineParser.getParser(system, type, pi, line);
561                  },
562                  pi -> Collections.singleton(NAVIGATION_SV_EPOCH_CLOCK)),
563 
564         /** Parser for broadcast orbit. */
565         BROADCAST_ORBIT(line -> line.startsWith("   "),
566                         (line, pi) -> {
567                             switch (++pi.messageLineNumber) {
568                                 case 1: pi.systemLineParser.parseFirstBroadcastOrbit(line, pi);
569                                 break;
570                                 case 2: pi.systemLineParser.parseSecondBroadcastOrbit(line, pi);
571                                 break;
572                                 case 3: pi.systemLineParser.parseThirdBroadcastOrbit(line, pi);
573                                 break;
574                                 case 4: pi.systemLineParser.parseFourthBroadcastOrbit(line, pi);
575                                 break;
576                                 case 5: pi.systemLineParser.parseFifthBroadcastOrbit(line, pi);
577                                 break;
578                                 case 6: pi.systemLineParser.parseSixthBroadcastOrbit(line, pi);
579                                 break;
580                                 case 7: pi.systemLineParser.parseSeventhBroadcastOrbit(line, pi);
581                                 break;
582                                 case 8: pi.systemLineParser.parseEighthBroadcastOrbit(line, pi);
583                                 break;
584                                 case 9: pi.systemLineParser.parseNinthBroadcastOrbit(line, pi);
585                                 break;
586                                 default:
587                                     // this should never happen
588                                     throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
589                                                               pi.lineNumber, pi.name, line);
590                             }
591 
592                         },
593                         LineParser::navigationNext),
594 
595         /** Parser for system time offset message model. */
596         STO_LINE_1(line -> true,
597                    (line, pi) -> {
598                        pi.sto.setTransmissionTime(Unit.SECOND.toSI(RinexUtils.parseDouble(line,  4, 19)));
599                        pi.sto.setA0(Unit.SECOND.toSI(RinexUtils.parseDouble(line, 23, 19)));
600                        pi.sto.setA1(S_PER_S.toSI(RinexUtils.parseDouble(line, 42, 19)));
601                        pi.sto.setA2(S_PER_S2.toSI(RinexUtils.parseDouble(line, 61, 19)));
602                        pi.file.addSystemTimeOffset(pi.sto);
603                        pi.sto = null;
604                    },
605                    LineParser::navigationNext),
606 
607         /** Parser for system time offset message space vehicle epoch and clock. */
608         STO_SV_EPOCH_CLOCK(line -> true,
609                            (line, pi) -> {
610 
611                                pi.sto.setDefinedTimeSystem(TimeSystem.parseTwoLettersCode(RinexUtils.parseString(line, 24, 2)));
612                                pi.sto.setReferenceTimeSystem(TimeSystem.parseTwoLettersCode(RinexUtils.parseString(line, 26, 2)));
613                                final String sbas = RinexUtils.parseString(line, 43, 18);
614                                pi.sto.setSbasId(sbas.length() > 0 ? SbasId.valueOf(sbas) : null);
615                                final String utc = RinexUtils.parseString(line, 62, 18);
616                                pi.sto.setUtcId(utc.length() > 0 ? UtcId.parseUtcId(utc) : null);
617 
618                                // TODO is the reference date relative to one or the other time scale?
619                                final int year  = RinexUtils.parseInt(line, 4, 4);
620                                final int month = RinexUtils.parseInt(line, 9, 2);
621                                final int day   = RinexUtils.parseInt(line, 12, 2);
622                                final int hours = RinexUtils.parseInt(line, 15, 2);
623                                final int min   = RinexUtils.parseInt(line, 18, 2);
624                                final int sec   = RinexUtils.parseInt(line, 21, 2);
625                                pi.sto.setReferenceEpoch(new AbsoluteDate(year, month, day, hours, min, sec,
626                                                                          pi.sto.getDefinedTimeSystem().getTimeScale(pi.timeScales)));
627 
628                            },
629                            pi -> Collections.singleton(STO_LINE_1)),
630 
631         /** Parser for system time offset message type. */
632         STO_TYPE(line -> line.startsWith("> STO"),
633                  (line, pi) -> {
634                      pi.closePendingMessage();
635                      pi.sto = new SystemTimeOffsetMessage(SatelliteSystem.parseSatelliteSystem(RinexUtils.parseString(line, 6, 1)),
636                                                           RinexUtils.parseInt(line, 7, 2),
637                                                           RinexUtils.parseString(line, 10, 4));
638                  },
639                  pi -> Collections.singleton(STO_SV_EPOCH_CLOCK)),
640 
641         /** Parser for Earth orientation parameter message model. */
642         EOP_LINE_2(line -> true,
643                    (line, pi) -> {
644                        pi.eop.setTransmissionTime(Unit.SECOND.toSI(RinexUtils.parseDouble(line,  4, 19)));
645                        pi.eop.setDut1(Unit.SECOND.toSI(RinexUtils.parseDouble(line, 23, 19)));
646                        pi.eop.setDut1Dot(S_PER_DAY.toSI(RinexUtils.parseDouble(line, 42, 19)));
647                        pi.eop.setDut1DotDot(S_PER_DAY2.toSI(RinexUtils.parseDouble(line, 61, 19)));
648                        pi.file.addEarthOrientationParameter(pi.eop);
649                        pi.eop = null;
650                    },
651                    LineParser::navigationNext),
652 
653         /** Parser for Earth orientation parameter message model. */
654         EOP_LINE_1(line -> true,
655                    (line, pi) -> {
656                        pi.eop.setYp(Unit.ARC_SECOND.toSI(RinexUtils.parseDouble(line, 23, 19)));
657                        pi.eop.setYpDot(AS_PER_DAY.toSI(RinexUtils.parseDouble(line, 42, 19)));
658                        pi.eop.setYpDotDot(AS_PER_DAY2.toSI(RinexUtils.parseDouble(line, 61, 19)));
659                    },
660                    pi -> Collections.singleton(EOP_LINE_2)),
661 
662         /** Parser for Earth orientation parameter message space vehicle epoch and clock. */
663         EOP_SV_EPOCH_CLOCK(line -> true,
664                            (line, pi) -> {
665                                final int year  = RinexUtils.parseInt(line, 4, 4);
666                                final int month = RinexUtils.parseInt(line, 9, 2);
667                                final int day   = RinexUtils.parseInt(line, 12, 2);
668                                final int hours = RinexUtils.parseInt(line, 15, 2);
669                                final int min   = RinexUtils.parseInt(line, 18, 2);
670                                final int sec   = RinexUtils.parseInt(line, 21, 2);
671                                pi.eop.setReferenceEpoch(new AbsoluteDate(year, month, day, hours, min, sec,
672                                                                          pi.eop.getSystem().getObservationTimeScale().getTimeScale(pi.timeScales)));
673                                pi.eop.setXp(Unit.ARC_SECOND.toSI(RinexUtils.parseDouble(line, 23, 19)));
674                                pi.eop.setXpDot(AS_PER_DAY.toSI(RinexUtils.parseDouble(line, 42, 19)));
675                                pi.eop.setXpDotDot(AS_PER_DAY2.toSI(RinexUtils.parseDouble(line, 61, 19)));
676                            },
677                            pi -> Collections.singleton(EOP_LINE_1)),
678 
679         /** Parser for Earth orientation parameter message type. */
680         EOP_TYPE(line -> line.startsWith("> EOP"),
681                  (line, pi) -> {
682                      pi.closePendingMessage();
683                      pi.eop = new EarthOrientationParameterMessage(SatelliteSystem.parseSatelliteSystem(RinexUtils.parseString(line, 6, 1)),
684                                                                    RinexUtils.parseInt(line, 7, 2),
685                                                                    RinexUtils.parseString(line, 10, 4));
686                  },
687                  pi -> Collections.singleton(EOP_SV_EPOCH_CLOCK)),
688 
689         /** Parser for ionosphere Klobuchar message model. */
690         KLOBUCHAR_LINE_2(line -> true,
691                          (line, pi) -> {
692                              pi.klobuchar.setBetaI(3, IonosphereKlobucharMessage.S_PER_SC_N[3].toSI(RinexUtils.parseDouble(line,  4, 19)));
693                              pi.klobuchar.setRegionCode(RinexUtils.parseDouble(line, 23, 19) < 0.5 ?
694                                                         RegionCode.WIDE_AREA : RegionCode.JAPAN);
695                              pi.file.addKlobucharMessage(pi.klobuchar);
696                              pi.klobuchar = null;
697                          },
698                          LineParser::navigationNext),
699 
700         /** Parser for ionosphere Klobuchar message model. */
701         KLOBUCHAR_LINE_1(line -> true,
702                          (line, pi) -> {
703                              pi.klobuchar.setAlphaI(3, IonosphereKlobucharMessage.S_PER_SC_N[3].toSI(RinexUtils.parseDouble(line,  4, 19)));
704                              pi.klobuchar.setBetaI(0, IonosphereKlobucharMessage.S_PER_SC_N[0].toSI(RinexUtils.parseDouble(line, 23, 19)));
705                              pi.klobuchar.setBetaI(1, IonosphereKlobucharMessage.S_PER_SC_N[1].toSI(RinexUtils.parseDouble(line, 42, 19)));
706                              pi.klobuchar.setBetaI(2, IonosphereKlobucharMessage.S_PER_SC_N[2].toSI(RinexUtils.parseDouble(line, 61, 19)));
707                          },
708                          pi -> Collections.singleton(KLOBUCHAR_LINE_2)),
709 
710         /** Parser for ionosphere Klobuchar message model. */
711         KLOBUCHAR_LINE_0(line -> true,
712                          (line, pi) -> {
713                              final int year  = RinexUtils.parseInt(line, 4, 4);
714                              final int month = RinexUtils.parseInt(line, 9, 2);
715                              final int day   = RinexUtils.parseInt(line, 12, 2);
716                              final int hours = RinexUtils.parseInt(line, 15, 2);
717                              final int min   = RinexUtils.parseInt(line, 18, 2);
718                              final int sec   = RinexUtils.parseInt(line, 21, 2);
719                              pi.klobuchar.setTransmitTime(new AbsoluteDate(year, month, day, hours, min, sec,
720                                                                            pi.klobuchar.getSystem().getObservationTimeScale().getTimeScale(pi.timeScales)));
721                              pi.klobuchar.setAlphaI(0, IonosphereKlobucharMessage.S_PER_SC_N[0].toSI(RinexUtils.parseDouble(line, 23, 19)));
722                              pi.klobuchar.setAlphaI(1, IonosphereKlobucharMessage.S_PER_SC_N[1].toSI(RinexUtils.parseDouble(line, 42, 19)));
723                              pi.klobuchar.setAlphaI(2, IonosphereKlobucharMessage.S_PER_SC_N[2].toSI(RinexUtils.parseDouble(line, 61, 19)));
724                          },
725                          pi -> Collections.singleton(KLOBUCHAR_LINE_1)),
726 
727         /** Parser for ionosphere Nequick-G message model. */
728         NEQUICK_LINE_1(line -> true,
729                        (line, pi) -> {
730                            pi.nequickG.setFlags((int) FastMath.rint(RinexUtils.parseDouble(line, 4, 19)));
731                            pi.file.addNequickGMessage(pi.nequickG);
732                            pi.nequickG = null;
733                        },
734                        LineParser::navigationNext),
735 
736         /** Parser for ionosphere Nequick-G message model. */
737         NEQUICK_LINE_0(line -> true,
738                        (line, pi) -> {
739                            final int year  = RinexUtils.parseInt(line, 4, 4);
740                            final int month = RinexUtils.parseInt(line, 9, 2);
741                            final int day   = RinexUtils.parseInt(line, 12, 2);
742                            final int hours = RinexUtils.parseInt(line, 15, 2);
743                            final int min   = RinexUtils.parseInt(line, 18, 2);
744                            final int sec   = RinexUtils.parseInt(line, 21, 2);
745                            pi.nequickG.setTransmitTime(new AbsoluteDate(year, month, day, hours, min, sec,
746                                                                         pi.nequickG.getSystem().getObservationTimeScale().getTimeScale(pi.timeScales)));
747                            pi.nequickG.setAi0(IonosphereNequickGMessage.SFU.toSI(RinexUtils.parseDouble(line, 23, 19)));
748                            pi.nequickG.setAi1(IonosphereNequickGMessage.SFU_PER_DEG.toSI(RinexUtils.parseDouble(line, 42, 19)));
749                            pi.nequickG.setAi2(IonosphereNequickGMessage.SFU_PER_DEG2.toSI(RinexUtils.parseDouble(line, 61, 19)));
750                        },
751                        pi -> Collections.singleton(NEQUICK_LINE_1)),
752 
753         /** Parser for ionosphere BDGIM message model. */
754         BDGIM_LINE_2(line -> true,
755                      (line, pi) -> {
756                          pi.bdgim.setAlphaI(7, Unit.TOTAL_ELECTRON_CONTENT_UNIT.toSI(RinexUtils.parseDouble(line,  4, 19)));
757                          pi.bdgim.setAlphaI(8, Unit.TOTAL_ELECTRON_CONTENT_UNIT.toSI(RinexUtils.parseDouble(line, 23, 19)));
758                          pi.file.addBDGIMMessage(pi.bdgim);
759                          pi.bdgim = null;
760                      },
761                      LineParser::navigationNext),
762 
763         /** Parser for ionosphere BDGIM message model. */
764         BDGIM_LINE_1(line -> true,
765                      (line, pi) -> {
766                          pi.bdgim.setAlphaI(3, Unit.TOTAL_ELECTRON_CONTENT_UNIT.toSI(RinexUtils.parseDouble(line,  4, 19)));
767                          pi.bdgim.setAlphaI(4, Unit.TOTAL_ELECTRON_CONTENT_UNIT.toSI(RinexUtils.parseDouble(line, 23, 19)));
768                          pi.bdgim.setAlphaI(5, Unit.TOTAL_ELECTRON_CONTENT_UNIT.toSI(RinexUtils.parseDouble(line, 42, 19)));
769                          pi.bdgim.setAlphaI(6, Unit.TOTAL_ELECTRON_CONTENT_UNIT.toSI(RinexUtils.parseDouble(line, 61, 19)));
770                      },
771                      pi -> Collections.singleton(BDGIM_LINE_2)),
772 
773         /** Parser for ionosphere BDGIM message model. */
774         BDGIM_LINE_0(line -> true,
775                      (line, pi) -> {
776                          final int year  = RinexUtils.parseInt(line, 4, 4);
777                          final int month = RinexUtils.parseInt(line, 9, 2);
778                          final int day   = RinexUtils.parseInt(line, 12, 2);
779                          final int hours = RinexUtils.parseInt(line, 15, 2);
780                          final int min   = RinexUtils.parseInt(line, 18, 2);
781                          final int sec   = RinexUtils.parseInt(line, 21, 2);
782                          pi.bdgim.setTransmitTime(new AbsoluteDate(year, month, day, hours, min, sec,
783                                                                    pi.bdgim.getSystem().getObservationTimeScale().getTimeScale(pi.timeScales)));
784                          pi.bdgim.setAlphaI(0, Unit.TOTAL_ELECTRON_CONTENT_UNIT.toSI(RinexUtils.parseDouble(line, 23, 19)));
785                          pi.bdgim.setAlphaI(1, Unit.TOTAL_ELECTRON_CONTENT_UNIT.toSI(RinexUtils.parseDouble(line, 42, 19)));
786                          pi.bdgim.setAlphaI(2, Unit.TOTAL_ELECTRON_CONTENT_UNIT.toSI(RinexUtils.parseDouble(line, 61, 19)));
787                      },
788                      pi -> Collections.singleton(BDGIM_LINE_1)),
789 
790         /** Parser for ionosphere message type. */
791         IONO_TYPE(line -> line.startsWith("> ION"),
792                   (line, pi) -> {
793                       pi.closePendingMessage();
794                       final SatelliteSystem system = SatelliteSystem.parseSatelliteSystem(RinexUtils.parseString(line, 6, 1));
795                       final int             prn    = RinexUtils.parseInt(line, 7, 2);
796                       final String          type   = RinexUtils.parseString(line, 10, 4);
797                       if (system == SatelliteSystem.GALILEO) {
798                           pi.nequickG = new IonosphereNequickGMessage(system, prn, type);
799                       } else {
800                           // in Rinex 4.00, tables A32 and A34 are ambiguous as both seem to apply
801                           // to Beidou CNVX messages, we consider BDGIM is the proper model in this case
802                           if (system == SatelliteSystem.BEIDOU && "CNVX".equals(type)) {
803                               pi.bdgim = new IonosphereBDGIMMessage(system, prn, type);
804                           } else {
805                               pi.klobuchar = new IonosphereKlobucharMessage(system, prn, type);
806                           }
807                       }
808                   },
809                   pi -> Collections.singleton(pi.nequickG != null ? NEQUICK_LINE_0 : (pi.bdgim != null ? BDGIM_LINE_0 : KLOBUCHAR_LINE_0)));
810 
811         /** Predicate for identifying lines that can be parsed. */
812         private final Predicate<String> canHandle;
813 
814         /** Parsing method. */
815         private final ParsingMethod parsingMethod;
816 
817         /** Provider for next line parsers. */
818         private final Function<ParseInfo, Iterable<LineParser>> allowedNextProvider;
819 
820         /** Simple constructor.
821          * @param canHandle predicate for identifying lines that can be parsed
822          * @param parsingMethod parsing method
823          * @param allowedNextProvider supplier for allowed parsers for next line
824          */
825         LineParser(final Predicate<String> canHandle, final ParsingMethod parsingMethod,
826                    final Function<ParseInfo, Iterable<LineParser>> allowedNextProvider) {
827             this.canHandle           = canHandle;
828             this.parsingMethod       = parsingMethod;
829             this.allowedNextProvider = allowedNextProvider;
830         }
831 
832         /** Get the allowed parsers for next lines while parsing Rinex header.
833          * @param parseInfo holder for transient data
834          * @return allowed parsers for next line
835          */
836         private static Iterable<LineParser> headerNext(final ParseInfo parseInfo) {
837             if (parseInfo.file.getHeader().getFormatVersion() < 3) {
838                 // Rinex 2.x header entries
839                 return Arrays.asList(HEADER_COMMENT, HEADER_PROGRAM,
840                                      HEADER_ION_ALPHA, HEADER_ION_BETA,
841                                      HEADER_DELTA_UTC, HEADER_CORR_SYSTEM_TIME,
842                                      HEADER_LEAP_SECONDS, HEADER_END);
843             } else if (parseInfo.file.getHeader().getFormatVersion() < 4) {
844                 // Rinex 3.x header entries
845                 return Arrays.asList(HEADER_COMMENT, HEADER_PROGRAM,
846                                      HEADER_IONOSPHERIC, HEADER_TIME,
847                                      HEADER_LEAP_SECONDS, HEADER_END);
848             } else {
849                 // Rinex 4.x header entries
850                 return Arrays.asList(HEADER_COMMENT, HEADER_PROGRAM,
851                                      HEADER_DOI, HEADER_LICENSE, HEADER_STATION_INFORMATION, HEADER_MERGED_FILE,
852                                      HEADER_LEAP_SECONDS, HEADER_END);
853             }
854         }
855 
856         /** Get the allowed parsers for next lines while parsing navigation date.
857          * @param parseInfo holder for transient data
858          * @return allowed parsers for next line
859          */
860         private static Iterable<LineParser> navigationNext(final ParseInfo parseInfo) {
861             if (parseInfo.gpsLNav    != null || parseInfo.gpsCNav    != null || parseInfo.galileoNav != null ||
862                 parseInfo.beidouLNav != null || parseInfo.beidouCNav != null || parseInfo.qzssLNav   != null ||
863                 parseInfo.qzssCNav   != null || parseInfo.irnssNav   != null || parseInfo.sbasNav    != null) {
864                 return Collections.singleton(BROADCAST_ORBIT);
865             } else if (parseInfo.glonassNav != null) {
866                 if (parseInfo.messageLineNumber < 3) {
867                     return Collections.singleton(BROADCAST_ORBIT);
868                 } else {
869                     // workaround for some invalid files that should nevertheless be parsed
870                     // we have encountered in the wild merged files that claimed to be in 3.05 version
871                     // and hence needed at least 4 broadcast GLONASS orbit lines (the fourth line was
872                     // introduced in 3.05), but in fact only had 3 broadcast lines. We think they were
873                     // merged from files in 3.04 or earlier format. In order to parse these files,
874                     // we accept after the third line either another broadcast orbit line or a new message
875                     if (parseInfo.file.getHeader().getFormatVersion() < 4) {
876                         return Arrays.asList(BROADCAST_ORBIT, NAVIGATION_SV_EPOCH_CLOCK);
877                     } else {
878                         return Arrays.asList(BROADCAST_ORBIT, EPH_TYPE, STO_TYPE, EOP_TYPE, IONO_TYPE);
879                     }
880                 }
881             } else if (parseInfo.file.getHeader().getFormatVersion() < 3) {
882                 return Collections.singleton(NAVIGATION_SV_EPOCH_CLOCK_RINEX_2);
883             } else if (parseInfo.file.getHeader().getFormatVersion() < 4) {
884                 return Collections.singleton(NAVIGATION_SV_EPOCH_CLOCK);
885             } else {
886                 return Arrays.asList(EPH_TYPE, STO_TYPE, EOP_TYPE, IONO_TYPE);
887             }
888         }
889 
890     }
891 
892     /** Parsers for satellite system specific lines. */
893     private enum SatelliteSystemLineParser {
894 
895         /** GPS legacy. */
896         GPS_LNAV() {
897 
898             /** {@inheritDoc} */
899             @Override
900             public void parseSvEpochSvClockLine(final String line, final ParseInfo pi) {
901                 if (pi.file.getHeader().getFormatVersion() < 3.0) {
902                     parseSvEpochSvClockLineRinex2(line, pi.timeScales.getGPS(), pi.gpsLNav);
903                 } else {
904                     parseSvEpochSvClockLine(line, pi.timeScales.getGPS(), pi.gpsLNav);
905                 }
906             }
907 
908             /** {@inheritDoc} */
909             @Override
910             public void parseFirstBroadcastOrbit(final String line, final ParseInfo pi) {
911                 pi.gpsLNav.setIODE(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
912                 pi.gpsLNav.setCrs(parseBroadcastDouble2(line,    pi.initialSpaces, Unit.METRE));
913                 pi.gpsLNav.setDeltaN(parseBroadcastDouble3(line, pi.initialSpaces, RAD_PER_S));
914                 pi.gpsLNav.setM0(parseBroadcastDouble4(line,     pi.initialSpaces, Unit.RADIAN));
915             }
916 
917             /** {@inheritDoc} */
918             @Override
919             public void parseSecondBroadcastOrbit(final String line, final ParseInfo pi) {
920                 pi.gpsLNav.setCuc(parseBroadcastDouble1(line, pi.initialSpaces, Unit.RADIAN));
921                 pi.gpsLNav.setE(parseBroadcastDouble2(line,     pi.initialSpaces, Unit.NONE));
922                 pi.gpsLNav.setCus(parseBroadcastDouble3(line,   pi.initialSpaces, Unit.RADIAN));
923                 pi.gpsLNav.setSqrtA(parseBroadcastDouble4(line, pi.initialSpaces, SQRT_M));
924             }
925 
926             /** {@inheritDoc} */
927             @Override
928             public void parseThirdBroadcastOrbit(final String line, final ParseInfo pi) {
929                 pi.gpsLNav.setTime(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
930                 pi.gpsLNav.setCic(parseBroadcastDouble2(line,    pi.initialSpaces, Unit.RADIAN));
931                 pi.gpsLNav.setOmega0(parseBroadcastDouble3(line, pi.initialSpaces, Unit.RADIAN));
932                 pi.gpsLNav.setCis(parseBroadcastDouble4(line,    pi.initialSpaces, Unit.RADIAN));
933             }
934 
935             /** {@inheritDoc} */
936             @Override
937             public void parseFourthBroadcastOrbit(final String line, final ParseInfo pi) {
938                 pi.gpsLNav.setI0(parseBroadcastDouble1(line, pi.initialSpaces, Unit.RADIAN));
939                 pi.gpsLNav.setCrc(parseBroadcastDouble2(line,      pi.initialSpaces, Unit.METRE));
940                 pi.gpsLNav.setPa(parseBroadcastDouble3(line,       pi.initialSpaces, Unit.RADIAN));
941                 pi.gpsLNav.setOmegaDot(parseBroadcastDouble4(line, pi.initialSpaces, RAD_PER_S));
942             }
943 
944             /** {@inheritDoc} */
945             @Override
946             public void parseFifthBroadcastOrbit(final String line, final ParseInfo pi) {
947                 // iDot
948                 pi.gpsLNav.setIDot(parseBroadcastDouble1(line, pi.initialSpaces, RAD_PER_S));
949                 // Codes on L2 channel (ignored)
950                 // RinexUtils.parseDouble(line, 23, 19)
951                 // GPS week (to go with Toe)
952                 pi.gpsLNav.setWeek((int) RinexUtils.parseDouble(line, 42, 19));
953                 pi.gpsLNav.setDate(new GNSSDate(pi.gpsLNav.getWeek(),
954                                                pi.gpsLNav.getTime(),
955                                                SatelliteSystem.GPS,
956                                                pi.timeScales).getDate());
957             }
958 
959             /** {@inheritDoc} */
960             @Override
961             public void parseSixthBroadcastOrbit(final String line, final ParseInfo pi) {
962                 pi.gpsLNav.setSvAccuracy(parseBroadcastDouble1(line, pi.initialSpaces, Unit.METRE));
963                 pi.gpsLNav.setSvHealth(parseBroadcastInt2(line, pi.initialSpaces));
964                 pi.gpsLNav.setTGD(parseBroadcastDouble3(line,   pi.initialSpaces, Unit.SECOND));
965                 pi.gpsLNav.setIODC(parseBroadcastInt4(line,     pi.initialSpaces));
966             }
967 
968             /** {@inheritDoc} */
969             @Override
970             public void parseSeventhBroadcastOrbit(final String line, final ParseInfo pi) {
971                 pi.gpsLNav.setTransmissionTime(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
972                 pi.gpsLNav.setFitInterval(parseBroadcastInt2(line, pi.initialSpaces));
973                 pi.closePendingMessage();
974             }
975 
976             /** {@inheritDoc} */
977             @Override
978             public void closeMessage(final ParseInfo pi) {
979                 pi.file.addGPSLegacyNavigationMessage(pi.gpsLNav);
980                 pi.gpsLNav = null;
981             }
982 
983         },
984 
985         /** GPS civilian.
986          * @since 12.0
987          */
988         GPS_CNAV() {
989 
990             /** {@inheritDoc} */
991             @Override
992             public void parseSvEpochSvClockLine(final String line, final ParseInfo pi) {
993                 parseSvEpochSvClockLine(line, pi.timeScales.getGPS(), pi.gpsCNav);
994             }
995 
996             /** {@inheritDoc} */
997             @Override
998             public void parseFirstBroadcastOrbit(final String line, final ParseInfo pi) {
999                 pi.gpsCNav.setADot(parseBroadcastDouble1(line, pi.initialSpaces, M_PER_S));
1000                 pi.gpsCNav.setCrs(parseBroadcastDouble2(line,    pi.initialSpaces, Unit.METRE));
1001                 pi.gpsCNav.setDeltaN(parseBroadcastDouble3(line, pi.initialSpaces, RAD_PER_S));
1002                 pi.gpsCNav.setM0(parseBroadcastDouble4(line,     pi.initialSpaces, Unit.RADIAN));
1003             }
1004 
1005             /** {@inheritDoc} */
1006             @Override
1007             public void parseSecondBroadcastOrbit(final String line, final ParseInfo pi) {
1008                 pi.gpsCNav.setCuc(parseBroadcastDouble1(line,   pi.initialSpaces, Unit.RADIAN));
1009                 pi.gpsCNav.setE(parseBroadcastDouble2(line,     pi.initialSpaces, Unit.NONE));
1010                 pi.gpsCNav.setCus(parseBroadcastDouble3(line,   pi.initialSpaces, Unit.RADIAN));
1011                 pi.gpsCNav.setSqrtA(parseBroadcastDouble4(line, pi.initialSpaces, SQRT_M));
1012             }
1013 
1014             /** {@inheritDoc} */
1015             @Override
1016             public void parseThirdBroadcastOrbit(final String line, final ParseInfo pi) {
1017                 pi.gpsCNav.setTime(parseBroadcastDouble1(line,   pi.initialSpaces, Unit.SECOND));
1018                 pi.gpsCNav.setCic(parseBroadcastDouble2(line,    pi.initialSpaces, Unit.RADIAN));
1019                 pi.gpsCNav.setOmega0(parseBroadcastDouble3(line, pi.initialSpaces, Unit.RADIAN));
1020                 pi.gpsCNav.setCis(parseBroadcastDouble4(line,    pi.initialSpaces, Unit.RADIAN));
1021             }
1022 
1023             /** {@inheritDoc} */
1024             @Override
1025             public void parseFourthBroadcastOrbit(final String line, final ParseInfo pi) {
1026                 pi.gpsCNav.setI0(parseBroadcastDouble1(line, pi.initialSpaces, Unit.RADIAN));
1027                 pi.gpsCNav.setCrc(parseBroadcastDouble2(line,      pi.initialSpaces, Unit.METRE));
1028                 pi.gpsCNav.setPa(parseBroadcastDouble3(line,       pi.initialSpaces, Unit.RADIAN));
1029                 pi.gpsCNav.setOmegaDot(parseBroadcastDouble4(line, pi.initialSpaces, RAD_PER_S));
1030             }
1031 
1032             /** {@inheritDoc} */
1033             @Override
1034             public void parseFifthBroadcastOrbit(final String line, final ParseInfo pi) {
1035                 pi.gpsCNav.setIDot(parseBroadcastDouble1(line, pi.initialSpaces, RAD_PER_S));
1036                 pi.gpsCNav.setDeltaN0Dot(parseBroadcastDouble2(line, pi.initialSpaces, RAD_PER_S2));
1037                 pi.gpsCNav.setUraiNed0(parseBroadcastInt3(line, pi.initialSpaces));
1038                 pi.gpsCNav.setUraiNed1(parseBroadcastInt4(line, pi.initialSpaces));
1039             }
1040 
1041             /** {@inheritDoc} */
1042             @Override
1043             public void parseSixthBroadcastOrbit(final String line, final ParseInfo pi) {
1044                 pi.gpsCNav.setUraiEd(parseBroadcastInt1(line, pi.initialSpaces));
1045                 pi.gpsCNav.setSvHealth(parseBroadcastInt2(line, pi.initialSpaces));
1046                 pi.gpsCNav.setTGD(parseBroadcastDouble3(line, pi.initialSpaces, Unit.SECOND));
1047                 pi.gpsCNav.setUraiNed2(parseBroadcastInt4(line, pi.initialSpaces));
1048             }
1049 
1050             /** {@inheritDoc} */
1051             @Override
1052             public void parseSeventhBroadcastOrbit(final String line, final ParseInfo pi) {
1053                 pi.gpsCNav.setIscL1CA(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
1054                 pi.gpsCNav.setIscL2C(parseBroadcastDouble2(line,  pi.initialSpaces, Unit.SECOND));
1055                 pi.gpsCNav.setIscL5I5(parseBroadcastDouble3(line, pi.initialSpaces, Unit.SECOND));
1056                 pi.gpsCNav.setIscL5Q5(parseBroadcastDouble4(line, pi.initialSpaces, Unit.SECOND));
1057             }
1058 
1059             /** {@inheritDoc} */
1060             @Override
1061             public void parseEighthBroadcastOrbit(final String line, final ParseInfo pi) {
1062                 if (pi.gpsCNav.isCnv2()) {
1063                     // in CNAV2 messages, there is an additional line for L1 CD and L1 CP inter signal delay
1064                     pi.gpsCNav.setIscL1CD(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
1065                     pi.gpsCNav.setIscL1CP(parseBroadcastDouble2(line, pi.initialSpaces, Unit.SECOND));
1066                 } else {
1067                     parseTransmissionTimeLine(line, pi);
1068                 }
1069             }
1070 
1071             /** {@inheritDoc} */
1072             @Override
1073             public void parseNinthBroadcastOrbit(final String line, final ParseInfo pi) {
1074                 parseTransmissionTimeLine(line, pi);
1075             }
1076 
1077             /** Parse transmission time line.
1078              * @param line line to parse
1079              * @param pi holder for transient data
1080              */
1081             private void parseTransmissionTimeLine(final String line, final ParseInfo pi) {
1082                 pi.gpsCNav.setTransmissionTime(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
1083                 pi.closePendingMessage();
1084             }
1085 
1086             /** {@inheritDoc} */
1087             @Override
1088             public void closeMessage(final ParseInfo pi) {
1089                 pi.file.addGPSLegacyNavigationMessage(pi.gpsCNav);
1090                 pi.gpsCNav = null;
1091             }
1092 
1093         },
1094 
1095         /** Galileo. */
1096         GALILEO() {
1097 
1098             /** {@inheritDoc} */
1099             @Override
1100             public void parseSvEpochSvClockLine(final String line, final ParseInfo pi) {
1101                 parseSvEpochSvClockLine(line, pi.timeScales.getGPS(), pi.galileoNav);
1102             }
1103 
1104             /** {@inheritDoc} */
1105             @Override
1106             public void parseFirstBroadcastOrbit(final String line, final ParseInfo pi) {
1107                 pi.galileoNav.setIODNav(parseBroadcastInt1(line, pi.initialSpaces));
1108                 pi.galileoNav.setCrs(parseBroadcastDouble2(line,       pi.initialSpaces, Unit.METRE));
1109                 pi.galileoNav.setDeltaN(parseBroadcastDouble3(line,    pi.initialSpaces, RAD_PER_S));
1110                 pi.galileoNav.setM0(parseBroadcastDouble4(line,        pi.initialSpaces, Unit.RADIAN));
1111             }
1112 
1113             /** {@inheritDoc} */
1114             @Override
1115             public void parseSecondBroadcastOrbit(final String line, final ParseInfo pi) {
1116                 pi.galileoNav.setCuc(parseBroadcastDouble1(line,   pi.initialSpaces, Unit.RADIAN));
1117                 pi.galileoNav.setE(parseBroadcastDouble2(line,     pi.initialSpaces, Unit.NONE));
1118                 pi.galileoNav.setCus(parseBroadcastDouble3(line,   pi.initialSpaces, Unit.RADIAN));
1119                 pi.galileoNav.setSqrtA(parseBroadcastDouble4(line, pi.initialSpaces, SQRT_M));
1120             }
1121 
1122             /** {@inheritDoc} */
1123             @Override
1124             public void parseThirdBroadcastOrbit(final String line, final ParseInfo pi) {
1125                 pi.galileoNav.setTime(parseBroadcastDouble1(line,   pi.initialSpaces, Unit.SECOND));
1126                 pi.galileoNav.setCic(parseBroadcastDouble2(line,    pi.initialSpaces, Unit.RADIAN));
1127                 pi.galileoNav.setOmega0(parseBroadcastDouble3(line, pi.initialSpaces, Unit.RADIAN));
1128                 pi.galileoNav.setCis(parseBroadcastDouble4(line,    pi.initialSpaces, Unit.RADIAN));
1129             }
1130 
1131             /** {@inheritDoc} */
1132             @Override
1133             public void parseFourthBroadcastOrbit(final String line, final ParseInfo pi) {
1134                 pi.galileoNav.setI0(parseBroadcastDouble1(line, pi.initialSpaces, Unit.RADIAN));
1135                 pi.galileoNav.setCrc(parseBroadcastDouble2(line,      pi.initialSpaces, Unit.METRE));
1136                 pi.galileoNav.setPa(parseBroadcastDouble3(line,       pi.initialSpaces, Unit.RADIAN));
1137                 pi.galileoNav.setOmegaDot(parseBroadcastDouble4(line, pi.initialSpaces, RAD_PER_S));
1138             }
1139 
1140             /** {@inheritDoc} */
1141             @Override
1142             public void parseFifthBroadcastOrbit(final String line, final ParseInfo pi) {
1143                 // iDot
1144                 pi.galileoNav.setIDot(parseBroadcastDouble1(line, pi.initialSpaces, RAD_PER_S));
1145                 pi.galileoNav.setDataSource(parseBroadcastInt2(line, pi.initialSpaces));
1146                 // GAL week (to go with Toe)
1147                 pi.galileoNav.setWeek(parseBroadcastInt3(line, pi.initialSpaces));
1148                 pi.galileoNav.setDate(new GNSSDate(pi.galileoNav.getWeek(),
1149                                                    pi.galileoNav.getTime(),
1150                                                    SatelliteSystem.GPS, // in Rinex files, week number is aligned to GPS week!
1151                                                    pi.timeScales).getDate());
1152             }
1153 
1154             /** {@inheritDoc} */
1155             @Override
1156             public void parseSixthBroadcastOrbit(final String line, final ParseInfo pi) {
1157                 pi.galileoNav.setSisa(parseBroadcastDouble1(line, pi.initialSpaces, Unit.METRE));
1158                 pi.galileoNav.setSvHealth(parseBroadcastDouble2(line, pi.initialSpaces, Unit.NONE));
1159                 pi.galileoNav.setBGDE1E5a(parseBroadcastDouble3(line, pi.initialSpaces, Unit.SECOND));
1160                 pi.galileoNav.setBGDE5bE1(parseBroadcastDouble4(line, pi.initialSpaces, Unit.SECOND));
1161             }
1162 
1163             /** {@inheritDoc} */
1164             @Override
1165             public void parseSeventhBroadcastOrbit(final String line, final ParseInfo pi) {
1166                 pi.galileoNav.setTransmissionTime(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
1167                 pi.closePendingMessage();
1168             }
1169 
1170             /** {@inheritDoc} */
1171             @Override
1172             public void closeMessage(final ParseInfo pi) {
1173                 pi.file.addGalileoNavigationMessage(pi.galileoNav);
1174                 pi.galileoNav = null;
1175             }
1176 
1177         },
1178 
1179         /** Glonass. */
1180         GLONASS() {
1181 
1182             /** {@inheritDoc} */
1183             @Override
1184             public void parseSvEpochSvClockLine(final String line, final ParseInfo pi) {
1185 
1186                 if (pi.file.getHeader().getFormatVersion() < 3.0) {
1187 
1188                     pi.glonassNav.setPRN(RinexUtils.parseInt(line, 0, 2));
1189 
1190                     // Toc
1191                     final int    year  = RinexUtils.convert2DigitsYear(RinexUtils.parseInt(line,  3, 2));
1192                     final int    month = RinexUtils.parseInt(line,  6, 2);
1193                     final int    day   = RinexUtils.parseInt(line,  9, 2);
1194                     final int    hours = RinexUtils.parseInt(line, 12, 2);
1195                     final int    min   = RinexUtils.parseInt(line, 15, 2);
1196                     final double sec   = RinexUtils.parseDouble(line, 17, 5);
1197                     pi.glonassNav.setEpochToc(new AbsoluteDate(year, month, day, hours, min, sec,
1198                                                                pi.timeScales.getUTC()));
1199 
1200                     // clock
1201                     pi.glonassNav.setTauN(-RinexUtils.parseDouble(line, 22, 19));
1202                     pi.glonassNav.setGammaN(RinexUtils.parseDouble(line, 41, 19));
1203                     pi.glonassNav.setTime(fmod(RinexUtils.parseDouble(line, 60, 19), Constants.JULIAN_DAY));
1204 
1205                     // Set the ephemeris epoch (same as time of clock epoch)
1206                     pi.glonassNav.setDate(pi.glonassNav.getEpochToc());
1207 
1208                 } else {
1209                     pi.glonassNav.setPRN(RinexUtils.parseInt(line, 1, 2));
1210 
1211                     // Toc
1212                     pi.glonassNav.setEpochToc(parsePrnSvEpochClock(line, pi.timeScales.getUTC()));
1213 
1214                     // clock
1215                     pi.glonassNav.setTauN(-RinexUtils.parseDouble(line, 23, 19));
1216                     pi.glonassNav.setGammaN(RinexUtils.parseDouble(line, 42, 19));
1217                     pi.glonassNav.setTime(fmod(RinexUtils.parseDouble(line, 61, 19), Constants.JULIAN_DAY));
1218 
1219                     // Set the ephemeris epoch (same as time of clock epoch)
1220                     pi.glonassNav.setDate(pi.glonassNav.getEpochToc());
1221                 }
1222 
1223             }
1224 
1225             /** {@inheritDoc} */
1226             @Override
1227             public void parseFirstBroadcastOrbit(final String line, final ParseInfo pi) {
1228                 pi.glonassNav.setX(parseBroadcastDouble1(line, pi.initialSpaces, KM));
1229                 pi.glonassNav.setXDot(parseBroadcastDouble2(line,    pi.initialSpaces, KM_PER_S));
1230                 pi.glonassNav.setXDotDot(parseBroadcastDouble3(line, pi.initialSpaces, KM_PER_S2));
1231                 pi.glonassNav.setHealth(parseBroadcastDouble4(line,  pi.initialSpaces, Unit.NONE));
1232             }
1233 
1234             /** {@inheritDoc} */
1235             @Override
1236             public void parseSecondBroadcastOrbit(final String line, final ParseInfo pi) {
1237                 pi.glonassNav.setY(parseBroadcastDouble1(line, pi.initialSpaces, KM));
1238                 pi.glonassNav.setYDot(parseBroadcastDouble2(line,            pi.initialSpaces, KM_PER_S));
1239                 pi.glonassNav.setYDotDot(parseBroadcastDouble3(line,         pi.initialSpaces, KM_PER_S2));
1240                 pi.glonassNav.setFrequencyNumber(parseBroadcastDouble4(line, pi.initialSpaces, Unit.NONE));
1241             }
1242 
1243             /** {@inheritDoc} */
1244             @Override
1245             public void parseThirdBroadcastOrbit(final String line, final ParseInfo pi) {
1246                 pi.glonassNav.setZ(parseBroadcastDouble1(line, pi.initialSpaces, KM));
1247                 pi.glonassNav.setZDot(parseBroadcastDouble2(line,    pi.initialSpaces, KM_PER_S));
1248                 pi.glonassNav.setZDotDot(parseBroadcastDouble3(line, pi.initialSpaces, KM_PER_S2));
1249                 if (pi.file.getHeader().getFormatVersion() < 3.045) {
1250                     pi.closePendingMessage();
1251                 }
1252             }
1253 
1254             /** {@inheritDoc} */
1255             @Override
1256             public void parseFourthBroadcastOrbit(final String line, final ParseInfo pi) {
1257                 pi.glonassNav.setStatusFlags(parseBroadcastDouble1(line, pi.initialSpaces, Unit.NONE));
1258                 pi.glonassNav.setGroupDelayDifference(parseBroadcastDouble2(line, pi.initialSpaces, Unit.NONE));
1259                 pi.glonassNav.setURA(parseBroadcastDouble3(line,                  pi.initialSpaces, Unit.NONE));
1260                 pi.glonassNav.setHealthFlags(parseBroadcastDouble4(line,          pi.initialSpaces, Unit.NONE));
1261                 pi.closePendingMessage();
1262             }
1263 
1264             /** {@inheritDoc} */
1265             @Override
1266             public void closeMessage(final ParseInfo pi) {
1267                 pi.file.addGlonassNavigationMessage(pi.glonassNav);
1268                 pi.glonassNav = null;
1269             }
1270 
1271         },
1272 
1273         /** QZSS legacy. */
1274         QZSS_LNAV() {
1275 
1276             /** {@inheritDoc} */
1277             @Override
1278             public void parseSvEpochSvClockLine(final String line, final ParseInfo pi) {
1279                 parseSvEpochSvClockLine(line, pi.timeScales.getGPS(), pi.qzssLNav);
1280             }
1281 
1282             /** {@inheritDoc} */
1283             @Override
1284             public void parseFirstBroadcastOrbit(final String line, final ParseInfo pi) {
1285                 pi.qzssLNav.setIODE(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
1286                 pi.qzssLNav.setCrs(parseBroadcastDouble2(line,    pi.initialSpaces, Unit.METRE));
1287                 pi.qzssLNav.setDeltaN(parseBroadcastDouble3(line, pi.initialSpaces, RAD_PER_S));
1288                 pi.qzssLNav.setM0(parseBroadcastDouble4(line,     pi.initialSpaces, Unit.RADIAN));
1289             }
1290 
1291             /** {@inheritDoc} */
1292             @Override
1293             public void parseSecondBroadcastOrbit(final String line, final ParseInfo pi) {
1294                 pi.qzssLNav.setCuc(parseBroadcastDouble1(line, pi.initialSpaces, Unit.RADIAN));
1295                 pi.qzssLNav.setE(parseBroadcastDouble2(line,     pi.initialSpaces, Unit.NONE));
1296                 pi.qzssLNav.setCus(parseBroadcastDouble3(line,   pi.initialSpaces, Unit.RADIAN));
1297                 pi.qzssLNav.setSqrtA(parseBroadcastDouble4(line, pi.initialSpaces, SQRT_M));
1298             }
1299 
1300             /** {@inheritDoc} */
1301             @Override
1302             public void parseThirdBroadcastOrbit(final String line, final ParseInfo pi) {
1303                 pi.qzssLNav.setTime(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
1304                 pi.qzssLNav.setCic(parseBroadcastDouble2(line,    pi.initialSpaces, Unit.RADIAN));
1305                 pi.qzssLNav.setOmega0(parseBroadcastDouble3(line, pi.initialSpaces, Unit.RADIAN));
1306                 pi.qzssLNav.setCis(parseBroadcastDouble4(line,    pi.initialSpaces, Unit.RADIAN));
1307             }
1308 
1309             /** {@inheritDoc} */
1310             @Override
1311             public void parseFourthBroadcastOrbit(final String line, final ParseInfo pi) {
1312                 pi.qzssLNav.setI0(parseBroadcastDouble1(line, pi.initialSpaces, Unit.RADIAN));
1313                 pi.qzssLNav.setCrc(parseBroadcastDouble2(line,      pi.initialSpaces, Unit.METRE));
1314                 pi.qzssLNav.setPa(parseBroadcastDouble3(line,       pi.initialSpaces, Unit.RADIAN));
1315                 pi.qzssLNav.setOmegaDot(parseBroadcastDouble4(line, pi.initialSpaces, RAD_PER_S));
1316             }
1317 
1318             /** {@inheritDoc} */
1319             @Override
1320             public void parseFifthBroadcastOrbit(final String line, final ParseInfo pi) {
1321                 // iDot
1322                 pi.qzssLNav.setIDot(parseBroadcastDouble1(line, pi.initialSpaces, RAD_PER_S));
1323                 // Codes on L2 channel (ignored)
1324                 // RinexUtils.parseDouble(line, 23, 19)
1325                 // GPS week (to go with Toe)
1326                 pi.qzssLNav.setWeek(parseBroadcastInt3(line, pi.initialSpaces));
1327                 pi.qzssLNav.setDate(new GNSSDate(pi.qzssLNav.getWeek(),
1328                                                  pi.qzssLNav.getTime(),
1329                                                  SatelliteSystem.GPS, // in Rinex files, week number is aligned to GPS week!
1330                                                  pi.timeScales).getDate());
1331             }
1332 
1333             /** {@inheritDoc} */
1334             @Override
1335             public void parseSixthBroadcastOrbit(final String line, final ParseInfo pi) {
1336                 pi.qzssLNav.setSvAccuracy(parseBroadcastDouble1(line,  pi.initialSpaces, Unit.METRE));
1337                 pi.qzssLNav.setSvHealth(parseBroadcastInt2(line, pi.initialSpaces));
1338                 pi.qzssLNav.setTGD(parseBroadcastDouble3(line,   pi.initialSpaces, Unit.SECOND));
1339                 pi.qzssLNav.setIODC(parseBroadcastInt4(line,     pi.initialSpaces));
1340             }
1341 
1342             /** {@inheritDoc} */
1343             @Override
1344             public void parseSeventhBroadcastOrbit(final String line, final ParseInfo pi) {
1345                 pi.qzssLNav.setTransmissionTime(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
1346                 pi.qzssLNav.setFitInterval(parseBroadcastInt2(line, pi.initialSpaces));
1347                 pi.closePendingMessage();
1348             }
1349 
1350             /** {@inheritDoc} */
1351             @Override
1352             public void closeMessage(final ParseInfo pi) {
1353                 pi.file.addQZSSLegacyNavigationMessage(pi.qzssLNav);
1354                 pi.qzssLNav = null;
1355             }
1356 
1357         },
1358 
1359         /** QZSS civilian.
1360          * @since 12.0
1361          */
1362         QZSS_CNAV() {
1363 
1364             /** {@inheritDoc} */
1365             @Override
1366             public void parseSvEpochSvClockLine(final String line, final ParseInfo pi) {
1367                 parseSvEpochSvClockLine(line, pi.timeScales.getGPS(), pi.qzssCNav);
1368             }
1369 
1370             /** {@inheritDoc} */
1371             @Override
1372             public void parseFirstBroadcastOrbit(final String line, final ParseInfo pi) {
1373                 pi.qzssCNav.setADot(parseBroadcastDouble1(line, pi.initialSpaces, M_PER_S));
1374                 pi.qzssCNav.setCrs(parseBroadcastDouble2(line,    pi.initialSpaces, Unit.METRE));
1375                 pi.qzssCNav.setDeltaN(parseBroadcastDouble3(line, pi.initialSpaces, RAD_PER_S));
1376                 pi.qzssCNav.setM0(parseBroadcastDouble4(line,     pi.initialSpaces, Unit.RADIAN));
1377             }
1378 
1379             /** {@inheritDoc} */
1380             @Override
1381             public void parseSecondBroadcastOrbit(final String line, final ParseInfo pi) {
1382                 pi.qzssCNav.setCuc(parseBroadcastDouble1(line, pi.initialSpaces, Unit.RADIAN));
1383                 pi.qzssCNav.setE(parseBroadcastDouble2(line,     pi.initialSpaces, Unit.NONE));
1384                 pi.qzssCNav.setCus(parseBroadcastDouble3(line,   pi.initialSpaces, Unit.RADIAN));
1385                 pi.qzssCNav.setSqrtA(parseBroadcastDouble4(line, pi.initialSpaces, SQRT_M));
1386             }
1387 
1388             /** {@inheritDoc} */
1389             @Override
1390             public void parseThirdBroadcastOrbit(final String line, final ParseInfo pi) {
1391                 pi.qzssCNav.setTime(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
1392                 pi.qzssCNav.setCic(parseBroadcastDouble2(line,    pi.initialSpaces, Unit.RADIAN));
1393                 pi.qzssCNav.setOmega0(parseBroadcastDouble3(line, pi.initialSpaces, Unit.RADIAN));
1394                 pi.qzssCNav.setCis(parseBroadcastDouble4(line,    pi.initialSpaces, Unit.RADIAN));
1395             }
1396 
1397             /** {@inheritDoc} */
1398             @Override
1399             public void parseFourthBroadcastOrbit(final String line, final ParseInfo pi) {
1400                 pi.qzssCNav.setI0(parseBroadcastDouble1(line, pi.initialSpaces, Unit.RADIAN));
1401                 pi.qzssCNav.setCrc(parseBroadcastDouble2(line,      pi.initialSpaces, Unit.METRE));
1402                 pi.qzssCNav.setPa(parseBroadcastDouble3(line,       pi.initialSpaces, Unit.RADIAN));
1403                 pi.qzssCNav.setOmegaDot(parseBroadcastDouble4(line, pi.initialSpaces, RAD_PER_S));
1404             }
1405 
1406             /** {@inheritDoc} */
1407             @Override
1408             public void parseFifthBroadcastOrbit(final String line, final ParseInfo pi) {
1409                 pi.qzssCNav.setIDot(parseBroadcastDouble1(line, pi.initialSpaces, RAD_PER_S));
1410                 pi.qzssCNav.setDeltaN0Dot(parseBroadcastDouble2(line, pi.initialSpaces, RAD_PER_S2));
1411                 pi.qzssCNav.setUraiNed0(parseBroadcastInt3(line, pi.initialSpaces));
1412                 pi.qzssCNav.setUraiNed1(parseBroadcastInt4(line, pi.initialSpaces));
1413             }
1414 
1415             /** {@inheritDoc} */
1416             @Override
1417             public void parseSixthBroadcastOrbit(final String line, final ParseInfo pi) {
1418                 pi.qzssCNav.setUraiEd(parseBroadcastInt1(line, pi.initialSpaces));
1419                 pi.qzssCNav.setSvHealth(parseBroadcastInt2(line, pi.initialSpaces));
1420                 pi.qzssCNav.setTGD(parseBroadcastDouble3(line, pi.initialSpaces, Unit.SECOND));
1421                 pi.qzssCNav.setUraiNed2(parseBroadcastInt4(line, pi.initialSpaces));
1422             }
1423 
1424             /** {@inheritDoc} */
1425             @Override
1426             public void parseSeventhBroadcastOrbit(final String line, final ParseInfo pi) {
1427                 pi.qzssCNav.setIscL1CA(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
1428                 pi.qzssCNav.setIscL2C(parseBroadcastDouble2(line,  pi.initialSpaces, Unit.SECOND));
1429                 pi.qzssCNav.setIscL5I5(parseBroadcastDouble3(line, pi.initialSpaces, Unit.SECOND));
1430                 pi.qzssCNav.setIscL5Q5(parseBroadcastDouble4(line, pi.initialSpaces, Unit.SECOND));
1431             }
1432 
1433             /** {@inheritDoc} */
1434             @Override
1435             public void parseEighthBroadcastOrbit(final String line, final ParseInfo pi) {
1436                 if (pi.qzssCNav.isCnv2()) {
1437                     // in CNAV2 messages, there is an additional line for L1 CD and L1 CP inter signal delay
1438                     pi.qzssCNav.setIscL1CD(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
1439                     pi.qzssCNav.setIscL1CP(parseBroadcastDouble2(line, pi.initialSpaces, Unit.SECOND));
1440                 } else {
1441                     parseTransmissionTimeLine(line, pi);
1442                 }
1443             }
1444 
1445             /** {@inheritDoc} */
1446             @Override
1447             public void parseNinthBroadcastOrbit(final String line, final ParseInfo pi) {
1448                 parseTransmissionTimeLine(line, pi);
1449             }
1450 
1451             /** Parse transmission time line.
1452              * @param line line to parse
1453              * @param pi holder for transient data
1454              */
1455             private void parseTransmissionTimeLine(final String line, final ParseInfo pi) {
1456                 pi.qzssCNav.setTransmissionTime(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
1457                 pi.closePendingMessage();
1458             }
1459 
1460             /** {@inheritDoc} */
1461             @Override
1462             public void closeMessage(final ParseInfo pi) {
1463                 pi.file.addQZSSCivilianNavigationMessage(pi.qzssCNav);
1464                 pi.qzssCNav = null;
1465             }
1466 
1467         },
1468 
1469         /** Beidou legacy. */
1470         BEIDOU_D1_D2() {
1471 
1472             /** {@inheritDoc} */
1473             @Override
1474             public void parseSvEpochSvClockLine(final String line, final ParseInfo pi) {
1475                 parseSvEpochSvClockLine(line, pi.timeScales.getBDT(), pi.beidouLNav);
1476             }
1477 
1478             /** {@inheritDoc} */
1479             @Override
1480             public void parseFirstBroadcastOrbit(final String line, final ParseInfo pi) {
1481                 pi.beidouLNav.setAODE(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
1482                 pi.beidouLNav.setCrs(parseBroadcastDouble2(line,    pi.initialSpaces, Unit.METRE));
1483                 pi.beidouLNav.setDeltaN(parseBroadcastDouble3(line, pi.initialSpaces, RAD_PER_S));
1484                 pi.beidouLNav.setM0(parseBroadcastDouble4(line,     pi.initialSpaces, Unit.RADIAN));
1485             }
1486 
1487             /** {@inheritDoc} */
1488             @Override
1489             public void parseSecondBroadcastOrbit(final String line, final ParseInfo pi) {
1490                 pi.beidouLNav.setCuc(parseBroadcastDouble1(line,   pi.initialSpaces, Unit.RADIAN));
1491                 pi.beidouLNav.setE(parseBroadcastDouble2(line,     pi.initialSpaces, Unit.NONE));
1492                 pi.beidouLNav.setCus(parseBroadcastDouble3(line,   pi.initialSpaces, Unit.RADIAN));
1493                 pi.beidouLNav.setSqrtA(parseBroadcastDouble4(line, pi.initialSpaces, SQRT_M));
1494             }
1495 
1496             /** {@inheritDoc} */
1497             @Override
1498             public void parseThirdBroadcastOrbit(final String line, final ParseInfo pi) {
1499                 pi.beidouLNav.setTime(parseBroadcastDouble1(line,   pi.initialSpaces, Unit.SECOND));
1500                 pi.beidouLNav.setCic(parseBroadcastDouble2(line,    pi.initialSpaces, Unit.RADIAN));
1501                 pi.beidouLNav.setOmega0(parseBroadcastDouble3(line, pi.initialSpaces, Unit.RADIAN));
1502                 pi.beidouLNav.setCis(parseBroadcastDouble4(line,    pi.initialSpaces, Unit.RADIAN));
1503             }
1504 
1505             /** {@inheritDoc} */
1506             @Override
1507             public void parseFourthBroadcastOrbit(final String line, final ParseInfo pi) {
1508                 pi.beidouLNav.setI0(parseBroadcastDouble1(line,       pi.initialSpaces, Unit.RADIAN));
1509                 pi.beidouLNav.setCrc(parseBroadcastDouble2(line,      pi.initialSpaces, Unit.METRE));
1510                 pi.beidouLNav.setPa(parseBroadcastDouble3(line,       pi.initialSpaces, Unit.RADIAN));
1511                 pi.beidouLNav.setOmegaDot(parseBroadcastDouble4(line, pi.initialSpaces, RAD_PER_S));
1512             }
1513 
1514             /** {@inheritDoc} */
1515             @Override
1516             public void parseFifthBroadcastOrbit(final String line, final ParseInfo pi) {
1517                 // iDot
1518                 pi.beidouLNav.setIDot(parseBroadcastDouble1(line, pi.initialSpaces, RAD_PER_S));
1519                 // BDT week (to go with Toe)
1520                 pi.beidouLNav.setWeek(parseBroadcastInt3(line, pi.initialSpaces));
1521                 pi.beidouLNav.setDate(new GNSSDate(pi.beidouLNav.getWeek(),
1522                                                    pi.beidouLNav.getTime(),
1523                                                    SatelliteSystem.BEIDOU,
1524                                                    pi.timeScales).getDate());
1525             }
1526 
1527             /** {@inheritDoc} */
1528             @Override
1529             public void parseSixthBroadcastOrbit(final String line, final ParseInfo pi) {
1530                 pi.beidouLNav.setSvAccuracy(parseBroadcastDouble1(line, pi.initialSpaces, Unit.METRE));
1531                 // TODO SatH1
1532                 pi.beidouLNav.setTGD1(parseBroadcastDouble3(line,       pi.initialSpaces, Unit.SECOND));
1533                 pi.beidouLNav.setTGD2(parseBroadcastDouble4(line,       pi.initialSpaces, Unit.SECOND));
1534             }
1535 
1536             /** {@inheritDoc} */
1537             @Override
1538             public void parseSeventhBroadcastOrbit(final String line, final ParseInfo pi) {
1539                 pi.beidouLNav.setTransmissionTime(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
1540                 pi.beidouLNav.setAODC(parseBroadcastDouble2(line,             pi.initialSpaces, Unit.SECOND));
1541                 pi.closePendingMessage();
1542             }
1543 
1544             /** {@inheritDoc} */
1545             @Override
1546             public void closeMessage(final ParseInfo pi) {
1547                 pi.file.addBeidouLegacyNavigationMessage(pi.beidouLNav);
1548                 pi.beidouLNav = null;
1549             }
1550 
1551         },
1552 
1553         /** Beidou-3 CNAV. */
1554         BEIDOU_CNV_123() {
1555 
1556             /** {@inheritDoc} */
1557             @Override
1558             public void parseSvEpochSvClockLine(final String line, final ParseInfo pi) {
1559                 parseSvEpochSvClockLine(line, pi.timeScales.getBDT(), pi.beidouCNav);
1560             }
1561 
1562             /** {@inheritDoc} */
1563             @Override
1564             public void parseFirstBroadcastOrbit(final String line, final ParseInfo pi) {
1565                 pi.beidouCNav.setADot(parseBroadcastDouble1(line, pi.initialSpaces, M_PER_S));
1566                 pi.beidouCNav.setCrs(parseBroadcastDouble2(line,    pi.initialSpaces, Unit.METRE));
1567                 pi.beidouCNav.setDeltaN(parseBroadcastDouble3(line, pi.initialSpaces, RAD_PER_S));
1568                 pi.beidouCNav.setM0(parseBroadcastDouble4(line,     pi.initialSpaces, Unit.RADIAN));
1569             }
1570 
1571             /** {@inheritDoc} */
1572             @Override
1573             public void parseSecondBroadcastOrbit(final String line, final ParseInfo pi) {
1574                 pi.beidouCNav.setCuc(parseBroadcastDouble1(line, pi.initialSpaces, Unit.RADIAN));
1575                 pi.beidouCNav.setE(parseBroadcastDouble2(line,     pi.initialSpaces, Unit.NONE));
1576                 pi.beidouCNav.setCus(parseBroadcastDouble3(line,   pi.initialSpaces, Unit.RADIAN));
1577                 pi.beidouCNav.setSqrtA(parseBroadcastDouble4(line, pi.initialSpaces, SQRT_M));
1578             }
1579 
1580             /** {@inheritDoc} */
1581             @Override
1582             public void parseThirdBroadcastOrbit(final String line, final ParseInfo pi) {
1583                 pi.beidouCNav.setTime(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
1584                 pi.beidouCNav.setCic(parseBroadcastDouble2(line,    pi.initialSpaces, Unit.RADIAN));
1585                 pi.beidouCNav.setOmega0(parseBroadcastDouble3(line, pi.initialSpaces, Unit.RADIAN));
1586                 pi.beidouCNav.setCis(parseBroadcastDouble4(line,    pi.initialSpaces, Unit.RADIAN));
1587             }
1588 
1589             /** {@inheritDoc} */
1590             @Override
1591             public void parseFourthBroadcastOrbit(final String line, final ParseInfo pi) {
1592                 pi.beidouCNav.setI0(parseBroadcastDouble1(line,       pi.initialSpaces, Unit.RADIAN));
1593                 pi.beidouCNav.setCrc(parseBroadcastDouble2(line,      pi.initialSpaces, Unit.METRE));
1594                 pi.beidouCNav.setPa(parseBroadcastDouble3(line,       pi.initialSpaces, Unit.RADIAN));
1595                 pi.beidouCNav.setOmegaDot(parseBroadcastDouble4(line, pi.initialSpaces, RAD_PER_S));
1596             }
1597 
1598             /** {@inheritDoc} */
1599             @Override
1600             public void parseFifthBroadcastOrbit(final String line, final ParseInfo pi) {
1601                 pi.beidouCNav.setIDot(parseBroadcastDouble1(line, pi.initialSpaces, RAD_PER_S));
1602                 pi.beidouCNav.setDeltaN0Dot(parseBroadcastDouble2(line, pi.initialSpaces, RAD_PER_S2));
1603                 switch (parseBroadcastInt3(line, pi.initialSpaces)) {
1604                     case 0 :
1605                         pi.beidouCNav.setSatelliteType(BeidouSatelliteType.RESERVED);
1606                         break;
1607                     case 1 :
1608                         pi.beidouCNav.setSatelliteType(BeidouSatelliteType.GEO);
1609                         break;
1610                     case 2 :
1611                         pi.beidouCNav.setSatelliteType(BeidouSatelliteType.IGSO);
1612                         break;
1613                     case 3 :
1614                         pi.beidouCNav.setSatelliteType(BeidouSatelliteType.MEO);
1615                         break;
1616                     default:
1617                         throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
1618                                                   pi.lineNumber, pi.name, line);
1619                 }
1620                 pi.beidouCNav.setTime(parseBroadcastDouble4(line,     pi.initialSpaces, Unit.SECOND));
1621             }
1622 
1623             /** {@inheritDoc} */
1624             @Override
1625             public void parseSixthBroadcastOrbit(final String line, final ParseInfo pi) {
1626                 pi.beidouCNav.setSisaiOe(parseBroadcastInt1(line, pi.initialSpaces));
1627                 pi.beidouCNav.setSisaiOcb(parseBroadcastInt2(line, pi.initialSpaces));
1628                 pi.beidouCNav.setSisaiOc1(parseBroadcastInt3(line, pi.initialSpaces));
1629                 pi.beidouCNav.setSisaiOc2(parseBroadcastInt4(line, pi.initialSpaces));
1630             }
1631 
1632             /** {@inheritDoc} */
1633             @Override
1634             public void parseSeventhBroadcastOrbit(final String line, final ParseInfo pi) {
1635                 if (pi.beidouCNav.getSignal() == Frequency.B1C) {
1636                     pi.beidouCNav.setIscB1CD(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
1637                     // field 2 is spare
1638                     pi.beidouCNav.setTgdB1Cp(parseBroadcastDouble3(line, pi.initialSpaces, Unit.SECOND));
1639                     pi.beidouCNav.setTgdB2ap(parseBroadcastDouble4(line, pi.initialSpaces, Unit.SECOND));
1640                 } else if (pi.beidouCNav.getSignal() == Frequency.B2A) {
1641                     // field 1 is spare
1642                     pi.beidouCNav.setIscB2AD(parseBroadcastDouble2(line, pi.initialSpaces, Unit.SECOND));
1643                     pi.beidouCNav.setTgdB1Cp(parseBroadcastDouble3(line, pi.initialSpaces, Unit.SECOND));
1644                     pi.beidouCNav.setTgdB2ap(parseBroadcastDouble4(line, pi.initialSpaces, Unit.SECOND));
1645                 } else {
1646                     parseSismaiHealthIntegrity(line, pi);
1647                 }
1648             }
1649 
1650             /** {@inheritDoc} */
1651             @Override
1652             public void parseEighthBroadcastOrbit(final String line, final ParseInfo pi) {
1653                 if (pi.beidouCNav.getSignal() == Frequency.B2B) {
1654                     pi.beidouCNav.setTransmissionTime(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
1655                     pi.closePendingMessage();
1656                 } else {
1657                     parseSismaiHealthIntegrity(line, pi);
1658                 }
1659             }
1660 
1661             /** {@inheritDoc} */
1662             @Override
1663             public void parseNinthBroadcastOrbit(final String line, final ParseInfo pi) {
1664                 pi.beidouCNav.setTransmissionTime(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
1665                 // field 2 is spare
1666                 // field 3 is spare
1667                 pi.beidouCNav.setIODE(parseBroadcastInt4(line, pi.initialSpaces));
1668                 pi.closePendingMessage();
1669             }
1670 
1671             /** {@inheritDoc} */
1672             @Override
1673             public void closeMessage(final ParseInfo pi) {
1674                 pi.file.addBeidouCivilianNavigationMessage(pi.beidouCNav);
1675                 pi.beidouCNav = null;
1676             }
1677 
1678             /**
1679              * Parse the SISMAI/Health/integrity line.
1680              * @param line line to read
1681              * @param pi holder for transient data
1682              */
1683             private void parseSismaiHealthIntegrity(final String line, final ParseInfo pi) {
1684                 pi.beidouCNav.setSismai(parseBroadcastInt1(line, pi.initialSpaces));
1685                 pi.beidouCNav.setHealth(parseBroadcastInt2(line, pi.initialSpaces));
1686                 pi.beidouCNav.setIntegrityFlags(parseBroadcastInt3(line, pi.initialSpaces));
1687                 pi.beidouCNav.setIODC(parseBroadcastInt4(line, pi.initialSpaces));
1688             }
1689 
1690         },
1691 
1692         /** SBAS. */
1693         SBAS() {
1694 
1695             /** {@inheritDoc} */
1696             @Override
1697             public void parseSvEpochSvClockLine(final String line, final ParseInfo pi) {
1698 
1699                 // parse PRN
1700                 pi.sbasNav.setPRN(RinexUtils.parseInt(line, 1, 2));
1701 
1702                 // Time scale (UTC for Rinex 3.01 and GPS for other RINEX versions)
1703                 final int       version100 = (int) FastMath.rint(pi.file.getHeader().getFormatVersion() * 100);
1704                 final TimeScale timeScale  = (version100 == 301) ? pi.timeScales.getUTC() : pi.timeScales.getGPS();
1705 
1706                 pi.sbasNav.setEpochToc(parsePrnSvEpochClock(line, timeScale));
1707                 pi.sbasNav.setAGf0(parseBroadcastDouble2(line, pi.initialSpaces, Unit.SECOND));
1708                 pi.sbasNav.setAGf1(parseBroadcastDouble3(line, pi.initialSpaces, S_PER_S));
1709                 pi.sbasNav.setTime(parseBroadcastDouble4(line, pi.initialSpaces, Unit.SECOND));
1710 
1711                 // Set the ephemeris epoch (same as time of clock epoch)
1712                 pi.sbasNav.setDate(pi.sbasNav.getEpochToc());
1713 
1714             }
1715 
1716             /** {@inheritDoc} */
1717             @Override
1718             public void parseFirstBroadcastOrbit(final String line, final ParseInfo pi) {
1719                 pi.sbasNav.setX(parseBroadcastDouble1(line, pi.initialSpaces, KM));
1720                 pi.sbasNav.setXDot(parseBroadcastDouble2(line,    pi.initialSpaces, KM_PER_S));
1721                 pi.sbasNav.setXDotDot(parseBroadcastDouble3(line, pi.initialSpaces, KM_PER_S2));
1722                 pi.sbasNav.setHealth(parseBroadcastDouble4(line,  pi.initialSpaces, Unit.NONE));
1723             }
1724 
1725             /** {@inheritDoc} */
1726             @Override
1727             public void parseSecondBroadcastOrbit(final String line, final ParseInfo pi) {
1728                 pi.sbasNav.setY(parseBroadcastDouble1(line, pi.initialSpaces, KM));
1729                 pi.sbasNav.setYDot(parseBroadcastDouble2(line,    pi.initialSpaces, KM_PER_S));
1730                 pi.sbasNav.setYDotDot(parseBroadcastDouble3(line, pi.initialSpaces, KM_PER_S2));
1731                 pi.sbasNav.setURA(parseBroadcastDouble4(line,     pi.initialSpaces, Unit.NONE));
1732             }
1733 
1734             /** {@inheritDoc} */
1735             @Override
1736             public void parseThirdBroadcastOrbit(final String line, final ParseInfo pi) {
1737                 pi.sbasNav.setZ(parseBroadcastDouble1(line, pi.initialSpaces, KM));
1738                 pi.sbasNav.setZDot(parseBroadcastDouble2(line,    pi.initialSpaces, KM_PER_S));
1739                 pi.sbasNav.setZDotDot(parseBroadcastDouble3(line, pi.initialSpaces, KM_PER_S2));
1740                 pi.sbasNav.setIODN(parseBroadcastDouble4(line,    pi.initialSpaces, Unit.NONE));
1741                 pi.closePendingMessage();
1742             }
1743 
1744             /** {@inheritDoc} */
1745             @Override
1746             public void closeMessage(final ParseInfo pi) {
1747                 pi.file.addSBASNavigationMessage(pi.sbasNav);
1748                 pi.sbasNav = null;
1749             }
1750 
1751         },
1752 
1753         /** IRNSS. */
1754         IRNSS() {
1755 
1756             /** {@inheritDoc} */
1757             @Override
1758             public void parseSvEpochSvClockLine(final String line, final ParseInfo pi) {
1759                 parseSvEpochSvClockLine(line, pi.timeScales.getIRNSS(), pi.irnssNav);
1760             }
1761 
1762             /** {@inheritDoc} */
1763             @Override
1764             public void parseFirstBroadcastOrbit(final String line, final ParseInfo pi) {
1765                 pi.irnssNav.setIODEC(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
1766                 pi.irnssNav.setCrs(parseBroadcastDouble2(line,     pi.initialSpaces, Unit.METRE));
1767                 pi.irnssNav.setDeltaN(parseBroadcastDouble3(line,  pi.initialSpaces, RAD_PER_S));
1768                 pi.irnssNav.setM0(parseBroadcastDouble4(line,      pi.initialSpaces, Unit.RADIAN));
1769             }
1770 
1771             /** {@inheritDoc} */
1772             @Override
1773             public void parseSecondBroadcastOrbit(final String line, final ParseInfo pi) {
1774                 pi.irnssNav.setCuc(parseBroadcastDouble1(line, pi.initialSpaces, Unit.RADIAN));
1775                 pi.irnssNav.setE(parseBroadcastDouble2(line,     pi.initialSpaces, Unit.NONE));
1776                 pi.irnssNav.setCus(parseBroadcastDouble3(line,   pi.initialSpaces, Unit.RADIAN));
1777                 pi.irnssNav.setSqrtA(parseBroadcastDouble4(line, pi.initialSpaces, SQRT_M));
1778             }
1779 
1780             /** {@inheritDoc} */
1781             @Override
1782             public void parseThirdBroadcastOrbit(final String line, final ParseInfo pi) {
1783                 pi.irnssNav.setTime(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
1784                 pi.irnssNav.setCic(parseBroadcastDouble2(line,    pi.initialSpaces, Unit.RADIAN));
1785                 pi.irnssNav.setOmega0(parseBroadcastDouble3(line, pi.initialSpaces, Unit.RADIAN));
1786                 pi.irnssNav.setCis(parseBroadcastDouble4(line,    pi.initialSpaces, Unit.RADIAN));
1787             }
1788 
1789             /** {@inheritDoc} */
1790             @Override
1791             public void parseFourthBroadcastOrbit(final String line, final ParseInfo pi) {
1792                 pi.irnssNav.setI0(parseBroadcastDouble1(line, pi.initialSpaces, Unit.RADIAN));
1793                 pi.irnssNav.setCrc(parseBroadcastDouble2(line,      pi.initialSpaces, Unit.METRE));
1794                 pi.irnssNav.setPa(parseBroadcastDouble3(line,       pi.initialSpaces, Unit.RADIAN));
1795                 pi.irnssNav.setOmegaDot(parseBroadcastDouble4(line, pi.initialSpaces, RAD_PER_S));
1796             }
1797 
1798             /** {@inheritDoc} */
1799             @Override
1800             public void parseFifthBroadcastOrbit(final String line, final ParseInfo pi) {
1801                 // iDot
1802                 pi.irnssNav.setIDot(parseBroadcastDouble1(line, pi.initialSpaces, RAD_PER_S));
1803                 // IRNSS week (to go with Toe)
1804                 pi.irnssNav.setWeek(parseBroadcastInt3(line, pi.initialSpaces));
1805                 pi.irnssNav.setDate(new GNSSDate(pi.irnssNav.getWeek(),
1806                                                  pi.irnssNav.getTime(),
1807                                                  SatelliteSystem.GPS, // in Rinex files, week number is aligned to GPS week!
1808                                                  pi.timeScales).getDate());
1809             }
1810 
1811             /** {@inheritDoc} */
1812             @Override
1813             public void parseSixthBroadcastOrbit(final String line, final ParseInfo pi) {
1814                 pi.irnssNav.setURA(parseBroadcastDouble1(line,      pi.initialSpaces, Unit.METRE));
1815                 pi.irnssNav.setSvHealth(parseBroadcastDouble2(line, pi.initialSpaces, Unit.NONE));
1816                 pi.irnssNav.setTGD(parseBroadcastDouble3(line,      pi.initialSpaces, Unit.SECOND));
1817             }
1818 
1819             /** {@inheritDoc} */
1820             @Override
1821             public void parseSeventhBroadcastOrbit(final String line, final ParseInfo pi) {
1822                 pi.irnssNav.setTransmissionTime(parseBroadcastDouble1(line, pi.initialSpaces, Unit.SECOND));
1823                 pi.closePendingMessage();
1824             }
1825 
1826             /** {@inheritDoc} */
1827             @Override
1828             public void closeMessage(final ParseInfo pi) {
1829                 pi.file.addIRNSSNavigationMessage(pi.irnssNav);
1830                 pi.irnssNav = null;
1831             }
1832 
1833         };
1834 
1835         /** Get the parse for navigation message.
1836          * @param system satellite system
1837          * @param type message type (null for Rinex 3.x)
1838          * @param parseInfo container for transient data
1839          * @param line line being parsed
1840          * @return the satellite system line parser
1841          */
1842         private static SatelliteSystemLineParser getParser(final SatelliteSystem system, final String type,
1843                                                            final ParseInfo parseInfo, final String line) {
1844             switch (system) {
1845                 case GPS :
1846                     if (type == null || type.equals(LegacyNavigationMessage.LNAV)) {
1847                         parseInfo.gpsLNav = new GPSLegacyNavigationMessage();
1848                         return GPS_LNAV;
1849                     } else if (type.equals(CivilianNavigationMessage.CNAV)) {
1850                         parseInfo.gpsCNav = new GPSCivilianNavigationMessage(false);
1851                         return GPS_CNAV;
1852                     } else if (type.equals(CivilianNavigationMessage.CNV2)) {
1853                         parseInfo.gpsCNav = new GPSCivilianNavigationMessage(true);
1854                         return GPS_CNAV;
1855                     }
1856                     break;
1857                 case GALILEO :
1858                     if (type == null || type.equals("INAV") || type.equals("FNAV")) {
1859                         parseInfo.galileoNav = new GalileoNavigationMessage();
1860                         return GALILEO;
1861                     }
1862                     break;
1863                 case GLONASS :
1864                     if (type == null || type.equals("FDMA")) {
1865                         parseInfo.glonassNav = new GLONASSNavigationMessage();
1866                         return GLONASS;
1867                     }
1868                     break;
1869                 case QZSS :
1870                     if (type == null || type.equals(LegacyNavigationMessage.LNAV)) {
1871                         parseInfo.qzssLNav = new QZSSLegacyNavigationMessage();
1872                         return QZSS_LNAV;
1873                     } else if (type.equals(CivilianNavigationMessage.CNAV)) {
1874                         parseInfo.qzssCNav = new QZSSCivilianNavigationMessage(false);
1875                         return QZSS_CNAV;
1876                     } else if (type.equals(CivilianNavigationMessage.CNV2)) {
1877                         parseInfo.qzssCNav = new QZSSCivilianNavigationMessage(true);
1878                         return QZSS_CNAV;
1879                     }
1880                     break;
1881                 case BEIDOU :
1882                     if (type == null ||
1883                         type.equals(BeidouLegacyNavigationMessage.D1) ||
1884                         type.equals(BeidouLegacyNavigationMessage.D2)) {
1885                         parseInfo.beidouLNav = new BeidouLegacyNavigationMessage();
1886                         return BEIDOU_D1_D2;
1887                     } else if (type.equals(BeidouCivilianNavigationMessage.CNV1)) {
1888                         parseInfo.beidouCNav = new BeidouCivilianNavigationMessage(Frequency.B1C);
1889                         return BEIDOU_CNV_123;
1890                     } else if (type.equals(BeidouCivilianNavigationMessage.CNV2)) {
1891                         parseInfo.beidouCNav = new BeidouCivilianNavigationMessage(Frequency.B2A);
1892                         return BEIDOU_CNV_123;
1893                     } else if (type.equals(BeidouCivilianNavigationMessage.CNV3)) {
1894                         parseInfo.beidouCNav = new BeidouCivilianNavigationMessage(Frequency.B2B);
1895                         return BEIDOU_CNV_123;
1896                     }
1897                     break;
1898                 case IRNSS :
1899                     if (type == null || type.equals("LNAV")) {
1900                         parseInfo.irnssNav = new IRNSSNavigationMessage();
1901                         return IRNSS;
1902                     }
1903                     break;
1904                 case SBAS :
1905                     if (type == null || type.equals("SBAS")) {
1906                         parseInfo.sbasNav = new SBASNavigationMessage();
1907                         return SBAS;
1908                     }
1909                     break;
1910                 default:
1911                     // do nothing, handle error after the switch
1912             }
1913             throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
1914                                       parseInfo.lineNumber, parseInfo.name, line);
1915         }
1916 
1917         /**
1918          * Parse the SV/Epoch/Sv clock of the navigation message.
1919          * @param line line to read
1920          * @param timeScale time scale to use
1921          * @param message navigation message
1922          */
1923         protected void parseSvEpochSvClockLineRinex2(final String line, final TimeScale timeScale,
1924                                                      final AbstractNavigationMessage message) {
1925             // PRN
1926             message.setPRN(RinexUtils.parseInt(line, 0, 2));
1927 
1928             // Toc
1929             final int    year  = RinexUtils.convert2DigitsYear(RinexUtils.parseInt(line,  2, 3));
1930             final int    month = RinexUtils.parseInt(line,  5, 3);
1931             final int    day   = RinexUtils.parseInt(line,  8, 3);
1932             final int    hours = RinexUtils.parseInt(line, 11, 3);
1933             final int    min   = RinexUtils.parseInt(line, 14, 3);
1934             final double sec   = RinexUtils.parseDouble(line, 17, 5);
1935             message.setEpochToc(new AbsoluteDate( year, month, day, hours, min, sec, timeScale));
1936 
1937             // clock
1938             message.setAf0(RinexUtils.parseDouble(line, 22, 19));
1939             message.setAf1(RinexUtils.parseDouble(line, 41, 19));
1940             message.setAf2(RinexUtils.parseDouble(line, 60, 19));
1941 
1942         }
1943 
1944         /**
1945          * Parse the SV/Epoch/Sv clock of the navigation message.
1946          * @param line line to read
1947          * @param timeScale time scale to use
1948          * @param message navigation message
1949          */
1950         protected void parseSvEpochSvClockLine(final String line, final TimeScale timeScale,
1951                                                final AbstractNavigationMessage message) {
1952             // PRN
1953             message.setPRN(RinexUtils.parseInt(line, 1, 2));
1954 
1955             // Toc
1956             message.setEpochToc(parsePrnSvEpochClock(line, timeScale));
1957 
1958             // clock
1959             message.setAf0(RinexUtils.parseDouble(line, 23, 19));
1960             message.setAf1(RinexUtils.parseDouble(line, 42, 19));
1961             message.setAf2(RinexUtils.parseDouble(line, 61, 19));
1962 
1963         }
1964 
1965         /** Parse epoch field of a Sv/epoch/clock line.
1966          * @param line line to parse
1967          * @param timeScale time scale to use
1968          * @return parsed field
1969          */
1970         protected AbsoluteDate parsePrnSvEpochClock(final String line, final TimeScale timeScale) {
1971             final int year  = RinexUtils.parseInt(line, 4, 4);
1972             final int month = RinexUtils.parseInt(line, 9, 2);
1973             final int day   = RinexUtils.parseInt(line, 12, 2);
1974             final int hours = RinexUtils.parseInt(line, 15, 2);
1975             final int min   = RinexUtils.parseInt(line, 18, 2);
1976             final int sec   = RinexUtils.parseInt(line, 21, 2);
1977             return new AbsoluteDate(year, month, day, hours, min, sec, timeScale);
1978         }
1979 
1980         /** Parse double field 1 of a broadcast orbit line.
1981          * @param line line to parse
1982          * @param initialSpaces number of initial spaces in the line
1983          * @param unit unit to used for parsing the field
1984          * @return parsed field
1985          */
1986         protected double parseBroadcastDouble1(final String line, final int initialSpaces, final Unit unit) {
1987             return unit.toSI(RinexUtils.parseDouble(line, initialSpaces, 19));
1988         }
1989 
1990         /** Parse integer field 1 of a broadcast orbit line.
1991          * @param line line to parse
1992          * @param initialSpaces number of initial spaces in the line
1993          * @return parsed field
1994          */
1995         protected int parseBroadcastInt1(final String line, final int initialSpaces) {
1996             return (int) FastMath.rint(RinexUtils.parseDouble(line, initialSpaces, 19));
1997         }
1998 
1999         /** Parse double field 2 of a broadcast orbit line.
2000          * @param line line to parse
2001          * @param initialSpaces number of initial spaces in the line
2002          * @param unit unit to used for parsing the field
2003          * @return parsed field
2004          */
2005         protected double parseBroadcastDouble2(final String line, final int initialSpaces, final Unit unit) {
2006             return unit.toSI(RinexUtils.parseDouble(line, initialSpaces + 19, 19));
2007         }
2008 
2009         /** Parse integer field 2 of a broadcast orbit line.
2010          * @param line line to parse
2011          * @param initialSpaces number of initial spaces in the line
2012          * @return parsed field
2013          */
2014         protected int parseBroadcastInt2(final String line, final int initialSpaces) {
2015             return (int) FastMath.rint(RinexUtils.parseDouble(line, initialSpaces + 19, 19));
2016         }
2017 
2018         /** Parse double field 3 of a broadcast orbit line.
2019          * @param line line to parse
2020          * @param initialSpaces number of initial spaces in the line
2021          * @param unit unit to used for parsing the field
2022          * @return parsed field
2023          */
2024         protected double parseBroadcastDouble3(final String line, final int initialSpaces, final Unit unit) {
2025             return unit.toSI(RinexUtils.parseDouble(line, initialSpaces + 38, 19));
2026         }
2027 
2028         /** Parse integer field 3 of a broadcast orbit line.
2029          * @param line line to parse
2030          * @param initialSpaces number of initial spaces in the line
2031          * @return parsed field
2032          */
2033         protected int parseBroadcastInt3(final String line, final int initialSpaces) {
2034             return (int) FastMath.rint(RinexUtils.parseDouble(line, initialSpaces + 38, 19));
2035         }
2036 
2037         /** Parse double field 4 of a broadcast orbit line.
2038          * @param line line to parse
2039          * @param initialSpaces number of initial spaces in the line
2040          * @param unit unit to used for parsing the field
2041          * @return parsed field
2042          */
2043         protected double parseBroadcastDouble4(final String line, final int initialSpaces, final Unit unit) {
2044             return unit.toSI(RinexUtils.parseDouble(line, initialSpaces + 57, 19));
2045         }
2046 
2047         /** Parse integer field 4 of a broadcast orbit line.
2048          * @param line line to parse
2049          * @param initialSpaces number of initial spaces in the line
2050          * @return parsed field
2051          */
2052         protected int parseBroadcastInt4(final String line, final int initialSpaces) {
2053             return (int) FastMath.rint(RinexUtils.parseDouble(line, initialSpaces + 57, 19));
2054         }
2055 
2056         /**
2057          * Parse the SV/Epoch/Sv clock of the navigation message.
2058          * @param line line to read
2059          * @param pi holder for transient data
2060          */
2061         public abstract void parseSvEpochSvClockLine(String line, ParseInfo pi);
2062 
2063         /**
2064          * Parse the "BROADCASTORBIT - 1" line.
2065          * @param line line to read
2066          * @param pi holder for transient data
2067          */
2068         public abstract void parseFirstBroadcastOrbit(String line, ParseInfo pi);
2069 
2070         /**
2071          * Parse the "BROADCASTORBIT - 2" line.
2072          * @param line line to read
2073          * @param pi holder for transient data
2074          */
2075         public abstract void parseSecondBroadcastOrbit(String line, ParseInfo pi);
2076 
2077         /**
2078          * Parse the "BROADCASTORBIT - 3" line.
2079          * @param line line to read
2080          * @param pi holder for transient data
2081          */
2082         public abstract void parseThirdBroadcastOrbit(String line, ParseInfo pi);
2083 
2084         /**
2085          * Parse the "BROADCASTORBIT - 4" line.
2086          * @param line line to read
2087          * @param pi holder for transient data
2088          */
2089         public void parseFourthBroadcastOrbit(final String line, final ParseInfo pi) {
2090             // this should never be called (except by some tests that use reflection)
2091             throw new OrekitInternalError(null);
2092         }
2093 
2094         /**
2095          * Parse the "BROADCASTORBIT - 5" line.
2096          * @param line line to read
2097          * @param pi holder for transient data
2098          */
2099         public void parseFifthBroadcastOrbit(final String line, final ParseInfo pi) {
2100             // this should never be called (except by some tests that use reflection)
2101             throw new OrekitInternalError(null);
2102         }
2103 
2104         /**
2105          * Parse the "BROADCASTORBIT - 6" line.
2106          * @param line line to read
2107          * @param pi holder for transient data
2108          */
2109         public void parseSixthBroadcastOrbit(final String line, final ParseInfo pi) {
2110             // this should never be called (except by some tests that use reflection)
2111             throw new OrekitInternalError(null);
2112         }
2113 
2114         /**
2115          * Parse the "BROADCASTORBIT - 7" line.
2116          * @param line line to read
2117          * @param pi holder for transient data
2118          */
2119         public void parseSeventhBroadcastOrbit(final String line, final ParseInfo pi) {
2120             // this should never be called (except by some tests that use reflection)
2121             throw new OrekitInternalError(null);
2122         }
2123 
2124         /**
2125          * Parse the "BROADCASTORBIT - 8" line.
2126          * @param line line to read
2127          * @param pi holder for transient data
2128          */
2129         public void parseEighthBroadcastOrbit(final String line, final ParseInfo pi) {
2130             // this should never be called (except by some tests that use reflection)
2131             throw new OrekitInternalError(null);
2132         }
2133 
2134         /**
2135          * Parse the "BROADCASTORBIT - 9" line.
2136          * @param line line to read
2137          * @param pi holder for transient data
2138          */
2139         public void parseNinthBroadcastOrbit(final String line, final ParseInfo pi) {
2140             // this should never be called (except by some tests that use reflection)
2141             throw new OrekitInternalError(null);
2142         }
2143 
2144         /**
2145          * Close a message as last line was parsed.
2146          * @param pi holder for transient data
2147          */
2148         public abstract void closeMessage(ParseInfo pi);
2149 
2150         /**
2151          * Calculates the floating-point remainder of a / b.
2152          * <p>
2153          * fmod = a - x * b
2154          * where x = (int) a / b
2155          * </p>
2156          * @param a numerator
2157          * @param b denominator
2158          * @return the floating-point remainder of a / b
2159          */
2160         private static double fmod(final double a, final double b) {
2161             final double x = (int) (a / b);
2162             return a - x * b;
2163         }
2164 
2165     }
2166 
2167     /** Parsing method. */
2168     @FunctionalInterface
2169     private interface ParsingMethod {
2170         /** Parse a line.
2171          * @param line line to parse
2172          * @param parseInfo holder for transient data
2173          */
2174         void parse(String line, ParseInfo parseInfo);
2175     }
2176 
2177 }