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