OPMParser.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 org.hipparchus.exception.DummyLocalizable;
import org.hipparchus.geometry.euclidean.threed.Vector3D;
import org.orekit.annotation.DefaultDataContext;
import org.orekit.data.DataContext;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.time.AbsoluteDate;
import org.orekit.utils.IERSConventions;
/** A parser for the CCSDS OPM (Orbit Parameter Message).
* @author sports
* @since 6.1
*/
public class OPMParser extends ODMParser {
/** 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>This method uses the {@link DataContext#getDefault() default data context}. See
* {@link #withDataContext(DataContext)}.
*/
@DefaultDataContext
public OPMParser() {
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>
*
* @param dataContext used by the parser.
*
* @see #OPMParser()
* @see #withDataContext(DataContext)
* @since 10.1
*/
public OPMParser(final DataContext dataContext) {
this(AbsoluteDate.FUTURE_INFINITY, Double.NaN, null, true, 0, 0, "", 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 dataContext used to retrieve frames, time scales, etc.
*/
private OPMParser(final AbsoluteDate missionReferenceDate, final double mu,
final IERSConventions conventions, final boolean simpleEOP,
final int launchYear, final int launchNumber,
final String launchPiece, final DataContext dataContext) {
super(missionReferenceDate, mu, conventions, simpleEOP, launchYear, launchNumber,
launchPiece, dataContext);
}
/** {@inheritDoc} */
public OPMParser withMissionReferenceDate(final AbsoluteDate newMissionReferenceDate) {
return new OPMParser(newMissionReferenceDate, getMu(), getConventions(), isSimpleEOP(),
getLaunchYear(), getLaunchNumber(), getLaunchPiece(), getDataContext());
}
/** {@inheritDoc} */
public OPMParser withMu(final double newMu) {
return new OPMParser(getMissionReferenceDate(), newMu, getConventions(), isSimpleEOP(),
getLaunchYear(), getLaunchNumber(), getLaunchPiece(), getDataContext());
}
/** {@inheritDoc} */
public OPMParser withConventions(final IERSConventions newConventions) {
return new OPMParser(getMissionReferenceDate(), getMu(), newConventions, isSimpleEOP(),
getLaunchYear(), getLaunchNumber(), getLaunchPiece(), getDataContext());
}
/** {@inheritDoc} */
public OPMParser withSimpleEOP(final boolean newSimpleEOP) {
return new OPMParser(getMissionReferenceDate(), getMu(), getConventions(), newSimpleEOP,
getLaunchYear(), getLaunchNumber(), getLaunchPiece(), getDataContext());
}
/** {@inheritDoc} */
public OPMParser withInternationalDesignator(final int newLaunchYear,
final int newLaunchNumber,
final String newLaunchPiece) {
return new OPMParser(getMissionReferenceDate(), getMu(), getConventions(), isSimpleEOP(),
newLaunchYear, newLaunchNumber, newLaunchPiece, getDataContext());
}
@Override
public OPMParser withDataContext(final DataContext newDataContext) {
return new OPMParser(getMissionReferenceDate(), getMu(), getConventions(), isSimpleEOP(),
getLaunchYear(), getLaunchNumber(), getLaunchPiece(), newDataContext);
}
/** {@inheritDoc} */
@Override
public OPMFile parse(final String fileName) {
return (OPMFile) super.parse(fileName);
}
/** {@inheritDoc} */
@Override
public OPMFile parse(final InputStream stream) {
return (OPMFile) super.parse(stream);
}
/** {@inheritDoc} */
public OPMFile parse(final InputStream stream, final String fileName) {
try {
final BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8));
// initialize internal data structures
final ParseInfo pi = new ParseInfo();
pi.fileName = fileName;
final OPMFile 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());
pi.file.getMetaData().setLaunchYear(getLaunchYear());
pi.file.getMetaData().setLaunchNumber(getLaunchNumber());
pi.file.getMetaData().setLaunchPiece(getLaunchPiece());
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_OPM_VERS:
file.setFormatVersion(pi.keyValue.getDoubleValue());
break;
case X:
pi.x = pi.keyValue.getDoubleValue() * 1000;
break;
case Y:
pi.y = pi.keyValue.getDoubleValue() * 1000;
break;
case Z:
pi.z = pi.keyValue.getDoubleValue() * 1000;
break;
case X_DOT:
pi.x_dot = pi.keyValue.getDoubleValue() * 1000;
break;
case Y_DOT:
pi.y_dot = pi.keyValue.getDoubleValue() * 1000;
break;
case Z_DOT:
pi.z_dot = pi.keyValue.getDoubleValue() * 1000;
break;
case MAN_EPOCH_IGNITION:
if (pi.maneuver != null) {
file.addManeuver(pi.maneuver);
}
pi.maneuver = new OPMFile.Maneuver();
pi.maneuver.setEpochIgnition(parseDate(pi.keyValue.getValue(), file.getMetaData().getTimeSystem()));
if (!pi.commentTmp.isEmpty()) {
pi.maneuver.setComment(pi.commentTmp);
pi.commentTmp.clear();
}
break;
case MAN_DURATION:
pi.maneuver.setDuration(pi.keyValue.getDoubleValue());
break;
case MAN_DELTA_MASS:
pi.maneuver.setDeltaMass(pi.keyValue.getDoubleValue());
break;
case MAN_REF_FRAME:
final CCSDSFrame manFrame = parseCCSDSFrame(pi.keyValue.getValue());
if (manFrame.isLof()) {
pi.maneuver.setRefLofType(manFrame.getLofType());
} else {
pi.maneuver.setRefFrame(manFrame.getFrame(
getConventions(),
isSimpleEOP(),
getDataContext()));
}
break;
case MAN_DV_1:
pi.maneuver.setdV(new Vector3D(pi.keyValue.getDoubleValue() * 1000,
pi.maneuver.getDV().getY(),
pi.maneuver.getDV().getZ()));
break;
case MAN_DV_2:
pi.maneuver.setdV(new Vector3D(pi.maneuver.getDV().getX(),
pi.keyValue.getDoubleValue() * 1000,
pi.maneuver.getDV().getZ()));
break;
case MAN_DV_3:
pi.maneuver.setdV(new Vector3D(pi.maneuver.getDV().getX(),
pi.maneuver.getDV().getY(),
pi.keyValue.getDoubleValue() * 1000));
break;
default:
boolean parsed = false;
parsed = parsed || parseComment(pi.keyValue, pi.commentTmp);
parsed = parsed || parseHeaderEntry(pi.keyValue, file, pi.commentTmp);
parsed = parsed || parseMetaDataEntry(pi.keyValue, file.getMetaData(), pi.commentTmp);
parsed = parsed || parseGeneralStateDataEntry(pi.keyValue, file, pi.commentTmp);
if (!parsed) {
throw new OrekitException(OrekitMessages.CCSDS_UNEXPECTED_KEYWORD, pi.lineNumber, pi.fileName, line);
}
}
}
file.setPosition(new Vector3D(pi.x, pi.y, pi.z));
file.setVelocity(new Vector3D(pi.x_dot, pi.y_dot, pi.z_dot));
if (pi.maneuver != null) {
file.addManeuver(pi.maneuver);
}
reader.close();
return file;
} catch (IOException ioe) {
throw new OrekitException(ioe, new DummyLocalizable(ioe.getMessage()));
}
}
/** Private class used to stock OPM parsing info.
* @author sports
*/
private static class ParseInfo {
/** OPM file being read. */
private OPMFile file;
/** Name of the file. */
private String fileName;
/** Current line number. */
private int lineNumber;
/** Key value of the line being read. */
private KeyValue keyValue;
/** Stored comments. */
private List<String> commentTmp;
/** First component of position vector. */
private double x;
/** Second component of position vector. */
private double y;
/** Third component of position vector. */
private double z;
/** First component of velocity vector. */
private double x_dot;
/** Second component of velocity vector. */
private double y_dot;
/** Third component of velocity vector. */
private double z_dot;
/** Current maneuver. */
private OPMFile.Maneuver maneuver;
/** Create a new {@link ParseInfo} object. */
protected ParseInfo() {
file = new OPMFile();
lineNumber = 0;
commentTmp = new ArrayList<String>();
maneuver = null;
}
}
}