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
19 import java.io.IOException;
20 import java.util.Collections;
21
22 import org.hipparchus.exception.LocalizedCoreFormats;
23 import org.orekit.bodies.OneAxisEllipsoid;
24 import org.orekit.errors.OrekitException;
25 import org.orekit.errors.OrekitMessages;
26 import org.orekit.files.ccsds.definitions.FrameFacade;
27 import org.orekit.files.ccsds.ndm.odm.OdmHeader;
28 import org.orekit.files.ccsds.section.XmlStructureKey;
29 import org.orekit.files.ccsds.utils.FileFormat;
30 import org.orekit.files.ccsds.utils.generation.Generator;
31 import org.orekit.frames.Frame;
32 import org.orekit.propagation.Propagator;
33 import org.orekit.propagation.SpacecraftState;
34 import org.orekit.propagation.sampling.OrekitFixedStepHandler;
35 import org.orekit.time.AbsoluteDate;
36 import org.orekit.utils.TimeStampedPVCoordinates;
37
38 /**
39 * A writer for OCM files.
40 *
41 * <p> Each instance corresponds to a single Orbit Comprehensive Message.
42 * A new OCM ephemeris trajectory state history block is started by calling
43 * {@link #newBlock()}.
44 * </p>
45 *
46 * <p>
47 * This writer is intended to write only trajectory state history blocks.
48 * It does not writes physical properties, covariance data, maneuver data,
49 * perturbations parameters, orbit determination or user-defined parameters.
50 * If these blocks are needed, then {@link OcmWriter OcmWriter} must be
51 * used as it handles all OCM data blocks.
52 * </p>
53 * <p>
54 * The trajectory blocks metadata identifiers ({@code TRAJ_ID},
55 * {@code TRAJ_PREV_ID}, {@code TRAJ_NEXT_ID}) are updated automatically
56 * using {@link TrajectoryStateHistoryMetadata#incrementTrajID(String)},
57 * so users should generally only set {@link TrajectoryStateHistoryMetadata#setTrajID(String)}
58 * in the template.
59 * </p>
60 *
61 * <p>
62 * The blocks returned by this class can be used as step handlers for a {@link Propagator}.
63 * </p>
64 *
65 * <pre>{@code
66 * Propagator propagator = ...; // pre-configured propagator
67 * OCMWriter ocmWriter = ...; // pre-configured writer
68 * try (Generator out = ...; // set-up output stream
69 * StreamingOcmWriter sw = new StreamingOcmWriter(out, ocmWriter, header, metadata, template)) { // set-up streaming writer
70 *
71 * // write block 1
72 * propagator.getMultiplexer().add(step, sw.newBlock());
73 * propagator.propagate(startDate1, stopDate1);
74 *
75 * ...
76 *
77 * // write block n
78 * propagator.getMultiplexer().clear();
79 * propagator.getMultiplexer().add(step, sw.newBlock());
80 * propagator.propagate(startDateN, stopDateN);
81 *
82 * }
83 * }</pre>
84 *
85 *
86 * @author Luc Maisonobe
87 * @see OcmWriter
88 * @see EphemerisOcmWriter
89 * @since 12.0
90 */
91 public class StreamingOcmWriter implements AutoCloseable {
92
93 /** Generator for OCM output. */
94 private final Generator generator;
95
96 /** Writer for the OCM message format. */
97 private final OcmWriter writer;
98
99 /** Writer for the trajectory data block. */
100 private TrajectoryStateHistoryWriter trajectoryWriter;
101
102 /** Header. */
103 private final OdmHeader header;
104
105 /** Current metadata. */
106 private final OcmMetadata metadata;
107
108 /** Current trajectory metadata. */
109 private final TrajectoryStateHistoryMetadata trajectoryMetadata;
110
111 /** If the propagator's frame should be used. */
112 private final boolean useAttitudeFrame;
113
114 /** Indicator for writing header. */
115 private boolean headerWritePending;
116
117 /** Last Z coordinate seen. */
118 private double lastZ;
119
120 /**
121 * Construct a writer that for each segment uses the reference frame of the
122 * first state's attitude.
123 *
124 * @param generator generator for OCM output
125 * @param writer writer for the OCM message format
126 * @param header file header (may be null)
127 * @param metadata file metadata
128 * @param template template for trajectory metadata
129 * @see #StreamingOcmWriter(Generator, OcmWriter, OdmHeader, OcmMetadata, TrajectoryStateHistoryMetadata, boolean)
130 */
131 public StreamingOcmWriter(final Generator generator, final OcmWriter writer,
132 final OdmHeader header, final OcmMetadata metadata,
133 final TrajectoryStateHistoryMetadata template) {
134 this(generator, writer, header, metadata, template, true);
135 }
136
137 /**
138 * Simple constructor.
139 *
140 * @param generator generator for OCM output
141 * @param writer writer for the OCM message format
142 * @param header file header (may be null)
143 * @param metadata file metadata
144 * @param template template for trajectory metadata
145 * @param useAttitudeFrame if {@code true} then the reference frame for
146 * each segment is taken from the first state's
147 * attitude. Otherwise the {@code template}'s
148 * reference frame is used, {@link
149 * TrajectoryStateHistoryMetadata#getTrajReferenceFrame()}.
150 */
151 public StreamingOcmWriter(final Generator generator, final OcmWriter writer,
152 final OdmHeader header, final OcmMetadata metadata,
153 final TrajectoryStateHistoryMetadata template,
154 final boolean useAttitudeFrame) {
155 this.generator = generator;
156 this.writer = writer;
157 this.header = header;
158 this.metadata = metadata;
159 this.trajectoryMetadata = template.copy(header == null ? writer.getDefaultVersion() : header.getFormatVersion());
160 this.useAttitudeFrame = useAttitudeFrame;
161 this.headerWritePending = true;
162 this.lastZ = Double.NaN;
163 }
164
165 /**
166 * Create a writer for a new OCM trajectory state history block.
167 * <p> The returned writer can only write a single trajectory state history block in an OCM.
168 * This method must be called to create a writer for each trajectory state history block.
169 * @return a new OCM trajectory state history block writer, ready for use.
170 */
171 public BlockWriter newBlock() {
172 return new BlockWriter();
173 }
174
175 /** {@inheritDoc} */
176 @Override
177 public void close() throws IOException {
178 writer.writeFooter(generator);
179 }
180
181 /** A writer for a trajectory state history block of an OCM. */
182 public class BlockWriter implements OrekitFixedStepHandler {
183
184 /** Reference frame of this segment. */
185 private Frame frame;
186
187 /** Elements type. */
188 private OrbitElementsType type;
189
190 /** Number of ascending nodes crossings. */
191 private int crossings;
192
193 /** Empty constructor.
194 * <p>
195 * This constructor is not strictly necessary, but it prevents spurious
196 * javadoc warnings with JDK 18 and later.
197 * </p>
198 * @since 12.0
199 */
200 public BlockWriter() {
201 // nothing to do
202 }
203
204 /**
205 * {@inheritDoc}
206 *
207 * <p>Writes the header automatically on first segment.
208 * Sets the {@link OcmMetadataKey#START_TIME} and {@link OcmMetadataKey#STOP_TIME} in this
209 * block metadata if not already set by the user.
210 */
211 @Override
212 public void init(final SpacecraftState s0, final AbsoluteDate t, final double step) {
213 try {
214 final AbsoluteDate date = s0.getDate();
215 if (t.isBefore(date)) {
216 throw new OrekitException(OrekitMessages.NON_CHRONOLOGICALLY_SORTED_ENTRIES,
217 date, t, date.durationFrom(t));
218 }
219
220 if (headerWritePending) {
221 // we write the header and metadata only for the first segment
222 writer.writeHeader(generator, header);
223 if (generator.getFormat() == FileFormat.XML) {
224 generator.enterSection(XmlStructureKey.segment.name());
225 }
226 new OcmMetadataWriter(metadata, writer.getTimeConverter()).write(generator);
227 if (generator.getFormat() == FileFormat.XML) {
228 generator.enterSection(XmlStructureKey.data.name());
229 }
230 headerWritePending = false;
231 }
232
233 trajectoryMetadata.setTrajNextID(TrajectoryStateHistoryMetadata.incrementTrajID(trajectoryMetadata.getTrajID()));
234 trajectoryMetadata.setUseableStartTime(date);
235 trajectoryMetadata.setUseableStopTime(t);
236 if (useAttitudeFrame) {
237 frame = s0.getAttitude().getReferenceFrame();
238 trajectoryMetadata.setTrajReferenceFrame(FrameFacade.map(frame));
239 } else {
240 frame = trajectoryMetadata.getTrajReferenceFrame().asFrame();
241 }
242
243 crossings = 0;
244 type = trajectoryMetadata.getTrajType();
245
246 final OneAxisEllipsoid body = trajectoryMetadata.getTrajType() == OrbitElementsType.GEODETIC ?
247 new OneAxisEllipsoid(writer.getEquatorialRadius(),
248 writer.getFlattening(),
249 trajectoryMetadata.getTrajReferenceFrame().asFrame()) :
250 null;
251 final double mu = s0.isOrbitDefined() ? s0.getOrbit().getMu() : Double.NaN;
252 trajectoryWriter = new TrajectoryStateHistoryWriter(new TrajectoryStateHistory(trajectoryMetadata,
253 Collections.emptyList(),
254 body, mu),
255 writer.getTimeConverter());
256 trajectoryWriter.enterSection(generator);
257 trajectoryWriter.writeMetadata(generator);
258
259 } catch (IOException e) {
260 throw new OrekitException(e, LocalizedCoreFormats.SIMPLE_MESSAGE, e.getLocalizedMessage());
261 }
262 }
263
264 /** {@inheritDoc}. */
265 @Override
266 public void handleStep(final SpacecraftState currentState) {
267 try {
268 final TimeStampedPVCoordinates pv =
269 currentState.getPVCoordinates(frame);
270 if (lastZ < 0.0 && pv.getPosition().getZ() >= 0.0) {
271 // we crossed ascending node
272 ++crossings;
273 }
274 final double mu = currentState.isOrbitDefined() ? currentState.getOrbit().getMu() : Double.NaN;
275 lastZ = pv.getPosition().getZ();
276 final TrajectoryState state = new TrajectoryState(type, pv.getDate(),
277 type.toRawElements(pv, frame,
278 trajectoryWriter.getHistory().getBody(),
279 mu));
280 trajectoryWriter.writeState(generator, state, type.getUnits());
281 } catch (IOException e) {
282 throw new OrekitException(e, LocalizedCoreFormats.SIMPLE_MESSAGE, e.getLocalizedMessage());
283 }
284 }
285
286 /** {@inheritDoc}. */
287 @Override
288 public void finish(final SpacecraftState finalState) {
289 try {
290
291 trajectoryWriter.exitSection(generator);
292
293 // update the trajectory IDs
294 trajectoryMetadata.setTrajPrevID(trajectoryMetadata.getTrajID());
295 trajectoryMetadata.setTrajID(trajectoryMetadata.getTrajNextID());
296
297 if (trajectoryMetadata.getOrbRevNum() >= 0) {
298 // update the orbits revolution number
299 trajectoryMetadata.setOrbRevNum(trajectoryMetadata.getOrbRevNum() + crossings);
300 }
301
302 } catch (IOException e) {
303 throw new OrekitException(e, LocalizedCoreFormats.SIMPLE_MESSAGE, e.getLocalizedMessage());
304 }
305 }
306
307 }
308
309 }