OEMParser.java
/* Copyright 2002-2020 CS GROUP
* Licensed to CS GROUP (CS) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* CS licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.orekit.files.ccsds;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import org.hipparchus.exception.DummyLocalizable;
import org.hipparchus.geometry.euclidean.threed.Vector3D;
import org.hipparchus.linear.MatrixUtils;
import org.hipparchus.linear.RealMatrix;
import org.orekit.annotation.DefaultDataContext;
import org.orekit.data.DataContext;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.files.general.EphemerisFileParser;
import org.orekit.frames.Frame;
import org.orekit.frames.LOFType;
import org.orekit.time.AbsoluteDate;
import org.orekit.utils.IERSConventions;
import org.orekit.utils.TimeStampedPVCoordinates;
/**
* A parser for the CCSDS OEM (Orbit Ephemeris Message).
* @author sports
* @since 6.1
*/
public class OEMParser extends ODMParser implements EphemerisFileParser {
/** Default interpolation degree. */
private int interpolationDegree;
/** Simple constructor.
* <p>
* This class is immutable, and hence thread safe. When parts
* must be changed, such as reference date for Mission Elapsed Time or
* Mission Relative Time time systems, or the gravitational coefficient or
* the IERS conventions, the various {@code withXxx} methods must be called,
* which create a new immutable instance with the new parameters. This
* is a combination of the
* <a href="https://en.wikipedia.org/wiki/Builder_pattern">builder design
* pattern</a> and a
* <a href="http://en.wikipedia.org/wiki/Fluent_interface">fluent
* interface</a>.
* </p>
* <p>
* The initial date for Mission Elapsed Time and Mission Relative Time time systems is not set here.
* If such time systems are used, it must be initialized before parsing by calling {@link
* #withMissionReferenceDate(AbsoluteDate)}.
* </p>
* <p>
* The gravitational coefficient is not set here. If it is needed in order
* to parse Cartesian orbits where the value is not set in the CCSDS file, it must
* be initialized before parsing by calling {@link #withMu(double)}.
* </p>
* <p>
* The IERS conventions to use is not set here. If it is needed in order to
* parse some reference frames or UT1 time scale, it must be initialized before
* parsing by calling {@link #withConventions(IERSConventions)}.
* </p>
* <p>
* The international designator parameters (launch year, launch number and
* launch piece) are not set here. If they are needed, they must be initialized before
* parsing by calling {@link #withInternationalDesignator(int, int, String)}
* </p>
* <p>
* The default interpolation degree is not set here. It is set to one by default. If another value
* is needed it must be initialized before parsing by calling {@link #withInterpolationDegree(int)}
* </p>
*
* <p>This method uses the {@link DataContext#getDefault() default data context}. See
* {@link #withDataContext(DataContext)}.
*/
@DefaultDataContext
public OEMParser() {
this(DataContext.getDefault());
}
/** Constructor with data context.
* <p>
* This class is immutable, and hence thread safe. When parts
* must be changed, such as reference date for Mission Elapsed Time or
* Mission Relative Time time systems, or the gravitational coefficient or
* the IERS conventions, the various {@code withXxx} methods must be called,
* which create a new immutable instance with the new parameters. This
* is a combination of the
* <a href="https://en.wikipedia.org/wiki/Builder_pattern">builder design
* pattern</a> and a
* <a href="http://en.wikipedia.org/wiki/Fluent_interface">fluent
* interface</a>.
* </p>
* <p>
* The initial date for Mission Elapsed Time and Mission Relative Time time systems is not set here.
* If such time systems are used, it must be initialized before parsing by calling {@link
* #withMissionReferenceDate(AbsoluteDate)}.
* </p>
* <p>
* The gravitational coefficient is not set here. If it is needed in order
* to parse Cartesian orbits where the value is not set in the CCSDS file, it must
* be initialized before parsing by calling {@link #withMu(double)}.
* </p>
* <p>
* The IERS conventions to use is not set here. If it is needed in order to
* parse some reference frames or UT1 time scale, it must be initialized before
* parsing by calling {@link #withConventions(IERSConventions)}.
* </p>
* <p>
* The international designator parameters (launch year, launch number and
* launch piece) are not set here. If they are needed, they must be initialized before
* parsing by calling {@link #withInternationalDesignator(int, int, String)}
* </p>
* <p>
* The default interpolation degree is not set here. It is set to one by default. If another value
* is needed it must be initialized before parsing by calling {@link #withInterpolationDegree(int)}
* </p>
*
* @param dataContext used by the parser.
*
* @see #OEMParser()
* @see #withDataContext(DataContext)
* @since 10.1
*/
public OEMParser(final DataContext dataContext) {
this(AbsoluteDate.FUTURE_INFINITY, Double.NaN, null, true, 0, 0, "", 1, dataContext);
}
/** Complete constructor.
* @param missionReferenceDate reference date for Mission Elapsed Time or Mission Relative Time time systems
* @param mu gravitational coefficient
* @param conventions IERS Conventions
* @param simpleEOP if true, tidal effects are ignored when interpolating EOP
* @param launchYear launch year for TLEs
* @param launchNumber launch number for TLEs
* @param launchPiece piece of launch (from "A" to "ZZZ") for TLEs
* @param interpolationDegree interpolation degree
* @param dataContext used to retrieve frames, time scales, etc.
*/
private OEMParser(final AbsoluteDate missionReferenceDate, final double mu,
final IERSConventions conventions, final boolean simpleEOP,
final int launchYear, final int launchNumber,
final String launchPiece, final int interpolationDegree,
final DataContext dataContext) {
super(missionReferenceDate, mu, conventions, simpleEOP, launchYear, launchNumber,
launchPiece, dataContext);
this.interpolationDegree = interpolationDegree;
}
/** {@inheritDoc} */
@Override
public OEMParser withMissionReferenceDate(final AbsoluteDate newMissionReferenceDate) {
return new OEMParser(newMissionReferenceDate, getMu(), getConventions(), isSimpleEOP(),
getLaunchYear(), getLaunchNumber(), getLaunchPiece(),
getInterpolationDegree(), getDataContext());
}
/** {@inheritDoc} */
@Override
public OEMParser withMu(final double newMu) {
return new OEMParser(getMissionReferenceDate(), newMu, getConventions(), isSimpleEOP(),
getLaunchYear(), getLaunchNumber(), getLaunchPiece(),
getInterpolationDegree(), getDataContext());
}
/** {@inheritDoc} */
@Override
public OEMParser withConventions(final IERSConventions newConventions) {
return new OEMParser(getMissionReferenceDate(), getMu(), newConventions, isSimpleEOP(),
getLaunchYear(), getLaunchNumber(), getLaunchPiece(),
getInterpolationDegree(), getDataContext());
}
/** {@inheritDoc} */
@Override
public OEMParser withSimpleEOP(final boolean newSimpleEOP) {
return new OEMParser(getMissionReferenceDate(), getMu(), getConventions(), newSimpleEOP,
getLaunchYear(), getLaunchNumber(), getLaunchPiece(),
getInterpolationDegree(), getDataContext());
}
/** {@inheritDoc} */
@Override
public OEMParser withInternationalDesignator(final int newLaunchYear,
final int newLaunchNumber,
final String newLaunchPiece) {
return new OEMParser(getMissionReferenceDate(), getMu(), getConventions(), isSimpleEOP(),
newLaunchYear, newLaunchNumber, newLaunchPiece,
getInterpolationDegree(), getDataContext());
}
/** {@inheritDoc} */
@Override
public OEMParser withDataContext(final DataContext dataContext) {
return new OEMParser(getMissionReferenceDate(), getMu(), getConventions(), isSimpleEOP(),
getLaunchYear(), getLaunchNumber(), getLaunchPiece(),
getInterpolationDegree(), dataContext);
}
/** Set default interpolation degree.
* <p>
* This method may be used to set a default interpolation degree which will be used
* when no interpolation degree is parsed in the meta-data of the file. Upon instantiation
* with {@link #OEMParser(DataContext)} the default interpolation degree is one.
* </p>
* @param newInterpolationDegree default interpolation degree to use while parsing
* @return a new instance, with interpolation degree data replaced
* @see #getInterpolationDegree()
* @since 10.3
*/
public OEMParser withInterpolationDegree(final int newInterpolationDegree) {
return new OEMParser(getMissionReferenceDate(), getMu(), getConventions(), isSimpleEOP(),
getLaunchYear(), getLaunchNumber(), getLaunchPiece(),
newInterpolationDegree, getDataContext());
}
/** Get default interpolation degree.
* @return interpolationDegree default interpolation degree to use while parsing
* @see #withInterpolationDegree(int)
* @since 10.3
*/
public int getInterpolationDegree() {
return interpolationDegree;
}
/** {@inheritDoc} */
@Override
public OEMFile parse(final String fileName) {
return (OEMFile) super.parse(fileName);
}
/** {@inheritDoc} */
@Override
public OEMFile parse(final InputStream stream) {
return (OEMFile) super.parse(stream);
}
/** {@inheritDoc} */
@Override
public OEMFile parse(final InputStream stream, final String fileName) {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) {
return parse(reader, fileName);
} catch (IOException ioe) {
throw new OrekitException(ioe, new DummyLocalizable(ioe.getMessage()));
}
}
/** {@inheritDoc} */
@Override
public OEMFile parse(final BufferedReader reader, final String fileName) {
try {
// initialize internal data structures
final ParseInfo pi = new ParseInfo();
pi.fileName = fileName;
final OEMFile file = pi.file;
// set the additional data that has been configured prior the parsing by the user.
pi.file.setMissionReferenceDate(getMissionReferenceDate());
pi.file.setMuSet(getMu());
pi.file.setConventions(getConventions());
pi.file.setDataContext(getDataContext());
for (String line = reader.readLine(); line != null; line = reader.readLine()) {
++pi.lineNumber;
if (line.trim().length() == 0) {
continue;
}
pi.keyValue = new KeyValue(line, pi.lineNumber, pi.fileName);
if (pi.keyValue.getKeyword() == null) {
throw new OrekitException(OrekitMessages.CCSDS_UNEXPECTED_KEYWORD, pi.lineNumber, pi.fileName, line);
}
switch (pi.keyValue.getKeyword()) {
case CCSDS_OEM_VERS:
file.setFormatVersion(pi.keyValue.getDoubleValue());
break;
case META_START:
file.addEphemeridesBlock();
pi.lastEphemeridesBlock = file.getEphemeridesBlocks().get(file.getEphemeridesBlocks().size() - 1);
pi.lastEphemeridesBlock.getMetaData().setLaunchYear(getLaunchYear());
pi.lastEphemeridesBlock.getMetaData().setLaunchNumber(getLaunchNumber());
pi.lastEphemeridesBlock.getMetaData().setLaunchPiece(getLaunchPiece());
pi.lastEphemeridesBlock.setInterpolationDegree(getInterpolationDegree());
break;
case START_TIME:
pi.lastEphemeridesBlock.setStartTime(parseDate(pi.keyValue.getValue(),
pi.lastEphemeridesBlock.getMetaData().getTimeSystem()));
break;
case USEABLE_START_TIME:
pi.lastEphemeridesBlock.setUseableStartTime(parseDate(pi.keyValue.getValue(),
pi.lastEphemeridesBlock.getMetaData().getTimeSystem()));
break;
case USEABLE_STOP_TIME:
pi.lastEphemeridesBlock.setUseableStopTime(parseDate(pi.keyValue.getValue(), pi.lastEphemeridesBlock.getMetaData().getTimeSystem()));
break;
case STOP_TIME:
pi.lastEphemeridesBlock.setStopTime(parseDate(pi.keyValue.getValue(), pi.lastEphemeridesBlock.getMetaData().getTimeSystem()));
break;
case INTERPOLATION:
pi.lastEphemeridesBlock.setInterpolationMethod(pi.keyValue.getValue());
break;
case INTERPOLATION_DEGREE:
pi.lastEphemeridesBlock.setInterpolationDegree(Integer.parseInt(pi.keyValue.getValue()));
break;
case META_STOP:
file.setMuUsed();
parseEphemeridesDataLines(reader, pi);
break;
case COVARIANCE_START:
parseCovarianceDataLines(reader, pi);
break;
default:
boolean parsed = false;
parsed = parsed || parseComment(pi.keyValue, pi.commentTmp);
parsed = parsed || parseHeaderEntry(pi.keyValue, file, pi.commentTmp);
if (pi.lastEphemeridesBlock != null) {
parsed = parsed || parseMetaDataEntry(pi.keyValue,
pi.lastEphemeridesBlock.getMetaData(), pi.commentTmp);
if (parsed && pi.keyValue.getKeyword() == Keyword.REF_FRAME_EPOCH) {
pi.lastEphemeridesBlock.setHasRefFrameEpoch(true);
}
}
if (!parsed) {
throw new OrekitException(OrekitMessages.CCSDS_UNEXPECTED_KEYWORD, pi.lineNumber, pi.fileName, line);
}
}
}
file.checkTimeSystems();
return file;
} catch (IOException ioe) {
throw new OrekitException(ioe, new DummyLocalizable(ioe.getMessage()));
}
}
/**
* Parse an ephemeris data line and add its content to the ephemerides
* block.
*
* @param reader the reader
* @param pi the parser info
* @exception IOException if an error occurs while reading from the stream
*/
private void parseEphemeridesDataLines(final BufferedReader reader, final ParseInfo pi)
throws IOException {
for (String line = reader.readLine(); line != null; line = reader.readLine()) {
++pi.lineNumber;
if (line.trim().length() > 0) {
pi.keyValue = new KeyValue(line, pi.lineNumber, pi.fileName);
if (pi.keyValue.getKeyword() == null) {
try (Scanner sc = new Scanner(line)) {
final AbsoluteDate date = parseDate(sc.next(), pi.lastEphemeridesBlock.getMetaData().getTimeSystem());
final Vector3D position = new Vector3D(Double.parseDouble(sc.next()) * 1000,
Double.parseDouble(sc.next()) * 1000,
Double.parseDouble(sc.next()) * 1000);
final Vector3D velocity = new Vector3D(Double.parseDouble(sc.next()) * 1000,
Double.parseDouble(sc.next()) * 1000,
Double.parseDouble(sc.next()) * 1000);
Vector3D acceleration = Vector3D.NaN;
boolean hasAcceleration = false;
if (sc.hasNext()) {
acceleration = new Vector3D(Double.parseDouble(sc.next()) * 1000,
Double.parseDouble(sc.next()) * 1000,
Double.parseDouble(sc.next()) * 1000);
hasAcceleration = true;
}
final TimeStampedPVCoordinates epDataLine;
if (hasAcceleration) {
epDataLine = new TimeStampedPVCoordinates(date, position, velocity, acceleration);
} else {
epDataLine = new TimeStampedPVCoordinates(date, position, velocity);
}
pi.lastEphemeridesBlock.getEphemeridesDataLines().add(epDataLine);
pi.lastEphemeridesBlock.updateHasAcceleration(hasAcceleration);
} catch (NumberFormatException nfe) {
throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
pi.lineNumber, pi.fileName, line);
}
} else {
switch (pi.keyValue.getKeyword()) {
case META_START:
pi.lastEphemeridesBlock.setEphemeridesDataLinesComment(pi.commentTmp);
pi.commentTmp.clear();
pi.lineNumber--;
reader.reset();
return;
case COVARIANCE_START:
pi.lastEphemeridesBlock.setEphemeridesDataLinesComment(pi.commentTmp);
pi.commentTmp.clear();
pi.lineNumber--;
reader.reset();
return;
case COMMENT:
pi.commentTmp.add(pi.keyValue.getValue());
break;
default :
throw new OrekitException(OrekitMessages.CCSDS_UNEXPECTED_KEYWORD, pi.lineNumber, pi.fileName, line);
}
}
}
reader.mark(300);
}
}
/**
* Parse the covariance data lines, create a set of CovarianceMatrix objects
* and add them in the covarianceMatrices list of the ephemerides block.
*
* @param reader the reader
* @param pi the parser info
* @throws IOException if an error occurs while reading from the stream
*/
private void parseCovarianceDataLines(final BufferedReader reader, final ParseInfo pi)
throws IOException {
int i = 0;
for (String line = reader.readLine(); line != null; line = reader.readLine()) {
++pi.lineNumber;
if (line.trim().length() == 0) {
continue;
}
pi.keyValue = new KeyValue(line, pi.lineNumber, pi.fileName);
if (pi.keyValue.getKeyword() == null) {
try (Scanner sc = new Scanner(line)) {
for (int j = 0; j < i + 1; j++) {
pi.lastMatrix.addToEntry(i, j, Double.parseDouble(sc.next()));
if (j != i) {
pi.lastMatrix.addToEntry(j, i, pi.lastMatrix.getEntry(i, j));
}
}
if (i == 5) {
final OEMFile.CovarianceMatrix cm =
new OEMFile.CovarianceMatrix(pi.epoch, pi.covRefLofType, pi.covRefFrame, pi.lastMatrix);
pi.lastEphemeridesBlock.getCovarianceMatrices().add(cm);
}
i++;
} catch (NumberFormatException nfe) {
throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
pi.lineNumber, pi.fileName, line);
}
} else {
switch (pi.keyValue.getKeyword()) {
case EPOCH :
i = 0;
pi.covRefLofType = null;
pi.covRefFrame = null;
pi.lastMatrix = MatrixUtils.createRealMatrix(6, 6);
pi.epoch = parseDate(pi.keyValue.getValue(), pi.lastEphemeridesBlock.getMetaData().getTimeSystem());
break;
case COV_REF_FRAME :
final CCSDSFrame frame = parseCCSDSFrame(pi.keyValue.getValue());
if (frame.isLof()) {
pi.covRefLofType = frame.getLofType();
pi.covRefFrame = null;
} else {
pi.covRefLofType = null;
pi.covRefFrame = frame.getFrame(getConventions(), isSimpleEOP(), getDataContext());
}
break;
case COVARIANCE_STOP :
return;
default :
throw new OrekitException(OrekitMessages.CCSDS_UNEXPECTED_KEYWORD, pi.lineNumber, pi.fileName, line);
}
}
}
}
/** Private class used to stock OEM parsing info.
* @author sports
*/
private static class ParseInfo {
/** Ephemerides block being parsed. */
private OEMFile.EphemeridesBlock lastEphemeridesBlock;
/** Name of the file. */
private String fileName;
/** Current line number. */
private int lineNumber;
/** OEM file being read. */
private OEMFile file;
/** Key value of the line being read. */
private KeyValue keyValue;
/** Stored epoch. */
private AbsoluteDate epoch;
/** Covariance reference type of Local Orbital Frame. */
private LOFType covRefLofType;
/** Covariance reference frame. */
private Frame covRefFrame;
/** Stored matrix. */
private RealMatrix lastMatrix;
/** Stored comments. */
private List<String> commentTmp;
/** Create a new {@link ParseInfo} object. */
protected ParseInfo() {
lineNumber = 0;
file = new OEMFile();
commentTmp = new ArrayList<String>();
}
}
}