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.Constants;
  45. import org.orekit.utils.units.Unit;
  46. import org.orekit.utils.units.UnitsConverter;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  135.     }

  136.     /**
  137.      * Computes if a day shift has happened comparing the current and past epoch, described by seconds in the day.
  138.      * This is useful as the data is sorted in the chronological order inside the file.
  139.      *
  140.      * @param lastSecOfDay
  141.      * @param secOfDay
  142.      * @return Boolean true if change in day.
  143.      */
  144.     private static int checkRollover(final double lastSecOfDay, final double secOfDay) {
  145.         return (secOfDay > lastSecOfDay) ? 0 : 1;
  146.     }

  147.     /** Transient data used for parsing a CRD file. The data is kept in a
  148.      * separate data structure to make the parser thread-safe.
  149.      * <p><b>Note</b>: The class intentionally does not provide accessor
  150.      * methods, as it is only used internally for parsing a CRD file.</p>
  151.      */
  152.     private class ParseInfo {

  153.         /** The corresponding CDR file. */
  154.         private CRD file;

  155.         /** Version. */
  156.         private int version;

  157.         /** The current data block. */
  158.         private CRDDataBlock dataBlock;

  159.         /** Data block header. */
  160.         private CRDHeader header;

  161.         /** Cofiguration records. */
  162.         private CRDConfiguration configurationRecords;

  163.         /** Time scale. */
  164.         private TimeScale timeScale;

  165.         /** Current data block start epoch. */
  166.         private DateComponents startEpoch;

  167.         /** End Of File reached indicator. */
  168.         private boolean done;

  169.         /** Last parsed range measurement. */
  170.         private RangeMeasurement lastRange;

  171.         /**
  172.          * Constructor.
  173.          */
  174.         protected ParseInfo() {

  175.             // Initialise default values
  176.             this.done       = false;
  177.             this.version    = 1;
  178.             this.startEpoch = DateComponents.J2000_EPOCH;
  179.             this.lastRange  = null;

  180.             // Initialise empty object
  181.             this.file                 = new CRD();
  182.             this.header               = new CRDHeader();
  183.             this.configurationRecords = new CRDConfiguration();
  184.             this.dataBlock            = new CRDDataBlock();

  185.             // Time scale
  186.             this.timeScale = CRDParser.this.timeScale;

  187.         }

  188.     }

  189.     /** Parsers for specific lines. */
  190.     private enum LineParser {

  191.         /** Format header. */
  192.         H1("H1", "h1") {

  193.             /** {@inheritDoc} */
  194.             @Override
  195.             public void parse(final String line, final ParseInfo pi) {

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

  198.                 // Format and version
  199.                 final String format = values[1];
  200.                 pi.version = Integer.parseInt(values[2]);

  201.                 // Throw an exception if format is not equal to "CRD"
  202.                 if (!format.equalsIgnoreCase(FILE_FORMAT)) {
  203.                     throw new OrekitException(OrekitMessages.UNEXPECTED_FORMAT_FOR_ILRS_FILE, FILE_FORMAT, format);
  204.                 }

  205.                 // Fill first elements
  206.                 pi.header.setFormat(format);
  207.                 pi.header.setVersion(pi.version);

  208.                 // Epoch of ephemeris production
  209.                 final int year  = Integer.parseInt(values[3]);
  210.                 final int month = Integer.parseInt(values[4]);
  211.                 final int day   = Integer.parseInt(values[5]);
  212.                 pi.header.setProductionEpoch(new DateComponents(year, month, day));

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

  215.             }

  216.             /** {@inheritDoc} */
  217.             @Override
  218.             public Stream<LineParser> allowedNext() {
  219.                 return Stream.of(H2, COMMENTS);
  220.             }

  221.         },

  222.         /** Format header. */
  223.         H2("H2", "h2") {

  224.             /** {@inheritDoc} */
  225.             @Override
  226.             public void parse(final String line, final ParseInfo pi) {

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

  229.                 // Station name
  230.                 pi.header.setStationName(values[1]);

  231.                 // Crustal Dynamics Project keys
  232.                 pi.header.setSystemIdentifier(Integer.parseInt(values[2]));
  233.                 pi.header.setSystemNumber(Integer.parseInt(values[3]));
  234.                 pi.header.setSystemOccupancy(Integer.parseInt(values[4]));

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

  237.                 // Station network
  238.                 if (pi.version == 2) {
  239.                     pi.header.setStationNetword(values[6]);
  240.                 }

  241.             }

  242.             /** {@inheritDoc} */
  243.             @Override
  244.             public Stream<LineParser> allowedNext() {
  245.                 return Stream.of(H3, COMMENTS);
  246.             }

  247.         },

  248.         /** Format header. */
  249.         H3("H3", "h3") {

  250.             /** {@inheritDoc} */
  251.             @Override
  252.             public void parse(final String line, final ParseInfo pi) {

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

  255.                 // Target name
  256.                 pi.header.setName(values[1]);

  257.                 // Identifiers
  258.                 pi.header.setIlrsSatelliteId(values[2]);
  259.                 pi.header.setSic(values[3]);
  260.                 pi.header.setNoradId(values[4]);

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

  263.                 // Target class and location (if needed)
  264.                 pi.header.setTargetClass(Integer.parseInt(values[6]));
  265.                 if (pi.version == 2) {
  266.                     pi.header.setTargetLocation(Integer.parseInt(values[7]));
  267.                 }

  268.             }

  269.             /** {@inheritDoc} */
  270.             @Override
  271.             public Stream<LineParser> allowedNext() {
  272.                 return Stream.of(H4, COMMENTS);
  273.             }

  274.         },

  275.         /** Format header. */
  276.         H4("H4", "h4") {

  277.             /** {@inheritDoc} */
  278.             @Override
  279.             public void parse(final String line, final ParseInfo pi) {

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

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

  284.                 // Start epoch
  285.                 final int    yearS   = Integer.parseInt(values[2]);
  286.                 final int    monthS  = Integer.parseInt(values[3]);
  287.                 final int    dayS    = Integer.parseInt(values[4]);
  288.                 final int    hourS   = Integer.parseInt(values[5]);
  289.                 final int    minuteS = Integer.parseInt(values[6]);
  290.                 final double secondS = Integer.parseInt(values[7]);

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

  292.                 pi.header.setStartEpoch(new AbsoluteDate(yearS, monthS, dayS,
  293.                         hourS, minuteS, secondS,
  294.                         pi.timeScale));

  295.                 // End epoch
  296.                 final int    yearE   = Integer.parseInt(values[8]);
  297.                 final int    monthE  = Integer.parseInt(values[9]);
  298.                 final int    dayE    = Integer.parseInt(values[10]);
  299.                 final int    hourE   = Integer.parseInt(values[11]);
  300.                 final int    minuteE = Integer.parseInt(values[12]);
  301.                 final double secondE = Integer.parseInt(values[13]);

  302.                 pi.header.setEndEpoch(new AbsoluteDate(yearE, monthE, dayE,
  303.                         hourE, minuteE, secondE,
  304.                         pi.timeScale));

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

  307.                 // Correction flags
  308.                 pi.header.setIsTroposphericRefractionApplied(readBoolean(values[15]));
  309.                 pi.header.setIsCenterOfMassCorrectionApplied(readBoolean(values[16]));
  310.                 pi.header.setIsReceiveAmplitudeCorrectionApplied(readBoolean(values[17]));
  311.                 pi.header.setIsStationSystemDelayApplied(readBoolean(values[18]));
  312.                 pi.header.setIsTransponderDelayApplied(readBoolean(values[19]));

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

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

  317.             }

  318.             /** {@inheritDoc} */
  319.             @Override
  320.             public Stream<LineParser> allowedNext() {
  321.                 return Stream.of(H5, C0, COMMENTS);
  322.             }

  323.         },

  324.         /** Format header. */
  325.         H5("H5", "h5") {

  326.             /** {@inheritDoc} */
  327.             @Override
  328.             public void parse(final String line, final ParseInfo pi) {

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

  331.                 // Fill data
  332.                 pi.header.setPredictionType(Integer.parseInt(values[1]));
  333.                 pi.header.setYearOfCentury(Integer.parseInt(values[2]));
  334.                 pi.header.setDateAndTime(values[3]);
  335.                 pi.header.setPredictionProvider(values[4]);
  336.                 pi.header.setSequenceNumber(Integer.parseInt(values[5]));

  337.             }

  338.             /** {@inheritDoc} */
  339.             @Override
  340.             public Stream<LineParser> allowedNext() {
  341.                 return Stream.of(C0, COMMENTS);
  342.             }

  343.         },

  344.         /** System configuration record. */
  345.         C0("C0", "c0") {

  346.             /** {@inheritDoc} */
  347.             @Override
  348.             public void parse(final String line, final ParseInfo pi) {

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

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

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

  355.                 // System ID
  356.                 systemRecord.setSystemId(values[3]);

  357.                 // Set the system configuration record
  358.                 pi.configurationRecords.setSystemRecord(systemRecord);

  359.             }

  360.             /** {@inheritDoc} */
  361.             @Override
  362.             public Stream<LineParser> allowedNext() {
  363.                 return Stream.of(C1, C2, C3, C4, C5, C6, C7, TEN, ELEVEN, METEO, ANGLES, CALIB, STAT, COMPATIBILITY, COMMENTS);
  364.             }

  365.         },


  366.         /** Laser configuration record. */
  367.         C1("C1", "c1") {

  368.             /** {@inheritDoc} */
  369.             @Override
  370.             public void parse(final String line, final ParseInfo pi) {

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

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

  375.                 // Fill values
  376.                 laserRecord.setLaserId(values[2]);
  377.                 laserRecord.setLaserType(values[3]);
  378.                 laserRecord.setPrimaryWavelength(NM.toSI(Double.parseDouble(values[4])));
  379.                 laserRecord.setNominalFireRate(Double.parseDouble(values[5]));
  380.                 laserRecord.setPulseEnergy(Double.parseDouble(values[6]));
  381.                 laserRecord.setPulseWidth(Double.parseDouble(values[7]));
  382.                 laserRecord.setBeamDivergence(Double.parseDouble(values[8]));
  383.                 laserRecord.setPulseInOutgoingSemiTrain(Integer.parseInt(values[9]));

  384.                 // Set the laser configuration record
  385.                 pi.configurationRecords.setLaserRecord(laserRecord);

  386.             }

  387.             /** {@inheritDoc} */
  388.             @Override
  389.             public Stream<LineParser> allowedNext() {
  390.                 return Stream.of(C2, C3, C4, C5, C6, C7, TEN, ELEVEN, METEO, ANGLES, CALIB, STAT, COMPATIBILITY, COMMENTS);
  391.             }

  392.         },

  393.         /** Detector configuration record. */
  394.         C2("C2", "c2") {

  395.             /** {@inheritDoc} */
  396.             @Override
  397.             public void parse(final String line, final ParseInfo pi) {

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

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

  402.                 // Fill values
  403.                 detectorRecord.setDetectorId(values[2]);
  404.                 detectorRecord.setDetectorType(values[3]);
  405.                 detectorRecord.setApplicableWavelength(NM.toSI(Double.parseDouble(values[4])));
  406.                 detectorRecord.setQuantumEfficiency(Double.parseDouble(values[5]));
  407.                 detectorRecord.setAppliedVoltage(Double.parseDouble(values[6]));
  408.                 detectorRecord.setDarkCount(KHZ.toSI(Double.parseDouble(values[7])));
  409.                 detectorRecord.setOutputPulseType(values[8]);
  410.                 detectorRecord.setOutputPulseWidth(Double.parseDouble(values[9]));
  411.                 detectorRecord.setSpectralFilter(NM.toSI(Double.parseDouble(values[10])));
  412.                 detectorRecord.setTransmissionOfSpectralFilter(Double.parseDouble(values[11]));
  413.                 detectorRecord.setSpatialFilter(Double.parseDouble(values[12]));
  414.                 detectorRecord.setExternalSignalProcessing(values[13]);

  415.                 // Check file version for additional data
  416.                 if (pi.version == 2) {
  417.                     detectorRecord.setAmplifierGain(Double.parseDouble(values[14]));
  418.                     detectorRecord.setAmplifierBandwidth(KHZ.toSI(Double.parseDouble(values[15])));
  419.                     detectorRecord.setAmplifierInUse(values[16]);
  420.                 }

  421.                 // Set the detector configuration record
  422.                 pi.configurationRecords.setDetectorRecord(detectorRecord);

  423.             }

  424.             /** {@inheritDoc} */
  425.             @Override
  426.             public Stream<LineParser> allowedNext() {
  427.                 return Stream.of(C3, C4, C5, C6, C7, TEN, ELEVEN, METEO, ANGLES, CALIB, STAT, COMPATIBILITY, COMMENTS);
  428.             }

  429.         },

  430.         /** Timing system configuration record. */
  431.         C3("C3", "c3") {

  432.             /** {@inheritDoc} */
  433.             @Override
  434.             public void parse(final String line, final ParseInfo pi) {

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

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

  439.                 // Fill values
  440.                 timingRecord.setLocalTimingId(values[2]);
  441.                 timingRecord.setTimeSource(values[3]);
  442.                 timingRecord.setFrequencySource(values[4]);
  443.                 timingRecord.setTimer(values[5]);
  444.                 timingRecord.setTimerSerialNumber(values[6]);
  445.                 timingRecord.setEpochDelayCorrection(US.toSI(Double.parseDouble(values[7])));

  446.                 // Set the timing system configuration record
  447.                 pi.configurationRecords.setTimingRecord(timingRecord);

  448.             }

  449.             /** {@inheritDoc} */
  450.             @Override
  451.             public Stream<LineParser> allowedNext() {
  452.                 return Stream.of(C4, C5, C6, C7, TEN, ELEVEN, METEO, ANGLES, CALIB, STAT, COMPATIBILITY, COMMENTS);
  453.             }

  454.         },

  455.         /** Transponder configuration record. */
  456.         C4("C4", "c4") {

  457.             /** {@inheritDoc} */
  458.             @Override
  459.             public void parse(final String line, final ParseInfo pi) {

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

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

  464.                 // Estimated offsets and drifts
  465.                 transponderRecord.setTransponderId(values[2]);
  466.                 transponderRecord.setStationUTCOffset(NM.toSI(Double.parseDouble(values[3])));
  467.                 transponderRecord.setStationOscDrift(Double.parseDouble(values[4]));
  468.                 transponderRecord.setTranspUTCOffset(NM.toSI(Double.parseDouble(values[5])));
  469.                 transponderRecord.setTranspOscDrift(Double.parseDouble(values[6]));

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

  472.                 // Clock and drift indicators
  473.                 transponderRecord.setStationClockAndDriftApplied(Integer.parseInt(values[8]));
  474.                 transponderRecord.setSpacecraftClockAndDriftApplied(Integer.parseInt(values[9]));

  475.                 // Spacecraft time simplified
  476.                 transponderRecord.setIsSpacecraftTimeSimplified(readBoolean(values[10]));

  477.                 // Set the transponder configuration record
  478.                 pi.configurationRecords.setTransponderRecord(transponderRecord);

  479.             }

  480.             /** {@inheritDoc} */
  481.             @Override
  482.             public Stream<LineParser> allowedNext() {
  483.                 return Stream.of(C5, C6, C7, TEN, ELEVEN, METEO, ANGLES, CALIB, STAT, COMPATIBILITY, COMMENTS);
  484.             }

  485.         },

  486.         /** Software configuration record. */
  487.         C5("C5", "c5") {

  488.             /** {@inheritDoc} */
  489.             @Override
  490.             public void parse(final String line, final ParseInfo pi) {

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

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

  495.                 // Fill values
  496.                 softwareRecord.setSoftwareId(values[2]);
  497.                 softwareRecord.setTrackingSoftwares(COMMA.split(values[3]));
  498.                 softwareRecord.setTrackingSoftwareVersions(COMMA.split(values[4]));
  499.                 softwareRecord.setProcessingSoftwares(COMMA.split(values[5]));
  500.                 softwareRecord.setProcessingSoftwareVersions(COMMA.split(values[6]));

  501.                 // Set the software configuration record
  502.                 pi.configurationRecords.setSoftwareRecord(softwareRecord);

  503.             }

  504.             /** {@inheritDoc} */
  505.             @Override
  506.             public Stream<LineParser> allowedNext() {
  507.                 return Stream.of(C6, C7, TEN, ELEVEN, METEO, ANGLES, CALIB, STAT, COMPATIBILITY, COMMENTS);
  508.             }

  509.         },

  510.         /** Meteorological instrumentation configuration record. */
  511.         C6("C6", "c6") {

  512.             /** {@inheritDoc} */
  513.             @Override
  514.             public void parse(final String line, final ParseInfo pi) {

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

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

  519.                 // Fill values
  520.                 meteoRecord.setMeteorologicalId(values[2]);
  521.                 meteoRecord.setPressSensorManufacturer(values[3]);
  522.                 meteoRecord.setPressSensorModel(values[4]);
  523.                 meteoRecord.setPressSensorSerialNumber(values[5]);
  524.                 meteoRecord.setTempSensorManufacturer(values[6]);
  525.                 meteoRecord.setTempSensorModel(values[7]);
  526.                 meteoRecord.setTempSensorSerialNumber(values[8]);
  527.                 meteoRecord.setHumiSensorManufacturer(values[9]);
  528.                 meteoRecord.setHumiSensorModel(values[10]);
  529.                 meteoRecord.setHumiSensorSerialNumber(values[11]);

  530.                 // Set the meteorological configuration record
  531.                 pi.configurationRecords.setMeteorologicalRecord(meteoRecord);

  532.             }

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

  538.         },

  539.         /** Calibration Target configuration record. */
  540.         C7("C7", "c7") {

  541.             /** {@inheritDoc} */
  542.             @Override
  543.             public void parse(final String line, final ParseInfo pi) {
  544.                 // Not implemented yet
  545.             }

  546.             /** {@inheritDoc} */
  547.             @Override
  548.             public Stream<LineParser> allowedNext() {
  549.                 return Stream.of(TEN, ELEVEN, METEO, ANGLES, CALIB, STAT, COMPATIBILITY, COMMENTS);
  550.             }

  551.         },

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

  554.             /** Storage for the last epoch parsed to compare with the current.*/
  555.             private double lastSecOfDay = 0;

  556.             /** Shift in days due to the rollover.*/
  557.             private int dayShift = 0;

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

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

  563.                 // Read data
  564.                 final double secOfDay     = Double.parseDouble(values[1]);
  565.                 final double timeOfFlight = Double.parseDouble(values[2]);
  566.                 final int    epochEvent   = Integer.parseInt(values[4]);

  567.                 // Check secOfDay for rollover
  568.                 dayShift = dayShift + checkRollover(lastSecOfDay, secOfDay);
  569.                 // Initialise a new Range measurement
  570.                 AbsoluteDate epoch = new AbsoluteDate(pi.startEpoch, new TimeComponents(secOfDay), pi.timeScale);
  571.                 if (pi.lastRange != null) {
  572.                     final double duration = epoch.durationFrom(pi.lastRange.getDate());
  573.                     if (duration < 0) {
  574.                         epoch = epoch.shiftedBy(Constants.JULIAN_DAY);
  575.                     }
  576.                 }
  577.                 final RangeMeasurement range = new RangeMeasurement(epoch, timeOfFlight, epochEvent);
  578.                 pi.dataBlock.addRangeData(range);

  579.                 lastSecOfDay = secOfDay;
  580.                 pi.lastRange = range;

  581.             }

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

  587.         },

  588.         /** Range Record (Normal point). */
  589.         ELEVEN("11") {

  590.             /** Storage for the last epoch parsed to compare with the current.*/
  591.             private double lastSecOfDay = 0;

  592.             /** Shift in days due to the rollover.*/
  593.             private int dayShift = 0;

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

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

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

  604.                 // Check secOfDay for rollover
  605.                 dayShift = dayShift + checkRollover(lastSecOfDay, secOfDay);
  606.                 // Initialise a new Range measurement
  607.                 AbsoluteDate epoch = new AbsoluteDate(pi.startEpoch, new TimeComponents(secOfDay), pi.timeScale);
  608.                 if (pi.lastRange != null) {
  609.                     final double duration = epoch.durationFrom(pi.lastRange.getDate());
  610.                     if (duration < 0) {
  611.                         epoch = epoch.shiftedBy(Constants.JULIAN_DAY);
  612.                     }
  613.                 }
  614.                 final RangeMeasurement range = new RangeMeasurement(epoch, timeOfFlight, epochEvent, snr);
  615.                 pi.dataBlock.addRangeData(range);

  616.                 lastSecOfDay = secOfDay;
  617.                 pi.lastRange = range;

  618.             }

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

  624.         },

  625.         /** Range Supplement Record. */
  626.         TWELVE("12") {

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

  632.             /** {@inheritDoc} */
  633.             @Override
  634.             public Stream<LineParser> allowedNext() {
  635.                 return Stream.of(H8, TEN, ELEVEN, TWELVE, METEO, ANGLES, CALIB, STAT, COMPATIBILITY, COMMENTS);
  636.             }

  637.         },

  638.         /** Meteorological record. */
  639.         METEO("20") {

  640.             /** Storage for the last epoch parsed to compare with the current.*/
  641.             private double lastSecOfDay = 0;

  642.             /** Shift in days due to the rollover.*/
  643.             private int dayShift = 0;

  644.             /** {@inheritDoc} */
  645.             @Override
  646.             public void parse(final String line, final ParseInfo pi) {

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

  649.                 // Read data
  650.                 final double secOfDay    = Double.parseDouble(values[1]);
  651.                 final double pressure    = MBAR_TO_BAR.convert(Double.parseDouble(values[2]));
  652.                 final double temperature = Double.parseDouble(values[3]);
  653.                 final double humidity    = Double.parseDouble(values[4]);

  654.                 // Check secOfDay for rollover
  655.                 dayShift = dayShift + checkRollover(lastSecOfDay, secOfDay);
  656.                 // Initialise a new Range measurement
  657.                 final AbsoluteDate epoch = new AbsoluteDate(pi.startEpoch, new TimeComponents(secOfDay), pi.timeScale).shiftedBy(dayShift * 86400);
  658.                 final MeteorologicalMeasurement meteo = new MeteorologicalMeasurement(epoch, pressure,
  659.                         temperature, humidity);
  660.                 pi.dataBlock.addMeteoData(meteo);

  661.             }

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

  667.         },

  668.         /** Meteorological Supplement record. */
  669.         METEO_SUPP("21") {

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

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

  680.         },

  681.         /** Pointing Angle Record. */
  682.         ANGLES("30") {

  683.             /** Storage for the last epoch parsed to compare with the current.*/
  684.             private double lastSecOfDay = 0;

  685.             /** Shift in days due to the rollover.*/
  686.             private int dayShift = 0;

  687.             /** {@inheritDoc} */
  688.             @Override
  689.             public void parse(final String line, final ParseInfo pi) {

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

  692.                 // Read data
  693.                 final double  secOfDay              = Double.parseDouble(values[1]);
  694.                 final double  azmiuth               = FastMath.toRadians(Double.parseDouble(values[2]));
  695.                 final double  elevation             = FastMath.toRadians(Double.parseDouble(values[3]));
  696.                 final int     directionFlag         = Integer.parseInt(values[4]);
  697.                 final int     orginFlag             = Integer.parseInt(values[5]);
  698.                 final boolean isRefractionCorrected = readBoolean(values[6]);


  699.                 // Angles rates
  700.                 double azimuthRate   = Double.NaN;
  701.                 double elevationRate = Double.NaN;
  702.                 if (pi.version == 2) {
  703.                     azimuthRate   = readDoubleWithNaN(values[7]);
  704.                     elevationRate = readDoubleWithNaN(values[8]);
  705.                 }

  706.                 // Check secOfDay for rollover
  707.                 dayShift = dayShift + checkRollover(lastSecOfDay, secOfDay);
  708.                 // Initialise a new angles measurement
  709.                 final AbsoluteDate epoch = new AbsoluteDate(pi.startEpoch, new TimeComponents(secOfDay), pi.timeScale);
  710.                 final AnglesMeasurement angles = new AnglesMeasurement(epoch, azmiuth, elevation,
  711.                         directionFlag, orginFlag,
  712.                         isRefractionCorrected,
  713.                         azimuthRate, elevationRate);
  714.                 pi.dataBlock.addAnglesData(angles);

  715.             }

  716.             /** {@inheritDoc} */
  717.             @Override
  718.             public Stream<LineParser> allowedNext() {
  719.                 return Stream.of(H8, METEO, TEN, ELEVEN, ANGLES, CALIB, STAT, COMPATIBILITY, COMMENTS);
  720.             }

  721.         },

  722.         /** Calibration Record. */
  723.         CALIB("40") {

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

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

  734.         },

  735.         /** Calibration Details Record. */
  736.         CALIB_DETAILS("41") {

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

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

  747.         },

  748.         /** Calibration "Shot" Record. */
  749.         CALIB_SHOT("42") {

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

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

  760.         },

  761.         /** Session (Pass) Statistics Record. */
  762.         STAT("50") {

  763.             /** {@inheritDoc} */
  764.             @Override
  765.             public void parse(final String line, final ParseInfo pi) {
  766.                 // Not implemented yet
  767.             }

  768.             /** {@inheritDoc} */
  769.             @Override
  770.             public Stream<LineParser> allowedNext() {
  771.                 return Stream.of(H8, METEO, CALIB, CALIB_DETAILS, CALIB_SHOT, TEN, ELEVEN, TWELVE, ANGLES, STAT, COMPATIBILITY, H8, COMMENTS);
  772.             }

  773.         },

  774.         /** Compatibility record. */
  775.         COMPATIBILITY("60") {

  776.             /** {@inheritDoc} */
  777.             @Override
  778.             public void parse(final String line, final ParseInfo pi) {
  779.                 // Not implemented yet
  780.             }

  781.             /** {@inheritDoc} */
  782.             @Override
  783.             public Stream<LineParser> allowedNext() {
  784.                 return Stream.of(H8, METEO, CALIB, CALIB_DETAILS, CALIB_SHOT, TEN, ELEVEN, TWELVE, ANGLES, STAT, COMPATIBILITY, COMMENTS);
  785.             }

  786.         },

  787.         /** Comments. */
  788.         COMMENTS("00") {

  789.             /** {@inheritDoc} */
  790.             @Override
  791.             public void parse(final String line, final ParseInfo pi) {

  792.                 // Comment
  793.                 final String comment = line.split(getFirstIdentifier())[1].trim();
  794.                 pi.file.getComments().add(comment);

  795.             }

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

  801.             }

  802.         },

  803.         /** End of data block. */
  804.         H8("H8", "h8") {

  805.             /** {@inheritDoc} */
  806.             @Override
  807.             public void parse(final String line, final ParseInfo pi) {

  808.                 // Fill data block
  809.                 pi.dataBlock.setHeader(pi.header);
  810.                 pi.dataBlock.setConfigurationRecords(pi.configurationRecords);

  811.                 // Add the data block to the CRD file
  812.                 pi.file.addDataBlock(pi.dataBlock);

  813.                 // Initialize a new empty containers
  814.                 pi.startEpoch           = DateComponents.J2000_EPOCH;
  815.                 pi.header               = new CRDHeader();
  816.                 pi.configurationRecords = new CRDConfiguration();
  817.                 pi.dataBlock            = new CRDDataBlock();
  818.                 pi.lastRange            = null;

  819.             }

  820.             /** {@inheritDoc} */
  821.             @Override
  822.             public Stream<LineParser> allowedNext() {
  823.                 return Stream.of(H1, H9, COMMENTS);
  824.             }

  825.         },

  826.         /** Last record in file. */
  827.         H9("H9", "h9") {

  828.             /** {@inheritDoc} */
  829.             @Override
  830.             public void parse(final String line, final ParseInfo pi) {
  831.                 pi.done = true;
  832.             }

  833.             /** {@inheritDoc} */
  834.             @Override
  835.             public Stream<LineParser> allowedNext() {
  836.                 return Stream.of(H9);
  837.             }

  838.         };

  839.         /** Patterns for identifying line. */
  840.         private final Pattern[] patterns;

  841.         /** Identifiers. */
  842.         private final String[] identifiers;

  843.         /** Simple constructor.
  844.          * @param identifier regular expression for identifying line (i.e. first element)
  845.          */
  846.         LineParser(final String... identifier) {
  847.             this.identifiers = identifier;
  848.             // Initialise patterns
  849.             this.patterns    = new Pattern[identifiers.length];
  850.             for (int index = 0; index < patterns.length; index++) {
  851.                 patterns[index] = Pattern.compile(identifiers[index]);
  852.             }
  853.         }

  854.         /**
  855.          * Get the regular expression for identifying line.
  856.          * @return the regular expression for identifying line
  857.          */
  858.         public String getFirstIdentifier() {
  859.             return identifiers[0];
  860.         }

  861.         /** Parse a line.
  862.          * @param line line to parse
  863.          * @param pi holder for transient data
  864.          */
  865.         public abstract void parse(String line, ParseInfo pi);

  866.         /** Get the allowed parsers for next line.
  867.          * @return allowed parsers for next line
  868.          */
  869.         public abstract Stream<LineParser> allowedNext();

  870.         /** Check if parser can handle line.
  871.          * @param line line to parse
  872.          * @return true if parser can handle the specified line
  873.          */
  874.         public boolean canHandle(final String line) {
  875.             // Line identifier
  876.             final String lineId = SEPARATOR.split(line)[0];
  877.             // Loop on patterns
  878.             for (Pattern pattern : patterns) {
  879.                 if (pattern.matcher(lineId).matches()) {
  880.                     return true;
  881.                 }
  882.             }
  883.             // No match
  884.             return false;
  885.         }

  886.         /**
  887.          * Read a boolean from a string value.
  888.          * @param value input value
  889.          * @return the correspondin boolean
  890.          */
  891.         private static boolean readBoolean(final String value) {
  892.             return Integer.parseInt(value) == 1;
  893.         }

  894.         /**
  895.          * Read a double value taking into consideration a possible "na".
  896.          * @param value input string
  897.          * @return the corresponding double value
  898.          */
  899.         private static double readDoubleWithNaN(final String value) {
  900.             return "na".equals(value) ? Double.NaN : Double.parseDouble(value);
  901.         }

  902.     }

  903. }