CRDParser.java

  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. import java.io.BufferedReader;
  19. import java.io.IOException;
  20. import java.util.Optional;
  21. import java.util.regex.Pattern;
  22. import java.util.stream.Stream;

  23. import org.hipparchus.util.FastMath;
  24. import org.orekit.annotation.DefaultDataContext;
  25. import org.orekit.data.DataContext;
  26. import org.orekit.data.DataSource;
  27. import org.orekit.errors.OrekitException;
  28. import org.orekit.errors.OrekitMessages;
  29. import org.orekit.files.ilrs.CRD.AnglesMeasurement;
  30. import org.orekit.files.ilrs.CRD.CRDDataBlock;
  31. import org.orekit.files.ilrs.CRD.MeteorologicalMeasurement;
  32. import org.orekit.files.ilrs.CRD.RangeMeasurement;
  33. import org.orekit.files.ilrs.CRDConfiguration.DetectorConfiguration;
  34. import org.orekit.files.ilrs.CRDConfiguration.LaserConfiguration;
  35. import org.orekit.files.ilrs.CRDConfiguration.MeteorologicalConfiguration;
  36. import org.orekit.files.ilrs.CRDConfiguration.SoftwareConfiguration;
  37. import org.orekit.files.ilrs.CRDConfiguration.SystemConfiguration;
  38. import org.orekit.files.ilrs.CRDConfiguration.TimingSystemConfiguration;
  39. import org.orekit.files.ilrs.CRDConfiguration.TransponderConfiguration;
  40. import org.orekit.time.AbsoluteDate;
  41. import org.orekit.time.DateComponents;
  42. import org.orekit.time.TimeComponents;
  43. import org.orekit.time.TimeScale;
  44. import org.orekit.utils.units.Unit;
  45. import org.orekit.utils.units.UnitsConverter;

  46. /**
  47.  * A parser for the CRD data file format.
  48.  * <p>
  49.  * It supports both 1.0 and 2.0 versions
  50.  * <p>
  51.  * <b>Note</b>: Not all the records are read by the parser. Only the most significants are parsed.
  52.  * Contributions are welcome to support more fields in the format.
  53.  * @see <a href="https://ilrs.gsfc.nasa.gov/docs/2009/crd_v1.01.pdf">1.0 file format</a>
  54.  * @see <a href="https://ilrs.gsfc.nasa.gov/docs/2019/crd_v2.01.pdf">2.0 file format</a>
  55.  * @author Bryan Cazabonne
  56.  * @since 10.3
  57.  */
  58. public class CRDParser {

  59.     /** Default supported files name pattern for CRD files. */
  60.     public static final String DEFAULT_CRD_SUPPORTED_NAMES = "^(?!0+$)\\w{1,12}\\_\\d{6,8}.\\w{3}$";

  61.     /** Nanometers units. */
  62.     private static final Unit NM = Unit.parse("nm");

  63.     /** Kilohertz units. */
  64.     private static final Unit KHZ = Unit.parse("kHz");

  65.     /** Microseconds units. */
  66.     private static final Unit US = Unit.parse("µs");

  67.     /** mbar to bar converter. */
  68.     private static final UnitsConverter MBAR_TO_BAR = new UnitsConverter(Unit.parse("mbar"), Unit.parse("bar"));

  69.     /** File format. */
  70.     private static final String FILE_FORMAT = "CRD";

  71.     /** Pattern for delimiting regular expressions. */
  72.     private static final Pattern SEPARATOR = Pattern.compile("\\s+");

  73.     /** Pattern for delimiting expressions with comma. */
  74.     private static final Pattern COMMA = Pattern.compile(",");

  75.     /** Time scale used to define epochs in CPF file. */
  76.     private final TimeScale timeScale;

  77.     /**
  78.      * Default constructor.
  79.      * <p>
  80.      * This constructor uses the {@link DataContext#getDefault() default data context}.
  81.      */
  82.     @DefaultDataContext
  83.     public CRDParser() {
  84.         this(DataContext.getDefault().getTimeScales().getUTC());
  85.     }

  86.     /**
  87.      * Constructor.
  88.      * @param utc utc time scale to read epochs
  89.      */
  90.     public CRDParser(final TimeScale utc) {
  91.         this.timeScale = utc;
  92.     }

  93.     /**
  94.      * Get the time scale used to read the file.
  95.      * @return the time scale used to read the file
  96.      */
  97.     public TimeScale getTimeScale() {
  98.         return timeScale;
  99.     }

  100.     /**
  101.      * Parse a CRD file.
  102.      * @param source data source containing the CRD file.
  103.      * @return a parsed CRD file.
  104.      * @throws IOException if {@code reader} throws one.
  105.      */
  106.     public CRD parse(final DataSource source) throws IOException {

  107.         // Initialize internal data structures
  108.         final ParseInfo pi = new ParseInfo();

  109.         int lineNumber = 0;
  110.         Stream<LineParser> cdrParsers = Stream.of(LineParser.H1);
  111.         try (BufferedReader reader = new BufferedReader(source.getOpener().openReaderOnce())) {
  112.             for (String line = reader.readLine(); line != null; line = reader.readLine()) {
  113.                 ++lineNumber;
  114.                 final String l = line;
  115.                 final Optional<LineParser> selected = cdrParsers.filter(p -> p.canHandle(l)).findFirst();
  116.                 if (selected.isPresent()) {
  117.                     try {
  118.                         selected.get().parse(line, pi);
  119.                     } catch (StringIndexOutOfBoundsException | NumberFormatException e) {
  120.                         throw new OrekitException(e,
  121.                                                   OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  122.                                                   lineNumber, source.getName(), line);
  123.                     }
  124.                     cdrParsers = selected.get().allowedNext();
  125.                 }
  126.                 if (pi.done) {
  127.                     // Return file
  128.                     return pi.file;
  129.                 }
  130.             }
  131.         }

  132.         // We never reached the EOF marker
  133.         throw new OrekitException(OrekitMessages.CRD_UNEXPECTED_END_OF_FILE, lineNumber);

  134.     }

  135.     /** Transient data used for parsing a CRD file. The data is kept in a
  136.      * separate data structure to make the parser thread-safe.
  137.      * <p><b>Note</b>: The class intentionally does not provide accessor
  138.      * methods, as it is only used internally for parsing a CRD file.</p>
  139.      */
  140.     private class ParseInfo {

  141.         /** The corresponding CDR file. */
  142.         private CRD file;

  143.         /** Version. */
  144.         private int version;

  145.         /** The current data block. */
  146.         private CRDDataBlock dataBlock;

  147.         /** Data block header. */
  148.         private CRDHeader header;

  149.         /** Cofiguration records. */
  150.         private CRDConfiguration configurationRecords;

  151.         /** Time scale. */
  152.         private TimeScale timeScale;

  153.         /** Current data block start epoch. */
  154.         private DateComponents startEpoch;

  155.         /** End Of File reached indicator. */
  156.         private boolean done;

  157.         /**
  158.          * Constructor.
  159.          */
  160.         protected ParseInfo() {

  161.             // Initialise default values
  162.             this.done       = false;
  163.             this.version    = 1;
  164.             this.startEpoch = DateComponents.J2000_EPOCH;

  165.             // Initialise empty object
  166.             this.file                 = new CRD();
  167.             this.header               = new CRDHeader();
  168.             this.configurationRecords = new CRDConfiguration();
  169.             this.dataBlock            = new CRDDataBlock();

  170.             // Time scale
  171.             this.timeScale = CRDParser.this.timeScale;

  172.         }

  173.     }

  174.     /** Parsers for specific lines. */
  175.     private enum LineParser {

  176.         /** Format header. */
  177.         H1("H1", "h1") {

  178.             /** {@inheritDoc} */
  179.             @Override
  180.             public void parse(final String line, final ParseInfo pi) {

  181.                 // Data contained in the line
  182.                 final String[] values = SEPARATOR.split(line);

  183.                 // Format and version
  184.                 final String format = values[1];
  185.                 pi.version = Integer.parseInt(values[2]);

  186.                 // Throw an exception if format is not equal to "CRD"
  187.                 if (!format.equalsIgnoreCase(FILE_FORMAT)) {
  188.                     throw new OrekitException(OrekitMessages.UNEXPECTED_FORMAT_FOR_ILRS_FILE, FILE_FORMAT, format);
  189.                 }

  190.                 // Fill first elements
  191.                 pi.header.setFormat(format);
  192.                 pi.header.setVersion(pi.version);

  193.                 // Epoch of ephemeris production
  194.                 final int year  = Integer.parseInt(values[3]);
  195.                 final int month = Integer.parseInt(values[4]);
  196.                 final int day   = Integer.parseInt(values[5]);
  197.                 pi.header.setProductionEpoch(new DateComponents(year, month, day));

  198.                 // Hour of ephemeris production
  199.                 pi.header.setProductionHour(Integer.parseInt(values[6]));

  200.             }

  201.             /** {@inheritDoc} */
  202.             @Override
  203.             public Stream<LineParser> allowedNext() {
  204.                 return Stream.of(H2, COMMENTS);
  205.             }

  206.         },

  207.         /** Format header. */
  208.         H2("H2", "h2") {

  209.             /** {@inheritDoc} */
  210.             @Override
  211.             public void parse(final String line, final ParseInfo pi) {

  212.                 // Data contained in the line
  213.                 final String[] values = SEPARATOR.split(line);

  214.                 // Station name
  215.                 pi.header.setStationName(values[1]);

  216.                 // Crustal Dynamics Project keys
  217.                 pi.header.setSystemIdentifier(Integer.parseInt(values[2]));
  218.                 pi.header.setSystemNumber(Integer.parseInt(values[3]));
  219.                 pi.header.setSystemOccupancy(Integer.parseInt(values[4]));

  220.                 // Station epoch time scale
  221.                 pi.header.setEpochIdentifier(Integer.parseInt(values[5]));

  222.                 // Station network
  223.                 if (pi.version == 2) {
  224.                     pi.header.setStationNetword(values[6]);
  225.                 }

  226.             }

  227.             /** {@inheritDoc} */
  228.             @Override
  229.             public Stream<LineParser> allowedNext() {
  230.                 return Stream.of(H3, COMMENTS);
  231.             }

  232.         },

  233.         /** Format header. */
  234.         H3("H3", "h3") {

  235.             /** {@inheritDoc} */
  236.             @Override
  237.             public void parse(final String line, final ParseInfo pi) {

  238.                 // Data contained in the line
  239.                 final String[] values = SEPARATOR.split(line);

  240.                 // Target name
  241.                 pi.header.setName(values[1]);

  242.                 // Identifiers
  243.                 pi.header.setIlrsSatelliteId(values[2]);
  244.                 pi.header.setSic(values[3]);
  245.                 pi.header.setNoradId(values[4]);

  246.                 // Spacecraft Epoch Time Scale
  247.                 pi.header.setSpacecraftEpochTimeScale(Integer.parseInt(values[5]));

  248.                 // Target class and location (if needed)
  249.                 pi.header.setTargetClass(Integer.parseInt(values[6]));
  250.                 if (pi.version == 2) {
  251.                     pi.header.setTargetLocation(Integer.parseInt(values[7]));
  252.                 }

  253.             }

  254.             /** {@inheritDoc} */
  255.             @Override
  256.             public Stream<LineParser> allowedNext() {
  257.                 return Stream.of(H4, COMMENTS);
  258.             }

  259.         },

  260.         /** Format header. */
  261.         H4("H4", "h4") {

  262.             /** {@inheritDoc} */
  263.             @Override
  264.             public void parse(final String line, final ParseInfo pi) {

  265.                 // Data contained in the line
  266.                 final String[] values = SEPARATOR.split(line);

  267.                 // Data type
  268.                 pi.header.setDataType(Integer.parseInt(values[1]));

  269.                 // Start epoch
  270.                 final int    yearS   = Integer.parseInt(values[2]);
  271.                 final int    monthS  = Integer.parseInt(values[3]);
  272.                 final int    dayS    = Integer.parseInt(values[4]);
  273.                 final int    hourS   = Integer.parseInt(values[5]);
  274.                 final int    minuteS = Integer.parseInt(values[6]);
  275.                 final double secondS = Integer.parseInt(values[7]);

  276.                 pi.startEpoch = new DateComponents(yearS, monthS, dayS);

  277.                 pi.header.setStartEpoch(new AbsoluteDate(yearS, monthS, dayS,
  278.                                                          hourS, minuteS, secondS,
  279.                                                          pi.timeScale));

  280.                 // End epoch
  281.                 final int    yearE   = Integer.parseInt(values[8]);
  282.                 final int    monthE  = Integer.parseInt(values[9]);
  283.                 final int    dayE    = Integer.parseInt(values[10]);
  284.                 final int    hourE   = Integer.parseInt(values[11]);
  285.                 final int    minuteE = Integer.parseInt(values[12]);
  286.                 final double secondE = Integer.parseInt(values[13]);

  287.                 pi.header.setEndEpoch(new AbsoluteDate(yearE, monthE, dayE,
  288.                                                        hourE, minuteE, secondE,
  289.                                                        pi.timeScale));

  290.                 // Data release
  291.                 pi.header.setDataReleaseFlag(Integer.parseInt(values[14]));

  292.                 // Correction flags
  293.                 pi.header.setIsTroposphericRefractionApplied(readBoolean(values[15]));
  294.                 pi.header.setIsCenterOfMassCorrectionApplied(readBoolean(values[16]));
  295.                 pi.header.setIsReceiveAmplitudeCorrectionApplied(readBoolean(values[17]));
  296.                 pi.header.setIsStationSystemDelayApplied(readBoolean(values[18]));
  297.                 pi.header.setIsTransponderDelayApplied(readBoolean(values[19]));

  298.                 // Range type indicator
  299.                 pi.header.setRangeType(Integer.parseInt(values[20]));

  300.                 // Data quality indicator
  301.                 pi.header.setQualityIndicator(Integer.parseInt(values[21]));

  302.             }

  303.             /** {@inheritDoc} */
  304.             @Override
  305.             public Stream<LineParser> allowedNext() {
  306.                 return Stream.of(H5, C0, COMMENTS);
  307.             }

  308.         },

  309.         /** Format header. */
  310.         H5("H5", "h5") {

  311.             /** {@inheritDoc} */
  312.             @Override
  313.             public void parse(final String line, final ParseInfo pi) {

  314.                 // Data contained in the line
  315.                 final String[] values = SEPARATOR.split(line);

  316.                 // Fill data
  317.                 pi.header.setPredictionType(Integer.parseInt(values[1]));
  318.                 pi.header.setYearOfCentury(Integer.parseInt(values[2]));
  319.                 pi.header.setDateAndTime(values[3]);
  320.                 pi.header.setPredictionProvider(values[4]);
  321.                 pi.header.setSequenceNumber(Integer.parseInt(values[5]));

  322.             }

  323.             /** {@inheritDoc} */
  324.             @Override
  325.             public Stream<LineParser> allowedNext() {
  326.                 return Stream.of(C0, COMMENTS);
  327.             }

  328.         },

  329.         /** System configuration record. */
  330.         C0("C0", "c0") {

  331.             /** {@inheritDoc} */
  332.             @Override
  333.             public void parse(final String line, final ParseInfo pi) {

  334.                 // Initialise an empty system configuration record
  335.                 final SystemConfiguration systemRecord = new SystemConfiguration();

  336.                 // Data contained in the line
  337.                 final String[] values = SEPARATOR.split(line);

  338.                 // Wavelength
  339.                 systemRecord.setWavelength(NM.toSI(Double.parseDouble(values[2])));

  340.                 // System ID
  341.                 systemRecord.setSystemId(values[3]);

  342.                 // Set the system configuration record
  343.                 pi.configurationRecords.setSystemRecord(systemRecord);

  344.             }

  345.             /** {@inheritDoc} */
  346.             @Override
  347.             public Stream<LineParser> allowedNext() {
  348.                 return Stream.of(C1, C2, C3, C4, C5, C6, C7, TEN, ELEVEN, METEO, ANGLES, CALIB, STAT, COMPATIBILITY, COMMENTS);
  349.             }

  350.         },


  351.         /** Laser configuration record. */
  352.         C1("C1", "c1") {

  353.             /** {@inheritDoc} */
  354.             @Override
  355.             public void parse(final String line, final ParseInfo pi) {

  356.                 // Initialise an empty laser configuration record
  357.                 final LaserConfiguration laserRecord = new LaserConfiguration();

  358.                 // Data contained in the line
  359.                 final String[] values = SEPARATOR.split(line);

  360.                 // Fill values
  361.                 laserRecord.setLaserId(values[2]);
  362.                 laserRecord.setLaserType(values[3]);
  363.                 laserRecord.setPrimaryWavelength(NM.toSI(Double.parseDouble(values[4])));
  364.                 laserRecord.setNominalFireRate(Double.parseDouble(values[5]));
  365.                 laserRecord.setPulseEnergy(Double.parseDouble(values[6]));
  366.                 laserRecord.setPulseWidth(Double.parseDouble(values[7]));
  367.                 laserRecord.setBeamDivergence(Double.parseDouble(values[8]));
  368.                 laserRecord.setPulseInOutgoingSemiTrain(Integer.parseInt(values[9]));

  369.                 // Set the laser configuration record
  370.                 pi.configurationRecords.setLaserRecord(laserRecord);

  371.             }

  372.             /** {@inheritDoc} */
  373.             @Override
  374.             public Stream<LineParser> allowedNext() {
  375.                 return Stream.of(C2, C3, C4, C5, C6, C7, TEN, ELEVEN, METEO, ANGLES, CALIB, STAT, COMPATIBILITY, COMMENTS);
  376.             }

  377.         },

  378.         /** Detector configuration record. */
  379.         C2("C2", "c2") {

  380.             /** {@inheritDoc} */
  381.             @Override
  382.             public void parse(final String line, final ParseInfo pi) {

  383.                 // Initialise an empty detector configuration record
  384.                 final DetectorConfiguration detectorRecord = new DetectorConfiguration();

  385.                 // Data contained in the line
  386.                 final String[] values = SEPARATOR.split(line);

  387.                 // Fill values
  388.                 detectorRecord.setDetectorId(values[2]);
  389.                 detectorRecord.setDetectorType(values[3]);
  390.                 detectorRecord.setApplicableWavelength(NM.toSI(Double.parseDouble(values[4])));
  391.                 detectorRecord.setQuantumEfficiency(Double.parseDouble(values[5]));
  392.                 detectorRecord.setAppliedVoltage(Double.parseDouble(values[6]));
  393.                 detectorRecord.setDarkCount(KHZ.toSI(Double.parseDouble(values[7])));
  394.                 detectorRecord.setOutputPulseType(values[8]);
  395.                 detectorRecord.setOutputPulseWidth(Double.parseDouble(values[9]));
  396.                 detectorRecord.setSpectralFilter(NM.toSI(Double.parseDouble(values[10])));
  397.                 detectorRecord.setTransmissionOfSpectralFilter(Double.parseDouble(values[11]));
  398.                 detectorRecord.setSpatialFilter(Double.parseDouble(values[12]));
  399.                 detectorRecord.setExternalSignalProcessing(values[13]);

  400.                 // Check file version for additional data
  401.                 if (pi.version == 2) {
  402.                     detectorRecord.setAmplifierGain(Double.parseDouble(values[14]));
  403.                     detectorRecord.setAmplifierBandwidth(KHZ.toSI(Double.parseDouble(values[15])));
  404.                     detectorRecord.setAmplifierInUse(values[16]);
  405.                 }

  406.                 // Set the detector configuration record
  407.                 pi.configurationRecords.setDetectorRecord(detectorRecord);

  408.             }

  409.             /** {@inheritDoc} */
  410.             @Override
  411.             public Stream<LineParser> allowedNext() {
  412.                 return Stream.of(C3, C4, C5, C6, C7, TEN, ELEVEN, METEO, ANGLES, CALIB, STAT, COMPATIBILITY, COMMENTS);
  413.             }

  414.         },

  415.         /** Timing system configuration record. */
  416.         C3("C3", "c3") {

  417.             /** {@inheritDoc} */
  418.             @Override
  419.             public void parse(final String line, final ParseInfo pi) {

  420.                 // Initialise an empty timing system configuration record
  421.                 final TimingSystemConfiguration timingRecord = new TimingSystemConfiguration();

  422.                 // Data contained in the line
  423.                 final String[] values = SEPARATOR.split(line);

  424.                 // Fill values
  425.                 timingRecord.setLocalTimingId(values[2]);
  426.                 timingRecord.setTimeSource(values[3]);
  427.                 timingRecord.setFrequencySource(values[4]);
  428.                 timingRecord.setTimer(values[5]);
  429.                 timingRecord.setTimerSerialNumber(values[6]);
  430.                 timingRecord.setEpochDelayCorrection(US.toSI(Double.parseDouble(values[7])));

  431.                 // Set the timing system configuration record
  432.                 pi.configurationRecords.setTimingRecord(timingRecord);

  433.             }

  434.             /** {@inheritDoc} */
  435.             @Override
  436.             public Stream<LineParser> allowedNext() {
  437.                 return Stream.of(C4, C5, C6, C7, TEN, ELEVEN, METEO, ANGLES, CALIB, STAT, COMPATIBILITY, COMMENTS);
  438.             }

  439.         },

  440.         /** Transponder configuration record. */
  441.         C4("C4", "c4") {

  442.             /** {@inheritDoc} */
  443.             @Override
  444.             public void parse(final String line, final ParseInfo pi) {

  445.                 // Initialise an empty transponder configuration record
  446.                 final TransponderConfiguration transponderRecord = new TransponderConfiguration();

  447.                 // Data contained in the line
  448.                 final String[] values = SEPARATOR.split(line);

  449.                 // Estimated offsets and drifts
  450.                 transponderRecord.setTransponderId(values[2]);
  451.                 transponderRecord.setStationUTCOffset(NM.toSI(Double.parseDouble(values[3])));
  452.                 transponderRecord.setStationOscDrift(Double.parseDouble(values[4]));
  453.                 transponderRecord.setTranspUTCOffset(NM.toSI(Double.parseDouble(values[5])));
  454.                 transponderRecord.setTranspOscDrift(Double.parseDouble(values[6]));

  455.                 // Transponder clock reference time
  456.                 transponderRecord.setTranspClkRefTime(Double.parseDouble(values[7]));

  457.                 // Clock and drift indicators
  458.                 transponderRecord.setStationClockAndDriftApplied(Integer.parseInt(values[8]));
  459.                 transponderRecord.setSpacecraftClockAndDriftApplied(Integer.parseInt(values[9]));

  460.                 // Spacecraft time simplified
  461.                 transponderRecord.setIsSpacecraftTimeSimplified(readBoolean(values[10]));

  462.                 // Set the transponder configuration record
  463.                 pi.configurationRecords.setTransponderRecord(transponderRecord);

  464.             }

  465.             /** {@inheritDoc} */
  466.             @Override
  467.             public Stream<LineParser> allowedNext() {
  468.                 return Stream.of(C5, C6, C7, TEN, ELEVEN, METEO, ANGLES, CALIB, STAT, COMPATIBILITY, COMMENTS);
  469.             }

  470.         },

  471.         /** Software configuration record. */
  472.         C5("C5", "c5") {

  473.             /** {@inheritDoc} */
  474.             @Override
  475.             public void parse(final String line, final ParseInfo pi) {

  476.                 // Initialise an empty software configuration record
  477.                 final SoftwareConfiguration softwareRecord = new SoftwareConfiguration();

  478.                 // Data contained in the line
  479.                 final String[] values = SEPARATOR.split(line);

  480.                 // Fill values
  481.                 softwareRecord.setSoftwareId(values[2]);
  482.                 softwareRecord.setTrackingSoftwares(COMMA.split(values[3]));
  483.                 softwareRecord.setTrackingSoftwareVersions(COMMA.split(values[4]));
  484.                 softwareRecord.setProcessingSoftwares(COMMA.split(values[5]));
  485.                 softwareRecord.setProcessingSoftwareVersions(COMMA.split(values[6]));

  486.                 // Set the software configuration record
  487.                 pi.configurationRecords.setSoftwareRecord(softwareRecord);

  488.             }

  489.             /** {@inheritDoc} */
  490.             @Override
  491.             public Stream<LineParser> allowedNext() {
  492.                 return Stream.of(C6, C7, TEN, ELEVEN, METEO, ANGLES, CALIB, STAT, COMPATIBILITY, COMMENTS);
  493.             }

  494.         },

  495.         /** Meteorological instrumentation configuration record. */
  496.         C6("C6", "c6") {

  497.             /** {@inheritDoc} */
  498.             @Override
  499.             public void parse(final String line, final ParseInfo pi) {

  500.                 // Initialise an empty meteorological configuration record
  501.                 final MeteorologicalConfiguration meteoRecord = new MeteorologicalConfiguration();

  502.                 // Data contained in the line
  503.                 final String[] values = SEPARATOR.split(line);

  504.                 // Fill values
  505.                 meteoRecord.setMeteorologicalId(values[2]);
  506.                 meteoRecord.setPressSensorManufacturer(values[3]);
  507.                 meteoRecord.setPressSensorModel(values[4]);
  508.                 meteoRecord.setPressSensorSerialNumber(values[5]);
  509.                 meteoRecord.setTempSensorManufacturer(values[6]);
  510.                 meteoRecord.setTempSensorModel(values[7]);
  511.                 meteoRecord.setTempSensorSerialNumber(values[8]);
  512.                 meteoRecord.setHumiSensorManufacturer(values[9]);
  513.                 meteoRecord.setHumiSensorModel(values[10]);
  514.                 meteoRecord.setHumiSensorSerialNumber(values[11]);

  515.                 // Set the meteorological configuration record
  516.                 pi.configurationRecords.setMeteorologicalRecord(meteoRecord);

  517.             }

  518.             /** {@inheritDoc} */
  519.             @Override
  520.             public Stream<LineParser> allowedNext() {
  521.                 return Stream.of(C7, TEN, ELEVEN, METEO, ANGLES, CALIB, STAT, COMPATIBILITY, COMMENTS);
  522.             }

  523.         },

  524.         /** Calibration Target configuration record. */
  525.         C7("C7", "c7") {

  526.             /** {@inheritDoc} */
  527.             @Override
  528.             public void parse(final String line, final ParseInfo pi) {
  529.                 // Not implemented yet
  530.             }

  531.             /** {@inheritDoc} */
  532.             @Override
  533.             public Stream<LineParser> allowedNext() {
  534.                 return Stream.of(TEN, ELEVEN, METEO, ANGLES, CALIB, STAT, COMPATIBILITY, COMMENTS);
  535.             }

  536.         },

  537.         /** Range Record (Full rate, Sampled Engineering/Quicklook). */
  538.         TEN("10") {

  539.             /** {@inheritDoc} */
  540.             @Override
  541.             public void parse(final String line, final ParseInfo pi) {

  542.                 // Data contained in the line
  543.                 final String[] values = SEPARATOR.split(line);

  544.                 // Read data
  545.                 final double secOfDay     = Double.parseDouble(values[1]);
  546.                 final double timeOfFlight = Double.parseDouble(values[2]);
  547.                 final int    epochEvent   = Integer.parseInt(values[4]);

  548.                 // Initialise a new Range measurement
  549.                 final AbsoluteDate epoch = new AbsoluteDate(pi.startEpoch, new TimeComponents(secOfDay), pi.timeScale);
  550.                 final RangeMeasurement range = new RangeMeasurement(epoch, timeOfFlight, epochEvent);
  551.                 pi.dataBlock.addRangeData(range);

  552.             }

  553.             /** {@inheritDoc} */
  554.             @Override
  555.             public Stream<LineParser> allowedNext() {
  556.                 return Stream.of(H8, TEN, TWELVE, METEO, ANGLES, CALIB, STAT, COMPATIBILITY, COMMENTS);
  557.             }

  558.         },

  559.         /** Range Record (Normal point). */
  560.         ELEVEN("11") {

  561.             /** {@inheritDoc} */
  562.             @Override
  563.             public void parse(final String line, final ParseInfo pi) {

  564.                 // Data contained in the line
  565.                 final String[] values = SEPARATOR.split(line);

  566.                 // Read data
  567.                 final double secOfDay     = Double.parseDouble(values[1]);
  568.                 final double timeOfFlight = Double.parseDouble(values[2]);
  569.                 final int    epochEvent   = Integer.parseInt(values[4]);
  570.                 final double snr          = (pi.version == 2) ? Double.parseDouble(values[13]) : Double.NaN;

  571.                 // Initialise a new Range measurement
  572.                 final AbsoluteDate epoch = new AbsoluteDate(pi.startEpoch, new TimeComponents(secOfDay), pi.timeScale);
  573.                 final RangeMeasurement range = new RangeMeasurement(epoch, timeOfFlight, epochEvent, snr);
  574.                 pi.dataBlock.addRangeData(range);

  575.             }

  576.             /** {@inheritDoc} */
  577.             @Override
  578.             public Stream<LineParser> allowedNext() {
  579.                 return Stream.of(H8, ELEVEN, TWELVE, METEO, ANGLES, CALIB, STAT, COMPATIBILITY, COMMENTS);
  580.             }

  581.         },

  582.         /** Range Supplement Record. */
  583.         TWELVE("12") {

  584.             /** {@inheritDoc} */
  585.             @Override
  586.             public void parse(final String line, final ParseInfo pi) {
  587.                 // Not implemented yet
  588.             }

  589.             /** {@inheritDoc} */
  590.             @Override
  591.             public Stream<LineParser> allowedNext() {
  592.                 return Stream.of(H8, TEN, ELEVEN, TWELVE, METEO, ANGLES, CALIB, STAT, COMPATIBILITY, COMMENTS);
  593.             }

  594.         },

  595.         /** Meteorological record. */
  596.         METEO("20") {

  597.             /** {@inheritDoc} */
  598.             @Override
  599.             public void parse(final String line, final ParseInfo pi) {

  600.                 // Data contained in the line
  601.                 final String[] values = SEPARATOR.split(line);

  602.                 // Read data
  603.                 final double secOfDay    = Double.parseDouble(values[1]);
  604.                 final double pressure    = MBAR_TO_BAR.convert(Double.parseDouble(values[2]));
  605.                 final double temperature = Double.parseDouble(values[3]);
  606.                 final double humidity    = Double.parseDouble(values[4]);

  607.                 // Initialise a new meteorological measurement
  608.                 final AbsoluteDate epoch = new AbsoluteDate(pi.startEpoch, new TimeComponents(secOfDay), pi.timeScale);
  609.                 final MeteorologicalMeasurement meteo = new MeteorologicalMeasurement(epoch, pressure,
  610.                                                                                       temperature, humidity);
  611.                 pi.dataBlock.addMeteoData(meteo);

  612.             }

  613.             /** {@inheritDoc} */
  614.             @Override
  615.             public Stream<LineParser> allowedNext() {
  616.                 return Stream.of(H8, METEO, METEO_SUPP, TEN, ELEVEN, TWELVE, ANGLES, CALIB, STAT, COMPATIBILITY, COMMENTS);
  617.             }

  618.         },

  619.         /** Meteorological Supplement record. */
  620.         METEO_SUPP("21") {

  621.             /** {@inheritDoc} */
  622.             @Override
  623.             public void parse(final String line, final ParseInfo pi) {
  624.                 // Not implemented yet
  625.             }

  626.             /** {@inheritDoc} */
  627.             @Override
  628.             public Stream<LineParser> allowedNext() {
  629.                 return Stream.of(H8, METEO, METEO_SUPP, TEN, ELEVEN, TWELVE, ANGLES, CALIB, STAT, COMPATIBILITY, COMMENTS);
  630.             }

  631.         },

  632.         /** Pointing Angle Record. */
  633.         ANGLES("30") {

  634.             /** {@inheritDoc} */
  635.             @Override
  636.             public void parse(final String line, final ParseInfo pi) {

  637.                 // Data contained in the line
  638.                 final String[] values = SEPARATOR.split(line);

  639.                 // Read data
  640.                 final double  secOfDay              = Double.parseDouble(values[1]);
  641.                 final double  azmiuth               = FastMath.toRadians(Double.parseDouble(values[2]));
  642.                 final double  elevation             = FastMath.toRadians(Double.parseDouble(values[3]));
  643.                 final int     directionFlag         = Integer.parseInt(values[4]);
  644.                 final int     orginFlag             = Integer.parseInt(values[5]);
  645.                 final boolean isRefractionCorrected = readBoolean(values[6]);


  646.                 // Angles rates
  647.                 double azimuthRate   = Double.NaN;
  648.                 double elevationRate = Double.NaN;
  649.                 if (pi.version == 2) {
  650.                     azimuthRate   = readDoubleWithNaN(values[7]);
  651.                     elevationRate = readDoubleWithNaN(values[8]);
  652.                 }

  653.                 // Initialise a new angles measurement
  654.                 final AbsoluteDate epoch = new AbsoluteDate(pi.startEpoch, new TimeComponents(secOfDay), pi.timeScale);
  655.                 final AnglesMeasurement angles = new AnglesMeasurement(epoch, azmiuth, elevation,
  656.                                                                        directionFlag, orginFlag,
  657.                                                                        isRefractionCorrected,
  658.                                                                        azimuthRate, elevationRate);
  659.                 pi.dataBlock.addAnglesData(angles);

  660.             }

  661.             /** {@inheritDoc} */
  662.             @Override
  663.             public Stream<LineParser> allowedNext() {
  664.                 return Stream.of(H8, METEO, TEN, ELEVEN, ANGLES, CALIB, STAT, COMPATIBILITY, COMMENTS);
  665.             }

  666.         },

  667.         /** Calibration Record. */
  668.         CALIB("40") {

  669.             /** {@inheritDoc} */
  670.             @Override
  671.             public void parse(final String line, final ParseInfo pi) {
  672.                 // Not implemented yet
  673.             }

  674.             /** {@inheritDoc} */
  675.             @Override
  676.             public Stream<LineParser> allowedNext() {
  677.                 return Stream.of(H8, METEO, CALIB, CALIB_DETAILS, CALIB_SHOT, TEN, ELEVEN, TWELVE, ANGLES, STAT, COMPATIBILITY, COMMENTS);
  678.             }

  679.         },

  680.         /** Calibration Details Record. */
  681.         CALIB_DETAILS("41") {

  682.             /** {@inheritDoc} */
  683.             @Override
  684.             public void parse(final String line, final ParseInfo pi) {
  685.                 // Not implemented yet
  686.             }

  687.             /** {@inheritDoc} */
  688.             @Override
  689.             public Stream<LineParser> allowedNext() {
  690.                 return Stream.of(H8, METEO, CALIB, CALIB_DETAILS, CALIB_SHOT, TEN, ELEVEN, TWELVE, ANGLES, STAT, COMPATIBILITY, COMMENTS);
  691.             }

  692.         },

  693.         /** Calibration "Shot" Record. */
  694.         CALIB_SHOT("42") {

  695.             /** {@inheritDoc} */
  696.             @Override
  697.             public void parse(final String line, final ParseInfo pi) {
  698.                 // Not implemented yet
  699.             }

  700.             /** {@inheritDoc} */
  701.             @Override
  702.             public Stream<LineParser> allowedNext() {
  703.                 return Stream.of(H8, METEO, CALIB, CALIB_DETAILS, CALIB_SHOT, TEN, ELEVEN, TWELVE, ANGLES, STAT, COMPATIBILITY, COMMENTS);
  704.             }

  705.         },

  706.         /** Session (Pass) Statistics Record. */
  707.         STAT("50") {

  708.             /** {@inheritDoc} */
  709.             @Override
  710.             public void parse(final String line, final ParseInfo pi) {
  711.                 // Not implemented yet
  712.             }

  713.             /** {@inheritDoc} */
  714.             @Override
  715.             public Stream<LineParser> allowedNext() {
  716.                 return Stream.of(H8, METEO, CALIB, CALIB_DETAILS, CALIB_SHOT, TEN, ELEVEN, TWELVE, ANGLES, STAT, COMPATIBILITY, H8, COMMENTS);
  717.             }

  718.         },

  719.         /** Compatibility record. */
  720.         COMPATIBILITY("60") {

  721.             /** {@inheritDoc} */
  722.             @Override
  723.             public void parse(final String line, final ParseInfo pi) {
  724.                 // Not implemented yet
  725.             }

  726.             /** {@inheritDoc} */
  727.             @Override
  728.             public Stream<LineParser> allowedNext() {
  729.                 return Stream.of(H8, METEO, CALIB, CALIB_DETAILS, CALIB_SHOT, TEN, ELEVEN, TWELVE, ANGLES, STAT, COMPATIBILITY, COMMENTS);
  730.             }

  731.         },

  732.         /** Comments. */
  733.         COMMENTS("00") {

  734.             /** {@inheritDoc} */
  735.             @Override
  736.             public void parse(final String line, final ParseInfo pi) {

  737.                 // Comment
  738.                 final String comment = line.split(getFirstIdentifier())[1].trim();
  739.                 pi.file.getComments().add(comment);

  740.             }

  741.             /** {@inheritDoc} */
  742.             @Override
  743.             public Stream<LineParser> allowedNext() {
  744.                 return Stream.of(H1, H2, H3, H4, H5, H8, H9, C0, C1, C2, C3, C4, C5, C6, C7, TEN, ELEVEN, TWELVE, METEO,
  745.                                  METEO_SUPP, ANGLES, CALIB, CALIB_DETAILS, CALIB_SHOT, STAT, COMPATIBILITY, COMMENTS);

  746.             }

  747.         },

  748.         /** End of data block. */
  749.         H8("H8", "h8") {

  750.             /** {@inheritDoc} */
  751.             @Override
  752.             public void parse(final String line, final ParseInfo pi) {

  753.                 // Fill data block
  754.                 pi.dataBlock.setHeader(pi.header);
  755.                 pi.dataBlock.setConfigurationRecords(pi.configurationRecords);

  756.                 // Add the data block to the CRD file
  757.                 pi.file.addDataBlock(pi.dataBlock);

  758.                 // Initialize a new empty containers
  759.                 pi.startEpoch           = DateComponents.J2000_EPOCH;
  760.                 pi.header               = new CRDHeader();
  761.                 pi.configurationRecords = new CRDConfiguration();
  762.                 pi.dataBlock            = new CRDDataBlock();

  763.             }

  764.             /** {@inheritDoc} */
  765.             @Override
  766.             public Stream<LineParser> allowedNext() {
  767.                 return Stream.of(H1, H9, COMMENTS);
  768.             }

  769.         },

  770.         /** Last record in file. */
  771.         H9("H9", "h9") {

  772.             /** {@inheritDoc} */
  773.             @Override
  774.             public void parse(final String line, final ParseInfo pi) {
  775.                 pi.done = true;
  776.             }

  777.             /** {@inheritDoc} */
  778.             @Override
  779.             public Stream<LineParser> allowedNext() {
  780.                 return Stream.of(H9);
  781.             }

  782.         };

  783.         /** Patterns for identifying line. */
  784.         private final Pattern[] patterns;

  785.         /** Identifiers. */
  786.         private final String[] identifiers;

  787.         /** Simple constructor.
  788.          * @param identifier regular expression for identifying line (i.e. first element)
  789.          */
  790.         LineParser(final String... identifier) {
  791.             this.identifiers = identifier;
  792.             // Initialise patterns
  793.             this.patterns    = new Pattern[identifiers.length];
  794.             for (int index = 0; index < patterns.length; index++) {
  795.                 patterns[index] = Pattern.compile(identifiers[index]);
  796.             }
  797.         }

  798.         /**
  799.          * Get the regular expression for identifying line.
  800.          * @return the regular expression for identifying line
  801.          */
  802.         public String getFirstIdentifier() {
  803.             return identifiers[0];
  804.         }

  805.         /** Parse a line.
  806.          * @param line line to parse
  807.          * @param pi holder for transient data
  808.          */
  809.         public abstract void parse(String line, ParseInfo pi);

  810.         /** Get the allowed parsers for next line.
  811.          * @return allowed parsers for next line
  812.          */
  813.         public abstract Stream<LineParser> allowedNext();

  814.         /** Check if parser can handle line.
  815.          * @param line line to parse
  816.          * @return true if parser can handle the specified line
  817.          */
  818.         public boolean canHandle(final String line) {
  819.             // Line identifier
  820.             final String lineId = SEPARATOR.split(line)[0];
  821.             // Loop on patterns
  822.             for (Pattern pattern : patterns) {
  823.                 if (pattern.matcher(lineId).matches()) {
  824.                     return true;
  825.                 }
  826.             }
  827.             // No match
  828.             return false;
  829.         }

  830.         /**
  831.          * Read a boolean from a string value.
  832.          * @param value input value
  833.          * @return the correspondin boolean
  834.          */
  835.         private static boolean readBoolean(final String value) {
  836.             return Integer.parseInt(value) == 1;
  837.         }

  838.         /**
  839.          * Read a double value taking into consideration a possible "na".
  840.          * @param value input string
  841.          * @return the corresponding double value
  842.          */
  843.         private static double readDoubleWithNaN(final String value) {
  844.             return "na".equals(value) ? Double.NaN : Double.parseDouble(value);
  845.         }

  846.     }

  847. }