1   /* Copyright 2002-2021 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.gnss.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.HashMap;
24  import java.util.InputMismatchException;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Optional;
28  import java.util.regex.Pattern;
29  import java.util.stream.Stream;
30  
31  import org.hipparchus.util.FastMath;
32  import org.orekit.annotation.DefaultDataContext;
33  import org.orekit.data.DataContext;
34  import org.orekit.data.DataSource;
35  import org.orekit.errors.OrekitException;
36  import org.orekit.errors.OrekitMessages;
37  import org.orekit.gnss.SatelliteSystem;
38  import org.orekit.gnss.TimeSystem;
39  import org.orekit.gnss.navigation.RinexNavigation.TimeSystemCorrection;
40  import org.orekit.propagation.analytical.gnss.data.BeidouNavigationMessage;
41  import org.orekit.propagation.analytical.gnss.data.GLONASSNavigationMessage;
42  import org.orekit.propagation.analytical.gnss.data.GPSNavigationMessage;
43  import org.orekit.propagation.analytical.gnss.data.GalileoNavigationMessage;
44  import org.orekit.propagation.analytical.gnss.data.IRNSSNavigationMessage;
45  import org.orekit.propagation.analytical.gnss.data.QZSSNavigationMessage;
46  import org.orekit.propagation.analytical.gnss.data.SBASNavigationMessage;
47  import org.orekit.time.AbsoluteDate;
48  import org.orekit.time.DateComponents;
49  import org.orekit.time.GNSSDate;
50  import org.orekit.time.TimeComponents;
51  import org.orekit.time.TimeScale;
52  import org.orekit.time.TimeScales;
53  import org.orekit.utils.Constants;
54  
55  /**
56   * Parser for RINEX navigation messages files.
57   * <p>
58   * This parser handles RINEX version from 3.01 to 3.05. It is not adapted for RINEX 2.10 and 2.11 versions.
59   * </p>
60   * @see <a href="https://files.igs.org/pub/data/format/rinex301.pdf"> 3.01 navigation messages file format</a>
61   * @see <a href="https://files.igs.org/pub/data/format/rinex302.pdf"> 3.02 navigation messages file format</a>
62   * @see <a href="https://files.igs.org/pub/data/format/rinex303.pdf"> 3.03 navigation messages file format</a>
63   * @see <a href="https://files.igs.org/pub/data/format/rinex304.pdf"> 3.04 navigation messages file format</a>
64   * @see <a href="https://files.igs.org/pub/data/format/rinex305.pdf"> 3.05 navigation messages file format</a>
65   *
66   * @author Bryan Cazabonne
67   * @since 11.0
68   *
69   */
70  public class RinexNavigationParser {
71  
72      /** Handled clock file format versions. */
73      private static final List<Double> HANDLED_VERSIONS = Arrays.asList(3.01, 3.02, 3.03, 3.04, 3.05);
74  
75      /** File Type. */
76      private static final String FILE_TYPE = "N";
77  
78      /** Seconds to milliseconds converter. */
79      private static final Double SEC_TO_MILLI = 1000.0;
80  
81      /** Kilometer to meters converter. */
82      private static final Double KM_TO_M = 1000.0;
83  
84      /** Set of time scales. */
85      private final TimeScales timeScales;
86  
87      /**
88       * Constructor.
89       * <p>This constructor uses the {@link DataContext#getDefault() default data context}.</p>
90       * @see #RinexNavigationParser(TimeScales)
91       *
92       */
93      @DefaultDataContext
94      public RinexNavigationParser() {
95          this(DataContext.getDefault().getTimeScales());
96      }
97  
98      /**
99       * Constructor.
100      * @param timeScales the set of time scales used for parsing dates.
101      */
102     public RinexNavigationParser(final TimeScales timeScales) {
103         this.timeScales = timeScales;
104     }
105 
106     /**
107      * Parse RINEX navigation messages.
108      * @param source source providing the data to parse
109      * @return a parsed  RINEX navigation messages file
110      * @throws IOException if {@code reader} throws one
111      */
112     public RinexNavigation parse(final DataSource source) throws IOException {
113 
114         // initialize internal data structures
115         final ParseInfo pi = new ParseInfo();
116 
117         int lineNumber = 0;
118         Stream<LineParser> candidateParsers = Stream.of(LineParser.HEADER_VERSION);
119         try (Reader reader = source.getOpener().openReaderOnce();
120              BufferedReader br = new BufferedReader(reader)) {
121             for (String line = br.readLine(); line != null; line = br.readLine()) {
122                 ++lineNumber;
123                 final String l = line;
124                 final Optional<LineParser> selected = candidateParsers.filter(p -> p.canHandle(l)).findFirst();
125                 if (selected.isPresent()) {
126                     try {
127                         selected.get().parse(line, pi);
128                     } catch (StringIndexOutOfBoundsException | NumberFormatException | InputMismatchException e) {
129                         throw new OrekitException(e,
130                                                   OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
131                                                   lineNumber, source.getName(), line);
132                     }
133                     candidateParsers = selected.get().allowedNext();
134                 } else {
135                     throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
136                                               lineNumber, source.getName(), line);
137                 }
138             }
139         }
140 
141         return pi.file;
142 
143     }
144 
145     /**
146      * Parse a double value.
147      * @param line line to parse
148      * @param startIndex start index
149      * @param size size of the value
150      * @return the parsed value
151      */
152     private static double parseDouble(final String line, final int startIndex, final int size) {
153         return Double.parseDouble(line.substring(startIndex, startIndex + size).replace('D', 'E').trim());
154     }
155 
156     /**
157      * Parse an integer value.
158      * @param line line to parse
159      * @param startIndex start index
160      * @param size size of the value
161      * @return the parsed value
162      */
163     private static int parseInt(final String line, final int startIndex, final int size) {
164         return Integer.parseInt(line.substring(startIndex, startIndex + size).trim());
165     }
166 
167     /**
168      * Parse a string value.
169      * @param line line to parse
170      * @param startIndex start index
171      * @param size size of the value
172      * @return the parsed value
173      */
174     private static String parseString(final String line, final int startIndex, final int size) {
175         return line.substring(startIndex, startIndex + size).trim();
176     }
177 
178     /** Transient data used for parsing a RINEX navigation messages file. */
179     private class ParseInfo {
180 
181         /** Set of time scales for parsing dates. */
182         private final TimeScales timeScales;
183 
184         /** The corresponding navigation messages file object. */
185         private RinexNavigation file;
186 
187         /** The version of the navigation file. */
188         private double version;
189 
190         /** Flag indicating the distinction between "alpha" and "beta" ionospheric coefficients. */
191         private boolean isIonosphereAlphaInitialized;
192 
193         /** Satellite system line parser. */
194         private SatelliteSystemLineParser systemLineParser;
195 
196         /** Current line number of the navigation message. */
197         private int lineNumber;
198 
199         /** Container for GPS navigation message. */
200         private GPSNavigationMessage gpsNav;
201 
202         /** Container for Galileo navigation message. */
203         private GalileoNavigationMessage galileoNav;
204 
205         /** Container for Beidou navigation message. */
206         private BeidouNavigationMessage beidouNav;
207 
208         /** Container for QZSS navigation message. */
209         private QZSSNavigationMessage qzssNav;
210 
211         /** Container for IRNSS navigation message. */
212         private IRNSSNavigationMessage irnssNav;
213 
214         /** Container for GLONASS navigation message. */
215         private GLONASSNavigationMessage glonassNav;
216 
217         /** Container for SBAS navigation message. */
218         private SBASNavigationMessage sbasNav;
219 
220         /** Constructor, build the ParseInfo object. */
221         ParseInfo() {
222             // Initialize default values for fields
223             this.timeScales                   = RinexNavigationParser.this.timeScales;
224             this.version                      = 1.0;
225             this.isIonosphereAlphaInitialized = false;
226             this.file                         = new RinexNavigation();
227             this.systemLineParser             = SatelliteSystemLineParser.GPS;
228             this.lineNumber                   = 0;
229             this.gpsNav                       = new GPSNavigationMessage();
230             this.galileoNav                   = new GalileoNavigationMessage();
231             this.beidouNav                    = new BeidouNavigationMessage();
232             this.qzssNav                      = new QZSSNavigationMessage();
233             this.irnssNav                     = new IRNSSNavigationMessage();
234             this.glonassNav                   = new GLONASSNavigationMessage();
235             this.sbasNav                      = new SBASNavigationMessage();
236         }
237 
238     }
239 
240     /** Parsers for specific lines. */
241     private enum LineParser {
242 
243         /** Parser for version, file type and satellite system. */
244         HEADER_VERSION("^.+RINEX VERSION / TYPE( )*$") {
245 
246             /** {@inheritDoc} */
247             @Override
248             public void parse(final String line, final ParseInfo pi) {
249 
250                 // Rinex version
251                 pi.version = parseDouble(line, 0, 9);
252 
253                 // Throw exception if format version is not handled
254                 if (!HANDLED_VERSIONS.contains(pi.version)) {
255                     throw new OrekitException(OrekitMessages.NAVIGATION_FILE_UNSUPPORTED_VERSION, pi.version);
256                 }
257                 pi.file.setFormatVersion(pi.version);
258 
259                 // File type
260                 pi.file.setFileType(FILE_TYPE);
261 
262                 // Satellite system
263                 final SatelliteSystem system = SatelliteSystem.parseSatelliteSystem(parseString(line, 40, 1));
264                 pi.file.setSatelliteSystem(system);
265 
266             }
267 
268             /** {@inheritDoc} */
269             @Override
270             public Stream<LineParser> allowedNext() {
271                 return Stream.of(HEADER_PROGRAM);
272             }
273 
274         },
275 
276         /** Parser for generating program and emiting agency. */
277         HEADER_PROGRAM("^.+PGM / RUN BY / DATE( )*$") {
278 
279             /** {@inheritDoc} */
280             @Override
281             public void parse(final String line, final ParseInfo pi) {
282 
283                 // Name of the generating program
284                 final String programName = parseString(line, 0, 20);
285                 pi.file.setProgramName(programName);
286 
287                 // Name of the emiting agency
288                 final String agencyName = parseString(line, 20, 20);
289                 pi.file.setAgencyName(agencyName);
290 
291                 // Date and time of file creation
292                 final String date     = parseString(line, 40, 8);
293                 final String time     = parseString(line, 49, 6);
294                 final String timeZone = parseString(line, 56, 4);
295                 pi.file.setCreationDateString(date);
296                 pi.file.setCreationTimeString(time);
297                 pi.file.setCreationTimeZoneString(timeZone);
298 
299                 // Convert date and time to an Orekit absolute date
300                 final DateComponents dateComponents = new DateComponents(parseInt(date, 0, 4),
301                                                                          parseInt(date, 4, 2),
302                                                                          parseInt(date, 6, 2));
303                 final TimeComponents timeComponents = new TimeComponents(parseInt(time, 0, 2),
304                                                                          parseInt(time, 2, 2),
305                                                                          parseInt(time, 4, 2));
306                 pi.file.setCreationDate(new AbsoluteDate(dateComponents,
307                                                          timeComponents,
308                                                          TimeSystem.parseTimeSystem(timeZone).getTimeScale(pi.timeScales)));
309 
310             }
311 
312             /** {@inheritDoc} */
313             @Override
314             public Stream<LineParser> allowedNext() {
315                 return Stream.of(HEADER_COMMENT, HEADER_IONOSPHERIC, HEADER_TIME, HEADER_LEAP_SECONDS, HEADER_END);
316             }
317 
318         },
319 
320         /** Parser for comments. */
321         HEADER_COMMENT("^.+COMMENT( )*$") {
322 
323             /** {@inheritDoc} */
324             @Override
325             public void parse(final String line, final ParseInfo pi) {
326                 pi.file.addComment(parseString(line, 0, 60));
327             }
328 
329             /** {@inheritDoc} */
330             @Override
331             public Stream<LineParser> allowedNext() {
332                 return Stream.of(HEADER_COMMENT, HEADER_IONOSPHERIC, HEADER_TIME, HEADER_LEAP_SECONDS, HEADER_END);
333             }
334 
335         },
336 
337         /** Parser for ionospheric correction parameters. */
338         HEADER_IONOSPHERIC("^.+IONOSPHERIC CORR( )*$") {
339 
340             /** {@inheritDoc} */
341             @Override
342             public void parse(final String line, final ParseInfo pi) {
343 
344                 // Satellite system
345                 final String ionoType = parseString(line, 0, 3);
346                 pi.file.setIonosphericCorrectionType(ionoType);
347 
348                 // Read coefficients
349                 final double[] parameters = new double[4];
350                 parameters[0] = parseDouble(line, 5,  12);
351                 parameters[1] = parseDouble(line, 17, 12);
352                 parameters[2] = parseDouble(line, 29, 12);
353                 parameters[3] = parseDouble(line, 41, 12);
354 
355                 // Verify if we are parsing Galileo ionospheric parameters
356                 if ("GAL".equals(ionoType)) {
357 
358                     // We are parsing Galileo ionospheric parameters
359                     pi.file.setNeQuickAlpha(parameters);
360 
361                 } else {
362                     // We are parsing Klobuchar ionospheric parameters
363 
364                     // Verify if we are parsing "alpha" or "beta" ionospheric parameters
365                     if (pi.isIonosphereAlphaInitialized) {
366 
367                         // Ionospheric "beta" parameters
368                         pi.file.setKlobucharBeta(parameters);
369 
370                     } else {
371 
372                         // Ionospheric "alpha" parameters
373                         pi.file.setKlobucharAlpha(parameters);
374 
375                         // Set the flag to true
376                         pi.isIonosphereAlphaInitialized = true;
377 
378                     }
379 
380                 }
381 
382             }
383 
384             /** {@inheritDoc} */
385             @Override
386             public Stream<LineParser> allowedNext() {
387                 return Stream.of(HEADER_COMMENT, HEADER_IONOSPHERIC, HEADER_TIME, HEADER_LEAP_SECONDS, HEADER_END);
388             }
389 
390         },
391 
392         /** Parser for corrections to transform the system time to UTC or to other time systems. */
393         HEADER_TIME("^.+TIME SYSTEM CORR( )*$") {
394 
395             /** {@inheritDoc} */
396             @Override
397             public void parse(final String line, final ParseInfo pi) {
398 
399                 // Read fields
400                 final String type    = parseString(line, 0,  4);
401                 final double a0      = parseDouble(line, 5,  17);
402                 final double a1      = parseDouble(line, 22, 16);
403                 final int    refTime = parseInt(line, 38, 7);
404                 final int    refWeek = parseInt(line, 46, 5);
405 
406                 // Add to the list
407                 final TimeSystemCorrection tsc = new TimeSystemCorrection(type, a0, a1, refTime, refWeek);
408                 pi.file.addTimeSystemCorrections(tsc);
409 
410             }
411 
412             /** {@inheritDoc} */
413             @Override
414             public Stream<LineParser> allowedNext() {
415                 return Stream.of(HEADER_COMMENT, HEADER_TIME, HEADER_LEAP_SECONDS, HEADER_END);
416             }
417 
418         },
419 
420         /** Parser for leap seconds. */
421         HEADER_LEAP_SECONDS("^.+LEAP SECONDS( )*$") {
422 
423             /** {@inheritDoc} */
424             @Override
425             public void parse(final String line, final ParseInfo pi) {
426                 // Current number of leap seconds
427                 pi.file.setNumberOfLeapSeconds(parseInt(line, 0, 6));
428             }
429 
430             /** {@inheritDoc} */
431             @Override
432             public Stream<LineParser> allowedNext() {
433                 return Stream.of(HEADER_COMMENT, HEADER_IONOSPHERIC, HEADER_TIME, HEADER_END);
434             }
435 
436         },
437 
438         /** Parser for the end of header. */
439         HEADER_END("^.+END OF HEADER( )*$") {
440 
441             /** {@inheritDoc} */
442             @Override
443             public void parse(final String line, final ParseInfo pi) {
444                 // do nothing...
445             }
446 
447             /** {@inheritDoc} */
448             @Override
449             public Stream<LineParser> allowedNext() {
450                 return Stream.of(NAVIGATION_MESSAGE_FIRST);
451             }
452 
453         },
454 
455         /** Parser for navigation message first data line. */
456         NAVIGATION_MESSAGE_FIRST("(^G|^R|^E|^C|^I|^J|^S).+$") {
457 
458             /** {@inheritDoc} */
459             @Override
460             public void parse(final String line, final ParseInfo pi) {
461 
462                 // Set the line number to 0
463                 pi.lineNumber = 0;
464 
465                 // Current satellite system
466                 final String key = parseString(line, 0, 1);
467 
468                 // Initialize parser
469                 pi.systemLineParser = SatelliteSystemLineParser.getSatelliteSystemLineParser(key);
470 
471                 // Read first line
472                 pi.systemLineParser.parseFirstLine(line, pi);
473 
474             }
475 
476             /** {@inheritDoc} */
477             @Override
478             public Stream<LineParser> allowedNext() {
479                 return Stream.of(NAVIGATION_BROADCAST_ORBIT);
480             }
481 
482         },
483 
484         /** Parser for broadcast orbit. */
485         NAVIGATION_BROADCAST_ORBIT("^    .+") {
486 
487             /** {@inheritDoc} */
488             @Override
489             public void parse(final String line, final ParseInfo pi) {
490 
491                 // Increment the line number
492                 pi.lineNumber++;
493 
494                 // Read the corresponding line
495                 if (pi.lineNumber == 1) {
496                     // BROADCAST ORBIT – 1
497                     pi.systemLineParser.parseFirstBroadcastOrbit(line, pi);
498                 } else if (pi.lineNumber == 2) {
499                     // BROADCAST ORBIT – 2
500                     pi.systemLineParser.parseSecondBroadcastOrbit(line, pi);
501                 } else if (pi.lineNumber == 3) {
502                     // BROADCAST ORBIT – 3
503                     pi.systemLineParser.parseThirdBroadcastOrbit(line, pi);
504                 } else if (pi.lineNumber == 4) {
505                     // BROADCAST ORBIT – 4
506                     pi.systemLineParser.parseFourthBroadcastOrbit(line, pi);
507                 } else if (pi.lineNumber == 5) {
508                     // BROADCAST ORBIT – 5
509                     pi.systemLineParser.parseFifthBroadcastOrbit(line, pi);
510                 } else if (pi.lineNumber == 6) {
511                     // BROADCAST ORBIT – 6
512                     pi.systemLineParser.parseSixthBroadcastOrbit(line, pi);
513                 } else {
514                     // BROADCAST ORBIT – 7
515                     pi.systemLineParser.parseSeventhBroadcastOrbit(line, pi);
516                 }
517 
518             }
519 
520             /** {@inheritDoc} */
521             @Override
522             public Stream<LineParser> allowedNext() {
523                 return Stream.of(NAVIGATION_MESSAGE_FIRST, NAVIGATION_BROADCAST_ORBIT);
524             }
525 
526         };
527 
528         /** Pattern for identifying line. */
529         private final Pattern pattern;
530 
531         /** Simple constructor.
532          * @param lineRegexp regular expression for identifying line
533          */
534         LineParser(final String lineRegexp) {
535             pattern = Pattern.compile(lineRegexp);
536         }
537 
538         /** Parse a line.
539          * @param line line to parse
540          * @param pi holder for transient data
541          */
542         public abstract void parse(String line, ParseInfo pi);
543 
544         /** Get the allowed parsers for next line.
545          * @return allowed parsers for next line
546          */
547         public abstract Stream<LineParser> allowedNext();
548 
549         /** Check if parser can handle line.
550          * @param line line to parse
551          * @return true if parser can handle the specified line
552          */
553         public boolean canHandle(final String line) {
554             return pattern.matcher(line).matches();
555         }
556 
557     }
558 
559     /** Parsers for satellite system specific lines. */
560     private enum SatelliteSystemLineParser {
561 
562         /** GPS. */
563         GPS("G") {
564 
565             /** {@inheritDoc} */
566             @Override
567             public void parseFirstLine(final String line, final ParseInfo pi) {
568                 // PRN
569                 pi.gpsNav.setPRN(parseInt(line, 1, 2));
570 
571                 // Toc
572                 final int gpsTocYear  = parseInt(line, 4, 4);
573                 final int gpsTocMonth = parseInt(line, 9, 2);
574                 final int gpsTocDay   = parseInt(line, 12, 2);
575                 final int gpsTocHours = parseInt(line, 15, 2);
576                 final int gpsTocMin   = parseInt(line, 18, 2);
577                 final int gpsTocSec   = parseInt(line, 21, 2);
578                 pi.gpsNav.setEpochToc(new AbsoluteDate(gpsTocYear, gpsTocMonth, gpsTocDay, gpsTocHours,
579                                                        gpsTocMin, gpsTocSec, pi.timeScales.getGPS()));
580 
581                 // Af0, Af1, and Af2
582                 pi.gpsNav.setAf0(parseDouble(line, 23, 19));
583                 pi.gpsNav.setAf1(parseDouble(line, 42, 19));
584                 pi.gpsNav.setAf2(parseDouble(line, 61, 19));
585             }
586 
587             /** {@inheritDoc} */
588             @Override
589             public void parseFirstBroadcastOrbit(final String line, final ParseInfo pi) {
590                 // IODE
591                 pi.gpsNav.setIODE(parseDouble(line, 4, 19));
592                 // Crs
593                 pi.gpsNav.setCrs(parseDouble(line, 23, 19));
594                 // Delta n
595                 pi.gpsNav.setDeltaN(parseDouble(line, 42, 19));
596                 // M0
597                 pi.gpsNav.setM0(parseDouble(line, 61, 19));
598             }
599 
600             /** {@inheritDoc} */
601             @Override
602             public void parseSecondBroadcastOrbit(final String line, final ParseInfo pi) {
603                 // Cuc
604                 pi.gpsNav.setCuc(parseDouble(line, 4, 19));
605                 // e
606                 pi.gpsNav.setE(parseDouble(line, 23, 19));
607                 // Cus
608                 pi.gpsNav.setCus(parseDouble(line, 42, 19));
609                 // sqrt(A)
610                 pi.gpsNav.setSqrtA(parseDouble(line, 61, 19));
611             }
612 
613             /** {@inheritDoc} */
614             @Override
615             public void parseThirdBroadcastOrbit(final String line, final ParseInfo pi) {
616                 // Toe
617                 pi.gpsNav.setTime(parseDouble(line, 4, 19));
618                 // Cic
619                 pi.gpsNav.setCic(parseDouble(line, 23, 19));
620                 // Omega0
621                 pi.gpsNav.setOmega0(parseDouble(line, 42, 19));
622                 // Cis
623                 pi.gpsNav.setCis(parseDouble(line, 61, 19));
624             }
625 
626             /** {@inheritDoc} */
627             @Override
628             public void parseFourthBroadcastOrbit(final String line, final ParseInfo pi) {
629                 // i0
630                 pi.gpsNav.setI0(parseDouble(line, 4, 19));
631                 // Crc
632                 pi.gpsNav.setCrc(parseDouble(line, 23, 19));
633                 // omega
634                 pi.gpsNav.setPa(parseDouble(line, 42, 19));
635                 // OMEGA DOT
636                 pi.gpsNav.setOmegaDot(parseDouble(line, 61, 19));
637             }
638 
639             /** {@inheritDoc} */
640             @Override
641             public void parseFifthBroadcastOrbit(final String line, final ParseInfo pi) {
642                 // iDot
643                 pi.gpsNav.setIDot(parseDouble(line, 4, 19));
644                 // Codes on L2 channel (ignored)
645                 // parseDouble(line, 23, 19)
646                 // GPS week (to go with Toe)
647                 pi.gpsNav.setWeek((int) parseDouble(line, 42, 19));
648                 pi.gpsNav.setDate(new GNSSDate(pi.gpsNav.getWeek(),
649                                                SEC_TO_MILLI * pi.gpsNav.getTime(),
650                                                SatelliteSystem.GPS,
651                                                pi.timeScales).getDate());
652             }
653 
654             /** {@inheritDoc} */
655             @Override
656             public void parseSixthBroadcastOrbit(final String line, final ParseInfo pi) {
657                 // SV accuracy
658                 pi.gpsNav.setSvAccuracy(parseDouble(line, 4, 19));
659                 // Health
660                 pi.gpsNav.setSvHealth(parseDouble(line, 23, 19));
661                 // TGD
662                 pi.gpsNav.setTGD(parseDouble(line, 42, 19));
663                 // IODC
664                 pi.gpsNav.setIODC(parseDouble(line, 61, 19));
665             }
666 
667             /** {@inheritDoc} */
668             @Override
669             public void parseSeventhBroadcastOrbit(final String line, final ParseInfo pi) {
670                 // Add the navigation message to the file
671                 pi.file.addGPSNavigationMessage(pi.gpsNav);
672                 // Reinitialized the container for navigation data
673                 pi.gpsNav = new GPSNavigationMessage();
674             }
675 
676         },
677 
678         /** Galileo. */
679         GALILEO("E") {
680 
681             /** {@inheritDoc} */
682             @Override
683             public void parseFirstLine(final String line, final ParseInfo pi) {
684                 // PRN
685                 pi.galileoNav.setPRN(parseInt(line, 1, 2));
686 
687                 // Toc
688                 final int galileoTocYear  = parseInt(line, 4, 4);
689                 final int galileoTocMonth = parseInt(line, 9, 2);
690                 final int galileoTocDay   = parseInt(line, 12, 2);
691                 final int galileoTocHours = parseInt(line, 15, 2);
692                 final int galileoTocMin   = parseInt(line, 18, 2);
693                 final int galileoTocSec   = parseInt(line, 21, 2);
694                 pi.galileoNav.setEpochToc(new AbsoluteDate(galileoTocYear, galileoTocMonth, galileoTocDay, galileoTocHours,
695                                                        galileoTocMin, galileoTocSec, pi.timeScales.getGST()));
696 
697                 // Af0, Af1, and Af2
698                 pi.galileoNav.setAf0(parseDouble(line, 23, 19));
699                 pi.galileoNav.setAf1(parseDouble(line, 42, 19));
700                 pi.galileoNav.setAf2(parseDouble(line, 61, 19));
701             }
702 
703             /** {@inheritDoc} */
704             @Override
705             public void parseFirstBroadcastOrbit(final String line, final ParseInfo pi) {
706                 // IODNav
707                 pi.galileoNav.setIODNav(parseDouble(line, 4, 19));
708                 // Crs
709                 pi.galileoNav.setCrs(parseDouble(line, 23, 19));
710                 // Delta n
711                 pi.galileoNav.setDeltaN(parseDouble(line, 42, 19));
712                 // M0
713                 pi.galileoNav.setM0(parseDouble(line, 61, 19));
714             }
715 
716             /** {@inheritDoc} */
717             @Override
718             public void parseSecondBroadcastOrbit(final String line, final ParseInfo pi) {
719                 // Cuc
720                 pi.galileoNav.setCuc(parseDouble(line, 4, 19));
721                 // e
722                 pi.galileoNav.setE(parseDouble(line, 23, 19));
723                 // Cus
724                 pi.galileoNav.setCus(parseDouble(line, 42, 19));
725                 // sqrt(A)
726                 pi.galileoNav.setSqrtA(parseDouble(line, 61, 19));
727             }
728 
729             /** {@inheritDoc} */
730             @Override
731             public void parseThirdBroadcastOrbit(final String line, final ParseInfo pi) {
732                 // Toe
733                 pi.galileoNav.setTime(parseDouble(line, 4, 19));
734                 // Cic
735                 pi.galileoNav.setCic(parseDouble(line, 23, 19));
736                 // Omega0
737                 pi.galileoNav.setOmega0(parseDouble(line, 42, 19));
738                 // Cis
739                 pi.galileoNav.setCis(parseDouble(line, 61, 19));
740             }
741 
742             /** {@inheritDoc} */
743             @Override
744             public void parseFourthBroadcastOrbit(final String line, final ParseInfo pi) {
745                 // i0
746                 pi.galileoNav.setI0(parseDouble(line, 4, 19));
747                 // Crc
748                 pi.galileoNav.setCrc(parseDouble(line, 23, 19));
749                 // omega
750                 pi.galileoNav.setPa(parseDouble(line, 42, 19));
751                 // OMEGA DOT
752                 pi.galileoNav.setOmegaDot(parseDouble(line, 61, 19));
753             }
754 
755             /** {@inheritDoc} */
756             @Override
757             public void parseFifthBroadcastOrbit(final String line, final ParseInfo pi) {
758                 // iDot
759                 pi.galileoNav.setIDot(parseDouble(line, 4, 19));
760                 // Data sources (ignored)
761                 // parseDouble(line, 23, 19)
762                 // GAL week (to go with Toe)
763                 pi.galileoNav.setWeek((int) parseDouble(line, 42, 19));
764                 pi.galileoNav.setDate(new GNSSDate(pi.galileoNav.getWeek(),
765                                                    SEC_TO_MILLI * pi.galileoNav.getTime(),
766                                                    SatelliteSystem.GALILEO,
767                                                    pi.timeScales).getDate());
768             }
769 
770             /** {@inheritDoc} */
771             @Override
772             public void parseSixthBroadcastOrbit(final String line, final ParseInfo pi) {
773                 // SISA
774                 pi.galileoNav.setSisa(parseDouble(line, 4, 19));
775                 // Health
776                 pi.galileoNav.setSvHealth(parseDouble(line, 23, 19));
777                 // E5a/E1 BGD
778                 pi.galileoNav.setBGDE1E5a(parseDouble(line, 42, 19));
779                 // E5b/E1 BGD
780                 pi.galileoNav.setBGDE5bE1(parseDouble(line, 61, 19));
781             }
782 
783             /** {@inheritDoc} */
784             @Override
785             public void parseSeventhBroadcastOrbit(final String line, final ParseInfo pi) {
786                 // Add the navigation message to the file
787                 pi.file.addGalileoNavigationMessage(pi.galileoNav);
788                 // Reinitialized the container for navigation data
789                 pi.galileoNav = new GalileoNavigationMessage();
790             }
791 
792         },
793 
794         /** Beidou. */
795         BEIDOU("C") {
796 
797             /** {@inheritDoc} */
798             @Override
799             public void parseFirstLine(final String line, final ParseInfo pi) {
800                 // PRN
801                 pi.beidouNav.setPRN(parseInt(line, 1, 2));
802 
803                 // Toc
804                 final int beidouTocYear  = parseInt(line, 4, 4);
805                 final int beidouTocMonth = parseInt(line, 9, 2);
806                 final int beidouTocDay   = parseInt(line, 12, 2);
807                 final int beidouTocHours = parseInt(line, 15, 2);
808                 final int beidouTocMin   = parseInt(line, 18, 2);
809                 final int beidouTocSec   = parseInt(line, 21, 2);
810                 pi.beidouNav.setEpochToc(new AbsoluteDate(beidouTocYear, beidouTocMonth, beidouTocDay, beidouTocHours,
811                                                        beidouTocMin, beidouTocSec, pi.timeScales.getBDT()));
812 
813                 // Af0, Af1, and Af2
814                 pi.beidouNav.setAf0(parseDouble(line, 23, 19));
815                 pi.beidouNav.setAf1(parseDouble(line, 42, 19));
816                 pi.beidouNav.setAf2(parseDouble(line, 61, 19));
817             }
818 
819             /** {@inheritDoc} */
820             @Override
821             public void parseFirstBroadcastOrbit(final String line, final ParseInfo pi) {
822                 // AODE
823                 pi.beidouNav.setAODE(parseDouble(line, 4, 19));
824                 // Crs
825                 pi.beidouNav.setCrs(parseDouble(line, 23, 19));
826                 // Delta n
827                 pi.beidouNav.setDeltaN(parseDouble(line, 42, 19));
828                 // M0
829                 pi.beidouNav.setM0(parseDouble(line, 61, 19));
830             }
831 
832             /** {@inheritDoc} */
833             @Override
834             public void parseSecondBroadcastOrbit(final String line, final ParseInfo pi) {
835                 // Cuc
836                 pi.beidouNav.setCuc(parseDouble(line, 4, 19));
837                 // e
838                 pi.beidouNav.setE(parseDouble(line, 23, 19));
839                 // Cus
840                 pi.beidouNav.setCus(parseDouble(line, 42, 19));
841                 // sqrt(A)
842                 pi.beidouNav.setSqrtA(parseDouble(line, 61, 19));
843             }
844 
845             /** {@inheritDoc} */
846             @Override
847             public void parseThirdBroadcastOrbit(final String line, final ParseInfo pi) {
848                 // Toe
849                 pi.beidouNav.setTime(parseDouble(line, 4, 19));
850                 // Cic
851                 pi.beidouNav.setCic(parseDouble(line, 23, 19));
852                 // Omega0
853                 pi.beidouNav.setOmega0(parseDouble(line, 42, 19));
854                 // Cis
855                 pi.beidouNav.setCis(parseDouble(line, 61, 19));
856             }
857 
858             /** {@inheritDoc} */
859             @Override
860             public void parseFourthBroadcastOrbit(final String line, final ParseInfo pi) {
861                 // i0
862                 pi.beidouNav.setI0(parseDouble(line, 4, 19));
863                 // Crc
864                 pi.beidouNav.setCrc(parseDouble(line, 23, 19));
865                 // omega
866                 pi.beidouNav.setPa(parseDouble(line, 42, 19));
867                 // OMEGA DOT
868                 pi.beidouNav.setOmegaDot(parseDouble(line, 61, 19));
869             }
870 
871             /** {@inheritDoc} */
872             @Override
873             public void parseFifthBroadcastOrbit(final String line, final ParseInfo pi) {
874                 // iDot
875                 pi.beidouNav.setIDot(parseDouble(line, 4, 19));
876                 // BDT week (to go with Toe)
877                 pi.beidouNav.setWeek((int) parseDouble(line, 42, 19));
878                 pi.beidouNav.setDate(new GNSSDate(pi.beidouNav.getWeek(),
879                                                   SEC_TO_MILLI * pi.beidouNav.getTime(),
880                                                   SatelliteSystem.BEIDOU,
881                                                   pi.timeScales).getDate());
882             }
883 
884             /** {@inheritDoc} */
885             @Override
886             public void parseSixthBroadcastOrbit(final String line, final ParseInfo pi) {
887                 // SV accuracy
888                 pi.beidouNav.setSvAccuracy(parseDouble(line, 4, 19));
889                 // SatH1 (ignored)
890                 // parseDouble(line, 23, 19)
891                 // TGD1
892                 pi.beidouNav.setTGD1(parseDouble(line, 42, 19));
893                 // TGD2
894                 pi.beidouNav.setTGD2(parseDouble(line, 61, 19));
895             }
896 
897             /** {@inheritDoc} */
898             @Override
899             public void parseSeventhBroadcastOrbit(final String line, final ParseInfo pi) {
900                 // Transmission time of message (ignored)
901                 // parseDouble(line, 4, 19);
902                 // AODC
903                 pi.beidouNav.setAODC(parseDouble(line, 23, 19));
904                 // Add the navigation message to the file
905                 pi.file.addBeidouNavigationMessage(pi.beidouNav);
906                 // Reinitialized the container for navigation data
907                 pi.beidouNav = new BeidouNavigationMessage();
908 
909             }
910 
911         },
912 
913         /** QZSS. */
914         QZSS("J") {
915 
916             /** {@inheritDoc} */
917             @Override
918             public void parseFirstLine(final String line, final ParseInfo pi) {
919                 // PRN
920                 pi.qzssNav.setPRN(parseInt(line, 1, 2));
921 
922                 // Toc
923                 final int qzssTocYear  = parseInt(line, 4, 4);
924                 final int qzssTocMonth = parseInt(line, 9, 2);
925                 final int qzssTocDay   = parseInt(line, 12, 2);
926                 final int qzssTocHours = parseInt(line, 15, 2);
927                 final int qzssTocMin   = parseInt(line, 18, 2);
928                 final int qzssTocSec   = parseInt(line, 21, 2);
929                 pi.qzssNav.setEpochToc(new AbsoluteDate(qzssTocYear, qzssTocMonth, qzssTocDay, qzssTocHours,
930                                                        qzssTocMin, qzssTocSec, pi.timeScales.getQZSS()));
931 
932                 // Af0, Af1, and Af2
933                 pi.qzssNav.setAf0(parseDouble(line, 23, 19));
934                 pi.qzssNav.setAf1(parseDouble(line, 42, 19));
935                 pi.qzssNav.setAf2(parseDouble(line, 61, 19));
936             }
937 
938             /** {@inheritDoc} */
939             @Override
940             public void parseFirstBroadcastOrbit(final String line, final ParseInfo pi) {
941                 // IODE
942                 pi.qzssNav.setIODE(parseDouble(line, 4, 19));
943                 // Crs
944                 pi.qzssNav.setCrs(parseDouble(line, 23, 19));
945                 // Delta n
946                 pi.qzssNav.setDeltaN(parseDouble(line, 42, 19));
947                 // M0
948                 pi.qzssNav.setM0(parseDouble(line, 61, 19));
949             }
950 
951             /** {@inheritDoc} */
952             @Override
953             public void parseSecondBroadcastOrbit(final String line, final ParseInfo pi) {
954                 // Cuc
955                 pi.qzssNav.setCuc(parseDouble(line, 4, 19));
956                 // e
957                 pi.qzssNav.setE(parseDouble(line, 23, 19));
958                 // Cus
959                 pi.qzssNav.setCus(parseDouble(line, 42, 19));
960                 // sqrt(A)
961                 pi.qzssNav.setSqrtA(parseDouble(line, 61, 19));
962             }
963 
964             /** {@inheritDoc} */
965             @Override
966             public void parseThirdBroadcastOrbit(final String line, final ParseInfo pi) {
967                 // Toe
968                 pi.qzssNav.setTime(parseDouble(line, 4, 19));
969                 // Cic
970                 pi.qzssNav.setCic(parseDouble(line, 23, 19));
971                 // Omega0
972                 pi.qzssNav.setOmega0(parseDouble(line, 42, 19));
973                 // Cis
974                 pi.qzssNav.setCis(parseDouble(line, 61, 19));
975             }
976 
977             /** {@inheritDoc} */
978             @Override
979             public void parseFourthBroadcastOrbit(final String line, final ParseInfo pi) {
980                 // i0
981                 pi.qzssNav.setI0(parseDouble(line, 4, 19));
982                 // Crc
983                 pi.qzssNav.setCrc(parseDouble(line, 23, 19));
984                 // omega
985                 pi.qzssNav.setPa(parseDouble(line, 42, 19));
986                 // OMEGA DOT
987                 pi.qzssNav.setOmegaDot(parseDouble(line, 61, 19));
988             }
989 
990             /** {@inheritDoc} */
991             @Override
992             public void parseFifthBroadcastOrbit(final String line, final ParseInfo pi) {
993                 // iDot
994                 pi.qzssNav.setIDot(parseDouble(line, 4, 19));
995                 // Codes on L2 channel (ignored)
996                 // parseDouble(line, 23, 19)
997                 // GPS week (to go with Toe)
998                 pi.qzssNav.setWeek((int) parseDouble(line, 42, 19));
999                 pi.qzssNav.setDate(new GNSSDate(pi.qzssNav.getWeek(),
1000                                                 SEC_TO_MILLI * pi.qzssNav.getTime(),
1001                                                 SatelliteSystem.QZSS,
1002                                                 pi.timeScales).getDate());
1003             }
1004 
1005             /** {@inheritDoc} */
1006             @Override
1007             public void parseSixthBroadcastOrbit(final String line, final ParseInfo pi) {
1008                 // SV accuracy
1009                 pi.qzssNav.setSvAccuracy(parseDouble(line, 4, 19));
1010                 // Health
1011                 pi.qzssNav.setSvHealth(parseDouble(line, 23, 19));
1012                 // TGD
1013                 pi.qzssNav.setTGD(parseDouble(line, 42, 19));
1014                 // IODC
1015                 pi.qzssNav.setIODC(parseDouble(line, 61, 19));
1016             }
1017 
1018             /** {@inheritDoc} */
1019             @Override
1020             public void parseSeventhBroadcastOrbit(final String line, final ParseInfo pi) {
1021                 // Add the navigation message to the file
1022                 pi.file.addQZSSNavigationMessage(pi.qzssNav);
1023                 // Reinitialized the container for navigation data
1024                 pi.qzssNav = new QZSSNavigationMessage();
1025             }
1026 
1027         },
1028 
1029         /** IRNSS. */
1030         IRNSS("I") {
1031 
1032             /** {@inheritDoc} */
1033             @Override
1034             public void parseFirstLine(final String line, final ParseInfo pi) {
1035                 // PRN
1036                 pi.irnssNav.setPRN(parseInt(line, 1, 2));
1037 
1038                 // Toc
1039                 final int irnssTocYear  = parseInt(line, 4, 4);
1040                 final int irnssTocMonth = parseInt(line, 9, 2);
1041                 final int irnssTocDay   = parseInt(line, 12, 2);
1042                 final int irnssTocHours = parseInt(line, 15, 2);
1043                 final int irnssTocMin   = parseInt(line, 18, 2);
1044                 final int irnssTocSec   = parseInt(line, 21, 2);
1045                 pi.irnssNav.setEpochToc(new AbsoluteDate(irnssTocYear, irnssTocMonth, irnssTocDay, irnssTocHours,
1046                                                          irnssTocMin, irnssTocSec, pi.timeScales.getIRNSS()));
1047 
1048                 // Af0, Af1, and Af2
1049                 pi.irnssNav.setAf0(parseDouble(line, 23, 19));
1050                 pi.irnssNav.setAf1(parseDouble(line, 42, 19));
1051                 pi.irnssNav.setAf2(parseDouble(line, 61, 19));
1052             }
1053 
1054             /** {@inheritDoc} */
1055             @Override
1056             public void parseFirstBroadcastOrbit(final String line, final ParseInfo pi) {
1057                 // IODEC
1058                 pi.irnssNav.setIODEC(parseDouble(line, 4, 19));
1059                 // Crs
1060                 pi.irnssNav.setCrs(parseDouble(line, 23, 19));
1061                 // Delta n
1062                 pi.irnssNav.setDeltaN(parseDouble(line, 42, 19));
1063                 // M0
1064                 pi.irnssNav.setM0(parseDouble(line, 61, 19));
1065             }
1066 
1067             /** {@inheritDoc} */
1068             @Override
1069             public void parseSecondBroadcastOrbit(final String line, final ParseInfo pi) {
1070                 // Cuc
1071                 pi.irnssNav.setCuc(parseDouble(line, 4, 19));
1072                 // e
1073                 pi.irnssNav.setE(parseDouble(line, 23, 19));
1074                 // Cus
1075                 pi.irnssNav.setCus(parseDouble(line, 42, 19));
1076                 // sqrt(A)
1077                 pi.irnssNav.setSqrtA(parseDouble(line, 61, 19));
1078             }
1079 
1080             /** {@inheritDoc} */
1081             @Override
1082             public void parseThirdBroadcastOrbit(final String line, final ParseInfo pi) {
1083                 // Toe
1084                 pi.irnssNav.setTime(parseDouble(line, 4, 19));
1085                 // Cic
1086                 pi.irnssNav.setCic(parseDouble(line, 23, 19));
1087                 // Omega0
1088                 pi.irnssNav.setOmega0(parseDouble(line, 42, 19));
1089                 // Cis
1090                 pi.irnssNav.setCis(parseDouble(line, 61, 19));
1091             }
1092 
1093             /** {@inheritDoc} */
1094             @Override
1095             public void parseFourthBroadcastOrbit(final String line, final ParseInfo pi) {
1096                 // i0
1097                 pi.irnssNav.setI0(parseDouble(line, 4, 19));
1098                 // Crc
1099                 pi.irnssNav.setCrc(parseDouble(line, 23, 19));
1100                 // omega
1101                 pi.irnssNav.setPa(parseDouble(line, 42, 19));
1102                 // OMEGA DOT
1103                 pi.irnssNav.setOmegaDot(parseDouble(line, 61, 19));
1104             }
1105 
1106             /** {@inheritDoc} */
1107             @Override
1108             public void parseFifthBroadcastOrbit(final String line, final ParseInfo pi) {
1109                 // iDot
1110                 pi.irnssNav.setIDot(parseDouble(line, 4, 19));
1111                 // IRNSS week (to go with Toe)
1112                 pi.irnssNav.setWeek((int) parseDouble(line, 42, 19));
1113                 pi.irnssNav.setDate(new GNSSDate(pi.irnssNav.getWeek(),
1114                                                  SEC_TO_MILLI * pi.irnssNav.getTime(),
1115                                                  SatelliteSystem.IRNSS,
1116                                                  pi.timeScales).getDate());
1117             }
1118 
1119             /** {@inheritDoc} */
1120             @Override
1121             public void parseSixthBroadcastOrbit(final String line, final ParseInfo pi) {
1122                 // SV accuracy
1123                 pi.irnssNav.setURA(parseDouble(line, 4, 19));
1124                 // Health
1125                 pi.irnssNav.setSvHealth(parseDouble(line, 23, 19));
1126                 // TGD
1127                 pi.irnssNav.setTGD(parseDouble(line, 42, 19));
1128             }
1129 
1130             /** {@inheritDoc} */
1131             @Override
1132             public void parseSeventhBroadcastOrbit(final String line, final ParseInfo pi) {
1133                 // Add the navigation message to the file
1134                 pi.file.addIRNSSNavigationMessage(pi.irnssNav);
1135                 // Reinitialized the container for navigation data
1136                 pi.irnssNav = new IRNSSNavigationMessage();
1137 
1138             }
1139 
1140         },
1141 
1142         /** Glonass. */
1143         GLONASS("R") {
1144 
1145             /** {@inheritDoc} */
1146             @Override
1147             public void parseFirstLine(final String line, final ParseInfo pi) {
1148                 // PRN
1149                 pi.glonassNav.setPRN(parseInt(line, 1, 2));
1150 
1151                 // Toc
1152                 final int glonassTocYear  = parseInt(line, 4, 4);
1153                 final int glonassTocMonth = parseInt(line, 9, 2);
1154                 final int glonassTocDay   = parseInt(line, 12, 2);
1155                 final int glonassTocHours = parseInt(line, 15, 2);
1156                 final int glonassTocMin   = parseInt(line, 18, 2);
1157                 final int glonassTocSec   = parseInt(line, 21, 2);
1158                 final AbsoluteDate date = new AbsoluteDate(glonassTocYear, glonassTocMonth, glonassTocDay, glonassTocHours,
1159                                                            glonassTocMin, glonassTocSec, pi.timeScales.getUTC());
1160 
1161                 // Build a GPS date
1162                 final GNSSDate gpsEpoch = new GNSSDate(date, SatelliteSystem.GPS, pi.timeScales);
1163 
1164                 // Toc rounded by 15 min in UTC
1165                 final double secInWeek = FastMath.floor((0.001 * gpsEpoch.getMilliInWeek() + 450.0) / 900.0) * 900.0;
1166                 final AbsoluteDate rounded = new GNSSDate(gpsEpoch.getWeekNumber(),
1167                                                           SEC_TO_MILLI * secInWeek,
1168                                                           SatelliteSystem.GPS, pi.timeScales).getDate();
1169 
1170                 pi.glonassNav.setEpochToc(rounded);
1171 
1172                 // TauN (we read -TauN) and GammaN
1173                 pi.glonassNav.setTauN(-parseDouble(line, 23, 19));
1174                 pi.glonassNav.setGammaN(parseDouble(line, 42, 19));
1175 
1176                 // Date
1177                 pi.glonassNav.setDate(rounded);
1178 
1179                 // Time
1180                 pi.glonassNav.setTime(fmod(parseDouble(line, 61, 19), Constants.JULIAN_DAY));
1181 
1182             }
1183 
1184             /** {@inheritDoc} */
1185             @Override
1186             public void parseFirstBroadcastOrbit(final String line, final ParseInfo pi) {
1187                 // X
1188                 pi.glonassNav.setX(parseDouble(line, 4, 19) * KM_TO_M);
1189                 // Vx
1190                 pi.glonassNav.setXDot(parseDouble(line, 23, 19) * KM_TO_M);
1191                 // Ax
1192                 pi.glonassNav.setXDotDot(parseDouble(line, 42, 19) * KM_TO_M);
1193                 // Health
1194                 pi.glonassNav.setHealth(parseDouble(line, 61, 19));
1195             }
1196 
1197             /** {@inheritDoc} */
1198             @Override
1199             public void parseSecondBroadcastOrbit(final String line, final ParseInfo pi) {
1200                 // Y
1201                 pi.glonassNav.setY(parseDouble(line, 4, 19) * KM_TO_M);
1202                 // Vy
1203                 pi.glonassNav.setYDot(parseDouble(line, 23, 19) * KM_TO_M);
1204                 // Ay
1205                 pi.glonassNav.setYDotDot(parseDouble(line, 42, 19) * KM_TO_M);
1206                 // Frequency number
1207                 pi.glonassNav.setFrequencyNumber(parseDouble(line, 61, 19));
1208             }
1209 
1210             /** {@inheritDoc} */
1211             @Override
1212             public void parseThirdBroadcastOrbit(final String line, final ParseInfo pi) {
1213                 // Z
1214                 pi.glonassNav.setZ(parseDouble(line, 4, 19) * KM_TO_M);
1215                 // Vz
1216                 pi.glonassNav.setZDot(parseDouble(line, 23, 19) * KM_TO_M);
1217                 // Az
1218                 pi.glonassNav.setZDotDot(parseDouble(line, 42, 19) * KM_TO_M);
1219 
1220                 // Add the navigation message to the file
1221                 pi.file.addGlonassNavigationMessage(pi.glonassNav);
1222                 // Reinitialized the container for navigation data
1223                 pi.glonassNav = new GLONASSNavigationMessage();
1224             }
1225 
1226             /** {@inheritDoc} */
1227             @Override
1228             public void parseFourthBroadcastOrbit(final String line, final ParseInfo pi) {
1229                 // Nothing to do for GLONASS
1230             }
1231 
1232             /** {@inheritDoc} */
1233             @Override
1234             public void parseFifthBroadcastOrbit(final String line, final ParseInfo pi) {
1235                 // Nothing to do for GLONASS
1236             }
1237 
1238             /** {@inheritDoc} */
1239             @Override
1240             public void parseSixthBroadcastOrbit(final String line, final ParseInfo pi) {
1241                 // Nothing to do for GLONASS
1242             }
1243 
1244             /** {@inheritDoc} */
1245             @Override
1246             public void parseSeventhBroadcastOrbit(final String line, final ParseInfo pi) {
1247                 // Nothing to do for GLONASS
1248             }
1249 
1250         },
1251 
1252         /** SBAS. */
1253         SBAS("S") {
1254 
1255             /** {@inheritDoc} */
1256             @Override
1257             public void parseFirstLine(final String line, final ParseInfo pi) {
1258                 // PRN
1259                 pi.sbasNav.setPRN(parseInt(line, 1, 2));
1260 
1261                 // Toc
1262                 final int sbasTocYear  = parseInt(line, 4, 4);
1263                 final int sbasTocMonth = parseInt(line, 9, 2);
1264                 final int sbasTocDay   = parseInt(line, 12, 2);
1265                 final int sbasTocHours = parseInt(line, 15, 2);
1266                 final int sbasTocMin   = parseInt(line, 18, 2);
1267                 final int sbasTocSec   = parseInt(line, 21, 2);
1268                 // Time scale (UTC for Rinex 3.01 and GPS for other RINEX versions)
1269                 final TimeScale    timeScale = ((int) pi.version * 100 == 301) ? pi.timeScales.getUTC() : pi.timeScales.getGPS();
1270                 final AbsoluteDate refEpoch   = new AbsoluteDate(sbasTocYear, sbasTocMonth, sbasTocDay, sbasTocHours,
1271                                                                  sbasTocMin, sbasTocSec, timeScale);
1272                 pi.sbasNav.setEpochToc(refEpoch);
1273 
1274                 // AGf0 and AGf1
1275                 pi.sbasNav.setAGf0(parseDouble(line, 23, 19));
1276                 pi.sbasNav.setAGf1(parseDouble(line, 42, 19));
1277                 pi.sbasNav.setTime(parseDouble(line, 61, 19));
1278 
1279                 // Set the ephemeris epoch (same as time of clock epoch)
1280                 pi.sbasNav.setDate(refEpoch);
1281             }
1282 
1283             /** {@inheritDoc} */
1284             @Override
1285             public void parseFirstBroadcastOrbit(final String line, final ParseInfo pi) {
1286                 // X
1287                 pi.sbasNav.setX(parseDouble(line, 4, 19) * KM_TO_M);
1288                 // Vx
1289                 pi.sbasNav.setXDot(parseDouble(line, 23, 19) * KM_TO_M);
1290                 // Ax
1291                 pi.sbasNav.setXDotDot(parseDouble(line, 42, 19) * KM_TO_M);
1292                 // Health
1293                 pi.sbasNav.setHealth(parseDouble(line, 61, 19));
1294             }
1295 
1296             /** {@inheritDoc} */
1297             @Override
1298             public void parseSecondBroadcastOrbit(final String line, final ParseInfo pi) {
1299                 // Y
1300                 pi.sbasNav.setY(parseDouble(line, 4, 19) * KM_TO_M);
1301                 // Vy
1302                 pi.sbasNav.setYDot(parseDouble(line, 23, 19) * KM_TO_M);
1303                 // Ay
1304                 pi.sbasNav.setYDotDot(parseDouble(line, 42, 19) * KM_TO_M);
1305                 // URA
1306                 pi.sbasNav.setURA(parseDouble(line, 61, 19));
1307             }
1308 
1309             /** {@inheritDoc} */
1310             @Override
1311             public void parseThirdBroadcastOrbit(final String line, final ParseInfo pi) {
1312                 // Z
1313                 pi.sbasNav.setZ(parseDouble(line, 4, 19) * KM_TO_M);
1314                 // Vz
1315                 pi.sbasNav.setZDot(parseDouble(line, 23, 19) * KM_TO_M);
1316                 // Az
1317                 pi.sbasNav.setZDotDot(parseDouble(line, 42, 19) * KM_TO_M);
1318                 // IODN
1319                 pi.sbasNav.setIODN(parseDouble(line, 61, 19));
1320 
1321                 // Add the navigation message to the file
1322                 pi.file.addSBASNavigationMessage(pi.sbasNav);
1323 
1324                 // Reinitialized the container for navigation data
1325                 pi.sbasNav = new SBASNavigationMessage();
1326 
1327             }
1328 
1329             /** {@inheritDoc} */
1330             @Override
1331             public void parseFourthBroadcastOrbit(final String line, final ParseInfo pi) {
1332                 // Nothing to do for SBAS
1333             }
1334 
1335             /** {@inheritDoc} */
1336             @Override
1337             public void parseFifthBroadcastOrbit(final String line, final ParseInfo pi) {
1338                 // Nothing to do for SBAS
1339             }
1340 
1341             /** {@inheritDoc} */
1342             @Override
1343             public void parseSixthBroadcastOrbit(final String line, final ParseInfo pi) {
1344                 // Nothing to do for SBAS
1345             }
1346 
1347             /** {@inheritDoc} */
1348             @Override
1349             public void parseSeventhBroadcastOrbit(final String line, final ParseInfo pi) {
1350                 // Nothing to do for SBAS
1351             }
1352 
1353         };
1354 
1355         /** Parsing map. */
1356         private static final Map<String, SatelliteSystemLineParser> KEYS_MAP = new HashMap<>();
1357         static {
1358             for (final SatelliteSystemLineParser satelliteSystem : values()) {
1359                 KEYS_MAP.put(satelliteSystem.getKey(), satelliteSystem);
1360             }
1361         }
1362 
1363         /** Satellite system key. */
1364         private String key;
1365 
1366         /**
1367          * Constructor.
1368          * @param key satellite system key
1369          */
1370         SatelliteSystemLineParser(final String key) {
1371             this.key = key;
1372         }
1373 
1374         /**
1375          * Getter for the satellite system key.
1376          * @return the satellite system key
1377          */
1378         public String getKey() {
1379             return key;
1380         }
1381 
1382         /** Parse a string to get the satellite system.
1383          * <p>
1384          * The string first character must be the satellite system.
1385          * </p>
1386          * @param s string to parse
1387          * @return the satellite system
1388          */
1389         public static SatelliteSystemLineParser getSatelliteSystemLineParser(final String s) {
1390             return KEYS_MAP.get(s);
1391         }
1392 
1393         /**
1394          * Parse the first line of the navigation message.
1395          * @param line line to read
1396          * @param pi holder for transient data
1397          */
1398         public abstract void parseFirstLine(String line, ParseInfo pi);
1399 
1400         /**
1401          * Parse the "BROADCASTORBIT - 1" line.
1402          * @param line line to read
1403          * @param pi holder for transient data
1404          */
1405         public abstract void parseFirstBroadcastOrbit(String line, ParseInfo pi);
1406 
1407         /**
1408          * Parse the "BROADCASTORBIT - 2" line.
1409          * @param line line to read
1410          * @param pi holder for transient data
1411          */
1412         public abstract void parseSecondBroadcastOrbit(String line, ParseInfo pi);
1413 
1414         /**
1415          * Parse the "BROADCASTORBIT - 3" line.
1416          * @param line line to read
1417          * @param pi holder for transient data
1418          */
1419         public abstract void parseThirdBroadcastOrbit(String line, ParseInfo pi);
1420 
1421         /**
1422          * Parse the "BROADCASTORBIT - 4" line.
1423          * @param line line to read
1424          * @param pi holder for transient data
1425          */
1426         public abstract void parseFourthBroadcastOrbit(String line, ParseInfo pi);
1427 
1428         /**
1429          * Parse the "BROADCASTORBIT - 5" line.
1430          * @param line line to read
1431          * @param pi holder for transient data
1432          */
1433         public abstract void parseFifthBroadcastOrbit(String line, ParseInfo pi);
1434 
1435         /**
1436          * Parse the "BROADCASTORBIT - 6" line.
1437          * @param line line to read
1438          * @param pi holder for transient data
1439          */
1440         public abstract void parseSixthBroadcastOrbit(String line, ParseInfo pi);
1441 
1442         /**
1443          * Parse the "BROADCASTORBIT - 7" line.
1444          * @param line line to read
1445          * @param pi holder for transient data
1446          */
1447         public abstract void parseSeventhBroadcastOrbit(String line, ParseInfo pi);
1448 
1449         /**
1450          * Calculates the floating-point remainder of a / b.
1451          * <p>
1452          * fmod = a - x * b
1453          * where x = (int) a / b
1454          * </p>
1455          * @param a numerator
1456          * @param b denominator
1457          * @return the floating-point remainder of a / b
1458          */
1459         private static double fmod(final double a, final double b) {
1460             final double x = (int) (a / b);
1461             return a - x * b;
1462         }
1463 
1464     }
1465 
1466 }