OEMParser.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.ccsds;

  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.util.ArrayList;
  24. import java.util.List;
  25. import java.util.Scanner;

  26. import org.hipparchus.exception.DummyLocalizable;
  27. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  28. import org.hipparchus.linear.MatrixUtils;
  29. import org.hipparchus.linear.RealMatrix;
  30. import org.orekit.annotation.DefaultDataContext;
  31. import org.orekit.data.DataContext;
  32. import org.orekit.errors.OrekitException;
  33. import org.orekit.errors.OrekitMessages;
  34. import org.orekit.files.general.EphemerisFileParser;
  35. import org.orekit.frames.Frame;
  36. import org.orekit.frames.LOFType;
  37. import org.orekit.time.AbsoluteDate;
  38. import org.orekit.utils.IERSConventions;
  39. import org.orekit.utils.TimeStampedPVCoordinates;

  40. /**
  41.  * A parser for the CCSDS OEM (Orbit Ephemeris Message).
  42.  * @author sports
  43.  * @since 6.1
  44.  */
  45. public class OEMParser extends ODMParser implements EphemerisFileParser {

  46.     /** Default interpolation degree. */
  47.     private int interpolationDegree;

  48.     /** Simple constructor.
  49.      * <p>
  50.      * This class is immutable, and hence thread safe. When parts
  51.      * must be changed, such as reference date for Mission Elapsed Time or
  52.      * Mission Relative Time time systems, or the gravitational coefficient or
  53.      * the IERS conventions, the various {@code withXxx} methods must be called,
  54.      * which create a new immutable instance with the new parameters. This
  55.      * is a combination of the
  56.      * <a href="https://en.wikipedia.org/wiki/Builder_pattern">builder design
  57.      * pattern</a> and a
  58.      * <a href="http://en.wikipedia.org/wiki/Fluent_interface">fluent
  59.      * interface</a>.
  60.      * </p>
  61.      * <p>
  62.      * The initial date for Mission Elapsed Time and Mission Relative Time time systems is not set here.
  63.      * If such time systems are used, it must be initialized before parsing by calling {@link
  64.      * #withMissionReferenceDate(AbsoluteDate)}.
  65.      * </p>
  66.      * <p>
  67.      * The gravitational coefficient is not set here. If it is needed in order
  68.      * to parse Cartesian orbits where the value is not set in the CCSDS file, it must
  69.      * be initialized before parsing by calling {@link #withMu(double)}.
  70.      * </p>
  71.      * <p>
  72.      * The IERS conventions to use is not set here. If it is needed in order to
  73.      * parse some reference frames or UT1 time scale, it must be initialized before
  74.      * parsing by calling {@link #withConventions(IERSConventions)}.
  75.      * </p>
  76.      * <p>
  77.      * The international designator parameters (launch year, launch number and
  78.      * launch piece) are not set here. If they are needed, they must be initialized before
  79.      * parsing by calling {@link #withInternationalDesignator(int, int, String)}
  80.      * </p>
  81.      * <p>
  82.      * The default interpolation degree is not set here. It is set to one by default. If another value
  83.      * is needed it must be initialized before parsing by calling {@link #withInterpolationDegree(int)}
  84.      * </p>
  85.      *
  86.      * <p>This method uses the {@link DataContext#getDefault() default data context}. See
  87.      * {@link #withDataContext(DataContext)}.
  88.      */
  89.     @DefaultDataContext
  90.     public OEMParser() {
  91.         this(DataContext.getDefault());
  92.     }

  93.     /** Constructor with data context.
  94.      * <p>
  95.      * This class is immutable, and hence thread safe. When parts
  96.      * must be changed, such as reference date for Mission Elapsed Time or
  97.      * Mission Relative Time time systems, or the gravitational coefficient or
  98.      * the IERS conventions, the various {@code withXxx} methods must be called,
  99.      * which create a new immutable instance with the new parameters. This
  100.      * is a combination of the
  101.      * <a href="https://en.wikipedia.org/wiki/Builder_pattern">builder design
  102.      * pattern</a> and a
  103.      * <a href="http://en.wikipedia.org/wiki/Fluent_interface">fluent
  104.      * interface</a>.
  105.      * </p>
  106.      * <p>
  107.      * The initial date for Mission Elapsed Time and Mission Relative Time time systems is not set here.
  108.      * If such time systems are used, it must be initialized before parsing by calling {@link
  109.      * #withMissionReferenceDate(AbsoluteDate)}.
  110.      * </p>
  111.      * <p>
  112.      * The gravitational coefficient is not set here. If it is needed in order
  113.      * to parse Cartesian orbits where the value is not set in the CCSDS file, it must
  114.      * be initialized before parsing by calling {@link #withMu(double)}.
  115.      * </p>
  116.      * <p>
  117.      * The IERS conventions to use is not set here. If it is needed in order to
  118.      * parse some reference frames or UT1 time scale, it must be initialized before
  119.      * parsing by calling {@link #withConventions(IERSConventions)}.
  120.      * </p>
  121.      * <p>
  122.      * The international designator parameters (launch year, launch number and
  123.      * launch piece) are not set here. If they are needed, they must be initialized before
  124.      * parsing by calling {@link #withInternationalDesignator(int, int, String)}
  125.      * </p>
  126.      * <p>
  127.      * The default interpolation degree is not set here. It is set to one by default. If another value
  128.      * is needed it must be initialized before parsing by calling {@link #withInterpolationDegree(int)}
  129.      * </p>
  130.      *
  131.      * @param dataContext used by the parser.
  132.      *
  133.      * @see #OEMParser()
  134.      * @see #withDataContext(DataContext)
  135.      * @since 10.1
  136.      */
  137.     public OEMParser(final DataContext dataContext) {
  138.         this(AbsoluteDate.FUTURE_INFINITY, Double.NaN, null, true, 0, 0, "", 1, dataContext);
  139.     }

  140.     /** Complete constructor.
  141.      * @param missionReferenceDate reference date for Mission Elapsed Time or Mission Relative Time time systems
  142.      * @param mu gravitational coefficient
  143.      * @param conventions IERS Conventions
  144.      * @param simpleEOP if true, tidal effects are ignored when interpolating EOP
  145.      * @param launchYear launch year for TLEs
  146.      * @param launchNumber launch number for TLEs
  147.      * @param launchPiece piece of launch (from "A" to "ZZZ") for TLEs
  148.      * @param interpolationDegree interpolation degree
  149.      * @param dataContext used to retrieve frames, time scales, etc.
  150.      */
  151.     private OEMParser(final AbsoluteDate missionReferenceDate, final double mu,
  152.                       final IERSConventions conventions, final boolean simpleEOP,
  153.                       final int launchYear, final int launchNumber,
  154.                       final String launchPiece, final int interpolationDegree,
  155.                       final DataContext dataContext) {
  156.         super(missionReferenceDate, mu, conventions, simpleEOP, launchYear, launchNumber,
  157.                 launchPiece, dataContext);
  158.         this.interpolationDegree = interpolationDegree;
  159.     }

  160.     /** {@inheritDoc} */
  161.     @Override
  162.     public OEMParser withMissionReferenceDate(final AbsoluteDate newMissionReferenceDate) {
  163.         return new OEMParser(newMissionReferenceDate, getMu(), getConventions(), isSimpleEOP(),
  164.                              getLaunchYear(), getLaunchNumber(), getLaunchPiece(),
  165.                              getInterpolationDegree(), getDataContext());
  166.     }

  167.     /** {@inheritDoc} */
  168.     @Override
  169.     public OEMParser withMu(final double newMu) {
  170.         return new OEMParser(getMissionReferenceDate(), newMu, getConventions(), isSimpleEOP(),
  171.                              getLaunchYear(), getLaunchNumber(), getLaunchPiece(),
  172.                              getInterpolationDegree(), getDataContext());
  173.     }

  174.     /** {@inheritDoc} */
  175.     @Override
  176.     public OEMParser withConventions(final IERSConventions newConventions) {
  177.         return new OEMParser(getMissionReferenceDate(), getMu(), newConventions, isSimpleEOP(),
  178.                              getLaunchYear(), getLaunchNumber(), getLaunchPiece(),
  179.                              getInterpolationDegree(), getDataContext());
  180.     }

  181.     /** {@inheritDoc} */
  182.     @Override
  183.     public OEMParser withSimpleEOP(final boolean newSimpleEOP) {
  184.         return new OEMParser(getMissionReferenceDate(), getMu(), getConventions(), newSimpleEOP,
  185.                              getLaunchYear(), getLaunchNumber(), getLaunchPiece(),
  186.                              getInterpolationDegree(), getDataContext());
  187.     }

  188.     /** {@inheritDoc} */
  189.     @Override
  190.     public OEMParser withInternationalDesignator(final int newLaunchYear,
  191.                                                  final int newLaunchNumber,
  192.                                                  final String newLaunchPiece) {
  193.         return new OEMParser(getMissionReferenceDate(), getMu(), getConventions(), isSimpleEOP(),
  194.                              newLaunchYear, newLaunchNumber, newLaunchPiece,
  195.                              getInterpolationDegree(), getDataContext());
  196.     }

  197.     /** {@inheritDoc} */
  198.     @Override
  199.     public OEMParser withDataContext(final DataContext dataContext) {
  200.         return new OEMParser(getMissionReferenceDate(), getMu(), getConventions(), isSimpleEOP(),
  201.                              getLaunchYear(), getLaunchNumber(), getLaunchPiece(),
  202.                              getInterpolationDegree(), dataContext);
  203.     }

  204.     /** Set default interpolation degree.
  205.      * <p>
  206.      * This method may be used to set a default interpolation degree which will be used
  207.      * when no interpolation degree is parsed in the meta-data of the file. Upon instantiation
  208.      * with {@link #OEMParser(DataContext)} the default interpolation degree is one.
  209.      * </p>
  210.      * @param newInterpolationDegree default interpolation degree to use while parsing
  211.      * @return a new instance, with interpolation degree data replaced
  212.      * @see #getInterpolationDegree()
  213.      * @since 10.3
  214.      */
  215.     public OEMParser withInterpolationDegree(final int newInterpolationDegree) {
  216.         return new OEMParser(getMissionReferenceDate(), getMu(), getConventions(), isSimpleEOP(),
  217.                              getLaunchYear(), getLaunchNumber(), getLaunchPiece(),
  218.                              newInterpolationDegree, getDataContext());
  219.     }

  220.     /** Get default interpolation degree.
  221.      * @return interpolationDegree default interpolation degree to use while parsing
  222.      * @see #withInterpolationDegree(int)
  223.      * @since 10.3
  224.      */
  225.     public int getInterpolationDegree() {
  226.         return interpolationDegree;
  227.     }

  228.     /** {@inheritDoc} */
  229.     @Override
  230.     public OEMFile parse(final String fileName) {
  231.         return (OEMFile) super.parse(fileName);
  232.     }

  233.     /** {@inheritDoc} */
  234.     @Override
  235.     public OEMFile parse(final InputStream stream) {
  236.         return (OEMFile) super.parse(stream);
  237.     }

  238.     /** {@inheritDoc} */
  239.     @Override
  240.     public OEMFile parse(final InputStream stream, final String fileName) {
  241.         try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) {
  242.             return parse(reader, fileName);
  243.         } catch (IOException ioe) {
  244.             throw new OrekitException(ioe, new DummyLocalizable(ioe.getMessage()));
  245.         }
  246.     }

  247.     /** {@inheritDoc} */
  248.     @Override
  249.     public OEMFile parse(final BufferedReader reader, final String fileName) {

  250.         try {

  251.             // initialize internal data structures
  252.             final ParseInfo pi = new ParseInfo();
  253.             pi.fileName = fileName;
  254.             final OEMFile file = pi.file;

  255.             // set the additional data that has been configured prior the parsing by the user.
  256.             pi.file.setMissionReferenceDate(getMissionReferenceDate());
  257.             pi.file.setMuSet(getMu());
  258.             pi.file.setConventions(getConventions());
  259.             pi.file.setDataContext(getDataContext());

  260.             for (String line = reader.readLine(); line != null; line = reader.readLine()) {
  261.                 ++pi.lineNumber;
  262.                 if (line.trim().length() == 0) {
  263.                     continue;
  264.                 }
  265.                 pi.keyValue = new KeyValue(line, pi.lineNumber, pi.fileName);
  266.                 if (pi.keyValue.getKeyword() == null) {
  267.                     throw new OrekitException(OrekitMessages.CCSDS_UNEXPECTED_KEYWORD, pi.lineNumber, pi.fileName, line);
  268.                 }
  269.                 switch (pi.keyValue.getKeyword()) {
  270.                     case CCSDS_OEM_VERS:
  271.                         file.setFormatVersion(pi.keyValue.getDoubleValue());
  272.                         break;

  273.                     case META_START:
  274.                         file.addEphemeridesBlock();
  275.                         pi.lastEphemeridesBlock = file.getEphemeridesBlocks().get(file.getEphemeridesBlocks().size() - 1);
  276.                         pi.lastEphemeridesBlock.getMetaData().setLaunchYear(getLaunchYear());
  277.                         pi.lastEphemeridesBlock.getMetaData().setLaunchNumber(getLaunchNumber());
  278.                         pi.lastEphemeridesBlock.getMetaData().setLaunchPiece(getLaunchPiece());
  279.                         pi.lastEphemeridesBlock.setInterpolationDegree(getInterpolationDegree());
  280.                         break;

  281.                     case START_TIME:
  282.                         pi.lastEphemeridesBlock.setStartTime(parseDate(pi.keyValue.getValue(),
  283.                                                                        pi.lastEphemeridesBlock.getMetaData().getTimeSystem()));
  284.                         break;

  285.                     case USEABLE_START_TIME:
  286.                         pi.lastEphemeridesBlock.setUseableStartTime(parseDate(pi.keyValue.getValue(),
  287.                                                                               pi.lastEphemeridesBlock.getMetaData().getTimeSystem()));
  288.                         break;

  289.                     case USEABLE_STOP_TIME:
  290.                         pi.lastEphemeridesBlock.setUseableStopTime(parseDate(pi.keyValue.getValue(), pi.lastEphemeridesBlock.getMetaData().getTimeSystem()));
  291.                         break;

  292.                     case STOP_TIME:
  293.                         pi.lastEphemeridesBlock.setStopTime(parseDate(pi.keyValue.getValue(), pi.lastEphemeridesBlock.getMetaData().getTimeSystem()));
  294.                         break;

  295.                     case INTERPOLATION:
  296.                         pi.lastEphemeridesBlock.setInterpolationMethod(pi.keyValue.getValue());
  297.                         break;

  298.                     case INTERPOLATION_DEGREE:
  299.                         pi.lastEphemeridesBlock.setInterpolationDegree(Integer.parseInt(pi.keyValue.getValue()));
  300.                         break;

  301.                     case META_STOP:
  302.                         file.setMuUsed();
  303.                         parseEphemeridesDataLines(reader, pi);
  304.                         break;

  305.                     case COVARIANCE_START:
  306.                         parseCovarianceDataLines(reader, pi);
  307.                         break;

  308.                     default:
  309.                         boolean parsed = false;
  310.                         parsed = parsed || parseComment(pi.keyValue, pi.commentTmp);
  311.                         parsed = parsed || parseHeaderEntry(pi.keyValue, file, pi.commentTmp);
  312.                         if (pi.lastEphemeridesBlock != null) {
  313.                             parsed = parsed || parseMetaDataEntry(pi.keyValue,
  314.                                                                   pi.lastEphemeridesBlock.getMetaData(), pi.commentTmp);
  315.                             if (parsed && pi.keyValue.getKeyword() == Keyword.REF_FRAME_EPOCH) {
  316.                                 pi.lastEphemeridesBlock.setHasRefFrameEpoch(true);
  317.                             }
  318.                         }
  319.                         if (!parsed) {
  320.                             throw new OrekitException(OrekitMessages.CCSDS_UNEXPECTED_KEYWORD, pi.lineNumber, pi.fileName, line);
  321.                         }
  322.                 }
  323.             }
  324.             file.checkTimeSystems();
  325.             return file;
  326.         } catch (IOException ioe) {
  327.             throw new OrekitException(ioe, new DummyLocalizable(ioe.getMessage()));
  328.         }
  329.     }

  330.     /**
  331.      * Parse an ephemeris data line and add its content to the ephemerides
  332.      * block.
  333.      *
  334.      * @param reader the reader
  335.      * @param pi the parser info
  336.      * @exception IOException if an error occurs while reading from the stream
  337.      */
  338.     private void parseEphemeridesDataLines(final BufferedReader reader,  final ParseInfo pi)
  339.         throws IOException {

  340.         for (String line = reader.readLine(); line != null; line = reader.readLine()) {

  341.             ++pi.lineNumber;
  342.             if (line.trim().length() > 0) {
  343.                 pi.keyValue = new KeyValue(line, pi.lineNumber, pi.fileName);
  344.                 if (pi.keyValue.getKeyword() == null) {
  345.                     try (Scanner sc = new Scanner(line)) {
  346.                         final AbsoluteDate date = parseDate(sc.next(), pi.lastEphemeridesBlock.getMetaData().getTimeSystem());
  347.                         final Vector3D position = new Vector3D(Double.parseDouble(sc.next()) * 1000,
  348.                                                                Double.parseDouble(sc.next()) * 1000,
  349.                                                                Double.parseDouble(sc.next()) * 1000);
  350.                         final Vector3D velocity = new Vector3D(Double.parseDouble(sc.next()) * 1000,
  351.                                                                Double.parseDouble(sc.next()) * 1000,
  352.                                                                Double.parseDouble(sc.next()) * 1000);
  353.                         Vector3D acceleration = Vector3D.NaN;
  354.                         boolean hasAcceleration = false;
  355.                         if (sc.hasNext()) {
  356.                             acceleration = new Vector3D(Double.parseDouble(sc.next()) * 1000,
  357.                                                         Double.parseDouble(sc.next()) * 1000,
  358.                                                         Double.parseDouble(sc.next()) * 1000);
  359.                             hasAcceleration = true;
  360.                         }
  361.                         final TimeStampedPVCoordinates epDataLine;
  362.                         if (hasAcceleration) {
  363.                             epDataLine = new TimeStampedPVCoordinates(date, position, velocity, acceleration);
  364.                         } else {
  365.                             epDataLine = new TimeStampedPVCoordinates(date, position, velocity);
  366.                         }
  367.                         pi.lastEphemeridesBlock.getEphemeridesDataLines().add(epDataLine);
  368.                         pi.lastEphemeridesBlock.updateHasAcceleration(hasAcceleration);
  369.                     } catch (NumberFormatException nfe) {
  370.                         throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  371.                                                   pi.lineNumber, pi.fileName, line);
  372.                     }
  373.                 } else {
  374.                     switch (pi.keyValue.getKeyword()) {
  375.                         case META_START:
  376.                             pi.lastEphemeridesBlock.setEphemeridesDataLinesComment(pi.commentTmp);
  377.                             pi.commentTmp.clear();
  378.                             pi.lineNumber--;
  379.                             reader.reset();
  380.                             return;
  381.                         case COVARIANCE_START:
  382.                             pi.lastEphemeridesBlock.setEphemeridesDataLinesComment(pi.commentTmp);
  383.                             pi.commentTmp.clear();
  384.                             pi.lineNumber--;
  385.                             reader.reset();
  386.                             return;
  387.                         case COMMENT:
  388.                             pi.commentTmp.add(pi.keyValue.getValue());
  389.                             break;
  390.                         default :
  391.                             throw new OrekitException(OrekitMessages.CCSDS_UNEXPECTED_KEYWORD, pi.lineNumber, pi.fileName, line);
  392.                     }
  393.                 }
  394.             }
  395.             reader.mark(300);

  396.         }
  397.     }

  398.     /**
  399.      * Parse the covariance data lines, create a set of CovarianceMatrix objects
  400.      * and add them in the covarianceMatrices list of the ephemerides block.
  401.      *
  402.      * @param reader the reader
  403.      * @param pi the parser info
  404.      * @throws IOException if an error occurs while reading from the stream
  405.      */
  406.     private void parseCovarianceDataLines(final BufferedReader reader, final ParseInfo pi)
  407.         throws IOException {
  408.         int i = 0;
  409.         for (String line = reader.readLine(); line != null; line = reader.readLine()) {

  410.             ++pi.lineNumber;
  411.             if (line.trim().length() == 0) {
  412.                 continue;
  413.             }
  414.             pi.keyValue = new KeyValue(line, pi.lineNumber, pi.fileName);
  415.             if (pi.keyValue.getKeyword() == null) {
  416.                 try (Scanner sc = new Scanner(line)) {
  417.                     for (int j = 0; j < i + 1; j++) {
  418.                         pi.lastMatrix.addToEntry(i, j, Double.parseDouble(sc.next()));
  419.                         if (j != i) {
  420.                             pi.lastMatrix.addToEntry(j, i, pi.lastMatrix.getEntry(i, j));
  421.                         }
  422.                     }
  423.                     if (i == 5) {
  424.                         final OEMFile.CovarianceMatrix cm =
  425.                                 new OEMFile.CovarianceMatrix(pi.epoch, pi.covRefLofType, pi.covRefFrame, pi.lastMatrix);
  426.                         pi.lastEphemeridesBlock.getCovarianceMatrices().add(cm);
  427.                     }
  428.                     i++;
  429.                 } catch (NumberFormatException nfe) {
  430.                     throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  431.                                               pi.lineNumber, pi.fileName, line);
  432.                 }
  433.             } else {
  434.                 switch (pi.keyValue.getKeyword()) {
  435.                     case EPOCH :
  436.                         i                = 0;
  437.                         pi.covRefLofType = null;
  438.                         pi.covRefFrame   = null;
  439.                         pi.lastMatrix    = MatrixUtils.createRealMatrix(6, 6);
  440.                         pi.epoch         = parseDate(pi.keyValue.getValue(), pi.lastEphemeridesBlock.getMetaData().getTimeSystem());
  441.                         break;
  442.                     case COV_REF_FRAME :
  443.                         final CCSDSFrame frame = parseCCSDSFrame(pi.keyValue.getValue());
  444.                         if (frame.isLof()) {
  445.                             pi.covRefLofType = frame.getLofType();
  446.                             pi.covRefFrame   = null;
  447.                         } else {
  448.                             pi.covRefLofType = null;
  449.                             pi.covRefFrame   = frame.getFrame(getConventions(), isSimpleEOP(), getDataContext());
  450.                         }
  451.                         break;
  452.                     case COVARIANCE_STOP :
  453.                         return;
  454.                     default :
  455.                         throw new OrekitException(OrekitMessages.CCSDS_UNEXPECTED_KEYWORD, pi.lineNumber, pi.fileName, line);
  456.                 }
  457.             }
  458.         }
  459.     }

  460.     /** Private class used to stock OEM parsing info.
  461.      * @author sports
  462.      */
  463.     private static class ParseInfo {

  464.         /** Ephemerides block being parsed. */
  465.         private OEMFile.EphemeridesBlock lastEphemeridesBlock;

  466.         /** Name of the file. */
  467.         private String fileName;

  468.         /** Current line number. */
  469.         private int lineNumber;

  470.         /** OEM file being read. */
  471.         private OEMFile file;

  472.         /** Key value of the line being read. */
  473.         private KeyValue keyValue;

  474.         /** Stored epoch. */
  475.         private AbsoluteDate epoch;

  476.         /** Covariance reference type of Local Orbital Frame. */
  477.         private LOFType covRefLofType;

  478.         /** Covariance reference frame. */
  479.         private Frame covRefFrame;
  480.         /** Stored matrix. */
  481.         private RealMatrix lastMatrix;

  482.         /** Stored comments. */
  483.         private List<String> commentTmp;

  484.         /** Create a new {@link ParseInfo} object. */
  485.         protected ParseInfo() {
  486.             lineNumber = 0;
  487.             file = new OEMFile();
  488.             commentTmp = new ArrayList<String>();
  489.         }
  490.     }
  491. }