CRDParser.java

  1. /* Copyright 2002-2020 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.io.InputStream;
  21. import java.io.InputStreamReader;
  22. import java.nio.charset.StandardCharsets;
  23. import java.nio.file.Files;
  24. import java.nio.file.Paths;
  25. import java.util.Optional;
  26. import java.util.regex.Pattern;
  27. import java.util.stream.Stream;

  28. import org.hipparchus.util.FastMath;
  29. import org.orekit.annotation.DefaultDataContext;
  30. import org.orekit.data.DataContext;
  31. import org.orekit.errors.OrekitException;
  32. import org.orekit.errors.OrekitMessages;
  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.files.ilrs.CRDFile.AnglesMeasurement;
  41. import org.orekit.files.ilrs.CRDFile.CRDDataBlock;
  42. import org.orekit.files.ilrs.CRDFile.MeteorologicalMeasurement;
  43. import org.orekit.files.ilrs.CRDFile.RangeMeasurement;
  44. import org.orekit.time.AbsoluteDate;
  45. import org.orekit.time.DateComponents;
  46. import org.orekit.time.TimeComponents;
  47. import org.orekit.time.TimeScale;

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

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

  63.     /** Nanometers to meters converter. */
  64.     private static final double NM_TO_M = 1.0e-9;

  65.     /** Kilohertz to hertz converter. */
  66.     private static final double KHZ_TO_HZ = 1.0e3;

  67.     /** Microseconds to seconds converter. */
  68.     private static final double US_TO_S = 1.0e-6;

  69.     /** Milli to none converter. */
  70.     private static final double MILLI_TO_NONE = 1.0e-3;

  71.     /** File format. */
  72.     private static final String FILE_FORMAT = "CRD";

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

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

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

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

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

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

  102.     /**
  103.      * Parse a CRD file from an input stream using the UTF-8 charset.
  104.      *
  105.      * <p> This method creates a {@link BufferedReader} from the stream and as such this
  106.      * method may read more data than necessary from {@code stream} and the additional
  107.      * data will be lost. The other parse methods do not have this issue.
  108.      *
  109.      * @param stream to read the CRD file from.
  110.      * @return a parsed CRD file.
  111.      * @throws IOException if {@code stream} throws one.
  112.      * @see #parse(String)
  113.      * @see #parse(BufferedReader, String)
  114.      */
  115.     public CRDFile parse(final InputStream stream) throws IOException {
  116.         try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) {
  117.             return parse(reader, stream.toString());
  118.         }
  119.     }

  120.     /**
  121.      * Parse an CRD file from a file on the local file system.
  122.      * @param fileName path to the CRD file.
  123.      * @return parsed CRD file.
  124.      * @throws IOException if one is thrown while opening or reading from {@code fileName}
  125.      */
  126.     public CRDFile parse(final String fileName) throws IOException {
  127.         try (BufferedReader reader = Files.newBufferedReader(Paths.get(fileName),
  128.                                                              StandardCharsets.UTF_8)) {
  129.             return parse(reader, fileName);
  130.         }
  131.     }

  132.     /**
  133.      * Parse a CRD file from a stream.
  134.      * @param reader   containing the CRD file.
  135.      * @param fileName to use in error messages.
  136.      * @return a parsed CRD file.
  137.      * @throws IOException if {@code reader} throws one.
  138.      */
  139.     public CRDFile parse(final BufferedReader reader,
  140.                          final String fileName) throws IOException {

  141.         // Initialize internal data structures
  142.         final ParseInfo pi = new ParseInfo();

  143.         int lineNumber = 0;
  144.         Stream<LineParser> cdrParsers = Stream.of(LineParser.H1);
  145.         for (String line = reader.readLine(); line != null; line = reader.readLine()) {
  146.             ++lineNumber;
  147.             final String l = line;
  148.             final Optional<LineParser> selected = cdrParsers.filter(p -> p.canHandle(l)).findFirst();
  149.             if (selected.isPresent()) {
  150.                 try {
  151.                     selected.get().parse(line, pi);
  152.                 } catch (StringIndexOutOfBoundsException | NumberFormatException e) {
  153.                     throw new OrekitException(e,
  154.                                               OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  155.                                               lineNumber, fileName, line);
  156.                 }
  157.                 cdrParsers = selected.get().allowedNext();
  158.             }
  159.             if (pi.done) {
  160.                 // Return file
  161.                 return pi.file;
  162.             }
  163.         }

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

  166.     }

  167.     /** Transient data used for parsing a CRD file. The data is kept in a
  168.      * separate data structure to make the parser thread-safe.
  169.      * <p><b>Note</b>: The class intentionally does not provide accessor
  170.      * methods, as it is only used internally for parsing a CRD file.</p>
  171.      */
  172.     private class ParseInfo {

  173.         /** The corresponding CDR file. */
  174.         private CRDFile file;

  175.         /** Version. */
  176.         private int version;

  177.         /** The current data block. */
  178.         private CRDDataBlock dataBlock;

  179.         /** Data block header. */
  180.         private CRDHeader header;

  181.         /** Cofiguration records. */
  182.         private CRDConfiguration configurationRecords;

  183.         /** Time scale. */
  184.         private TimeScale timeScale;

  185.         /** Current data block start epoch. */
  186.         private DateComponents startEpoch;

  187.         /** End Of File reached indicator. */
  188.         private boolean done;

  189.         /**
  190.          * Constructor.
  191.          */
  192.         protected ParseInfo() {

  193.             // Initialise default values
  194.             this.done       = false;
  195.             this.version    = 1;
  196.             this.startEpoch = DateComponents.J2000_EPOCH;

  197.             // Initialise empty object
  198.             this.file                 = new CRDFile();
  199.             this.header               = new CRDHeader();
  200.             this.configurationRecords = new CRDConfiguration();
  201.             this.dataBlock            = new CRDDataBlock();

  202.             // Time scale
  203.             this.timeScale = CRDParser.this.timeScale;

  204.         }

  205.     }

  206.     /** Parsers for specific lines. */
  207.     private enum LineParser {

  208.         /** Format header. */
  209.         H1("H1", "h1") {

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

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

  215.                 // Format and version
  216.                 final String format = values[1];
  217.                 pi.version = Integer.parseInt(values[2]);

  218.                 // Throw an exception if format is not equal to "CRD"
  219.                 if (!format.equalsIgnoreCase(FILE_FORMAT)) {
  220.                     throw new OrekitException(OrekitMessages.UNEXPECTED_FORMAT_FOR_ILRS_FILE, FILE_FORMAT, format);
  221.                 }

  222.                 // Fill first elements
  223.                 pi.header.setFormat(format);
  224.                 pi.header.setVersion(pi.version);

  225.                 // Epoch of ephemeris production
  226.                 final int year  = Integer.parseInt(values[3]);
  227.                 final int month = Integer.parseInt(values[4]);
  228.                 final int day   = Integer.parseInt(values[5]);
  229.                 pi.header.setProductionEpoch(new DateComponents(year, month, day));

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

  232.             }

  233.             /** {@inheritDoc} */
  234.             @Override
  235.             public Stream<LineParser> allowedNext() {
  236.                 return Stream.of(H2);
  237.             }

  238.         },

  239.         /** Format header. */
  240.         H2("H2", "h2") {

  241.             /** {@inheritDoc} */
  242.             @Override
  243.             public void parse(final String line, final ParseInfo pi) {

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

  246.                 // Station name
  247.                 pi.header.setStationName(values[1]);

  248.                 // Crustal Dynamics Project keys
  249.                 pi.header.setSystemIdentifier(Integer.parseInt(values[2]));
  250.                 pi.header.setSystemNumber(Integer.parseInt(values[3]));
  251.                 pi.header.setSystemOccupancy(Integer.parseInt(values[4]));

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

  254.                 // Station network
  255.                 if (pi.version == 2) {
  256.                     pi.header.setStationNetword(values[6]);
  257.                 }

  258.             }

  259.             /** {@inheritDoc} */
  260.             @Override
  261.             public Stream<LineParser> allowedNext() {
  262.                 return Stream.of(H3);
  263.             }

  264.         },

  265.         /** Format header. */
  266.         H3("H3", "h3") {

  267.             /** {@inheritDoc} */
  268.             @Override
  269.             public void parse(final String line, final ParseInfo pi) {

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

  272.                 // Target name
  273.                 pi.header.setName(values[1]);

  274.                 // Identifiers
  275.                 pi.header.setIlrsSatelliteId(values[2]);
  276.                 pi.header.setSic(values[3]);
  277.                 pi.header.setNoradId(values[4]);

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

  280.                 // Target class and location (if needed)
  281.                 pi.header.setTargetClass(Integer.parseInt(values[6]));
  282.                 if (pi.version == 2) {
  283.                     pi.header.setTargetLocation(Integer.parseInt(values[7]));
  284.                 }

  285.             }

  286.             /** {@inheritDoc} */
  287.             @Override
  288.             public Stream<LineParser> allowedNext() {
  289.                 return Stream.of(H4);
  290.             }

  291.         },

  292.         /** Format header. */
  293.         H4("H4", "h4") {

  294.             /** {@inheritDoc} */
  295.             @Override
  296.             public void parse(final String line, final ParseInfo pi) {

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

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

  301.                 // Start epoch
  302.                 final int    yearS   = Integer.parseInt(values[2]);
  303.                 final int    monthS  = Integer.parseInt(values[3]);
  304.                 final int    dayS    = Integer.parseInt(values[4]);
  305.                 final int    hourS   = Integer.parseInt(values[5]);
  306.                 final int    minuteS = Integer.parseInt(values[6]);
  307.                 final double secondS = Integer.parseInt(values[7]);

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

  309.                 pi.header.setStartEpoch(new AbsoluteDate(yearS, monthS, dayS,
  310.                                                          hourS, minuteS, secondS,
  311.                                                          pi.timeScale));

  312.                 // End epoch
  313.                 final int    yearE   = Integer.parseInt(values[8]);
  314.                 final int    monthE  = Integer.parseInt(values[9]);
  315.                 final int    dayE    = Integer.parseInt(values[10]);
  316.                 final int    hourE   = Integer.parseInt(values[11]);
  317.                 final int    minuteE = Integer.parseInt(values[12]);
  318.                 final double secondE = Integer.parseInt(values[13]);

  319.                 pi.header.setEndEpoch(new AbsoluteDate(yearE, monthE, dayE,
  320.                                                        hourE, minuteE, secondE,
  321.                                                        pi.timeScale));

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

  324.                 // Correction flags
  325.                 pi.header.setIsTroposphericRefractionApplied(readBoolean(values[15]));
  326.                 pi.header.setIsCenterOfMassCorrectionApplied(readBoolean(values[16]));
  327.                 pi.header.setIsReceiveAmplitudeCorrectionApplied(readBoolean(values[17]));
  328.                 pi.header.setIsStationSystemDelayApplied(readBoolean(values[18]));
  329.                 pi.header.setIsTransponderDelayApplied(readBoolean(values[19]));

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

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

  334.             }

  335.             /** {@inheritDoc} */
  336.             @Override
  337.             public Stream<LineParser> allowedNext() {
  338.                 return Stream.of(H5, C0);
  339.             }

  340.         },

  341.         /** Format header. */
  342.         H5("H5", "h5") {

  343.             /** {@inheritDoc} */
  344.             @Override
  345.             public void parse(final String line, final ParseInfo pi) {

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

  348.                 // Fill data
  349.                 pi.header.setPredictionType(Integer.parseInt(values[1]));
  350.                 pi.header.setYearOfCentury(Integer.parseInt(values[2]));
  351.                 pi.header.setDateAndTime(values[3]);
  352.                 pi.header.setPredictionProvider(values[4]);
  353.                 pi.header.setSequenceNumber(Integer.parseInt(values[5]));

  354.             }

  355.             /** {@inheritDoc} */
  356.             @Override
  357.             public Stream<LineParser> allowedNext() {
  358.                 return Stream.of(C0);
  359.             }

  360.         },

  361.         /** System configuration record. */
  362.         C0("C0", "c0") {

  363.             /** {@inheritDoc} */
  364.             @Override
  365.             public void parse(final String line, final ParseInfo pi) {

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

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

  370.                 // Wavelength
  371.                 systemRecord.setWavelength(Double.parseDouble(values[2]) * NM_TO_M);

  372.                 // System ID
  373.                 systemRecord.setSystemId(values[3]);

  374.                 // Set the system configuration record
  375.                 pi.configurationRecords.setSystemRecord(systemRecord);

  376.             }

  377.             /** {@inheritDoc} */
  378.             @Override
  379.             public Stream<LineParser> allowedNext() {
  380.                 return Stream.of(C1, C2, C3, C4, C5, C6, C7, TEN, ELEVEN, METEO, ANGLES, CALIB, STAT, COMPATIBILITY);
  381.             }

  382.         },


  383.         /** Laser configuration record. */
  384.         C1("C1", "c1") {

  385.             /** {@inheritDoc} */
  386.             @Override
  387.             public void parse(final String line, final ParseInfo pi) {

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

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

  392.                 // Fill values
  393.                 laserRecord.setLaserId(values[2]);
  394.                 laserRecord.setLaserType(values[3]);
  395.                 laserRecord.setPrimaryWavelength(Double.parseDouble(values[4]) * NM_TO_M);
  396.                 laserRecord.setNominalFireRate(Double.parseDouble(values[5]));
  397.                 laserRecord.setPulseEnergy(Double.parseDouble(values[6]));
  398.                 laserRecord.setPulseWidth(Double.parseDouble(values[7]));
  399.                 laserRecord.setBeamDivergence(Double.parseDouble(values[8]));
  400.                 laserRecord.setPulseInOutgoingSemiTrain(Integer.parseInt(values[9]));

  401.                 // Set the laser configuration record
  402.                 pi.configurationRecords.setLaserRecord(laserRecord);

  403.             }

  404.             /** {@inheritDoc} */
  405.             @Override
  406.             public Stream<LineParser> allowedNext() {
  407.                 return Stream.of(C2, C3, C4, C5, C6, C7, TEN, ELEVEN, METEO, ANGLES, CALIB, STAT, COMPATIBILITY);
  408.             }

  409.         },

  410.         /** Detector configuration record. */
  411.         C2("C2", "c2") {

  412.             /** {@inheritDoc} */
  413.             @Override
  414.             public void parse(final String line, final ParseInfo pi) {

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

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

  419.                 // Fill values
  420.                 detectorRecord.setDetectorId(values[2]);
  421.                 detectorRecord.setDetectorType(values[3]);
  422.                 detectorRecord.setApplicableWavelength(Double.parseDouble(values[4]) * NM_TO_M);
  423.                 detectorRecord.setQuantumEfficiency(Double.parseDouble(values[5]));
  424.                 detectorRecord.setAppliedVoltage(Double.parseDouble(values[6]));
  425.                 detectorRecord.setDarkCount(Double.parseDouble(values[7]) * KHZ_TO_HZ);
  426.                 detectorRecord.setOutputPulseType(values[8]);
  427.                 detectorRecord.setOutputPulseWidth(Double.parseDouble(values[9]));
  428.                 detectorRecord.setSpectralFilter(Double.parseDouble(values[10]) * NM_TO_M);
  429.                 detectorRecord.setTransmissionOfSpectralFilter(Double.parseDouble(values[11]));
  430.                 detectorRecord.setSpatialFilter(Double.parseDouble(values[12]));
  431.                 detectorRecord.setExternalSignalProcessing(values[13]);

  432.                 // Check file version for additional data
  433.                 if (pi.version == 2) {
  434.                     detectorRecord.setAmplifierGain(Double.parseDouble(values[14]));
  435.                     detectorRecord.setAmplifierBandwidth(Double.parseDouble(values[15]) * KHZ_TO_HZ);
  436.                     detectorRecord.setAmplifierInUse(values[16]);
  437.                 }

  438.                 // Set the detector configuration record
  439.                 pi.configurationRecords.setDetectorRecord(detectorRecord);

  440.             }

  441.             /** {@inheritDoc} */
  442.             @Override
  443.             public Stream<LineParser> allowedNext() {
  444.                 return Stream.of(C3, C4, C5, C6, C7, TEN, ELEVEN, METEO, ANGLES, CALIB, STAT, COMPATIBILITY);
  445.             }

  446.         },

  447.         /** Timing system configuration record. */
  448.         C3("C3", "c3") {

  449.             /** {@inheritDoc} */
  450.             @Override
  451.             public void parse(final String line, final ParseInfo pi) {

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

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

  456.                 // Fill values
  457.                 timingRecord.setLocalTimingId(values[2]);
  458.                 timingRecord.setTimeSource(values[3]);
  459.                 timingRecord.setFrequencySource(values[4]);
  460.                 timingRecord.setTimer(values[5]);
  461.                 timingRecord.setTimerSerialNumber(values[6]);
  462.                 timingRecord.setEpochDelayCorrection(Double.parseDouble(values[7]) * US_TO_S);

  463.                 // Set the timing system configuration record
  464.                 pi.configurationRecords.setTimingRecord(timingRecord);

  465.             }

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

  471.         },

  472.         /** Transponder configuration record. */
  473.         C4("C4", "c4") {

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

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

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

  481.                 // Estimated offsets and drifts
  482.                 transponderRecord.setTransponderId(values[2]);
  483.                 transponderRecord.setStationUTCOffset(Double.parseDouble(values[3]) * NM_TO_M);
  484.                 transponderRecord.setStationOscDrift(Double.parseDouble(values[4]));
  485.                 transponderRecord.setTranspUTCOffset(Double.parseDouble(values[5]) * NM_TO_M);
  486.                 transponderRecord.setTranspOscDrift(Double.parseDouble(values[6]));

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

  489.                 // Clock and drift indicators
  490.                 transponderRecord.setStationClockAndDriftApplied(Integer.parseInt(values[8]));
  491.                 transponderRecord.setSpacecraftClockAndDriftApplied(Integer.parseInt(values[9]));

  492.                 // Spacecraft time simplified
  493.                 transponderRecord.setIsSpacecraftTimeSimplified(readBoolean(values[10]));

  494.                 // Set the transponder configuration record
  495.                 pi.configurationRecords.setTransponderRecord(transponderRecord);

  496.             }

  497.             /** {@inheritDoc} */
  498.             @Override
  499.             public Stream<LineParser> allowedNext() {
  500.                 return Stream.of(C5, C6, C7, TEN, ELEVEN, METEO, ANGLES, CALIB, STAT, COMPATIBILITY);
  501.             }

  502.         },

  503.         /** Software configuration record. */
  504.         C5("C5", "c5") {

  505.             /** {@inheritDoc} */
  506.             @Override
  507.             public void parse(final String line, final ParseInfo pi) {

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

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

  512.                 // Fill values
  513.                 softwareRecord.setSoftwareId(values[2]);
  514.                 softwareRecord.setTrackingSoftwares(COMMA.split(values[3]));
  515.                 softwareRecord.setTrackingSoftwareVersions(COMMA.split(values[4]));
  516.                 softwareRecord.setProcessingSoftwares(COMMA.split(values[5]));
  517.                 softwareRecord.setProcessingSoftwareVersions(COMMA.split(values[6]));

  518.                 // Set the software configuration record
  519.                 pi.configurationRecords.setSoftwareRecord(softwareRecord);

  520.             }

  521.             /** {@inheritDoc} */
  522.             @Override
  523.             public Stream<LineParser> allowedNext() {
  524.                 return Stream.of(C6, C7, TEN, ELEVEN, METEO, ANGLES, CALIB, STAT, COMPATIBILITY);
  525.             }

  526.         },

  527.         /** Meteorological instrumentation configuration record. */
  528.         C6("C6", "c6") {

  529.             /** {@inheritDoc} */
  530.             @Override
  531.             public void parse(final String line, final ParseInfo pi) {

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

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

  536.                 // Fill values
  537.                 meteoRecord.setMeteorologicalId(values[2]);
  538.                 meteoRecord.setPressSensorManufacturer(values[3]);
  539.                 meteoRecord.setPressSensorModel(values[4]);
  540.                 meteoRecord.setPressSensorSerialNumber(values[5]);
  541.                 meteoRecord.setTempSensorManufacturer(values[6]);
  542.                 meteoRecord.setTempSensorModel(values[7]);
  543.                 meteoRecord.setTempSensorSerialNumber(values[8]);
  544.                 meteoRecord.setHumiSensorManufacturer(values[9]);
  545.                 meteoRecord.setHumiSensorModel(values[10]);
  546.                 meteoRecord.setHumiSensorSerialNumber(values[11]);

  547.                 // Set the meteorological configuration record
  548.                 pi.configurationRecords.setMeteorologicalRecord(meteoRecord);

  549.             }

  550.             /** {@inheritDoc} */
  551.             @Override
  552.             public Stream<LineParser> allowedNext() {
  553.                 return Stream.of(C7, TEN, ELEVEN, METEO, ANGLES, CALIB, STAT, COMPATIBILITY);
  554.             }

  555.         },

  556.         /** Calibration Target configuration record. */
  557.         C7("C7", "c7") {

  558.             /** {@inheritDoc} */
  559.             @Override
  560.             public void parse(final String line, final ParseInfo pi) {
  561.                 // Not implemented yet
  562.             }

  563.             /** {@inheritDoc} */
  564.             @Override
  565.             public Stream<LineParser> allowedNext() {
  566.                 return Stream.of(TEN, ELEVEN, METEO, ANGLES, CALIB, STAT, COMPATIBILITY);
  567.             }

  568.         },

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

  571.             /** {@inheritDoc} */
  572.             @Override
  573.             public void parse(final String line, final ParseInfo pi) {

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

  576.                 // Read data
  577.                 final double secOfDay     = Double.parseDouble(values[1]);
  578.                 final double timeOfFlight = Double.parseDouble(values[2]);
  579.                 final int    epochEvent   = Integer.parseInt(values[4]);

  580.                 // Initialise a new Range measurement
  581.                 final AbsoluteDate epoch = new AbsoluteDate(pi.startEpoch, new TimeComponents(secOfDay), pi.timeScale);
  582.                 final RangeMeasurement range = new RangeMeasurement(epoch, timeOfFlight, epochEvent);
  583.                 pi.dataBlock.addRangeData(range);

  584.             }

  585.             /** {@inheritDoc} */
  586.             @Override
  587.             public Stream<LineParser> allowedNext() {
  588.                 return Stream.of(H8, TEN, TWELVE, METEO, ANGLES, CALIB, STAT, COMPATIBILITY);
  589.             }

  590.         },

  591.         /** Range Record (Normal point). */
  592.         ELEVEN("11") {

  593.             /** {@inheritDoc} */
  594.             @Override
  595.             public void parse(final String line, final ParseInfo pi) {

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

  598.                 // Read data
  599.                 final double secOfDay     = Double.parseDouble(values[1]);
  600.                 final double timeOfFlight = Double.parseDouble(values[2]);
  601.                 final int    epochEvent   = Integer.parseInt(values[4]);
  602.                 final double snr          = (pi.version == 2) ? Double.parseDouble(values[13]) : Double.NaN;

  603.                 // Initialise a new Range measurement
  604.                 final AbsoluteDate epoch = new AbsoluteDate(pi.startEpoch, new TimeComponents(secOfDay), pi.timeScale);
  605.                 final RangeMeasurement range = new RangeMeasurement(epoch, timeOfFlight, epochEvent, snr);
  606.                 pi.dataBlock.addRangeData(range);

  607.             }

  608.             /** {@inheritDoc} */
  609.             @Override
  610.             public Stream<LineParser> allowedNext() {
  611.                 return Stream.of(H8, ELEVEN, TWELVE, METEO, ANGLES, CALIB, STAT, COMPATIBILITY);
  612.             }

  613.         },

  614.         /** Range Supplement Record. */
  615.         TWELVE("12") {

  616.             /** {@inheritDoc} */
  617.             @Override
  618.             public void parse(final String line, final ParseInfo pi) {
  619.                 // Not implemented yet
  620.             }

  621.             /** {@inheritDoc} */
  622.             @Override
  623.             public Stream<LineParser> allowedNext() {
  624.                 return Stream.of(H8, TEN, ELEVEN, TWELVE, METEO, ANGLES, CALIB, STAT, COMPATIBILITY);
  625.             }

  626.         },

  627.         /** Meteorological record. */
  628.         METEO("20") {

  629.             /** {@inheritDoc} */
  630.             @Override
  631.             public void parse(final String line, final ParseInfo pi) {

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

  634.                 // Read data
  635.                 final double secOfDay    = Double.parseDouble(values[1]);
  636.                 final double pressure    = Double.parseDouble(values[2]) * MILLI_TO_NONE;
  637.                 final double temperature = Double.parseDouble(values[3]);
  638.                 final double humidity    = Double.parseDouble(values[4]);

  639.                 // Initialise a new meteorological measurement
  640.                 final AbsoluteDate epoch = new AbsoluteDate(pi.startEpoch, new TimeComponents(secOfDay), pi.timeScale);
  641.                 final MeteorologicalMeasurement meteo = new MeteorologicalMeasurement(epoch, pressure,
  642.                                                                                       temperature, humidity);
  643.                 pi.dataBlock.addMeteoData(meteo);

  644.             }

  645.             /** {@inheritDoc} */
  646.             @Override
  647.             public Stream<LineParser> allowedNext() {
  648.                 return Stream.of(H8, METEO, METEO_SUPP, TEN, ELEVEN, TWELVE, ANGLES, CALIB, STAT, COMPATIBILITY);
  649.             }

  650.         },

  651.         /** Meteorological Supplement record. */
  652.         METEO_SUPP("21") {

  653.             /** {@inheritDoc} */
  654.             @Override
  655.             public void parse(final String line, final ParseInfo pi) {
  656.                 // Not implemented yet
  657.             }

  658.             /** {@inheritDoc} */
  659.             @Override
  660.             public Stream<LineParser> allowedNext() {
  661.                 return Stream.of(H8, METEO, METEO_SUPP, TEN, ELEVEN, TWELVE, ANGLES, CALIB, STAT, COMPATIBILITY);
  662.             }

  663.         },

  664.         /** Pointing Angle Record. */
  665.         ANGLES("30") {

  666.             /** {@inheritDoc} */
  667.             @Override
  668.             public void parse(final String line, final ParseInfo pi) {

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

  671.                 // Read data
  672.                 final double  secOfDay              = Double.parseDouble(values[1]);
  673.                 final double  azmiuth               = FastMath.toRadians(Double.parseDouble(values[2]));
  674.                 final double  elevation             = FastMath.toRadians(Double.parseDouble(values[3]));
  675.                 final int     directionFlag         = Integer.parseInt(values[4]);
  676.                 final int     orginFlag             = Integer.parseInt(values[5]);
  677.                 final boolean isRefractionCorrected = readBoolean(values[6]);


  678.                 // Angles rates
  679.                 double azimuthRate   = Double.NaN;
  680.                 double elevationRate = Double.NaN;
  681.                 if (pi.version == 2) {
  682.                     azimuthRate   = readDoubleWithNaN(values[7]);
  683.                     elevationRate = readDoubleWithNaN(values[8]);
  684.                 }

  685.                 // Initialise a new angles measurement
  686.                 final AbsoluteDate epoch = new AbsoluteDate(pi.startEpoch, new TimeComponents(secOfDay), pi.timeScale);
  687.                 final AnglesMeasurement angles = new AnglesMeasurement(epoch, azmiuth, elevation,
  688.                                                                        directionFlag, orginFlag,
  689.                                                                        isRefractionCorrected,
  690.                                                                        azimuthRate, elevationRate);
  691.                 pi.dataBlock.addAnglesData(angles);

  692.             }

  693.             /** {@inheritDoc} */
  694.             @Override
  695.             public Stream<LineParser> allowedNext() {
  696.                 return Stream.of(H8, METEO, TEN, ELEVEN, ANGLES, CALIB, STAT, COMPATIBILITY);
  697.             }

  698.         },

  699.         /** Calibration Record. */
  700.         CALIB("40") {

  701.             /** {@inheritDoc} */
  702.             @Override
  703.             public void parse(final String line, final ParseInfo pi) {
  704.                 // Not implemented yet
  705.             }

  706.             /** {@inheritDoc} */
  707.             @Override
  708.             public Stream<LineParser> allowedNext() {
  709.                 return Stream.of(H8, METEO, CALIB, CALIB_DETAILS, CALIB_SHOT, TEN, ELEVEN, TWELVE, ANGLES, STAT, COMPATIBILITY);
  710.             }

  711.         },

  712.         /** Calibration Details Record. */
  713.         CALIB_DETAILS("41") {

  714.             /** {@inheritDoc} */
  715.             @Override
  716.             public void parse(final String line, final ParseInfo pi) {
  717.                 // Not implemented yet
  718.             }

  719.             /** {@inheritDoc} */
  720.             @Override
  721.             public Stream<LineParser> allowedNext() {
  722.                 return Stream.of(H8, METEO, CALIB, CALIB_DETAILS, CALIB_SHOT, TEN, ELEVEN, TWELVE, ANGLES, STAT, COMPATIBILITY);
  723.             }

  724.         },

  725.         /** Calibration "Shot" Record. */
  726.         CALIB_SHOT("42") {

  727.             /** {@inheritDoc} */
  728.             @Override
  729.             public void parse(final String line, final ParseInfo pi) {
  730.                 // Not implemented yet
  731.             }

  732.             /** {@inheritDoc} */
  733.             @Override
  734.             public Stream<LineParser> allowedNext() {
  735.                 return Stream.of(H8, METEO, CALIB, CALIB_DETAILS, CALIB_SHOT, TEN, ELEVEN, TWELVE, ANGLES, STAT, COMPATIBILITY);
  736.             }

  737.         },

  738.         /** Session (Pass) Statistics Record. */
  739.         STAT("50") {

  740.             /** {@inheritDoc} */
  741.             @Override
  742.             public void parse(final String line, final ParseInfo pi) {
  743.                 // Not implemented yet
  744.             }

  745.             /** {@inheritDoc} */
  746.             @Override
  747.             public Stream<LineParser> allowedNext() {
  748.                 return Stream.of(H8, METEO, CALIB, CALIB_DETAILS, CALIB_SHOT, TEN, ELEVEN, TWELVE, ANGLES, STAT, COMPATIBILITY, H8);
  749.             }

  750.         },

  751.         /** Compatibility record. */
  752.         COMPATIBILITY("60") {

  753.             /** {@inheritDoc} */
  754.             @Override
  755.             public void parse(final String line, final ParseInfo pi) {
  756.                 // Not implemented yet
  757.             }

  758.             /** {@inheritDoc} */
  759.             @Override
  760.             public Stream<LineParser> allowedNext() {
  761.                 return Stream.of(H8, METEO, CALIB, CALIB_DETAILS, CALIB_SHOT, TEN, ELEVEN, TWELVE, ANGLES, STAT, COMPATIBILITY);
  762.             }

  763.         },

  764.         /** Comments. */
  765.         COMMENTS("00") {

  766.             /** {@inheritDoc} */
  767.             @Override
  768.             public void parse(final String line, final ParseInfo pi) {

  769.                 // Comment
  770.                 final String comment = line.split(getFirstIdentifier())[1].trim();
  771.                 pi.file.getComments().add(comment);

  772.             }

  773.             /** {@inheritDoc} */
  774.             @Override
  775.             public Stream<LineParser> allowedNext() {
  776.                 return Stream.of(H1, H2, H3, H4, H5, H8, C0, C1, C2, C3, C4, C5, C6, C7);
  777.             }

  778.         },

  779.         /** End of data block. */
  780.         H8("H8", "h8") {

  781.             /** {@inheritDoc} */
  782.             @Override
  783.             public void parse(final String line, final ParseInfo pi) {

  784.                 // Fill data block
  785.                 pi.dataBlock.setHeader(pi.header);
  786.                 pi.dataBlock.setConfigurationRecords(pi.configurationRecords);

  787.                 // Add the data block to the CRD file
  788.                 pi.file.addDataBlock(pi.dataBlock);

  789.                 // Initialize a new empty containers
  790.                 pi.startEpoch           = DateComponents.J2000_EPOCH;
  791.                 pi.header               = new CRDHeader();
  792.                 pi.configurationRecords = new CRDConfiguration();
  793.                 pi.dataBlock            = new CRDDataBlock();

  794.             }

  795.             /** {@inheritDoc} */
  796.             @Override
  797.             public Stream<LineParser> allowedNext() {
  798.                 return Stream.of(H1, H9);
  799.             }

  800.         },

  801.         /** Last record in file. */
  802.         H9("H9", "h9") {

  803.             /** {@inheritDoc} */
  804.             @Override
  805.             public void parse(final String line, final ParseInfo pi) {
  806.                 pi.done = true;
  807.             }

  808.             /** {@inheritDoc} */
  809.             @Override
  810.             public Stream<LineParser> allowedNext() {
  811.                 return Stream.of(H9);
  812.             }

  813.         };

  814.         /** Patterns for identifying line. */
  815.         private final Pattern[] patterns;

  816.         /** Identifiers. */
  817.         private final String[] identifiers;

  818.         /** Simple constructor.
  819.          * @param identifier regular expression for identifying line (i.e. first element)
  820.          */
  821.         LineParser(final String... identifier) {
  822.             this.identifiers = identifier;
  823.             // Initialise patterns
  824.             this.patterns    = new Pattern[identifiers.length];
  825.             for (int index = 0; index < patterns.length; index++) {
  826.                 patterns[index] = Pattern.compile(identifiers[index]);
  827.             }
  828.         }

  829.         /**
  830.          * Get the regular expression for identifying line.
  831.          * @return the regular expression for identifying line
  832.          */
  833.         public String getFirstIdentifier() {
  834.             return identifiers[0];
  835.         }

  836.         /** Parse a line.
  837.          * @param line line to parse
  838.          * @param pi holder for transient data
  839.          */
  840.         public abstract void parse(String line, ParseInfo pi);

  841.         /** Get the allowed parsers for next line.
  842.          * @return allowed parsers for next line
  843.          */
  844.         public abstract Stream<LineParser> allowedNext();

  845.         /** Check if parser can handle line.
  846.          * @param line line to parse
  847.          * @return true if parser can handle the specified line
  848.          */
  849.         public boolean canHandle(final String line) {
  850.             // Line identifier
  851.             final String lineId = SEPARATOR.split(line)[0];
  852.             // Loop on patterns
  853.             for (Pattern pattern : patterns) {
  854.                 if (pattern.matcher(lineId).matches()) {
  855.                     return true;
  856.                 }
  857.             }
  858.             // No match
  859.             return false;
  860.         }

  861.         /**
  862.          * Read a boolean from a string value.
  863.          * @param value input value
  864.          * @return the correspondin boolean
  865.          */
  866.         private static boolean readBoolean(final String value) {
  867.             return Integer.parseInt(value) == 1;
  868.         }

  869.         /**
  870.          * Read a double value taking into consideration a possible "na".
  871.          * @param value input string
  872.          * @return the corresponding double value
  873.          */
  874.         private static double readDoubleWithNaN(final String value) {
  875.             return "na".equals(value) ? Double.NaN : Double.parseDouble(value);
  876.         }

  877.     }

  878. }