StreamingOcmWriter.java

  1. /* Contributed in the public domain.
  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.  * CS 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.ocm;

  18. import java.io.IOException;
  19. import java.util.Collections;

  20. import org.hipparchus.exception.LocalizedCoreFormats;
  21. import org.orekit.bodies.OneAxisEllipsoid;
  22. import org.orekit.errors.OrekitException;
  23. import org.orekit.errors.OrekitMessages;
  24. import org.orekit.files.ccsds.definitions.FrameFacade;
  25. import org.orekit.files.ccsds.ndm.odm.OdmHeader;
  26. import org.orekit.files.ccsds.section.XmlStructureKey;
  27. import org.orekit.files.ccsds.utils.FileFormat;
  28. import org.orekit.files.ccsds.utils.generation.Generator;
  29. import org.orekit.frames.Frame;
  30. import org.orekit.propagation.Propagator;
  31. import org.orekit.propagation.SpacecraftState;
  32. import org.orekit.propagation.sampling.OrekitFixedStepHandler;
  33. import org.orekit.time.AbsoluteDate;
  34. import org.orekit.utils.TimeStampedPVCoordinates;

  35. /**
  36.  * A writer for OCM files.
  37.  *
  38.  * <p> Each instance corresponds to a single Orbit Comprehensive Message.
  39.  * A new OCM ephemeris trajectory state history block is started by calling
  40.  * {@link #newBlock()}.
  41.  * </p>
  42.  *
  43.  * <p>
  44.  * This writer is intended to write only trajectory state history blocks.
  45.  * It does not writes physical properties, covariance data, maneuver data,
  46.  * perturbations parameters, orbit determination or user-defined parameters.
  47.  * If these blocks are needed, then {@link OcmWriter OcmWriter} must be
  48.  * used as it handles all OCM data blocks.
  49.  * </p>
  50.  * <p>
  51.  * The trajectory blocks metadata identifiers ({@code TRAJ_ID},
  52.  * {@code TRAJ_PREV_ID}, {@code TRAJ_NEXT_ID}) are updated automatically
  53.  * using {@link TrajectoryStateHistoryMetadata#incrementTrajID(String)},
  54.  * so users should generally only set {@link TrajectoryStateHistoryMetadata#setTrajID(String)}
  55.  * in the template.
  56.  * </p>
  57.  *
  58.  * <p>
  59.  * The blocks returned by this class can be used as step handlers for a {@link Propagator}.
  60.  * </p>
  61.  *
  62.  * <pre>{@code
  63.  * Propagator propagator = ...; // pre-configured propagator
  64.  * OCMWriter  ocmWriter  = ...; // pre-configured writer
  65.  *   try (Generator out = ...;  // set-up output stream
  66.  *        StreamingOcmWriter sw = new StreamingOcmWriter(out, ocmWriter, header, metadata, template)) { // set-up streaming writer
  67.  *
  68.  *     // write block 1
  69.  *     propagator.getMultiplexer().add(step, sw.newBlock());
  70.  *     propagator.propagate(startDate1, stopDate1);
  71.  *
  72.  *     ...
  73.  *
  74.  *     // write block n
  75.  *     propagator.getMultiplexer().clear();
  76.  *     propagator.getMultiplexer().add(step, sw.newBlock());
  77.  *     propagator.propagate(startDateN, stopDateN);
  78.  *
  79.  *   }
  80.  * }</pre>
  81.  *
  82.  *
  83.  * @author Luc Maisonobe
  84.  * @see OcmWriter
  85.  * @see EphemerisOcmWriter
  86.  * @since 12.0
  87.  */
  88. public class StreamingOcmWriter implements AutoCloseable {

  89.     /** Generator for OCM output. */
  90.     private final Generator generator;

  91.     /** Writer for the OCM message format. */
  92.     private final OcmWriter writer;

  93.     /** Writer for the trajectory data block. */
  94.     private TrajectoryStateHistoryWriter trajectoryWriter;

  95.     /** Header. */
  96.     private final OdmHeader header;

  97.     /** Current metadata. */
  98.     private final OcmMetadata metadata;

  99.     /** Current trajectory metadata. */
  100.     private final TrajectoryStateHistoryMetadata trajectoryMetadata;

  101.     /** If the propagator's frame should be used. */
  102.     private final boolean useAttitudeFrame;

  103.     /** Indicator for writing header. */
  104.     private boolean headerWritePending;

  105.     /** Last Z coordinate seen. */
  106.     private double lastZ;

  107.     /**
  108.      * Construct a writer that for each segment uses the reference frame of the
  109.      * first state's attitude.
  110.      *
  111.      * @param generator generator for OCM output
  112.      * @param writer    writer for the OCM message format
  113.      * @param header    file header (may be null)
  114.      * @param metadata  file metadata
  115.      * @param template  template for trajectory metadata
  116.      * @see #StreamingOcmWriter(Generator, OcmWriter, OdmHeader, OcmMetadata, TrajectoryStateHistoryMetadata, boolean)
  117.      */
  118.     public StreamingOcmWriter(final Generator generator, final OcmWriter writer,
  119.                               final OdmHeader header, final OcmMetadata metadata,
  120.                               final TrajectoryStateHistoryMetadata template) {
  121.         this(generator, writer, header, metadata, template, true);
  122.     }

  123.     /**
  124.      * Simple constructor.
  125.      *
  126.      * @param generator           generator for OCM output
  127.      * @param writer              writer for the OCM message format
  128.      * @param header              file header (may be null)
  129.      * @param metadata            file metadata
  130.      * @param template            template for trajectory metadata
  131.      * @param useAttitudeFrame    if {@code true} then the reference frame for
  132.      *                            each segment is taken from the first state's
  133.      *                            attitude. Otherwise the {@code template}'s
  134.      *                            reference frame is used, {@link
  135.      *                            TrajectoryStateHistoryMetadata#getTrajReferenceFrame()}.
  136.      */
  137.     public StreamingOcmWriter(final Generator generator, final OcmWriter writer,
  138.                               final OdmHeader header, final OcmMetadata metadata,
  139.                               final TrajectoryStateHistoryMetadata template,
  140.                               final boolean useAttitudeFrame) {
  141.         this.generator           = generator;
  142.         this.writer              = writer;
  143.         this.header              = header;
  144.         this.metadata            = metadata;
  145.         this.trajectoryMetadata  = template.copy(header == null ? writer.getDefaultVersion() : header.getFormatVersion());
  146.         this.useAttitudeFrame    = useAttitudeFrame;
  147.         this.headerWritePending  = true;
  148.         this.lastZ               = Double.NaN;
  149.     }

  150.     /**
  151.      * Create a writer for a new OCM trajectory state history block.
  152.      * <p> The returned writer can only write a single trajectory state history block in an OCM.
  153.      * This method must be called to create a writer for each trajectory state history block.
  154.      * @return a new OCM trajectory state history block writer, ready for use.
  155.      */
  156.     public BlockWriter newBlock() {
  157.         return new BlockWriter();
  158.     }

  159.     /** {@inheritDoc} */
  160.     @Override
  161.     public void close() throws IOException {
  162.         writer.writeFooter(generator);
  163.     }

  164.     /** A writer for a trajectory state history block of an OCM. */
  165.     public class BlockWriter implements OrekitFixedStepHandler {

  166.         /** Reference frame of this segment. */
  167.         private Frame frame;

  168.         /** Elements type. */
  169.         private OrbitElementsType type;

  170.         /** Number of ascending nodes crossings. */
  171.         private int crossings;

  172.         /** Empty constructor.
  173.          * <p>
  174.          * This constructor is not strictly necessary, but it prevents spurious
  175.          * javadoc warnings with JDK 18 and later.
  176.          * </p>
  177.          * @since 12.0
  178.          */
  179.         public BlockWriter() {
  180.             // nothing to do
  181.         }

  182.         /**
  183.          * {@inheritDoc}
  184.          *
  185.          * <p>Writes the header automatically on first segment.
  186.          * Sets the {@link OcmMetadataKey#START_TIME} and {@link OcmMetadataKey#STOP_TIME} in this
  187.          * block metadata if not already set by the user.
  188.          */
  189.         @Override
  190.         public void init(final SpacecraftState s0, final AbsoluteDate t, final double step) {
  191.             try {
  192.                 final AbsoluteDate date = s0.getDate();
  193.                 if (t.isBefore(date)) {
  194.                     throw new OrekitException(OrekitMessages.NON_CHRONOLOGICALLY_SORTED_ENTRIES,
  195.                             date, t, date.durationFrom(t));
  196.                 }

  197.                 if (headerWritePending) {
  198.                     // we write the header and metadata only for the first segment
  199.                     writer.writeHeader(generator, header);
  200.                     if (generator.getFormat() == FileFormat.XML) {
  201.                         generator.enterSection(XmlStructureKey.segment.name());
  202.                     }
  203.                     new OcmMetadataWriter(metadata, writer.getTimeConverter()).write(generator);
  204.                     if (generator.getFormat() == FileFormat.XML) {
  205.                         generator.enterSection(XmlStructureKey.data.name());
  206.                     }
  207.                     headerWritePending = false;
  208.                 }

  209.                 trajectoryMetadata.setTrajNextID(TrajectoryStateHistoryMetadata.incrementTrajID(trajectoryMetadata.getTrajID()));
  210.                 trajectoryMetadata.setUseableStartTime(date);
  211.                 trajectoryMetadata.setUseableStopTime(t);
  212.                 if (useAttitudeFrame) {
  213.                     frame = s0.getAttitude().getReferenceFrame();
  214.                     trajectoryMetadata.setTrajReferenceFrame(FrameFacade.map(frame));
  215.                 } else {
  216.                     frame = trajectoryMetadata.getTrajReferenceFrame().asFrame();
  217.                 }

  218.                 crossings = 0;
  219.                 type      = trajectoryMetadata.getTrajType();

  220.                 final OneAxisEllipsoid body = trajectoryMetadata.getTrajType() == OrbitElementsType.GEODETIC ?
  221.                                               new OneAxisEllipsoid(writer.getEquatorialRadius(),
  222.                                                                    writer.getFlattening(),
  223.                                                                    trajectoryMetadata.getTrajReferenceFrame().asFrame()) :
  224.                                               null;
  225.                 final double mu = s0.isOrbitDefined() ? s0.getOrbit().getMu() : Double.NaN;
  226.                 trajectoryWriter = new TrajectoryStateHistoryWriter(new TrajectoryStateHistory(trajectoryMetadata,
  227.                                                                                                Collections.emptyList(),
  228.                                                                                                body, mu),
  229.                                                                     writer.getTimeConverter());
  230.                 trajectoryWriter.enterSection(generator);
  231.                 trajectoryWriter.writeMetadata(generator);

  232.             } catch (IOException e) {
  233.                 throw new OrekitException(e, LocalizedCoreFormats.SIMPLE_MESSAGE, e.getLocalizedMessage());
  234.             }
  235.         }

  236.         /** {@inheritDoc}. */
  237.         @Override
  238.         public void handleStep(final SpacecraftState currentState) {
  239.             try {
  240.                 final TimeStampedPVCoordinates pv =
  241.                         currentState.getPVCoordinates(frame);
  242.                 if (lastZ < 0.0 && pv.getPosition().getZ() >= 0.0) {
  243.                     // we crossed ascending node
  244.                     ++crossings;
  245.                 }
  246.                 final double mu = currentState.isOrbitDefined() ? currentState.getOrbit().getMu() : Double.NaN;
  247.                 lastZ = pv.getPosition().getZ();
  248.                 final TrajectoryState state = new TrajectoryState(type, pv.getDate(),
  249.                                                                   type.toRawElements(pv, frame,
  250.                                                                                      trajectoryWriter.getHistory().getBody(),
  251.                                                                                      mu));
  252.                 trajectoryWriter.writeState(generator, state, type.getUnits());
  253.             } catch (IOException e) {
  254.                 throw new OrekitException(e, LocalizedCoreFormats.SIMPLE_MESSAGE, e.getLocalizedMessage());
  255.             }
  256.         }

  257.         /** {@inheritDoc}. */
  258.         @Override
  259.         public void finish(final SpacecraftState finalState) {
  260.             try {

  261.                 trajectoryWriter.exitSection(generator);

  262.                 // update the trajectory IDs
  263.                 trajectoryMetadata.setTrajPrevID(trajectoryMetadata.getTrajID());
  264.                 trajectoryMetadata.setTrajID(trajectoryMetadata.getTrajNextID());

  265.                 if (trajectoryMetadata.getOrbRevNum() >= 0) {
  266.                     // update the orbits revolution number
  267.                     trajectoryMetadata.setOrbRevNum(trajectoryMetadata.getOrbRevNum() + crossings);
  268.                 }

  269.             } catch (IOException e) {
  270.                 throw new OrekitException(e, LocalizedCoreFormats.SIMPLE_MESSAGE, e.getLocalizedMessage());
  271.             }
  272.         }

  273.     }

  274. }