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;
- final double mu = s0.isOrbitDefined() ? s0.getOrbit().getMu() : Double.NaN;
- trajectoryWriter = new TrajectoryStateHistoryWriter(new TrajectoryStateHistory(trajectoryMetadata,
- Collections.emptyList(),
- body, mu),
- 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;
- }
- final double mu = currentState.isOrbitDefined() ? currentState.getOrbit().getMu() : Double.NaN;
- lastZ = pv.getPosition().getZ();
- final TrajectoryState state = new TrajectoryState(type, pv.getDate(),
- type.toRawElements(pv, frame,
- trajectoryWriter.getHistory().getBody(),
- mu));
- 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());
- }
- }
- }
- }