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 }