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.ccsds.ndm.odm.oem;
18  
19  import java.util.ArrayList;
20  import java.util.List;
21  import java.util.regex.Pattern;
22  
23  import org.orekit.data.DataContext;
24  import org.orekit.data.DataSource;
25  import org.orekit.errors.OrekitException;
26  import org.orekit.errors.OrekitMessages;
27  import org.orekit.files.ccsds.definitions.Units;
28  import org.orekit.files.ccsds.ndm.ParsedUnitsBehavior;
29  import org.orekit.files.ccsds.ndm.odm.CartesianCovariance;
30  import org.orekit.files.ccsds.ndm.odm.CartesianCovarianceKey;
31  import org.orekit.files.ccsds.ndm.odm.CommonMetadata;
32  import org.orekit.files.ccsds.ndm.odm.CommonMetadataKey;
33  import org.orekit.files.ccsds.ndm.odm.OdmParser;
34  import org.orekit.files.ccsds.ndm.odm.OdmMetadataKey;
35  import org.orekit.files.ccsds.ndm.odm.StateVector;
36  import org.orekit.files.ccsds.ndm.odm.StateVectorKey;
37  import org.orekit.files.ccsds.section.Header;
38  import org.orekit.files.ccsds.section.HeaderProcessingState;
39  import org.orekit.files.ccsds.section.KvnStructureProcessingState;
40  import org.orekit.files.ccsds.section.MetadataKey;
41  import org.orekit.files.ccsds.section.XmlStructureProcessingState;
42  import org.orekit.files.ccsds.utils.ContextBinding;
43  import org.orekit.files.ccsds.utils.FileFormat;
44  import org.orekit.files.ccsds.utils.lexical.ParseToken;
45  import org.orekit.files.ccsds.utils.lexical.TokenType;
46  import org.orekit.files.ccsds.utils.parsing.ProcessingState;
47  import org.orekit.files.general.EphemerisFileParser;
48  import org.orekit.time.AbsoluteDate;
49  import org.orekit.utils.IERSConventions;
50  import org.orekit.utils.units.Unit;
51  
52  /**
53   * A parser for the CCSDS OEM (Orbit Ephemeris Message).
54   * <p>
55   * Note than starting with Orekit 11.0, CCSDS message parsers are
56   * mutable objects that gather the data being parsed, until the
57   * message is complete and the {@link #parseMessage(org.orekit.data.DataSource)
58   * parseMessage} method has returned. This implies that parsers
59   * should <em>not</em> be used in a multi-thread context. The recommended
60   * way to use parsers is to either dedicate one parser for each message
61   * and drop it afterwards, or to use a single-thread loop.
62   * </p>
63   * @author sports
64   * @since 6.1
65   */
66  public class OemParser extends OdmParser<Oem, OemParser> implements EphemerisFileParser<Oem> {
67  
68      /** Comment marker. */
69      private static final String COMMENT = "COMMENT";
70  
71                      /** Pattern for splitting strings at blanks. */
72      private static final Pattern SPLIT_AT_BLANKS = Pattern.compile("\\s+");
73  
74      /** File header. */
75      private Header header;
76  
77      /** File segments. */
78      private List<OemSegment> segments;
79  
80      /** Metadata for current observation block. */
81      private OemMetadata metadata;
82  
83      /** Context binding valid for current metadata. */
84      private ContextBinding context;
85  
86      /** Current Ephemerides block being parsed. */
87      private OemData currentBlock;
88  
89      /** Indicator for covariance parsing. */
90      private boolean inCovariance;
91  
92      /** Current covariance matrix being parsed. */
93      private CartesianCovariance currentCovariance;
94  
95      /** Current row number in covariance matrix. */
96      private int currentRow;
97  
98      /** Default interpolation degree. */
99      private int defaultInterpolationDegree;
100 
101     /** Processor for global message structure. */
102     private ProcessingState structureProcessor;
103 
104     /** State vector logical block being read. */
105     private StateVector stateVectorBlock;
106 
107     /**
108      * Complete constructor.
109      * <p>
110      * Calling this constructor directly is not recommended. Users should rather use
111      * {@link org.orekit.files.ccsds.ndm.ParserBuilder#buildOemParser()
112      * parserBuilder.buildOemParser()}.
113      * </p>
114      * @param conventions IERS Conventions
115      * @param simpleEOP if true, tidal effects are ignored when interpolating EOP
116      * @param dataContext used to retrieve frames, time scales, etc.
117      * @param missionReferenceDate reference date for Mission Elapsed Time or Mission Relative Time time systems
118      * (may be null if time system is absolute)
119      * @param mu gravitational coefficient
120      * @param defaultInterpolationDegree default interpolation degree
121      * @param parsedUnitsBehavior behavior to adopt for handling parsed units
122      */
123     public OemParser(final IERSConventions conventions, final boolean simpleEOP,
124                      final DataContext dataContext,
125                      final AbsoluteDate missionReferenceDate, final double mu,
126                      final int defaultInterpolationDegree, final ParsedUnitsBehavior parsedUnitsBehavior) {
127         super(Oem.ROOT, Oem.FORMAT_VERSION_KEY, conventions, simpleEOP, dataContext,
128               missionReferenceDate, mu, parsedUnitsBehavior);
129         this.defaultInterpolationDegree  = defaultInterpolationDegree;
130     }
131 
132     /** {@inheritDoc} */
133     @Override
134     public Oem parse(final DataSource source) {
135         return parseMessage(source);
136     }
137 
138     /** {@inheritDoc} */
139     @Override
140     public Header getHeader() {
141         return header;
142     }
143 
144     /** {@inheritDoc} */
145     @Override
146     public void reset(final FileFormat fileFormat) {
147         header            = new Header(3.0);
148         segments          = new ArrayList<>();
149         metadata          = null;
150         context           = null;
151         currentBlock      = null;
152         inCovariance      = false;
153         currentCovariance = null;
154         currentRow        = -1;
155         if (fileFormat == FileFormat.XML) {
156             structureProcessor = new XmlStructureProcessingState(Oem.ROOT, this);
157             reset(fileFormat, structureProcessor);
158         } else {
159             structureProcessor = new KvnStructureProcessingState(this);
160             reset(fileFormat, new HeaderProcessingState(this));
161         }
162     }
163 
164     /** {@inheritDoc} */
165     @Override
166     public boolean prepareHeader() {
167         anticipateNext(new HeaderProcessingState(this));
168         return true;
169     }
170 
171     /** {@inheritDoc} */
172     @Override
173     public boolean inHeader() {
174         anticipateNext(structureProcessor);
175         return true;
176     }
177 
178     /** {@inheritDoc} */
179     @Override
180     public boolean finalizeHeader() {
181         header.validate(header.getFormatVersion());
182         return true;
183     }
184 
185     /** {@inheritDoc} */
186     @Override
187     public boolean prepareMetadata() {
188         if (currentBlock != null) {
189             // we have started a new segment, we need to finalize the previous one
190             finalizeData();
191         }
192         metadata = new OemMetadata(defaultInterpolationDegree);
193         context  = new ContextBinding(this::getConventions, this::isSimpleEOP,
194                                       this::getDataContext, this::getParsedUnitsBehavior,
195                                       this::getMissionReferenceDate,
196                                       metadata::getTimeSystem, () -> 0.0, () -> 1.0);
197         anticipateNext(this::processMetadataToken);
198         return true;
199     }
200 
201     /** {@inheritDoc} */
202     @Override
203     public boolean inMetadata() {
204         anticipateNext(structureProcessor);
205         return true;
206     }
207 
208     /** {@inheritDoc} */
209     @Override
210     public boolean finalizeMetadata() {
211         metadata.finalizeMetadata(context);
212         metadata.validate(header.getFormatVersion());
213         if (metadata.getCenter().getBody() != null) {
214             setMuCreated(metadata.getCenter().getBody().getGM());
215         }
216         anticipateNext(getFileFormat() == FileFormat.XML ? structureProcessor : this::processKvnDataToken);
217         return true;
218     }
219 
220     /** {@inheritDoc} */
221     @Override
222     public boolean prepareData() {
223         currentBlock = new OemData();
224         anticipateNext(getFileFormat() == FileFormat.XML ? this::processXmlSubStructureToken : this::processMetadataToken);
225         return true;
226     }
227 
228     /** {@inheritDoc} */
229     @Override
230     public boolean inData() {
231         anticipateNext(getFileFormat() == FileFormat.XML ? structureProcessor : this::processKvnCovarianceToken);
232         return true;
233     }
234 
235     /** {@inheritDoc} */
236     @Override
237     public boolean finalizeData() {
238         if (metadata != null) {
239             currentBlock.validate(header.getFormatVersion());
240             segments.add(new OemSegment(metadata, currentBlock, getSelectedMu()));
241         }
242         metadata          = null;
243         context           = null;
244         currentBlock      = null;
245         inCovariance      = false;
246         currentCovariance = null;
247         currentRow        = -1;
248         return true;
249     }
250 
251     /** {@inheritDoc} */
252     @Override
253     public Oem build() {
254         // OEM KVN file lack a DATA_STOP keyword, hence we can't call finalizeData()
255         // automatically before the end of the file
256         finalizeData();
257         final Oem file = new Oem(header, segments, getConventions(), getDataContext(), getSelectedMu());
258         file.checkTimeSystems();
259         return file;
260     }
261 
262     /** Manage state vector section in a XML message.
263      * @param starting if true, parser is entering the section
264      * otherwise it is leaving the section
265      * @return always return true
266      */
267     boolean manageXmlStateVectorSection(final boolean starting) {
268         if (starting) {
269             stateVectorBlock = new StateVector();
270             anticipateNext(this::processXmlStateVectorToken);
271         } else {
272             currentBlock.addData(stateVectorBlock.toTimeStampedPVCoordinates(),
273                                  stateVectorBlock.hasAcceleration());
274             stateVectorBlock = null;
275             anticipateNext(structureProcessor);
276         }
277         return true;
278     }
279 
280     /** Manage covariance matrix section.
281      * @param starting if true, parser is entering the section
282      * otherwise it is leaving the section
283      * @return always return true
284      */
285     boolean manageCovarianceSection(final boolean starting) {
286         if (starting) {
287             // save the current metadata for later retrieval of reference frame
288             final CommonMetadata savedMetadata = metadata;
289             currentCovariance = new CartesianCovariance(() -> savedMetadata.getReferenceFrame());
290             anticipateNext(getFileFormat() == FileFormat.XML ?
291                         this::processXmlCovarianceToken :
292                         this::processKvnCovarianceToken);
293         } else {
294             currentBlock.addCovarianceMatrix(currentCovariance);
295             currentCovariance = null;
296             anticipateNext(structureProcessor);
297         }
298         return true;
299     }
300 
301     /** Process one metadata token.
302      * @param token token to process
303      * @return true if token was processed, false otherwise
304      */
305     private boolean processMetadataToken(final ParseToken token) {
306         inMetadata();
307         try {
308             return token.getName() != null &&
309                    MetadataKey.valueOf(token.getName()).process(token, context, metadata);
310         } catch (IllegalArgumentException iaeM) {
311             try {
312                 return OdmMetadataKey.valueOf(token.getName()).process(token, context, metadata);
313             } catch (IllegalArgumentException iaeD) {
314                 try {
315                     return CommonMetadataKey.valueOf(token.getName()).process(token, context, metadata);
316                 } catch (IllegalArgumentException iaeC) {
317                     try {
318                         return OemMetadataKey.valueOf(token.getName()).process(token, context, metadata);
319                     } catch (IllegalArgumentException iaeE) {
320                         // token has not been recognized
321                         return false;
322                     }
323                 }
324             }
325         }
326     }
327 
328     /** Process one XML data substructure token.
329      * @param token token to process
330      * @return true if token was processed, false otherwise
331      */
332     private boolean processXmlSubStructureToken(final ParseToken token) {
333         if (COMMENT.equals(token.getName())) {
334             return token.getType() == TokenType.ENTRY ? currentBlock.addComment(token.getContentAsNormalizedString()) : true;
335         } else {
336             try {
337                 return token.getName() != null &&
338                                 OemDataSubStructureKey.valueOf(token.getName()).process(token, this);
339             } catch (IllegalArgumentException iae) {
340                 // token has not been recognized
341                 return false;
342             }
343         }
344     }
345 
346     /** Process one data token in a KVN message.
347      * @param token token to process
348      * @return true if token was processed, false otherwise
349      */
350     private boolean processKvnDataToken(final ParseToken token) {
351         if (currentBlock == null) {
352             // OEM KVN file lack a DATA_START keyword, hence we can't call prepareData()
353             // automatically before the first data token arrives
354             prepareData();
355         }
356         inData();
357         if (COMMENT.equals(token.getName())) {
358             return token.getType() == TokenType.ENTRY ? currentBlock.addComment(token.getContentAsNormalizedString()) : true;
359         } else if (token.getType() == TokenType.RAW_LINE) {
360             try {
361                 final String[] fields = SPLIT_AT_BLANKS.split(token.getRawContent().trim());
362                 if (fields.length != 7 && fields.length != 10) {
363                     throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
364                                               token.getLineNumber(), token.getFileName(), token.getContentAsNormalizedString());
365                 }
366                 stateVectorBlock = new StateVector();
367                 stateVectorBlock.setEpoch(context.getTimeSystem().getConverter(context).parse(fields[0]));
368                 stateVectorBlock.setP(0, Unit.KILOMETRE.toSI(Double.parseDouble(fields[1])));
369                 stateVectorBlock.setP(1, Unit.KILOMETRE.toSI(Double.parseDouble(fields[2])));
370                 stateVectorBlock.setP(2, Unit.KILOMETRE.toSI(Double.parseDouble(fields[3])));
371                 stateVectorBlock.setV(0, Units.KM_PER_S.toSI(Double.parseDouble(fields[4])));
372                 stateVectorBlock.setV(1, Units.KM_PER_S.toSI(Double.parseDouble(fields[5])));
373                 stateVectorBlock.setV(2, Units.KM_PER_S.toSI(Double.parseDouble(fields[6])));
374                 if (fields.length == 10) {
375                     stateVectorBlock.setA(0, Units.KM_PER_S2.toSI(Double.parseDouble(fields[7])));
376                     stateVectorBlock.setA(1, Units.KM_PER_S2.toSI(Double.parseDouble(fields[8])));
377                     stateVectorBlock.setA(2, Units.KM_PER_S2.toSI(Double.parseDouble(fields[9])));
378                 }
379                 return currentBlock.addData(stateVectorBlock.toTimeStampedPVCoordinates(),
380                                             stateVectorBlock.hasAcceleration());
381             } catch (NumberFormatException nfe) {
382                 throw new OrekitException(nfe, OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
383                                           token.getLineNumber(), token.getFileName(), token.getRawContent());
384             }
385         } else {
386             // not a raw line, it is most probably either the end of the data section or a covariance section
387             return false;
388         }
389     }
390 
391     /** Process one state vector data token in a XML message.
392      * @param token token to process
393      * @return true if token was processed, false otherwise
394      */
395     private boolean processXmlStateVectorToken(final ParseToken token) {
396         anticipateNext(this::processXmlSubStructureToken);
397         try {
398             return token.getName() != null &&
399                    StateVectorKey.valueOf(token.getName()).process(token, context, stateVectorBlock);
400         } catch (IllegalArgumentException iae) {
401             // token has not been recognized
402             return false;
403         }
404     }
405 
406     /** Process one covariance token in a KVN message.
407      * @param token token to process
408      * @return true if token was processed, false otherwise
409      */
410     private boolean processKvnCovarianceToken(final ParseToken token) {
411         anticipateNext(getFileFormat() == FileFormat.XML ? structureProcessor : this::processMetadataToken);
412         if (token.getName() != null) {
413             if (OemDataSubStructureKey.COVARIANCE.name().equals(token.getName()) ||
414                 OemDataSubStructureKey.covarianceMatrix.name().equals(token.getName())) {
415                 // we are entering/leaving covariance section
416                 inCovariance = token.getType() == TokenType.START;
417                 return true;
418             } else if (!inCovariance) {
419                 // this is not a covariance token
420                 return false;
421             } else {
422                 // named tokens in covariance section must be at the start, before the raw lines
423                 if (currentRow > 0) {
424                     // the previous covariance matrix was not completed
425                     throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_ELEMENT_IN_FILE,
426                                               token.getName(), token.getLineNumber(), token.getFileName());
427                 }
428 
429                 if (currentCovariance == null) {
430                     // save the current metadata for later retrieval of reference frame
431                     final CommonMetadata savedMetadata = metadata;
432                     currentCovariance = new CartesianCovariance(() -> savedMetadata.getReferenceFrame());
433                     currentRow        = 0;
434                 }
435 
436                 // parse the token
437                 try {
438                     return CartesianCovarianceKey.valueOf(token.getName()).
439                            process(token, context, currentCovariance);
440                 } catch (IllegalArgumentException iae) {
441                     // token not recognized
442                     return false;
443                 }
444 
445             }
446         } else {
447             // this is a raw line
448             try {
449                 final String[] fields = SPLIT_AT_BLANKS.split(token.getContentAsNormalizedString().trim());
450                 if (fields.length != currentRow + 1) {
451                     throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
452                                               token.getLineNumber(), token.getFileName(), token.getContentAsNormalizedString());
453                 }
454                 for (int j = 0; j < fields.length; ++j) {
455                     currentCovariance.setCovarianceMatrixEntry(currentRow, j, 1.0e6 * Double.parseDouble(fields[j]));
456                 }
457                 if (++currentRow == 6) {
458                     // this was the last row
459                     currentBlock.addCovarianceMatrix(currentCovariance);
460                     currentCovariance = null;
461                     currentRow        = -1;
462                 }
463                 return true;
464             } catch (NumberFormatException nfe) {
465                 throw new OrekitException(nfe, OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
466                                           token.getLineNumber(), token.getFileName(), token.getContentAsNormalizedString());
467             }
468         }
469     }
470 
471     /** Process one covariance matrix data token in a XML message.
472      * @param token token to process
473      * @return true if token was processed, false otherwise
474      */
475     private boolean processXmlCovarianceToken(final ParseToken token) {
476         anticipateNext(this::processXmlSubStructureToken);
477         try {
478             return token.getName() != null &&
479                    CartesianCovarianceKey.valueOf(token.getName()).process(token, context, currentCovariance);
480         } catch (IllegalArgumentException iae) {
481             // token has not been recognized
482             return false;
483         }
484     }
485 
486 }