1   /* Contributed in the public domain.
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    * CS 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.Collections;
21  
22  import org.hipparchus.exception.LocalizedCoreFormats;
23  import org.orekit.bodies.OneAxisEllipsoid;
24  import org.orekit.errors.OrekitException;
25  import org.orekit.errors.OrekitMessages;
26  import org.orekit.files.ccsds.definitions.FrameFacade;
27  import org.orekit.files.ccsds.ndm.odm.OdmHeader;
28  import org.orekit.files.ccsds.section.XmlStructureKey;
29  import org.orekit.files.ccsds.utils.FileFormat;
30  import org.orekit.files.ccsds.utils.generation.Generator;
31  import org.orekit.frames.Frame;
32  import org.orekit.propagation.Propagator;
33  import org.orekit.propagation.SpacecraftState;
34  import org.orekit.propagation.sampling.OrekitFixedStepHandler;
35  import org.orekit.time.AbsoluteDate;
36  import org.orekit.utils.TimeStampedPVCoordinates;
37  
38  /**
39   * A writer for OCM files.
40   *
41   * <p> Each instance corresponds to a single Orbit Comprehensive Message.
42   * A new OCM ephemeris trajectory state history block is started by calling
43   * {@link #newBlock()}.
44   * </p>
45   *
46   * <p>
47   * This writer is intended to write only trajectory state history blocks.
48   * It does not writes physical properties, covariance data, maneuver data,
49   * perturbations parameters, orbit determination or user-defined parameters.
50   * If these blocks are needed, then {@link OcmWriter OcmWriter} must be
51   * used as it handles all OCM data blocks.
52   * </p>
53   * <p>
54   * The trajectory blocks metadata identifiers ({@code TRAJ_ID},
55   * {@code TRAJ_PREV_ID}, {@code TRAJ_NEXT_ID}) are updated automatically
56   * using {@link TrajectoryStateHistoryMetadata#incrementTrajID(String)},
57   * so users should generally only set {@link TrajectoryStateHistoryMetadata#setTrajID(String)}
58   * in the template.
59   * </p>
60   *
61   * <p>
62   * The blocks returned by this class can be used as step handlers for a {@link Propagator}.
63   * </p>
64   *
65   * <pre>{@code
66   * Propagator propagator = ...; // pre-configured propagator
67   * OCMWriter  ocmWriter  = ...; // pre-configured writer
68   *   try (Generator out = ...;  // set-up output stream
69   *        StreamingOcmWriter sw = new StreamingOcmWriter(out, ocmWriter, header, metadata, template)) { // set-up streaming writer
70   *
71   *     // write block 1
72   *     propagator.getMultiplexer().add(step, sw.newBlock());
73   *     propagator.propagate(startDate1, stopDate1);
74   *
75   *     ...
76   *
77   *     // write block n
78   *     propagator.getMultiplexer().clear();
79   *     propagator.getMultiplexer().add(step, sw.newBlock());
80   *     propagator.propagate(startDateN, stopDateN);
81   *
82   *   }
83   * }</pre>
84   *
85   *
86   * @author Luc Maisonobe
87   * @see OcmWriter
88   * @see EphemerisOcmWriter
89   * @since 12.0
90   */
91  public class StreamingOcmWriter implements AutoCloseable {
92  
93      /** Generator for OCM output. */
94      private final Generator generator;
95  
96      /** Writer for the OCM message format. */
97      private final OcmWriter writer;
98  
99      /** Writer for the trajectory data block. */
100     private TrajectoryStateHistoryWriter trajectoryWriter;
101 
102     /** Header. */
103     private final OdmHeader header;
104 
105     /** Current metadata. */
106     private final OcmMetadata metadata;
107 
108     /** Current trajectory metadata. */
109     private final TrajectoryStateHistoryMetadata trajectoryMetadata;
110 
111     /** If the propagator's frame should be used. */
112     private final boolean useAttitudeFrame;
113 
114     /** Indicator for writing header. */
115     private boolean headerWritePending;
116 
117     /** Last Z coordinate seen. */
118     private double lastZ;
119 
120     /**
121      * Construct a writer that for each segment uses the reference frame of the
122      * first state's attitude.
123      *
124      * @param generator generator for OCM output
125      * @param writer    writer for the OCM message format
126      * @param header    file header (may be null)
127      * @param metadata  file metadata
128      * @param template  template for trajectory metadata
129      * @see #StreamingOcmWriter(Generator, OcmWriter, OdmHeader, OcmMetadata, TrajectoryStateHistoryMetadata, boolean)
130      */
131     public StreamingOcmWriter(final Generator generator, final OcmWriter writer,
132                               final OdmHeader header, final OcmMetadata metadata,
133                               final TrajectoryStateHistoryMetadata template) {
134         this(generator, writer, header, metadata, template, true);
135     }
136 
137     /**
138      * Simple constructor.
139      *
140      * @param generator           generator for OCM output
141      * @param writer              writer for the OCM message format
142      * @param header              file header (may be null)
143      * @param metadata            file metadata
144      * @param template            template for trajectory metadata
145      * @param useAttitudeFrame    if {@code true} then the reference frame for
146      *                            each segment is taken from the first state's
147      *                            attitude. Otherwise the {@code template}'s
148      *                            reference frame is used, {@link
149      *                            TrajectoryStateHistoryMetadata#getTrajReferenceFrame()}.
150      */
151     public StreamingOcmWriter(final Generator generator, final OcmWriter writer,
152                               final OdmHeader header, final OcmMetadata metadata,
153                               final TrajectoryStateHistoryMetadata template,
154                               final boolean useAttitudeFrame) {
155         this.generator           = generator;
156         this.writer              = writer;
157         this.header              = header;
158         this.metadata            = metadata;
159         this.trajectoryMetadata  = template.copy(header == null ? writer.getDefaultVersion() : header.getFormatVersion());
160         this.useAttitudeFrame    = useAttitudeFrame;
161         this.headerWritePending  = true;
162         this.lastZ               = Double.NaN;
163     }
164 
165     /**
166      * Create a writer for a new OCM trajectory state history block.
167      * <p> The returned writer can only write a single trajectory state history block in an OCM.
168      * This method must be called to create a writer for each trajectory state history block.
169      * @return a new OCM trajectory state history block writer, ready for use.
170      */
171     public BlockWriter newBlock() {
172         return new BlockWriter();
173     }
174 
175     /** {@inheritDoc} */
176     @Override
177     public void close() throws IOException {
178         writer.writeFooter(generator);
179     }
180 
181     /** A writer for a trajectory state history block of an OCM. */
182     public class BlockWriter implements OrekitFixedStepHandler {
183 
184         /** Reference frame of this segment. */
185         private Frame frame;
186 
187         /** Elements type. */
188         private OrbitElementsType type;
189 
190         /** Number of ascending nodes crossings. */
191         private int crossings;
192 
193         /** Empty constructor.
194          * <p>
195          * This constructor is not strictly necessary, but it prevents spurious
196          * javadoc warnings with JDK 18 and later.
197          * </p>
198          * @since 12.0
199          */
200         public BlockWriter() {
201             // nothing to do
202         }
203 
204         /**
205          * {@inheritDoc}
206          *
207          * <p>Writes the header automatically on first segment.
208          * Sets the {@link OcmMetadataKey#START_TIME} and {@link OcmMetadataKey#STOP_TIME} in this
209          * block metadata if not already set by the user.
210          */
211         @Override
212         public void init(final SpacecraftState s0, final AbsoluteDate t, final double step) {
213             try {
214                 final AbsoluteDate date = s0.getDate();
215                 if (t.isBefore(date)) {
216                     throw new OrekitException(OrekitMessages.NON_CHRONOLOGICALLY_SORTED_ENTRIES,
217                             date, t, date.durationFrom(t));
218                 }
219 
220                 if (headerWritePending) {
221                     // we write the header and metadata only for the first segment
222                     writer.writeHeader(generator, header);
223                     if (generator.getFormat() == FileFormat.XML) {
224                         generator.enterSection(XmlStructureKey.segment.name());
225                     }
226                     new OcmMetadataWriter(metadata, writer.getTimeConverter()).write(generator);
227                     if (generator.getFormat() == FileFormat.XML) {
228                         generator.enterSection(XmlStructureKey.data.name());
229                     }
230                     headerWritePending = false;
231                 }
232 
233                 trajectoryMetadata.setTrajNextID(TrajectoryStateHistoryMetadata.incrementTrajID(trajectoryMetadata.getTrajID()));
234                 trajectoryMetadata.setUseableStartTime(date);
235                 trajectoryMetadata.setUseableStopTime(t);
236                 if (useAttitudeFrame) {
237                     frame = s0.getAttitude().getReferenceFrame();
238                     trajectoryMetadata.setTrajReferenceFrame(FrameFacade.map(frame));
239                 } else {
240                     frame = trajectoryMetadata.getTrajReferenceFrame().asFrame();
241                 }
242 
243                 crossings = 0;
244                 type      = trajectoryMetadata.getTrajType();
245 
246                 final OneAxisEllipsoid body = trajectoryMetadata.getTrajType() == OrbitElementsType.GEODETIC ?
247                                               new OneAxisEllipsoid(writer.getEquatorialRadius(),
248                                                                    writer.getFlattening(),
249                                                                    trajectoryMetadata.getTrajReferenceFrame().asFrame()) :
250                                               null;
251                 final double mu = s0.isOrbitDefined() ? s0.getOrbit().getMu() : Double.NaN;
252                 trajectoryWriter = new TrajectoryStateHistoryWriter(new TrajectoryStateHistory(trajectoryMetadata,
253                                                                                                Collections.emptyList(),
254                                                                                                body, mu),
255                                                                     writer.getTimeConverter());
256                 trajectoryWriter.enterSection(generator);
257                 trajectoryWriter.writeMetadata(generator);
258 
259             } catch (IOException e) {
260                 throw new OrekitException(e, LocalizedCoreFormats.SIMPLE_MESSAGE, e.getLocalizedMessage());
261             }
262         }
263 
264         /** {@inheritDoc}. */
265         @Override
266         public void handleStep(final SpacecraftState currentState) {
267             try {
268                 final TimeStampedPVCoordinates pv =
269                         currentState.getPVCoordinates(frame);
270                 if (lastZ < 0.0 && pv.getPosition().getZ() >= 0.0) {
271                     // we crossed ascending node
272                     ++crossings;
273                 }
274                 final double mu = currentState.isOrbitDefined() ? currentState.getOrbit().getMu() : Double.NaN;
275                 lastZ = pv.getPosition().getZ();
276                 final TrajectoryState state = new TrajectoryState(type, pv.getDate(),
277                                                                   type.toRawElements(pv, frame,
278                                                                                      trajectoryWriter.getHistory().getBody(),
279                                                                                      mu));
280                 trajectoryWriter.writeState(generator, state, type.getUnits());
281             } catch (IOException e) {
282                 throw new OrekitException(e, LocalizedCoreFormats.SIMPLE_MESSAGE, e.getLocalizedMessage());
283             }
284         }
285 
286         /** {@inheritDoc}. */
287         @Override
288         public void finish(final SpacecraftState finalState) {
289             try {
290 
291                 trajectoryWriter.exitSection(generator);
292 
293                 // update the trajectory IDs
294                 trajectoryMetadata.setTrajPrevID(trajectoryMetadata.getTrajID());
295                 trajectoryMetadata.setTrajID(trajectoryMetadata.getTrajNextID());
296 
297                 if (trajectoryMetadata.getOrbRevNum() >= 0) {
298                     // update the orbits revolution number
299                     trajectoryMetadata.setOrbRevNum(trajectoryMetadata.getOrbRevNum() + crossings);
300                 }
301 
302             } catch (IOException e) {
303                 throw new OrekitException(e, LocalizedCoreFormats.SIMPLE_MESSAGE, e.getLocalizedMessage());
304             }
305         }
306 
307     }
308 
309 }