OemWriter.java

  1. /* Copyright 2016 Applied Defense Solutions (ADS)
  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.  * ADS 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.ndm.odm.oem;

  18. import java.io.IOException;
  19. import java.util.Date;
  20. import java.util.List;

  21. import org.hipparchus.linear.RealMatrix;
  22. import org.orekit.data.DataContext;
  23. import org.orekit.errors.OrekitException;
  24. import org.orekit.errors.OrekitMessages;
  25. import org.orekit.files.ccsds.definitions.TimeSystem;
  26. import org.orekit.files.ccsds.definitions.Units;
  27. import org.orekit.files.ccsds.ndm.ParsedUnitsBehavior;
  28. import org.orekit.files.ccsds.ndm.odm.CartesianCovariance;
  29. import org.orekit.files.ccsds.ndm.odm.CartesianCovarianceKey;
  30. import org.orekit.files.ccsds.ndm.odm.CommonMetadataKey;
  31. import org.orekit.files.ccsds.ndm.odm.OdmHeader;
  32. import org.orekit.files.ccsds.ndm.odm.OdmMetadataKey;
  33. import org.orekit.files.ccsds.ndm.odm.StateVectorKey;
  34. import org.orekit.files.ccsds.section.HeaderKey;
  35. import org.orekit.files.ccsds.section.KvnStructureKey;
  36. import org.orekit.files.ccsds.section.MetadataKey;
  37. import org.orekit.files.ccsds.section.XmlStructureKey;
  38. import org.orekit.files.ccsds.utils.ContextBinding;
  39. import org.orekit.files.ccsds.utils.FileFormat;
  40. import org.orekit.files.ccsds.utils.generation.AbstractMessageWriter;
  41. import org.orekit.files.ccsds.utils.generation.Generator;
  42. import org.orekit.time.AbsoluteDate;
  43. import org.orekit.utils.CartesianDerivativesFilter;
  44. import org.orekit.utils.IERSConventions;
  45. import org.orekit.utils.TimeStampedPVCoordinates;
  46. import org.orekit.utils.units.Unit;

  47. /**
  48.  * A writer for Orbit Ephemeris Message (OEM) files.
  49.  *
  50.  * <h2> Metadata </h2>
  51.  *
  52.  * <p> The OEM metadata used by this writer is described in the following table. Many
  53.  * metadata items are optional or have default values so they do not need to be specified.
  54.  * At a minimum the user must supply those values that are required and for which no
  55.  * default exits: {@link OdmMetadataKey#OBJECT_NAME}, and {@link CommonMetadataKey#OBJECT_ID}. The usage
  56.  * column in the table indicates where the metadata item is used, either in the OEM header
  57.  * or in the metadata section at the start of an OEM ephemeris segment.
  58.  *
  59.  * <table>
  60.  * <caption>OEM metadata</caption>
  61.  *     <thead>
  62.  *         <tr>
  63.  *             <th>Keyword</th>
  64.  *             <th>Usage</th>
  65.  *             <th>Obligatory</th>
  66.  *             <th>Default</th>
  67.  *             <th>Reference</th>
  68.  *    </thead>
  69.  *    <tbody>
  70.  *        <tr>
  71.  *            <td>{@code CCSDS_OEM_VERS}</td>
  72.  *            <td>Header</td>
  73.  *            <td>Yes</td>
  74.  *            <td>{@link Oem#FORMAT_VERSION_KEY}</td>
  75.  *            <td>Table 5-2</td>
  76.  *        <tr>
  77.  *            <td>{@code COMMENT}</td>
  78.  *            <td>Header</td>
  79.  *            <td>No</td>
  80.  *            <td></td>
  81.  *            <td>Table 5-2</td>
  82.  *        <tr>
  83.  *            <td>{@link HeaderKey#CREATION_DATE}</td>
  84.  *            <td>Header</td>
  85.  *            <td>Yes</td>
  86.  *            <td>{@link Date#Date() Now}</td>
  87.  *            <td>Table 5.2, 6.5.9</td>
  88.  *        <tr>
  89.  *            <td>{@link HeaderKey#ORIGINATOR}</td>
  90.  *            <td>Header</td>
  91.  *            <td>Yes</td>
  92.  *            <td>{@link #DEFAULT_ORIGINATOR}</td>
  93.  *            <td>Table 5-2</td>
  94.  *        <tr>
  95.  *            <td>{@link OdmMetadataKey#OBJECT_NAME}</td>
  96.  *            <td>Segment</td>
  97.  *            <td>Yes</td>
  98.  *            <td></td>
  99.  *            <td>Table 5-3</td>
  100.  *        <tr>
  101.  *            <td>{@link CommonMetadataKey#OBJECT_ID}</td>
  102.  *            <td>Segment</td>
  103.  *            <td>Yes</td>
  104.  *            <td></td>
  105.  *            <td>Table 5-3</td>
  106.  *        <tr>
  107.  *            <td>{@link CommonMetadataKey#CENTER_NAME}</td>
  108.  *            <td>Segment</td>
  109.  *            <td>Yes</td>
  110.  *            <td></td>
  111.  *            <td>Table 5-3</td>
  112.  *        <tr>
  113.  *            <td>{@link CommonMetadataKey#REF_FRAME}</td>
  114.  *            <td>Segment</td>
  115.  *            <td>Yes</td>
  116.  *            <td></td>
  117.  *            <td>Table 5-3, Annex A</td>
  118.  *        <tr>
  119.  *            <td>{@link CommonMetadataKey#REF_FRAME_EPOCH}</td>
  120.  *            <td>Segment</td>
  121.  *            <td>No</td>
  122.  *            <td></td>
  123.  *            <td>Table 5-3, 6.5.9</td>
  124.  *        <tr>
  125.  *            <td>{@link MetadataKey#TIME_SYSTEM}</td>
  126.  *            <td>Segment</td>
  127.  *            <td>Yes</td>
  128.  *            <td></td>
  129.  *        <tr>
  130.  *            <td>{@link OemMetadataKey#START_TIME}</td>
  131.  *            <td>Segment</td>
  132.  *            <td>Yes</td>
  133.  *            <td></td>
  134.  *            <td>Table 5-3, 6.5.9</td>
  135.  *        <tr>
  136.  *            <td>{@link OemMetadataKey#USEABLE_START_TIME}</td>
  137.  *            <td>Segment</td>
  138.  *            <td>No</td>
  139.  *            <td></td>
  140.  *            <td>Table 5-3, 6.5.9</td>
  141.  *        <tr>
  142.  *            <td>{@link OemMetadataKey#STOP_TIME}</td>
  143.  *            <td>Segment</td>
  144.  *            <td>Yes</td>
  145.  *            <td></td>
  146.  *            <td>Table 5-3, 6.5.9</td>
  147.  *        <tr>
  148.  *            <td>{@link OemMetadataKey#USEABLE_STOP_TIME}</td>
  149.  *            <td>Segment</td>
  150.  *            <td>No</td>
  151.  *            <td></td>
  152.  *            <td>Table 5-3, 6.5.9</td>
  153.  *        <tr>
  154.  *            <td>{@link OemMetadataKey#INTERPOLATION}</td>
  155.  *            <td>Segment</td>
  156.  *            <td>No</td>
  157.  *            <td></td>
  158.  *            <td>Table 5-3</td>
  159.  *        <tr>
  160.  *            <td>{@link OemMetadataKey#INTERPOLATION_DEGREE}</td>
  161.  *            <td>Segment</td>
  162.  *            <td>No</td>
  163.  *            <td></td>
  164.  *            <td>Table 5-3</td>
  165.  *    </tbody>
  166.  *</table>
  167.  *
  168.  * <p> The {@link MetadataKey#TIME_SYSTEM} must be constant for the whole file and is used
  169.  * to interpret all dates except {@link HeaderKey#CREATION_DATE} which is always in {@link
  170.  * TimeSystem#UTC UTC}. The guessing algorithm is not guaranteed to work so it is recommended
  171.  * to provide values for {@link CommonMetadataKey#CENTER_NAME} and {@link MetadataKey#TIME_SYSTEM}
  172.  * to avoid any bugs associated with incorrect guesses.
  173.  *
  174.  * <p> Standardized values for {@link MetadataKey#TIME_SYSTEM} are GMST, GPS, MET, MRT, SCLK,
  175.  * TAI, TCB, TDB, TT, UT1, and UTC. Standardized values for reference frames
  176.  * are EME2000, GTOD, ICRF, ITRF2000, ITRF-93, ITRF-97, LVLH, RTN, QSW, TOD, TNW, NTW and RSW.
  177.  * Additionally ITRF followed by a four digit year may be used.
  178.  *
  179.  * @author Hank Grabowski
  180.  * @author Evan Ward
  181.  * @since 9.0
  182.  * @see <a href="https://public.ccsds.org/Pubs/502x0b2c1.pdf">CCSDS 502.0-B-2 Orbit Data
  183.  *      Messages</a>
  184.  * @see <a href="https://public.ccsds.org/Pubs/500x0g4.pdf">CCSDS 500.0-G-4 Navigation
  185.  *      Data Definitions and Conventions</a>
  186.  * @see StreamingOemWriter
  187.  */
  188. public class OemWriter extends AbstractMessageWriter<OdmHeader, OemSegment, Oem> {

  189.     /** Version number implemented. **/
  190.     public static final double CCSDS_OEM_VERS = 3.0;

  191.     /** Default file name for error messages. */
  192.     public static final String DEFAULT_FILE_NAME = "<OEM output>";

  193.     /** Padding width for aligning the '=' sign. */
  194.     public static final int KVN_PADDING_WIDTH = 20;

  195.     /**
  196.      * Constructor used to create a new OEM writer configured with the necessary parameters
  197.      * to successfully fill in all required fields that aren't part of a standard object.
  198.      * <p>
  199.      * If the mandatory header entries are not present (or if header is null),
  200.      * built-in defaults will be used
  201.      * </p>
  202.      * <p>
  203.      * The writer is built from the complete header and partial metadata. The template
  204.      * metadata is used to initialize and independent local copy, that will be updated
  205.      * as new segments are written (with at least the segment start and stop will change,
  206.      * but some other parts may change too). The {@code template} argument itself is not
  207.      * changed.
  208.      * </p>
  209.      * <p>
  210.      * Calling this constructor directly is not recommended. Users should rather use
  211.      * {@link org.orekit.files.ccsds.ndm.WriterBuilder#buildOemWriter()
  212.      * writerBuilder.buildOemWriter()}.
  213.      * </p>
  214.      * @param conventions IERS Conventions
  215.      * @param dataContext used to retrieve frames, time scales, etc.
  216.      * @param missionReferenceDate reference date for Mission Elapsed Time or Mission Relative Time time systems
  217.      * @since 11.0
  218.      * @see #DEFAULT_FILE_NAME
  219.      */
  220.     public OemWriter(final IERSConventions conventions, final DataContext dataContext,
  221.                      final AbsoluteDate missionReferenceDate) {
  222.         super(Oem.ROOT, Oem.FORMAT_VERSION_KEY, CCSDS_OEM_VERS,
  223.               new ContextBinding(
  224.                   () -> conventions, () -> true, () -> dataContext,
  225.                   () -> ParsedUnitsBehavior.STRICT_COMPLIANCE,
  226.                   () -> missionReferenceDate, () -> TimeSystem.UTC, () -> 0.0, () -> 1.0));
  227.     }

  228.     /** {@inheritDoc} */
  229.     @Override
  230.     protected void writeSegmentContent(final Generator generator, final double formatVersion,
  231.                                        final OemSegment segment)
  232.         throws IOException {

  233.         final OemMetadata metadata = segment.getMetadata();
  234.         writeMetadata(generator, metadata);

  235.         startData(generator);

  236.         // write data comments
  237.         generator.writeComments(segment.getData().getComments());

  238.         // Loop on orbit data
  239.         final CartesianDerivativesFilter filter = segment.getAvailableDerivatives();
  240.         if (filter == CartesianDerivativesFilter.USE_P) {
  241.             throw new OrekitException(OrekitMessages.MISSING_VELOCITY);
  242.         }
  243.         final boolean useAcceleration = filter.equals(CartesianDerivativesFilter.USE_PVA);
  244.         for (final TimeStampedPVCoordinates coordinates : segment.getCoordinates()) {
  245.             writeOrbitEphemerisLine(generator, metadata, coordinates, useAcceleration);
  246.         }

  247.         // output covariance data
  248.         writeCovariances(generator, segment.getMetadata(), segment.getData().getCovarianceMatrices());

  249.         endData(generator);

  250.     }

  251.     /** Write an ephemeris segment metadata.
  252.      * @param generator generator to use for producing output
  253.      * @param metadata metadata to write
  254.      * @throws IOException if the output stream throws one while writing.
  255.      */
  256.     void writeMetadata(final Generator generator, final OemMetadata metadata)
  257.         throws IOException {

  258.         // add an empty line for presentation
  259.         generator.newLine();

  260.         final ContextBinding oldContext = getContext();
  261.         setContext(new ContextBinding(oldContext::getConventions,
  262.                                       oldContext::isSimpleEOP,
  263.                                       oldContext::getDataContext,
  264.                                       oldContext::getParsedUnitsBehavior,
  265.                                       oldContext::getReferenceDate,
  266.                                       metadata::getTimeSystem,
  267.                                       oldContext::getClockCount,
  268.                                       oldContext::getClockRate));

  269.         // Start metadata
  270.         generator.enterSection(generator.getFormat() == FileFormat.KVN ?
  271.                                KvnStructureKey.META.name() :
  272.                                XmlStructureKey.metadata.name());

  273.         generator.writeComments(metadata.getComments());

  274.         // objects
  275.         generator.writeEntry(OdmMetadataKey.OBJECT_NAME.name(),    metadata.getObjectName(),       null, true);
  276.         generator.writeEntry(CommonMetadataKey.OBJECT_ID.name(),   metadata.getObjectID(),         null, true);
  277.         generator.writeEntry(CommonMetadataKey.CENTER_NAME.name(), metadata.getCenter().getName(), null, false);

  278.         // frames
  279.         generator.writeEntry(CommonMetadataKey.REF_FRAME.name(), metadata.getReferenceFrame().getName(), null, true);
  280.         if (metadata.getFrameEpoch() != null) {
  281.             generator.writeEntry(CommonMetadataKey.REF_FRAME_EPOCH.name(),
  282.                                  getTimeConverter(), metadata.getFrameEpoch(),
  283.                                  true, false);
  284.         }

  285.         // time
  286.         generator.writeEntry(MetadataKey.TIME_SYSTEM.name(), metadata.getTimeSystem(), true);
  287.         generator.writeEntry(OemMetadataKey.START_TIME.name(), getTimeConverter(), metadata.getStartTime(), false, true);
  288.         if (metadata.getUseableStartTime() != null) {
  289.             generator.writeEntry(OemMetadataKey.USEABLE_START_TIME.name(), getTimeConverter(), metadata.getUseableStartTime(), false, false);
  290.         }
  291.         if (metadata.getUseableStopTime() != null) {
  292.             generator.writeEntry(OemMetadataKey.USEABLE_STOP_TIME.name(), getTimeConverter(), metadata.getUseableStopTime(), false, false);
  293.         }
  294.         generator.writeEntry(OemMetadataKey.STOP_TIME.name(), getTimeConverter(), metadata.getStopTime(), false, true);

  295.         // interpolation
  296.         generator.writeEntry(OemMetadataKey.INTERPOLATION.name(), metadata.getInterpolationMethod(), false);
  297.         // treat degree < 0 as equivalent to null
  298.         if (metadata.getInterpolationDegree() >= 0) {
  299.             generator.writeEntry(OemMetadataKey.INTERPOLATION_DEGREE.name(),
  300.                     Integer.toString(metadata.getInterpolationDegree()),
  301.                     null, false);
  302.         }

  303.         // Stop metadata
  304.         generator.exitSection();

  305.         // add an empty line for presentation
  306.         generator.newLine();

  307.     }

  308.     /**
  309.      * Write a single orbit ephemeris line .
  310.      * @param generator generator to use for producing output
  311.      * @param metadata metadata to use for interpreting data
  312.      * @param coordinates orbit information for a given date
  313.      * @param useAcceleration is true, the acceleration data must be used
  314.      * @throws IOException if the output stream throws one while writing.
  315.      */
  316.     void writeOrbitEphemerisLine(final Generator generator, final OemMetadata metadata,
  317.                                  final TimeStampedPVCoordinates coordinates,
  318.                                  final boolean useAcceleration)
  319.         throws IOException {

  320.         if (generator.getFormat() == FileFormat.KVN) {

  321.             // Epoch
  322.             generator.writeRawData(generator.dateToString(getTimeConverter(), coordinates.getDate()));

  323.             // Position data in km
  324.             generator.writeRawData(' ');
  325.             generator.writeRawData(String.format(generator.doubleToString(Unit.KILOMETRE.fromSI(coordinates.getPosition().getX()))));
  326.             generator.writeRawData(' ');
  327.             generator.writeRawData(String.format(generator.doubleToString(Unit.KILOMETRE.fromSI(coordinates.getPosition().getY()))));
  328.             generator.writeRawData(' ');
  329.             generator.writeRawData(String.format(generator.doubleToString(Unit.KILOMETRE.fromSI(coordinates.getPosition().getZ()))));

  330.             // Velocity data in km/s
  331.             generator.writeRawData(' ');
  332.             generator.writeRawData(String.format(generator.doubleToString(Units.KM_PER_S.fromSI(coordinates.getVelocity().getX()))));
  333.             generator.writeRawData(' ');
  334.             generator.writeRawData(String.format(generator.doubleToString(Units.KM_PER_S.fromSI(coordinates.getVelocity().getY()))));
  335.             generator.writeRawData(' ');
  336.             generator.writeRawData(String.format(generator.doubleToString(Units.KM_PER_S.fromSI(coordinates.getVelocity().getZ()))));

  337.             // Acceleration data in km/s²
  338.             if (useAcceleration) {
  339.                 generator.writeRawData(' ');
  340.                 generator.writeRawData(String.format(generator.doubleToString(Units.KM_PER_S2.fromSI(coordinates.getAcceleration().getX()))));
  341.                 generator.writeRawData(' ');
  342.                 generator.writeRawData(String.format(generator.doubleToString(Units.KM_PER_S2.fromSI(coordinates.getAcceleration().getY()))));
  343.                 generator.writeRawData(' ');
  344.                 generator.writeRawData(String.format(generator.doubleToString(Units.KM_PER_S2.fromSI(coordinates.getAcceleration().getZ()))));
  345.             }

  346.             // end the line
  347.             generator.newLine();
  348.         } else {
  349.             generator.enterSection(OemDataSubStructureKey.stateVector.name());

  350.             // Epoch
  351.             generator.writeEntry(StateVectorKey.EPOCH.name(), getTimeConverter(), coordinates.getDate(), false, true);

  352.             // Position data in km
  353.             generator.writeEntry(StateVectorKey.X.name(), coordinates.getPosition().getX(), Unit.KILOMETRE, true);
  354.             generator.writeEntry(StateVectorKey.Y.name(), coordinates.getPosition().getY(), Unit.KILOMETRE, true);
  355.             generator.writeEntry(StateVectorKey.Z.name(), coordinates.getPosition().getZ(), Unit.KILOMETRE, true);

  356.             // Velocity data in km/s
  357.             generator.writeEntry(StateVectorKey.X_DOT.name(), coordinates.getVelocity().getX(), Units.KM_PER_S, true);
  358.             generator.writeEntry(StateVectorKey.Y_DOT.name(), coordinates.getVelocity().getY(), Units.KM_PER_S, true);
  359.             generator.writeEntry(StateVectorKey.Z_DOT.name(), coordinates.getVelocity().getZ(), Units.KM_PER_S, true);

  360.             // Acceleration data in km/s²
  361.             if (useAcceleration) {
  362.                 generator.writeEntry(StateVectorKey.X_DDOT.name(), coordinates.getAcceleration().getX(), Units.KM_PER_S2, true);
  363.                 generator.writeEntry(StateVectorKey.Y_DDOT.name(), coordinates.getAcceleration().getY(), Units.KM_PER_S2, true);
  364.                 generator.writeEntry(StateVectorKey.Z_DDOT.name(), coordinates.getAcceleration().getZ(), Units.KM_PER_S2, true);
  365.             }

  366.             generator.exitSection();

  367.         }
  368.     }

  369.     /**
  370.      * Write a covariance matrices.
  371.      * @param generator generator to use for producing output
  372.      * @param metadata metadata to use for interpreting data
  373.      * @param covariances covariances to write
  374.      * @throws IOException if the output stream throws one while writing.
  375.      */
  376.     void writeCovariances(final Generator generator, final OemMetadata metadata,
  377.                           final List<CartesianCovariance> covariances)
  378.         throws IOException {
  379.         if (covariances != null && !covariances.isEmpty()) {

  380.             // enter the global covariance section in KVN
  381.             if (generator.getFormat() == FileFormat.KVN) {
  382.                 generator.enterSection(OemDataSubStructureKey.COVARIANCE.name());
  383.             }

  384.             for (final CartesianCovariance covariance : covariances) {
  385.                 writeCovariance(generator, metadata, covariance);
  386.             }

  387.             // exit the global covariance section in KVN
  388.             if (generator.getFormat() == FileFormat.KVN) {
  389.                 generator.exitSection();
  390.             }

  391.         }
  392.     }

  393.     /**
  394.      * Write a single covariance matrix.
  395.      * @param generator generator to use for producing output
  396.      * @param metadata metadata to use for interpreting data
  397.      * @param covariance covariance to write
  398.      * @throws IOException if the output stream throws one while writing.
  399.      */
  400.     private void writeCovariance(final Generator generator, final OemMetadata metadata,
  401.                                  final CartesianCovariance covariance)
  402.         throws IOException {

  403.         // wrapper for a single matrix in XML
  404.         if (generator.getFormat() == FileFormat.XML) {
  405.             generator.enterSection(OemDataSubStructureKey.covarianceMatrix.name());
  406.         }

  407.         // epoch
  408.         generator.writeEntry(CartesianCovarianceKey.EPOCH.name(), getTimeConverter(), covariance.getEpoch(), false, true);

  409.         // reference frame
  410.         if (covariance.getReferenceFrame() != metadata.getReferenceFrame()) {
  411.             generator.writeEntry(CartesianCovarianceKey.COV_REF_FRAME.name(), covariance.getReferenceFrame().getName(), null, false);
  412.         }

  413.         // matrix data
  414.         final RealMatrix m = covariance.getCovarianceMatrix();
  415.         if (generator.getFormat() == FileFormat.KVN) {
  416.             for (int i = 0; i < m.getRowDimension(); ++i) {

  417.                 // write triangular matrix entries
  418.                 for (int j = 0; j <= i; ++j) {
  419.                     if (j > 0) {
  420.                         generator.writeRawData(' ');
  421.                     }
  422.                     generator.writeRawData(generator.doubleToString(Units.KM2.fromSI(m.getEntry(i, j))));
  423.                 }

  424.                 // end the line
  425.                 generator.newLine();

  426.             }
  427.         } else {
  428.             generator.writeEntry(CartesianCovarianceKey.CX_X.name(),         m.getEntry(0, 0), Units.KM2,        true);
  429.             generator.writeEntry(CartesianCovarianceKey.CY_X.name(),         m.getEntry(1, 0), Units.KM2,        true);
  430.             generator.writeEntry(CartesianCovarianceKey.CY_Y.name(),         m.getEntry(1, 1), Units.KM2,        true);
  431.             generator.writeEntry(CartesianCovarianceKey.CZ_X.name(),         m.getEntry(2, 0), Units.KM2,        true);
  432.             generator.writeEntry(CartesianCovarianceKey.CZ_Y.name(),         m.getEntry(2, 1), Units.KM2,        true);
  433.             generator.writeEntry(CartesianCovarianceKey.CZ_Z.name(),         m.getEntry(2, 2), Units.KM2,        true);
  434.             generator.writeEntry(CartesianCovarianceKey.CX_DOT_X.name(),     m.getEntry(3, 0), Units.KM2_PER_S,  true);
  435.             generator.writeEntry(CartesianCovarianceKey.CX_DOT_Y.name(),     m.getEntry(3, 1), Units.KM2_PER_S,  true);
  436.             generator.writeEntry(CartesianCovarianceKey.CX_DOT_Z.name(),     m.getEntry(3, 2), Units.KM2_PER_S,  true);
  437.             generator.writeEntry(CartesianCovarianceKey.CX_DOT_X_DOT.name(), m.getEntry(3, 3), Units.KM2_PER_S2, true);
  438.             generator.writeEntry(CartesianCovarianceKey.CY_DOT_X.name(),     m.getEntry(4, 0), Units.KM2_PER_S,  true);
  439.             generator.writeEntry(CartesianCovarianceKey.CY_DOT_Y.name(),     m.getEntry(4, 1), Units.KM2_PER_S,  true);
  440.             generator.writeEntry(CartesianCovarianceKey.CY_DOT_Z.name(),     m.getEntry(4, 2), Units.KM2_PER_S,  true);
  441.             generator.writeEntry(CartesianCovarianceKey.CY_DOT_X_DOT.name(), m.getEntry(4, 3), Units.KM2_PER_S2, true);
  442.             generator.writeEntry(CartesianCovarianceKey.CY_DOT_Y_DOT.name(), m.getEntry(4, 4), Units.KM2_PER_S2, true);
  443.             generator.writeEntry(CartesianCovarianceKey.CZ_DOT_X.name(),     m.getEntry(5, 0), Units.KM2_PER_S,  true);
  444.             generator.writeEntry(CartesianCovarianceKey.CZ_DOT_Y.name(),     m.getEntry(5, 1), Units.KM2_PER_S,  true);
  445.             generator.writeEntry(CartesianCovarianceKey.CZ_DOT_Z.name(),     m.getEntry(5, 2), Units.KM2_PER_S,  true);
  446.             generator.writeEntry(CartesianCovarianceKey.CZ_DOT_X_DOT.name(), m.getEntry(5, 3), Units.KM2_PER_S2, true);
  447.             generator.writeEntry(CartesianCovarianceKey.CZ_DOT_Y_DOT.name(), m.getEntry(5, 4), Units.KM2_PER_S2, true);
  448.             generator.writeEntry(CartesianCovarianceKey.CZ_DOT_Z_DOT.name(), m.getEntry(5, 5), Units.KM2_PER_S2, true);
  449.         }

  450.         // wrapper for a single matrix in XML
  451.         if (generator.getFormat() == FileFormat.XML) {
  452.             generator.exitSection();
  453.         }

  454.     }

  455.     /** Start of a data block.
  456.      * @param generator generator to use for producing output
  457.      * @throws IOException if the output stream throws one while writing.
  458.      */
  459.     void startData(final Generator generator) throws IOException {
  460.         if (generator.getFormat() == FileFormat.XML) {
  461.             generator.enterSection(XmlStructureKey.data.name());
  462.         }
  463.     }

  464.     /** End of a data block.
  465.      * @param generator generator to use for producing output
  466.      * @throws IOException if the output stream throws one while writing.
  467.      */
  468.     void endData(final Generator generator) throws IOException {
  469.         if (generator.getFormat() == FileFormat.XML) {
  470.             generator.exitSection();
  471.         }
  472.     }

  473. }