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