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