1   /* Copyright 2002-2021 CS GROUP
2    * Licensed to CS GROUP (CS) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * CS licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.orekit.gnss;
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                          inRunBy                = false;
274                 boolean                          inMarkerName           = false;
275                 boolean                          inObserver             = false;
276                 boolean                          inRecType              = false;
277                 boolean                          inAntType              = false;
278                 boolean                          inAproxPos             = false;
279                 boolean                          inAntDelta             = false;
280                 boolean                          inTypesObs             = false;
281                 boolean                          inFirstObs             = false;
282                 boolean                          inPhaseShift           = false;
283                 boolean                          inGlonassSlot          = false;
284                 boolean                          inGlonassCOD           = false;
285                 RinexObservationHeader                      rinexHeader            = null;
286                 int                               scaleFactor            = 1;
287                 int                               nbObsScaleFactor       = 0;
288                 final List<ScaleFactorCorrection> scaleFactorCorrections = new ArrayList<>();
289                 final Map<SatelliteSystem, List<ObservationType>> listTypeObs = new HashMap<>();
290 
291                 //First line must  always contain Rinex Version, File Type and Satellite Systems Observed
292                 readLine(reader, true);
293                 if (line.length() < LABEL_START || !RINEX_VERSION_TYPE.equals(line.substring(LABEL_START).trim())) {
294                     throw new OrekitException(OrekitMessages.UNSUPPORTED_FILE_FORMAT, name);
295                 }
296                 formatVersion = parseDouble(0, 9);
297                 final int format100 = (int) FastMath.rint(100 * formatVersion);
298 
299                 if (format100 != 200 && format100 != 210 && format100 != 211 &&
300                     format100 != 212 && format100 != 220 && format100 != 300 &&
301                     format100 != 301 && format100 != 302 && format100 != 303 &&
302                     format100 != 304) {
303                     throw new OrekitException(OrekitMessages.UNSUPPORTED_FILE_FORMAT, name);
304                 }
305 
306                 //File Type must be Observation_Data
307                 if (!(parseString(20, 1)).equals(FILE_TYPE)) {
308                     throw new OrekitException(OrekitMessages.UNSUPPORTED_FILE_FORMAT, name);
309                 }
310                 satelliteSystem = SatelliteSystem.parseSatelliteSystem(parseString(40, 1));
311                 inRinexVersion = true;
312 
313                 switch (format100 / 100) {
314                     case 2: {
315 
316                         final int                   MAX_OBS_TYPES_PER_LINE_RNX2 = 9;
317                         final int                   MAX_N_SAT_OBSERVATION       = 12;
318                         final int                   MAX_N_TYPES_OBSERVATION     = 5;
319                         final int                   MAX_OBS_TYPES_SCALE_FACTOR  = 8;
320                         final List<ObservationType> typesObs = new ArrayList<>();
321 
322                         while (readLine(reader, false)) {
323 
324                             if (rinexHeader == null) {
325                                 switch(line.substring(LABEL_START).trim()) {
326                                     case COMMENT :
327                                         // nothing to do
328                                         break;
329                                     case PGM_RUN_BY_DATE :
330                                         inRunBy = true;
331                                         break;
332                                     case MARKER_NAME :
333                                         markerName = parseString(0, 60);
334                                         inMarkerName = true;
335                                         break;
336                                     case MARKER_NUMBER :
337                                         markerNumber = parseString(0, 20);
338                                         break;
339                                     case MARKER_TYPE :
340                                         markerType = parseString(0, 20);
341                                         break;
342                                     case OBSERVER_AGENCY :
343                                         observerName = parseString(0, 20);
344                                         agencyName   = parseString(20, 40);
345                                         inObserver = true;
346                                         break;
347                                     case REC_NB_TYPE_VERS :
348                                         receiverNumber  = parseString(0, 20);
349                                         receiverType    = parseString(20, 20);
350                                         receiverVersion = parseString(40, 20);
351                                         inRecType = true;
352                                         break;
353                                     case ANT_NB_TYPE :
354                                         antennaNumber = parseString(0, 20);
355                                         antennaType   = parseString(20, 20);
356                                         inAntType = true;
357                                         break;
358                                     case APPROX_POSITION_XYZ :
359                                         approxPos = new Vector3D(parseDouble(0, 14), parseDouble(14, 14),
360                                                                  parseDouble(28, 14));
361                                         inAproxPos = true;
362                                         break;
363                                     case ANTENNA_DELTA_H_E_N :
364                                         antHeight = parseDouble(0, 14);
365                                         eccentricities = new Vector2D(parseDouble(14, 14), parseDouble(28, 14));
366                                         inAntDelta = true;
367                                         break;
368                                     case ANTENNA_DELTA_X_Y_Z :
369                                         antRefPoint = new Vector3D(parseDouble(0, 14),
370                                                                    parseDouble(14, 14),
371                                                                    parseDouble(28, 14));
372                                         break;
373                                     case ANTENNA_B_SIGHT_XYZ :
374                                         antBSight = new Vector3D(parseDouble(0, 14),
375                                                                  parseDouble(14, 14),
376                                                                  parseDouble(28, 14));
377                                         break;
378                                     case CENTER_OF_MASS_XYZ :
379                                         centerMass = new Vector3D(parseDouble(0, 14),
380                                                                   parseDouble(14, 14),
381                                                                   parseDouble(28, 14));
382                                         break;
383                                     case NB_OF_SATELLITES :
384                                         nbSat = parseInt(0, 6);
385                                         break;
386                                     case WAVELENGTH_FACT_L1_2 :
387                                         //Optional line in header
388                                         //Not stored for now
389                                         break;
390                                     case RCV_CLOCK_OFFS_APPL :
391                                         clkOffset = parseInt(0, 6);
392                                         break;
393                                     case INTERVAL :
394                                         interval = parseDouble(0, 10);
395                                         break;
396                                     case TIME_OF_FIRST_OBS :
397                                         switch (satelliteSystem) {
398                                             case GPS:
399                                                 timeScale = timeScales.getGPS();
400                                                 break;
401                                             case GALILEO:
402                                                 timeScale = timeScales.getGST();
403                                                 break;
404                                             case GLONASS:
405                                                 timeScale = timeScales.getGLONASS();
406                                                 break;
407                                             case MIXED:
408                                                 //in Case of Mixed data, Timescale must be specified in the Time of First line
409                                                 timeScaleStr = parseString(48, 3);
410 
411                                                 if (timeScaleStr.equals(GPS)) {
412                                                     timeScale = timeScales.getGPS();
413                                                 } else if (timeScaleStr.equals(GAL)) {
414                                                     timeScale = timeScales.getGST();
415                                                 } else if (timeScaleStr.equals(GLO)) {
416                                                     timeScale = timeScales.getGLONASS();
417                                                 } else {
418                                                     throw new OrekitException(OrekitMessages.UNSUPPORTED_FILE_FORMAT, name);
419                                                 }
420                                                 break;
421                                             default :
422                                                 throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
423                                                                           lineNumber, name, line);
424                                         }
425 
426                                         tFirstObs = new AbsoluteDate(parseInt(0, 6),
427                                                                      parseInt(6, 6),
428                                                                      parseInt(12, 6),
429                                                                      parseInt(18, 6),
430                                                                      parseInt(24, 6),
431                                                                      parseDouble(30, 13), timeScale);
432                                         inFirstObs = true;
433                                         break;
434                                     case TIME_OF_LAST_OBS :
435                                         tLastObs = new AbsoluteDate(parseInt(0, 6),
436                                                                     parseInt(6, 6),
437                                                                     parseInt(12, 6),
438                                                                     parseInt(18, 6),
439                                                                     parseInt(24, 6),
440                                                                     parseDouble(30, 13), timeScale);
441                                         break;
442                                     case LEAP_SECONDS :
443                                         leapSeconds = parseInt(0, 6);
444                                         break;
445                                     case PRN_NB_OF_OBS :
446                                         //Optional line in header, indicates number of Observations par Satellite
447                                         //Not stored for now
448                                         break;
449                                     case NB_TYPES_OF_OBSERV :
450                                         nbTypes = parseInt(0, 6);
451                                         final int nbLinesTypesObs = (nbTypes + MAX_OBS_TYPES_PER_LINE_RNX2 - 1 ) / MAX_OBS_TYPES_PER_LINE_RNX2;
452 
453                                         for (int j = 0; j < nbLinesTypesObs; j++) {
454                                             if (j > 0) {
455                                                 readLine(reader, true);
456                                             }
457                                             final int iMax = FastMath.min(MAX_OBS_TYPES_PER_LINE_RNX2, nbTypes - typesObs.size());
458                                             for (int i = 0; i < iMax; i++) {
459                                                 try {
460                                                     typesObs.add(ObservationType.valueOf(parseString(10 + (6 * i), 2)));
461                                                 } catch (IllegalArgumentException iae) {
462                                                     throw new OrekitException(iae, OrekitMessages.UNKNOWN_RINEX_FREQUENCY,
463                                                                               parseString(10 + (6 * i), 2), name, lineNumber);
464                                                 }
465                                             }
466                                         }
467                                         inTypesObs = true;
468                                         break;
469                                     case OBS_SCALE_FACTOR :
470                                         scaleFactor      = FastMath.max(1, parseInt(0,  6));
471                                         nbObsScaleFactor = parseInt(6, 6);
472                                         if (nbObsScaleFactor > MAX_OBS_TYPES_SCALE_FACTOR) {
473                                             throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
474                                                                       lineNumber, name, line);
475                                         }
476                                         final List<ObservationType> typesObsScaleFactor = new ArrayList<>(nbObsScaleFactor);
477                                         for (int i = 0; i < nbObsScaleFactor; i++) {
478                                             typesObsScaleFactor.add(ObservationType.valueOf(parseString(16 + (6 * i), 2)));
479                                         }
480                                         scaleFactorCorrections.add(new ScaleFactorCorrection(satelliteSystem,
481                                                                                              scaleFactor, typesObsScaleFactor));
482                                         break;
483                                     case END_OF_HEADER :
484                                         //We make sure that we have read all the mandatory fields inside the header of the Rinex
485                                         if (!inRinexVersion || !inRunBy || !inMarkerName ||
486                                             !inObserver || !inRecType || !inAntType ||
487                                             formatVersion < 2.20 && !inAproxPos ||
488                                             formatVersion < 2.20 && !inAntDelta ||
489                                             !inTypesObs || !inFirstObs) {
490                                             throw new OrekitException(OrekitMessages.INCOMPLETE_HEADER, name);
491                                         }
492 
493                                         //Header information gathered
494                                         rinexHeader = new RinexObservationHeader(formatVersion, satelliteSystem,
495                                                                       markerName, markerNumber, markerType, observerName,
496                                                                       agencyName, receiverNumber, receiverType,
497                                                                       receiverVersion, antennaNumber, antennaType,
498                                                                       approxPos, antHeight, eccentricities,
499                                                                       antRefPoint, antBSight, centerMass, interval,
500                                                                       tFirstObs, tLastObs, clkOffset, leapSeconds);
501                                         break;
502                                     default :
503                                         if (rinexHeader == null) {
504                                             //There must be an error due to an unknown Label inside the Header
505                                             throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
506                                                                       lineNumber, name, line);
507                                         }
508                                 }
509                             } else {
510 
511                                 //Start of a new Observation
512                                 rcvrClkOffset     =  0;
513                                 nbLinesSat        = -1;
514                                 eventFlag         = -1;
515                                 nbSatObs          = -1;
516                                 satsObsList       = null;
517                                 tObs              = null;
518 
519                                 eventFlag = parseInt(28, 1);
520                                 //If eventFlag>1, we skip the corresponding lines to the next observation
521                                 if (eventFlag > 1) {
522                                     if (eventFlag == 6) {
523                                         nbSatObs  = parseInt(29, 3);
524                                         nbLinesSat = (nbSatObs + 12 - 1) / 12;
525                                         final int nbLinesObs = (nbTypes + 5 - 1) / 5;
526                                         final int nbLinesSkip = (nbLinesSat - 1) + nbSatObs * nbLinesObs;
527                                         for (int i = 0; i < nbLinesSkip; i++) {
528                                             readLine(reader, true);
529                                         }
530                                     } else {
531                                         final int nbLinesSkip = parseInt(29, 3);
532                                         for (int i = 0; i < nbLinesSkip; i++) {
533                                             readLine(reader, true);
534                                         }
535                                     }
536                                 } else {
537 
538                                     int y = parseInt(0, 3);
539                                     if (79 < y && y <= 99) {
540                                         y += 1900;
541                                     } else if (0 <= y && y <= 79) {
542                                         y += 2000;
543                                     }
544                                     tObs = new AbsoluteDate(y,
545                                                             parseInt(3, 3),
546                                                             parseInt(6, 3),
547                                                             parseInt(9, 3),
548                                                             parseInt(12, 3),
549                                                             parseDouble(15, 11), timeScale);
550 
551                                     nbSatObs  = parseInt(29, 3);
552                                     satsObsList   = new String[nbSatObs];
553                                     //If the total number of satellites was indicated in the Header
554                                     if (nbSat != -1 && nbSatObs > nbSat) {
555                                         //we check that the number of Sat in the observation is consistent
556                                         throw new OrekitException(OrekitMessages.INCONSISTENT_NUMBER_OF_SATS,
557                                                                   lineNumber, name, nbSatObs, nbSat);
558                                     }
559 
560                                     nbLinesSat = (nbSatObs + MAX_N_SAT_OBSERVATION - 1) / MAX_N_SAT_OBSERVATION;
561                                     for (int j = 0; j < nbLinesSat; j++) {
562                                         if (j > 0) {
563                                             readLine(reader, true);
564                                         }
565                                         final int iMax = FastMath.min(MAX_N_SAT_OBSERVATION, nbSatObs  - j * MAX_N_SAT_OBSERVATION);
566                                         for (int i = 0; i < iMax; i++) {
567                                             satsObsList[i + MAX_N_SAT_OBSERVATION * j] = parseString(32 + 3 * i, 3);
568                                         }
569 
570                                         //Read the Receiver Clock offset, if present
571                                         rcvrClkOffset = parseDouble(68, 12);
572                                         if (Double.isNaN(rcvrClkOffset)) {
573                                             rcvrClkOffset = 0.0;
574                                         }
575 
576                                     }
577 
578                                     //For each one of the Satellites in this observation
579                                     final int nbLinesObs = (nbTypes + MAX_N_TYPES_OBSERVATION - 1) / MAX_N_TYPES_OBSERVATION;
580                                     for (int k = 0; k < nbSatObs; k++) {
581 
582 
583                                         //Once the Date and Satellites list is read:
584                                         //  - to read the Data for each satellite
585                                         //  - 5 Observations per line
586                                         final List<ObservationData> observationData = new ArrayList<>(nbSatObs);
587                                         for (int j = 0; j < nbLinesObs; j++) {
588                                             readLine(reader, true);
589                                             final int iMax = FastMath.min(MAX_N_TYPES_OBSERVATION, nbTypes - observationData.size());
590                                             for (int i = 0; i < iMax; i++) {
591                                                 final ObservationType type = typesObs.get(observationData.size());
592                                                 double value = parseDouble(16 * i, 14);
593                                                 boolean scaleFactorFound = false;
594                                                 //We look for the lines of ScaledFactorCorrections
595                                                 for (int l = 0; l < scaleFactorCorrections.size() && !scaleFactorFound; ++l) {
596                                                     //We check if the next Observation Type to read needs to be scaled
597                                                     if (scaleFactorCorrections.get(l).getTypesObsScaled().contains(type)) {
598                                                         value /= scaleFactorCorrections.get(l).getCorrection();
599                                                         scaleFactorFound = true;
600                                                     }
601                                                 }
602                                                 observationData.add(new ObservationData(type,
603                                                                                         value,
604                                                                                         parseInt(14 + 16 * i, 1),
605                                                                                         parseInt(15 + 16 * i, 1)));
606                                             }
607                                         }
608 
609                                         //We check that the Satellite type is consistent with Satellite System in the top of the file
610                                         final SatelliteSystem satelliteSystemSat;
611                                         final int id;
612                                         if (satsObsList[k].length() < 3) {
613                                             // missing satellite system, we use the global one
614                                             satelliteSystemSat = satelliteSystem;
615                                             id                 = Integer.parseInt(satsObsList[k]);
616                                         } else {
617                                             satelliteSystemSat = SatelliteSystem.parseSatelliteSystem(satsObsList[k]);
618                                             id                 = Integer.parseInt(satsObsList[k].substring(1, 3).trim());
619                                         }
620                                         if (!satelliteSystem.equals(SatelliteSystem.MIXED)) {
621                                             if (!satelliteSystemSat.equals(satelliteSystem)) {
622                                                 throw new OrekitException(OrekitMessages.INCONSISTENT_SATELLITE_SYSTEM,
623                                                                           lineNumber, name, satelliteSystem, satelliteSystemSat);
624                                             }
625                                         }
626 
627                                         final int prnNumber;
628                                         switch (satelliteSystemSat) {
629                                             case GPS:
630                                             case GLONASS:
631                                             case GALILEO:
632                                                 prnNumber = id;
633                                                 break;
634                                             case SBAS:
635                                                 prnNumber = id + 100;
636                                                 break;
637                                             default:
638                                                 // MIXED satellite system is not allowed here
639                                                 throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
640                                                                           lineNumber, name, line);
641                                         }
642 
643                                         observationDataSets.add(new ObservationDataSet(rinexHeader, satelliteSystemSat, prnNumber,
644                                                                                        tObs, rcvrClkOffset, observationData));
645 
646                                     }
647                                 }
648                             }
649                         }
650                         break;
651                     }
652                     case 3: {
653 
654                         final int                   MAX_OBS_TYPES_PER_LINE_RNX3 = 13;
655                         final int           MAX_OBS_TYPES_SCALE_FACTOR_PER_LINE = 12;
656                         final int                    MAX_N_SAT_PHSHIFT_PER_LINE = 10;
657 
658                         final List<ObservationType>                       typeObs                = new ArrayList<>();
659                         String                                            sigStrengthUnit        = null;
660                         int                                               leapSecondsFuture      = 0;
661                         int                                               leapSecondsWeekNum     = 0;
662                         int                                               leapSecondsDayNum      = 0;
663                         final List<AppliedDCBS>                           listAppliedDCBs        = new ArrayList<>();
664                         final List<AppliedPCVS>                           listAppliedPCVS        = new ArrayList<>();
665                         SatelliteSystem                                   satSystemScaleFactor   = null;
666                         String[]                                          satsPhaseShift         = null;
667                         int                                               nbSatPhaseShift        = 0;
668                         SatelliteSystem                                   satSystemPhaseShift    = null;
669                         double                                            corrPhaseShift         = 0.0;
670                         final List<PhaseShiftCorrection>                  phaseShiftCorrections  = new ArrayList<>();
671                         ObservationType                                   phaseShiftTypeObs      = null;
672 
673 
674                         while (readLine(reader, false)) {
675                             if (rinexHeader == null) {
676                                 switch(line.substring(LABEL_START).trim()) {
677                                     case COMMENT :
678                                         // nothing to do
679                                         break;
680                                     case PGM_RUN_BY_DATE :
681                                         inRunBy = true;
682                                         break;
683                                     case MARKER_NAME :
684                                         markerName = parseString(0, 60);
685                                         inMarkerName = true;
686                                         break;
687                                     case MARKER_NUMBER :
688                                         markerNumber = parseString(0, 20);
689                                         break;
690                                     case MARKER_TYPE :
691                                         markerType = parseString(0, 20);
692                                         //Could be done with an Enumeration
693                                         break;
694                                     case OBSERVER_AGENCY :
695                                         observerName = parseString(0, 20);
696                                         agencyName   = parseString(20, 40);
697                                         inObserver = true;
698                                         break;
699                                     case REC_NB_TYPE_VERS :
700                                         receiverNumber  = parseString(0, 20);
701                                         receiverType    = parseString(20, 20);
702                                         receiverVersion = parseString(40, 20);
703                                         inRecType = true;
704                                         break;
705                                     case ANT_NB_TYPE :
706                                         antennaNumber = parseString(0, 20);
707                                         antennaType   = parseString(20, 20);
708                                         inAntType = true;
709                                         break;
710                                     case APPROX_POSITION_XYZ :
711                                         approxPos = new Vector3D(parseDouble(0, 14),
712                                                                  parseDouble(14, 14),
713                                                                  parseDouble(28, 14));
714                                         inAproxPos = true;
715                                         break;
716                                     case ANTENNA_DELTA_H_E_N :
717                                         antHeight = parseDouble(0, 14);
718                                         eccentricities = new Vector2D(parseDouble(14, 14),
719                                                                       parseDouble(28, 14));
720                                         inAntDelta = true;
721                                         break;
722                                     case ANTENNA_DELTA_X_Y_Z :
723                                         antRefPoint = new Vector3D(parseDouble(0, 14),
724                                                                    parseDouble(14, 14),
725                                                                    parseDouble(28, 14));
726                                         break;
727                                     case ANTENNA_PHASECENTER :
728                                         obsCode = parseString(2, 3);
729                                         antPhaseCenter = new Vector3D(parseDouble(5, 9),
730                                                                       parseDouble(14, 14),
731                                                                       parseDouble(28, 14));
732                                         break;
733                                     case ANTENNA_B_SIGHT_XYZ :
734                                         antBSight = new Vector3D(parseDouble(0, 14),
735                                                                  parseDouble(14, 14),
736                                                                  parseDouble(28, 14));
737                                         break;
738                                     case ANTENNA_ZERODIR_AZI :
739                                         antAzi = parseDouble(0, 14);
740                                         break;
741                                     case ANTENNA_ZERODIR_XYZ :
742                                         antZeroDir = new Vector3D(parseDouble(0, 14),
743                                                                   parseDouble(14, 14),
744                                                                   parseDouble(28, 14));
745                                         break;
746                                     case CENTER_OF_MASS_XYZ :
747                                         centerMass = new Vector3D(parseDouble(0, 14),
748                                                                   parseDouble(14, 14),
749                                                                   parseDouble(28, 14));
750                                         break;
751                                     case NB_OF_SATELLITES :
752                                         nbSat = parseInt(0, 6);
753                                         break;
754                                     case RCV_CLOCK_OFFS_APPL :
755                                         clkOffset = parseInt(0, 6);
756                                         break;
757                                     case INTERVAL :
758                                         interval = parseDouble(0, 10);
759                                         break;
760                                     case TIME_OF_FIRST_OBS :
761                                         switch(satelliteSystem) {
762                                             case GPS:
763                                                 timeScale = timeScales.getGPS();
764                                                 break;
765                                             case GALILEO:
766                                                 timeScale = timeScales.getGST();
767                                                 break;
768                                             case GLONASS:
769                                                 timeScale = timeScales.getGLONASS();
770                                                 break;
771                                             case QZSS:
772                                                 timeScale = timeScales.getQZSS();
773                                                 break;
774                                             case BEIDOU:
775                                                 timeScale = timeScales.getBDT();
776                                                 break;
777                                             case IRNSS:
778                                                 timeScale = timeScales.getIRNSS();
779                                                 break;
780                                             case MIXED:
781                                                 //in Case of Mixed data, Timescale must be specified in the Time of First line
782                                                 timeScaleStr = parseString(48, 3);
783 
784                                                 if (timeScaleStr.equals(GPS)) {
785                                                     timeScale = timeScales.getGPS();
786                                                 } else if (timeScaleStr.equals(GAL)) {
787                                                     timeScale = timeScales.getGST();
788                                                 } else if (timeScaleStr.equals(GLO)) {
789                                                     timeScale = timeScales.getGLONASS();
790                                                 } else if (timeScaleStr.equals(QZS)) {
791                                                     timeScale = timeScales.getQZSS();
792                                                 } else if (timeScaleStr.equals(BDT)) {
793                                                     timeScale = timeScales.getBDT();
794                                                 } else if (timeScaleStr.equals(IRN)) {
795                                                     timeScale = timeScales.getIRNSS();
796                                                 } else {
797                                                     throw new OrekitException(OrekitMessages.UNSUPPORTED_FILE_FORMAT, name);
798                                                 }
799                                                 break;
800                                             default :
801                                                 throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
802                                                                           lineNumber, name, line);
803                                         }
804 
805                                         tFirstObs = new AbsoluteDate(parseInt(0, 6),
806                                                                      parseInt(6, 6),
807                                                                      parseInt(12, 6),
808                                                                      parseInt(18, 6),
809                                                                      parseInt(24, 6),
810                                                                      parseDouble(30, 13), timeScale);
811                                         inFirstObs = true;
812                                         break;
813                                     case TIME_OF_LAST_OBS :
814                                         tLastObs = new AbsoluteDate(parseInt(0, 6),
815                                                                     parseInt(6, 6),
816                                                                     parseInt(12, 6),
817                                                                     parseInt(18, 6),
818                                                                     parseInt(24, 6),
819                                                                     parseDouble(30, 13), timeScale);
820                                         break;
821                                     case LEAP_SECONDS :
822                                         leapSeconds = parseInt(0, 6);
823                                         leapSecondsFuture = parseInt(6, 6);
824                                         leapSecondsWeekNum = parseInt(12, 6);
825                                         leapSecondsDayNum = parseInt(18, 6);
826                                         //Time System Identifier must be added, last A3 String
827                                         break;
828                                     case PRN_NB_OF_OBS :
829                                         //Optional line in header, indicates number of Observations par Satellite
830                                         //Not stored for now
831                                         break;
832                                     case SYS_NB_OBS_TYPES :
833                                         obsTypesSystem = null;
834                                         typeObs.clear();
835 
836                                         obsTypesSystem = SatelliteSystem.parseSatelliteSystem(parseString(0, 1));
837                                         nbTypes = parseInt(3, 3);
838 
839                                         final int nbLinesTypesObs = (nbTypes + MAX_OBS_TYPES_PER_LINE_RNX3 - 1) / MAX_OBS_TYPES_PER_LINE_RNX3;
840                                         for (int j = 0; j < nbLinesTypesObs; j++) {
841                                             if (j > 0) {
842                                                 readLine(reader, true);
843                                             }
844                                             final int iMax = FastMath.min(MAX_OBS_TYPES_PER_LINE_RNX3, nbTypes - typeObs.size());
845                                             for (int i = 0; i < iMax; i++) {
846                                                 try {
847                                                     typeObs.add(ObservationType.valueOf(parseString(7 + (4 * i), 3)));
848                                                 } catch (IllegalArgumentException iae) {
849                                                     throw new OrekitException(iae, OrekitMessages.UNKNOWN_RINEX_FREQUENCY,
850                                                                               parseString(7 + (4 * i), 3), name, lineNumber);
851                                                 }
852                                             }
853                                         }
854                                         listTypeObs.put(obsTypesSystem, new ArrayList<>(typeObs));
855                                         inTypesObs = true;
856                                         break;
857                                     case SIGNAL_STRENGTH_UNIT :
858                                         sigStrengthUnit = parseString(0, 20);
859                                         break;
860                                     case SYS_DCBS_APPLIED :
861 
862                                         listAppliedDCBs.add(new AppliedDCBS(SatelliteSystem.parseSatelliteSystem(parseString(0, 1)),
863                                                                             parseString(2, 17), parseString(20, 40)));
864                                         break;
865                                     case SYS_PCVS_APPLIED :
866 
867                                         listAppliedPCVS.add(new AppliedPCVS(SatelliteSystem.parseSatelliteSystem(parseString(0, 1)),
868                                                                             parseString(2, 17), parseString(20, 40)));
869                                         break;
870                                     case SYS_SCALE_FACTOR :
871                                         satSystemScaleFactor  = null;
872                                         scaleFactor           = 1;
873                                         nbObsScaleFactor      = 0;
874 
875                                         satSystemScaleFactor = SatelliteSystem.parseSatelliteSystem(parseString(0, 1));
876                                         scaleFactor          = parseInt(2, 4);
877                                         nbObsScaleFactor     = parseInt(8, 2);
878                                         final List<ObservationType> typesObsScaleFactor = new ArrayList<>(nbObsScaleFactor);
879 
880                                         if (nbObsScaleFactor == 0) {
881                                             typesObsScaleFactor.addAll(listTypeObs.get(satSystemScaleFactor));
882                                         } else {
883                                             final int nbLinesTypesObsScaleFactor = (nbObsScaleFactor + MAX_OBS_TYPES_SCALE_FACTOR_PER_LINE - 1) /
884                                                                                    MAX_OBS_TYPES_SCALE_FACTOR_PER_LINE;
885                                             for (int j = 0; j < nbLinesTypesObsScaleFactor; j++) {
886                                                 if ( j > 0) {
887                                                     readLine(reader, true);
888                                                 }
889                                                 final int iMax = FastMath.min(MAX_OBS_TYPES_SCALE_FACTOR_PER_LINE, nbObsScaleFactor - typesObsScaleFactor.size());
890                                                 for (int i = 0; i < iMax; i++) {
891                                                     typesObsScaleFactor.add(ObservationType.valueOf(parseString(11 + (4 * i), 3)));
892                                                 }
893                                             }
894                                         }
895 
896                                         scaleFactorCorrections.add(new ScaleFactorCorrection(satSystemScaleFactor,
897                                                                                              scaleFactor, typesObsScaleFactor));
898                                         break;
899                                     case SYS_PHASE_SHIFT  :
900                                     case SYS_PHASE_SHIFTS : {
901 
902                                         nbSatPhaseShift     = 0;
903                                         satsPhaseShift      = null;
904                                         corrPhaseShift      = 0.0;
905                                         phaseShiftTypeObs   = null;
906                                         satSystemPhaseShift = null;
907 
908                                         satSystemPhaseShift = SatelliteSystem.parseSatelliteSystem(parseString(0, 1));
909                                         final String to = parseString(2, 3);
910                                         phaseShiftTypeObs = to.isEmpty() ?
911                                                             null :
912                                                             ObservationType.valueOf(to.length() < 3 ? "L" + to : to);
913                                         nbSatPhaseShift = parseInt(16, 2);
914                                         corrPhaseShift = parseDouble(6, 8);
915 
916                                         if (nbSatPhaseShift == 0) {
917                                             //If nbSat with Phase Shift is not indicated: all the satellites are affected for this Obs Type
918                                         } else {
919                                             satsPhaseShift = new String[nbSatPhaseShift];
920                                             final int nbLinesSatPhaseShift = (nbSatPhaseShift + MAX_N_SAT_PHSHIFT_PER_LINE - 1) / MAX_N_SAT_PHSHIFT_PER_LINE;
921                                             for (int j = 0; j < nbLinesSatPhaseShift; j++) {
922                                                 if (j > 0) {
923                                                     readLine(reader, true);
924                                                 }
925                                                 final int iMax = FastMath.min(MAX_N_SAT_PHSHIFT_PER_LINE, nbSatPhaseShift - j * MAX_N_SAT_PHSHIFT_PER_LINE);
926                                                 for (int i = 0; i < iMax; i++) {
927                                                     satsPhaseShift[i + 10 * j] = parseString(19 + 4 * i, 3);
928                                                 }
929                                             }
930                                         }
931                                         phaseShiftCorrections.add(new PhaseShiftCorrection(satSystemPhaseShift,
932                                                                                            phaseShiftTypeObs,
933                                                                                            corrPhaseShift,
934                                                                                            satsPhaseShift));
935                                         inPhaseShift = true;
936                                         break;
937                                     }
938                                     case GLONASS_SLOT_FRQ_NB :
939                                         //Not defined yet
940                                         inGlonassSlot = true;
941                                         break;
942                                     case GLONASS_COD_PHS_BIS :
943                                         //Not defined yet
944                                         inGlonassCOD = true;
945                                         break;
946                                     case END_OF_HEADER :
947                                         //We make sure that we have read all the mandatory fields inside the header of the Rinex
948                                         if (!inRinexVersion || !inRunBy || !inMarkerName ||
949                                             !inObserver || !inRecType || !inAntType ||
950                                             !inAntDelta || !inTypesObs || !inFirstObs ||
951                                             formatVersion >= 3.01 && !inPhaseShift ||
952                                             formatVersion >= 3.03 && (!inGlonassSlot || !inGlonassCOD)) {
953                                             throw new OrekitException(OrekitMessages.INCOMPLETE_HEADER, name);
954                                         }
955 
956                                         //Header information gathered
957                                         rinexHeader = new RinexObservationHeader(formatVersion, satelliteSystem,
958                                                                       markerName, markerNumber, markerType,
959                                                                       observerName, agencyName, receiverNumber,
960                                                                       receiverType, receiverVersion, antennaNumber,
961                                                                       antennaType, approxPos, antHeight, eccentricities,
962                                                                       antRefPoint, obsCode, antPhaseCenter, antBSight,
963                                                                       antAzi, antZeroDir, centerMass, sigStrengthUnit,
964                                                                       interval, tFirstObs, tLastObs, clkOffset, listAppliedDCBs,
965                                                                       listAppliedPCVS, phaseShiftCorrections, leapSeconds,
966                                                                       leapSecondsFuture, leapSecondsWeekNum, leapSecondsDayNum);
967                                         break;
968                                     default :
969                                         if (rinexHeader == null) {
970                                             //There must be an error due to an unknown Label inside the Header
971                                             throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
972                                                                       lineNumber, name, line);
973                                         }
974                                 }
975                             } else {
976                                 //If End of Header
977 
978                                 //Start of a new Observation
979                                 rcvrClkOffset     =  0;
980                                 eventFlag         = -1;
981                                 nbSatObs          = -1;
982                                 tObs              = null;
983 
984                                 //A line that starts with ">" correspond to a new observation epoch
985                                 if (parseString(0, 1).equals(">")) {
986 
987                                     eventFlag = parseInt(31, 1);
988                                     //If eventFlag>1, we skip the corresponding lines to the next observation
989                                     if (eventFlag != 0) {
990                                         final int nbLinesSkip = parseInt(32, 3);
991                                         for (int i = 0; i < nbLinesSkip; i++) {
992                                             readLine(reader, true);
993                                         }
994                                     } else {
995 
996                                         tObs = new AbsoluteDate(parseInt(2, 4),
997                                                                 parseInt(6, 3),
998                                                                 parseInt(9, 3),
999                                                                 parseInt(12, 3),
1000                                                                 parseInt(15, 3),
1001                                                                 parseDouble(18, 11), timeScale);
1002 
1003                                         nbSatObs  = parseInt(32, 3);
1004                                         //If the total number of satellites was indicated in the Header
1005                                         if (nbSat != -1 && nbSatObs > nbSat) {
1006                                             //we check that the number of Sat in the observation is consistent
1007                                             throw new OrekitException(OrekitMessages.INCONSISTENT_NUMBER_OF_SATS,
1008                                                                       lineNumber, name, nbSatObs, nbSat);
1009                                         }
1010                                         //Read the Receiver Clock offset, if present
1011                                         rcvrClkOffset = parseDouble(41, 15);
1012                                         if (Double.isNaN(rcvrClkOffset)) {
1013                                             rcvrClkOffset = 0.0;
1014                                         }
1015 
1016                                         //For each one of the Satellites in this Observation
1017                                         for (int i = 0; i < nbSatObs; i++) {
1018 
1019                                             readLine(reader, true);
1020 
1021                                             //We check that the Satellite type is consistent with Satellite System in the top of the file
1022                                             final SatelliteSystem satelliteSystemSat = SatelliteSystem.parseSatelliteSystem(parseString(0, 1));
1023                                             if (!satelliteSystem.equals(SatelliteSystem.MIXED)) {
1024                                                 if (!satelliteSystemSat.equals(satelliteSystem)) {
1025                                                     throw new OrekitException(OrekitMessages.INCONSISTENT_SATELLITE_SYSTEM,
1026                                                                               lineNumber, name, satelliteSystem, satelliteSystemSat);
1027                                                 }
1028                                             }
1029 
1030                                             final int prn = parseInt(1, 2);
1031                                             final int prnNumber;
1032                                             switch (satelliteSystemSat) {
1033                                                 case GPS:
1034                                                 case GLONASS:
1035                                                 case GALILEO:
1036                                                 case BEIDOU:
1037                                                 case IRNSS:
1038                                                     prnNumber = prn;
1039                                                     break;
1040                                                 case QZSS:
1041                                                     prnNumber = prn + 192;
1042                                                     break;
1043                                                 case SBAS:
1044                                                     prnNumber = prn + 100;
1045                                                     break;
1046                                                 default:
1047                                                     // MIXED satellite system is not allowed here
1048                                                     throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
1049                                                                               lineNumber, name, line);
1050                                             }
1051                                             final List<ObservationData> observationData = new ArrayList<>(nbSatObs);
1052                                             for (int j = 0; j < listTypeObs.get(satelliteSystemSat).size(); j++) {
1053                                                 final ObservationType rf = listTypeObs.get(satelliteSystemSat).get(j);
1054                                                 boolean scaleFactorFound = false;
1055                                                 //We look for the lines of ScaledFactorCorrections that correspond to this SatSystem
1056                                                 int k = 0;
1057                                                 double value = parseDouble(3 + j * 16, 14);
1058                                                 while (k < scaleFactorCorrections.size() && !scaleFactorFound) {
1059                                                     if (scaleFactorCorrections.get(k).getSatelliteSystem().equals(satelliteSystemSat)) {
1060                                                         //We check if the next Observation Type to read needs to be scaled
1061                                                         if (scaleFactorCorrections.get(k).getTypesObsScaled().contains(rf)) {
1062                                                             value /= scaleFactorCorrections.get(k).getCorrection();
1063                                                             scaleFactorFound = true;
1064                                                         }
1065                                                     }
1066                                                     k++;
1067                                                 }
1068                                                 observationData.add(new ObservationData(rf,
1069                                                                                         value,
1070                                                                                         parseInt(17 + j * 16, 1),
1071                                                                                         parseInt(18 + j * 16, 1)));
1072                                             }
1073                                             observationDataSets.add(new ObservationDataSet(rinexHeader, satelliteSystemSat, prnNumber,
1074                                                                                            tObs, rcvrClkOffset, observationData));
1075 
1076                                         }
1077                                     }
1078                                 }
1079                             }
1080                         }
1081                         break;
1082                     }
1083                     default:
1084                         //If RINEX Version is neither 2 nor 3
1085                         throw new OrekitException(OrekitMessages.UNSUPPORTED_FILE_FORMAT, name);
1086                 }
1087             }
1088         }
1089 
1090         /** Read a new line.
1091          * @param reader reader from where to read line
1092          * @param complainIfEnd if true an exception should be thrown if end of file is encountered
1093          * @return true if a line has been read
1094          * @exception IOException if a read error occurs
1095          */
1096         private boolean readLine(final BufferedReader reader, final boolean complainIfEnd)
1097             throws IOException {
1098             line = reader.readLine();
1099             if (line == null && complainIfEnd) {
1100                 throw new OrekitException(OrekitMessages.UNEXPECTED_END_OF_FILE, name);
1101             }
1102             lineNumber++;
1103             return line != null;
1104         }
1105 
1106         /** Extract a string from a line.
1107          * @param start start index of the string
1108          * @param length length of the string
1109          * @return parsed string
1110          */
1111         private String parseString(final int start, final int length) {
1112             if (line.length() > start) {
1113                 return line.substring(start, FastMath.min(line.length(), start + length)).trim();
1114             } else {
1115                 return null;
1116             }
1117         }
1118 
1119         /** Extract an integer from a line.
1120          * @param start start index of the integer
1121          * @param length length of the integer
1122          * @return parsed integer
1123          */
1124         private int parseInt(final int start, final int length) {
1125             if (line.length() > start && !parseString(start, length).isEmpty()) {
1126                 return Integer.parseInt(parseString(start, length));
1127             } else {
1128                 return 0;
1129             }
1130         }
1131 
1132         /** Extract a double from a line.
1133          * @param start start index of the real
1134          * @param length length of the real
1135          * @return parsed real, or {@code Double.NaN} if field was empty
1136          */
1137         private double parseDouble(final int start, final int length) {
1138             if (line.length() > start && !parseString(start, length).isEmpty()) {
1139                 return Double.parseDouble(parseString(start, length));
1140             } else {
1141                 return Double.NaN;
1142             }
1143         }
1144 
1145         /** Phase Shift corrections.
1146          * Contains the phase shift corrections used to
1147          * generate phases consistent with respect to cycle shifts.
1148          */
1149         public class PhaseShiftCorrection {
1150 
1151             /** Satellite System. */
1152             private final SatelliteSystem satSystemPhaseShift;
1153             /** Carrier Phase Observation Code (may be null). */
1154             private final ObservationType typeObsPhaseShift;
1155             /** Phase Shift Corrections (cycles). */
1156             private final double phaseShiftCorrection;
1157             /** List of satellites involved. */
1158             private final String[] satsPhaseShift;
1159 
1160             /** Simple constructor.
1161              * @param satSystemPhaseShift Satellite System
1162              * @param typeObsPhaseShift Carrier Phase Observation Code (may be null)
1163              * @param phaseShiftCorrection Phase Shift Corrections (cycles)
1164              * @param satsPhaseShift List of satellites involved
1165              */
1166             private PhaseShiftCorrection(final SatelliteSystem satSystemPhaseShift,
1167                                          final ObservationType typeObsPhaseShift,
1168                                          final double phaseShiftCorrection, final String[] satsPhaseShift) {
1169                 this.satSystemPhaseShift = satSystemPhaseShift;
1170                 this.typeObsPhaseShift = typeObsPhaseShift;
1171                 this.phaseShiftCorrection = phaseShiftCorrection;
1172                 this.satsPhaseShift = satsPhaseShift;
1173             }
1174 
1175             /** Get the Satellite System.
1176              * @return Satellite System.
1177              */
1178             public SatelliteSystem getSatelliteSystem() {
1179                 return satSystemPhaseShift;
1180             }
1181             /** Get the Carrier Phase Observation Code.
1182              * <p>
1183              * The observation code may be null for the uncorrected reference
1184              * signal group
1185              * </p>
1186              * @return Carrier Phase Observation Code.
1187              */
1188             public ObservationType getTypeObs() {
1189                 return typeObsPhaseShift;
1190             }
1191             /** Get the Phase Shift Corrections.
1192              * @return Phase Shift Corrections (cycles)
1193              */
1194             public double getCorrection() {
1195                 return phaseShiftCorrection;
1196             }
1197             /** Get the list of satellites involved.
1198              * @return List of satellites involved (if null, all the sats are involved)
1199              */
1200             public String[] getSatsCorrected() {
1201                 //If empty, all the satellites of this constellation are affected for this Observation type
1202                 return satsPhaseShift == null ? null : satsPhaseShift.clone();
1203             }
1204         }
1205 
1206         /** Scale Factor to be applied.
1207          * Contains the scale factors of 10 applied to the data before
1208          * being stored into the RINEX file.
1209          */
1210         public class ScaleFactorCorrection {
1211 
1212             /** Satellite System. */
1213             private final SatelliteSystem satSystemScaleFactor;
1214             /** List of Observations types that have been scaled. */
1215             private final List<ObservationType> typesObsScaleFactor;
1216             /** Factor to divide stored observations with before use. */
1217             private final double scaleFactor;
1218 
1219             /** Simple constructor.
1220              * @param satSystemScaleFactor Satellite System
1221              * @param scaleFactor Factor to divide stored observations (1,10,100,1000)
1222              * @param typesObsScaleFactor List of Observations types that have been scaled
1223              */
1224             private ScaleFactorCorrection(final SatelliteSystem satSystemScaleFactor,
1225                                           final double scaleFactor,
1226                                           final List<ObservationType> typesObsScaleFactor) {
1227                 this.satSystemScaleFactor = satSystemScaleFactor;
1228                 this.scaleFactor = scaleFactor;
1229                 this.typesObsScaleFactor = typesObsScaleFactor;
1230             }
1231             /** Get the Satellite System.
1232              * @return Satellite System
1233              */
1234             public SatelliteSystem getSatelliteSystem() {
1235                 return satSystemScaleFactor;
1236             }
1237             /** Get the Scale Factor.
1238              * @return Scale Factor
1239              */
1240             public double getCorrection() {
1241                 return scaleFactor;
1242             }
1243             /** Get the list of Observation Types scaled.
1244              * @return List of Observation types scaled
1245              */
1246             public List<ObservationType> getTypesObsScaled() {
1247                 return typesObsScaleFactor;
1248             }
1249         }
1250 
1251     }
1252 
1253 }