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.ndm.odm.OdmHeader;
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.AccurateFormatter;
35  import org.orekit.utils.CartesianDerivativesFilter;
36  import org.orekit.utils.Formatter;
37  import org.orekit.utils.TimeStampedPVCoordinates;
38  
39  /** An {@link EphemerisFileWriter} generating {@link Oem OEM} files.
40   * @author Hank Grabowski
41   * @author Evan Ward
42   * @since 9.0
43   * @see <a href="https://public.ccsds.org/Pubs/502x0b2c1.pdf">CCSDS 502.0-B-2 Orbit Data
44   *      Messages</a>
45   * @see <a href="https://public.ccsds.org/Pubs/500x0g4.pdf">CCSDS 500.0-G-4 Navigation
46   *      Data Definitions and Conventions</a>
47   * @see StreamingOemWriter
48   */
49  public class EphemerisOemWriter implements EphemerisFileWriter {
50  
51      /** Underlying writer. */
52      private final OemWriter writer;
53  
54      /** Header. */
55      private final OdmHeader header;
56  
57      /** Current metadata. */
58      private final OemMetadata metadata;
59  
60      /** File format to use. */
61      private final FileFormat fileFormat;
62  
63      /** Output name for error messages. */
64      private final String outputName;
65  
66      /** Maximum offset for relative dates.
67       * @since 12.0
68       */
69      private final double maxRelativeOffset;
70  
71      /** Column number for aligning units. */
72      private final int unitsColumn;
73  
74      /** Used to format dates and doubles to string. */
75      private final Formatter formatter;
76  
77      /**
78       * Constructor used to create a new OEM writer configured with the necessary parameters
79       * to successfully fill in all required fields that aren't part of a standard object.
80       * <p>
81       * If the mandatory header entries are not present (or if header is null),
82       * built-in defaults will be used
83       * </p>
84       * <p>
85       * The writer is built from the complete header and partial metadata. The template
86       * metadata is used to initialize and independent local copy, that will be updated
87       * as new segments are written (with at least the segment start and stop will change,
88       * but some other parts may change too). The {@code template} argument itself is not
89       * changed.
90       * </p>
91       * @param writer underlying writer
92       * @param header file header (may be null)
93       * @param template template for metadata
94       * @param fileFormat file format to use
95       * @param outputName output name for error messages
96       * @param maxRelativeOffset maximum offset in seconds to use relative dates
97       * (if a date is too far from reference, it will be displayed as calendar elements)
98       * @param formatter used to format doubles and dates to string
99       * @param unitsColumn columns number for aligning units (if negative or zero, units are not output)
100      * @since 13.0
101      */
102     public EphemerisOemWriter(final OemWriter writer,
103                               final OdmHeader header, final OemMetadata template,
104                               final FileFormat fileFormat, final String outputName,
105                               final double maxRelativeOffset, final int unitsColumn, final Formatter formatter) {
106         this.writer            = writer;
107         this.header            = header;
108         this.metadata          = template.copy(header == null ? writer.getDefaultVersion() : header.getFormatVersion());
109         this.fileFormat        = fileFormat;
110         this.outputName        = outputName;
111         this.maxRelativeOffset = maxRelativeOffset;
112         this.unitsColumn       = unitsColumn;
113         this.formatter = formatter;
114     }
115 
116     /**
117      * Constructor used to create a new OEM writer configured with the necessary parameters
118      * to successfully fill in all required fields that aren't part of a standard object.
119      * <p>
120      * If the mandatory header entries are not present (or if header is null),
121      * built-in defaults will be used
122      * </p>
123      * <p>
124      * The writer is built from the complete header and partial metadata. The template
125      * metadata is used to initialize and independent local copy, that will be updated
126      * as new segments are written (with at least the segment start and stop will change,
127      * but some other parts may change too). The {@code template} argument itself is not
128      * changed.
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 EphemerisOemWriter(final OemWriter writer,
141                               final OdmHeader header, final OemMetadata 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 EphemerisFile.SatelliteEphemeris} does not have all the entries
150      * from {@link OemMetadata}, 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...) will be inherited from the template  metadata set at writer
154      * {@link #EphemerisOemWriter(OemWriter, OdmHeader, OemMetadata, FileFormat, String, double, int) construction}.
155      * </p>
156      */
157     @Override
158     public <C extends TimeStampedPVCoordinates, S extends EphemerisFile.EphemerisSegment<C>>
159         void write(final Appendable appendable, final EphemerisFile<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 SatelliteEphemeris<C, S> satEphem = ephemerisFile.getSatellites().get(metadata.getObjectID());
171         if (satEphem == null) {
172             throw new OrekitIllegalArgumentException(OrekitMessages.VALUE_NOT_FOUND,
173                                                      metadata.getObjectID(), "ephemerisFile");
174         }
175 
176         // Get ephemeris segments to output.
177         final List<S> segments = satEphem.getSegments();
178         if (segments.isEmpty()) {
179             // No data -> No output
180             return;
181         }
182 
183         try (Generator generator = fileFormat == FileFormat.KVN ?
184                                    new KvnGenerator(appendable, OemWriter.KVN_PADDING_WIDTH, outputName,
185                                                     maxRelativeOffset, unitsColumn, formatter) :
186                                    new XmlGenerator(appendable, XmlGenerator.DEFAULT_INDENT, outputName,
187                                                     maxRelativeOffset, unitsColumn > 0, null, formatter)) {
188 
189             writer.writeHeader(generator, header);
190 
191             // Loop on segments
192             for (final S segment : segments) {
193                 writeSegment(generator, segment);
194             }
195 
196             writer.writeFooter(generator);
197 
198         }
199 
200     }
201 
202     /** Write one segment.
203      * @param generator generator to use for producing output
204      * @param segment segment to write
205      * @param <C> type of the Cartesian coordinates
206      * @param <S> type of the segment
207      * @throws IOException if any buffer writing operations fails
208      */
209     public <C extends TimeStampedPVCoordinates, S extends EphemerisFile.EphemerisSegment<C>>
210         void writeSegment(final Generator generator, final S segment) throws IOException {
211 
212         // override template metadata with segment values
213         if (segment instanceof OemSegment) {
214             final OemSegment oemSegment = (OemSegment) segment;
215             metadata.setReferenceFrame(oemSegment.getMetadata().getReferenceFrame());
216         } else {
217             metadata.setReferenceFrame(FrameFacade.map(segment.getFrame()));
218         }
219         metadata.setStartTime(segment.getStart());
220         metadata.setStopTime(segment.getStop());
221         metadata.setInterpolationDegree(segment.getInterpolationSamples() - 1);
222         writer.writeMetadata(generator, metadata);
223 
224         // we enter data section
225         writer.startData(generator);
226 
227         if (segment instanceof OemSegment) {
228             // write data comments
229             generator.writeComments(((OemSegment) segment).getData().getComments());
230         }
231 
232         // Loop on orbit data
233         final CartesianDerivativesFilter filter = segment.getAvailableDerivatives();
234         if (filter == CartesianDerivativesFilter.USE_P) {
235             throw new OrekitException(OrekitMessages.MISSING_VELOCITY);
236         }
237         final boolean useAcceleration = filter.equals(CartesianDerivativesFilter.USE_PVA);
238         for (final TimeStampedPVCoordinates coordinates : segment.getCoordinates()) {
239             writer.writeOrbitEphemerisLine(generator, metadata, coordinates, useAcceleration);
240         }
241 
242         if (segment instanceof OemSegment) {
243             // output covariance data
244             writer.writeCovariances(generator, metadata, ((OemSegment) segment).getCovarianceMatrices());
245         }
246 
247         // we exit data section
248         writer.endData(generator);
249 
250     }
251 
252 }