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