1   /* Copyright 2002-2013 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.BufferedReader;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.InputStreamReader;
23  import java.util.ArrayList;
24  import java.util.List;
25  
26  import org.apache.commons.math3.exception.util.DummyLocalizable;
27  import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
28  import org.orekit.errors.OrekitException;
29  import org.orekit.errors.OrekitMessages;
30  import org.orekit.files.general.OrbitFileParser;
31  import org.orekit.time.AbsoluteDate;
32  import org.orekit.utils.IERSConventions;
33  
34  /** A parser for the CCSDS OPM (Orbit Parameter Message).
35   * @author sports
36   * @since 6.1
37   */
38  public class OPMParser extends ODMParser implements OrbitFileParser {
39  
40      /** Simple constructor.
41       * <p>
42       * This class is immutable, and hence thread safe. When parts
43       * must be changed, such as reference date for Mission Elapsed Time or
44       * Mission Relative Time time systems, or the gravitational coefficient or
45       * the IERS conventions, the various {@code withXxx} methods must be called,
46       * which create a new immutable instance with the new parameters. This
47       * is a combination of the <a href="">builder design pattern</a> and
48       * a <a href="http://en.wikipedia.org/wiki/Fluent_interface">fluent
49       * interface</a>.
50       * </p>
51       * <p>
52       * The initial date for Mission Elapsed Time and Mission Relative Time time systems is not set here.
53       * If such time systems are used, it must be initialized before parsing by calling {@link
54       * #withMissionReferenceDate(AbsoluteDate)}.
55       * </p>
56       * <p>
57       * The gravitational coefficient is not set here. If it is needed in order
58       * to parse Cartesian orbits where the value is not set in the CCSDS file, it must
59       * be initialized before parsing by calling {@link #withMu(double)}.
60       * </p>
61       * <p>
62       * The IERS conventions to use is not set here. If it is needed in order to
63       * parse some reference frames or UT1 time scale, it must be initialized before
64       * parsing by calling {@link #withConventions(IERSConventions)}.
65       * </p>
66       */
67      public OPMParser() {
68          this(AbsoluteDate.FUTURE_INFINITY, Double.NaN, null, true, 0, 0, "");
69      }
70  
71      /** Complete constructor.
72       * @param missionReferenceDate reference date for Mission Elapsed Time or Mission Relative Time time systems
73       * @param mu gravitational coefficient
74       * @param conventions IERS Conventions
75       * @param simpleEOP if true, tidal effects are ignored when interpolating EOP
76       * @param launchYear launch year for TLEs
77       * @param launchNumber launch number for TLEs
78       * @param launchPiece piece of launch (from "A" to "ZZZ") for TLEs
79       */
80      private OPMParser(final AbsoluteDate missionReferenceDate, final double mu,
81                        final IERSConventions conventions, final boolean simpleEOP,
82                        final int launchYear, final int launchNumber, final String launchPiece) {
83          super(missionReferenceDate, mu, conventions, simpleEOP, launchYear, launchNumber, launchPiece);
84      }
85  
86      /** {@inheritDoc} */
87      public OPMParser withMissionReferenceDate(final AbsoluteDate newMissionReferenceDate) {
88          return new OPMParser(newMissionReferenceDate, getMu(), getConventions(), isSimpleEOP(),
89                               getLaunchYear(), getLaunchNumber(), getLaunchPiece());
90      }
91  
92      /** {@inheritDoc} */
93      public OPMParser withMu(final double newMu) {
94          return new OPMParser(getMissionReferenceDate(), newMu, getConventions(), isSimpleEOP(),
95                               getLaunchYear(), getLaunchNumber(), getLaunchPiece());
96      }
97  
98      /** {@inheritDoc} */
99      public OPMParser withConventions(final IERSConventions newConventions) {
100         return new OPMParser(getMissionReferenceDate(), getMu(), newConventions, isSimpleEOP(),
101                              getLaunchYear(), getLaunchNumber(), getLaunchPiece());
102     }
103 
104     /** {@inheritDoc} */
105     public OPMParser withSimpleEOP(final boolean newSimpleEOP) {
106         return new OPMParser(getMissionReferenceDate(), getMu(), getConventions(), newSimpleEOP,
107                              getLaunchYear(), getLaunchNumber(), getLaunchPiece());
108     }
109 
110     /** {@inheritDoc} */
111     public OPMParser withInternationalDesignator(final int newLaunchYear,
112                                                  final int newLaunchNumber,
113                                                  final String newLaunchPiece) {
114         return new OPMParser(getMissionReferenceDate(), getMu(), getConventions(), isSimpleEOP(),
115                              newLaunchYear, newLaunchNumber, newLaunchPiece);
116     }
117 
118     /** {@inheritDoc} */
119     @Override
120     public OPMFile parse(final String fileName) throws OrekitException {
121         return (OPMFile) super.parse(fileName);
122     }
123 
124     /** {@inheritDoc} */
125     public OPMFile parse(final InputStream stream, final String fileName) throws OrekitException {
126 
127         try {
128             final BufferedReader reader = new BufferedReader(new InputStreamReader(stream, "UTF-8"));
129             // initialize internal data structures
130             final ParseInfo pi = new ParseInfo();
131             pi.fileName = fileName;
132             final OPMFile file = pi.file;
133 
134             // set the additional data that has been configured prior the parsing by the user.
135             pi.file.setMissionReferenceDate(getMissionReferenceDate());
136             pi.file.setMuSet(getMu());
137             pi.file.setConventions(getConventions());
138             pi.file.getMetaData().setLaunchYear(getLaunchYear());
139             pi.file.getMetaData().setLaunchNumber(getLaunchNumber());
140             pi.file.getMetaData().setLaunchPiece(getLaunchPiece());
141 
142             for (String line = reader.readLine(); line != null; line = reader.readLine()) {
143                 ++pi.lineNumber;
144                 if (line.trim().length() == 0) {
145                     continue;
146                 }
147                 pi.keyValue = new KeyValue(line, pi.lineNumber, pi.fileName);
148                 if (pi.keyValue.getKeyword() == null) {
149                     throw new OrekitException(OrekitMessages.CCSDS_UNEXPECTED_KEYWORD, pi.lineNumber, pi.fileName, line);
150                 }
151                 switch (pi.keyValue.getKeyword()) {
152 
153                 case CCSDS_OPM_VERS:
154                     file.setFormatVersion(pi.keyValue.getDoubleValue());
155                     break;
156 
157                 case X:
158                     pi.x = pi.keyValue.getDoubleValue() * 1000;
159                     break;
160 
161                 case Y:
162                     pi.y = pi.keyValue.getDoubleValue() * 1000;
163                     break;
164 
165                 case Z:
166                     pi.z = pi.keyValue.getDoubleValue() * 1000;
167                     break;
168 
169                 case X_DOT:
170                     pi.x_dot = pi.keyValue.getDoubleValue() * 1000;
171                     break;
172 
173                 case Y_DOT:
174                     pi.y_dot = pi.keyValue.getDoubleValue() * 1000;
175                     break;
176 
177                 case Z_DOT:
178                     pi.z_dot = pi.keyValue.getDoubleValue() * 1000;
179                     break;
180 
181                 case MAN_EPOCH_IGNITION:
182                     if (pi.maneuver != null) {
183                         file.addManeuver(pi.maneuver);
184                     }
185                     pi.maneuver = new OPMFile.Maneuver();
186                     pi.maneuver.setEpochIgnition(parseDate(pi.keyValue.getValue(), file.getTimeSystem()));
187                     if (!pi.commentTmp.isEmpty()) {
188                         pi.maneuver.setComment(pi.commentTmp);
189                         pi.commentTmp.clear();
190                     }
191                     break;
192 
193                 case MAN_DURATION:
194                     pi.maneuver.setDuration(pi.keyValue.getDoubleValue());
195                     break;
196 
197                 case MAN_DELTA_MASS:
198                     pi.maneuver.setDeltaMass(pi.keyValue.getDoubleValue());
199                     break;
200 
201                 case MAN_REF_FRAME:
202                     final CCSDSFrame manFrame = parseCCSDSFrame(pi.keyValue.getValue());
203                     if (manFrame.isLof()) {
204                         pi.maneuver.setRefLofType(manFrame.getLofType());
205                     } else {
206                         pi.maneuver.setRefFrame(manFrame.getFrame(getConventions(), isSimpleEOP()));
207                     }
208                     break;
209 
210                 case MAN_DV_1:
211                     pi.maneuver.setdV(new Vector3D(pi.keyValue.getDoubleValue() * 1000,
212                                                    pi.maneuver.getDV().getY(),
213                                                    pi.maneuver.getDV().getZ()));
214                     break;
215 
216                 case MAN_DV_2:
217                     pi.maneuver.setdV(new Vector3D(pi.maneuver.getDV().getX(),
218                                                    pi.keyValue.getDoubleValue() * 1000,
219                                                    pi.maneuver.getDV().getZ()));
220                     break;
221 
222                 case MAN_DV_3:
223                     pi.maneuver.setdV(new Vector3D(pi.maneuver.getDV().getX(),
224                                                    pi.maneuver.getDV().getY(),
225                                                    pi.keyValue.getDoubleValue() * 1000));
226                     break;
227 
228                 default:
229                     boolean parsed = false;
230                     parsed = parsed || parseComment(pi.keyValue, pi.commentTmp);
231                     parsed = parsed || parseHeaderEntry(pi.keyValue, file, pi.commentTmp);
232                     parsed = parsed || parseMetaDataEntry(pi.keyValue, file.getMetaData(), pi.commentTmp);
233                     parsed = parsed || parseGeneralStateDataEntry(pi.keyValue, file, pi.commentTmp);
234                     if (!parsed) {
235                         throw new OrekitException(OrekitMessages.CCSDS_UNEXPECTED_KEYWORD, pi.lineNumber, pi.fileName, line);
236                     }
237                 }
238 
239             }
240 
241             file.setPosition(new Vector3D(pi.x, pi.y, pi.z));
242             file.setVelocity(new Vector3D(pi.x_dot, pi.y_dot, pi.z_dot));
243             if (pi.maneuver != null) {
244                 file.addManeuver(pi.maneuver);
245             }
246             reader.close();
247             return file;
248         } catch (IOException ioe) {
249             throw new OrekitException(ioe, new DummyLocalizable(ioe.getMessage()));
250         }
251     }
252 
253     /** Private class used to stock OPM parsing info.
254      * @author sports
255      */
256     private static class ParseInfo {
257 
258         /** OPM file being read. */
259         private OPMFile file;
260 
261         /** Name of the file. */
262         private String fileName;
263 
264         /** Current line number. */
265         private int lineNumber;
266 
267         /** Key value of the line being read. */
268         private KeyValue keyValue;
269 
270         /** Stored comments. */
271         private List<String> commentTmp;
272 
273         /** First component of position vector. */
274         private double x;
275 
276         /** Second component of position vector. */
277         private double y;
278 
279         /** Third component of position vector. */
280         private double z;
281 
282         /** First component of velocity vector. */
283         private double x_dot;
284 
285         /** Second component of velocity vector. */
286         private double y_dot;
287 
288         /** Third component of velocity vector. */
289         private double z_dot;
290 
291         /** Current maneuver. */
292         private OPMFile.Maneuver maneuver;
293 
294         /** Create a new {@link ParseInfo} object. */
295         protected ParseInfo() {
296             file       = new OPMFile();
297             lineNumber = 0;
298             commentTmp = new ArrayList<String>();
299             maneuver   = null;
300         }
301     }
302 }