OemWriter.java
- /* Copyright 2016 Applied Defense Solutions (ADS)
- * Licensed to CS GROUP (CS) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * ADS licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package org.orekit.files.ccsds.ndm.odm.oem;
- import java.io.IOException;
- import java.util.Date;
- import java.util.List;
- import org.hipparchus.linear.RealMatrix;
- import org.orekit.data.DataContext;
- import org.orekit.errors.OrekitException;
- import org.orekit.errors.OrekitMessages;
- import org.orekit.files.ccsds.definitions.TimeSystem;
- import org.orekit.files.ccsds.definitions.Units;
- import org.orekit.files.ccsds.ndm.ParsedUnitsBehavior;
- import org.orekit.files.ccsds.ndm.odm.CartesianCovariance;
- import org.orekit.files.ccsds.ndm.odm.CartesianCovarianceKey;
- import org.orekit.files.ccsds.ndm.odm.CommonMetadataKey;
- import org.orekit.files.ccsds.ndm.odm.OdmHeader;
- import org.orekit.files.ccsds.ndm.odm.OdmMetadataKey;
- import org.orekit.files.ccsds.ndm.odm.StateVectorKey;
- import org.orekit.files.ccsds.section.HeaderKey;
- import org.orekit.files.ccsds.section.KvnStructureKey;
- import org.orekit.files.ccsds.section.MetadataKey;
- import org.orekit.files.ccsds.section.XmlStructureKey;
- import org.orekit.files.ccsds.utils.ContextBinding;
- import org.orekit.files.ccsds.utils.FileFormat;
- import org.orekit.files.ccsds.utils.generation.AbstractMessageWriter;
- import org.orekit.files.ccsds.utils.generation.Generator;
- import org.orekit.time.AbsoluteDate;
- import org.orekit.utils.AccurateFormatter;
- import org.orekit.utils.CartesianDerivativesFilter;
- import org.orekit.utils.IERSConventions;
- import org.orekit.utils.TimeStampedPVCoordinates;
- import org.orekit.utils.units.Unit;
- /**
- * A writer for Orbit Ephemeris Message (OEM) files.
- *
- * <h2> Metadata </h2>
- *
- * <p> The OEM metadata used by this writer is described in the following table. Many
- * metadata items are optional or have default values so they do not need to be specified.
- * At a minimum the user must supply those values that are required and for which no
- * default exits: {@link OdmMetadataKey#OBJECT_NAME}, and {@link CommonMetadataKey#OBJECT_ID}. The usage
- * column in the table indicates where the metadata item is used, either in the OEM header
- * or in the metadata section at the start of an OEM ephemeris segment.
- *
- * <table>
- * <caption>OEM metadata</caption>
- * <thead>
- * <tr>
- * <th>Keyword</th>
- * <th>Usage</th>
- * <th>Obligatory</th>
- * <th>Default</th>
- * <th>Reference</th>
- * </thead>
- * <tbody>
- * <tr>
- * <td>{@code CCSDS_OEM_VERS}</td>
- * <td>Header</td>
- * <td>Yes</td>
- * <td>{@link Oem#FORMAT_VERSION_KEY}</td>
- * <td>Table 5-2</td>
- * <tr>
- * <td>{@code COMMENT}</td>
- * <td>Header</td>
- * <td>No</td>
- * <td></td>
- * <td>Table 5-2</td>
- * <tr>
- * <td>{@link HeaderKey#CREATION_DATE}</td>
- * <td>Header</td>
- * <td>Yes</td>
- * <td>{@link Date#Date() Now}</td>
- * <td>Table 5.2, 6.5.9</td>
- * <tr>
- * <td>{@link HeaderKey#ORIGINATOR}</td>
- * <td>Header</td>
- * <td>Yes</td>
- * <td>{@link #DEFAULT_ORIGINATOR}</td>
- * <td>Table 5-2</td>
- * <tr>
- * <td>{@link OdmMetadataKey#OBJECT_NAME}</td>
- * <td>Segment</td>
- * <td>Yes</td>
- * <td></td>
- * <td>Table 5-3</td>
- * <tr>
- * <td>{@link CommonMetadataKey#OBJECT_ID}</td>
- * <td>Segment</td>
- * <td>Yes</td>
- * <td></td>
- * <td>Table 5-3</td>
- * <tr>
- * <td>{@link CommonMetadataKey#CENTER_NAME}</td>
- * <td>Segment</td>
- * <td>Yes</td>
- * <td></td>
- * <td>Table 5-3</td>
- * <tr>
- * <td>{@link CommonMetadataKey#REF_FRAME}</td>
- * <td>Segment</td>
- * <td>Yes</td>
- * <td></td>
- * <td>Table 5-3, Annex A</td>
- * <tr>
- * <td>{@link CommonMetadataKey#REF_FRAME_EPOCH}</td>
- * <td>Segment</td>
- * <td>No</td>
- * <td></td>
- * <td>Table 5-3, 6.5.9</td>
- * <tr>
- * <td>{@link MetadataKey#TIME_SYSTEM}</td>
- * <td>Segment</td>
- * <td>Yes</td>
- * <td></td>
- * <tr>
- * <td>{@link OemMetadataKey#START_TIME}</td>
- * <td>Segment</td>
- * <td>Yes</td>
- * <td></td>
- * <td>Table 5-3, 6.5.9</td>
- * <tr>
- * <td>{@link OemMetadataKey#USEABLE_START_TIME}</td>
- * <td>Segment</td>
- * <td>No</td>
- * <td></td>
- * <td>Table 5-3, 6.5.9</td>
- * <tr>
- * <td>{@link OemMetadataKey#STOP_TIME}</td>
- * <td>Segment</td>
- * <td>Yes</td>
- * <td></td>
- * <td>Table 5-3, 6.5.9</td>
- * <tr>
- * <td>{@link OemMetadataKey#USEABLE_STOP_TIME}</td>
- * <td>Segment</td>
- * <td>No</td>
- * <td></td>
- * <td>Table 5-3, 6.5.9</td>
- * <tr>
- * <td>{@link OemMetadataKey#INTERPOLATION}</td>
- * <td>Segment</td>
- * <td>No</td>
- * <td></td>
- * <td>Table 5-3</td>
- * <tr>
- * <td>{@link OemMetadataKey#INTERPOLATION_DEGREE}</td>
- * <td>Segment</td>
- * <td>No</td>
- * <td></td>
- * <td>Table 5-3</td>
- * </tbody>
- *</table>
- *
- * <p> The {@link MetadataKey#TIME_SYSTEM} must be constant for the whole file and is used
- * to interpret all dates except {@link HeaderKey#CREATION_DATE} which is always in {@link
- * TimeSystem#UTC UTC}. The guessing algorithm is not guaranteed to work so it is recommended
- * to provide values for {@link CommonMetadataKey#CENTER_NAME} and {@link MetadataKey#TIME_SYSTEM}
- * to avoid any bugs associated with incorrect guesses.
- *
- * <p> Standardized values for {@link MetadataKey#TIME_SYSTEM} are GMST, GPS, MET, MRT, SCLK,
- * TAI, TCB, TDB, TT, UT1, and UTC. Standardized values for reference frames
- * are EME2000, GTOD, ICRF, ITRF2000, ITRF-93, ITRF-97, LVLH, RTN, QSW, TOD, TNW, NTW and RSW.
- * Additionally ITRF followed by a four digit year may be used.
- *
- * @author Hank Grabowski
- * @author Evan Ward
- * @since 9.0
- * @see <a href="https://public.ccsds.org/Pubs/502x0b2c1.pdf">CCSDS 502.0-B-2 Orbit Data
- * Messages</a>
- * @see <a href="https://public.ccsds.org/Pubs/500x0g4.pdf">CCSDS 500.0-G-4 Navigation
- * Data Definitions and Conventions</a>
- * @see StreamingOemWriter
- */
- public class OemWriter extends AbstractMessageWriter<OdmHeader, OemSegment, Oem> {
- /** Version number implemented. **/
- public static final double CCSDS_OEM_VERS = 3.0;
- /** Default file name for error messages. */
- public static final String DEFAULT_FILE_NAME = "<OEM output>";
- /** Padding width for aligning the '=' sign. */
- public static final int KVN_PADDING_WIDTH = 20;
- /**
- * Constructor used to create a new OEM writer configured with the necessary parameters
- * to successfully fill in all required fields that aren't part of a standard object.
- * <p>
- * If the mandatory header entries are not present (or if header is null),
- * built-in defaults will be used
- * </p>
- * <p>
- * The writer is built from the complete header and partial metadata. The template
- * metadata is used to initialize and independent local copy, that will be updated
- * as new segments are written (with at least the segment start and stop will change,
- * but some other parts may change too). The {@code template} argument itself is not
- * changed.
- * </p>
- * <p>
- * Calling this constructor directly is not recommended. Users should rather use
- * {@link org.orekit.files.ccsds.ndm.WriterBuilder#buildOemWriter()
- * writerBuilder.buildOemWriter()}.
- * </p>
- * @param conventions IERS Conventions
- * @param dataContext used to retrieve frames, time scales, etc.
- * @param missionReferenceDate reference date for Mission Elapsed Time or Mission Relative Time time systems
- * @since 11.0
- * @see #DEFAULT_FILE_NAME
- */
- public OemWriter(final IERSConventions conventions, final DataContext dataContext,
- final AbsoluteDate missionReferenceDate) {
- super(Oem.ROOT, Oem.FORMAT_VERSION_KEY, CCSDS_OEM_VERS,
- new ContextBinding(
- () -> conventions, () -> true, () -> dataContext,
- () -> ParsedUnitsBehavior.STRICT_COMPLIANCE,
- () -> missionReferenceDate, () -> TimeSystem.UTC, () -> 0.0, () -> 1.0));
- }
- /** {@inheritDoc} */
- @Override
- protected void writeSegmentContent(final Generator generator, final double formatVersion,
- final OemSegment segment)
- throws IOException {
- final OemMetadata metadata = segment.getMetadata();
- writeMetadata(generator, metadata);
- startData(generator);
- // write data comments
- generator.writeComments(segment.getData().getComments());
- // Loop on orbit data
- final CartesianDerivativesFilter filter = segment.getAvailableDerivatives();
- if (filter == CartesianDerivativesFilter.USE_P) {
- throw new OrekitException(OrekitMessages.MISSING_VELOCITY);
- }
- final boolean useAcceleration = filter.equals(CartesianDerivativesFilter.USE_PVA);
- for (final TimeStampedPVCoordinates coordinates : segment.getCoordinates()) {
- writeOrbitEphemerisLine(generator, metadata, coordinates, useAcceleration);
- }
- // output covariance data
- writeCovariances(generator, segment.getMetadata(), segment.getData().getCovarianceMatrices());
- endData(generator);
- }
- /** Write an ephemeris segment metadata.
- * @param generator generator to use for producing output
- * @param metadata metadata to write
- * @throws IOException if the output stream throws one while writing.
- */
- void writeMetadata(final Generator generator, final OemMetadata metadata)
- throws IOException {
- // add an empty line for presentation
- generator.newLine();
- final ContextBinding oldContext = getContext();
- setContext(new ContextBinding(oldContext::getConventions,
- oldContext::isSimpleEOP,
- oldContext::getDataContext,
- oldContext::getParsedUnitsBehavior,
- oldContext::getReferenceDate,
- metadata::getTimeSystem,
- oldContext::getClockCount,
- oldContext::getClockRate));
- // Start metadata
- generator.enterSection(generator.getFormat() == FileFormat.KVN ?
- KvnStructureKey.META.name() :
- XmlStructureKey.metadata.name());
- generator.writeComments(metadata.getComments());
- // objects
- generator.writeEntry(OdmMetadataKey.OBJECT_NAME.name(), metadata.getObjectName(), null, true);
- generator.writeEntry(CommonMetadataKey.OBJECT_ID.name(), metadata.getObjectID(), null, true);
- generator.writeEntry(CommonMetadataKey.CENTER_NAME.name(), metadata.getCenter().getName(), null, false);
- // frames
- generator.writeEntry(CommonMetadataKey.REF_FRAME.name(), metadata.getReferenceFrame().getName(), null, true);
- if (metadata.getFrameEpoch() != null) {
- generator.writeEntry(CommonMetadataKey.REF_FRAME_EPOCH.name(),
- getTimeConverter(), metadata.getFrameEpoch(),
- true, false);
- }
- // time
- generator.writeEntry(MetadataKey.TIME_SYSTEM.name(), metadata.getTimeSystem(), true);
- generator.writeEntry(OemMetadataKey.START_TIME.name(), getTimeConverter(), metadata.getStartTime(), false, true);
- if (metadata.getUseableStartTime() != null) {
- generator.writeEntry(OemMetadataKey.USEABLE_START_TIME.name(), getTimeConverter(), metadata.getUseableStartTime(), false, false);
- }
- if (metadata.getUseableStopTime() != null) {
- generator.writeEntry(OemMetadataKey.USEABLE_STOP_TIME.name(), getTimeConverter(), metadata.getUseableStopTime(), false, false);
- }
- generator.writeEntry(OemMetadataKey.STOP_TIME.name(), getTimeConverter(), metadata.getStopTime(), false, true);
- // interpolation
- generator.writeEntry(OemMetadataKey.INTERPOLATION.name(), metadata.getInterpolationMethod(), false);
- // treat degree < 0 as equivalent to null
- if (metadata.getInterpolationDegree() >= 0) {
- generator.writeEntry(OemMetadataKey.INTERPOLATION_DEGREE.name(),
- Integer.toString(metadata.getInterpolationDegree()),
- null, false);
- }
- // Stop metadata
- generator.exitSection();
- // add an empty line for presentation
- generator.newLine();
- }
- /**
- * Write a single orbit ephemeris line .
- * @param generator generator to use for producing output
- * @param metadata metadata to use for interpreting data
- * @param coordinates orbit information for a given date
- * @param useAcceleration is true, the acceleration data must be used
- * @throws IOException if the output stream throws one while writing.
- */
- void writeOrbitEphemerisLine(final Generator generator, final OemMetadata metadata,
- final TimeStampedPVCoordinates coordinates,
- final boolean useAcceleration)
- throws IOException {
- if (generator.getFormat() == FileFormat.KVN) {
- // Epoch
- generator.writeRawData(generator.dateToString(getTimeConverter(), coordinates.getDate()));
- // Position data in km
- generator.writeRawData(' ');
- generator.writeRawData(String.format(AccurateFormatter.format(Unit.KILOMETRE.fromSI(coordinates.getPosition().getX()))));
- generator.writeRawData(' ');
- generator.writeRawData(String.format(AccurateFormatter.format(Unit.KILOMETRE.fromSI(coordinates.getPosition().getY()))));
- generator.writeRawData(' ');
- generator.writeRawData(String.format(AccurateFormatter.format(Unit.KILOMETRE.fromSI(coordinates.getPosition().getZ()))));
- // Velocity data in km/s
- generator.writeRawData(' ');
- generator.writeRawData(String.format(AccurateFormatter.format(Units.KM_PER_S.fromSI(coordinates.getVelocity().getX()))));
- generator.writeRawData(' ');
- generator.writeRawData(String.format(AccurateFormatter.format(Units.KM_PER_S.fromSI(coordinates.getVelocity().getY()))));
- generator.writeRawData(' ');
- generator.writeRawData(String.format(AccurateFormatter.format(Units.KM_PER_S.fromSI(coordinates.getVelocity().getZ()))));
- // Acceleration data in km/s²
- if (useAcceleration) {
- generator.writeRawData(' ');
- generator.writeRawData(String.format(AccurateFormatter.format(Units.KM_PER_S2.fromSI(coordinates.getAcceleration().getX()))));
- generator.writeRawData(' ');
- generator.writeRawData(String.format(AccurateFormatter.format(Units.KM_PER_S2.fromSI(coordinates.getAcceleration().getY()))));
- generator.writeRawData(' ');
- generator.writeRawData(String.format(AccurateFormatter.format(Units.KM_PER_S2.fromSI(coordinates.getAcceleration().getZ()))));
- }
- // end the line
- generator.newLine();
- } else {
- generator.enterSection(OemDataSubStructureKey.stateVector.name());
- // Epoch
- generator.writeEntry(StateVectorKey.EPOCH.name(), getTimeConverter(), coordinates.getDate(), false, true);
- // Position data in km
- generator.writeEntry(StateVectorKey.X.name(), coordinates.getPosition().getX(), Unit.KILOMETRE, true);
- generator.writeEntry(StateVectorKey.Y.name(), coordinates.getPosition().getY(), Unit.KILOMETRE, true);
- generator.writeEntry(StateVectorKey.Z.name(), coordinates.getPosition().getZ(), Unit.KILOMETRE, true);
- // Velocity data in km/s
- generator.writeEntry(StateVectorKey.X_DOT.name(), coordinates.getVelocity().getX(), Units.KM_PER_S, true);
- generator.writeEntry(StateVectorKey.Y_DOT.name(), coordinates.getVelocity().getY(), Units.KM_PER_S, true);
- generator.writeEntry(StateVectorKey.Z_DOT.name(), coordinates.getVelocity().getZ(), Units.KM_PER_S, true);
- // Acceleration data in km/s²
- if (useAcceleration) {
- generator.writeEntry(StateVectorKey.X_DDOT.name(), coordinates.getAcceleration().getX(), Units.KM_PER_S2, true);
- generator.writeEntry(StateVectorKey.Y_DDOT.name(), coordinates.getAcceleration().getY(), Units.KM_PER_S2, true);
- generator.writeEntry(StateVectorKey.Z_DDOT.name(), coordinates.getAcceleration().getZ(), Units.KM_PER_S2, true);
- }
- generator.exitSection();
- }
- }
- /**
- * Write a covariance matrices.
- * @param generator generator to use for producing output
- * @param metadata metadata to use for interpreting data
- * @param covariances covariances to write
- * @throws IOException if the output stream throws one while writing.
- */
- void writeCovariances(final Generator generator, final OemMetadata metadata,
- final List<CartesianCovariance> covariances)
- throws IOException {
- if (covariances != null && !covariances.isEmpty()) {
- // enter the global covariance section in KVN
- if (generator.getFormat() == FileFormat.KVN) {
- generator.enterSection(OemDataSubStructureKey.COVARIANCE.name());
- }
- for (final CartesianCovariance covariance : covariances) {
- writeCovariance(generator, metadata, covariance);
- }
- // exit the global covariance section in KVN
- if (generator.getFormat() == FileFormat.KVN) {
- generator.exitSection();
- }
- }
- }
- /**
- * Write a single covariance matrix.
- * @param generator generator to use for producing output
- * @param metadata metadata to use for interpreting data
- * @param covariance covariance to write
- * @throws IOException if the output stream throws one while writing.
- */
- private void writeCovariance(final Generator generator, final OemMetadata metadata,
- final CartesianCovariance covariance)
- throws IOException {
- // wrapper for a single matrix in XML
- if (generator.getFormat() == FileFormat.XML) {
- generator.enterSection(OemDataSubStructureKey.covarianceMatrix.name());
- }
- // epoch
- generator.writeEntry(CartesianCovarianceKey.EPOCH.name(), getTimeConverter(), covariance.getEpoch(), false, true);
- // reference frame
- if (covariance.getReferenceFrame() != metadata.getReferenceFrame()) {
- generator.writeEntry(CartesianCovarianceKey.COV_REF_FRAME.name(), covariance.getReferenceFrame().getName(), null, false);
- }
- // matrix data
- final RealMatrix m = covariance.getCovarianceMatrix();
- if (generator.getFormat() == FileFormat.KVN) {
- for (int i = 0; i < m.getRowDimension(); ++i) {
- // write triangular matrix entries
- for (int j = 0; j <= i; ++j) {
- if (j > 0) {
- generator.writeRawData(' ');
- }
- generator.writeRawData(AccurateFormatter.format(Units.KM2.fromSI(m.getEntry(i, j))));
- }
- // end the line
- generator.newLine();
- }
- } else {
- generator.writeEntry(CartesianCovarianceKey.CX_X.name(), m.getEntry(0, 0), Units.KM2, true);
- generator.writeEntry(CartesianCovarianceKey.CY_X.name(), m.getEntry(1, 0), Units.KM2, true);
- generator.writeEntry(CartesianCovarianceKey.CY_Y.name(), m.getEntry(1, 1), Units.KM2, true);
- generator.writeEntry(CartesianCovarianceKey.CZ_X.name(), m.getEntry(2, 0), Units.KM2, true);
- generator.writeEntry(CartesianCovarianceKey.CZ_Y.name(), m.getEntry(2, 1), Units.KM2, true);
- generator.writeEntry(CartesianCovarianceKey.CZ_Z.name(), m.getEntry(2, 2), Units.KM2, true);
- generator.writeEntry(CartesianCovarianceKey.CX_DOT_X.name(), m.getEntry(3, 0), Units.KM2_PER_S, true);
- generator.writeEntry(CartesianCovarianceKey.CX_DOT_Y.name(), m.getEntry(3, 1), Units.KM2_PER_S, true);
- generator.writeEntry(CartesianCovarianceKey.CX_DOT_Z.name(), m.getEntry(3, 2), Units.KM2_PER_S, true);
- generator.writeEntry(CartesianCovarianceKey.CX_DOT_X_DOT.name(), m.getEntry(3, 3), Units.KM2_PER_S2, true);
- generator.writeEntry(CartesianCovarianceKey.CY_DOT_X.name(), m.getEntry(4, 0), Units.KM2_PER_S, true);
- generator.writeEntry(CartesianCovarianceKey.CY_DOT_Y.name(), m.getEntry(4, 1), Units.KM2_PER_S, true);
- generator.writeEntry(CartesianCovarianceKey.CY_DOT_Z.name(), m.getEntry(4, 2), Units.KM2_PER_S, true);
- generator.writeEntry(CartesianCovarianceKey.CY_DOT_X_DOT.name(), m.getEntry(4, 3), Units.KM2_PER_S2, true);
- generator.writeEntry(CartesianCovarianceKey.CY_DOT_Y_DOT.name(), m.getEntry(4, 4), Units.KM2_PER_S2, true);
- generator.writeEntry(CartesianCovarianceKey.CZ_DOT_X.name(), m.getEntry(5, 0), Units.KM2_PER_S, true);
- generator.writeEntry(CartesianCovarianceKey.CZ_DOT_Y.name(), m.getEntry(5, 1), Units.KM2_PER_S, true);
- generator.writeEntry(CartesianCovarianceKey.CZ_DOT_Z.name(), m.getEntry(5, 2), Units.KM2_PER_S, true);
- generator.writeEntry(CartesianCovarianceKey.CZ_DOT_X_DOT.name(), m.getEntry(5, 3), Units.KM2_PER_S2, true);
- generator.writeEntry(CartesianCovarianceKey.CZ_DOT_Y_DOT.name(), m.getEntry(5, 4), Units.KM2_PER_S2, true);
- generator.writeEntry(CartesianCovarianceKey.CZ_DOT_Z_DOT.name(), m.getEntry(5, 5), Units.KM2_PER_S2, true);
- }
- // wrapper for a single matrix in XML
- if (generator.getFormat() == FileFormat.XML) {
- generator.exitSection();
- }
- }
- /** Start of a data block.
- * @param generator generator to use for producing output
- * @throws IOException if the output stream throws one while writing.
- */
- void startData(final Generator generator) throws IOException {
- if (generator.getFormat() == FileFormat.XML) {
- generator.enterSection(XmlStructureKey.data.name());
- }
- }
- /** End of a data block.
- * @param generator generator to use for producing output
- * @throws IOException if the output stream throws one while writing.
- */
- void endData(final Generator generator) throws IOException {
- if (generator.getFormat() == FileFormat.XML) {
- generator.exitSection();
- }
- }
- }