StreamingOcmWriter.java
/* Contributed in the public domain.
* 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.
* CS 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.ocm;
import java.io.IOException;
import java.util.Collections;
import org.hipparchus.exception.LocalizedCoreFormats;
import org.orekit.bodies.OneAxisEllipsoid;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.files.ccsds.definitions.FrameFacade;
import org.orekit.files.ccsds.ndm.odm.OdmHeader;
import org.orekit.files.ccsds.section.XmlStructureKey;
import org.orekit.files.ccsds.utils.FileFormat;
import org.orekit.files.ccsds.utils.generation.Generator;
import org.orekit.frames.Frame;
import org.orekit.propagation.Propagator;
import org.orekit.propagation.SpacecraftState;
import org.orekit.propagation.sampling.OrekitFixedStepHandler;
import org.orekit.time.AbsoluteDate;
import org.orekit.utils.TimeStampedPVCoordinates;
/**
* A writer for OCM files.
*
* <p> Each instance corresponds to a single Orbit Comprehensive Message.
* A new OCM ephemeris trajectory state history block is started by calling
* {@link #newBlock()}.
* </p>
*
* <p>
* This writer is intended to write only trajectory state history blocks.
* It does not writes physical properties, covariance data, maneuver data,
* perturbations parameters, orbit determination or user-defined parameters.
* If these blocks are needed, then {@link OcmWriter OcmWriter} must be
* used as it handles all OCM data blocks.
* </p>
* <p>
* The trajectory blocks metadata identifiers ({@code TRAJ_ID},
* {@code TRAJ_PREV_ID}, {@code TRAJ_NEXT_ID}) are updated automatically
* using {@link TrajectoryStateHistoryMetadata#incrementTrajID(String)},
* so users should generally only set {@link TrajectoryStateHistoryMetadata#setTrajID(String)}
* in the template.
* </p>
*
* <p>
* The blocks returned by this class can be used as step handlers for a {@link Propagator}.
* </p>
*
* <pre>{@code
* Propagator propagator = ...; // pre-configured propagator
* OCMWriter ocmWriter = ...; // pre-configured writer
* try (Generator out = ...; // set-up output stream
* StreamingOcmWriter sw = new StreamingOcmWriter(out, ocmWriter, header, metadata, template)) { // set-up streaming writer
*
* // write block 1
* propagator.getMultiplexer().add(step, sw.newBlock());
* propagator.propagate(startDate1, stopDate1);
*
* ...
*
* // write block n
* propagator.getMultiplexer().clear();
* propagator.getMultiplexer().add(step, sw.newBlock());
* propagator.propagate(startDateN, stopDateN);
*
* }
* }</pre>
*
*
* @author Luc Maisonobe
* @see OcmWriter
* @see EphemerisOcmWriter
* @since 12.0
*/
public class StreamingOcmWriter implements AutoCloseable {
/** Generator for OCM output. */
private final Generator generator;
/** Writer for the OCM message format. */
private final OcmWriter writer;
/** Writer for the trajectory data block. */
private TrajectoryStateHistoryWriter trajectoryWriter;
/** Header. */
private final OdmHeader header;
/** Current metadata. */
private final OcmMetadata metadata;
/** Current trajectory metadata. */
private final TrajectoryStateHistoryMetadata trajectoryMetadata;
/** If the propagator's frame should be used. */
private final boolean useAttitudeFrame;
/** Indicator for writing header. */
private boolean headerWritePending;
/** Last Z coordinate seen. */
private double lastZ;
/**
* Construct a writer that for each segment uses the reference frame of the
* first state's attitude.
*
* @param generator generator for OCM output
* @param writer writer for the OCM message format
* @param header file header (may be null)
* @param metadata file metadata
* @param template template for trajectory metadata
* @see #StreamingOcmWriter(Generator, OcmWriter, OdmHeader, OcmMetadata, TrajectoryStateHistoryMetadata, boolean)
*/
public StreamingOcmWriter(final Generator generator, final OcmWriter writer,
final OdmHeader header, final OcmMetadata metadata,
final TrajectoryStateHistoryMetadata template) {
this(generator, writer, header, metadata, template, true);
}
/**
* Simple constructor.
*
* @param generator generator for OCM output
* @param writer writer for the OCM message format
* @param header file header (may be null)
* @param metadata file metadata
* @param template template for trajectory metadata
* @param useAttitudeFrame if {@code true} then the reference frame for
* each segment is taken from the first state's
* attitude. Otherwise the {@code template}'s
* reference frame is used, {@link
* TrajectoryStateHistoryMetadata#getTrajReferenceFrame()}.
*/
public StreamingOcmWriter(final Generator generator, final OcmWriter writer,
final OdmHeader header, final OcmMetadata metadata,
final TrajectoryStateHistoryMetadata template,
final boolean useAttitudeFrame) {
this.generator = generator;
this.writer = writer;
this.header = header;
this.metadata = metadata;
this.trajectoryMetadata = template.copy(header == null ? writer.getDefaultVersion() : header.getFormatVersion());
this.useAttitudeFrame = useAttitudeFrame;
this.headerWritePending = true;
this.lastZ = Double.NaN;
}
/**
* Create a writer for a new OCM trajectory state history block.
* <p> The returned writer can only write a single trajectory state history block in an OCM.
* This method must be called to create a writer for each trajectory state history block.
* @return a new OCM trajectory state history block writer, ready for use.
*/
public BlockWriter newBlock() {
return new BlockWriter();
}
/** {@inheritDoc} */
@Override
public void close() throws IOException {
writer.writeFooter(generator);
}
/** A writer for a trajectory state history block of an OCM. */
public class BlockWriter implements OrekitFixedStepHandler {
/** Reference frame of this segment. */
private Frame frame;
/** Elements type. */
private OrbitElementsType type;
/** Number of ascending nodes crossings. */
private int crossings;
/** Empty constructor.
* <p>
* This constructor is not strictly necessary, but it prevents spurious
* javadoc warnings with JDK 18 and later.
* </p>
* @since 12.0
*/
public BlockWriter() {
// nothing to do
}
/**
* {@inheritDoc}
*
* <p>Writes the header automatically on first segment.
* Sets the {@link OcmMetadataKey#START_TIME} and {@link OcmMetadataKey#STOP_TIME} in this
* block metadata if not already set by the user.
*/
@Override
public void init(final SpacecraftState s0, final AbsoluteDate t, final double step) {
try {
final AbsoluteDate date = s0.getDate();
if (t.isBefore(date)) {
throw new OrekitException(OrekitMessages.NON_CHRONOLOGICALLY_SORTED_ENTRIES,
date, t, date.durationFrom(t));
}
if (headerWritePending) {
// we write the header and metadata only for the first segment
writer.writeHeader(generator, header);
if (generator.getFormat() == FileFormat.XML) {
generator.enterSection(XmlStructureKey.segment.name());
}
new OcmMetadataWriter(metadata, writer.getTimeConverter()).write(generator);
if (generator.getFormat() == FileFormat.XML) {
generator.enterSection(XmlStructureKey.data.name());
}
headerWritePending = false;
}
trajectoryMetadata.setTrajNextID(TrajectoryStateHistoryMetadata.incrementTrajID(trajectoryMetadata.getTrajID()));
trajectoryMetadata.setUseableStartTime(date);
trajectoryMetadata.setUseableStopTime(t);
if (useAttitudeFrame) {
frame = s0.getAttitude().getReferenceFrame();
trajectoryMetadata.setTrajReferenceFrame(FrameFacade.map(frame));
} else {
frame = trajectoryMetadata.getTrajReferenceFrame().asFrame();
}
crossings = 0;
type = trajectoryMetadata.getTrajType();
final OneAxisEllipsoid body = trajectoryMetadata.getTrajType() == OrbitElementsType.GEODETIC ?
new OneAxisEllipsoid(writer.getEquatorialRadius(),
writer.getFlattening(),
trajectoryMetadata.getTrajReferenceFrame().asFrame()) :
null;
trajectoryWriter = new TrajectoryStateHistoryWriter(new TrajectoryStateHistory(trajectoryMetadata,
Collections.emptyList(),
body, s0.getMu()),
writer.getTimeConverter());
trajectoryWriter.enterSection(generator);
trajectoryWriter.writeMetadata(generator);
} catch (IOException e) {
throw new OrekitException(e, LocalizedCoreFormats.SIMPLE_MESSAGE, e.getLocalizedMessage());
}
}
/** {@inheritDoc}. */
@Override
public void handleStep(final SpacecraftState currentState) {
try {
final TimeStampedPVCoordinates pv =
currentState.getPVCoordinates(frame);
if (lastZ < 0.0 && pv.getPosition().getZ() >= 0.0) {
// we crossed ascending node
++crossings;
}
lastZ = pv.getPosition().getZ();
final TrajectoryState state = new TrajectoryState(type, pv.getDate(),
type.toRawElements(pv, frame,
trajectoryWriter.getHistory().getBody(),
currentState.getMu()));
trajectoryWriter.writeState(generator, state, type.getUnits());
} catch (IOException e) {
throw new OrekitException(e, LocalizedCoreFormats.SIMPLE_MESSAGE, e.getLocalizedMessage());
}
}
/** {@inheritDoc}. */
@Override
public void finish(final SpacecraftState finalState) {
try {
trajectoryWriter.exitSection(generator);
// update the trajectory IDs
trajectoryMetadata.setTrajPrevID(trajectoryMetadata.getTrajID());
trajectoryMetadata.setTrajID(trajectoryMetadata.getTrajNextID());
if (trajectoryMetadata.getOrbRevNum() >= 0) {
// update the orbits revolution number
trajectoryMetadata.setOrbRevNum(trajectoryMetadata.getOrbRevNum() + crossings);
}
} catch (IOException e) {
throw new OrekitException(e, LocalizedCoreFormats.SIMPLE_MESSAGE, e.getLocalizedMessage());
}
}
}
}