1   /* Copyright 2016 Applied Defense Solutions (ADS)
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    * ADS 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.oem;
18  
19  import java.io.IOException;
20  import java.util.List;
21  
22  import org.orekit.errors.OrekitException;
23  import org.orekit.errors.OrekitIllegalArgumentException;
24  import org.orekit.errors.OrekitMessages;
25  import org.orekit.files.ccsds.definitions.FrameFacade;
26  import org.orekit.files.ccsds.section.Header;
27  import org.orekit.files.ccsds.utils.FileFormat;
28  import org.orekit.files.ccsds.utils.generation.Generator;
29  import org.orekit.files.ccsds.utils.generation.KvnGenerator;
30  import org.orekit.files.ccsds.utils.generation.XmlGenerator;
31  import org.orekit.files.general.EphemerisFile;
32  import org.orekit.files.general.EphemerisFile.SatelliteEphemeris;
33  import org.orekit.files.general.EphemerisFileWriter;
34  import org.orekit.utils.CartesianDerivativesFilter;
35  import org.orekit.utils.TimeStampedPVCoordinates;
36  
37  /** An {@link EphemerisFileWriter} generating {@link Oem OEM} files.
38   * @author Hank Grabowski
39   * @author Evan Ward
40   * @since 9.0
41   * @see <a href="https://public.ccsds.org/Pubs/502x0b2c1.pdf">CCSDS 502.0-B-2 Orbit Data
42   *      Messages</a>
43   * @see <a href="https://public.ccsds.org/Pubs/500x0g4.pdf">CCSDS 500.0-G-4 Navigation
44   *      Data Definitions and Conventions</a>
45   * @see StreamingOemWriter
46   */
47  public class EphemerisWriter implements EphemerisFileWriter {
48  
49      /** Underlying writer. */
50      private final OemWriter writer;
51  
52      /** Header. */
53      private final Header header;
54  
55      /** Current metadata. */
56      private final OemMetadata metadata;
57  
58      /** File format to use. */
59      private final FileFormat fileFormat;
60  
61      /** Output name for error messages. */
62      private final String outputName;
63  
64      /** Column number for aligning units. */
65      private final int unitsColumn;
66  
67      /**
68       * Constructor used to create a new OEM writer configured with the necessary parameters
69       * to successfully fill in all required fields that aren't part of a standard object.
70       * <p>
71       * If the mandatory header entries are not present (or if header is null),
72       * built-in defaults will be used
73       * </p>
74       * <p>
75       * The writer is built from the complete header and partial metadata. The template
76       * metadata is used to initialize and independent local copy, that will be updated
77       * as new segments are written (with at least the segment start and stop will change,
78       * but some other parts may change too). The {@code template} argument itself is not
79       * changed.
80       * </p>
81       * @param writer underlying writer
82       * @param header file header (may be null)
83       * @param template template for metadata
84       * @param fileFormat file format to use
85       * @param outputName output name for error messages
86       * @param unitsColumn columns number for aligning units (if negative or zero, units are not output)
87       * @since 11.0
88       */
89      public EphemerisWriter(final OemWriter writer,
90                             final Header header, final OemMetadata template,
91                             final FileFormat fileFormat, final String outputName,
92                             final int unitsColumn) {
93          this.writer      = writer;
94          this.header      = header;
95          this.metadata    = template.copy(header == null ? writer.getDefaultVersion() : header.getFormatVersion());
96          this.fileFormat  = fileFormat;
97          this.outputName  = outputName;
98          this.unitsColumn = unitsColumn;
99      }
100 
101     /** {@inheritDoc}
102      * <p>
103      * As {@code EphemerisFile.SatelliteEphemeris} does not have all the entries
104      * from {@link OemMetadata}, the only values that will be extracted from the
105      * {@code ephemerisFile} will be the start time, stop time, reference frame, interpolation
106      * method and interpolation degree. The missing values (like object name, local spacecraft
107      * body frame...) will be inherited from the template  metadata set at writer
108      * {@link #EphemerisWriter(OemWriter, Header, OemMetadata, FileFormat, String, int) construction}.
109      * </p>
110      */
111     @Override
112     public <C extends TimeStampedPVCoordinates, S extends EphemerisFile.EphemerisSegment<C>>
113         void write(final Appendable appendable, final EphemerisFile<C, S> ephemerisFile)
114         throws IOException {
115 
116         if (appendable == null) {
117             throw new OrekitIllegalArgumentException(OrekitMessages.NULL_ARGUMENT, "writer");
118         }
119 
120         if (ephemerisFile == null) {
121             return;
122         }
123 
124         final SatelliteEphemeris<C, S> satEphem = ephemerisFile.getSatellites().get(metadata.getObjectID());
125         if (satEphem == null) {
126             throw new OrekitIllegalArgumentException(OrekitMessages.VALUE_NOT_FOUND,
127                                                      metadata.getObjectID(), "ephemerisFile");
128         }
129 
130         // Get attitude ephemeris segments to output.
131         final List<S> segments = satEphem.getSegments();
132         if (segments.isEmpty()) {
133             // No data -> No output
134             return;
135         }
136 
137         try (Generator generator = fileFormat == FileFormat.KVN ?
138                                    new KvnGenerator(appendable, OemWriter.KVN_PADDING_WIDTH, outputName, unitsColumn) :
139                                    new XmlGenerator(appendable, XmlGenerator.DEFAULT_INDENT, outputName, unitsColumn > 0)) {
140 
141             writer.writeHeader(generator, header);
142 
143             // Loop on segments
144             for (final S segment : segments) {
145                 writeSegment(generator, segment);
146             }
147 
148             writer.writeFooter(generator);
149 
150         }
151 
152     }
153 
154     /** Write one segment.
155      * @param generator generator to use for producing output
156      * @param segment segment to write
157      * @param <C> type of the Cartesian coordinates
158      * @param <S> type of the segment
159      * @throws IOException if any buffer writing operations fails
160      */
161     public <C extends TimeStampedPVCoordinates, S extends EphemerisFile.EphemerisSegment<C>>
162         void writeSegment(final Generator generator, final S segment) throws IOException {
163 
164         // override template metadata with segment values
165         if (segment instanceof OemSegment) {
166             final OemSegment oemSegment = (OemSegment) segment;
167             metadata.setReferenceFrame(oemSegment.getMetadata().getReferenceFrame());
168         } else {
169             metadata.setReferenceFrame(FrameFacade.map(segment.getFrame()));
170         }
171         metadata.setStartTime(segment.getStart());
172         metadata.setStopTime(segment.getStop());
173         metadata.setInterpolationDegree(segment.getInterpolationSamples() - 1);
174         writer.writeMetadata(generator, metadata);
175 
176         if (segment instanceof OemSegment) {
177             // write data comments
178             generator.writeComments(((OemSegment) segment).getData().getComments());
179         }
180 
181         // Loop on orbit data
182         final CartesianDerivativesFilter filter = segment.getAvailableDerivatives();
183         if (filter == CartesianDerivativesFilter.USE_P) {
184             throw new OrekitException(OrekitMessages.MISSING_VELOCITY);
185         }
186         final boolean useAcceleration = filter.equals(CartesianDerivativesFilter.USE_PVA);
187         for (final TimeStampedPVCoordinates coordinates : segment.getCoordinates()) {
188             writer.writeOrbitEphemerisLine(generator, metadata, coordinates, useAcceleration);
189         }
190 
191         if (segment instanceof OemSegment) {
192             // output covariance data
193             writer.writeCovariances(generator, metadata, ((OemSegment) segment).getCovarianceMatrices());
194         }
195 
196     }
197 
198 }