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.oem;
18  
19  import java.io.IOException;
20  
21  import org.hipparchus.exception.LocalizedCoreFormats;
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.utils.generation.Generator;
27  import org.orekit.frames.Frame;
28  import org.orekit.propagation.Propagator;
29  import org.orekit.propagation.SpacecraftState;
30  import org.orekit.propagation.sampling.OrekitFixedStepHandler;
31  import org.orekit.time.AbsoluteDate;
32  import org.orekit.utils.TimeStampedPVCoordinates;
33  
34  /**
35   * A writer for OEM files.
36   *
37   * <p> Each instance corresponds to a single OEM file. A new OEM ephemeris segment is
38   * started by calling {@link #newSegment()}.
39   *
40   * <p>
41   * The segments returned by this class can be used as step handlers for a {@link Propagator}.
42   * </p>
43   *
44   * <pre>{@code
45   * Propagator propagator = ...; // pre-configured propagator
46   * OEMWriter  aemWriter  = ...; // pre-configured writer
47   *   try (Generator out = ...;  // set-up output stream
48   *        StreamingOemWriter sw = new StreamingOemWriter(out, oemWriter, header, metadata)) { // set-up streaming writer
49   *
50   *     // write segment 1
51   *     propagator.getMultiplexer().add(step, sw.newSegment());
52   *     propagator.propagate(startDate1, stopDate1);
53   *
54   *     ...
55   *
56   *     // write segment n
57   *     propagator.getMultiplexer().clear();
58   *     propagator.getMultiplexer().add(step, sw.newSegment());
59   *     propagator.propagate(startDateN, stopDateN);
60   *
61   *   }
62   * }</pre>
63   *
64   *
65   * @author Evan Ward
66   * @see <a href="https://public.ccsds.org/Pubs/502x0b2c1.pdf">CCSDS 502.0-B-2 Orbit Data
67   *      Messages</a>
68   * @see <a href="https://public.ccsds.org/Pubs/500x0g4.pdf">CCSDS 500.0-G-4 Navigation
69   *      Data Definitions and Conventions</a>
70   * @see OemWriter
71   */
72  public class StreamingOemWriter implements AutoCloseable {
73  
74      /** Generator for OEM output. */
75      private final Generator generator;
76  
77      /** Writer for the OEM message format. */
78      private final OemWriter writer;
79  
80      /** Header. */
81      private final OdmHeader header;
82  
83      /** Current metadata. */
84      private final OemMetadata metadata;
85  
86      /** If the propagator's frame should be used. */
87      private final boolean useAttitudeFrame;
88  
89      /** If acceleration should be included in the output. */
90      private final boolean includeAcceleration;
91  
92      /** Indicator for writing header. */
93      private boolean headerWritePending;
94  
95      /**
96       * Construct a writer that for each segment uses the reference frame of the
97       * first state's attitude.
98       *
99       * @param generator generator for OEM output
100      * @param writer    writer for the AEM message format
101      * @param header    file header (may be null)
102      * @param template  template for metadata
103      * @since 11.0
104      * @see #StreamingOemWriter(Generator, OemWriter, OdmHeader, OemMetadata, boolean)
105      */
106     public StreamingOemWriter(final Generator generator, final OemWriter writer,
107                               final OdmHeader header, final OemMetadata template) {
108         this(generator, writer, header, template, true);
109     }
110 
111     /**
112      * Construct a writer that writes position, velocity, and acceleration at
113      * each time step.
114      *
115      * @param generator        generator for OEM output
116      * @param writer           writer for the AEM message format
117      * @param header           file header (may be null)
118      * @param template         template for metadata
119      * @param useAttitudeFrame if {@code true} then the reference frame for each
120      *                         segment is taken from the first state's attitude.
121      *                         Otherwise the {@code template}'s reference frame
122      *                         is used, {@link OemMetadata#getReferenceFrame()}.
123      * @see #StreamingOemWriter(Generator, OemWriter, OdmHeader, OemMetadata, boolean, boolean)
124      * @since 11.1.2
125      */
126     public StreamingOemWriter(final Generator generator, final OemWriter writer,
127                               final OdmHeader header, final OemMetadata template,
128                               final boolean useAttitudeFrame) {
129         this(generator, writer, header, template, useAttitudeFrame, true);
130     }
131 
132     /**
133      * Simple constructor.
134      *
135      * @param generator           generator for OEM output
136      * @param writer              writer for the AEM message format
137      * @param header              file header (may be null)
138      * @param template            template for metadata
139      * @param useAttitudeFrame    if {@code true} then the reference frame for
140      *                            each segment is taken from the first state's
141      *                            attitude. Otherwise the {@code template}'s
142      *                            reference frame is used, {@link
143      *                            OemMetadata#getReferenceFrame()}.
144      * @param includeAcceleration if {@code true} then acceleration is included
145      *                            in the OEM file produced. Otherwise only
146      *                            position and velocity is included.
147      * @since 11.1.2
148      */
149     public StreamingOemWriter(final Generator generator, final OemWriter writer,
150                               final OdmHeader header, final OemMetadata template,
151                               final boolean useAttitudeFrame,
152                               final boolean includeAcceleration) {
153         this.generator          = generator;
154         this.writer             = writer;
155         this.header             = header;
156         this.metadata           = template.copy(header == null ? writer.getDefaultVersion() : header.getFormatVersion());
157         this.useAttitudeFrame   = useAttitudeFrame;
158         this.includeAcceleration = includeAcceleration;
159         this.headerWritePending = true;
160     }
161 
162     /**
163      * Create a writer for a new OEM ephemeris segment.
164      * <p> The returned writer can only write a single ephemeris segment in an OEM.
165      * This method must be called to create a writer for each ephemeris segment.
166      * @return a new OEM segment writer, ready for use.
167      */
168     public SegmentWriter newSegment() {
169         return new SegmentWriter();
170     }
171 
172     /** {@inheritDoc} */
173     @Override
174     public void close() throws IOException {
175         writer.writeFooter(generator);
176     }
177 
178     /** A writer for a segment of an OEM. */
179     public class SegmentWriter implements OrekitFixedStepHandler {
180 
181         /** Reference frame of this segment. */
182         private Frame frame;
183 
184         /** Empty constructor.
185          * <p>
186          * This constructor is not strictly necessary, but it prevents spurious
187          * javadoc warnings with JDK 18 and later.
188          * </p>
189          * @since 12.0
190          */
191         public SegmentWriter() {
192             // nothing to do
193         }
194 
195         /**
196          * {@inheritDoc}
197          *
198          * <p>Writes the header automatically on first segment.
199          * Sets the {@link OemMetadataKey#START_TIME} and {@link OemMetadataKey#STOP_TIME} in this
200          * segment's metadata if not already set by the user.
201          */
202         @Override
203         public void init(final SpacecraftState s0, final AbsoluteDate t, final double step) {
204             try {
205                 final AbsoluteDate date = s0.getDate();
206                 if (t.isBefore(date)) {
207                     throw new OrekitException(OrekitMessages.NON_CHRONOLOGICALLY_SORTED_ENTRIES,
208                             date, t, date.durationFrom(t));
209                 }
210 
211                 if (headerWritePending) {
212                     // we write the header only for the first segment
213                     writer.writeHeader(generator, header);
214                     headerWritePending = false;
215                 }
216 
217                 metadata.setStartTime(date);
218                 metadata.setUseableStartTime(null);
219                 metadata.setUseableStopTime(null);
220                 metadata.setStopTime(t);
221                 if (useAttitudeFrame) {
222                     frame = s0.getAttitude().getReferenceFrame();
223                     metadata.setReferenceFrame(FrameFacade.map(frame));
224                 } else {
225                     frame = metadata.getFrame();
226                 }
227                 writer.writeMetadata(generator, metadata);
228                 writer.startData(generator);
229             } catch (IOException e) {
230                 throw new OrekitException(e, LocalizedCoreFormats.SIMPLE_MESSAGE, e.getLocalizedMessage());
231             }
232         }
233 
234         /** {@inheritDoc}. */
235         @Override
236         public void handleStep(final SpacecraftState currentState) {
237             try {
238                 final TimeStampedPVCoordinates pv =
239                         currentState.getPVCoordinates(frame);
240                 writer.writeOrbitEphemerisLine(generator, metadata, pv, includeAcceleration);
241             } catch (IOException e) {
242                 throw new OrekitException(e, LocalizedCoreFormats.SIMPLE_MESSAGE, e.getLocalizedMessage());
243             }
244         }
245 
246         /** {@inheritDoc}. */
247         @Override
248         public void finish(final SpacecraftState finalState) {
249             try {
250                 writer.endData(generator);
251             } catch (IOException e) {
252                 throw new OrekitException(e, LocalizedCoreFormats.SIMPLE_MESSAGE, e.getLocalizedMessage());
253             }
254         }
255 
256     }
257 
258 }