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 }