1   /* Copyright 2002-2021 CS GROUP
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.ilrs;
18  
19  import java.io.IOException;
20  import java.util.Locale;
21  
22  import org.hipparchus.exception.LocalizedCoreFormats;
23  import org.hipparchus.geometry.euclidean.threed.Vector3D;
24  import org.orekit.errors.OrekitException;
25  import org.orekit.frames.Frame;
26  import org.orekit.propagation.Propagator;
27  import org.orekit.propagation.SpacecraftState;
28  import org.orekit.propagation.sampling.OrekitFixedStepHandler;
29  import org.orekit.time.AbsoluteDate;
30  import org.orekit.time.DateTimeComponents;
31  import org.orekit.time.TimeScale;
32  import org.orekit.utils.TimeStampedPVCoordinates;
33  
34  /**
35   * A writer for CPF files.
36   *
37   * <p> Each instance corresponds to a single CPF file.
38   *
39   * <p> This class can be used as a step handler for a {@link Propagator}.
40   * The following example shows its use as a step handler.
41   *
42   * <p>
43   * <b>Note:</b> By default, only required header keys are wrote (H1 and H2). Furthermore, only position data can be written.
44   * Other keys (optionals) are simply ignored.
45   * Contributions are welcome to support more fields in the format.
46   *
47   * @author Bryan Cazabonne
48   * @since 10.3
49   */
50  public class StreamingCpfWriter {
51  
52      /** New line separator for output file. */
53      private static final String NEW_LINE = "\n";
54  
55      /** String A2 Format. */
56      private static final String A2 = "%2s";
57  
58      /** String A3 Format. */
59      private static final String A3 = "%3s";
60  
61      /** String A4 Format. */
62      private static final String A4 = "%4s";
63  
64      /** String A8 Format. */
65      private static final String A8 = "%8s";
66  
67      /** String A10 Format. */
68      private static final String A10 = "%10s";
69  
70      /** Integer I1 Format. */
71      private static final String I1 = "%1d";
72  
73      /** Integer I2 Format. */
74      private static final String I2 = "%2d";
75  
76      /** Integer I3 Format. */
77      private static final String I3 = "%3d";
78  
79      /** Integer I4 Format. */
80      private static final String I4 = "%4d";
81  
82      /** Integer I5 Format. */
83      private static final String I5 = "%5d";
84  
85      /** Real 13.6 Format. */
86      private static final String F13_6 = "%13.6f";
87  
88      /** Real 17.3 Format. */
89      private static final String F17_3 = "%17.3f";
90  
91      /** Space. */
92      private static final String SPACE = " ";
93  
94      /** File format. */
95      private static final String FORMAT = "CPF";
96  
97      /** Default locale. */
98      private static final Locale STANDARDIZED_LOCALE = Locale.US;
99  
100     /** Default value for direction flag in position record. */
101     private static final int DEFAULT_DIRECTION_FLAG = 0;
102 
103     /** Output stream. */
104     private final Appendable writer;
105 
106     /** Time scale for all dates. */
107     private final TimeScale timeScale;
108 
109     /** Container for header data. */
110     private final CPFHeader header;
111 
112     /**
113      * Create a CPF writer than streams data to the given output stream.
114      *
115      * @param writer     the output stream for the CPF file.
116      * @param timeScale  for all times in the CPF
117      * @param header     container for header data
118      */
119     public StreamingCpfWriter(final Appendable writer,
120                               final TimeScale timeScale,
121                               final CPFHeader header) {
122 
123         this.writer     = writer;
124         this.timeScale  = timeScale;
125         this.header     = header;
126     }
127 
128     /**
129      * Writes the CPF header for the file.
130      * @throws IOException if the stream cannot write to stream
131      */
132     public void writeHeader() throws IOException {
133 
134         // Write H1
135         HeaderLineWriter.H1.write(header, writer, timeScale);
136         writer.append(NEW_LINE);
137 
138         // Write H2
139         HeaderLineWriter.H2.write(header, writer, timeScale);
140         writer.append(NEW_LINE);
141 
142         // End of header
143         writer.append("H9");
144         writer.append(NEW_LINE);
145 
146     }
147 
148     /**
149      * Write end of file.
150      * @throws IOException if the stream cannot write to stream
151      */
152     public void writeEndOfFile() throws IOException {
153         writer.append("99");
154     }
155 
156     /**
157      * Create a writer for a new CPF ephemeris segment.
158      * <p>
159      * The returned writer can only write a single ephemeris segment in a CPF.
160      * </p>
161      * @param frame the reference frame to use for the segment.
162      * @return a new CPF segment, ready for writing.
163      */
164     public Segment newSegment(final Frame frame) {
165         return new Segment(frame);
166     }
167 
168     /**
169      * Write a String value in the file.
170      * @param cpfWriter writer
171      * @param format format
172      * @param value value
173      * @throws IOException if value cannot be written
174      */
175     private static void writeValue(final Appendable cpfWriter, final String format, final String value)
176         throws IOException {
177         cpfWriter.append(String.format(STANDARDIZED_LOCALE, format, value)).append(SPACE);
178     }
179 
180     /**
181      * Write a integer value in the file.
182      * @param cpfWriter writer
183      * @param format format
184      * @param value value
185      * @throws IOException if value cannot be written
186      */
187     private static void writeValue(final Appendable cpfWriter, final String format, final int value)
188         throws IOException {
189         cpfWriter.append(String.format(STANDARDIZED_LOCALE, format, value)).append(SPACE);
190     }
191 
192     /**
193      * Write a real value in the file.
194      * @param cpfWriter writer
195      * @param format format
196      * @param value value
197      * @throws IOException if value cannot be written
198      */
199     private static void writeValue(final Appendable cpfWriter, final String format, final double value)
200         throws IOException {
201         cpfWriter.append(String.format(STANDARDIZED_LOCALE, format, value)).append(SPACE);
202     }
203 
204     /**
205      * Write a String value in the file.
206      * @param cpfWriter writer
207      * @param format format
208      * @param value value
209      * @throws IOException if value cannot be writtent
210      */
211     private static void writeValue(final Appendable cpfWriter, final String format, final boolean value)
212         throws IOException {
213         // Change to an integer value
214         final int intValue = value ? 1 : 0;
215         writeValue(cpfWriter, format, intValue);
216     }
217 
218     /** A writer for a segment of a CPF. */
219     public class Segment implements OrekitFixedStepHandler {
220 
221         /** Reference frame of the output states. */
222         private final Frame frame;
223 
224         /**
225          * Create a new segment writer.
226          *
227          * @param frame    for the output states. Used by {@link #handleStep(SpacecraftState,
228          *                 boolean)}.
229          */
230         private Segment(final Frame frame) {
231             this.frame = frame;
232         }
233 
234         /** {@inheritDoc}. */
235         @Override
236         public void handleStep(final SpacecraftState currentState) {
237             try {
238 
239                 // Write ephemeris line
240                 writeEphemerisLine(currentState.getPVCoordinates(frame));
241 
242             } catch (IOException e) {
243                 throw new OrekitException(e, LocalizedCoreFormats.SIMPLE_MESSAGE,
244                                           e.getLocalizedMessage());
245             }
246 
247         }
248 
249         /** {@inheritDoc}. */
250         @Override
251         public void finish(final SpacecraftState finalState) {
252             try {
253                 // Write ephemeris line
254                 writeEphemerisLine(finalState.getPVCoordinates(frame));
255 
256                 // Write end of file
257                 writeEndOfFile();
258 
259             } catch (IOException e) {
260                 throw new OrekitException(e, LocalizedCoreFormats.SIMPLE_MESSAGE,
261                                           e.getLocalizedMessage());
262             }
263 
264         }
265 
266         /**
267          * Write a single ephemeris line This method does not
268          * write the velocity terms.
269          *
270          * @param pv the time, position, and velocity to write.
271          * @throws IOException if the output stream throws one while writing.
272          */
273         public void writeEphemerisLine(final TimeStampedPVCoordinates pv)
274             throws IOException {
275 
276             // Record type and direction flag
277             writeValue(writer, A2, "10");
278             writeValue(writer, I1, DEFAULT_DIRECTION_FLAG);
279 
280             // Epoch
281             final AbsoluteDate epoch = pv.getDate();
282             final DateTimeComponents dtc = epoch.getComponents(timeScale);
283             writeValue(writer, I5, dtc.getDate().getMJD());
284             writeValue(writer, F13_6, dtc.getTime().getSecondsInLocalDay());
285 
286             // Leap second flag (default 0)
287             writeValue(writer, I2, 0);
288 
289             // Position
290             final Vector3D position = pv.getPosition();
291             writeValue(writer, F17_3, position.getX());
292             writeValue(writer, F17_3, position.getY());
293             writeValue(writer, F17_3, position.getZ());
294 
295             // New line
296             writer.append(NEW_LINE);
297 
298         }
299 
300     }
301 
302     /** Writer for specific header lines. */
303     public enum HeaderLineWriter {
304 
305         /** Header first line. */
306         H1("H1") {
307 
308             /** {@inheritDoc} */
309             @Override
310             public void write(final CPFHeader cpfHeader, final Appendable cpfWriter, final TimeScale timescale)
311                 throws IOException {
312 
313                 // write first keys
314                 writeValue(cpfWriter, A2, getIdentifier());
315                 writeValue(cpfWriter, A3, FORMAT);
316                 writeValue(cpfWriter, I2, cpfHeader.getVersion());
317                 writeValue(cpfWriter, A3, cpfHeader.getSource());
318                 writeValue(cpfWriter, I4, cpfHeader.getProductionEpoch().getYear());
319                 writeValue(cpfWriter, I2, cpfHeader.getProductionEpoch().getMonth());
320                 writeValue(cpfWriter, I2, cpfHeader.getProductionEpoch().getDay());
321                 writeValue(cpfWriter, I2, cpfHeader.getProductionHour());
322                 writeValue(cpfWriter, I3, cpfHeader.getSequenceNumber());
323 
324                 // check file version
325                 if (cpfHeader.getVersion() == 2) {
326                     writeValue(cpfWriter, I2, cpfHeader.getSubDailySequenceNumber());
327                 }
328 
329                 // write last key
330                 writeValue(cpfWriter, A10, cpfHeader.getName());
331 
332             }
333 
334         },
335 
336         /** Header second line. */
337         H2("H2") {
338 
339             /** {@inheritDoc} */
340             @Override
341             public void write(final CPFHeader cpfHeader, final Appendable cpfWriter, final TimeScale timescale)
342                 throws IOException {
343 
344                 // write identifiers
345                 writeValue(cpfWriter, A2, getIdentifier());
346                 writeValue(cpfWriter, A8, cpfHeader.getIlrsSatelliteId());
347                 writeValue(cpfWriter, A4, cpfHeader.getSic());
348                 writeValue(cpfWriter, A8, cpfHeader.getNoradId());
349 
350                 // write starting epoch
351                 final AbsoluteDate starting = cpfHeader.getStartEpoch();
352                 final DateTimeComponents dtcStart = starting.getComponents(timescale);
353                 writeValue(cpfWriter, I4, dtcStart.getDate().getYear());
354                 writeValue(cpfWriter, I2, dtcStart.getDate().getMonth());
355                 writeValue(cpfWriter, I2, dtcStart.getDate().getDay());
356                 writeValue(cpfWriter, I2, dtcStart.getTime().getHour());
357                 writeValue(cpfWriter, I2, dtcStart.getTime().getMinute());
358                 writeValue(cpfWriter, I2, (int) dtcStart.getTime().getSecond());
359 
360                 // write ending epoch
361                 final AbsoluteDate ending = cpfHeader.getEndEpoch();
362                 final DateTimeComponents dtcEnd = ending.getComponents(timescale);
363                 writeValue(cpfWriter, I4, dtcEnd.getDate().getYear());
364                 writeValue(cpfWriter, I2, dtcEnd.getDate().getMonth());
365                 writeValue(cpfWriter, I2, dtcEnd.getDate().getDay());
366                 writeValue(cpfWriter, I2, dtcEnd.getTime().getHour());
367                 writeValue(cpfWriter, I2, dtcEnd.getTime().getMinute());
368                 writeValue(cpfWriter, I2, (int)  dtcEnd.getTime().getSecond());
369 
370                 // write last keys
371                 writeValue(cpfWriter, I5, cpfHeader.getStep());
372                 writeValue(cpfWriter, I1, cpfHeader.isCompatibleWithTIVs());
373                 writeValue(cpfWriter, I1, cpfHeader.getTargetClass());
374                 writeValue(cpfWriter, I2, cpfHeader.getRefFrameId());
375                 writeValue(cpfWriter, I1, cpfHeader.getRotationalAngleType());
376                 writeValue(cpfWriter, I1, cpfHeader.isCenterOfMassCorrectionApplied());
377                 if (cpfHeader.getVersion() == 2) {
378                     writeValue(cpfWriter, I1, cpfHeader.getTargetLocation());
379                 }
380 
381             }
382 
383         };
384 
385         /** Identifier. */
386         private final String identifier;
387 
388         /** Simple constructor.
389          * @param identifier regular expression for identifying line (i.e. first element)
390          */
391         HeaderLineWriter(final String identifier) {
392             this.identifier = identifier;
393         }
394 
395         /** Write a line.
396          * @param cpfHeader container for header data
397          * @param cpfWriter writer
398          * @param timescale time scale for dates
399          * @throws IOException
400          *             if any buffer writing operations fail or if the underlying
401          *             format doesn't support a configuration in the file
402          */
403         public abstract void write(CPFHeader cpfHeader, Appendable cpfWriter, TimeScale timescale)  throws IOException;
404 
405         /**
406          * Get the regular expression for identifying line.
407          * @return the regular expression for identifying line
408          */
409         public String getIdentifier() {
410             return identifier;
411         }
412 
413     }
414 
415 }