1   /* Copyright 2002-2022 CS GROUP
2    * Licensed to CS GROUP (CS) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * CS licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.orekit.files.ilrs;
18  
19  import java.io.BufferedReader;
20  import java.io.IOException;
21  import java.util.Optional;
22  import java.util.regex.Pattern;
23  import java.util.stream.Stream;
24  
25  import org.hipparchus.util.FastMath;
26  import org.orekit.annotation.DefaultDataContext;
27  import org.orekit.data.DataContext;
28  import org.orekit.data.DataSource;
29  import org.orekit.errors.OrekitException;
30  import org.orekit.errors.OrekitMessages;
31  import org.orekit.files.ilrs.CRD.AnglesMeasurement;
32  import org.orekit.files.ilrs.CRD.CRDDataBlock;
33  import org.orekit.files.ilrs.CRD.MeteorologicalMeasurement;
34  import org.orekit.files.ilrs.CRD.RangeMeasurement;
35  import org.orekit.files.ilrs.CRDConfiguration.DetectorConfiguration;
36  import org.orekit.files.ilrs.CRDConfiguration.LaserConfiguration;
37  import org.orekit.files.ilrs.CRDConfiguration.MeteorologicalConfiguration;
38  import org.orekit.files.ilrs.CRDConfiguration.SoftwareConfiguration;
39  import org.orekit.files.ilrs.CRDConfiguration.SystemConfiguration;
40  import org.orekit.files.ilrs.CRDConfiguration.TimingSystemConfiguration;
41  import org.orekit.files.ilrs.CRDConfiguration.TransponderConfiguration;
42  import org.orekit.time.AbsoluteDate;
43  import org.orekit.time.DateComponents;
44  import org.orekit.time.TimeComponents;
45  import org.orekit.time.TimeScale;
46  import org.orekit.utils.units.Unit;
47  import org.orekit.utils.units.UnitsConverter;
48  
49  /**
50   * A parser for the CRD data file format.
51   * <p>
52   * It supports both 1.0 and 2.0 versions
53   * <p>
54   * <b>Note</b>: Not all the records are read by the parser. Only the most significants are parsed.
55   * Contributions are welcome to support more fields in the format.
56   * @see <a href="https://ilrs.gsfc.nasa.gov/docs/2009/crd_v1.01.pdf">1.0 file format</a>
57   * @see <a href="https://ilrs.gsfc.nasa.gov/docs/2019/crd_v2.01.pdf">2.0 file format</a>
58   * @author Bryan Cazabonne
59   * @since 10.3
60   */
61  public class CRDParser {
62  
63      /** Default supported files name pattern for CRD files. */
64      public static final String DEFAULT_CRD_SUPPORTED_NAMES = "^(?!0+$)\\w{1,12}\\_\\d{6,8}.\\w{3}$";
65  
66      /** Nanometers units. */
67      private static final Unit NM = Unit.parse("nm");
68  
69      /** Kilohertz units. */
70      private static final Unit KHZ = Unit.parse("kHz");
71  
72      /** Microseconds units. */
73      private static final Unit US = Unit.parse("µs");
74  
75      /** mbar to bar converter. */
76      private static final UnitsConverter MBAR_TO_BAR = new UnitsConverter(Unit.parse("mbar"), Unit.parse("bar"));
77  
78      /** File format. */
79      private static final String FILE_FORMAT = "CRD";
80  
81      /** Pattern for delimiting regular expressions. */
82      private static final Pattern SEPARATOR = Pattern.compile("\\s+");
83  
84      /** Pattern for delimiting expressions with comma. */
85      private static final Pattern COMMA = Pattern.compile(",");
86  
87      /** Time scale used to define epochs in CPF file. */
88      private final TimeScale timeScale;
89  
90      /**
91       * Default constructor.
92       * <p>
93       * This constructor uses the {@link DataContext#getDefault() default data context}.
94       */
95      @DefaultDataContext
96      public CRDParser() {
97          this(DataContext.getDefault().getTimeScales().getUTC());
98      }
99  
100     /**
101      * Constructor.
102      * @param utc utc time scale to read epochs
103      */
104     public CRDParser(final TimeScale utc) {
105         this.timeScale = utc;
106     }
107 
108     /**
109      * Get the time scale used to read the file.
110      * @return the time scale used to read the file
111      */
112     public TimeScale getTimeScale() {
113         return timeScale;
114     }
115 
116     /**
117      * Parse a CRD file.
118      * @param source data source containing the CRD file.
119      * @return a parsed CRD file.
120      * @throws IOException if {@code reader} throws one.
121      */
122     public CRD parse(final DataSource source) throws IOException {
123 
124         // Initialize internal data structures
125         final ParseInfo pi = new ParseInfo();
126 
127         int lineNumber = 0;
128         Stream<LineParser> cdrParsers = Stream.of(LineParser.H1);
129         try (BufferedReader reader = new BufferedReader(source.getOpener().openReaderOnce())) {
130             for (String line = reader.readLine(); line != null; line = reader.readLine()) {
131                 ++lineNumber;
132                 final String l = line;
133                 final Optional<LineParser> selected = cdrParsers.filter(p -> p.canHandle(l)).findFirst();
134                 if (selected.isPresent()) {
135                     try {
136                         selected.get().parse(line, pi);
137                     } catch (StringIndexOutOfBoundsException | NumberFormatException e) {
138                         throw new OrekitException(e,
139                                                   OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
140                                                   lineNumber, source.getName(), line);
141                     }
142                     cdrParsers = selected.get().allowedNext();
143                 }
144                 if (pi.done) {
145                     // Return file
146                     return pi.file;
147                 }
148             }
149         }
150 
151         // We never reached the EOF marker
152         throw new OrekitException(OrekitMessages.CRD_UNEXPECTED_END_OF_FILE, lineNumber);
153 
154     }
155 
156     /** Transient data used for parsing a CRD file. The data is kept in a
157      * separate data structure to make the parser thread-safe.
158      * <p><b>Note</b>: The class intentionally does not provide accessor
159      * methods, as it is only used internally for parsing a CRD file.</p>
160      */
161     private class ParseInfo {
162 
163         /** The corresponding CDR file. */
164         private CRD file;
165 
166         /** Version. */
167         private int version;
168 
169         /** The current data block. */
170         private CRDDataBlock dataBlock;
171 
172         /** Data block header. */
173         private CRDHeader header;
174 
175         /** Cofiguration records. */
176         private CRDConfiguration configurationRecords;
177 
178         /** Time scale. */
179         private TimeScale timeScale;
180 
181         /** Current data block start epoch. */
182         private DateComponents startEpoch;
183 
184         /** End Of File reached indicator. */
185         private boolean done;
186 
187         /**
188          * Constructor.
189          */
190         protected ParseInfo() {
191 
192             // Initialise default values
193             this.done       = false;
194             this.version    = 1;
195             this.startEpoch = DateComponents.J2000_EPOCH;
196 
197             // Initialise empty object
198             this.file                 = new CRD();
199             this.header               = new CRDHeader();
200             this.configurationRecords = new CRDConfiguration();
201             this.dataBlock            = new CRDDataBlock();
202 
203             // Time scale
204             this.timeScale = CRDParser.this.timeScale;
205 
206         }
207 
208     }
209 
210     /** Parsers for specific lines. */
211     private enum LineParser {
212 
213         /** Format header. */
214         H1("H1", "h1") {
215 
216             /** {@inheritDoc} */
217             @Override
218             public void parse(final String line, final ParseInfo pi) {
219 
220                 // Data contained in the line
221                 final String[] values = SEPARATOR.split(line);
222 
223                 // Format and version
224                 final String format = values[1];
225                 pi.version = Integer.parseInt(values[2]);
226 
227                 // Throw an exception if format is not equal to "CRD"
228                 if (!format.equalsIgnoreCase(FILE_FORMAT)) {
229                     throw new OrekitException(OrekitMessages.UNEXPECTED_FORMAT_FOR_ILRS_FILE, FILE_FORMAT, format);
230                 }
231 
232                 // Fill first elements
233                 pi.header.setFormat(format);
234                 pi.header.setVersion(pi.version);
235 
236                 // Epoch of ephemeris production
237                 final int year  = Integer.parseInt(values[3]);
238                 final int month = Integer.parseInt(values[4]);
239                 final int day   = Integer.parseInt(values[5]);
240                 pi.header.setProductionEpoch(new DateComponents(year, month, day));
241 
242                 // Hour of ephemeris production
243                 pi.header.setProductionHour(Integer.parseInt(values[6]));
244 
245             }
246 
247             /** {@inheritDoc} */
248             @Override
249             public Stream<LineParser> allowedNext() {
250                 return Stream.of(H2, COMMENTS);
251             }
252 
253         },
254 
255         /** Format header. */
256         H2("H2", "h2") {
257 
258             /** {@inheritDoc} */
259             @Override
260             public void parse(final String line, final ParseInfo pi) {
261 
262                 // Data contained in the line
263                 final String[] values = SEPARATOR.split(line);
264 
265                 // Station name
266                 pi.header.setStationName(values[1]);
267 
268                 // Crustal Dynamics Project keys
269                 pi.header.setSystemIdentifier(Integer.parseInt(values[2]));
270                 pi.header.setSystemNumber(Integer.parseInt(values[3]));
271                 pi.header.setSystemOccupancy(Integer.parseInt(values[4]));
272 
273                 // Station epoch time scale
274                 pi.header.setEpochIdentifier(Integer.parseInt(values[5]));
275 
276                 // Station network
277                 if (pi.version == 2) {
278                     pi.header.setStationNetword(values[6]);
279                 }
280 
281             }
282 
283             /** {@inheritDoc} */
284             @Override
285             public Stream<LineParser> allowedNext() {
286                 return Stream.of(H3, COMMENTS);
287             }
288 
289         },
290 
291         /** Format header. */
292         H3("H3", "h3") {
293 
294             /** {@inheritDoc} */
295             @Override
296             public void parse(final String line, final ParseInfo pi) {
297 
298                 // Data contained in the line
299                 final String[] values = SEPARATOR.split(line);
300 
301                 // Target name
302                 pi.header.setName(values[1]);
303 
304                 // Identifiers
305                 pi.header.setIlrsSatelliteId(values[2]);
306                 pi.header.setSic(values[3]);
307                 pi.header.setNoradId(values[4]);
308 
309                 // Spacecraft Epoch Time Scale
310                 pi.header.setSpacecraftEpochTimeScale(Integer.parseInt(values[5]));
311 
312                 // Target class and location (if needed)
313                 pi.header.setTargetClass(Integer.parseInt(values[6]));
314                 if (pi.version == 2) {
315                     pi.header.setTargetLocation(Integer.parseInt(values[7]));
316                 }
317 
318             }
319 
320             /** {@inheritDoc} */
321             @Override
322             public Stream<LineParser> allowedNext() {
323                 return Stream.of(H4, COMMENTS);
324             }
325 
326         },
327 
328         /** Format header. */
329         H4("H4", "h4") {
330 
331             /** {@inheritDoc} */
332             @Override
333             public void parse(final String line, final ParseInfo pi) {
334 
335                 // Data contained in the line
336                 final String[] values = SEPARATOR.split(line);
337 
338                 // Data type
339                 pi.header.setDataType(Integer.parseInt(values[1]));
340 
341                 // Start epoch
342                 final int    yearS   = Integer.parseInt(values[2]);
343                 final int    monthS  = Integer.parseInt(values[3]);
344                 final int    dayS    = Integer.parseInt(values[4]);
345                 final int    hourS   = Integer.parseInt(values[5]);
346                 final int    minuteS = Integer.parseInt(values[6]);
347                 final double secondS = Integer.parseInt(values[7]);
348 
349                 pi.startEpoch = new DateComponents(yearS, monthS, dayS);
350 
351                 pi.header.setStartEpoch(new AbsoluteDate(yearS, monthS, dayS,
352                                                          hourS, minuteS, secondS,
353                                                          pi.timeScale));
354 
355                 // End epoch
356                 final int    yearE   = Integer.parseInt(values[8]);
357                 final int    monthE  = Integer.parseInt(values[9]);
358                 final int    dayE    = Integer.parseInt(values[10]);
359                 final int    hourE   = Integer.parseInt(values[11]);
360                 final int    minuteE = Integer.parseInt(values[12]);
361                 final double secondE = Integer.parseInt(values[13]);
362 
363                 pi.header.setEndEpoch(new AbsoluteDate(yearE, monthE, dayE,
364                                                        hourE, minuteE, secondE,
365                                                        pi.timeScale));
366 
367                 // Data release
368                 pi.header.setDataReleaseFlag(Integer.parseInt(values[14]));
369 
370                 // Correction flags
371                 pi.header.setIsTroposphericRefractionApplied(readBoolean(values[15]));
372                 pi.header.setIsCenterOfMassCorrectionApplied(readBoolean(values[16]));
373                 pi.header.setIsReceiveAmplitudeCorrectionApplied(readBoolean(values[17]));
374                 pi.header.setIsStationSystemDelayApplied(readBoolean(values[18]));
375                 pi.header.setIsTransponderDelayApplied(readBoolean(values[19]));
376 
377                 // Range type indicator
378                 pi.header.setRangeType(Integer.parseInt(values[20]));
379 
380                 // Data quality indicator
381                 pi.header.setQualityIndicator(Integer.parseInt(values[21]));
382 
383             }
384 
385             /** {@inheritDoc} */
386             @Override
387             public Stream<LineParser> allowedNext() {
388                 return Stream.of(H5, C0, COMMENTS);
389             }
390 
391         },
392 
393         /** Format header. */
394         H5("H5", "h5") {
395 
396             /** {@inheritDoc} */
397             @Override
398             public void parse(final String line, final ParseInfo pi) {
399 
400                 // Data contained in the line
401                 final String[] values = SEPARATOR.split(line);
402 
403                 // Fill data
404                 pi.header.setPredictionType(Integer.parseInt(values[1]));
405                 pi.header.setYearOfCentury(Integer.parseInt(values[2]));
406                 pi.header.setDateAndTime(values[3]);
407                 pi.header.setPredictionProvider(values[4]);
408                 pi.header.setSequenceNumber(Integer.parseInt(values[5]));
409 
410             }
411 
412             /** {@inheritDoc} */
413             @Override
414             public Stream<LineParser> allowedNext() {
415                 return Stream.of(C0, COMMENTS);
416             }
417 
418         },
419 
420         /** System configuration record. */
421         C0("C0", "c0") {
422 
423             /** {@inheritDoc} */
424             @Override
425             public void parse(final String line, final ParseInfo pi) {
426 
427                 // Initialise an empty system configuration record
428                 final SystemConfiguration systemRecord = new SystemConfiguration();
429 
430                 // Data contained in the line
431                 final String[] values = SEPARATOR.split(line);
432 
433                 // Wavelength
434                 systemRecord.setWavelength(NM.toSI(Double.parseDouble(values[2])));
435 
436                 // System ID
437                 systemRecord.setSystemId(values[3]);
438 
439                 // Set the system configuration record
440                 pi.configurationRecords.setSystemRecord(systemRecord);
441 
442             }
443 
444             /** {@inheritDoc} */
445             @Override
446             public Stream<LineParser> allowedNext() {
447                 return Stream.of(C1, C2, C3, C4, C5, C6, C7, TEN, ELEVEN, METEO, ANGLES, CALIB, STAT, COMPATIBILITY, COMMENTS);
448             }
449 
450         },
451 
452 
453         /** Laser configuration record. */
454         C1("C1", "c1") {
455 
456             /** {@inheritDoc} */
457             @Override
458             public void parse(final String line, final ParseInfo pi) {
459 
460                 // Initialise an empty laser configuration record
461                 final LaserConfiguration laserRecord = new LaserConfiguration();
462 
463                 // Data contained in the line
464                 final String[] values = SEPARATOR.split(line);
465 
466                 // Fill values
467                 laserRecord.setLaserId(values[2]);
468                 laserRecord.setLaserType(values[3]);
469                 laserRecord.setPrimaryWavelength(NM.toSI(Double.parseDouble(values[4])));
470                 laserRecord.setNominalFireRate(Double.parseDouble(values[5]));
471                 laserRecord.setPulseEnergy(Double.parseDouble(values[6]));
472                 laserRecord.setPulseWidth(Double.parseDouble(values[7]));
473                 laserRecord.setBeamDivergence(Double.parseDouble(values[8]));
474                 laserRecord.setPulseInOutgoingSemiTrain(Integer.parseInt(values[9]));
475 
476                 // Set the laser configuration record
477                 pi.configurationRecords.setLaserRecord(laserRecord);
478 
479             }
480 
481             /** {@inheritDoc} */
482             @Override
483             public Stream<LineParser> allowedNext() {
484                 return Stream.of(C2, C3, C4, C5, C6, C7, TEN, ELEVEN, METEO, ANGLES, CALIB, STAT, COMPATIBILITY, COMMENTS);
485             }
486 
487         },
488 
489         /** Detector configuration record. */
490         C2("C2", "c2") {
491 
492             /** {@inheritDoc} */
493             @Override
494             public void parse(final String line, final ParseInfo pi) {
495 
496                 // Initialise an empty detector configuration record
497                 final DetectorConfiguration detectorRecord = new DetectorConfiguration();
498 
499                 // Data contained in the line
500                 final String[] values = SEPARATOR.split(line);
501 
502                 // Fill values
503                 detectorRecord.setDetectorId(values[2]);
504                 detectorRecord.setDetectorType(values[3]);
505                 detectorRecord.setApplicableWavelength(NM.toSI(Double.parseDouble(values[4])));
506                 detectorRecord.setQuantumEfficiency(Double.parseDouble(values[5]));
507                 detectorRecord.setAppliedVoltage(Double.parseDouble(values[6]));
508                 detectorRecord.setDarkCount(KHZ.toSI(Double.parseDouble(values[7])));
509                 detectorRecord.setOutputPulseType(values[8]);
510                 detectorRecord.setOutputPulseWidth(Double.parseDouble(values[9]));
511                 detectorRecord.setSpectralFilter(NM.toSI(Double.parseDouble(values[10])));
512                 detectorRecord.setTransmissionOfSpectralFilter(Double.parseDouble(values[11]));
513                 detectorRecord.setSpatialFilter(Double.parseDouble(values[12]));
514                 detectorRecord.setExternalSignalProcessing(values[13]);
515 
516                 // Check file version for additional data
517                 if (pi.version == 2) {
518                     detectorRecord.setAmplifierGain(Double.parseDouble(values[14]));
519                     detectorRecord.setAmplifierBandwidth(KHZ.toSI(Double.parseDouble(values[15])));
520                     detectorRecord.setAmplifierInUse(values[16]);
521                 }
522 
523                 // Set the detector configuration record
524                 pi.configurationRecords.setDetectorRecord(detectorRecord);
525 
526             }
527 
528             /** {@inheritDoc} */
529             @Override
530             public Stream<LineParser> allowedNext() {
531                 return Stream.of(C3, C4, C5, C6, C7, TEN, ELEVEN, METEO, ANGLES, CALIB, STAT, COMPATIBILITY, COMMENTS);
532             }
533 
534         },
535 
536         /** Timing system configuration record. */
537         C3("C3", "c3") {
538 
539             /** {@inheritDoc} */
540             @Override
541             public void parse(final String line, final ParseInfo pi) {
542 
543                 // Initialise an empty timing system configuration record
544                 final TimingSystemConfiguration timingRecord = new TimingSystemConfiguration();
545 
546                 // Data contained in the line
547                 final String[] values = SEPARATOR.split(line);
548 
549                 // Fill values
550                 timingRecord.setLocalTimingId(values[2]);
551                 timingRecord.setTimeSource(values[3]);
552                 timingRecord.setFrequencySource(values[4]);
553                 timingRecord.setTimer(values[5]);
554                 timingRecord.setTimerSerialNumber(values[6]);
555                 timingRecord.setEpochDelayCorrection(US.toSI(Double.parseDouble(values[7])));
556 
557                 // Set the timing system configuration record
558                 pi.configurationRecords.setTimingRecord(timingRecord);
559 
560             }
561 
562             /** {@inheritDoc} */
563             @Override
564             public Stream<LineParser> allowedNext() {
565                 return Stream.of(C4, C5, C6, C7, TEN, ELEVEN, METEO, ANGLES, CALIB, STAT, COMPATIBILITY, COMMENTS);
566             }
567 
568         },
569 
570         /** Transponder configuration record. */
571         C4("C4", "c4") {
572 
573             /** {@inheritDoc} */
574             @Override
575             public void parse(final String line, final ParseInfo pi) {
576 
577                 // Initialise an empty transponder configuration record
578                 final TransponderConfiguration transponderRecord = new TransponderConfiguration();
579 
580                 // Data contained in the line
581                 final String[] values = SEPARATOR.split(line);
582 
583                 // Estimated offsets and drifts
584                 transponderRecord.setTransponderId(values[2]);
585                 transponderRecord.setStationUTCOffset(NM.toSI(Double.parseDouble(values[3])));
586                 transponderRecord.setStationOscDrift(Double.parseDouble(values[4]));
587                 transponderRecord.setTranspUTCOffset(NM.toSI(Double.parseDouble(values[5])));
588                 transponderRecord.setTranspOscDrift(Double.parseDouble(values[6]));
589 
590                 // Transponder clock reference time
591                 transponderRecord.setTranspClkRefTime(Double.parseDouble(values[7]));
592 
593                 // Clock and drift indicators
594                 transponderRecord.setStationClockAndDriftApplied(Integer.parseInt(values[8]));
595                 transponderRecord.setSpacecraftClockAndDriftApplied(Integer.parseInt(values[9]));
596 
597                 // Spacecraft time simplified
598                 transponderRecord.setIsSpacecraftTimeSimplified(readBoolean(values[10]));
599 
600                 // Set the transponder configuration record
601                 pi.configurationRecords.setTransponderRecord(transponderRecord);
602 
603             }
604 
605             /** {@inheritDoc} */
606             @Override
607             public Stream<LineParser> allowedNext() {
608                 return Stream.of(C5, C6, C7, TEN, ELEVEN, METEO, ANGLES, CALIB, STAT, COMPATIBILITY, COMMENTS);
609             }
610 
611         },
612 
613         /** Software configuration record. */
614         C5("C5", "c5") {
615 
616             /** {@inheritDoc} */
617             @Override
618             public void parse(final String line, final ParseInfo pi) {
619 
620                 // Initialise an empty software configuration record
621                 final SoftwareConfiguration softwareRecord = new SoftwareConfiguration();
622 
623                 // Data contained in the line
624                 final String[] values = SEPARATOR.split(line);
625 
626                 // Fill values
627                 softwareRecord.setSoftwareId(values[2]);
628                 softwareRecord.setTrackingSoftwares(COMMA.split(values[3]));
629                 softwareRecord.setTrackingSoftwareVersions(COMMA.split(values[4]));
630                 softwareRecord.setProcessingSoftwares(COMMA.split(values[5]));
631                 softwareRecord.setProcessingSoftwareVersions(COMMA.split(values[6]));
632 
633                 // Set the software configuration record
634                 pi.configurationRecords.setSoftwareRecord(softwareRecord);
635 
636             }
637 
638             /** {@inheritDoc} */
639             @Override
640             public Stream<LineParser> allowedNext() {
641                 return Stream.of(C6, C7, TEN, ELEVEN, METEO, ANGLES, CALIB, STAT, COMPATIBILITY, COMMENTS);
642             }
643 
644         },
645 
646         /** Meteorological instrumentation configuration record. */
647         C6("C6", "c6") {
648 
649             /** {@inheritDoc} */
650             @Override
651             public void parse(final String line, final ParseInfo pi) {
652 
653                 // Initialise an empty meteorological configuration record
654                 final MeteorologicalConfiguration meteoRecord = new MeteorologicalConfiguration();
655 
656                 // Data contained in the line
657                 final String[] values = SEPARATOR.split(line);
658 
659                 // Fill values
660                 meteoRecord.setMeteorologicalId(values[2]);
661                 meteoRecord.setPressSensorManufacturer(values[3]);
662                 meteoRecord.setPressSensorModel(values[4]);
663                 meteoRecord.setPressSensorSerialNumber(values[5]);
664                 meteoRecord.setTempSensorManufacturer(values[6]);
665                 meteoRecord.setTempSensorModel(values[7]);
666                 meteoRecord.setTempSensorSerialNumber(values[8]);
667                 meteoRecord.setHumiSensorManufacturer(values[9]);
668                 meteoRecord.setHumiSensorModel(values[10]);
669                 meteoRecord.setHumiSensorSerialNumber(values[11]);
670 
671                 // Set the meteorological configuration record
672                 pi.configurationRecords.setMeteorologicalRecord(meteoRecord);
673 
674             }
675 
676             /** {@inheritDoc} */
677             @Override
678             public Stream<LineParser> allowedNext() {
679                 return Stream.of(C7, TEN, ELEVEN, METEO, ANGLES, CALIB, STAT, COMPATIBILITY, COMMENTS);
680             }
681 
682         },
683 
684         /** Calibration Target configuration record. */
685         C7("C7", "c7") {
686 
687             /** {@inheritDoc} */
688             @Override
689             public void parse(final String line, final ParseInfo pi) {
690                 // Not implemented yet
691             }
692 
693             /** {@inheritDoc} */
694             @Override
695             public Stream<LineParser> allowedNext() {
696                 return Stream.of(TEN, ELEVEN, METEO, ANGLES, CALIB, STAT, COMPATIBILITY, COMMENTS);
697             }
698 
699         },
700 
701         /** Range Record (Full rate, Sampled Engineering/Quicklook). */
702         TEN("10") {
703 
704             /** {@inheritDoc} */
705             @Override
706             public void parse(final String line, final ParseInfo pi) {
707 
708                 // Data contained in the line
709                 final String[] values = SEPARATOR.split(line);
710 
711                 // Read data
712                 final double secOfDay     = Double.parseDouble(values[1]);
713                 final double timeOfFlight = Double.parseDouble(values[2]);
714                 final int    epochEvent   = Integer.parseInt(values[4]);
715 
716                 // Initialise a new Range measurement
717                 final AbsoluteDate epoch = new AbsoluteDate(pi.startEpoch, new TimeComponents(secOfDay), pi.timeScale);
718                 final RangeMeasurement range = new RangeMeasurement(epoch, timeOfFlight, epochEvent);
719                 pi.dataBlock.addRangeData(range);
720 
721             }
722 
723             /** {@inheritDoc} */
724             @Override
725             public Stream<LineParser> allowedNext() {
726                 return Stream.of(H8, TEN, TWELVE, METEO, ANGLES, CALIB, STAT, COMPATIBILITY, COMMENTS);
727             }
728 
729         },
730 
731         /** Range Record (Normal point). */
732         ELEVEN("11") {
733 
734             /** {@inheritDoc} */
735             @Override
736             public void parse(final String line, final ParseInfo pi) {
737 
738                 // Data contained in the line
739                 final String[] values = SEPARATOR.split(line);
740 
741                 // Read data
742                 final double secOfDay     = Double.parseDouble(values[1]);
743                 final double timeOfFlight = Double.parseDouble(values[2]);
744                 final int    epochEvent   = Integer.parseInt(values[4]);
745                 final double snr          = (pi.version == 2) ? Double.parseDouble(values[13]) : Double.NaN;
746 
747                 // Initialise a new Range measurement
748                 final AbsoluteDate epoch = new AbsoluteDate(pi.startEpoch, new TimeComponents(secOfDay), pi.timeScale);
749                 final RangeMeasurement range = new RangeMeasurement(epoch, timeOfFlight, epochEvent, snr);
750                 pi.dataBlock.addRangeData(range);
751 
752             }
753 
754             /** {@inheritDoc} */
755             @Override
756             public Stream<LineParser> allowedNext() {
757                 return Stream.of(H8, ELEVEN, TWELVE, METEO, ANGLES, CALIB, STAT, COMPATIBILITY, COMMENTS);
758             }
759 
760         },
761 
762         /** Range Supplement Record. */
763         TWELVE("12") {
764 
765             /** {@inheritDoc} */
766             @Override
767             public void parse(final String line, final ParseInfo pi) {
768                 // Not implemented yet
769             }
770 
771             /** {@inheritDoc} */
772             @Override
773             public Stream<LineParser> allowedNext() {
774                 return Stream.of(H8, TEN, ELEVEN, TWELVE, METEO, ANGLES, CALIB, STAT, COMPATIBILITY, COMMENTS);
775             }
776 
777         },
778 
779         /** Meteorological record. */
780         METEO("20") {
781 
782             /** {@inheritDoc} */
783             @Override
784             public void parse(final String line, final ParseInfo pi) {
785 
786                 // Data contained in the line
787                 final String[] values = SEPARATOR.split(line);
788 
789                 // Read data
790                 final double secOfDay    = Double.parseDouble(values[1]);
791                 final double pressure    = MBAR_TO_BAR.convert(Double.parseDouble(values[2]));
792                 final double temperature = Double.parseDouble(values[3]);
793                 final double humidity    = Double.parseDouble(values[4]);
794 
795                 // Initialise a new meteorological measurement
796                 final AbsoluteDate epoch = new AbsoluteDate(pi.startEpoch, new TimeComponents(secOfDay), pi.timeScale);
797                 final MeteorologicalMeasurement meteo = new MeteorologicalMeasurement(epoch, pressure,
798                                                                                       temperature, humidity);
799                 pi.dataBlock.addMeteoData(meteo);
800 
801             }
802 
803             /** {@inheritDoc} */
804             @Override
805             public Stream<LineParser> allowedNext() {
806                 return Stream.of(H8, METEO, METEO_SUPP, TEN, ELEVEN, TWELVE, ANGLES, CALIB, STAT, COMPATIBILITY, COMMENTS);
807             }
808 
809         },
810 
811         /** Meteorological Supplement record. */
812         METEO_SUPP("21") {
813 
814             /** {@inheritDoc} */
815             @Override
816             public void parse(final String line, final ParseInfo pi) {
817                 // Not implemented yet
818             }
819 
820             /** {@inheritDoc} */
821             @Override
822             public Stream<LineParser> allowedNext() {
823                 return Stream.of(H8, METEO, METEO_SUPP, TEN, ELEVEN, TWELVE, ANGLES, CALIB, STAT, COMPATIBILITY, COMMENTS);
824             }
825 
826         },
827 
828         /** Pointing Angle Record. */
829         ANGLES("30") {
830 
831             /** {@inheritDoc} */
832             @Override
833             public void parse(final String line, final ParseInfo pi) {
834 
835                 // Data contained in the line
836                 final String[] values = SEPARATOR.split(line);
837 
838                 // Read data
839                 final double  secOfDay              = Double.parseDouble(values[1]);
840                 final double  azmiuth               = FastMath.toRadians(Double.parseDouble(values[2]));
841                 final double  elevation             = FastMath.toRadians(Double.parseDouble(values[3]));
842                 final int     directionFlag         = Integer.parseInt(values[4]);
843                 final int     orginFlag             = Integer.parseInt(values[5]);
844                 final boolean isRefractionCorrected = readBoolean(values[6]);
845 
846 
847                 // Angles rates
848                 double azimuthRate   = Double.NaN;
849                 double elevationRate = Double.NaN;
850                 if (pi.version == 2) {
851                     azimuthRate   = readDoubleWithNaN(values[7]);
852                     elevationRate = readDoubleWithNaN(values[8]);
853                 }
854 
855                 // Initialise a new angles measurement
856                 final AbsoluteDate epoch = new AbsoluteDate(pi.startEpoch, new TimeComponents(secOfDay), pi.timeScale);
857                 final AnglesMeasurement angles = new AnglesMeasurement(epoch, azmiuth, elevation,
858                                                                        directionFlag, orginFlag,
859                                                                        isRefractionCorrected,
860                                                                        azimuthRate, elevationRate);
861                 pi.dataBlock.addAnglesData(angles);
862 
863             }
864 
865             /** {@inheritDoc} */
866             @Override
867             public Stream<LineParser> allowedNext() {
868                 return Stream.of(H8, METEO, TEN, ELEVEN, ANGLES, CALIB, STAT, COMPATIBILITY, COMMENTS);
869             }
870 
871         },
872 
873         /** Calibration Record. */
874         CALIB("40") {
875 
876             /** {@inheritDoc} */
877             @Override
878             public void parse(final String line, final ParseInfo pi) {
879                 // Not implemented yet
880             }
881 
882             /** {@inheritDoc} */
883             @Override
884             public Stream<LineParser> allowedNext() {
885                 return Stream.of(H8, METEO, CALIB, CALIB_DETAILS, CALIB_SHOT, TEN, ELEVEN, TWELVE, ANGLES, STAT, COMPATIBILITY, COMMENTS);
886             }
887 
888         },
889 
890         /** Calibration Details Record. */
891         CALIB_DETAILS("41") {
892 
893             /** {@inheritDoc} */
894             @Override
895             public void parse(final String line, final ParseInfo pi) {
896                 // Not implemented yet
897             }
898 
899             /** {@inheritDoc} */
900             @Override
901             public Stream<LineParser> allowedNext() {
902                 return Stream.of(H8, METEO, CALIB, CALIB_DETAILS, CALIB_SHOT, TEN, ELEVEN, TWELVE, ANGLES, STAT, COMPATIBILITY, COMMENTS);
903             }
904 
905         },
906 
907         /** Calibration "Shot" Record. */
908         CALIB_SHOT("42") {
909 
910             /** {@inheritDoc} */
911             @Override
912             public void parse(final String line, final ParseInfo pi) {
913                 // Not implemented yet
914             }
915 
916             /** {@inheritDoc} */
917             @Override
918             public Stream<LineParser> allowedNext() {
919                 return Stream.of(H8, METEO, CALIB, CALIB_DETAILS, CALIB_SHOT, TEN, ELEVEN, TWELVE, ANGLES, STAT, COMPATIBILITY, COMMENTS);
920             }
921 
922         },
923 
924         /** Session (Pass) Statistics Record. */
925         STAT("50") {
926 
927             /** {@inheritDoc} */
928             @Override
929             public void parse(final String line, final ParseInfo pi) {
930                 // Not implemented yet
931             }
932 
933             /** {@inheritDoc} */
934             @Override
935             public Stream<LineParser> allowedNext() {
936                 return Stream.of(H8, METEO, CALIB, CALIB_DETAILS, CALIB_SHOT, TEN, ELEVEN, TWELVE, ANGLES, STAT, COMPATIBILITY, H8, COMMENTS);
937             }
938 
939         },
940 
941         /** Compatibility record. */
942         COMPATIBILITY("60") {
943 
944             /** {@inheritDoc} */
945             @Override
946             public void parse(final String line, final ParseInfo pi) {
947                 // Not implemented yet
948             }
949 
950             /** {@inheritDoc} */
951             @Override
952             public Stream<LineParser> allowedNext() {
953                 return Stream.of(H8, METEO, CALIB, CALIB_DETAILS, CALIB_SHOT, TEN, ELEVEN, TWELVE, ANGLES, STAT, COMPATIBILITY, COMMENTS);
954             }
955 
956         },
957 
958         /** Comments. */
959         COMMENTS("00") {
960 
961             /** {@inheritDoc} */
962             @Override
963             public void parse(final String line, final ParseInfo pi) {
964 
965                 // Comment
966                 final String comment = line.split(getFirstIdentifier())[1].trim();
967                 pi.file.getComments().add(comment);
968 
969             }
970 
971             /** {@inheritDoc} */
972             @Override
973             public Stream<LineParser> allowedNext() {
974                 return Stream.of(H1, H2, H3, H4, H5, H8, H9, C0, C1, C2, C3, C4, C5, C6, C7, TEN, ELEVEN, TWELVE, METEO,
975                                  METEO_SUPP, ANGLES, CALIB, CALIB_DETAILS, CALIB_SHOT, STAT, COMPATIBILITY, COMMENTS);
976 
977             }
978 
979         },
980 
981         /** End of data block. */
982         H8("H8", "h8") {
983 
984             /** {@inheritDoc} */
985             @Override
986             public void parse(final String line, final ParseInfo pi) {
987 
988                 // Fill data block
989                 pi.dataBlock.setHeader(pi.header);
990                 pi.dataBlock.setConfigurationRecords(pi.configurationRecords);
991 
992                 // Add the data block to the CRD file
993                 pi.file.addDataBlock(pi.dataBlock);
994 
995                 // Initialize a new empty containers
996                 pi.startEpoch           = DateComponents.J2000_EPOCH;
997                 pi.header               = new CRDHeader();
998                 pi.configurationRecords = new CRDConfiguration();
999                 pi.dataBlock            = new CRDDataBlock();
1000 
1001             }
1002 
1003             /** {@inheritDoc} */
1004             @Override
1005             public Stream<LineParser> allowedNext() {
1006                 return Stream.of(H1, H9, COMMENTS);
1007             }
1008 
1009         },
1010 
1011         /** Last record in file. */
1012         H9("H9", "h9") {
1013 
1014             /** {@inheritDoc} */
1015             @Override
1016             public void parse(final String line, final ParseInfo pi) {
1017                 pi.done = true;
1018             }
1019 
1020             /** {@inheritDoc} */
1021             @Override
1022             public Stream<LineParser> allowedNext() {
1023                 return Stream.of(H9);
1024             }
1025 
1026         };
1027 
1028         /** Patterns for identifying line. */
1029         private final Pattern[] patterns;
1030 
1031         /** Identifiers. */
1032         private final String[] identifiers;
1033 
1034         /** Simple constructor.
1035          * @param identifier regular expression for identifying line (i.e. first element)
1036          */
1037         LineParser(final String... identifier) {
1038             this.identifiers = identifier;
1039             // Initialise patterns
1040             this.patterns    = new Pattern[identifiers.length];
1041             for (int index = 0; index < patterns.length; index++) {
1042                 patterns[index] = Pattern.compile(identifiers[index]);
1043             }
1044         }
1045 
1046         /**
1047          * Get the regular expression for identifying line.
1048          * @return the regular expression for identifying line
1049          */
1050         public String getFirstIdentifier() {
1051             return identifiers[0];
1052         }
1053 
1054         /** Parse a line.
1055          * @param line line to parse
1056          * @param pi holder for transient data
1057          */
1058         public abstract void parse(String line, ParseInfo pi);
1059 
1060         /** Get the allowed parsers for next line.
1061          * @return allowed parsers for next line
1062          */
1063         public abstract Stream<LineParser> allowedNext();
1064 
1065         /** Check if parser can handle line.
1066          * @param line line to parse
1067          * @return true if parser can handle the specified line
1068          */
1069         public boolean canHandle(final String line) {
1070             // Line identifier
1071             final String lineId = SEPARATOR.split(line)[0];
1072             // Loop on patterns
1073             for (Pattern pattern : patterns) {
1074                 if (pattern.matcher(lineId).matches()) {
1075                     return true;
1076                 }
1077             }
1078             // No match
1079             return false;
1080         }
1081 
1082         /**
1083          * Read a boolean from a string value.
1084          * @param value input value
1085          * @return the correspondin boolean
1086          */
1087         private static boolean readBoolean(final String value) {
1088             return Integer.parseInt(value) == 1;
1089         }
1090 
1091         /**
1092          * Read a double value taking into consideration a possible "na".
1093          * @param value input string
1094          * @return the corresponding double value
1095          */
1096         private static double readDoubleWithNaN(final String value) {
1097             return "na".equals(value) ? Double.NaN : Double.parseDouble(value);
1098         }
1099 
1100     }
1101 
1102 }