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 }