1   /* Copyright 2002-2022 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;
18  import java.io.BufferedInputStream;
19  import java.io.BufferedReader;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.InputStreamReader;
23  import java.nio.charset.StandardCharsets;
24  import java.util.ArrayList;
25  import java.util.Collections;
26  import java.util.HashMap;
27  import java.util.List;
28  import java.util.Map;
29  
30  import org.hipparchus.exception.DummyLocalizable;
31  import org.hipparchus.geometry.euclidean.threed.Vector3D;
32  import org.hipparchus.geometry.euclidean.twod.Vector2D;
33  import org.hipparchus.util.FastMath;
34  import org.orekit.annotation.DefaultDataContext;
35  import org.orekit.data.DataContext;
36  import org.orekit.data.DataLoader;
37  import org.orekit.data.DataProvidersManager;
38  import org.orekit.data.DataSource;
39  import org.orekit.errors.OrekitException;
40  import org.orekit.errors.OrekitMessages;
41  import org.orekit.time.AbsoluteDate;
42  import org.orekit.time.TimeScale;
43  import org.orekit.time.TimeScales;
44  
45  /** Loader for Rinex measurements files.
46   * <p>
47   * Supported versions are: 2.00, 2.10, 2.11, 2.12 (unofficial), 2.20 (unofficial),
48   * 3.00, 3.01, 3.02, 3.03, and 3.04.
49   * </p>
50   * @see <a href="ftp://igs.org/pub/data/format/rinex2.txt">rinex 2.0</a>
51   * @see <a href="ftp://igs.org/pub/data/format/rinex210.txt">rinex 2.10</a>
52   * @see <a href="ftp://igs.org/pub/data/format/rinex211.txt">rinex 2.11</a>
53   * @see <a href="http://www.aiub.unibe.ch/download/rinex/rinex212.txt">unofficial rinex 2.12</a>
54   * @see <a href="http://www.aiub.unibe.ch/download/rinex/rnx_leo.txt">unofficial rinex 2.20</a>
55   * @see <a href="ftp://igs.org/pub/data/format/rinex300.pdf">rinex 3.00</a>
56   * @see <a href="ftp://igs.org/pub/data/format/rinex301.pdf">rinex 3.01</a>
57   * @see <a href="ftp://igs.org/pub/data/format/rinex302.pdf">rinex 3.02</a>
58   * @see <a href="ftp://igs.org/pub/data/format/rinex303.pdf">rinex 3.03</a>
59   * @see <a href="ftp://igs.org/pub/data/format/rinex304.pdf">rinex 3.04</a>
60   * @since 9.2
61   */
62  public class RinexObservationLoader {
63  
64      /** Default supported files name pattern for rinex 2 observation files. */
65      public static final String DEFAULT_RINEX_2_SUPPORTED_NAMES = "^\\w{4}\\d{3}[0a-x](?:\\d{2})?\\.\\d{2}[oO]$";
66  
67      /** Default supported files name pattern for rinex 3 observation files. */
68      public static final String DEFAULT_RINEX_3_SUPPORTED_NAMES = "^\\w{9}_\\w{1}_\\d{11}_\\d{2}\\w_\\d{2}\\w{1}_\\w{2}\\.rnx$";
69  
70      // CHECKSTYLE: stop JavadocVariable check
71      private static final String RINEX_VERSION_TYPE   = "RINEX VERSION / TYPE";
72      private static final String COMMENT              = "COMMENT";
73      private static final String PGM_RUN_BY_DATE      = "PGM / RUN BY / DATE";
74      private static final String MARKER_NAME          = "MARKER NAME";
75      private static final String MARKER_NUMBER        = "MARKER NUMBER";
76      private static final String MARKER_TYPE          = "MARKER TYPE";
77      private static final String OBSERVER_AGENCY      = "OBSERVER / AGENCY";
78      private static final String REC_NB_TYPE_VERS     = "REC # / TYPE / VERS";
79      private static final String ANT_NB_TYPE          = "ANT # / TYPE";
80      private static final String APPROX_POSITION_XYZ  = "APPROX POSITION XYZ";
81      private static final String ANTENNA_DELTA_H_E_N  = "ANTENNA: DELTA H/E/N";
82      private static final String ANTENNA_DELTA_X_Y_Z  = "ANTENNA: DELTA X/Y/Z";
83      private static final String ANTENNA_PHASECENTER  = "ANTENNA: PHASECENTER";
84      private static final String ANTENNA_B_SIGHT_XYZ  = "ANTENNA: B.SIGHT XYZ";
85      private static final String ANTENNA_ZERODIR_AZI  = "ANTENNA: ZERODIR AZI";
86      private static final String ANTENNA_ZERODIR_XYZ  = "ANTENNA: ZERODIR XYZ";
87      private static final String NB_OF_SATELLITES     = "# OF SATELLITES";
88      private static final String WAVELENGTH_FACT_L1_2 = "WAVELENGTH FACT L1/2";
89      private static final String RCV_CLOCK_OFFS_APPL  = "RCV CLOCK OFFS APPL";
90      private static final String INTERVAL             = "INTERVAL";
91      private static final String TIME_OF_FIRST_OBS    = "TIME OF FIRST OBS";
92      private static final String TIME_OF_LAST_OBS     = "TIME OF LAST OBS";
93      private static final String LEAP_SECONDS         = "LEAP SECONDS";
94      private static final String PRN_NB_OF_OBS        = "PRN / # OF OBS";
95      private static final String NB_TYPES_OF_OBSERV   = "# / TYPES OF OBSERV";
96      private static final String END_OF_HEADER        = "END OF HEADER";
97      private static final String CENTER_OF_MASS_XYZ   = "CENTER OF MASS: XYZ";
98      private static final String SIGNAL_STRENGTH_UNIT = "SIGNAL STRENGTH UNIT";
99      private static final String SYS_NB_OBS_TYPES     = "SYS / # / OBS TYPES";
100     private static final String SYS_DCBS_APPLIED     = "SYS / DCBS APPLIED";
101     private static final String SYS_PCVS_APPLIED     = "SYS / PCVS APPLIED";
102     private static final String SYS_SCALE_FACTOR     = "SYS / SCALE FACTOR";
103     private static final String SYS_PHASE_SHIFT      = "SYS / PHASE SHIFT";
104     private static final String SYS_PHASE_SHIFTS     = "SYS / PHASE SHIFTS";
105     private static final String GLONASS_SLOT_FRQ_NB  = "GLONASS SLOT / FRQ #";
106     private static final String GLONASS_COD_PHS_BIS  = "GLONASS COD/PHS/BIS";
107     private static final String OBS_SCALE_FACTOR     = "OBS SCALE FACTOR";
108 
109     private static final String GPS                  = "GPS";
110     private static final String GAL                  = "GAL";
111     private static final String GLO                  = "GLO";
112     private static final String QZS                  = "QZS";
113     private static final String BDT                  = "BDT";
114     private static final String IRN                  = "IRN";
115     // CHECKSTYLE: resume JavadocVariable check
116 
117     /** Rinex Observations. */
118     private final List<ObservationDataSet> observationDataSets;
119 
120     /** Set of time scales. */
121     private final TimeScales timeScales;
122 
123     /** Simple constructor.
124      * <p>
125      * This constructor is used when the rinex files are managed by the
126      * global {@link DataContext#getDefault() default data context}.
127      * </p>
128      * @param supportedNames regular expression for supported files names
129      * @see #RinexObservationLoader(String, DataProvidersManager, TimeScales)
130      */
131     @DefaultDataContext
132     public RinexObservationLoader(final String supportedNames) {
133         this(supportedNames, DataContext.getDefault().getDataProvidersManager(),
134                 DataContext.getDefault().getTimeScales());
135     }
136 
137     /**
138      * Create a RINEX loader/parser with the given source of RINEX auxiliary data files.
139      *
140      * <p>
141      * This constructor is used when the rinex files are managed by the given
142      * {@code dataProvidersManager}.
143      * </p>
144      * @param supportedNames regular expression for supported files names
145      * @param dataProvidersManager provides access to auxiliary data.
146      * @param timeScales the set of time scales to use when parsing dates.
147      * @since 10.1
148      */
149     public RinexObservationLoader(final String supportedNames,
150                        final DataProvidersManager dataProvidersManager,
151                        final TimeScales timeScales) {
152         observationDataSets = new ArrayList<>();
153         this.timeScales = timeScales;
154         dataProvidersManager.feed(supportedNames, new Parser());
155     }
156 
157     /** Simple constructor. This constructor uses the {@link DataContext#getDefault()
158      * default data context}.
159      *
160      * @param source source for the RINEX data
161      * @see #RinexObservationLoader(DataSource, TimeScales)
162      */
163     @DefaultDataContext
164     public RinexObservationLoader(final DataSource source) {
165         this(source, DataContext.getDefault().getTimeScales());
166     }
167 
168     /**
169      * Loads RINEX from the given input stream using the specified auxiliary data.
170      *
171      * @param source source for the RINEX data
172      * @param timeScales the set of time scales to use when parsing dates.
173      * @since 10.1
174      */
175     public RinexObservationLoader(final DataSource source, final TimeScales timeScales) {
176         try {
177             this.timeScales = timeScales;
178             observationDataSets = new ArrayList<>();
179             try (InputStream         is  = source.getOpener().openStreamOnce();
180                  BufferedInputStream bis = new BufferedInputStream(is)) {
181                 new Parser().loadData(bis, source.getName());
182             }
183         } catch (IOException ioe) {
184             throw new OrekitException(ioe, new DummyLocalizable(ioe.getMessage()));
185         }
186     }
187 
188     /** Get parsed rinex observations data sets.
189      * @return unmodifiable view of parsed rinex observations
190      * @since 9.3
191      */
192     public List<ObservationDataSet> getObservationDataSets() {
193         return Collections.unmodifiableList(observationDataSets);
194     }
195 
196     /** Parser for rinex files.
197      */
198     public class Parser implements DataLoader {
199 
200         /** Index of label in data lines. */
201         private static final int LABEL_START = 60;
202 
203         /** File type accepted (only Observation Data). */
204         private static final String FILE_TYPE = "O"; //Only Observation Data files
205 
206         /** Name of the file. */
207         private String name;
208 
209         /** Current line. */
210         private String line;
211 
212         /** current line number. */
213         private int lineNumber;
214 
215         /** {@inheritDoc} */
216         @Override
217         public boolean stillAcceptsData() {
218             // we load all rinex files we can find
219             return true;
220         }
221 
222         /** {@inheritDoc} */
223         @Override
224         public void loadData(final InputStream input, final String fileName)
225             throws IOException, OrekitException {
226 
227             try (BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {
228 
229                 this.name       = fileName;
230                 this.line       = null;
231                 this.lineNumber = 0;
232 
233                 // placeholders for parsed data
234                 SatelliteSystem                  satelliteSystem        = null;
235                 double                           formatVersion          = Double.NaN;
236                 boolean                          inRinexVersion         = false;
237                 SatelliteSystem                  obsTypesSystem         = null;
238                 String                           markerName             = null;
239                 String                           markerNumber           = null;
240                 String                           markerType             = null;
241                 String                           observerName           = null;
242                 String                           agencyName             = null;
243                 String                           receiverNumber         = null;
244                 String                           receiverType           = null;
245                 String                           receiverVersion        = null;
246                 String                           antennaNumber          = null;
247                 String                           antennaType            = null;
248                 Vector3D                         approxPos              = null;
249                 Vector3D                         antRefPoint            = null;
250                 String                           obsCode                = null;
251                 Vector3D                         antPhaseCenter         = null;
252                 Vector3D                         antBSight              = null;
253                 double                           antAzi                 = Double.NaN;
254                 Vector3D                         antZeroDir             = null;
255                 Vector3D                         centerMass             = null;
256                 double                           antHeight              = Double.NaN;
257                 Vector2D                         eccentricities         = Vector2D.ZERO;
258                 int                              clkOffset              = -1;
259                 int                              nbTypes                = -1;
260                 int                              nbSat                  = -1;
261                 double                           interval               = Double.NaN;
262                 AbsoluteDate                     tFirstObs              = AbsoluteDate.PAST_INFINITY;
263                 AbsoluteDate                     tLastObs               = AbsoluteDate.FUTURE_INFINITY;
264                 TimeScale                        timeScale              = null;
265                 String                           timeScaleStr           = null;
266                 int                              leapSeconds            = 0;
267                 AbsoluteDate                     tObs                   = AbsoluteDate.PAST_INFINITY;
268                 String[]                         satsObsList            = null;
269                 int                              eventFlag              = -1;
270                 int                              nbSatObs               = -1;
271                 int                              nbLinesSat             = -1;
272                 double                           rcvrClkOffset          = 0;
273                 boolean                          inMarkerName           = false;
274                 boolean                          inObserver             = false;
275                 boolean                          inRecType              = false;
276                 boolean                          inAntType              = false;
277                 boolean                          inAproxPos             = false;
278                 boolean                          inAntDelta             = false;
279                 boolean                          inTypesObs             = false;
280                 boolean                          inFirstObs             = false;
281                 boolean                          inPhaseShift           = false;
282                 RinexObservationHeader                      rinexHeader            = null;
283                 int                               scaleFactor            = 1;
284                 int                               nbObsScaleFactor       = 0;
285                 final List<ScaleFactorCorrection> scaleFactorCorrections = new ArrayList<>();
286                 final Map<SatelliteSystem, List<ObservationType>> listTypeObs = new HashMap<>();
287 
288                 //First line must  always contain Rinex Version, File Type and Satellite Systems Observed
289                 readLine(reader, true);
290                 if (line.length() < LABEL_START || !RINEX_VERSION_TYPE.equals(line.substring(LABEL_START).trim())) {
291                     throw new OrekitException(OrekitMessages.UNSUPPORTED_FILE_FORMAT, name);
292                 }
293                 formatVersion = parseDouble(0, 9);
294                 final int format100 = (int) FastMath.rint(100 * formatVersion);
295 
296                 if (format100 != 200 && format100 != 210 && format100 != 211 &&
297                     format100 != 212 && format100 != 220 && format100 != 300 &&
298                     format100 != 301 && format100 != 302 && format100 != 303 &&
299                     format100 != 304) {
300                     throw new OrekitException(OrekitMessages.UNSUPPORTED_FILE_FORMAT, name);
301                 }
302 
303                 //File Type must be Observation_Data
304                 if (!(parseString(20, 1)).equals(FILE_TYPE)) {
305                     throw new OrekitException(OrekitMessages.UNSUPPORTED_FILE_FORMAT, name);
306                 }
307                 satelliteSystem = SatelliteSystem.parseSatelliteSystem(parseString(40, 1));
308                 inRinexVersion = true;
309 
310                 switch (format100 / 100) {
311                     case 2: {
312 
313                         final int                   MAX_OBS_TYPES_PER_LINE_RNX2 = 9;
314                         final int                   MAX_N_SAT_OBSERVATION       = 12;
315                         final int                   MAX_N_TYPES_OBSERVATION     = 5;
316                         final int                   MAX_OBS_TYPES_SCALE_FACTOR  = 8;
317                         final List<ObservationType> typesObs = new ArrayList<>();
318 
319                         while (readLine(reader, false)) {
320 
321                             if (rinexHeader == null) {
322                                 switch (line.substring(LABEL_START).trim()) {
323                                     case COMMENT :
324                                         // nothing to do
325                                         break;
326                                     case PGM_RUN_BY_DATE :
327                                         // nothing to do
328                                         break;
329                                     case MARKER_NAME :
330                                         markerName = parseString(0, 60);
331                                         inMarkerName = true;
332                                         break;
333                                     case MARKER_NUMBER :
334                                         markerNumber = parseString(0, 20);
335                                         break;
336                                     case MARKER_TYPE :
337                                         markerType = parseString(0, 20);
338                                         break;
339                                     case OBSERVER_AGENCY :
340                                         observerName = parseString(0, 20);
341                                         agencyName   = parseString(20, 40);
342                                         inObserver = true;
343                                         break;
344                                     case REC_NB_TYPE_VERS :
345                                         receiverNumber  = parseString(0, 20);
346                                         receiverType    = parseString(20, 20);
347                                         receiverVersion = parseString(40, 20);
348                                         inRecType = true;
349                                         break;
350                                     case ANT_NB_TYPE :
351                                         antennaNumber = parseString(0, 20);
352                                         antennaType   = parseString(20, 20);
353                                         inAntType = true;
354                                         break;
355                                     case APPROX_POSITION_XYZ :
356                                         approxPos = new Vector3D(parseDouble(0, 14), parseDouble(14, 14),
357                                                                  parseDouble(28, 14));
358                                         inAproxPos = true;
359                                         break;
360                                     case ANTENNA_DELTA_H_E_N :
361                                         antHeight = parseDouble(0, 14);
362                                         eccentricities = new Vector2D(parseDouble(14, 14), parseDouble(28, 14));
363                                         inAntDelta = true;
364                                         break;
365                                     case ANTENNA_DELTA_X_Y_Z :
366                                         antRefPoint = new Vector3D(parseDouble(0, 14),
367                                                                    parseDouble(14, 14),
368                                                                    parseDouble(28, 14));
369                                         break;
370                                     case ANTENNA_B_SIGHT_XYZ :
371                                         antBSight = new Vector3D(parseDouble(0, 14),
372                                                                  parseDouble(14, 14),
373                                                                  parseDouble(28, 14));
374                                         break;
375                                     case CENTER_OF_MASS_XYZ :
376                                         centerMass = new Vector3D(parseDouble(0, 14),
377                                                                   parseDouble(14, 14),
378                                                                   parseDouble(28, 14));
379                                         break;
380                                     case NB_OF_SATELLITES :
381                                         nbSat = parseInt(0, 6);
382                                         break;
383                                     case WAVELENGTH_FACT_L1_2 :
384                                         //Optional line in header
385                                         //Not stored for now
386                                         break;
387                                     case RCV_CLOCK_OFFS_APPL :
388                                         clkOffset = parseInt(0, 6);
389                                         break;
390                                     case INTERVAL :
391                                         interval = parseDouble(0, 10);
392                                         break;
393                                     case TIME_OF_FIRST_OBS :
394                                         switch (satelliteSystem) {
395                                             case GPS:
396                                                 timeScale = timeScales.getGPS();
397                                                 break;
398                                             case GALILEO:
399                                                 timeScale = timeScales.getGST();
400                                                 break;
401                                             case GLONASS:
402                                                 timeScale = timeScales.getGLONASS();
403                                                 break;
404                                             case MIXED:
405                                                 //in Case of Mixed data, Timescale must be specified in the Time of First line
406                                                 timeScaleStr = parseString(48, 3);
407 
408                                                 if (timeScaleStr.equals(GPS)) {
409                                                     timeScale = timeScales.getGPS();
410                                                 } else if (timeScaleStr.equals(GAL)) {
411                                                     timeScale = timeScales.getGST();
412                                                 } else if (timeScaleStr.equals(GLO)) {
413                                                     timeScale = timeScales.getGLONASS();
414                                                 } else {
415                                                     throw new OrekitException(OrekitMessages.UNSUPPORTED_FILE_FORMAT, name);
416                                                 }
417                                                 break;
418                                             default :
419                                                 throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
420                                                                           lineNumber, name, line);
421                                         }
422 
423                                         tFirstObs = new AbsoluteDate(parseInt(0, 6),
424                                                                      parseInt(6, 6),
425                                                                      parseInt(12, 6),
426                                                                      parseInt(18, 6),
427                                                                      parseInt(24, 6),
428                                                                      parseDouble(30, 13), timeScale);
429                                         inFirstObs = true;
430                                         break;
431                                     case TIME_OF_LAST_OBS :
432                                         tLastObs = new AbsoluteDate(parseInt(0, 6),
433                                                                     parseInt(6, 6),
434                                                                     parseInt(12, 6),
435                                                                     parseInt(18, 6),
436                                                                     parseInt(24, 6),
437                                                                     parseDouble(30, 13), timeScale);
438                                         break;
439                                     case LEAP_SECONDS :
440                                         leapSeconds = parseInt(0, 6);
441                                         break;
442                                     case PRN_NB_OF_OBS :
443                                         //Optional line in header, indicates number of Observations par Satellite
444                                         //Not stored for now
445                                         break;
446                                     case NB_TYPES_OF_OBSERV :
447                                         nbTypes = parseInt(0, 6);
448                                         final int nbLinesTypesObs = (nbTypes + MAX_OBS_TYPES_PER_LINE_RNX2 - 1 ) / MAX_OBS_TYPES_PER_LINE_RNX2;
449 
450                                         for (int j = 0; j < nbLinesTypesObs; j++) {
451                                             if (j > 0) {
452                                                 readLine(reader, true);
453                                             }
454                                             final int iMax = FastMath.min(MAX_OBS_TYPES_PER_LINE_RNX2, nbTypes - typesObs.size());
455                                             for (int i = 0; i < iMax; i++) {
456                                                 try {
457                                                     typesObs.add(ObservationType.valueOf(parseString(10 + (6 * i), 2)));
458                                                 } catch (IllegalArgumentException iae) {
459                                                     throw new OrekitException(iae, OrekitMessages.UNKNOWN_RINEX_FREQUENCY,
460                                                                               parseString(10 + (6 * i), 2), name, lineNumber);
461                                                 }
462                                             }
463                                         }
464                                         inTypesObs = true;
465                                         break;
466                                     case OBS_SCALE_FACTOR :
467                                         scaleFactor      = FastMath.max(1, parseInt(0,  6));
468                                         nbObsScaleFactor = parseInt(6, 6);
469                                         if (nbObsScaleFactor > MAX_OBS_TYPES_SCALE_FACTOR) {
470                                             throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
471                                                                       lineNumber, name, line);
472                                         }
473                                         final List<ObservationType> typesObsScaleFactor = new ArrayList<>(nbObsScaleFactor);
474                                         for (int i = 0; i < nbObsScaleFactor; i++) {
475                                             typesObsScaleFactor.add(ObservationType.valueOf(parseString(16 + (6 * i), 2)));
476                                         }
477                                         scaleFactorCorrections.add(new ScaleFactorCorrection(satelliteSystem,
478                                                                                              scaleFactor, typesObsScaleFactor));
479                                         break;
480                                     case END_OF_HEADER :
481                                         //We make sure that we have read all the mandatory fields inside the header of the Rinex
482                                         if (!inRinexVersion || !inMarkerName ||
483                                             !inObserver || !inRecType || !inAntType ||
484                                             formatVersion < 2.20 && !inAproxPos ||
485                                             formatVersion < 2.20 && !inAntDelta ||
486                                             !inTypesObs || !inFirstObs) {
487                                             throw new OrekitException(OrekitMessages.INCOMPLETE_HEADER, name);
488                                         }
489 
490                                         //Header information gathered
491                                         rinexHeader = new RinexObservationHeader(formatVersion, satelliteSystem,
492                                                                       markerName, markerNumber, markerType, observerName,
493                                                                       agencyName, receiverNumber, receiverType,
494                                                                       receiverVersion, antennaNumber, antennaType,
495                                                                       approxPos, antHeight, eccentricities,
496                                                                       antRefPoint, antBSight, centerMass, interval,
497                                                                       tFirstObs, tLastObs, clkOffset, leapSeconds);
498                                         break;
499                                     default :
500                                         if (rinexHeader == null) {
501                                             //There must be an error due to an unknown Label inside the Header
502                                             throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
503                                                                       lineNumber, name, line);
504                                         }
505                                 }
506                             } else {
507 
508                                 //Start of a new Observation
509                                 rcvrClkOffset     =  0;
510                                 nbLinesSat        = -1;
511                                 eventFlag         = -1;
512                                 nbSatObs          = -1;
513                                 satsObsList       = null;
514                                 tObs              = null;
515 
516                                 eventFlag = parseInt(28, 1);
517                                 //If eventFlag>1, we skip the corresponding lines to the next observation
518                                 if (eventFlag > 1) {
519                                     if (eventFlag == 6) {
520                                         nbSatObs  = parseInt(29, 3);
521                                         nbLinesSat = (nbSatObs + 12 - 1) / 12;
522                                         final int nbLinesObs = (nbTypes + 5 - 1) / 5;
523                                         final int nbLinesSkip = (nbLinesSat - 1) + nbSatObs * nbLinesObs;
524                                         for (int i = 0; i < nbLinesSkip; i++) {
525                                             readLine(reader, true);
526                                         }
527                                     } else {
528                                         final int nbLinesSkip = parseInt(29, 3);
529                                         for (int i = 0; i < nbLinesSkip; i++) {
530                                             readLine(reader, true);
531                                         }
532                                     }
533                                 } else {
534 
535                                     int y = parseInt(0, 3);
536                                     if (79 < y && y <= 99) {
537                                         y += 1900;
538                                     } else if (0 <= y && y <= 79) {
539                                         y += 2000;
540                                     }
541                                     tObs = new AbsoluteDate(y,
542                                                             parseInt(3, 3),
543                                                             parseInt(6, 3),
544                                                             parseInt(9, 3),
545                                                             parseInt(12, 3),
546                                                             parseDouble(15, 11), timeScale);
547 
548                                     nbSatObs  = parseInt(29, 3);
549                                     satsObsList   = new String[nbSatObs];
550                                     //If the total number of satellites was indicated in the Header
551                                     if (nbSat != -1 && nbSatObs > nbSat) {
552                                         //we check that the number of Sat in the observation is consistent
553                                         throw new OrekitException(OrekitMessages.INCONSISTENT_NUMBER_OF_SATS,
554                                                                   lineNumber, name, nbSatObs, nbSat);
555                                     }
556 
557                                     nbLinesSat = (nbSatObs + MAX_N_SAT_OBSERVATION - 1) / MAX_N_SAT_OBSERVATION;
558                                     for (int j = 0; j < nbLinesSat; j++) {
559                                         if (j > 0) {
560                                             readLine(reader, true);
561                                         }
562                                         final int iMax = FastMath.min(MAX_N_SAT_OBSERVATION, nbSatObs  - j * MAX_N_SAT_OBSERVATION);
563                                         for (int i = 0; i < iMax; i++) {
564                                             satsObsList[i + MAX_N_SAT_OBSERVATION * j] = parseString(32 + 3 * i, 3);
565                                         }
566 
567                                         //Read the Receiver Clock offset, if present
568                                         rcvrClkOffset = parseDouble(68, 12);
569                                         if (Double.isNaN(rcvrClkOffset)) {
570                                             rcvrClkOffset = 0.0;
571                                         }
572 
573                                     }
574 
575                                     //For each one of the Satellites in this observation
576                                     final int nbLinesObs = (nbTypes + MAX_N_TYPES_OBSERVATION - 1) / MAX_N_TYPES_OBSERVATION;
577                                     for (int k = 0; k < nbSatObs; k++) {
578 
579 
580                                         //Once the Date and Satellites list is read:
581                                         //  - to read the Data for each satellite
582                                         //  - 5 Observations per line
583                                         final List<ObservationData> observationData = new ArrayList<>(nbSatObs);
584                                         for (int j = 0; j < nbLinesObs; j++) {
585                                             readLine(reader, true);
586                                             final int iMax = FastMath.min(MAX_N_TYPES_OBSERVATION, nbTypes - observationData.size());
587                                             for (int i = 0; i < iMax; i++) {
588                                                 final ObservationType type = typesObs.get(observationData.size());
589                                                 double value = parseDouble(16 * i, 14);
590                                                 boolean scaleFactorFound = false;
591                                                 //We look for the lines of ScaledFactorCorrections
592                                                 for (int l = 0; l < scaleFactorCorrections.size() && !scaleFactorFound; ++l) {
593                                                     //We check if the next Observation Type to read needs to be scaled
594                                                     if (scaleFactorCorrections.get(l).getTypesObsScaled().contains(type)) {
595                                                         value /= scaleFactorCorrections.get(l).getCorrection();
596                                                         scaleFactorFound = true;
597                                                     }
598                                                 }
599                                                 observationData.add(new ObservationData(type,
600                                                                                         value,
601                                                                                         parseInt(14 + 16 * i, 1),
602                                                                                         parseInt(15 + 16 * i, 1)));
603                                             }
604                                         }
605 
606                                         //We check that the Satellite type is consistent with Satellite System in the top of the file
607                                         final SatelliteSystem satelliteSystemSat;
608                                         final int id;
609                                         if (satsObsList[k].length() < 3) {
610                                             // missing satellite system, we use the global one
611                                             satelliteSystemSat = satelliteSystem;
612                                             id                 = Integer.parseInt(satsObsList[k]);
613                                         } else {
614                                             satelliteSystemSat = SatelliteSystem.parseSatelliteSystem(satsObsList[k]);
615                                             id                 = Integer.parseInt(satsObsList[k].substring(1, 3).trim());
616                                         }
617                                         if (!satelliteSystem.equals(SatelliteSystem.MIXED)) {
618                                             if (!satelliteSystemSat.equals(satelliteSystem)) {
619                                                 throw new OrekitException(OrekitMessages.INCONSISTENT_SATELLITE_SYSTEM,
620                                                                           lineNumber, name, satelliteSystem, satelliteSystemSat);
621                                             }
622                                         }
623 
624                                         final int prnNumber;
625                                         switch (satelliteSystemSat) {
626                                             case GPS:
627                                             case GLONASS:
628                                             case GALILEO:
629                                                 prnNumber = id;
630                                                 break;
631                                             case SBAS:
632                                                 prnNumber = id + 100;
633                                                 break;
634                                             default:
635                                                 // MIXED satellite system is not allowed here
636                                                 throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
637                                                                           lineNumber, name, line);
638                                         }
639 
640                                         observationDataSets.add(new ObservationDataSet(rinexHeader, satelliteSystemSat, prnNumber,
641                                                                                        tObs, rcvrClkOffset, observationData));
642 
643                                     }
644                                 }
645                             }
646                         }
647                         break;
648                     }
649                     case 3: {
650 
651                         final int                   MAX_OBS_TYPES_PER_LINE_RNX3 = 13;
652                         final int           MAX_OBS_TYPES_SCALE_FACTOR_PER_LINE = 12;
653                         final int                    MAX_N_SAT_PHSHIFT_PER_LINE = 10;
654 
655                         final List<ObservationType>                       typeObs                = new ArrayList<>();
656                         String                                            sigStrengthUnit        = null;
657                         int                                               leapSecondsFuture      = 0;
658                         int                                               leapSecondsWeekNum     = 0;
659                         int                                               leapSecondsDayNum      = 0;
660                         final List<AppliedDCBS>                           listAppliedDCBs        = new ArrayList<>();
661                         final List<AppliedPCVS>                           listAppliedPCVS        = new ArrayList<>();
662                         SatelliteSystem                                   satSystemScaleFactor   = null;
663                         String[]                                          satsPhaseShift         = null;
664                         int                                               nbSatPhaseShift        = 0;
665                         SatelliteSystem                                   satSystemPhaseShift    = null;
666                         double                                            corrPhaseShift         = 0.0;
667                         final List<PhaseShiftCorrection>                  phaseShiftCorrections  = new ArrayList<>();
668                         ObservationType                                   phaseShiftTypeObs      = null;
669 
670 
671                         while (readLine(reader, false)) {
672                             if (rinexHeader == null) {
673                                 switch (line.substring(LABEL_START).trim()) {
674                                     case COMMENT :
675                                         // nothing to do
676                                         break;
677                                     case PGM_RUN_BY_DATE :
678                                         // nothing to do
679                                         break;
680                                     case MARKER_NAME :
681                                         markerName = parseString(0, 60);
682                                         inMarkerName = true;
683                                         break;
684                                     case MARKER_NUMBER :
685                                         markerNumber = parseString(0, 20);
686                                         break;
687                                     case MARKER_TYPE :
688                                         markerType = parseString(0, 20);
689                                         //Could be done with an Enumeration
690                                         break;
691                                     case OBSERVER_AGENCY :
692                                         observerName = parseString(0, 20);
693                                         agencyName   = parseString(20, 40);
694                                         inObserver = true;
695                                         break;
696                                     case REC_NB_TYPE_VERS :
697                                         receiverNumber  = parseString(0, 20);
698                                         receiverType    = parseString(20, 20);
699                                         receiverVersion = parseString(40, 20);
700                                         inRecType = true;
701                                         break;
702                                     case ANT_NB_TYPE :
703                                         antennaNumber = parseString(0, 20);
704                                         antennaType   = parseString(20, 20);
705                                         inAntType = true;
706                                         break;
707                                     case APPROX_POSITION_XYZ :
708                                         approxPos = new Vector3D(parseDouble(0, 14),
709                                                                  parseDouble(14, 14),
710                                                                  parseDouble(28, 14));
711                                         inAproxPos = true;
712                                         break;
713                                     case ANTENNA_DELTA_H_E_N :
714                                         antHeight = parseDouble(0, 14);
715                                         eccentricities = new Vector2D(parseDouble(14, 14),
716                                                                       parseDouble(28, 14));
717                                         inAntDelta = true;
718                                         break;
719                                     case ANTENNA_DELTA_X_Y_Z :
720                                         antRefPoint = new Vector3D(parseDouble(0, 14),
721                                                                    parseDouble(14, 14),
722                                                                    parseDouble(28, 14));
723                                         break;
724                                     case ANTENNA_PHASECENTER :
725                                         obsCode = parseString(2, 3);
726                                         antPhaseCenter = new Vector3D(parseDouble(5, 9),
727                                                                       parseDouble(14, 14),
728                                                                       parseDouble(28, 14));
729                                         break;
730                                     case ANTENNA_B_SIGHT_XYZ :
731                                         antBSight = new Vector3D(parseDouble(0, 14),
732                                                                  parseDouble(14, 14),
733                                                                  parseDouble(28, 14));
734                                         break;
735                                     case ANTENNA_ZERODIR_AZI :
736                                         antAzi = parseDouble(0, 14);
737                                         break;
738                                     case ANTENNA_ZERODIR_XYZ :
739                                         antZeroDir = new Vector3D(parseDouble(0, 14),
740                                                                   parseDouble(14, 14),
741                                                                   parseDouble(28, 14));
742                                         break;
743                                     case CENTER_OF_MASS_XYZ :
744                                         centerMass = new Vector3D(parseDouble(0, 14),
745                                                                   parseDouble(14, 14),
746                                                                   parseDouble(28, 14));
747                                         break;
748                                     case NB_OF_SATELLITES :
749                                         nbSat = parseInt(0, 6);
750                                         break;
751                                     case RCV_CLOCK_OFFS_APPL :
752                                         clkOffset = parseInt(0, 6);
753                                         break;
754                                     case INTERVAL :
755                                         interval = parseDouble(0, 10);
756                                         break;
757                                     case TIME_OF_FIRST_OBS :
758                                         switch (satelliteSystem) {
759                                             case GPS:
760                                                 timeScale = timeScales.getGPS();
761                                                 break;
762                                             case GALILEO:
763                                                 timeScale = timeScales.getGST();
764                                                 break;
765                                             case GLONASS:
766                                                 timeScale = timeScales.getGLONASS();
767                                                 break;
768                                             case QZSS:
769                                                 timeScale = timeScales.getQZSS();
770                                                 break;
771                                             case BEIDOU:
772                                                 timeScale = timeScales.getBDT();
773                                                 break;
774                                             case IRNSS:
775                                                 timeScale = timeScales.getIRNSS();
776                                                 break;
777                                             case MIXED:
778                                                 //in Case of Mixed data, Timescale must be specified in the Time of First line
779                                                 timeScaleStr = parseString(48, 3);
780 
781                                                 if (timeScaleStr.equals(GPS)) {
782                                                     timeScale = timeScales.getGPS();
783                                                 } else if (timeScaleStr.equals(GAL)) {
784                                                     timeScale = timeScales.getGST();
785                                                 } else if (timeScaleStr.equals(GLO)) {
786                                                     timeScale = timeScales.getGLONASS();
787                                                 } else if (timeScaleStr.equals(QZS)) {
788                                                     timeScale = timeScales.getQZSS();
789                                                 } else if (timeScaleStr.equals(BDT)) {
790                                                     timeScale = timeScales.getBDT();
791                                                 } else if (timeScaleStr.equals(IRN)) {
792                                                     timeScale = timeScales.getIRNSS();
793                                                 } else {
794                                                     throw new OrekitException(OrekitMessages.UNSUPPORTED_FILE_FORMAT, name);
795                                                 }
796                                                 break;
797                                             default :
798                                                 throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
799                                                                           lineNumber, name, line);
800                                         }
801 
802                                         tFirstObs = new AbsoluteDate(parseInt(0, 6),
803                                                                      parseInt(6, 6),
804                                                                      parseInt(12, 6),
805                                                                      parseInt(18, 6),
806                                                                      parseInt(24, 6),
807                                                                      parseDouble(30, 13), timeScale);
808                                         inFirstObs = true;
809                                         break;
810                                     case TIME_OF_LAST_OBS :
811                                         tLastObs = new AbsoluteDate(parseInt(0, 6),
812                                                                     parseInt(6, 6),
813                                                                     parseInt(12, 6),
814                                                                     parseInt(18, 6),
815                                                                     parseInt(24, 6),
816                                                                     parseDouble(30, 13), timeScale);
817                                         break;
818                                     case LEAP_SECONDS :
819                                         leapSeconds = parseInt(0, 6);
820                                         leapSecondsFuture = parseInt(6, 6);
821                                         leapSecondsWeekNum = parseInt(12, 6);
822                                         leapSecondsDayNum = parseInt(18, 6);
823                                         //Time System Identifier must be added, last A3 String
824                                         break;
825                                     case PRN_NB_OF_OBS :
826                                         //Optional line in header, indicates number of Observations par Satellite
827                                         //Not stored for now
828                                         break;
829                                     case SYS_NB_OBS_TYPES :
830                                         obsTypesSystem = null;
831                                         typeObs.clear();
832 
833                                         obsTypesSystem = SatelliteSystem.parseSatelliteSystem(parseString(0, 1));
834                                         nbTypes = parseInt(3, 3);
835 
836                                         final int nbLinesTypesObs = (nbTypes + MAX_OBS_TYPES_PER_LINE_RNX3 - 1) / MAX_OBS_TYPES_PER_LINE_RNX3;
837                                         for (int j = 0; j < nbLinesTypesObs; j++) {
838                                             if (j > 0) {
839                                                 readLine(reader, true);
840                                             }
841                                             final int iMax = FastMath.min(MAX_OBS_TYPES_PER_LINE_RNX3, nbTypes - typeObs.size());
842                                             for (int i = 0; i < iMax; i++) {
843                                                 try {
844                                                     typeObs.add(ObservationType.valueOf(parseString(7 + (4 * i), 3)));
845                                                 } catch (IllegalArgumentException iae) {
846                                                     throw new OrekitException(iae, OrekitMessages.UNKNOWN_RINEX_FREQUENCY,
847                                                                               parseString(7 + (4 * i), 3), name, lineNumber);
848                                                 }
849                                             }
850                                         }
851                                         listTypeObs.put(obsTypesSystem, new ArrayList<>(typeObs));
852                                         inTypesObs = true;
853                                         break;
854                                     case SIGNAL_STRENGTH_UNIT :
855                                         sigStrengthUnit = parseString(0, 20);
856                                         break;
857                                     case SYS_DCBS_APPLIED :
858 
859                                         listAppliedDCBs.add(new AppliedDCBS(SatelliteSystem.parseSatelliteSystem(parseString(0, 1)),
860                                                                             parseString(2, 17), parseString(20, 40)));
861                                         break;
862                                     case SYS_PCVS_APPLIED :
863 
864                                         listAppliedPCVS.add(new AppliedPCVS(SatelliteSystem.parseSatelliteSystem(parseString(0, 1)),
865                                                                             parseString(2, 17), parseString(20, 40)));
866                                         break;
867                                     case SYS_SCALE_FACTOR :
868                                         satSystemScaleFactor  = null;
869                                         scaleFactor           = 1;
870                                         nbObsScaleFactor      = 0;
871 
872                                         satSystemScaleFactor = SatelliteSystem.parseSatelliteSystem(parseString(0, 1));
873                                         scaleFactor          = parseInt(2, 4);
874                                         nbObsScaleFactor     = parseInt(8, 2);
875                                         final List<ObservationType> typesObsScaleFactor = new ArrayList<>(nbObsScaleFactor);
876 
877                                         if (nbObsScaleFactor == 0) {
878                                             typesObsScaleFactor.addAll(listTypeObs.get(satSystemScaleFactor));
879                                         } else {
880                                             final int nbLinesTypesObsScaleFactor = (nbObsScaleFactor + MAX_OBS_TYPES_SCALE_FACTOR_PER_LINE - 1) /
881                                                                                    MAX_OBS_TYPES_SCALE_FACTOR_PER_LINE;
882                                             for (int j = 0; j < nbLinesTypesObsScaleFactor; j++) {
883                                                 if ( j > 0) {
884                                                     readLine(reader, true);
885                                                 }
886                                                 final int iMax = FastMath.min(MAX_OBS_TYPES_SCALE_FACTOR_PER_LINE, nbObsScaleFactor - typesObsScaleFactor.size());
887                                                 for (int i = 0; i < iMax; i++) {
888                                                     typesObsScaleFactor.add(ObservationType.valueOf(parseString(11 + (4 * i), 3)));
889                                                 }
890                                             }
891                                         }
892 
893                                         scaleFactorCorrections.add(new ScaleFactorCorrection(satSystemScaleFactor,
894                                                                                              scaleFactor, typesObsScaleFactor));
895                                         break;
896                                     case SYS_PHASE_SHIFT  :
897                                     case SYS_PHASE_SHIFTS : {
898 
899                                         nbSatPhaseShift     = 0;
900                                         satsPhaseShift      = null;
901                                         corrPhaseShift      = 0.0;
902                                         phaseShiftTypeObs   = null;
903                                         satSystemPhaseShift = null;
904 
905                                         satSystemPhaseShift = SatelliteSystem.parseSatelliteSystem(parseString(0, 1));
906                                         final String to = parseString(2, 3);
907                                         phaseShiftTypeObs = to.isEmpty() ?
908                                                             null :
909                                                             ObservationType.valueOf(to.length() < 3 ? "L" + to : to);
910                                         nbSatPhaseShift = parseInt(16, 2);
911                                         corrPhaseShift = parseDouble(6, 8);
912 
913                                         if (nbSatPhaseShift == 0) {
914                                             //If nbSat with Phase Shift is not indicated: all the satellites are affected for this Obs Type
915                                         } else {
916                                             satsPhaseShift = new String[nbSatPhaseShift];
917                                             final int nbLinesSatPhaseShift = (nbSatPhaseShift + MAX_N_SAT_PHSHIFT_PER_LINE - 1) / MAX_N_SAT_PHSHIFT_PER_LINE;
918                                             for (int j = 0; j < nbLinesSatPhaseShift; j++) {
919                                                 if (j > 0) {
920                                                     readLine(reader, true);
921                                                 }
922                                                 final int iMax = FastMath.min(MAX_N_SAT_PHSHIFT_PER_LINE, nbSatPhaseShift - j * MAX_N_SAT_PHSHIFT_PER_LINE);
923                                                 for (int i = 0; i < iMax; i++) {
924                                                     satsPhaseShift[i + 10 * j] = parseString(19 + 4 * i, 3);
925                                                 }
926                                             }
927                                         }
928                                         phaseShiftCorrections.add(new PhaseShiftCorrection(satSystemPhaseShift,
929                                                                                            phaseShiftTypeObs,
930                                                                                            corrPhaseShift,
931                                                                                            satsPhaseShift));
932                                         inPhaseShift = true;
933                                         break;
934                                     }
935                                     case GLONASS_SLOT_FRQ_NB :
936                                         // Not defined yet
937                                         break;
938                                     case GLONASS_COD_PHS_BIS :
939                                         // Not defined yet
940                                         break;
941                                     case END_OF_HEADER :
942                                         //We make sure that we have read all the mandatory fields inside the header of the Rinex
943                                         if (!inRinexVersion || !inMarkerName ||
944                                             !inObserver || !inRecType || !inAntType ||
945                                             !inAntDelta || !inTypesObs || !inFirstObs ||
946                                             formatVersion >= 3.01 && !inPhaseShift) {
947                                             throw new OrekitException(OrekitMessages.INCOMPLETE_HEADER, name);
948                                         }
949 
950                                         //Header information gathered
951                                         rinexHeader = new RinexObservationHeader(formatVersion, satelliteSystem,
952                                                                       markerName, markerNumber, markerType,
953                                                                       observerName, agencyName, receiverNumber,
954                                                                       receiverType, receiverVersion, antennaNumber,
955                                                                       antennaType, approxPos, antHeight, eccentricities,
956                                                                       antRefPoint, obsCode, antPhaseCenter, antBSight,
957                                                                       antAzi, antZeroDir, centerMass, sigStrengthUnit,
958                                                                       interval, tFirstObs, tLastObs, clkOffset, listAppliedDCBs,
959                                                                       listAppliedPCVS, phaseShiftCorrections, leapSeconds,
960                                                                       leapSecondsFuture, leapSecondsWeekNum, leapSecondsDayNum);
961                                         break;
962                                     default :
963                                         if (rinexHeader == null) {
964                                             //There must be an error due to an unknown Label inside the Header
965                                             throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
966                                                                       lineNumber, name, line);
967                                         }
968                                 }
969                             } else {
970                                 //If End of Header
971 
972                                 //Start of a new Observation
973                                 rcvrClkOffset     =  0;
974                                 eventFlag         = -1;
975                                 nbSatObs          = -1;
976                                 tObs              = null;
977 
978                                 //A line that starts with ">" correspond to a new observation epoch
979                                 if (parseString(0, 1).equals(">")) {
980 
981                                     eventFlag = parseInt(31, 1);
982                                     //If eventFlag>1, we skip the corresponding lines to the next observation
983                                     if (eventFlag != 0) {
984                                         final int nbLinesSkip = parseInt(32, 3);
985                                         for (int i = 0; i < nbLinesSkip; i++) {
986                                             readLine(reader, true);
987                                         }
988                                     } else {
989 
990                                         tObs = new AbsoluteDate(parseInt(2, 4),
991                                                                 parseInt(6, 3),
992                                                                 parseInt(9, 3),
993                                                                 parseInt(12, 3),
994                                                                 parseInt(15, 3),
995                                                                 parseDouble(18, 11), timeScale);
996 
997                                         nbSatObs  = parseInt(32, 3);
998                                         //If the total number of satellites was indicated in the Header
999                                         if (nbSat != -1 && nbSatObs > nbSat) {
1000                                             //we check that the number of Sat in the observation is consistent
1001                                             throw new OrekitException(OrekitMessages.INCONSISTENT_NUMBER_OF_SATS,
1002                                                                       lineNumber, name, nbSatObs, nbSat);
1003                                         }
1004                                         //Read the Receiver Clock offset, if present
1005                                         rcvrClkOffset = parseDouble(41, 15);
1006                                         if (Double.isNaN(rcvrClkOffset)) {
1007                                             rcvrClkOffset = 0.0;
1008                                         }
1009 
1010                                         //For each one of the Satellites in this Observation
1011                                         for (int i = 0; i < nbSatObs; i++) {
1012 
1013                                             readLine(reader, true);
1014 
1015                                             //We check that the Satellite type is consistent with Satellite System in the top of the file
1016                                             final SatelliteSystem satelliteSystemSat = SatelliteSystem.parseSatelliteSystem(parseString(0, 1));
1017                                             if (!satelliteSystem.equals(SatelliteSystem.MIXED)) {
1018                                                 if (!satelliteSystemSat.equals(satelliteSystem)) {
1019                                                     throw new OrekitException(OrekitMessages.INCONSISTENT_SATELLITE_SYSTEM,
1020                                                                               lineNumber, name, satelliteSystem, satelliteSystemSat);
1021                                                 }
1022                                             }
1023 
1024                                             final int prn = parseInt(1, 2);
1025                                             final int prnNumber;
1026                                             switch (satelliteSystemSat) {
1027                                                 case GPS:
1028                                                 case GLONASS:
1029                                                 case GALILEO:
1030                                                 case BEIDOU:
1031                                                 case IRNSS:
1032                                                     prnNumber = prn;
1033                                                     break;
1034                                                 case QZSS:
1035                                                     prnNumber = prn + 192;
1036                                                     break;
1037                                                 case SBAS:
1038                                                     prnNumber = prn + 100;
1039                                                     break;
1040                                                 default:
1041                                                     // MIXED satellite system is not allowed here
1042                                                     throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
1043                                                                               lineNumber, name, line);
1044                                             }
1045                                             final List<ObservationData> observationData = new ArrayList<>(nbSatObs);
1046                                             for (int j = 0; j < listTypeObs.get(satelliteSystemSat).size(); j++) {
1047                                                 final ObservationType rf = listTypeObs.get(satelliteSystemSat).get(j);
1048                                                 boolean scaleFactorFound = false;
1049                                                 //We look for the lines of ScaledFactorCorrections that correspond to this SatSystem
1050                                                 int k = 0;
1051                                                 double value = parseDouble(3 + j * 16, 14);
1052                                                 while (k < scaleFactorCorrections.size() && !scaleFactorFound) {
1053                                                     if (scaleFactorCorrections.get(k).getSatelliteSystem().equals(satelliteSystemSat)) {
1054                                                         //We check if the next Observation Type to read needs to be scaled
1055                                                         if (scaleFactorCorrections.get(k).getTypesObsScaled().contains(rf)) {
1056                                                             value /= scaleFactorCorrections.get(k).getCorrection();
1057                                                             scaleFactorFound = true;
1058                                                         }
1059                                                     }
1060                                                     k++;
1061                                                 }
1062                                                 observationData.add(new ObservationData(rf,
1063                                                                                         value,
1064                                                                                         parseInt(17 + j * 16, 1),
1065                                                                                         parseInt(18 + j * 16, 1)));
1066                                             }
1067                                             observationDataSets.add(new ObservationDataSet(rinexHeader, satelliteSystemSat, prnNumber,
1068                                                                                            tObs, rcvrClkOffset, observationData));
1069 
1070                                         }
1071                                     }
1072                                 }
1073                             }
1074                         }
1075                         break;
1076                     }
1077                     default:
1078                         //If RINEX Version is neither 2 nor 3
1079                         throw new OrekitException(OrekitMessages.UNSUPPORTED_FILE_FORMAT, name);
1080                 }
1081             }
1082         }
1083 
1084         /** Read a new line.
1085          * @param reader reader from where to read line
1086          * @param complainIfEnd if true an exception should be thrown if end of file is encountered
1087          * @return true if a line has been read
1088          * @exception IOException if a read error occurs
1089          */
1090         private boolean readLine(final BufferedReader reader, final boolean complainIfEnd)
1091             throws IOException {
1092             line = reader.readLine();
1093             if (line == null && complainIfEnd) {
1094                 throw new OrekitException(OrekitMessages.UNEXPECTED_END_OF_FILE, name);
1095             }
1096             lineNumber++;
1097             return line != null;
1098         }
1099 
1100         /** Extract a string from a line.
1101          * @param start start index of the string
1102          * @param length length of the string
1103          * @return parsed string
1104          */
1105         private String parseString(final int start, final int length) {
1106             if (line.length() > start) {
1107                 return line.substring(start, FastMath.min(line.length(), start + length)).trim();
1108             } else {
1109                 return null;
1110             }
1111         }
1112 
1113         /** Extract an integer from a line.
1114          * @param start start index of the integer
1115          * @param length length of the integer
1116          * @return parsed integer
1117          */
1118         private int parseInt(final int start, final int length) {
1119             if (line.length() > start && !parseString(start, length).isEmpty()) {
1120                 return Integer.parseInt(parseString(start, length));
1121             } else {
1122                 return 0;
1123             }
1124         }
1125 
1126         /** Extract a double from a line.
1127          * @param start start index of the real
1128          * @param length length of the real
1129          * @return parsed real, or {@code Double.NaN} if field was empty
1130          */
1131         private double parseDouble(final int start, final int length) {
1132             if (line.length() > start && !parseString(start, length).isEmpty()) {
1133                 return Double.parseDouble(parseString(start, length));
1134             } else {
1135                 return Double.NaN;
1136             }
1137         }
1138 
1139         /** Phase Shift corrections.
1140          * Contains the phase shift corrections used to
1141          * generate phases consistent with respect to cycle shifts.
1142          */
1143         public class PhaseShiftCorrection {
1144 
1145             /** Satellite System. */
1146             private final SatelliteSystem satSystemPhaseShift;
1147             /** Carrier Phase Observation Code (may be null). */
1148             private final ObservationType typeObsPhaseShift;
1149             /** Phase Shift Corrections (cycles). */
1150             private final double phaseShiftCorrection;
1151             /** List of satellites involved. */
1152             private final String[] satsPhaseShift;
1153 
1154             /** Simple constructor.
1155              * @param satSystemPhaseShift Satellite System
1156              * @param typeObsPhaseShift Carrier Phase Observation Code (may be null)
1157              * @param phaseShiftCorrection Phase Shift Corrections (cycles)
1158              * @param satsPhaseShift List of satellites involved
1159              */
1160             private PhaseShiftCorrection(final SatelliteSystem satSystemPhaseShift,
1161                                          final ObservationType typeObsPhaseShift,
1162                                          final double phaseShiftCorrection, final String[] satsPhaseShift) {
1163                 this.satSystemPhaseShift = satSystemPhaseShift;
1164                 this.typeObsPhaseShift = typeObsPhaseShift;
1165                 this.phaseShiftCorrection = phaseShiftCorrection;
1166                 this.satsPhaseShift = satsPhaseShift;
1167             }
1168 
1169             /** Get the Satellite System.
1170              * @return Satellite System.
1171              */
1172             public SatelliteSystem getSatelliteSystem() {
1173                 return satSystemPhaseShift;
1174             }
1175             /** Get the Carrier Phase Observation Code.
1176              * <p>
1177              * The observation code may be null for the uncorrected reference
1178              * signal group
1179              * </p>
1180              * @return Carrier Phase Observation Code.
1181              */
1182             public ObservationType getTypeObs() {
1183                 return typeObsPhaseShift;
1184             }
1185             /** Get the Phase Shift Corrections.
1186              * @return Phase Shift Corrections (cycles)
1187              */
1188             public double getCorrection() {
1189                 return phaseShiftCorrection;
1190             }
1191             /** Get the list of satellites involved.
1192              * @return List of satellites involved (if null, all the sats are involved)
1193              */
1194             public String[] getSatsCorrected() {
1195                 //If empty, all the satellites of this constellation are affected for this Observation type
1196                 return satsPhaseShift == null ? null : satsPhaseShift.clone();
1197             }
1198         }
1199 
1200         /** Scale Factor to be applied.
1201          * Contains the scale factors of 10 applied to the data before
1202          * being stored into the RINEX file.
1203          */
1204         public class ScaleFactorCorrection {
1205 
1206             /** Satellite System. */
1207             private final SatelliteSystem satSystemScaleFactor;
1208             /** List of Observations types that have been scaled. */
1209             private final List<ObservationType> typesObsScaleFactor;
1210             /** Factor to divide stored observations with before use. */
1211             private final double scaleFactor;
1212 
1213             /** Simple constructor.
1214              * @param satSystemScaleFactor Satellite System
1215              * @param scaleFactor Factor to divide stored observations (1,10,100,1000)
1216              * @param typesObsScaleFactor List of Observations types that have been scaled
1217              */
1218             private ScaleFactorCorrection(final SatelliteSystem satSystemScaleFactor,
1219                                           final double scaleFactor,
1220                                           final List<ObservationType> typesObsScaleFactor) {
1221                 this.satSystemScaleFactor = satSystemScaleFactor;
1222                 this.scaleFactor = scaleFactor;
1223                 this.typesObsScaleFactor = typesObsScaleFactor;
1224             }
1225             /** Get the Satellite System.
1226              * @return Satellite System
1227              */
1228             public SatelliteSystem getSatelliteSystem() {
1229                 return satSystemScaleFactor;
1230             }
1231             /** Get the Scale Factor.
1232              * @return Scale Factor
1233              */
1234             public double getCorrection() {
1235                 return scaleFactor;
1236             }
1237             /** Get the list of Observation Types scaled.
1238              * @return List of Observation types scaled
1239              */
1240             public List<ObservationType> getTypesObsScaled() {
1241                 return typesObsScaleFactor;
1242             }
1243         }
1244 
1245     }
1246 
1247 }