1   /* Copyright 2002-2026 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.parsers;
18  
19  import org.hipparchus.util.FastMath;
20  import org.orekit.errors.OrekitException;
21  import org.orekit.errors.OrekitMessages;
22  import org.orekit.files.rinex.navigation.EarthOrientationParameterMessage;
23  import org.orekit.files.rinex.navigation.IonosphereBDGIMMessage;
24  import org.orekit.files.rinex.navigation.IonosphereGlonassCdmsMessage;
25  import org.orekit.files.rinex.navigation.IonosphereKlobucharMessage;
26  import org.orekit.files.rinex.navigation.IonosphereNavICKlobucharMessage;
27  import org.orekit.files.rinex.navigation.IonosphereNavICNeQuickNMessage;
28  import org.orekit.files.rinex.navigation.IonosphereNequickGMessage;
29  import org.orekit.files.rinex.navigation.IonosphericCorrectionType;
30  import org.orekit.files.rinex.navigation.KlobucharIonosphericCorrection;
31  import org.orekit.files.rinex.navigation.NeQuickGIonosphericCorrection;
32  import org.orekit.files.rinex.navigation.RecordType;
33  import org.orekit.files.rinex.navigation.RinexNavigation;
34  import org.orekit.files.rinex.navigation.RinexNavigationHeader;
35  import org.orekit.files.rinex.navigation.SystemTimeOffsetMessage;
36  import org.orekit.files.rinex.navigation.parsers.ephemeris.BeidouCnv123Parser;
37  import org.orekit.files.rinex.navigation.parsers.ephemeris.BeidouD1D2Parser;
38  import org.orekit.files.rinex.navigation.parsers.ephemeris.GPSCnavParser;
39  import org.orekit.files.rinex.navigation.parsers.ephemeris.GPSLnavParser;
40  import org.orekit.files.rinex.navigation.parsers.ephemeris.GalileoParser;
41  import org.orekit.files.rinex.navigation.parsers.ephemeris.GlonassCdmaParser;
42  import org.orekit.files.rinex.navigation.parsers.ephemeris.GlonassFdmaParser;
43  import org.orekit.files.rinex.navigation.parsers.ephemeris.NavICL1NvParser;
44  import org.orekit.files.rinex.navigation.parsers.ephemeris.NavICLnavParser;
45  import org.orekit.files.rinex.navigation.parsers.ephemeris.QzssCnavParser;
46  import org.orekit.files.rinex.navigation.parsers.ephemeris.QzssLnavParser;
47  import org.orekit.files.rinex.navigation.parsers.ephemeris.SbasParser;
48  import org.orekit.files.rinex.navigation.parsers.ionosphere.BdgimParser;
49  import org.orekit.files.rinex.navigation.parsers.ionosphere.GlonassCdmsGParser;
50  import org.orekit.files.rinex.navigation.parsers.ionosphere.KlobucharParser;
51  import org.orekit.files.rinex.navigation.parsers.ionosphere.NavICKlobucharParser;
52  import org.orekit.files.rinex.navigation.parsers.ionosphere.NavICNeQuickNParser;
53  import org.orekit.files.rinex.navigation.parsers.ionosphere.NeQuickGParser;
54  import org.orekit.files.rinex.utils.ParsingUtils;
55  import org.orekit.frames.Frame;
56  import org.orekit.gnss.SatelliteSystem;
57  import org.orekit.propagation.analytical.gnss.data.BeidouCivilianNavigationMessageFactory;
58  import org.orekit.propagation.analytical.gnss.data.BeidouCivilianType;
59  import org.orekit.propagation.analytical.gnss.data.BeidouLegacyNavigationMessage;
60  import org.orekit.propagation.analytical.gnss.data.BeidouLegacyNavigationMessageFactory;
61  import org.orekit.propagation.analytical.gnss.data.BeidouSatelliteType;
62  import org.orekit.propagation.analytical.gnss.data.GLONASSFdmaNavigationMessage;
63  import org.orekit.propagation.analytical.gnss.data.GPSCivilianNavigationMessage;
64  import org.orekit.propagation.analytical.gnss.data.GPSCivilianNavigationMessageFactory;
65  import org.orekit.propagation.analytical.gnss.data.GPSLegacyNavigationMessage;
66  import org.orekit.propagation.analytical.gnss.data.GPSLegacyNavigationMessageFactory;
67  import org.orekit.propagation.analytical.gnss.data.GalileoNavigationMessage;
68  import org.orekit.propagation.analytical.gnss.data.GalileoNavigationMessageFactory;
69  import org.orekit.propagation.analytical.gnss.data.NavICL1NvNavigationMessage;
70  import org.orekit.propagation.analytical.gnss.data.NavICL1NvNavigationMessageFactory;
71  import org.orekit.propagation.analytical.gnss.data.NavICLegacyNavigationMessage;
72  import org.orekit.propagation.analytical.gnss.data.NavICLegacyNavigationMessageFactory;
73  import org.orekit.propagation.analytical.gnss.data.QZSSCivilianNavigationMessage;
74  import org.orekit.propagation.analytical.gnss.data.QZSSCivilianNavigationMessageFactory;
75  import org.orekit.propagation.analytical.gnss.data.QZSSLegacyNavigationMessage;
76  import org.orekit.propagation.analytical.gnss.data.QZSSLegacyNavigationMessageFactory;
77  import org.orekit.propagation.analytical.gnss.data.SBASNavigationMessage;
78  import org.orekit.time.AbsoluteDate;
79  import org.orekit.time.TimeScale;
80  import org.orekit.time.TimeScales;
81  import org.orekit.utils.units.Unit;
82  
83  /** Container for parsing data.
84   * @author Bryan Cazabonne
85   * @author Luc Maisonobe
86   * @since 14.0
87   */
88  public class ParseInfo {
89  
90      /** Index of field 1 (not counting variable initial spaces). */
91      private static  final int INDEX_1 = 0;
92  
93      /** Index of field 2 (not counting variable initial spaces). */
94      private static  final int INDEX_2 = 19;
95  
96      /** Index of field 3 (not counting variable initial spaces). */
97      private static  final int INDEX_3 = 38;
98  
99      /** Index of field 4 (not counting variable initial spaces). */
100     private static  final int INDEX_4 = 57;
101 
102     /** Field length. */
103     private static  final int LENGTH = 19;
104 
105     /** Name of the data source. */
106     private final String name;
107 
108     /** Set of time scales for parsing dates. */
109     private final TimeScales timeScales;
110 
111     /** Reference inertial frame.
112      * @since 14.0
113      */
114     private final Frame inertial;
115 
116     /** Body fixed frame.
117      * @since 14.0
118      */
119     private final Frame bodyFixed;
120 
121     /** The corresponding navigation messages file object. */
122     private final RinexNavigation file;
123 
124     /** Number of initial spaces in messages lines. */
125     private int initialSpaces;
126 
127     /** Flag indicating header has been completely parsed. */
128     private boolean headerParsed;
129 
130     /** Ionospheric correction type. */
131     private IonosphericCorrectionType ionosphericCorrectionType;
132 
133     /** Ionospheric correction time mark. */
134     private char timeMark;
135 
136     /** Klobuchar α coefficients. */
137     private double[] klobucharAlpha;
138 
139     /** Klobuchar β coefficients. */
140     private double[] klobucharBeta;
141 
142     /** Record line parser. */
143     private RecordLineParser recordLineParser;
144 
145     /** Current line. */
146     private String line;
147 
148     /** Current global line number. */
149     private int lineNumber;
150 
151     /** Current line number within the navigation message. */
152     private int recordLineNumber;
153 
154     /** Constructor, build the ParseInfo object.
155      * @param name       name of the data source
156      * @param timeScales set of time scales for parsing dates
157      * @param inertial   reference inertial frame
158      * @param bodyFixed  body fixed frame
159      */
160     public ParseInfo(final String name, final TimeScales timeScales,
161                      final Frame inertial, final Frame bodyFixed) {
162         // Initialize default values for fields
163         this.name       = name;
164         this.timeScales = timeScales;
165         this.inertial    = inertial;
166         this.bodyFixed  = bodyFixed;
167         this.file       = new RinexNavigation();
168 
169         // reset the default values set by header constructor
170         this.file.getHeader().setProgramName(null);
171         this.file.getHeader().setRunByName(null);
172         this.file.getHeader().setCreationDateComponents(null);
173 
174     }
175 
176     /** Parse a date.
177      * @param system satellite system
178      * @return parsed date
179      * @since 14.0
180      */
181     public AbsoluteDate parseDate(final SatelliteSystem system) {
182         return parseDate(system.getObservationTimeScale().getTimeScale(timeScales));
183     }
184 
185     /** Parse a date.
186      * @param timeScale time scale
187      * @return parsed date
188      * @since 14.0
189      */
190     public AbsoluteDate parseDate(final TimeScale timeScale) {
191         final int year  = ParsingUtils.parseInt(line, 4, 4);
192         final int month = ParsingUtils.parseInt(line, 9, 2);
193         final int day   = ParsingUtils.parseInt(line, 12, 2);
194         final int hours = ParsingUtils.parseInt(line, 15, 2);
195         final int min   = ParsingUtils.parseInt(line, 18, 2);
196         final int sec   = ParsingUtils.parseInt(line, 21, 2);
197         return new AbsoluteDate(year, month, day, hours, min, sec, timeScale);
198     }
199 
200     /** Parse field 1 of a message line.
201      * @param unit unit to apply
202      * @return parsed field
203      * @since 14.0
204      */
205     public double parseDouble1(final Unit unit) {
206         return parseDouble(unit, initialSpaces + INDEX_1);
207     }
208 
209     /** Parse field 1 of a message line.
210      * @return parsed field
211      * @since 14.0
212      */
213     public int parseInt1() {
214         return parseInt(initialSpaces + INDEX_1);
215     }
216 
217     /** Parse field 2 of a message line.
218      * @param unit unit to apply
219      * @return parsed field
220      * @since 14.0
221      */
222     public double parseDouble2(final Unit unit) {
223         return parseDouble(unit, initialSpaces + INDEX_2);
224     }
225 
226     /** Parse field 2 of a message line.
227      * @return parsed field
228      * @since 14.0
229      */
230     public int parseInt2() {
231         return parseInt(initialSpaces + INDEX_2);
232     }
233 
234     /** Parse field 3 of a message line.
235      * @param unit unit to apply
236      * @return parsed field
237      * @since 14.0
238      */
239     public double parseDouble3(final Unit unit) {
240         return parseDouble(unit, initialSpaces + INDEX_3);
241     }
242 
243     /** Parse field 3 of a message line.
244      * @return parsed field
245      * @since 14.0
246      */
247     public int parseInt3() {
248         return parseInt(initialSpaces + INDEX_3);
249     }
250 
251     /** Parse field 4 of a message line.
252      * @param unit unit to apply
253      * @return parsed field
254      * @since 14.0
255      */
256     public double parseDouble4(final Unit unit) {
257         return parseDouble(unit, initialSpaces + INDEX_4);
258     }
259 
260     /** Parse field 4 of a message line.
261      * @return parsed field
262      * @since 14.0
263      */
264     public int parseInt4() {
265         return parseInt(initialSpaces + INDEX_4);
266     }
267 
268     /** Parse raw field n of a message line.
269      * @param index index of first field character
270      * @return parsed field
271      */
272     private double rawDouble(final int index) {
273         return ParsingUtils.parseDouble(line, index, LENGTH);
274     }
275 
276     /** Parse field n of a message line.
277      * @param unit unit to apply
278      * @param index index of first field character
279      * @return parsed field
280      */
281     private double parseDouble(final Unit unit, final int index) {
282         return unit.toSI(rawDouble(index));
283     }
284 
285     /** Parse field n of a message line.
286      * @param index index of first field character
287      * @return parsed field
288      */
289     private int parseInt(final int index) {
290         return (int) FastMath.rint(rawDouble(index));
291     }
292 
293     /** Parse a comment.
294      */
295     public void parseComment() {
296         ParsingUtils.parseComment(lineNumber, line, file);
297     }
298 
299     /** Ensure navigation record has been closed.
300      */
301     public void closePendingRecord() {
302         if (recordLineParser != null) {
303             recordLineParser.closeRecord(file);
304             recordLineParser = null;
305         }
306     }
307 
308     /** Get the file name.
309      * @return file name
310      */
311     public String getName() {
312         return name;
313     }
314 
315     /** Get the time scales.
316      * @return time scales
317      */
318     public TimeScales getTimeScales() {
319         return timeScales;
320     }
321 
322     /** Get the completed file.
323      * @return completed file
324      */
325     public RinexNavigation getCompletedFile() {
326 
327         // check the header has been properly parsed
328         if (!headerParsed) {
329             throw new OrekitException(OrekitMessages.UNEXPECTED_END_OF_FILE, name);
330         }
331 
332         // close the last message
333         closePendingRecord();
334 
335         return file;
336 
337     }
338 
339     /** Get the navigation file header.
340      * @return navigation file header
341      */
342     public RinexNavigationHeader getHeader() {
343         return file.getHeader();
344     }
345 
346     /** Set the header parsing indicator.
347      * @param headerParsed if true, header has been parsed
348      */
349     public void setHeaderParsed(final boolean headerParsed) {
350         this.headerParsed = headerParsed;
351     }
352 
353     /** Set the number of initial spaces in messages lines.
354      * @param initialSpaces number of initial spaces in messages lines
355      */
356     public void setInitialSpaces(final int initialSpaces) {
357         this.initialSpaces = initialSpaces;
358     }
359 
360     /** Get the current line.
361      * @return current line
362      */
363     public String getLine() {
364         return line;
365     }
366 
367     /** set the current line.
368      * @param line current line
369      */
370     public void setLine(final String line) {
371         this.line = line;
372         ++lineNumber;
373     }
374 
375     /** Get the line number.
376      * @return line number
377      */
378     public int getLineNumber() {
379         return lineNumber;
380     }
381 
382     /** Get the line number within the navigation record.
383      * @return line number within the navigation record
384      */
385     public int getRecordLineNumber() {
386         return recordLineNumber;
387     }
388 
389     /** Set the record line parser.
390      * @param recordType retord type
391      */
392     public void setRecordLineParser(final RecordType recordType) {
393         final SatelliteSystem system  = SatelliteSystem.parseSatelliteSystem(ParsingUtils.parseString(line, 6, 1));
394         final int             prn     = ParsingUtils.parseInt(line, 7, 2);
395         final String          type    = ParsingUtils.parseString(line, 10, 4);
396         final String          subtype = ParsingUtils.parseString(line, 15, 4);
397         setRecordLineParser(recordType, system, prn, type, subtype);
398     }
399 
400     /** Set the record line parser.
401      * @param recordType retord type
402      * @param system satellite system
403      * @param prn satellite number
404      * @param messageType message type
405      * @param subType subtype
406      */
407     public void setRecordLineParser(final RecordType recordType,
408                                     final SatelliteSystem system, final int prn,
409                                     final String messageType, final String subType) {
410 
411         // Set the line number to 0
412         recordLineNumber = 0;
413 
414         closePendingRecord();
415 
416         recordLineParser = null;
417         switch (recordType) {
418             case STO:
419                 recordLineParser = buildStoRecordLineParser(system, prn, messageType, subType);
420                 break;
421             case EOP:
422                 recordLineParser = buildEopRecordLineParser(system, prn, messageType, subType);
423                 break;
424             case ION:
425                 recordLineParser = buildIonRecordLineParser(system, prn, messageType, subType);
426                 break;
427             case ORBIT :
428                 recordLineParser = buildEphRecordLineParser(system, messageType);
429                 break;
430             default :
431                 // do nothing, handle error after the switch
432         }
433 
434         if (recordLineParser == null) {
435             throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
436                                       lineNumber, name, line);
437         }
438 
439     }
440 
441     /** Build a line parser for system time offset record.
442      * @param system satellite system
443      * @param prn satellite number
444      * @param messageType message type
445      * @param subType subtype
446      * @return built record container
447      */
448     private RecordLineParser buildStoRecordLineParser(final SatelliteSystem system, final int prn,
449                                                       final String messageType, final String subType) {
450         return new SystemTimeOffsetParser(this, new SystemTimeOffsetMessage(system, prn,
451                                                                             messageType, subType));
452     }
453 
454     /** Build a line parser for Earth Orientation Parameter record.
455      * @param system satellite system
456      * @param prn satellite number
457      * @param messageType message type
458      * @param subType subtype
459      * @return built record container
460      */
461     private RecordLineParser buildEopRecordLineParser(final SatelliteSystem system, final int prn,
462                                                       final String messageType, final String subType) {
463         return new EarthOrientationParameterParser(this, new EarthOrientationParameterMessage(system, prn,
464                                                                                               messageType, subType));
465     }
466 
467     /** Build a line parser for ionosphere record.
468      * @param system satellite system
469      * @param prn satellite number
470      * @param messageType message type
471      * @param subType subtype
472      * @return built record container
473      */
474     private RecordLineParser buildIonRecordLineParser(final SatelliteSystem system, final int prn,
475                                                       final String messageType, final String subType) {
476         if (system == SatelliteSystem.GALILEO) {
477             return new NeQuickGParser(this, new IonosphereNequickGMessage(system, prn, messageType, subType));
478         } else if (system == SatelliteSystem.BEIDOU && "CNVX".equals(messageType)) {
479             // in Rinex 4.00, tables A32 and A34 (A35 and A37 in Rinex 4.02) are ambiguous
480             // as both seem to apply to Beidou CNVX messages; we consider BDGIM is the
481             // proper model in this case
482             return new BdgimParser(this, new IonosphereBDGIMMessage(system, prn, messageType, subType));
483         } else if (system == SatelliteSystem.NAVIC &&
484                    NavICL1NvNavigationMessage.L1NV.equals(messageType) &&
485                    "KLOB".equals(subType)) {
486             return new NavICKlobucharParser(this, new IonosphereNavICKlobucharMessage(system, prn, messageType, subType));
487         } else if (system == SatelliteSystem.NAVIC &&
488                    NavICL1NvNavigationMessage.L1NV.equals(messageType) &&
489                   "NEQN".equals(subType)) {
490             return new NavICNeQuickNParser(this, new IonosphereNavICNeQuickNMessage(system, prn, messageType, subType));
491         } else if (system == SatelliteSystem.GLONASS) {
492             return new GlonassCdmsGParser(this, new IonosphereGlonassCdmsMessage(system, prn, messageType, subType));
493         } else  {
494             return new KlobucharParser(this, new IonosphereKlobucharMessage(system, prn, messageType, subType));
495         }
496     }
497 
498     /** Build a line parser for ephemeris records.
499      * @param system satellite system
500      * @param messageType message type
501      * @return record parser for ephemeris message
502      */
503     private RecordLineParser buildEphRecordLineParser(final SatelliteSystem system, final String messageType) {
504         switch (system) {
505             case GPS:
506                 if (messageType == null || messageType.equals(GPSLegacyNavigationMessage.LNAV)) {
507                     // in Rinex, week number is aligned to GPS week!
508                     return new GPSLnavParser(this,
509                                              new GPSLegacyNavigationMessageFactory(timeScales,
510                                                                                    SatelliteSystem.GPS,
511                                                                                    GPSLegacyNavigationMessage.LNAV,
512                                                                                    inertial, bodyFixed));
513                 } else if (messageType.equals(GPSCivilianNavigationMessage.CNAV)) {
514                     // in Rinex, week number is aligned to GPS week!
515                     return new GPSCnavParser(this,
516                                              new GPSCivilianNavigationMessageFactory(timeScales,
517                                                                                      SatelliteSystem.GPS,
518                                                                                      GPSCivilianNavigationMessage.CNAV,
519                                                                                      inertial, bodyFixed, false));
520                 } else if (messageType.equals(GPSCivilianNavigationMessage.CNV2)) {
521                     // in Rinex, week number is aligned to GPS week!
522                     return new GPSCnavParser(this,
523                                              new GPSCivilianNavigationMessageFactory(timeScales,
524                                                                                      SatelliteSystem.GPS,
525                                                                                      GPSCivilianNavigationMessage.CNV2,
526                                                                                      inertial, bodyFixed, true));
527                 }
528                 break;
529             case GALILEO:
530                 if (messageType == null || messageType.equals(GalileoNavigationMessage.INAV) || messageType.equals(
531                     GalileoNavigationMessage.FNAV)) {
532                     // in Rinex, week number is aligned to GPS week!
533                     return new GalileoParser(this, new GalileoNavigationMessageFactory(timeScales,
534                                                                                        SatelliteSystem.GPS,
535                                                                                        messageType,
536                                                                                        inertial, bodyFixed));
537                 }
538                 break;
539             case GLONASS:
540                 if (messageType == null || messageType.equals("FDMA")) {
541                     return new GlonassFdmaParser(this, new GLONASSFdmaNavigationMessage());
542                 } else if (messageType.equals("L1OC") || messageType.equals("L3OC")) {
543                     return new GlonassCdmaParser(this);
544                 }
545                 break;
546             case QZSS:
547                 if (messageType == null || messageType.equals(QZSSLegacyNavigationMessage.LNAV)) {
548                     // in Rinex, week number is aligned to GPS week!
549                     return new QzssLnavParser(this,
550                                               new QZSSLegacyNavigationMessageFactory(timeScales,
551                                                                                      SatelliteSystem.GPS,
552                                                                                      QZSSLegacyNavigationMessage.LNAV,
553                                                                                      inertial, bodyFixed));
554                 } else if (messageType.equals(QZSSCivilianNavigationMessage.CNAV)) {
555                     // in Rinex, week number is aligned to GPS week!
556                     return new QzssCnavParser(this,
557                                               new QZSSCivilianNavigationMessageFactory(timeScales,
558                                                                                        SatelliteSystem.GPS,
559                                                                                        QZSSCivilianNavigationMessage.CNAV,
560                                                                                        inertial, bodyFixed, false));
561                 } else if (messageType.equals(QZSSCivilianNavigationMessage.CNV2)) {
562                     // in Rinex, week number is aligned to GPS week!
563                     return new QzssCnavParser(this,
564                                               new QZSSCivilianNavigationMessageFactory(timeScales,
565                                                                                        SatelliteSystem.GPS,
566                                                                                        QZSSCivilianNavigationMessage.CNV2,
567                                                                                        inertial, bodyFixed,
568                                                                                        true));
569                 }
570                 break;
571             case BEIDOU:
572                 if (messageType == null ||
573                     messageType.equals(BeidouLegacyNavigationMessage.D1) ||
574                     messageType.equals(BeidouLegacyNavigationMessage.D2)) {
575                     // in Rinex, week number for Beidou is really aligned to Beidou week!
576                     final boolean d2 = BeidouLegacyNavigationMessage.D2.equals(messageType);
577                     return new BeidouD1D2Parser(this,
578                                                 new BeidouLegacyNavigationMessageFactory(timeScales,
579                                                                                          SatelliteSystem.BEIDOU,
580                                                                                          messageType,
581                                                                                          inertial, bodyFixed,
582                                                                                          d2));
583                 } else {
584                     // in Rinex, week number for Beidou is really aligned to Beidou week!
585                     try {
586                         return new BeidouCnv123Parser(this,
587                                                       new BeidouCivilianNavigationMessageFactory(timeScales,
588                                                                                                  SatelliteSystem.BEIDOU,
589                                                                                                  messageType,
590                                                                                                  inertial, bodyFixed,
591                                                                                                  BeidouCivilianType.valueOf(messageType),
592                                                                                                  BeidouSatelliteType.GEO));
593                     } catch (IllegalArgumentException iae) {
594                         throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
595                                                   lineNumber, name, line);
596                     }
597                 }
598             case NAVIC:
599                 if (messageType == null || messageType.equals(NavICLegacyNavigationMessage.LNAV)) {
600                     // in Rinex, week number is aligned to GPS week!
601                     return new NavICLnavParser(this,
602                                                new NavICLegacyNavigationMessageFactory(timeScales,
603                                                                                        SatelliteSystem.GPS,
604                                                                                        NavICLegacyNavigationMessage.LNAV,
605                                                                                        inertial, bodyFixed));
606                 } else if (messageType.equals(NavICL1NvNavigationMessage.L1NV)) {
607                     // in Rinex, week number is aligned to GPS week!
608                     return new NavICL1NvParser(this,
609                                                new NavICL1NvNavigationMessageFactory(timeScales,
610                                                                                      SatelliteSystem.GPS,
611                                                                                      NavICL1NvNavigationMessage.L1NV,
612                                                                                      inertial, bodyFixed));
613                 }
614                 break;
615             case SBAS:
616                 if (messageType == null || messageType.equals("SBAS")) {
617                     return new SbasParser(this, new SBASNavigationMessage());
618                 }
619                 break;
620             default:
621                 // do nothing, handle error after the switch
622         }
623 
624         // no parse could be set up
625         return null;
626 
627     }
628 
629     /** Get the message line parser.
630      * @return message line parser
631      */
632     public RecordLineParser getRecordLineParser() {
633         return recordLineParser;
634     }
635 
636     /** Parse next record line.
637      */
638     public void parseRecordLine() {
639         switch (++recordLineNumber) {
640             case 1: recordLineParser.parseLine01();
641             break;
642             case 2: recordLineParser.parseLine02();
643             break;
644             case 3: recordLineParser.parseLine03();
645             break;
646             case 4: recordLineParser.parseLine04();
647             break;
648             case 5: recordLineParser.parseLine05();
649             break;
650             case 6: recordLineParser.parseLine06();
651             break;
652             case 7: recordLineParser.parseLine07();
653             break;
654             case 8: recordLineParser.parseLine08();
655             break;
656             case 9: recordLineParser.parseLine09();
657             break;
658             default:
659                 // this should never happen
660                 throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
661                                           lineNumber, name, line);
662         }
663     }
664 
665     /**
666      * Set the ionospheric correction type.
667      * @param ionosphericCorrectionType ionospheric correction type
668      * @since 14.0
669      */
670     public void setIonosphericCorrectionType(final IonosphericCorrectionType ionosphericCorrectionType) {
671         this.ionosphericCorrectionType = ionosphericCorrectionType;
672     }
673 
674     /**
675      * Set the ionospheric correction time mark.
676      * @param timeMark ionospheric correction time mark
677      * @since 14.0
678      */
679     public void setTimeMark(final char timeMark) {
680         this.timeMark = timeMark;
681     }
682 
683     /**
684      * Set the α ionospheric parameters.
685      * @param klobucharAlpha the α ionospheric parameters to set
686      */
687     public void setKlobucharAlpha(final double[] klobucharAlpha) {
688         this.klobucharAlpha = klobucharAlpha.clone();
689         setKlobucharIfComplete();
690     }
691 
692     /**
693      * Set the β ionospheric parameters.
694      * @param klobucharBeta the β ionospheric parameters to set
695      */
696     public void setKlobucharBeta(final double[] klobucharBeta) {
697         this.klobucharBeta = klobucharBeta.clone();
698         setKlobucharIfComplete();
699     }
700 
701     /** Add the Klobuchar correction if it is complete.
702      */
703     private void setKlobucharIfComplete() {
704         if (klobucharAlpha != null && klobucharBeta != null) {
705             file.getHeader().addIonosphericCorrection(new KlobucharIonosphericCorrection(ionosphericCorrectionType,
706                                                                                          timeMark,
707                                                                                          klobucharAlpha,
708                                                                                          klobucharBeta));
709             ionosphericCorrectionType = null;
710             timeMark                  = 0;
711             klobucharAlpha            = null;
712             klobucharBeta             = null;
713         }
714     }
715 
716     /**
717      * Set the α ionospheric parameters.
718      * @param neQuickAlpha the α ionospheric parameters to set
719      */
720     public void setNeQuickAlpha(final double[] neQuickAlpha) {
721         file.getHeader().addIonosphericCorrection(new NeQuickGIonosphericCorrection(ionosphericCorrectionType,
722                                                                                     timeMark,
723                                                                                     neQuickAlpha));
724         ionosphericCorrectionType = null;
725         timeMark                  = 0;
726     }
727 
728 }