ODMParser.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.FileInputStream;
  19. import java.io.IOException;
  20. import java.io.InputStream;
  21. import java.util.List;
  22. import java.util.regex.Matcher;
  23. import java.util.regex.Pattern;

  24. import org.hipparchus.util.FastMath;
  25. import org.orekit.annotation.DefaultDataContext;
  26. import org.orekit.bodies.CelestialBodies;
  27. import org.orekit.data.DataContext;
  28. import org.orekit.errors.OrekitException;
  29. import org.orekit.errors.OrekitMessages;
  30. import org.orekit.time.AbsoluteDate;
  31. import org.orekit.utils.IERSConventions;

  32. /**
  33.  * Base class for all CCSDS Orbit Data Message parsers.
  34.  *
  35.  * <p> This base class is immutable, and hence thread safe. When parts must be
  36.  * changed, such as reference date for Mission Elapsed Time or Mission Relative
  37.  * Time time systems, or the gravitational coefficient or the IERS conventions,
  38.  * the various {@code withXxx} methods must be called, which create a new
  39.  * immutable instance with the new parameters. This is a combination of the <a
  40.  * href="https://en.wikipedia.org/wiki/Builder_pattern">builder design
  41.  * pattern</a> and a <a href="http://en.wikipedia.org/wiki/Fluent_interface">fluent
  42.  * interface</a>.
  43.  *
  44.  * @author Luc Maisonobe
  45.  * @since 6.1
  46.  */
  47. public abstract class ODMParser {

  48.     /** Pattern for international designator. */
  49.     private static final Pattern INTERNATIONAL_DESIGNATOR = Pattern.compile("(\\p{Digit}{4})-(\\p{Digit}{3})(\\p{Upper}{1,3})");

  50.     /** Pattern for dash. */
  51.     private static final Pattern DASH = Pattern.compile("-");

  52.     /** Reference date for Mission Elapsed Time or Mission Relative Time time systems. */
  53.     private final AbsoluteDate missionReferenceDate;

  54.     /** Gravitational coefficient. */
  55.     private final  double mu;

  56.     /** IERS Conventions. */
  57.     private final  IERSConventions conventions;

  58.     /** Indicator for simple or accurate EOP interpolation. */
  59.     private final  boolean simpleEOP;

  60.     /** Data context used for obtain frames and time scales. */
  61.     private final DataContext dataContext;

  62.     /** Launch Year. */
  63.     private int launchYear;

  64.     /** Launch number. */
  65.     private int launchNumber;

  66.     /** Piece of launch (from "A" to "ZZZ"). */
  67.     private String launchPiece;

  68.     /** Complete constructor.
  69.      *
  70.      * <p>This method uses the {@link DataContext#getDefault() default data context}.
  71.      *
  72.      * @param missionReferenceDate reference date for Mission Elapsed Time or Mission Relative Time time systems
  73.      * @param mu gravitational coefficient
  74.      * @param conventions IERS Conventions
  75.      * @param simpleEOP if true, tidal effects are ignored when interpolating EOP
  76.      * @param launchYear launch year for TLEs
  77.      * @param launchNumber launch number for TLEs
  78.      * @param launchPiece piece of launch (from "A" to "ZZZ") for TLEs
  79.      * @see #ODMParser(AbsoluteDate, double, IERSConventions, boolean, int, int, String, DataContext)
  80.      * @deprecated use {@link #ODMParser(AbsoluteDate, double, IERSConventions, boolean,
  81.      * int, int, String, DataContext)} instead.
  82.      */
  83.     @Deprecated
  84.     @DefaultDataContext
  85.     protected ODMParser(final AbsoluteDate missionReferenceDate, final double mu,
  86.                         final IERSConventions conventions, final boolean simpleEOP,
  87.                         final int launchYear, final int launchNumber, final String launchPiece) {
  88.         this(missionReferenceDate, mu, conventions, simpleEOP, launchYear, launchNumber,
  89.                 launchPiece, DataContext.getDefault());
  90.     }

  91.     /** Complete constructor.
  92.      * @param missionReferenceDate reference date for Mission Elapsed Time or Mission Relative Time time systems
  93.      * @param mu gravitational coefficient
  94.      * @param conventions IERS Conventions
  95.      * @param simpleEOP if true, tidal effects are ignored when interpolating EOP
  96.      * @param launchYear launch year for TLEs
  97.      * @param launchNumber launch number for TLEs
  98.      * @param launchPiece piece of launch (from "A" to "ZZZ") for TLEs
  99.      * @param dataContext used to retrieve frames and time scales.
  100.      * @since 10.1
  101.      */
  102.     protected ODMParser(final AbsoluteDate missionReferenceDate, final double mu,
  103.                         final IERSConventions conventions, final boolean simpleEOP,
  104.                         final int launchYear, final int launchNumber,
  105.                         final String launchPiece,
  106.                         final DataContext dataContext) {
  107.         this.missionReferenceDate = missionReferenceDate;
  108.         this.mu                   = mu;
  109.         this.conventions          = conventions;
  110.         this.simpleEOP            = simpleEOP;
  111.         this.launchYear           = launchYear;
  112.         this.launchNumber         = launchNumber;
  113.         this.launchPiece          = launchPiece;
  114.         this.dataContext          = dataContext;
  115.     }

  116.     /** Set initial date.
  117.      * @param newMissionReferenceDate mission reference date to use while parsing
  118.      * @return a new instance, with mission reference date replaced
  119.      * @see #getMissionReferenceDate()
  120.      */
  121.     public abstract ODMParser withMissionReferenceDate(AbsoluteDate newMissionReferenceDate);

  122.     /** Get initial date.
  123.      * @return mission reference date to use while parsing
  124.      * @see #withMissionReferenceDate(AbsoluteDate)
  125.      */
  126.     public AbsoluteDate getMissionReferenceDate() {
  127.         return missionReferenceDate;
  128.     }

  129.     /** Set gravitational coefficient.
  130.      * @param newMu gravitational coefficient to use while parsing
  131.      * @return a new instance, with gravitational coefficient value replaced
  132.      * @see #getMu()
  133.      */
  134.     public abstract ODMParser withMu(double newMu);

  135.     /** Get gravitational coefficient.
  136.      * @return gravitational coefficient to use while parsing
  137.      * @see #withMu(double)
  138.      */
  139.     public double getMu() {
  140.         return mu;
  141.     }

  142.     /** Set IERS conventions.
  143.      * @param newConventions IERS conventions to use while parsing
  144.      * @return a new instance, with IERS conventions replaced
  145.      * @see #getConventions()
  146.      */
  147.     public abstract ODMParser withConventions(IERSConventions newConventions);

  148.     /** Get IERS conventions.
  149.      * @return IERS conventions to use while parsing
  150.      * @see #withConventions(IERSConventions)
  151.      */
  152.     public IERSConventions getConventions() {
  153.         return conventions;
  154.     }

  155.     /** Set EOP interpolation method.
  156.      * @param newSimpleEOP if true, tidal effects are ignored when interpolating EOP
  157.      * @return a new instance, with EOP interpolation method replaced
  158.      * @see #isSimpleEOP()
  159.      */
  160.     public abstract ODMParser withSimpleEOP(boolean newSimpleEOP);

  161.     /** Get EOP interpolation method.
  162.      * @return true if tidal effects are ignored when interpolating EOP
  163.      * @see #withSimpleEOP(boolean)
  164.      */
  165.     public boolean isSimpleEOP() {
  166.         return simpleEOP;
  167.     }

  168.     /** Set international designator.
  169.      * <p>
  170.      * This method may be used to ensure the launch year number and pieces are
  171.      * correctly set if they are not present in the CCSDS file header in the
  172.      * OBJECT_ID in the form YYYY-NNNP{PP}. If they are already in the header,
  173.      * they will be parsed automatically regardless of this method being called
  174.      * or not (i.e. header information override information set here).
  175.      * </p>
  176.      * @param newLaunchYear launch year
  177.      * @param newLaunchNumber launch number
  178.      * @param newLaunchPiece piece of launch (from "A" to "ZZZ")
  179.      * @return a new instance, with TLE settings replaced
  180.      */
  181.     public abstract ODMParser withInternationalDesignator(int newLaunchYear,
  182.                                                           int newLaunchNumber,
  183.                                                           String newLaunchPiece);

  184.     /** Get the launch year.
  185.      * @return launch year
  186.      */
  187.     public int getLaunchYear() {
  188.         return launchYear;
  189.     }

  190.     /** Get the launch number.
  191.      * @return launch number
  192.      */
  193.     public int getLaunchNumber() {
  194.         return launchNumber;
  195.     }

  196.     /** Get the piece of launch.
  197.      * @return piece of launch
  198.      */
  199.     public String getLaunchPiece() {
  200.         return launchPiece;
  201.     }

  202.     /**
  203.      * Get the data context used for getting frames, time scales, and celestial bodies.
  204.      *
  205.      * @return the data context.
  206.      */
  207.     public DataContext getDataContext() {
  208.         return dataContext;
  209.     }

  210.     /**
  211.      * Set the data context.
  212.      *
  213.      * @param newDataContext used for frames, time scales, and celestial bodies.
  214.      * @return a new instance with the data context replaced.
  215.      */
  216.     public abstract ODMParser withDataContext(DataContext newDataContext);

  217.     /** Parse a CCSDS Orbit Data Message.
  218.      * @param fileName name of the file containing the message
  219.      * @return parsed orbit
  220.      */
  221.     public ODMFile parse(final String fileName) {
  222.         try (InputStream stream = new FileInputStream(fileName)) {
  223.             return parse(stream, fileName);
  224.         } catch (IOException e) {
  225.             throw new OrekitException(OrekitMessages.UNABLE_TO_FIND_FILE, fileName);
  226.         }
  227.     }

  228.     /** Parse a CCSDS Orbit Data Message.
  229.      * @param stream stream containing message
  230.      * @return parsed orbit
  231.      */
  232.     public ODMFile parse(final InputStream stream) {
  233.         return parse(stream, "<unknown>");
  234.     }

  235.     /** Parse a CCSDS Orbit Data Message.
  236.      * @param stream stream containing message
  237.      * @param fileName name of the file containing the message (for error messages)
  238.      * @return parsed orbit
  239.      */
  240.     public abstract ODMFile parse(InputStream stream, String fileName);

  241.     /** Parse a comment line.
  242.      * @param keyValue key=value pair containing the comment
  243.      * @param comment placeholder where the current comment line should be added
  244.      * @return true if the line was a comment line and was parsed
  245.      */
  246.     protected boolean parseComment(final KeyValue keyValue, final List<String> comment) {
  247.         if (keyValue.getKeyword() == Keyword.COMMENT) {
  248.             comment.add(keyValue.getValue());
  249.             return true;
  250.         } else {
  251.             return false;
  252.         }
  253.     }

  254.     /** Parse an entry from the header.
  255.      * @param keyValue key = value pair
  256.      * @param odmFile instance to update with parsed entry
  257.      * @param comment previous comment lines, will be emptied if used by the keyword
  258.      * @return true if the keyword was a header keyword and has been parsed
  259.      */
  260.     protected boolean parseHeaderEntry(final KeyValue keyValue,
  261.                                        final ODMFile odmFile, final List<String> comment) {
  262.         switch (keyValue.getKeyword()) {

  263.             case CREATION_DATE:
  264.                 if (!comment.isEmpty()) {
  265.                     odmFile.setHeaderComment(comment);
  266.                     comment.clear();
  267.                 }
  268.                 odmFile.setCreationDate(new AbsoluteDate(
  269.                         keyValue.getValue(),
  270.                         dataContext.getTimeScales().getUTC()));
  271.                 return true;

  272.             case ORIGINATOR:
  273.                 odmFile.setOriginator(keyValue.getValue());
  274.                 return true;

  275.             default:
  276.                 return false;

  277.         }

  278.     }

  279.     /** Parse a meta-data key = value entry.
  280.      * @param keyValue key = value pair
  281.      * @param metaData instance to update with parsed entry
  282.      * @param comment previous comment lines, will be emptied if used by the keyword
  283.      * @return true if the keyword was a meta-data keyword and has been parsed
  284.      */
  285.     protected boolean parseMetaDataEntry(final KeyValue keyValue,
  286.                                          final ODMMetaData metaData, final List<String> comment) {
  287.         switch (keyValue.getKeyword()) {
  288.             case OBJECT_NAME:
  289.                 if (!comment.isEmpty()) {
  290.                     metaData.setComment(comment);
  291.                     comment.clear();
  292.                 }
  293.                 metaData.setObjectName(keyValue.getValue());
  294.                 return true;

  295.             case OBJECT_ID: {
  296.                 metaData.setObjectID(keyValue.getValue());
  297.                 final Matcher matcher = INTERNATIONAL_DESIGNATOR.matcher(keyValue.getValue());
  298.                 if (matcher.matches()) {
  299.                     metaData.setLaunchYear(Integer.parseInt(matcher.group(1)));
  300.                     metaData.setLaunchNumber(Integer.parseInt(matcher.group(2)));
  301.                     metaData.setLaunchPiece(matcher.group(3));
  302.                 }
  303.                 return true;
  304.             }

  305.             case CENTER_NAME:
  306.                 metaData.setCenterName(keyValue.getValue());
  307.                 final String canonicalValue;
  308.                 if (keyValue.getValue().equals("SOLAR SYSTEM BARYCENTER") || keyValue.getValue().equals("SSB")) {
  309.                     canonicalValue = "SOLAR_SYSTEM_BARYCENTER";
  310.                 } else if (keyValue.getValue().equals("EARTH MOON BARYCENTER") || keyValue.getValue().equals("EARTH-MOON BARYCENTER") ||
  311.                         keyValue.getValue().equals("EARTH BARYCENTER") || keyValue.getValue().equals("EMB")) {
  312.                     canonicalValue = "EARTH_MOON";
  313.                 } else {
  314.                     canonicalValue = keyValue.getValue();
  315.                 }
  316.                 for (final CenterName c : CenterName.values()) {
  317.                     if (c.name().equals(canonicalValue)) {
  318.                         metaData.setHasCreatableBody(true);
  319.                         final CelestialBodies celestialBodies =
  320.                                 getDataContext().getCelestialBodies();
  321.                         metaData.setCenterBody(c.getCelestialBody(celestialBodies));
  322.                         metaData.getODMFile().setMuCreated(
  323.                                 c.getCelestialBody(celestialBodies).getGM());
  324.                     }
  325.                 }
  326.                 return true;

  327.             case REF_FRAME:
  328.                 metaData.setFrameString(keyValue.getValue());
  329.                 metaData.setRefFrame(parseCCSDSFrame(keyValue.getValue())
  330.                         .getFrame(getConventions(), isSimpleEOP(), getDataContext()));
  331.                 return true;

  332.             case REF_FRAME_EPOCH:
  333.                 metaData.setFrameEpochString(keyValue.getValue());
  334.                 return true;

  335.             case TIME_SYSTEM:
  336.                 if (!CcsdsTimeScale.contains(keyValue.getValue())) {
  337.                     throw new OrekitException(
  338.                             OrekitMessages.CCSDS_TIME_SYSTEM_NOT_IMPLEMENTED,
  339.                             keyValue.getValue());
  340.                 }
  341.                 final CcsdsTimeScale timeSystem =
  342.                         CcsdsTimeScale.valueOf(keyValue.getValue());
  343.                 metaData.setTimeSystem(timeSystem);
  344.                 if (metaData.getFrameEpochString() != null) {
  345.                     metaData.setFrameEpoch(parseDate(metaData.getFrameEpochString(), timeSystem));
  346.                 }
  347.                 return true;

  348.             default:
  349.                 return false;
  350.         }
  351.     }

  352.     /** Parse a general state data key = value entry.
  353.      * @param keyValue key = value pair
  354.      * @param general instance to update with parsed entry
  355.      * @param comment previous comment lines, will be emptied if used by the keyword
  356.      * @return true if the keyword was a meta-data keyword and has been parsed
  357.      */
  358.     protected boolean parseGeneralStateDataEntry(final KeyValue keyValue,
  359.                                                  final OGMFile general, final List<String> comment) {
  360.         switch (keyValue.getKeyword()) {

  361.             case EPOCH:
  362.                 general.setEpochComment(comment);
  363.                 comment.clear();
  364.                 general.setEpoch(parseDate(keyValue.getValue(), general.getMetaData().getTimeSystem()));
  365.                 return true;

  366.             case SEMI_MAJOR_AXIS:
  367.                 general.setKeplerianElementsComment(comment);
  368.                 comment.clear();
  369.                 general.setA(keyValue.getDoubleValue() * 1000);
  370.                 general.setHasKeplerianElements(true);
  371.                 return true;

  372.             case ECCENTRICITY:
  373.                 general.setE(keyValue.getDoubleValue());
  374.                 return true;

  375.             case INCLINATION:
  376.                 general.setI(FastMath.toRadians(keyValue.getDoubleValue()));
  377.                 return true;

  378.             case RA_OF_ASC_NODE:
  379.                 general.setRaan(FastMath.toRadians(keyValue.getDoubleValue()));
  380.                 return true;

  381.             case ARG_OF_PERICENTER:
  382.                 general.setPa(FastMath.toRadians(keyValue.getDoubleValue()));
  383.                 return true;

  384.             case TRUE_ANOMALY:
  385.                 general.setAnomalyType("TRUE");
  386.                 general.setAnomaly(FastMath.toRadians(keyValue.getDoubleValue()));
  387.                 return true;

  388.             case MEAN_ANOMALY:
  389.                 general.setAnomalyType("MEAN");
  390.                 general.setAnomaly(FastMath.toRadians(keyValue.getDoubleValue()));
  391.                 return true;

  392.             case GM:
  393.                 general.setMuParsed(keyValue.getDoubleValue() * 1e9);
  394.                 return true;

  395.             case MASS:
  396.                 comment.addAll(0, general.getSpacecraftComment());
  397.                 general.setSpacecraftComment(comment);
  398.                 comment.clear();
  399.                 general.setMass(keyValue.getDoubleValue());
  400.                 return true;

  401.             case SOLAR_RAD_AREA:
  402.                 comment.addAll(0, general.getSpacecraftComment());
  403.                 general.setSpacecraftComment(comment);
  404.                 comment.clear();
  405.                 general.setSolarRadArea(keyValue.getDoubleValue());
  406.                 return true;

  407.             case SOLAR_RAD_COEFF:
  408.                 comment.addAll(0, general.getSpacecraftComment());
  409.                 general.setSpacecraftComment(comment);
  410.                 comment.clear();
  411.                 general.setSolarRadCoeff(keyValue.getDoubleValue());
  412.                 return true;

  413.             case DRAG_AREA:
  414.                 comment.addAll(0, general.getSpacecraftComment());
  415.                 general.setSpacecraftComment(comment);
  416.                 comment.clear();
  417.                 general.setDragArea(keyValue.getDoubleValue());
  418.                 return true;

  419.             case DRAG_COEFF:
  420.                 comment.addAll(0, general.getSpacecraftComment());
  421.                 general.setSpacecraftComment(comment);
  422.                 comment.clear();
  423.                 general.setDragCoeff(keyValue.getDoubleValue());
  424.                 return true;

  425.             case COV_REF_FRAME:
  426.                 general.setCovarianceComment(comment);
  427.                 comment.clear();
  428.                 final CCSDSFrame covFrame = parseCCSDSFrame(keyValue.getValue());
  429.                 if (covFrame.isLof()) {
  430.                     general.setCovRefLofType(covFrame.getLofType());
  431.                 } else {
  432.                     general.setCovRefFrame(covFrame
  433.                             .getFrame(getConventions(), isSimpleEOP(), getDataContext()));
  434.                 }
  435.                 return true;

  436.             case CX_X:
  437.                 general.createCovarianceMatrix();
  438.                 general.setCovarianceMatrixEntry(0, 0, keyValue.getDoubleValue() * 1.0e6);
  439.                 return true;

  440.             case CY_X:
  441.                 general.setCovarianceMatrixEntry(0, 1, keyValue.getDoubleValue() * 1.0e6);
  442.                 return true;

  443.             case CY_Y:
  444.                 general.setCovarianceMatrixEntry(1, 1, keyValue.getDoubleValue() * 1.0e6);
  445.                 return true;

  446.             case CZ_X:
  447.                 general.setCovarianceMatrixEntry(0, 2, keyValue.getDoubleValue() * 1.0e6);
  448.                 return true;

  449.             case CZ_Y:
  450.                 general.setCovarianceMatrixEntry(1, 2, keyValue.getDoubleValue() * 1.0e6);
  451.                 return true;

  452.             case CZ_Z:
  453.                 general.setCovarianceMatrixEntry(2, 2, keyValue.getDoubleValue() * 1.0e6);
  454.                 return true;

  455.             case CX_DOT_X:
  456.                 general.setCovarianceMatrixEntry(0, 3, keyValue.getDoubleValue() * 1.0e6);
  457.                 return true;

  458.             case CX_DOT_Y:
  459.                 general.setCovarianceMatrixEntry(1, 3, keyValue.getDoubleValue() * 1.0e6);
  460.                 return true;

  461.             case CX_DOT_Z:
  462.                 general.setCovarianceMatrixEntry(2, 3, keyValue.getDoubleValue() * 1.0e6);
  463.                 return true;

  464.             case CX_DOT_X_DOT:
  465.                 general.setCovarianceMatrixEntry(3, 3, keyValue.getDoubleValue() * 1.0e6);
  466.                 return true;

  467.             case CY_DOT_X:
  468.                 general.setCovarianceMatrixEntry(0, 4, keyValue.getDoubleValue() * 1.0e6);
  469.                 return true;

  470.             case CY_DOT_Y:
  471.                 general.setCovarianceMatrixEntry(1, 4, keyValue.getDoubleValue() * 1.0e6);
  472.                 return true;

  473.             case CY_DOT_Z:
  474.                 general.setCovarianceMatrixEntry(2, 4, keyValue.getDoubleValue() * 1.0e6);
  475.                 return true;

  476.             case CY_DOT_X_DOT:
  477.                 general.setCovarianceMatrixEntry(3, 4, keyValue.getDoubleValue() * 1.0e6);
  478.                 return true;

  479.             case CY_DOT_Y_DOT:
  480.                 general.setCovarianceMatrixEntry(4, 4, keyValue.getDoubleValue() * 1.0e6);
  481.                 return true;

  482.             case CZ_DOT_X:
  483.                 general.setCovarianceMatrixEntry(0, 5, keyValue.getDoubleValue() * 1.0e6);
  484.                 return true;

  485.             case CZ_DOT_Y:
  486.                 general.setCovarianceMatrixEntry(1, 5, keyValue.getDoubleValue() * 1.0e6);
  487.                 return true;

  488.             case CZ_DOT_Z:
  489.                 general.setCovarianceMatrixEntry(2, 5, keyValue.getDoubleValue() * 1.0e6);
  490.                 return true;

  491.             case CZ_DOT_X_DOT:
  492.                 general.setCovarianceMatrixEntry(3, 5, keyValue.getDoubleValue() * 1.0e6);
  493.                 return true;

  494.             case CZ_DOT_Y_DOT:
  495.                 general.setCovarianceMatrixEntry(4, 5, keyValue.getDoubleValue() * 1.0e6);
  496.                 return true;

  497.             case CZ_DOT_Z_DOT:
  498.                 general.setCovarianceMatrixEntry(5, 5, keyValue.getDoubleValue() * 1.0e6);
  499.                 return true;

  500.             case USER_DEFINED_X:
  501.                 general.setUserDefinedParameters(keyValue.getKey(), keyValue.getValue());
  502.                 return true;

  503.             default:
  504.                 return false;
  505.         }
  506.     }

  507.     /** Parse a CCSDS frame.
  508.      * @param frameName name of the frame, as the value of a CCSDS key=value line
  509.      * @return CCSDS frame corresponding to the name
  510.      */
  511.     protected CCSDSFrame parseCCSDSFrame(final String frameName) {
  512.         return CCSDSFrame.valueOf(DASH.matcher(frameName).replaceAll(""));
  513.     }

  514.     /** Parse a date.
  515.      * @param date date to parse, as the value of a CCSDS key=value line
  516.      * @param timeSystem time system to use
  517.      * @return parsed date
  518.      */
  519.     protected AbsoluteDate parseDate(final String date, final CcsdsTimeScale timeSystem) {
  520.         return timeSystem.parseDate(date, conventions, missionReferenceDate,
  521.                 getDataContext().getTimeScales());
  522.     }

  523. }