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 }