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