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