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