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