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.ocm;
18
19 import java.io.IOException;
20 import java.util.ArrayList;
21 import java.util.List;
22
23 import org.orekit.bodies.OneAxisEllipsoid;
24 import org.orekit.errors.OrekitIllegalArgumentException;
25 import org.orekit.errors.OrekitMessages;
26 import org.orekit.files.ccsds.ndm.odm.OdmHeader;
27 import org.orekit.files.ccsds.section.XmlStructureKey;
28 import org.orekit.files.ccsds.utils.FileFormat;
29 import org.orekit.files.ccsds.utils.generation.Generator;
30 import org.orekit.files.ccsds.utils.generation.KvnGenerator;
31 import org.orekit.files.ccsds.utils.generation.XmlGenerator;
32 import org.orekit.files.general.EphemerisFile;
33 import org.orekit.files.general.EphemerisFile.SatelliteEphemeris;
34 import org.orekit.files.general.EphemerisFileWriter;
35 import org.orekit.frames.Frame;
36 import org.orekit.utils.AccurateFormatter;
37 import org.orekit.utils.Formatter;
38 import org.orekit.utils.TimeStampedPVCoordinates;
39
40 /** An {@link EphemerisFileWriter} generating {@link Ocm OCM} files.
41 * <p>
42 * This writer is intended to write only trajectory state history blocks.
43 * It does not writes physical properties, covariance data, maneuver data,
44 * perturbations parameters, orbit determination or user-defined parameters.
45 * If these blocks are needed, then {@link OcmWriter OcmWriter} must be
46 * used as it handles all OCM data blocks.
47 * </p>
48 * <p>
49 * The trajectory blocks metadata identifiers ({@code TRAJ_ID},
50 * {@code TRAJ_PREV_ID}, {@code TRAJ_NEXT_ID}) are updated automatically
51 * using {@link TrajectoryStateHistoryMetadata#incrementTrajID(String)},
52 * so users should generally only set {@link TrajectoryStateHistoryMetadata#setTrajID(String)}
53 * in the template.
54 * </p>
55 * @author Luc Maisonobe
56 * @since 12.0
57 * @see OcmWriter
58 * @see StreamingOcmWriter
59 */
60 public class EphemerisOcmWriter implements EphemerisFileWriter {
61
62 /** Underlying writer. */
63 private final OcmWriter writer;
64
65 /** Header. */
66 private final OdmHeader header;
67
68 /** File metadata. */
69 private final OcmMetadata metadata;
70
71 /** Current trajectory metadata. */
72 private final TrajectoryStateHistoryMetadata trajectoryMetadata;
73
74 /** File format to use. */
75 private final FileFormat fileFormat;
76
77 /** Output name for error messages. */
78 private final String outputName;
79
80 /** Column number for aligning units. */
81 private final int unitsColumn;
82
83 /** Maximum offset for relative dates. */
84 private final double maxRelativeOffset;
85
86 /** Used to format dates and doubles to string. */
87 private final Formatter formatter;
88
89 /** Central body.
90 * @since 12.0
91 */
92 private final OneAxisEllipsoid body;
93
94 /**
95 * Constructor used to create a new OCM writer configured with the necessary parameters
96 * to successfully fill in all required fields that aren't part of a standard object.
97 * <p>
98 * If the mandatory header entries are not present (or if header is null),
99 * built-in defaults will be used
100 * </p>
101 * <p>
102 * The writer is built from the complete header and partial metadata. The template
103 * metadata is used to initialize and independent local copy, that will be updated
104 * as new segments are written (with at least the segment start and stop will change,
105 * but some other parts may change too). The {@code template} argument itself is not
106 * changed.
107 * </p>
108 * @param writer underlying writer
109 * @param header file header (may be null)
110 * @param metadata file metadata
111 * @param template template for trajectory metadata
112 * @param fileFormat file format to use
113 * @param outputName output name for error messages
114 * @param maxRelativeOffset maximum offset in seconds to use relative dates
115 * @param formatter used to format date and double to string.
116 * (if a date is too far from reference, it will be displayed as calendar elements)
117 * @param unitsColumn columns number for aligning units (if negative or zero, units are not output)
118 */
119 public EphemerisOcmWriter(final OcmWriter writer,
120 final OdmHeader header, final OcmMetadata metadata,
121 final TrajectoryStateHistoryMetadata template,
122 final FileFormat fileFormat, final String outputName,
123 final double maxRelativeOffset, final int unitsColumn, final Formatter formatter) {
124 this.writer = writer;
125 this.header = header;
126 this.metadata = metadata.copy(header == null ? writer.getDefaultVersion() : header.getFormatVersion());
127 this.trajectoryMetadata = template.copy(header == null ? writer.getDefaultVersion() : header.getFormatVersion());
128 this.fileFormat = fileFormat;
129 this.outputName = outputName;
130 this.maxRelativeOffset = maxRelativeOffset;
131 this.unitsColumn = unitsColumn;
132 this.body = Double.isNaN(writer.getEquatorialRadius()) ?
133 null :
134 new OneAxisEllipsoid(writer.getEquatorialRadius(),
135 writer.getFlattening(),
136 template.getTrajReferenceFrame().asFrame());
137 this.formatter = formatter;
138 }
139
140 /**
141 * Constructor used to create a new OCM writer configured with the necessary parameters
142 * to successfully fill in all required fields that aren't part of a standard object.
143 * <p>
144 * If the mandatory header entries are not present (or if header is null),
145 * built-in defaults will be used
146 * </p>
147 * <p>
148 * The writer is built from the complete header and partial metadata. The template
149 * metadata is used to initialize and independent local copy, that will be updated
150 * as new segments are written (with at least the segment start and stop will change,
151 * but some other parts may change too). The {@code template} argument itself is not
152 * changed.
153 * </p>
154 * @param writer underlying writer
155 * @param header file header (may be null)
156 * @param metadata file metadata
157 * @param template template for trajectory metadata
158 * @param fileFormat file format to use
159 * @param outputName output name for error messages
160 * @param maxRelativeOffset maximum offset in seconds to use relative dates
161 * (if a date is too far from reference, it will be displayed as calendar elements)
162 * @param unitsColumn columns number for aligning units (if negative or zero, units are not output)
163 */
164 public EphemerisOcmWriter(final OcmWriter writer,
165 final OdmHeader header, final OcmMetadata metadata,
166 final TrajectoryStateHistoryMetadata template,
167 final FileFormat fileFormat, final String outputName,
168 final double maxRelativeOffset, final int unitsColumn) {
169 this(writer, header, metadata, template, fileFormat, outputName, maxRelativeOffset, unitsColumn, new AccurateFormatter());
170 }
171
172 /** {@inheritDoc}
173 * <p>
174 * As {@code EphemerisFile.SatelliteEphemeris} does not have all the entries
175 * from {@link OcmMetadata}, the only values that will be extracted from the
176 * {@code ephemerisFile} will be the start time, stop time, reference frame, interpolation
177 * method and interpolation degree. The missing values (like object name, local spacecraft
178 * body frame...) will be inherited from the template metadata set at writer
179 * {@link #EphemerisOcmWriter(OcmWriter, OdmHeader, OcmMetadata, TrajectoryStateHistoryMetadata,
180 * FileFormat, String, double, int) construction}.
181 * </p>
182 */
183 @Override
184 public <C extends TimeStampedPVCoordinates, S extends EphemerisFile.EphemerisSegment<C>>
185 void write(final Appendable appendable, final EphemerisFile<C, S> ephemerisFile)
186 throws IOException {
187
188 if (appendable == null) {
189 throw new OrekitIllegalArgumentException(OrekitMessages.NULL_ARGUMENT, "writer");
190 }
191
192 if (ephemerisFile == null) {
193 return;
194 }
195
196 final String name;
197 if (metadata.getObjectName() != null) {
198 name = metadata.getObjectName();
199 } else if (metadata.getInternationalDesignator() != null) {
200 name = metadata.getInternationalDesignator();
201 } else if (metadata.getObjectDesignator() != null) {
202 name = metadata.getObjectDesignator();
203 } else {
204 name = Ocm.UNKNOWN_OBJECT;
205 }
206 final SatelliteEphemeris<C, S> satEphem = ephemerisFile.getSatellites().get(name);
207 if (satEphem == null) {
208 throw new OrekitIllegalArgumentException(OrekitMessages.VALUE_NOT_FOUND,
209 name, "ephemerisFile");
210 }
211
212 // Get trajectory blocks to output.
213 final List<S> blocks = satEphem.getSegments();
214 if (blocks.isEmpty()) {
215 // No data -> No output
216 return;
217 }
218
219 try (Generator generator = fileFormat == FileFormat.KVN ?
220 new KvnGenerator(appendable, OcmWriter.KVN_PADDING_WIDTH, outputName,
221 maxRelativeOffset, unitsColumn, formatter) :
222 new XmlGenerator(appendable, XmlGenerator.DEFAULT_INDENT, outputName,
223 maxRelativeOffset, unitsColumn > 0, null, formatter)) {
224
225 writer.writeHeader(generator, header);
226
227 if (generator.getFormat() == FileFormat.XML) {
228 generator.enterSection(XmlStructureKey.segment.name());
229 }
230
231 // write single segment metadata
232 metadata.setStartTime(blocks.get(0).getStart());
233 metadata.setStopTime(blocks.get(blocks.size() - 1).getStop());
234 new OcmMetadataWriter(metadata, writer.getTimeConverter()).write(generator);
235
236 if (generator.getFormat() == FileFormat.XML) {
237 generator.enterSection(XmlStructureKey.data.name());
238 }
239
240 // Loop on trajectory blocks
241 double lastZ = Double.NaN;
242 for (final S block : blocks) {
243
244 // prepare metadata
245 trajectoryMetadata.setTrajNextID(TrajectoryStateHistoryMetadata.incrementTrajID(trajectoryMetadata.getTrajID()));
246 trajectoryMetadata.setUseableStartTime(block.getStart());
247 trajectoryMetadata.setUseableStopTime(block.getStop());
248 trajectoryMetadata.setInterpolationDegree(block.getInterpolationSamples() - 1);
249
250 // prepare data
251 final OrbitElementsType type = trajectoryMetadata.getTrajType();
252 final Frame frame = trajectoryMetadata.getTrajReferenceFrame().asFrame();
253 int crossings = 0;
254 final List<TrajectoryState> states = new ArrayList<>(block.getCoordinates().size());
255 for (final C pv : block.getCoordinates()) {
256 if (lastZ < 0.0 && pv.getPosition().getZ() >= 0.0) {
257 // we crossed ascending node
258 ++crossings;
259 }
260 lastZ = pv.getPosition().getZ();
261 states.add(new TrajectoryState(type, pv.getDate(), type.toRawElements(pv, frame, body, block.getMu())));
262 }
263 final TrajectoryStateHistory history = new TrajectoryStateHistory(trajectoryMetadata, states,
264 body, block.getMu());
265
266 // write trajectory block
267 final TrajectoryStateHistoryWriter trajectoryWriter =
268 new TrajectoryStateHistoryWriter(history, writer.getTimeConverter());
269 trajectoryWriter.write(generator);
270
271 // update the trajectory IDs
272 trajectoryMetadata.setTrajPrevID(trajectoryMetadata.getTrajID());
273 trajectoryMetadata.setTrajID(trajectoryMetadata.getTrajNextID());
274
275 if (trajectoryMetadata.getOrbRevNum() >= 0) {
276 // update the orbits revolution number
277 trajectoryMetadata.setOrbRevNum(trajectoryMetadata.getOrbRevNum() + crossings);
278 }
279
280 }
281
282 if (generator.getFormat() == FileFormat.XML) {
283 generator.exitSection(); // exit data
284 generator.exitSection(); // exit segment
285 }
286
287 writer.writeFooter(generator);
288
289 }
290
291 }
292
293 }