Commit 1933abfe authored by Raphaël Fermé's avatar Raphaël Fermé
Browse files

Added AttitudeEphemerisFileParser and -Writer

parent acd5a4d5
......@@ -78,7 +78,7 @@ public class AEMFile extends ADMFile implements AttitudeEphemerisFile {
}
}
/** {@inheritDoc} */
@Override
public Map<String, AemSatelliteEphemeris> getSatellites() {
final Map<String, List<AttitudeEphemeridesBlock>> satellites = new HashMap<>();
......@@ -128,21 +128,25 @@ public class AEMFile extends ADMFile implements AttitudeEphemerisFile {
this.blocks = blocks;
}
/** {@inheritDoc} */
@Override
public String getId() {
return this.id;
}
/** {@inheritDoc} */
@Override
public List<AttitudeEphemeridesBlock> getSegments() {
return Collections.unmodifiableList(blocks);
}
/** {@inheritDoc} */
@Override
public AbsoluteDate getStart() {
return blocks.get(0).getStart();
}
/** {@inheritDoc} */
@Override
public AbsoluteDate getStop() {
return blocks.get(blocks.size() - 1).getStop();
......@@ -226,6 +230,7 @@ public class AEMFile extends ADMFile implements AttitudeEphemerisFile {
return attitudeDataLines;
}
/** {@inheritDoc} */
@Override
public List<TimeStampedAngularCoordinates> getAngularCoordinates() {
return Collections.unmodifiableList(this.attitudeDataLines);
......@@ -239,12 +244,13 @@ public class AEMFile extends ADMFile implements AttitudeEphemerisFile {
return metaData;
}
/** {@inheritDoc} */
@Override
public String getFrameCenterString() {
return this.getMetaData().getCenterName();
}
/** {@inheritDoc} */
@Override
public String getRefFrameAString() {
return refFrameAString;
......@@ -258,6 +264,7 @@ public class AEMFile extends ADMFile implements AttitudeEphemerisFile {
this.refFrameAString = frame;
}
/** {@inheritDoc} */
@Override
public String getRefFrameBString() {
return this.refFrameBString;
......@@ -287,6 +294,7 @@ public class AEMFile extends ADMFile implements AttitudeEphemerisFile {
this.rateFrameString = frame;
}
/** {@inheritDoc} */
@Override
public String getAttitudeDirection() {
return attitudeDir;
......@@ -300,6 +308,7 @@ public class AEMFile extends ADMFile implements AttitudeEphemerisFile {
this.attitudeDir = direction;
}
/** {@inheritDoc} */
@Override
public String getAttitudeType() {
return attitudeType;
......@@ -313,6 +322,7 @@ public class AEMFile extends ADMFile implements AttitudeEphemerisFile {
this.attitudeType = type;
}
/** {@inheritDoc} */
@Override
public boolean isFirst() {
return isFirst;
......@@ -342,11 +352,13 @@ public class AEMFile extends ADMFile implements AttitudeEphemerisFile {
this.eulerRotSeq = eulerRotSeq;
}
/** {@inheritDoc} */
@Override
public String getTimeScaleString() {
return metaData.getTimeSystem().toString();
}
/** {@inheritDoc} */
@Override
public TimeScale getTimeScale() {
return metaData.getTimeScale();
......@@ -416,6 +428,7 @@ public class AEMFile extends ADMFile implements AttitudeEphemerisFile {
this.useableStopTime = useableStopTime;
}
/** {@inheritDoc} */
@Override
public AbsoluteDate getStart() {
// usable start time overrides start time if it is set
......@@ -427,6 +440,7 @@ public class AEMFile extends ADMFile implements AttitudeEphemerisFile {
}
}
/** {@inheritDoc} */
@Override
public AbsoluteDate getStop() {
// useable stop time overrides stop time if it is set
......@@ -438,6 +452,7 @@ public class AEMFile extends ADMFile implements AttitudeEphemerisFile {
}
}
/** {@inheritDoc} */
@Override
public String getInterpolationMethod() {
return interpolationMethod;
......@@ -467,6 +482,7 @@ public class AEMFile extends ADMFile implements AttitudeEphemerisFile {
this.interpolationDegree = interpolationDegree;
}
/** {@inheritDoc} */
@Override
public int getInterpolationSamples() {
// From the standard it is not entirely clear how to interpret the degree.
......@@ -487,6 +503,7 @@ public class AEMFile extends ADMFile implements AttitudeEphemerisFile {
this.attitudeDataLinesComment = new ArrayList<String>(ephemeridesDataLinesComment);
}
/** {@inheritDoc} */
@Override
public RotationOrder getRotationOrder() {
return rotationOrder;
......
......@@ -33,6 +33,7 @@ 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.AttitudeEphemerisFileParser;
import org.orekit.time.AbsoluteDate;
import org.orekit.utils.IERSConventions;
import org.orekit.utils.TimeStampedAngularCoordinates;
......@@ -42,11 +43,14 @@ import org.orekit.utils.TimeStampedAngularCoordinates;
* @author Bryan Cazabonne
* @since 10.2
*/
public class AEMParser extends ADMParser {
public class AEMParser extends ADMParser implements AttitudeEphemerisFileParser {
/** Maximum number of elements in an attitude data line. */
private static final int MAX_SIZE = 8;
/** Default interpolation degree. */
private int interpolationDegree;
/**
* Simple constructor.
* <p>
......@@ -65,11 +69,21 @@ public class AEMParser extends ADMParser {
* 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>
* </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 zero 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)}.
......@@ -97,18 +111,28 @@ public class AEMParser extends ADMParser {
* 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>
* </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 zero 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 #AEMParser()
* @see #withDataContext(DataContext)
*/
public AEMParser(final DataContext dataContext) {
this(AbsoluteDate.FUTURE_INFINITY, Double.NaN, null, true, 0, 0, "", dataContext);
this(AbsoluteDate.FUTURE_INFINITY, Double.NaN, null, true, 0, 0, "", 0, dataContext);
}
/**
......@@ -120,42 +144,45 @@ public class AEMParser extends ADMParser {
* @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 default interpolation degree
* @param dataContext used to retrieve frames, time scales, etc.
*/
private AEMParser(final AbsoluteDate missionReferenceDate, final double mu,
final IERSConventions conventions, final boolean simpleEOP,
final int launchYear, final int launchNumber,
final String launchPiece, final DataContext dataContext) {
final String launchPiece, final int interpolationDegree,
final DataContext dataContext) {
super(missionReferenceDate, mu, conventions, simpleEOP, launchYear, launchNumber,
launchPiece, dataContext);
this.interpolationDegree = interpolationDegree;
}
/** {@inheritDoc} */
public AEMParser withMissionReferenceDate(final AbsoluteDate newMissionReferenceDate) {
return new AEMParser(newMissionReferenceDate, getMu(), getConventions(), isSimpleEOP(),
getLaunchYear(), getLaunchNumber(), getLaunchPiece(),
getDataContext());
getInterpolationDegree(), getDataContext());
}
/** {@inheritDoc} */
public AEMParser withMu(final double newMu) {
return new AEMParser(getMissionReferenceDate(), newMu, getConventions(), isSimpleEOP(),
getLaunchYear(), getLaunchNumber(), getLaunchPiece(),
getDataContext());
getInterpolationDegree(), getDataContext());
}
/** {@inheritDoc} */
public AEMParser withConventions(final IERSConventions newConventions) {
return new AEMParser(getMissionReferenceDate(), getMu(), newConventions, isSimpleEOP(),
getLaunchYear(), getLaunchNumber(), getLaunchPiece(),
getDataContext());
getInterpolationDegree(), getDataContext());
}
/** {@inheritDoc} */
public AEMParser withSimpleEOP(final boolean newSimpleEOP) {
return new AEMParser(getMissionReferenceDate(), getMu(), getConventions(), newSimpleEOP,
getLaunchYear(), getLaunchNumber(), getLaunchPiece(),
getDataContext());
getInterpolationDegree(), getDataContext());
}
/** {@inheritDoc} */
......@@ -164,15 +191,41 @@ public class AEMParser extends ADMParser {
final String newLaunchPiece) {
return new AEMParser(getMissionReferenceDate(), getMu(), getConventions(), isSimpleEOP(),
newLaunchYear, newLaunchNumber, newLaunchPiece,
getDataContext());
getInterpolationDegree(), getDataContext());
}
/** {@inheritDoc} */
@Override
public AEMParser withDataContext(final DataContext dataContext) {
return new AEMParser(getMissionReferenceDate(), getMu(), getConventions(), isSimpleEOP(),
getLaunchYear(), getLaunchNumber(), getLaunchPiece(),
dataContext);
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 #AEMParser(DataContext)} the default interpolation degree is zero.
* </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 AEMParser withInterpolationDegree(final int newInterpolationDegree) {
return new AEMParser(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} */
......@@ -188,6 +241,7 @@ public class AEMParser extends ADMParser {
}
/** {@inheritDoc} */
@Override
public AEMFile parse(final InputStream stream, final String fileName) {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) {
return parse(reader, fileName);
......@@ -196,12 +250,8 @@ public class AEMParser extends ADMParser {
}
}
/**
* Parse an attitude ephemeris file from a stream.
* @param reader containing the ephemeris file.
* @param fileName to use in error messages.
* @return a parsed attitude ephemeris file.
*/
/** {@inheritDoc} */
@Override
public AEMFile parse(final BufferedReader reader, final String fileName) {
try {
......@@ -237,6 +287,7 @@ public class AEMParser extends ADMParser {
pi.lastEphemeridesBlock.getMetaData().setLaunchYear(getLaunchYear());
pi.lastEphemeridesBlock.getMetaData().setLaunchNumber(getLaunchNumber());
pi.lastEphemeridesBlock.getMetaData().setLaunchPiece(getLaunchPiece());
pi.lastEphemeridesBlock.setInterpolationDegree(getInterpolationDegree());
break;
case REF_FRAME_A:
......
......@@ -27,9 +27,11 @@ import java.util.Map;
import org.orekit.errors.OrekitIllegalArgumentException;
import org.orekit.errors.OrekitMessages;
import org.orekit.files.ccsds.AEMFile.AemSatelliteEphemeris;
import org.orekit.files.ccsds.AEMFile.AttitudeEphemeridesBlock;
import org.orekit.files.ccsds.StreamingAemWriter.AEMSegment;
import org.orekit.files.general.AttitudeEphemerisFile;
import org.orekit.files.general.AttitudeEphemerisFile.SatelliteAttitudeEphemeris;
import org.orekit.files.general.AttitudeEphemerisFile.AttitudeEphemerisSegment;
import org.orekit.files.general.AttitudeEphemerisFileWriter;
import org.orekit.time.TimeScale;
import org.orekit.utils.TimeStampedAngularCoordinates;
......@@ -38,7 +40,7 @@ import org.orekit.utils.TimeStampedAngularCoordinates;
* @author Bryan Cazabonne
* @since 10.2
*/
public class AEMWriter {
public class AEMWriter implements AttitudeEphemerisFileWriter {
/** Originator name, usually the organization and/or country. **/
private final String originator;
......@@ -73,48 +75,41 @@ public class AEMWriter {
this.spaceObjectName = spaceObjectName;
}
/**
* Write the passed in {@link AEMFile} using the passed in {@link Appendable}.
* @param writer a configured Appendable to feed with text
* @param aemFile a populated aem file to serialize into the buffer
* @throws IOException if any buffer writing operations fail or if the underlying
* format doesn't support a configuration in the EphemerisFile
* for example having multiple satellites in one file, having
* the origin at an unspecified celestial body, etc.)
*/
public void write(final Appendable writer, final AEMFile aemFile)
/** {@inheritDoc} */
@Override
public void write(final Appendable writer, final AttitudeEphemerisFile ephemerisFile)
throws IOException {
if (writer == null) {
throw new OrekitIllegalArgumentException(OrekitMessages.NULL_ARGUMENT, "writer");
}
if (aemFile == null) {
if (ephemerisFile == null) {
return;
}
final String idToProcess;
if (spaceObjectId != null) {
if (aemFile.getSatellites().containsKey(spaceObjectId)) {
if (ephemerisFile.getSatellites().containsKey(spaceObjectId)) {
idToProcess = spaceObjectId;
} else {
throw new OrekitIllegalArgumentException(OrekitMessages.VALUE_NOT_FOUND, spaceObjectId, "ephemerisFile");
}
} else if (aemFile.getSatellites().keySet().size() == 1) {
idToProcess = aemFile.getSatellites().keySet().iterator().next();
} else if (ephemerisFile.getSatellites().keySet().size() == 1) {
idToProcess = ephemerisFile.getSatellites().keySet().iterator().next();
} else {
throw new OrekitIllegalArgumentException(OrekitMessages.EPHEMERIS_FILE_NO_MULTI_SUPPORT);
}
// Get satellite and attitude ephemeris segments to output.
final AemSatelliteEphemeris satEphem = aemFile.getSatellites().get(idToProcess);
final List<AttitudeEphemeridesBlock> segments = satEphem.getSegments();
final SatelliteAttitudeEphemeris satEphem = ephemerisFile.getSatellites().get(idToProcess);
final List<? extends AttitudeEphemerisSegment> segments = satEphem.getSegments();
if (segments.isEmpty()) {
// No data -> No output
return;
}
// First segment
final AttitudeEphemeridesBlock firstSegment = segments.get(0);
final AttitudeEphemerisSegment firstSegment = segments.get(0);
final String objectName = this.spaceObjectName == null ? idToProcess : this.spaceObjectName;
// Only one time scale per AEM file, see Section 4.2.5.4.2
......@@ -127,15 +122,19 @@ public class AEMWriter {
metadata.put(Keyword.OBJECT_NAME, objectName);
metadata.put(Keyword.OBJECT_ID, idToProcess);
// Header comments. If header comments are presents, they are assembled together in a single line
if (!aemFile.getHeaderComment().isEmpty()) {
// Loop on comments
final StringBuffer buffer = new StringBuffer();
for (String comment : aemFile.getHeaderComment()) {
buffer.append(comment);
// Header comments. If header comments are presents, they are assembled together in a single line
if (ephemerisFile instanceof AEMFile) {
// Cast to OEMFile
final AEMFile aemFile = (AEMFile) ephemerisFile;
if (!aemFile.getHeaderComment().isEmpty()) {
// Loop on comments
final StringBuffer buffer = new StringBuffer();
for (String comment : aemFile.getHeaderComment()) {
buffer.append(comment);
}
// Update metadata
metadata.put(Keyword.COMMENT, buffer.toString());
}
// Update metadata
metadata.put(Keyword.COMMENT, buffer.toString());
}
// Writer for AEM files
......@@ -144,7 +143,7 @@ public class AEMWriter {
aemWriter.writeHeader();
// Loop on segments
for (final AttitudeEphemeridesBlock segment : segments) {
for (final AttitudeEphemerisSegment segment : segments) {
// Segment specific metadata
metadata.clear();
metadata.put(Keyword.CENTER_NAME, segment.getFrameCenterString());
......@@ -155,7 +154,8 @@ public class AEMWriter {
metadata.put(Keyword.STOP_TIME, segment.getStop().toString(timeScale));
metadata.put(Keyword.ATTITUDE_TYPE, segment.getAttitudeType());
metadata.put(Keyword.INTERPOLATION_METHOD, segment.getInterpolationMethod());
metadata.put(Keyword.INTERPOLATION_DEGREE, String.valueOf(segment.getInterpolationDegree()));
metadata.put(Keyword.INTERPOLATION_DEGREE,
String.valueOf(segment.getInterpolationSamples() - 1));
final AEMSegment segmentWriter = aemWriter.newSegment(metadata);
segmentWriter.writeMetadata();
......@@ -171,18 +171,31 @@ public class AEMWriter {
}
/**
* Write the passed in {@link AEMFile} to a file at the output path specified.
* @param outputFilePath a file path that the corresponding file will be written to
* Write the passed in {@link AEMFile} using the passed in {@link Appendable}.
* @param writer a configured Appendable to feed with text
* @param aemFile a populated aem file to serialize into the buffer
* @throws IOException if any buffer writing operations fail or if the underlying
* format doesn't support a configuration in the EphemerisFile
* for example having multiple satellites in one file, having
* the origin at an unspecified celestial body, etc.)
*/
public void write(final Appendable writer, final AEMFile aemFile) throws IOException {
write(writer, (AttitudeEphemerisFile) aemFile);
}
/**
* Write the passed in {@link ephemerisFile} to a file at the output path specified.
* @param outputFilePath a file path that the corresponding file will be written to
* @param ephemerisFile a populated aem file to serialize into the buffer
* @throws IOException if any file writing operations fail or if the underlying
* format doesn't support a configuration in the EphemerisFile
* (for example having multiple satellites in one file, having
* the origin at an unspecified celestial body, etc.)
*/
public void write(final String outputFilePath, final AEMFile aemFile)
public void write(final String outputFilePath, final AttitudeEphemerisFile ephemerisFile)
throws IOException {
try (BufferedWriter writer = Files.newBufferedWriter(Paths.get(outputFilePath), StandardCharsets.UTF_8)) {
write(writer, aemFile);
write(writer, ephemerisFile);
}
}
......
......@@ -115,6 +115,7 @@ public class APMParser extends ADMParser {
}
/** {@inheritDoc} */
@Override
public APMParser withMissionReferenceDate(final AbsoluteDate newMissionReferenceDate) {
return new APMParser(newMissionReferenceDate, getMu(), getConventions(), isSimpleEOP(),
getLaunchYear(), getLaunchNumber(), getLaunchPiece(),
......@@ -122,6 +123,7 @@ public class APMParser extends ADMParser {
}
/** {@inheritDoc} */
@Override
public APMParser withMu(final double newMu) {
return new APMParser(getMissionReferenceDate(), newMu, getConventions(), isSimpleEOP(),
getLaunchYear(), getLaunchNumber(), getLaunchPiece(),
......@@ -129,6 +131,7 @@ public class APMParser extends ADMParser {
}
/** {@inheritDoc} */
@Override
public APMParser withConventions(final IERSConventions newConventions) {
return new APMParser(getMissionReferenceDate(), getMu(), newConventions, isSimpleEOP(),
getLaunchYear(), getLaunchNumber(), getLaunchPiece(),
......@@ -136,6 +139,7 @@ public class APMParser extends ADMParser {
}
/** {@inheritDoc} */
@Override
public APMParser withSimpleEOP(final boolean newSimpleEOP) {
return new APMParser(getMissionReferenceDate(), getMu(), getConventions(), newSimpleEOP,
getLaunchYear(), getLaunchNumber(), getLaunchPiece(),
......@@ -143,6 +147,7 @@ public class APMParser extends ADMParser {
}
/** {@inheritDoc} */
@Override
public APMParser withInternationalDesignator(final int newLaunchYear,
final int newLaunchNumber,
final String newLaunchPiece) {
......@@ -172,6 +177,7 @@ public class APMParser extends ADMParser {
}
/** {@inheritDoc} */
@Override
public APMFile parse(final InputStream stream, final String fileName) {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) {
......
......@@ -77,6 +77,7 @@ public class OEMFile extends ODMFile implements EphemerisFile {
}
}
/** {@inheritDoc} */
@Override
public Map<String, OemSatelliteEphemeris> getSatellites() {
final Map<String, List<EphemeridesBlock>> satellites = new HashMap<>();
......@@ -93,6 +94,68 @@ public class OEMFile extends ODMFile implements EphemerisFile {
return ret;
}
/** OEM ephemeris blocks for a single satellite. */
public static class OemSatelliteEphemeris implements SatelliteEphemeris {
/** ID of the satellite. */
private final String id;
/** Gravitational parameter for the satellite, in m^3 / s^2. */
private final double mu;
/** The ephemeris data for the satellite. */
private final List<EphemeridesBlock> blocks;
/**
* Create a container for the set of ephemeris blocks in the file that pertain to
* a single satellite.
*
* @param id of the satellite.
* @param mu standard gravitational parameter used to create orbits for the
* satellite, in m^3 / s^2.
* @param blocks containing ephemeris data for the satellite.
*/
public OemSatelliteEphemeris(final String id,
final double mu,
final List<EphemeridesBlock> blocks) {
this.id = id;
this.mu = mu;
this.blocks = blocks;
}
/** {@inheritDoc} */
@Override
public String getId() {
return this.id;
}
/** {@inheritDoc} */
@Override
public double getMu() {
return this.mu;
}
/** {@inheritDoc} */
@Override
public List<EphemeridesBlock> getSegments() {
return Collections.unmodifiableList(blocks);
}
/** {@inheritDoc} */