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
19 import java.io.IOException;
20 import java.util.List;
21
22 import org.orekit.errors.OrekitIllegalArgumentException;
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.utils.FileFormat;
27 import org.orekit.files.ccsds.utils.generation.Generator;
28 import org.orekit.files.ccsds.utils.generation.KvnGenerator;
29 import org.orekit.files.ccsds.utils.generation.XmlGenerator;
30 import org.orekit.files.general.AttitudeEphemerisFile;
31 import org.orekit.files.general.AttitudeEphemerisFile.SatelliteAttitudeEphemeris;
32 import org.orekit.files.general.AttitudeEphemerisFileWriter;
33 import org.orekit.utils.AccurateFormatter;
34 import org.orekit.utils.Formatter;
35 import org.orekit.utils.TimeStampedAngularCoordinates;
36
37 /** An {@link AttitudeEphemerisFileWriter} generating {@link Aem AEM} files.
38 * @author Bryan Cazabonne
39 * @since 11.0
40 */
41 public class AttitudeWriter implements AttitudeEphemerisFileWriter {
42
43 /** Underlying writer. */
44 private final AemWriter writer;
45
46 /** Header. */
47 private final AdmHeader header;
48
49 /** Current metadata. */
50 private final AemMetadata metadata;
51
52 /** File format to use. */
53 private final FileFormat fileFormat;
54
55 /** Output name for error messages. */
56 private final String outputName;
57
58 /** Maximum offset for relative dates.
59 * @since 12.0
60 */
61 private final double maxRelativeOffset;
62
63 /** Column number for aligning units. */
64 private final int unitsColumn;
65
66 /** Used to format dates and doubles to string. */
67 private final Formatter formatter;
68
69 /**
70 * Constructor used to create a new AEM writer configured with the necessary parameters
71 * to successfully fill in all required fields that aren't part of a standard object.
72 * <p>
73 * If the mandatory header entries are not present (or if header is null),
74 * built-in defaults will be used
75 * </p>
76 * <p>
77 * The writer is built from the complete header and partial metadata. The template
78 * metadata is used to initialize and independent local copy, that will be updated
79 * as new segments are written (with at least the segment start and stop will change,
80 * but some other parts may change too). The {@code template} argument itself is not
81 * changed.
82 * </p>
83 * <p>
84 * Calling this constructor directly is not recommended. Users should rather use
85 * {@link org.orekit.files.ccsds.ndm.WriterBuilder#buildAemWriter()}.
86 * </p>
87 * @param writer underlying writer
88 * @param header file header (may be null)
89 * @param template template for metadata
90 * @param fileFormat file format to use
91 * @param outputName output name for error messages
92 * @param maxRelativeOffset maximum offset in seconds to use relative dates
93 * (if a date is too far from reference, it will be displayed as calendar elements)
94 * @param unitsColumn columns number for aligning units (if negative or zero, units are not output)
95 * @param formatter how to format date and double to string.
96 * @since 13.0
97 */
98 public AttitudeWriter(final AemWriter writer,
99 final AdmHeader header, final AemMetadata template,
100 final FileFormat fileFormat, final String outputName,
101 final double maxRelativeOffset, final int unitsColumn, final Formatter formatter) {
102 this.writer = writer;
103 this.header = header;
104 this.metadata = template.copy(header == null ? writer.getDefaultVersion() : header.getFormatVersion());
105 this.fileFormat = fileFormat;
106 this.outputName = outputName;
107 this.maxRelativeOffset = maxRelativeOffset;
108 this.unitsColumn = unitsColumn;
109 this.formatter = formatter;
110 }
111
112 /**
113 * Constructor used to create a new AEM writer configured with the necessary parameters
114 * to successfully fill in all required fields that aren't part of a standard object.
115 * <p>
116 * If the mandatory header entries are not present (or if header is null),
117 * built-in defaults will be used
118 * </p>
119 * <p>
120 * The writer is built from the complete header and partial metadata. The template
121 * metadata is used to initialize and independent local copy, that will be updated
122 * as new segments are written (with at least the segment start and stop will change,
123 * but some other parts may change too). The {@code template} argument itself is not
124 * changed.
125 * </p>
126 * <p>
127 * Calling this constructor directly is not recommended. Users should rather use
128 * {@link org.orekit.files.ccsds.ndm.WriterBuilder#buildAemWriter()}.
129 * </p>
130 * @param writer underlying writer
131 * @param header file header (may be null)
132 * @param template template for metadata
133 * @param fileFormat file format to use
134 * @param outputName output name for error messages
135 * @param maxRelativeOffset maximum offset in seconds to use relative dates
136 * (if a date is too far from reference, it will be displayed as calendar elements)
137 * @param unitsColumn columns number for aligning units (if negative or zero, units are not output)
138 * @since 12.0
139 */
140 public AttitudeWriter(final AemWriter writer,
141 final AdmHeader header, final AemMetadata template,
142 final FileFormat fileFormat, final String outputName,
143 final double maxRelativeOffset, final int unitsColumn) {
144 this(writer, header, template, fileFormat, outputName, maxRelativeOffset, unitsColumn, new AccurateFormatter());
145 }
146
147 /** {@inheritDoc}
148 * <p>
149 * As {@code AttitudeEphemerisFile.SatelliteAttitudeEphemeris} does not have all the entries
150 * from {@link AemMetadata}, the only values that will be extracted from the
151 * {@code ephemerisFile} will be the start time, stop time, reference frame, interpolation
152 * method and interpolation degree. The missing values (like object name, local spacecraft
153 * body frame, attitude type...) will be inherited from the template metadata set at writer
154 * {@link #AttitudeWriter(AemWriter, AdmHeader, AemMetadata, FileFormat, String, double, int) construction}.
155 * </p>
156 */
157 @Override
158 public <C extends TimeStampedAngularCoordinates, S extends AttitudeEphemerisFile.AttitudeEphemerisSegment<C>>
159 void write(final Appendable appendable, final AttitudeEphemerisFile<C, S> ephemerisFile)
160 throws IOException {
161
162 if (appendable == null) {
163 throw new OrekitIllegalArgumentException(OrekitMessages.NULL_ARGUMENT, "writer");
164 }
165
166 if (ephemerisFile == null) {
167 return;
168 }
169
170 final SatelliteAttitudeEphemeris<C, S> satEphem =
171 ephemerisFile.getSatellites().get(metadata.getObjectID());
172 if (satEphem == null) {
173 throw new OrekitIllegalArgumentException(OrekitMessages.VALUE_NOT_FOUND,
174 metadata.getObjectID(), "ephemerisFile");
175 }
176
177 // Get attitude ephemeris segments to output.
178 final List<S> segments = satEphem.getSegments();
179 if (segments.isEmpty()) {
180 // No data -> No output
181 return;
182 }
183
184 try (Generator generator = fileFormat == FileFormat.KVN ?
185 new KvnGenerator(appendable, AemWriter.KVN_PADDING_WIDTH, outputName,
186 maxRelativeOffset, unitsColumn, formatter) :
187 new XmlGenerator(appendable, XmlGenerator.DEFAULT_INDENT, outputName,
188 maxRelativeOffset, unitsColumn > 0, null, formatter)) {
189
190 writer.writeHeader(generator, header);
191
192 // Loop on segments
193 for (final S segment : segments) {
194 writeSegment(generator, segment);
195 }
196
197 writer.writeFooter(generator);
198
199 }
200
201 }
202
203 /** Write one segment.
204 * @param generator generator to use for producing output
205 * @param segment segment to write
206 * @param <C> type of the angular coordinates
207 * @param <S> type of the segment
208 * @throws IOException if any buffer writing operations fails
209 */
210 private <C extends TimeStampedAngularCoordinates, S extends AttitudeEphemerisFile.AttitudeEphemerisSegment<C>>
211 void writeSegment(final Generator generator, final S segment) throws IOException {
212
213 // override template metadata with segment values
214 metadata.setStartTime(segment.getStart());
215 metadata.setStopTime(segment.getStop());
216 if (metadata.getEndpoints().getFrameA() == null ||
217 metadata.getEndpoints().getFrameA().asSpacecraftBodyFrame() == null) {
218 // the external frame must be frame A
219 metadata.getEndpoints().setFrameA(FrameFacade.map(segment.getReferenceFrame()));
220 } else {
221 // the external frame must be frame B
222 metadata.getEndpoints().setFrameB(FrameFacade.map(segment.getReferenceFrame()));
223 }
224 metadata.setInterpolationMethod(segment.getInterpolationMethod());
225 metadata.setInterpolationDegree(segment.getInterpolationSamples() - 1);
226 metadata.validate(header == null ? writer.getDefaultVersion() : header.getFormatVersion());
227 writer.writeMetadata(generator,
228 header == null ? writer.getDefaultVersion() : header.getFormatVersion(),
229 metadata);
230
231 // Loop on attitude data
232 writer.startAttitudeBlock(generator);
233 if (segment instanceof AemSegment) {
234 generator.writeComments(((AemSegment) segment).getData().getComments());
235 }
236 for (final TimeStampedAngularCoordinates coordinates : segment.getAngularCoordinates()) {
237 writer.writeAttitudeEphemerisLine(generator,
238 header == null ? writer.getDefaultVersion() : header.getFormatVersion(),
239 metadata, coordinates);
240 }
241 writer.endAttitudeBlock(generator);
242
243 }
244
245 }