1 /* Copyright 2002-2026 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
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.adm.AdmHeader;
26 import org.orekit.files.ccsds.section.Header;
27 import org.orekit.files.ccsds.utils.generation.Generator;
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
33 /**
34 * A writer for AEM files.
35 *
36 * <p> Each instance corresponds to a single AEM file.
37 *
38 * <p> This class can be used as a step handler for a {@link Propagator}.
39 *
40 * <pre>{@code
41 * Propagator propagator = ...; // pre-configured propagator
42 * AEMWriter aemWriter = ...; // pre-configured writer
43 * try (Generator out = ...; // set-up output stream
44 * StreamingAemWriter sw = new StreamingAemWriter(out, aemWriter)) { // set-up streaming writer
45 *
46 * // write segment 1
47 * propagator.getMultiplexer().add(step, sw.newSegment());
48 * propagator.propagate(startDate1, stopDate1);
49 *
50 * ...
51 *
52 * // write segment n
53 * propagator.getMultiplexer().clear();
54 * propagator.getMultiplexer().add(step, sw.newSegment());
55 * propagator.propagate(startDateN, stopDateN);
56 *
57 * }
58 * }</pre>
59 * @author Bryan Cazabonne
60 * @author Luc Maisonobe
61 * @see <a href="https://public.ccsds.org/Pubs/504x0b1c1.pdf">CCSDS 504.0-B-1 Attitude Data Messages</a>
62 * @see AemWriter
63 * @since 10.2
64 */
65 public class StreamingAemWriter implements AutoCloseable {
66
67 /** Generator for AEM output. */
68 private final Generator generator;
69
70 /** Writer for the AEM message format. */
71 private final AemWriter writer;
72
73 /** Header. */
74 private final AdmHeader header;
75
76 /** Current metadata. */
77 private final AemMetadata metadata;
78
79 /** Indicator for writing header. */
80 private boolean headerWritePending;
81
82 /** Simple constructor.
83 * @param generator generator for AEM output
84 * @param writer writer for the AEM message format
85 * @param header file header (may be null)
86 * @param template template for metadata
87 * @since 11.0
88 */
89 public StreamingAemWriter(final Generator generator, final AemWriter writer,
90 final AdmHeader header, final AemMetadata template) {
91 this.generator = generator;
92 this.writer = writer;
93 this.header = header;
94 this.metadata = template.copy(header == null ? writer.getDefaultVersion() : header.getFormatVersion());
95 this.headerWritePending = true;
96 }
97
98 /**
99 * Create a writer for a new AEM attitude ephemeris segment.
100 * <p> The returned writer can only write a single attitude ephemeris segment in an AEM.
101 * This method must be called to create a writer for each attitude ephemeris segment.
102 * @return a new AEM segment writer, ready for use.
103 */
104 public SegmentWriter newSegment() {
105 return new SegmentWriter();
106 }
107
108 /** {@inheritDoc} */
109 @Override
110 public void close() throws IOException {
111 writer.writeFooter(generator);
112 }
113
114 /** A writer for a segment of an AEM. */
115 public class SegmentWriter implements OrekitFixedStepHandler {
116
117 /** Empty constructor.
118 * <p>
119 * This constructor is not strictly necessary, but it prevents spurious
120 * javadoc warnings with JDK 18 and later.
121 * </p>
122 * @since 12.0
123 */
124 public SegmentWriter() {
125 // nothing to do
126 }
127
128 /**
129 * {@inheritDoc}
130 *
131 * <p> Sets the {@link AemMetadataKey#START_TIME} and {@link AemMetadataKey#STOP_TIME} in this
132 * segment's metadata if not already set by the user. Then calls
133 * {@link org.orekit.files.ccsds.utils.generation.AbstractMessageWriter#writeHeader(Generator, Header)
134 * writeHeader} if it is the first segment) and {@link AemWriter#writeMetadata(Generator, double, AemMetadata)} to start the segment.
135 */
136 @Override
137 public void init(final SpacecraftState s0, final AbsoluteDate t, final double step) {
138 try {
139 final AbsoluteDate date = s0.getDate();
140 if (t.isBefore(date)) {
141 throw new OrekitException(OrekitMessages.NON_CHRONOLOGICALLY_SORTED_ENTRIES,
142 date, t, date.durationFrom(t));
143 }
144
145 if (headerWritePending) {
146 // we write the header only for the first segment
147 writer.writeHeader(generator, header);
148 headerWritePending = false;
149 }
150
151 metadata.setStartTime(date);
152 metadata.setUseableStartTime(null);
153 metadata.setUseableStopTime(null);
154 metadata.setStopTime(t);
155 if (metadata.getEndpoints().getFrameA() == null ||
156 metadata.getEndpoints().getFrameA().asSpacecraftBodyFrame().isEmpty()) {
157 // the external frame must be frame A
158 metadata.getEndpoints().setFrameA(FrameFacade.map(s0.getAttitude().getReferenceFrame()));
159 } else {
160 // the external frame must be frame B
161 metadata.getEndpoints().setFrameB(FrameFacade.map(s0.getAttitude().getReferenceFrame()));
162 }
163 writer.writeMetadata(generator,
164 header == null ? writer.getDefaultVersion() : header.getFormatVersion(),
165 metadata);
166 writer.startAttitudeBlock(generator);
167 } catch (IOException e) {
168 throw new OrekitException(e, LocalizedCoreFormats.SIMPLE_MESSAGE, e.getLocalizedMessage());
169 }
170 }
171
172 /** {@inheritDoc}. */
173 @Override
174 public void handleStep(final SpacecraftState currentState) {
175 try {
176 writer.writeAttitudeEphemerisLine(generator,
177 header == null ? writer.getDefaultVersion() : header.getFormatVersion(),
178 metadata, currentState.getAttitude().getOrientation());
179 } catch (IOException e) {
180 throw new OrekitException(e, LocalizedCoreFormats.SIMPLE_MESSAGE, e.getLocalizedMessage());
181 }
182 }
183
184 /** {@inheritDoc}. */
185 @Override
186 public void finish(final SpacecraftState finalState) {
187 try {
188 writer.endAttitudeBlock(generator);
189 } catch (IOException e) {
190 throw new OrekitException(e, LocalizedCoreFormats.SIMPLE_MESSAGE, e.getLocalizedMessage());
191 }
192 }
193
194 }
195
196 }