StreamingAemWriter.java

  1. /* Copyright 2002-2025 CS GROUP
  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.adm.aem;

  18. import java.io.IOException;

  19. import org.hipparchus.exception.LocalizedCoreFormats;
  20. import org.orekit.errors.OrekitException;
  21. import org.orekit.errors.OrekitMessages;
  22. import org.orekit.files.ccsds.definitions.FrameFacade;
  23. import org.orekit.files.ccsds.ndm.adm.AdmHeader;
  24. import org.orekit.files.ccsds.section.Header;
  25. import org.orekit.files.ccsds.utils.generation.Generator;
  26. import org.orekit.propagation.Propagator;
  27. import org.orekit.propagation.SpacecraftState;
  28. import org.orekit.propagation.sampling.OrekitFixedStepHandler;
  29. import org.orekit.time.AbsoluteDate;

  30. /**
  31.  * A writer for AEM files.
  32.  *
  33.  * <p> Each instance corresponds to a single AEM file.
  34.  *
  35.  * <p> This class can be used as a step handler for a {@link Propagator}.
  36.  *
  37.  * <pre>{@code
  38.  * Propagator propagator = ...; // pre-configured propagator
  39.  * AEMWriter  aemWriter  = ...; // pre-configured writer
  40.  *   try (Generator out = ...;  // set-up output stream
  41.  *        StreamingAemWriter sw = new StreamingAemWriter(out, aemWriter)) { // set-up streaming writer
  42.  *
  43.  *     // write segment 1
  44.  *     propagator.getMultiplexer().add(step, sw.newSegment());
  45.  *     propagator.propagate(startDate1, stopDate1);
  46.  *
  47.  *     ...
  48.  *
  49.  *     // write segment n
  50.  *     propagator.getMultiplexer().clear();
  51.  *     propagator.getMultiplexer().add(step, sw.newSegment());
  52.  *     propagator.propagate(startDateN, stopDateN);
  53.  *
  54.  *   }
  55.  * }</pre>
  56.  * @author Bryan Cazabonne
  57.  * @author Luc Maisonobe
  58.  * @see <a href="https://public.ccsds.org/Pubs/504x0b1c1.pdf">CCSDS 504.0-B-1 Attitude Data Messages</a>
  59.  * @see AemWriter
  60.  * @since 10.2
  61.  */
  62. public class StreamingAemWriter implements AutoCloseable {

  63.     /** Generator for AEM output. */
  64.     private final Generator generator;

  65.     /** Writer for the AEM message format. */
  66.     private final AemWriter writer;

  67.     /** Header. */
  68.     private final AdmHeader header;

  69.     /** Current metadata. */
  70.     private final AemMetadata metadata;

  71.     /** Indicator for writing header. */
  72.     private boolean headerWritePending;

  73.     /** Simple constructor.
  74.      * @param generator generator for AEM output
  75.      * @param writer writer for the AEM message format
  76.      * @param header file header (may be null)
  77.      * @param template template for metadata
  78.      * @since 11.0
  79.      */
  80.     public StreamingAemWriter(final Generator generator, final AemWriter writer,
  81.                               final AdmHeader header, final AemMetadata template) {
  82.         this.generator          = generator;
  83.         this.writer             = writer;
  84.         this.header             = header;
  85.         this.metadata           = template.copy(header == null ? writer.getDefaultVersion() : header.getFormatVersion());
  86.         this.headerWritePending = true;
  87.     }

  88.     /**
  89.      * Create a writer for a new AEM attitude ephemeris segment.
  90.      * <p> The returned writer can only write a single attitude ephemeris segment in an AEM.
  91.      * This method must be called to create a writer for each attitude ephemeris segment.
  92.      * @return a new AEM segment writer, ready for use.
  93.      */
  94.     public SegmentWriter newSegment() {
  95.         return new SegmentWriter();
  96.     }

  97.     /** {@inheritDoc} */
  98.     @Override
  99.     public void close() throws IOException {
  100.         writer.writeFooter(generator);
  101.     }

  102.     /** A writer for a segment of an AEM. */
  103.     public class SegmentWriter implements OrekitFixedStepHandler {

  104.         /** Empty constructor.
  105.          * <p>
  106.          * This constructor is not strictly necessary, but it prevents spurious
  107.          * javadoc warnings with JDK 18 and later.
  108.          * </p>
  109.          * @since 12.0
  110.          */
  111.         public SegmentWriter() {
  112.             // nothing to do
  113.         }

  114.         /**
  115.          * {@inheritDoc}
  116.          *
  117.          * <p> Sets the {@link AemMetadataKey#START_TIME} and {@link AemMetadataKey#STOP_TIME} in this
  118.          * segment's metadata if not already set by the user. Then calls {@link AemWriter#writeHeader(Generator, Header)
  119.          * writeHeader} if it is the first segment) and {@link AemWriter#writeMetadata(Generator, double, AemMetadata)} to start the segment.
  120.          */
  121.         @Override
  122.         public void init(final SpacecraftState s0, final AbsoluteDate t, final double step) {
  123.             try {
  124.                 final AbsoluteDate date = s0.getDate();
  125.                 if (t.isBefore(date)) {
  126.                     throw new OrekitException(OrekitMessages.NON_CHRONOLOGICALLY_SORTED_ENTRIES,
  127.                             date, t, date.durationFrom(t));
  128.                 }

  129.                 if (headerWritePending) {
  130.                     // we write the header only for the first segment
  131.                     writer.writeHeader(generator, header);
  132.                     headerWritePending = false;
  133.                 }

  134.                 metadata.setStartTime(date);
  135.                 metadata.setUseableStartTime(null);
  136.                 metadata.setUseableStopTime(null);
  137.                 metadata.setStopTime(t);
  138.                 if (metadata.getEndpoints().getFrameA() == null ||
  139.                     metadata.getEndpoints().getFrameA().asSpacecraftBodyFrame() == null) {
  140.                     // the external frame must be frame A
  141.                     metadata.getEndpoints().setFrameA(FrameFacade.map(s0.getAttitude().getReferenceFrame()));
  142.                 } else {
  143.                     // the external frame must be frame B
  144.                     metadata.getEndpoints().setFrameB(FrameFacade.map(s0.getAttitude().getReferenceFrame()));
  145.                 }
  146.                 writer.writeMetadata(generator,
  147.                                      header == null ? writer.getDefaultVersion() : header.getFormatVersion(),
  148.                                      metadata);
  149.                 writer.startAttitudeBlock(generator);
  150.             } catch (IOException e) {
  151.                 throw new OrekitException(e, LocalizedCoreFormats.SIMPLE_MESSAGE, e.getLocalizedMessage());
  152.             }
  153.         }

  154.         /** {@inheritDoc}. */
  155.         @Override
  156.         public void handleStep(final SpacecraftState currentState) {
  157.             try {
  158.                 writer.writeAttitudeEphemerisLine(generator,
  159.                                                   header == null ? writer.getDefaultVersion() : header.getFormatVersion(),
  160.                                                   metadata, currentState.getAttitude().getOrientation());
  161.             } catch (IOException e) {
  162.                 throw new OrekitException(e, LocalizedCoreFormats.SIMPLE_MESSAGE, e.getLocalizedMessage());
  163.             }
  164.         }

  165.         /** {@inheritDoc}. */
  166.         @Override
  167.         public void finish(final SpacecraftState finalState) {
  168.             try {
  169.                 writer.endAttitudeBlock(generator);
  170.             } catch (IOException e) {
  171.                 throw new OrekitException(e, LocalizedCoreFormats.SIMPLE_MESSAGE, e.getLocalizedMessage());
  172.             }
  173.         }

  174.     }

  175. }