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.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 EphemerisOemWriter implements EphemerisFileWriter {
48  
49      /** Underlying writer. */
50      private final OemWriter writer;
51  
52      /** Header. */
53      private final OdmHeader 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      /** Maximum offset for relative dates.
65       * @since 12.0
66       */
67      private final double maxRelativeOffset;
68  
69      /** Column number for aligning units. */
70      private final int unitsColumn;
71  
72      /**
73       * Constructor used to create a new OEM writer configured with the necessary parameters
74       * to successfully fill in all required fields that aren't part of a standard object.
75       * <p>
76       * If the mandatory header entries are not present (or if header is null),
77       * built-in defaults will be used
78       * </p>
79       * <p>
80       * The writer is built from the complete header and partial metadata. The template
81       * metadata is used to initialize and independent local copy, that will be updated
82       * as new segments are written (with at least the segment start and stop will change,
83       * but some other parts may change too). The {@code template} argument itself is not
84       * changed.
85       * </p>
86       * @param writer underlying writer
87       * @param header file header (may be null)
88       * @param template template for metadata
89       * @param fileFormat file format to use
90       * @param outputName output name for error messages
91       * @param maxRelativeOffset maximum offset in seconds to use relative dates
92       * (if a date is too far from reference, it will be displayed as calendar elements)
93       * @param unitsColumn columns number for aligning units (if negative or zero, units are not output)
94       * @since 12.0
95       */
96      public EphemerisOemWriter(final OemWriter writer,
97                                final OdmHeader header, final OemMetadata template,
98                                final FileFormat fileFormat, final String outputName,
99                                final double maxRelativeOffset, final int unitsColumn) {
100         this.writer            = writer;
101         this.header            = header;
102         this.metadata          = template.copy(header == null ? writer.getDefaultVersion() : header.getFormatVersion());
103         this.fileFormat        = fileFormat;
104         this.outputName        = outputName;
105         this.maxRelativeOffset = maxRelativeOffset;
106         this.unitsColumn       = unitsColumn;
107     }
108 
109     /** {@inheritDoc}
110      * <p>
111      * As {@code EphemerisFile.SatelliteEphemeris} does not have all the entries
112      * from {@link OemMetadata}, the only values that will be extracted from the
113      * {@code ephemerisFile} will be the start time, stop time, reference frame, interpolation
114      * method and interpolation degree. The missing values (like object name, local spacecraft
115      * body frame...) will be inherited from the template  metadata set at writer
116      * {@link #EphemerisOemWriter(OemWriter, OdmHeader, OemMetadata, FileFormat, String, double, int) construction}.
117      * </p>
118      */
119     @Override
120     public <C extends TimeStampedPVCoordinates, S extends EphemerisFile.EphemerisSegment<C>>
121         void write(final Appendable appendable, final EphemerisFile<C, S> ephemerisFile)
122         throws IOException {
123 
124         if (appendable == null) {
125             throw new OrekitIllegalArgumentException(OrekitMessages.NULL_ARGUMENT, "writer");
126         }
127 
128         if (ephemerisFile == null) {
129             return;
130         }
131 
132         final SatelliteEphemeris<C, S> satEphem = ephemerisFile.getSatellites().get(metadata.getObjectID());
133         if (satEphem == null) {
134             throw new OrekitIllegalArgumentException(OrekitMessages.VALUE_NOT_FOUND,
135                                                      metadata.getObjectID(), "ephemerisFile");
136         }
137 
138         // Get ephemeris segments to output.
139         final List<S> segments = satEphem.getSegments();
140         if (segments.isEmpty()) {
141             // No data -> No output
142             return;
143         }
144 
145         try (Generator generator = fileFormat == FileFormat.KVN ?
146                                    new KvnGenerator(appendable, OemWriter.KVN_PADDING_WIDTH, outputName,
147                                                     maxRelativeOffset, unitsColumn) :
148                                    new XmlGenerator(appendable, XmlGenerator.DEFAULT_INDENT, outputName,
149                                                     maxRelativeOffset, unitsColumn > 0, null)) {
150 
151             writer.writeHeader(generator, header);
152 
153             // Loop on segments
154             for (final S segment : segments) {
155                 writeSegment(generator, segment);
156             }
157 
158             writer.writeFooter(generator);
159 
160         }
161 
162     }
163 
164     /** Write one segment.
165      * @param generator generator to use for producing output
166      * @param segment segment to write
167      * @param <C> type of the Cartesian coordinates
168      * @param <S> type of the segment
169      * @throws IOException if any buffer writing operations fails
170      */
171     public <C extends TimeStampedPVCoordinates, S extends EphemerisFile.EphemerisSegment<C>>
172         void writeSegment(final Generator generator, final S segment) throws IOException {
173 
174         // override template metadata with segment values
175         if (segment instanceof OemSegment) {
176             final OemSegment oemSegment = (OemSegment) segment;
177             metadata.setReferenceFrame(oemSegment.getMetadata().getReferenceFrame());
178         } else {
179             metadata.setReferenceFrame(FrameFacade.map(segment.getFrame()));
180         }
181         metadata.setStartTime(segment.getStart());
182         metadata.setStopTime(segment.getStop());
183         metadata.setInterpolationDegree(segment.getInterpolationSamples() - 1);
184         writer.writeMetadata(generator, metadata);
185 
186         // we enter data section
187         writer.startData(generator);
188 
189         if (segment instanceof OemSegment) {
190             // write data comments
191             generator.writeComments(((OemSegment) segment).getData().getComments());
192         }
193 
194         // Loop on orbit data
195         final CartesianDerivativesFilter filter = segment.getAvailableDerivatives();
196         if (filter == CartesianDerivativesFilter.USE_P) {
197             throw new OrekitException(OrekitMessages.MISSING_VELOCITY);
198         }
199         final boolean useAcceleration = filter.equals(CartesianDerivativesFilter.USE_PVA);
200         for (final TimeStampedPVCoordinates coordinates : segment.getCoordinates()) {
201             writer.writeOrbitEphemerisLine(generator, metadata, coordinates, useAcceleration);
202         }
203 
204         if (segment instanceof OemSegment) {
205             // output covariance data
206             writer.writeCovariances(generator, metadata, ((OemSegment) segment).getCovarianceMatrices());
207         }
208 
209         // we exit data section
210         writer.endData(generator);
211 
212     }
213 
214 }