1   /* Copyright 2002-2016 CS Systèmes d'Information
2    * Licensed to CS Systèmes d'Information (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;
18  
19  import org.hipparchus.util.FastMath;
20  import org.orekit.errors.OrekitException;
21  import org.orekit.errors.OrekitInternalError;
22  import org.orekit.errors.OrekitMessages;
23  import org.orekit.files.general.OrbitFile;
24  import org.orekit.time.AbsoluteDate;
25  import org.orekit.time.DateTimeComponents;
26  import org.orekit.time.TimeScalesFactory;
27  import org.orekit.utils.Constants;
28  import org.orekit.utils.IERSConventions;
29  
30  import java.io.FileInputStream;
31  import java.io.FileNotFoundException;
32  import java.io.IOException;
33  import java.io.InputStream;
34  import java.util.List;
35  import java.util.regex.Matcher;
36  import java.util.regex.Pattern;
37  
38  /**
39   * Base class for all CCSDS Orbit Data Message parsers.
40   *
41   * <p> This base class is immutable, and hence thread safe. When parts must be
42   * changed, such as reference date for Mission Elapsed Time or Mission Relative
43   * Time time systems, or the gravitational coefficient or the IERS conventions,
44   * the various {@code withXxx} methods must be called, which create a new
45   * immutable instance with the new parameters. This is a combination of the <a
46   * href="https://en.wikipedia.org/wiki/Builder_pattern">builder design
47   * pattern</a> and a <a href="http://en.wikipedia.org/wiki/Fluent_interface">fluent
48   * interface</a>.
49   *
50   * @author Luc Maisonobe
51   * @since 6.1
52   */
53  public abstract class ODMParser {
54  
55      /** Pattern for international designator. */
56      private static final Pattern INTERNATIONAL_DESIGNATOR = Pattern.compile("(\\p{Digit}{4})-(\\p{Digit}{3})(\\p{Upper}{1,3})");
57  
58      /** Reference date for Mission Elapsed Time or Mission Relative Time time systems. */
59      private final AbsoluteDate missionReferenceDate;
60  
61      /** Gravitational coefficient. */
62      private final  double mu;
63  
64      /** IERS Conventions. */
65      private final  IERSConventions conventions;
66  
67      /** Indicator for simple or accurate EOP interpolation. */
68      private final  boolean simpleEOP;
69  
70      /** Launch Year. */
71      private int launchYear;
72  
73      /** Launch number. */
74      private int launchNumber;
75  
76      /** Piece of launch (from "A" to "ZZZ"). */
77      private String launchPiece;
78  
79      /** Complete constructor.
80       * @param missionReferenceDate reference date for Mission Elapsed Time or Mission Relative Time time systems
81       * @param mu gravitational coefficient
82       * @param conventions IERS Conventions
83       * @param simpleEOP if true, tidal effects are ignored when interpolating EOP
84       * @param launchYear launch year for TLEs
85       * @param launchNumber launch number for TLEs
86       * @param launchPiece piece of launch (from "A" to "ZZZ") for TLEs
87       */
88      protected ODMParser(final AbsoluteDate missionReferenceDate, final double mu,
89                          final IERSConventions conventions, final boolean simpleEOP,
90                          final int launchYear, final int launchNumber, final String launchPiece) {
91          this.missionReferenceDate = missionReferenceDate;
92          this.mu                   = mu;
93          this.conventions          = conventions;
94          this.simpleEOP            = simpleEOP;
95          this.launchYear           = launchYear;
96          this.launchNumber         = launchNumber;
97          this.launchPiece          = launchPiece;
98      }
99  
100     /** Set initial date.
101      * @param newMissionReferenceDate mission reference date to use while parsing
102      * @return a new instance, with mission reference date replaced
103      * @see #getMissionReferenceDate()
104      */
105     public abstract ODMParser withMissionReferenceDate(final AbsoluteDate newMissionReferenceDate);
106 
107     /** Get initial date.
108      * @return mission reference date to use while parsing
109      * @see #withMissionReferenceDate(AbsoluteDate)
110      */
111     public AbsoluteDate getMissionReferenceDate() {
112         return missionReferenceDate;
113     }
114 
115     /** Set gravitational coefficient.
116      * @param newMu gravitational coefficient to use while parsing
117      * @return a new instance, with gravitational coefficient date replaced
118      * @see #getMu()
119      */
120     public abstract ODMParser withMu(final double newMu);
121 
122     /** Get gravitational coefficient.
123      * @return gravitational coefficient to use while parsing
124      * @see #withMu(double)
125      */
126     public double getMu() {
127         return mu;
128     }
129 
130     /** Set IERS conventions.
131      * @param newConventions IERS conventions to use while parsing
132      * @return a new instance, with IERS conventions replaced
133      * @see #getConventions()
134      */
135     public abstract ODMParser withConventions(final IERSConventions newConventions);
136 
137     /** Get IERS conventions.
138      * @return IERS conventions to use while parsing
139      * @see #withConventions(IERSConventions)
140      */
141     public IERSConventions getConventions() {
142         return conventions;
143     }
144 
145     /** Set EOP interpolation method.
146      * @param newSimpleEOP if true, tidal effects are ignored when interpolating EOP
147      * @return a new instance, with EOP interpolation method replaced
148      * @see #isSimpleEOP()
149      */
150     public abstract ODMParser withSimpleEOP(final boolean newSimpleEOP);
151 
152     /** Get EOP interpolation method.
153      * @return true if tidal effects are ignored when interpolating EOP
154      * @see #withSimpleEOP(boolean)
155      */
156     public boolean isSimpleEOP() {
157         return simpleEOP;
158     }
159 
160     /** Set international designator.
161      * <p>
162      * This method may be used to ensure the launch year number and pieces are
163      * correctly set if they are not present in the CCSDS file header in the
164      * OBJECT_ID in the form YYYY-NNN-P{PP}. If they are already in the header,
165      * they will be parsed automatically regardless of this method being called
166      * or not (i.e. header information override information set here).
167      * </p>
168      * @param newLaunchYear launch year
169      * @param newLaunchNumber launch number
170      * @param newLaunchPiece piece of launch (from "A" to "ZZZ")
171      * @return a new instance, with TLE settings replaced
172      */
173     public abstract ODMParser withInternationalDesignator(final int newLaunchYear,
174                                                           final int newLaunchNumber,
175                                                           final String newLaunchPiece);
176 
177     /** Get the launch year.
178      * @return launch year
179      */
180     public int getLaunchYear() {
181         return launchYear;
182     }
183 
184     /** Get the launch number.
185      * @return launch number
186      */
187     public int getLaunchNumber() {
188         return launchNumber;
189     }
190 
191     /** Get the piece of launch.
192      * @return piece of launch
193      */
194     public String getLaunchPiece() {
195         return launchPiece;
196     }
197 
198     /** Parse a CCSDS Orbit Data Message.
199      * @param fileName name of the file containing the message
200      * @return parsed orbit
201      * @exception OrekitException if orbit message cannot be parsed
202      */
203     public ODMFile parse(final String fileName)
204         throws OrekitException {
205 
206         InputStream stream = null;
207 
208         try {
209             stream = new FileInputStream(fileName);
210             return parse(stream, fileName);
211         } catch (FileNotFoundException e) {
212             throw new OrekitException(OrekitMessages.UNABLE_TO_FIND_FILE, fileName);
213         } finally {
214             try {
215                 if (stream != null) {
216                     stream.close();
217                 }
218             } catch (IOException e) {
219                 // ignore
220             }
221         }
222 
223     }
224 
225     /** Parse a CCSDS Orbit Data Message.
226      * @param stream stream containing message
227      * @return parsed orbit
228      * @exception OrekitException if orbit message cannot be parsed
229      */
230     public ODMFile parse(final InputStream stream)
231         throws OrekitException {
232         return parse(stream, "<unknown>");
233     }
234 
235     /** Parse a CCSDS Orbit Data Message.
236      * @param stream stream containing message
237      * @param fileName name of the file containing the message (for error messages)
238      * @return parsed orbit
239      * @exception OrekitException if orbit message cannot be parsed
240      */
241     public abstract ODMFile parse(final InputStream stream, final String fileName)
242         throws OrekitException;
243 
244     /** Parse a comment line.
245      * @param keyValue key=value pair containing the comment
246      * @param comment placeholder where the current comment line should be added
247      * @return true if the line was a comment line and was parsed
248      */
249     protected boolean parseComment(final KeyValue keyValue, final List<String> comment) {
250         if (keyValue.getKeyword() == Keyword.COMMENT) {
251             comment.add(keyValue.getValue());
252             return true;
253         } else {
254             return false;
255         }
256     }
257 
258     /** Parse an entry from the header.
259      * @param keyValue key = value pair
260      * @param odmFile instance to update with parsed entry
261      * @param comment previous comment lines, will be emptied if used by the keyword
262      * @return true if the keyword was a header keyword and has been parsed
263      * @exception OrekitException if UTC time scale cannot be retrieved to parse creation date
264      */
265     protected boolean parseHeaderEntry(final KeyValue keyValue,
266                                        final ODMFile odmFile, final List<String> comment)
267         throws OrekitException {
268         switch (keyValue.getKeyword()) {
269 
270             case CREATION_DATE:
271                 if (!comment.isEmpty()) {
272                     odmFile.setHeaderComment(comment);
273                     comment.clear();
274                 }
275                 odmFile.setCreationDate(new AbsoluteDate(keyValue.getValue(), TimeScalesFactory.getUTC()));
276                 return true;
277 
278             case ORIGINATOR:
279                 odmFile.setOriginator(keyValue.getValue());
280                 return true;
281 
282             default:
283                 return false;
284 
285         }
286 
287     }
288 
289     /** Parse a meta-data key = value entry.
290      * @param keyValue key = value pair
291      * @param metaData instance to update with parsed entry
292      * @param comment previous comment lines, will be emptied if used by the keyword
293      * @return true if the keyword was a meta-data keyword and has been parsed
294      * @exception OrekitException if center body or frame cannot be retrieved
295      */
296     protected boolean parseMetaDataEntry(final KeyValue keyValue,
297                                          final ODMMetaData metaData, final List<String> comment)
298         throws OrekitException {
299         switch (keyValue.getKeyword()) {
300             case OBJECT_NAME:
301                 if (!comment.isEmpty()) {
302                     metaData.setComment(comment);
303                     comment.clear();
304                 }
305                 metaData.setObjectName(keyValue.getValue());
306                 return true;
307 
308             case OBJECT_ID: {
309                 metaData.setObjectID(keyValue.getValue());
310                 final Matcher matcher = INTERNATIONAL_DESIGNATOR.matcher(keyValue.getValue());
311                 if (matcher.matches()) {
312                     metaData.setLaunchYear(Integer.parseInt(matcher.group(1)));
313                     metaData.setLaunchNumber(Integer.parseInt(matcher.group(2)));
314                     metaData.setLaunchPiece(matcher.group(3));
315                 }
316                 return true;
317             }
318 
319             case CENTER_NAME:
320                 metaData.setCenterName(keyValue.getValue());
321                 final String canonicalValue;
322                 if (keyValue.getValue().equals("SOLAR SYSTEM BARYCENTER") || keyValue.getValue().equals("SSB")) {
323                     canonicalValue = "SOLAR_SYSTEM_BARYCENTER";
324                 } else if (keyValue.getValue().equals("EARTH MOON BARYCENTER") || keyValue.getValue().equals("EARTH-MOON BARYCENTER") ||
325                         keyValue.getValue().equals("EARTH BARYCENTER") || keyValue.getValue().equals("EMB")) {
326                     canonicalValue = "EARTH_MOON";
327                 } else {
328                     canonicalValue = keyValue.getValue();
329                 }
330                 for (final CenterName c : CenterName.values()) {
331                     if (c.name().equals(canonicalValue)) {
332                         metaData.setHasCreatableBody(true);
333                         metaData.setCenterBody(c.getCelestialBody());
334                         metaData.getODMFile().setMuCreated(c.getCelestialBody().getGM());
335                     }
336                 }
337                 return true;
338 
339             case REF_FRAME:
340                 metaData.setRefFrame(parseCCSDSFrame(keyValue.getValue()).getFrame(getConventions(), isSimpleEOP()));
341                 return true;
342 
343             case REF_FRAME_EPOCH:
344                 metaData.setFrameEpochString(keyValue.getValue());
345                 return true;
346 
347             case TIME_SYSTEM:
348                 final OrbitFile.TimeSystem timeSystem = OrbitFile.TimeSystem.valueOf(keyValue.getValue());
349                 metaData.setTimeSystem(timeSystem);
350                 if (metaData.getFrameEpochString() != null) {
351                     metaData.setFrameEpoch(parseDate(metaData.getFrameEpochString(), timeSystem));
352                 }
353                 return true;
354 
355             default:
356                 return false;
357         }
358     }
359 
360     /** Parse a general state data key = value entry.
361      * @param keyValue key = value pair
362      * @param general instance to update with parsed entry
363      * @param comment previous comment lines, will be emptied if used by the keyword
364      * @return true if the keyword was a meta-data keyword and has been parsed
365      * @exception OrekitException if center body or frame cannot be retrieved
366      */
367     protected boolean parseGeneralStateDataEntry(final KeyValue keyValue,
368                                                  final OGMFile general, final List<String> comment)
369         throws OrekitException {
370         switch (keyValue.getKeyword()) {
371 
372             case EPOCH:
373                 general.setEpochComment(comment);
374                 comment.clear();
375                 general.setEpoch(parseDate(keyValue.getValue(), general.getTimeSystem()));
376                 return true;
377 
378             case SEMI_MAJOR_AXIS:
379                 general.setKeplerianElementsComment(comment);
380                 comment.clear();
381                 general.setA(keyValue.getDoubleValue() * 1000);
382                 general.setHasKeplerianElements(true);
383                 return true;
384 
385             case ECCENTRICITY:
386                 general.setE(keyValue.getDoubleValue());
387                 return true;
388 
389             case INCLINATION:
390                 general.setI(FastMath.toRadians(keyValue.getDoubleValue()));
391                 return true;
392 
393             case RA_OF_ASC_NODE:
394                 general.setRaan(FastMath.toRadians(keyValue.getDoubleValue()));
395                 return true;
396 
397             case ARG_OF_PERICENTER:
398                 general.setPa(FastMath.toRadians(keyValue.getDoubleValue()));
399                 return true;
400 
401             case TRUE_ANOMALY:
402                 general.setAnomalyType("TRUE");
403                 general.setAnomaly(FastMath.toRadians(keyValue.getDoubleValue()));
404                 return true;
405 
406             case MEAN_ANOMALY:
407                 general.setAnomalyType("MEAN");
408                 general.setAnomaly(FastMath.toRadians(keyValue.getDoubleValue()));
409                 return true;
410 
411             case GM:
412                 general.setMuParsed(keyValue.getDoubleValue() * 1e9);
413                 return true;
414 
415             case MASS:
416                 comment.addAll(0, general.getSpacecraftComment());
417                 general.setSpacecraftComment(comment);
418                 comment.clear();
419                 general.setMass(keyValue.getDoubleValue());
420                 return true;
421 
422             case SOLAR_RAD_AREA:
423                 comment.addAll(0, general.getSpacecraftComment());
424                 general.setSpacecraftComment(comment);
425                 comment.clear();
426                 general.setSolarRadArea(keyValue.getDoubleValue());
427                 return true;
428 
429             case SOLAR_RAD_COEFF:
430                 comment.addAll(0, general.getSpacecraftComment());
431                 general.setSpacecraftComment(comment);
432                 comment.clear();
433                 general.setSolarRadCoeff(keyValue.getDoubleValue());
434                 return true;
435 
436             case DRAG_AREA:
437                 comment.addAll(0, general.getSpacecraftComment());
438                 general.setSpacecraftComment(comment);
439                 comment.clear();
440                 general.setDragArea(keyValue.getDoubleValue());
441                 return true;
442 
443             case DRAG_COEFF:
444                 comment.addAll(0, general.getSpacecraftComment());
445                 general.setSpacecraftComment(comment);
446                 comment.clear();
447                 general.setDragCoeff(keyValue.getDoubleValue());
448                 return true;
449 
450             case COV_REF_FRAME:
451                 general.setCovarianceComment(comment);
452                 comment.clear();
453                 final CCSDSFrame covFrame = parseCCSDSFrame(keyValue.getValue());
454                 if (covFrame.isLof()) {
455                     general.setCovRefLofType(covFrame.getLofType());
456                 } else {
457                     general.setCovRefFrame(covFrame.getFrame(getConventions(), isSimpleEOP()));
458                 }
459                 return true;
460 
461             case CX_X:
462                 general.createCovarianceMatrix();
463                 general.setCovarianceMatrixEntry(0, 0, keyValue.getDoubleValue() * 1.0e6);
464                 return true;
465 
466             case CY_X:
467                 general.setCovarianceMatrixEntry(0, 1, keyValue.getDoubleValue() * 1.0e6);
468                 return true;
469 
470             case CY_Y:
471                 general.setCovarianceMatrixEntry(1, 1, keyValue.getDoubleValue() * 1.0e6);
472                 return true;
473 
474             case CZ_X:
475                 general.setCovarianceMatrixEntry(0, 2, keyValue.getDoubleValue() * 1.0e6);
476                 return true;
477 
478             case CZ_Y:
479                 general.setCovarianceMatrixEntry(1, 2, keyValue.getDoubleValue() * 1.0e6);
480                 return true;
481 
482             case CZ_Z:
483                 general.setCovarianceMatrixEntry(2, 2, keyValue.getDoubleValue() * 1.0e6);
484                 return true;
485 
486             case CX_DOT_X:
487                 general.setCovarianceMatrixEntry(0, 3, keyValue.getDoubleValue() * 1.0e6);
488                 return true;
489 
490             case CX_DOT_Y:
491                 general.setCovarianceMatrixEntry(1, 3, keyValue.getDoubleValue() * 1.0e6);
492                 return true;
493 
494             case CX_DOT_Z:
495                 general.setCovarianceMatrixEntry(2, 3, keyValue.getDoubleValue() * 1.0e6);
496                 return true;
497 
498             case CX_DOT_X_DOT:
499                 general.setCovarianceMatrixEntry(3, 3, keyValue.getDoubleValue() * 1.0e6);
500                 return true;
501 
502             case CY_DOT_X:
503                 general.setCovarianceMatrixEntry(0, 4, keyValue.getDoubleValue() * 1.0e6);
504                 return true;
505 
506             case CY_DOT_Y:
507                 general.setCovarianceMatrixEntry(1, 4, keyValue.getDoubleValue() * 1.0e6);
508                 return true;
509 
510             case CY_DOT_Z:
511                 general.setCovarianceMatrixEntry(2, 4, keyValue.getDoubleValue() * 1.0e6);
512                 return true;
513 
514             case CY_DOT_X_DOT:
515                 general.setCovarianceMatrixEntry(3, 4, keyValue.getDoubleValue() * 1.0e6);
516                 return true;
517 
518             case CY_DOT_Y_DOT:
519                 general.setCovarianceMatrixEntry(4, 4, keyValue.getDoubleValue() * 1.0e6);
520                 return true;
521 
522             case CZ_DOT_X:
523                 general.setCovarianceMatrixEntry(0, 5, keyValue.getDoubleValue() * 1.0e6);
524                 return true;
525 
526             case CZ_DOT_Y:
527                 general.setCovarianceMatrixEntry(1, 5, keyValue.getDoubleValue() * 1.0e6);
528                 return true;
529 
530             case CZ_DOT_Z:
531                 general.setCovarianceMatrixEntry(2, 5, keyValue.getDoubleValue() * 1.0e6);
532                 return true;
533 
534             case CZ_DOT_X_DOT:
535                 general.setCovarianceMatrixEntry(3, 5, keyValue.getDoubleValue() * 1.0e6);
536                 return true;
537 
538             case CZ_DOT_Y_DOT:
539                 general.setCovarianceMatrixEntry(4, 5, keyValue.getDoubleValue() * 1.0e6);
540                 return true;
541 
542             case CZ_DOT_Z_DOT:
543                 general.setCovarianceMatrixEntry(5, 5, keyValue.getDoubleValue() * 1.0e6);
544                 return true;
545 
546             case USER_DEFINED_X:
547                 general.setUserDefinedParameters(keyValue.getKey(), keyValue.getValue());
548                 return true;
549 
550             default:
551                 return false;
552         }
553     }
554 
555     /** Parse a CCSDS frame.
556      * @param frameName name of the frame, as the value of a CCSDS key=value line
557      * @return CCSDS frame corresponding to the name
558      */
559     protected CCSDSFrame parseCCSDSFrame(final String frameName) {
560         return CCSDSFrame.valueOf(frameName.replaceAll("-", ""));
561     }
562 
563     /** Parse a date.
564      * @param date date to parse, as the value of a CCSDS key=value line
565      * @param timeSystem time system to use
566      * @return parsed date
567      * @exception OrekitException if some time scale cannot be retrieved
568      */
569     protected AbsoluteDate parseDate(final String date, final OrbitFile.TimeSystem timeSystem)
570         throws OrekitException {
571         switch (timeSystem) {
572             case GMST:
573                 return new AbsoluteDate(date, TimeScalesFactory.getGMST(conventions, false));
574             case GPS:
575                 return new AbsoluteDate(date, TimeScalesFactory.getGPS());
576             case TAI:
577                 return new AbsoluteDate(date, TimeScalesFactory.getTAI());
578             case TCB:
579                 return new AbsoluteDate(date, TimeScalesFactory.getTCB());
580             case TDB:
581                 return new AbsoluteDate(date, TimeScalesFactory.getTDB());
582             case TCG:
583                 return new AbsoluteDate(date, TimeScalesFactory.getTCG());
584             case TT:
585                 return new AbsoluteDate(date, TimeScalesFactory.getTT());
586             case UT1:
587                 return new AbsoluteDate(date, TimeScalesFactory.getUT1(conventions, false));
588             case UTC:
589                 return new AbsoluteDate(date, TimeScalesFactory.getUTC());
590             case MET: {
591                 final DateTimeComponents clock = DateTimeComponents.parseDateTime(date);
592                 final double offset = clock.getDate().getYear() * Constants.JULIAN_YEAR +
593                         clock.getDate().getDayOfYear() * Constants.JULIAN_DAY +
594                         clock.getTime().getSecondsInUTCDay();
595                 return missionReferenceDate.shiftedBy(offset);
596             }
597             case MRT: {
598                 final DateTimeComponents clock = DateTimeComponents.parseDateTime(date);
599                 final double offset = clock.getDate().getYear() * Constants.JULIAN_YEAR +
600                         clock.getDate().getDayOfYear() * Constants.JULIAN_DAY +
601                         clock.getTime().getSecondsInUTCDay();
602                 return missionReferenceDate.shiftedBy(offset);
603             }
604             default:
605                 throw new OrekitInternalError(null);
606         }
607     }
608 
609 }