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.BufferedReader;
20  import java.io.IOException;
21  import java.io.Reader;
22  import java.util.Optional;
23  import java.util.regex.Pattern;
24  import java.util.stream.Stream;
25  
26  import org.hipparchus.exception.LocalizedCoreFormats;
27  import org.hipparchus.geometry.euclidean.threed.Vector3D;
28  import org.orekit.annotation.DefaultDataContext;
29  import org.orekit.data.DataContext;
30  import org.orekit.data.DataSource;
31  import org.orekit.errors.OrekitException;
32  import org.orekit.errors.OrekitMessages;
33  import org.orekit.files.general.EphemerisFileParser;
34  import org.orekit.frames.Frame;
35  import org.orekit.frames.Frames;
36  import org.orekit.time.AbsoluteDate;
37  import org.orekit.time.DateComponents;
38  import org.orekit.time.TimeScale;
39  import org.orekit.utils.CartesianDerivativesFilter;
40  import org.orekit.utils.Constants;
41  import org.orekit.utils.IERSConventions;
42  
43  /**
44   * A parser for the CPF orbit file format.
45   * <p>
46   * It supports both 1.0 and 2.0 versions
47   * <p>
48   * <b>Note:</b> Only required header keys are read. Furthermore, only position data are read.
49   * Other keys are simply ignored
50   * Contributions are welcome to support more fields in the format.
51   * </p>
52   * @see <a href="https://ilrs.gsfc.nasa.gov/docs/2006/cpf_1.01.pdf">1.0 file format</a>
53   * @see <a href="https://ilrs.gsfc.nasa.gov/docs/2018/cpf_2.00h-1.pdf">2.0 file format</a>
54   * @author Bryan Cazabonne
55   * @since 10.3
56   */
57  public class CPFParser implements EphemerisFileParser<CPF> {
58  
59      /** File format. */
60      private static final String FILE_FORMAT = "CPF";
61  
62      /** Miscroseconds to seconds converter. */
63      private static final double MS_TO_S = 1.0e-6;
64  
65      /** Pattern for delimiting regular expressions. */
66      private static final Pattern SEPARATOR = Pattern.compile("\\s+");
67  
68      /** Default number of sample for interpolating data (See: reference documents. */
69      private static final int DEFAULT_INTERPOLATION_SAMPLE = 10;
70  
71      /** Standard gravitational parameter in m^3 / s^2. */
72      private final double mu;
73  
74      /** Time scale used to define epochs in CPF file. */
75      private final TimeScale timeScale;
76  
77      /** Set of frames. */
78      private final Frames frames;
79  
80      /** Interpolation sample for data interpolating. */
81      private final int interpolationSample;
82  
83      /** IERS convention for frames. */
84      private final IERSConventions iersConvention;
85  
86      /**
87       * Default constructor.
88       * <p>
89       * This constructor uses the {@link DataContext#getDefault() default data context}.
90       */
91      @DefaultDataContext
92      public CPFParser() {
93          this(Constants.EIGEN5C_EARTH_MU, DEFAULT_INTERPOLATION_SAMPLE,
94               IERSConventions.IERS_2010, DataContext.getDefault().getTimeScales().getUTC(),
95               DataContext.getDefault().getFrames());
96      }
97  
98      /**
99       * Constructor.
100      * @param mu standard gravitational parameter to use for
101      *           creating {@link org.orekit.orbits.Orbit Orbits} from
102      *           the ephemeris data.
103      * @param interpolationSamples number of samples to use when interpolating
104      * @param iersConventions IERS convention for frames definition
105      * @param utc time scale used to define epochs in CPF files (UTC)
106      * @param frames set of frames for satellite coordinates
107      */
108     public CPFParser(final double mu,
109                      final int interpolationSamples,
110                      final IERSConventions iersConventions,
111                      final TimeScale utc,
112                      final Frames frames) {
113         this.mu                  = mu;
114         this.interpolationSample = interpolationSamples;
115         this.iersConvention      = iersConventions;
116         this.timeScale           = utc;
117         this.frames              = frames;
118     }
119 
120     /** {@inheritDoc} */
121     @Override
122     public CPF parse(final DataSource source) {
123 
124         try (Reader reader = source.getOpener().openReaderOnce();
125              BufferedReader br = (reader == null) ? null : new BufferedReader(reader)) {
126 
127             if (br == null) {
128                 throw new OrekitException(OrekitMessages.UNABLE_TO_FIND_FILE, source.getName());
129             }
130 
131             // initialize internal data structures
132             final ParseInfo pi = new ParseInfo();
133 
134             int lineNumber = 0;
135             Stream<LineParser> parsers = Stream.of(LineParser.H1);
136             for (String line = br.readLine(); line != null; line = br.readLine()) {
137                 ++lineNumber;
138                 final String l = line;
139                 final Optional<LineParser> selected = parsers.filter(p -> p.canHandle(l)).findFirst();
140                 if (selected.isPresent()) {
141                     try {
142                         selected.get().parse(line, pi);
143                     } catch (StringIndexOutOfBoundsException | NumberFormatException e) {
144                         throw new OrekitException(e,
145                                                   OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
146                                                   lineNumber, source.getName(), line);
147                     }
148                     parsers = selected.get().allowedNext();
149                 }
150                 if (pi.done) {
151                     pi.file.setFilter(pi.hasVelocityEntries ?
152                                                              CartesianDerivativesFilter.USE_PV :
153                                                                  CartesianDerivativesFilter.USE_P);
154                     // Return file
155                     return pi.file;
156                 }
157             }
158 
159             // We never reached the EOF marker
160             throw new OrekitException(OrekitMessages.CPF_UNEXPECTED_END_OF_FILE, lineNumber);
161 
162         } catch (IOException ioe) {
163             throw new OrekitException(ioe, LocalizedCoreFormats.SIMPLE_MESSAGE, ioe.getLocalizedMessage());
164         }
165 
166     }
167 
168     /** Transient data used for parsing a CPF file. The data is kept in a
169      * separate data structure to make the parser thread-safe.
170      * <p><b>Note</b>: The class intentionally does not provide accessor
171      * methods, as it is only used internally for parsing a CPF file.</p>
172      */
173     private class ParseInfo {
174 
175         /** The corresponding CPF file. */
176         private CPF file;
177 
178         /** IERS convention. */
179         private IERSConventions convention;
180 
181         /** Set of frames. */
182         private Frames frames;
183 
184         /** Frame for the ephemeris data. */
185         private Frame frame;
186 
187         /** Time scale. */
188         private TimeScale timeScale;
189 
190         /** Indicates if the SP3 file has velocity entries. */
191         private boolean hasVelocityEntries;
192 
193         /** End Of File reached indicator. */
194         private boolean done;
195 
196         /**
197          * Constructor.
198          */
199         protected ParseInfo() {
200 
201             // Initialise file
202             file = new CPF();
203 
204             // Time scale
205             this.timeScale = CPFParser.this.timeScale;
206 
207             // Initialise fields
208             file.setMu(mu);
209             file.setInterpolationSample(interpolationSample);
210             file.setTimeScale(timeScale);
211 
212             // Default values
213             this.done               = false;
214             this.hasVelocityEntries = false;
215 
216             // Default value for reference frame
217             this.convention = CPFParser.this.iersConvention;
218             this.frames     = CPFParser.this.frames;
219             frame           = frames.getITRF(convention, false);
220 
221         }
222 
223     }
224 
225     /** Parsers for specific lines. */
226     private enum LineParser {
227 
228         /** Header first line. */
229         H1("H1") {
230 
231             /** {@inheritDoc} */
232             @Override
233             public void parse(final String line, final ParseInfo pi) {
234 
235                 // Data contained in the line
236                 final String[] values = SEPARATOR.split(line);
237 
238                 // Index for reading data.
239                 // Allow taking into consideration difference between 1.0 and 2.0 formats
240                 int index = 1;
241 
242                 // Format
243                 final String format = values[index++];
244 
245                 // Throw an exception if format is not equal to "CPF"
246                 if (!FILE_FORMAT.equals(format)) {
247                     throw new OrekitException(OrekitMessages.UNEXPECTED_FORMAT_FOR_ILRS_FILE, FILE_FORMAT, format);
248                 }
249 
250                 // Fill first elements
251                 pi.file.getHeader().setFormat(format);
252                 pi.file.getHeader().setVersion(Integer.parseInt(values[index++]));
253                 pi.file.getHeader().setSource(values[index++]);
254 
255                 // Epoch of ephemeris production
256                 final int year  = Integer.parseInt(values[index++]);
257                 final int month = Integer.parseInt(values[index++]);
258                 final int day   = Integer.parseInt(values[index++]);
259                 pi.file.getHeader().setProductionEpoch(new DateComponents(year, month, day));
260 
261                 // Hour of ephemeris production
262                 pi.file.getHeader().setProductionHour(Integer.parseInt(values[index++]));
263 
264                 // Ephemeris sequence number
265                 pi.file.getHeader().setSequenceNumber(Integer.parseInt(values[index++]));
266 
267                 // Difference between version 1.0 and 2.0: sub-daily ephemeris sequence number
268                 if (pi.file.getHeader().getVersion() == 2) {
269                     pi.file.getHeader().setSubDailySequenceNumber(Integer.parseInt(values[index++]));
270                 }
271 
272                 // Target Name
273                 pi.file.getHeader().setName(values[index]);
274 
275             }
276 
277             /** {@inheritDoc} */
278             @Override
279             public Stream<LineParser> allowedNext() {
280                 return Stream.of(H2, ZERO);
281             }
282 
283         },
284 
285         /** Header second line. */
286         H2("H2") {
287 
288             /** {@inheritDoc} */
289             @Override
290             public void parse(final String line, final ParseInfo pi) {
291 
292                 // Data contained in the line
293                 final String[] values = SEPARATOR.split(line);
294 
295                 // Identifiers
296                 pi.file.getHeader().setIlrsSatelliteId(values[1]);
297                 pi.file.getHeader().setSic(values[2]);
298                 pi.file.getHeader().setNoradId(values[3]);
299 
300                 // Start epoch
301                 final int    yearS   = Integer.parseInt(values[4]);
302                 final int    monthS  = Integer.parseInt(values[5]);
303                 final int    dayS    = Integer.parseInt(values[6]);
304                 final int    hourS   = Integer.parseInt(values[7]);
305                 final int    minuteS = Integer.parseInt(values[8]);
306                 final double secondS = Integer.parseInt(values[9]);
307 
308                 pi.file.getHeader().setStartEpoch(new AbsoluteDate(yearS, monthS, dayS,
309                                                                    hourS, minuteS, secondS,
310                                                                    pi.file.getTimeScale()));
311 
312                 // End epoch
313                 final int    yearE   = Integer.parseInt(values[10]);
314                 final int    monthE  = Integer.parseInt(values[11]);
315                 final int    dayE    = Integer.parseInt(values[12]);
316                 final int    hourE   = Integer.parseInt(values[13]);
317                 final int    minuteE = Integer.parseInt(values[14]);
318                 final double secondE = Integer.parseInt(values[15]);
319 
320                 pi.file.getHeader().setEndEpoch(new AbsoluteDate(yearE, monthE, dayE,
321                                                                  hourE, minuteE, secondE,
322                                                                  pi.file.getTimeScale()));
323 
324                 // Time between table entries
325                 pi.file.getHeader().setStep(Integer.parseInt(values[16]));
326 
327                 // Compatibility with TIVs
328                 pi.file.getHeader().setIsCompatibleWithTIVs(Integer.parseInt(values[17]) == 1);
329 
330                 // Target class
331                 pi.file.getHeader().setTargetClass(Integer.parseInt(values[18]));
332 
333                 // Reference frame
334                 final int frameId = Integer.parseInt(values[19]);
335                 switch (frameId) {
336                     case 0:
337                         pi.frame = pi.frames.getITRF(pi.convention, false);
338                         break;
339                     case 1:
340                         pi.frame = pi.frames.getTOD(true);
341                         break;
342                     case 2:
343                         pi.frame = pi.frames.getMOD(pi.convention);
344                         break;
345                     default:
346                         pi.frame = pi.frames.getITRF(pi.convention, false);
347                         break;
348                 }
349                 pi.file.getHeader().setRefFrame(pi.frame);
350                 pi.file.getHeader().setRefFrameId(frameId);
351 
352                 // Last fields
353                 pi.file.getHeader().setRotationalAngleType(Integer.parseInt(values[20]));
354                 pi.file.getHeader().setIsCenterOfMassCorrectionApplied(Integer.parseInt(values[21]) == 1);
355                 if (pi.file.getHeader().getVersion() == 2) {
356                     pi.file.getHeader().setTargetLocation(Integer.parseInt(values[22]));
357                 }
358 
359             }
360 
361             /** {@inheritDoc} */
362             @Override
363             public Stream<LineParser> allowedNext() {
364                 return Stream.of(H3, H4, H5, H9, ZERO);
365             }
366 
367         },
368 
369         /** Header third line. */
370         H3("H3") {
371 
372             /** {@inheritDoc} */
373             @Override
374             public void parse(final String line, final ParseInfo pi) {
375                 // Not implemented yet
376             }
377 
378             /** {@inheritDoc} */
379             @Override
380             public Stream<LineParser> allowedNext() {
381                 return Stream.of(H4, H5, H9, ZERO);
382             }
383 
384         },
385 
386         /** Header fourth line. */
387         H4("H4") {
388 
389             /** {@inheritDoc} */
390             @Override
391             public void parse(final String line, final ParseInfo pi) {
392 
393                 // Data contained in the line
394                 final String[] values = SEPARATOR.split(line);
395 
396                 // Pulse Repetition Frequency (PRF)
397                 pi.file.getHeader().setPrf(Double.parseDouble(values[1]));
398 
399                 // Transponder information
400                 pi.file.getHeader().setTranspTransmitDelay(Double.parseDouble(values[2]) * MS_TO_S);
401                 pi.file.getHeader().setTranspUtcOffset(Double.parseDouble(values[3]) * MS_TO_S);
402                 pi.file.getHeader().setTranspOscDrift(Double.parseDouble(values[4]));
403                 if (pi.file.getHeader().getVersion() == 2) {
404                     pi.file.getHeader().setTranspClkRef(Double.parseDouble(values[5]));
405                 }
406 
407             }
408 
409             /** {@inheritDoc} */
410             @Override
411             public Stream<LineParser> allowedNext() {
412                 return Stream.of(H5, H9, ZERO);
413             }
414 
415         },
416 
417         /** Header fifth line. */
418         H5("H5") {
419 
420             /** {@inheritDoc} */
421             @Override
422             public void parse(final String line, final ParseInfo pi) {
423 
424                 // Approximate center of mass to reflector offset in meters
425                 final double offset = Double.parseDouble(SEPARATOR.split(line)[1]);
426                 pi.file.getHeader().setCenterOfMassOffset(offset);
427 
428             }
429 
430             /** {@inheritDoc} */
431             @Override
432             public Stream<LineParser> allowedNext() {
433                 return Stream.of(H9, ZERO);
434             }
435 
436         },
437 
438         /** Header last line. */
439         H9("H9") {
440 
441             /** {@inheritDoc} */
442             @Override
443             public void parse(final String line, final ParseInfo pi) {
444                 // End of header. Nothing to do
445             }
446 
447             /** {@inheritDoc} */
448             @Override
449             public Stream<LineParser> allowedNext() {
450                 return Stream.of(TEN, ZERO);
451             }
452 
453         },
454 
455         /** Position values. */
456         TEN("10") {
457 
458             /** {@inheritDoc} */
459             @Override
460             public void parse(final String line, final ParseInfo pi) {
461 
462                 // Data contained in the line
463                 final String[] values = SEPARATOR.split(line);
464 
465                 // Epoch
466                 final int mjd           = Integer.parseInt(values[2]);
467                 final double secInDay   = Double.parseDouble(values[3]);
468                 final AbsoluteDate date = AbsoluteDate.createMJDDate(mjd, secInDay, pi.timeScale);
469 
470                 // Leap second flag
471                 final int leap = Integer.parseInt(values[4]);
472 
473                 // Coordinates
474                 final double x = Double.parseDouble(values[5]);
475                 final double y = Double.parseDouble(values[6]);
476                 final double z = Double.parseDouble(values[7]);
477                 final Vector3D position = new Vector3D(x, y, z);
478 
479                 // CPF coordinate
480                 final CPF.CPFCoordinate coordinate = new CPF.CPFCoordinate(date, position, leap);
481                 pi.file.addSatelliteCoordinate(pi.file.getHeader().getIlrsSatelliteId(), coordinate);
482 
483             }
484 
485             /** {@inheritDoc} */
486             @Override
487             public Stream<LineParser> allowedNext() {
488                 return Stream.of(TEN, TWENTY, THIRTY, FORTY, FIFTY, SIXTY, SEVENTY, ZERO, EOF);
489             }
490 
491         },
492 
493         /** Velocity values. */
494         TWENTY("20") {
495 
496             /** {@inheritDoc} */
497             @Override
498             public void parse(final String line, final ParseInfo pi) {
499                 // Not implemented yet
500             }
501 
502             /** {@inheritDoc} */
503             @Override
504             public Stream<LineParser> allowedNext() {
505                 return Stream.of(TEN, TWENTY, THIRTY, FORTY, FIFTY, SIXTY, SEVENTY, ZERO, EOF);
506             }
507 
508         },
509 
510         /** Corrections. */
511         THIRTY("30") {
512 
513             /** {@inheritDoc} */
514             @Override
515             public void parse(final String line, final ParseInfo pi) {
516                 // Not implemented yet
517             }
518 
519             /** {@inheritDoc} */
520             @Override
521             public Stream<LineParser> allowedNext() {
522                 return Stream.of(TEN, TWENTY, THIRTY, FORTY, FIFTY, SIXTY, SEVENTY, ZERO, EOF);
523             }
524 
525         },
526 
527         /** Transponder specific. */
528         FORTY("40") {
529 
530             /** {@inheritDoc} */
531             @Override
532             public void parse(final String line, final ParseInfo pi) {
533                 // Not implemented yet
534             }
535 
536             /** {@inheritDoc} */
537             @Override
538             public Stream<LineParser> allowedNext() {
539                 return Stream.of(TEN, TWENTY, THIRTY, FORTY, FIFTY, SIXTY, SEVENTY, ZERO, EOF);
540             }
541 
542         },
543 
544         /** Offset from center of main body. */
545         FIFTY("50") {
546 
547             /** {@inheritDoc} */
548             @Override
549             public void parse(final String line, final ParseInfo pi) {
550                 // Not implemented yet
551             }
552 
553             /** {@inheritDoc} */
554             @Override
555             public Stream<LineParser> allowedNext() {
556                 return Stream.of(TEN, TWENTY, THIRTY, FORTY, FIFTY, SIXTY, SEVENTY, ZERO, EOF);
557             }
558 
559         },
560 
561         /** Rotation angle of offset. */
562         SIXTY("60") {
563 
564             /** {@inheritDoc} */
565             @Override
566             public void parse(final String line, final ParseInfo pi) {
567                 // Not implemented yet
568             }
569 
570             /** {@inheritDoc} */
571             @Override
572             public Stream<LineParser> allowedNext() {
573                 return Stream.of(TEN, TWENTY, THIRTY, FORTY, FIFTY, SIXTY, SEVENTY, ZERO, EOF);
574             }
575 
576         },
577 
578         /** Earth orientation. */
579         SEVENTY("70") {
580 
581             /** {@inheritDoc} */
582             @Override
583             public void parse(final String line, final ParseInfo pi) {
584                 // Not implemented yet
585             }
586 
587             /** {@inheritDoc} */
588             @Override
589             public Stream<LineParser> allowedNext() {
590                 return Stream.of(TEN, TWENTY, THIRTY, FORTY, FIFTY, SIXTY, SEVENTY, ZERO, EOF);
591             }
592 
593         },
594 
595         /** Comments. */
596         ZERO("00") {
597 
598             /** {@inheritDoc} */
599             @Override
600             public void parse(final String line, final ParseInfo pi) {
601 
602                 // Comment
603                 final String comment = line.split(getIdentifier())[1].trim();
604                 pi.file.getComments().add(comment);
605 
606             }
607 
608             /** {@inheritDoc} */
609             @Override
610             public Stream<LineParser> allowedNext() {
611                 return Stream.of(H1, H2, H3, H4, H5, H9,
612                                  TEN, TWENTY, THIRTY, FORTY, FIFTY, SIXTY, SEVENTY, ZERO, EOF);
613             }
614 
615         },
616 
617         /** Last record in ephemeris. */
618         EOF("99") {
619 
620             @Override
621             public void parse(final String line, final ParseInfo pi) {
622                 pi.done = true;
623             }
624 
625             /** {@inheritDoc} */
626             @Override
627             public Stream<LineParser> allowedNext() {
628                 return Stream.of(EOF);
629             }
630 
631         };
632 
633         /** Pattern for identifying line. */
634         private final Pattern pattern;
635 
636         /** Identifier. */
637         private final String identifier;
638 
639         /** Simple constructor.
640          * @param identifier regular expression for identifying line (i.e. first element)
641          */
642         LineParser(final String identifier) {
643             this.identifier = identifier;
644             pattern = Pattern.compile(identifier);
645         }
646 
647         /**
648          * Get the regular expression for identifying line.
649          * @return the regular expression for identifying line
650          */
651         public String getIdentifier() {
652             return identifier;
653         }
654 
655         /** Parse a line.
656          * @param line line to parse
657          * @param pi holder for transient data
658          */
659         public abstract void parse(String line, ParseInfo pi);
660 
661         /** Get the allowed parsers for next line.
662          * @return allowed parsers for next line
663          */
664         public abstract Stream<LineParser> allowedNext();
665 
666         /** Check if parser can handle line.
667          * @param line line to parse
668          * @return true if parser can handle the specified line
669          */
670         public boolean canHandle(final String line) {
671             return pattern.matcher(SEPARATOR.split(line)[0]).matches();
672         }
673 
674     }
675 
676 }