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.AccurateFormatter;
  44. import org.orekit.utils.CartesianDerivativesFilter;
  45. import org.orekit.utils.IERSConventions;
  46. import org.orekit.utils.TimeStampedPVCoordinates;
  47. import org.orekit.utils.units.Unit;

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

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

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

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

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

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

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

  236.         startData(generator);

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

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

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

  250.         endData(generator);

  251.     }

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

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

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

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

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

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

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

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

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

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

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

  308.     }

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

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

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

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

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

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

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

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

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

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

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

  367.             generator.exitSection();

  368.         }
  369.     }

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

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

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

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

  392.         }
  393.     }

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

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

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

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

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

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

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

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

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

  455.     }

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

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

  474. }