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 }