Commit 7130de8b authored by Bryan Cazabonne's avatar Bryan Cazabonne
Browse files

Merge branch 'issue-739' into 'develop'

Allowed initialization of an attitude provider from an attitude segment.

Closes #739

See merge request !115
parents e49ff479 258d59dd
Pipeline #786 passed with stages
in 27 minutes and 5 seconds
......@@ -21,6 +21,9 @@
</properties>
<body>
<release version="10.3" date="TBD" description="TBD">
<action dev="bryan" type="add" issue="739">
Allowed initialization of attitude provider from attitude segment.
</action>
<action dev="raphael" type="add" issue="705">
Allowed writing an AEM file from a list of SpacecraftStates.
</action>
......
......@@ -31,6 +31,7 @@ import org.hipparchus.util.FastMath;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.time.AbsoluteDate;
import org.orekit.utils.AngularDerivativesFilter;
import org.orekit.utils.TimeStampedAngularCoordinates;
/** Enumerate for AEM attitude type.
......@@ -79,6 +80,11 @@ public enum AEMAttitudeType {
return new TimeStampedAngularCoordinates(date, rotation, Vector3D.ZERO, Vector3D.ZERO);
}
@Override
public AngularDerivativesFilter getAngularDerivativesFilter() {
return AngularDerivativesFilter.USE_R;
}
},
/** Quaternion and derivatives. */
......@@ -138,6 +144,11 @@ public enum AEMAttitudeType {
return new TimeStampedAngularCoordinates(date, fieldRotation);
}
@Override
public AngularDerivativesFilter getAngularDerivativesFilter() {
return AngularDerivativesFilter.USE_RR;
}
},
/** Quaternion and rotation rate. */
......@@ -194,6 +205,11 @@ public enum AEMAttitudeType {
return new TimeStampedAngularCoordinates(date, rotation, rotationRate, Vector3D.ZERO);
}
@Override
public AngularDerivativesFilter getAngularDerivativesFilter() {
return AngularDerivativesFilter.USE_RR;
}
},
/** Euler angles. */
......@@ -233,6 +249,11 @@ public enum AEMAttitudeType {
return new TimeStampedAngularCoordinates(date, rotation, Vector3D.ZERO, Vector3D.ZERO);
}
@Override
public AngularDerivativesFilter getAngularDerivativesFilter() {
return AngularDerivativesFilter.USE_R;
}
},
/** Euler angles and rotation rate. */
......@@ -281,6 +302,11 @@ public enum AEMAttitudeType {
return new TimeStampedAngularCoordinates(date, rotation, rotationRate, Vector3D.ZERO);
}
@Override
public AngularDerivativesFilter getAngularDerivativesFilter() {
return AngularDerivativesFilter.USE_RR;
}
},
/** Spin. */
......@@ -304,6 +330,14 @@ public enum AEMAttitudeType {
throw new OrekitException(OrekitMessages.CCSDS_AEM_ATTITUDE_TYPE_NOT_IMPLEMENTED, getName());
}
@Override
public AngularDerivativesFilter getAngularDerivativesFilter() {
// Attitude parameters in the Specified Reference Frame for a Spin Stabilized Satellite
// are optional in CCSDS AEM format. Support for this attitude type is not implemented
// yet in Orekit.
throw new OrekitException(OrekitMessages.CCSDS_AEM_ATTITUDE_TYPE_NOT_IMPLEMENTED, getName());
}
},
/** Spin and nutation. */
......@@ -327,6 +361,14 @@ public enum AEMAttitudeType {
throw new OrekitException(OrekitMessages.CCSDS_AEM_ATTITUDE_TYPE_NOT_IMPLEMENTED, getName());
}
@Override
public AngularDerivativesFilter getAngularDerivativesFilter() {
// Attitude parameters in the Specified Reference Frame for a Spin Stabilized Satellite
// are optional in CCSDS AEM format. Support for this attitude type is not implemented
// yet in Orekit.
throw new OrekitException(OrekitMessages.CCSDS_AEM_ATTITUDE_TYPE_NOT_IMPLEMENTED, getName());
}
};
/** Codes map. */
......@@ -399,4 +441,10 @@ public enum AEMAttitudeType {
public abstract TimeStampedAngularCoordinates getAngularCoordinates(AbsoluteDate date, double[] attitudeData,
boolean isFirst, RotationOrder order);
/**
* Get the angular derivative filter corresponding to the attitude data.
* @return the angular derivative filter corresponding to the attitude data
*/
public abstract AngularDerivativesFilter getAngularDerivativesFilter();
}
......@@ -24,11 +24,15 @@ import java.util.Map;
import java.util.Map.Entry;
import org.hipparchus.geometry.euclidean.threed.RotationOrder;
import org.orekit.attitudes.AttitudeProvider;
import org.orekit.attitudes.TabulatedProvider;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.files.general.AttitudeEphemerisFile;
import org.orekit.frames.Frame;
import org.orekit.time.AbsoluteDate;
import org.orekit.time.TimeScale;
import org.orekit.utils.AngularDerivativesFilter;
import org.orekit.utils.TimeStampedAngularCoordinates;
/**
......@@ -109,7 +113,7 @@ public class AEMFile extends ADMFile implements AttitudeEphemerisFile {
* Create a container for the set of ephemeris blocks in the file that pertain to
* a single satellite. The satellite's ID is set to ""
* @param blocks containing ephemeris data for the satellite.
* @deprecated in 10.3, replaced by {@link #AemSatelliteEphemeris(String, List)}
* @deprecated in 10.3, replaced by AemSatelliteEphemeris(String, List)
*/
@Deprecated
public AemSatelliteEphemeris(final List<AttitudeEphemeridesBlock> blocks) {
......@@ -169,6 +173,9 @@ public class AEMFile extends ADMFile implements AttitudeEphemerisFile {
/** The reference frame B specifier, as it appeared in the file. */
private String refFrameBString;
/** The reference frame from which attitude is defined. */
private Frame refFrame;
/** Rotation direction of the attitude. */
private String attitudeDir;
......@@ -214,6 +221,9 @@ public class AEMFile extends ADMFile implements AttitudeEphemerisFile {
/** Data Lines comments. The list contains a string for each line of comment. */
private List<String> attitudeDataLinesComment;
/** Enumerate for selecting which derivatives to use in {@link #attitudeDataLines}. */
private AngularDerivativesFilter angularDerivativesFilter;
/**
* Constructor.
*/
......@@ -236,6 +246,22 @@ public class AEMFile extends ADMFile implements AttitudeEphemerisFile {
return Collections.unmodifiableList(this.attitudeDataLines);
}
/** {@inheritDoc} */
@Override
public AngularDerivativesFilter getAvailableDerivatives() {
return angularDerivativesFilter;
}
/**
* Update the value of {@link #angularDerivativesFilter}.
*
* @param pointAngularDerivativesFilter enumerate for selecting which derivatives to use in
* attitude data.
*/
void updateAngularDerivativesFilter(final AngularDerivativesFilter pointAngularDerivativesFilter) {
this.angularDerivativesFilter = pointAngularDerivativesFilter;
}
/**
* Get the meta-data for the block.
* @return meta-data for the block
......@@ -278,6 +304,23 @@ public class AEMFile extends ADMFile implements AttitudeEphemerisFile {
this.refFrameBString = frame;
}
/**
* Get the reference frame from which attitude is defined.
* @param frame reference frame
*/
public void setReferenceFrame(final Frame frame) {
this.refFrame = frame;
}
/**
* Get the reference frame from which attitude is defined.
* @return the reference frame from which attitude is defined
*/
public Frame getReferenceFrame() {
return refFrame;
}
/**
* Get the rate frame specifier as it appeared in the file.
* @return the rate frame name.
......@@ -517,6 +560,13 @@ public class AEMFile extends ADMFile implements AttitudeEphemerisFile {
this.rotationOrder = order;
}
/** {@inheritDoc} */
@Override
public AttitudeProvider getAttitudeProvider() {
return new TabulatedProvider(getReferenceFrame(), getAngularCoordinates(),
getInterpolationSamples(), getAvailableDerivatives());
}
}
......
......@@ -26,6 +26,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.regex.Pattern;
import org.hipparchus.exception.DummyLocalizable;
import org.hipparchus.geometry.euclidean.threed.RotationOrder;
......@@ -34,6 +35,7 @@ import org.orekit.data.DataContext;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.files.general.AttitudeEphemerisFileParser;
import org.orekit.frames.Frame;
import org.orekit.time.AbsoluteDate;
import org.orekit.utils.IERSConventions;
import org.orekit.utils.TimeStampedAngularCoordinates;
......@@ -45,12 +47,21 @@ import org.orekit.utils.TimeStampedAngularCoordinates;
*/
public class AEMParser extends ADMParser implements AttitudeEphemerisFileParser {
/** Pattern for dash. */
private static final Pattern DASH = Pattern.compile("-");
/** Maximum number of elements in an attitude data line. */
private static final int MAX_SIZE = 8;
/** Default interpolation degree. */
private int interpolationDegree;
/** Local Spacecraft Body Reference Frame A. */
private Frame localScBodyReferenceFrameA;
/** Local Spacecraft Body Reference Frame B. */
private Frame localScBodyReferenceFrameB;
/**
* Simple constructor.
* <p>
......@@ -219,6 +230,38 @@ public class AEMParser extends ADMParser implements AttitudeEphemerisFileParser
newInterpolationDegree, getDataContext());
}
/**
* Set the local spacecraft body reference frame A.
* <p>
* This frame corresponds to {@link Keyword#REF_FRAME_A} key in AEM file.
* This method may be used to set a reference frame "A" which will be used
* if the frame parsed in the file does not correspond to a default frame available
* in {@link CCSDSFrame} (e.g. SC_BODY_1, ACTUATOR_1, etc.).
* According to CCSDS ADM documentation, it is the responsibility of the end user
* to have an understanding of the location of these frames for their particular object.
* </p>
* @param frame the frame to set
*/
public void setLocalScBodyReferenceFrameA(final Frame frame) {
this.localScBodyReferenceFrameA = frame;
}
/**
* Set the local spacecraft body reference frame B.
* <p>
* This frame corresponds to {@link Keyword#REF_FRAME_B} key in AEM file.
* This method may be used to set a reference frame "B" which will be used
* if the frame parsed in the file does not correspond to a default frame available
* in {@link CCSDSFrame} (e.g. SC_BODY_1, ACTUATOR_1, etc.).
* According to CCSDS ADM documentation, it is the responsibility of the end user
* to have an understanding of the location of these frames for their particular object.
* </p>
* @param frame the frame to set
*/
public void setLocalScBodyReferenceFrameB(final Frame frame) {
this.localScBodyReferenceFrameB = frame;
}
/** Get default interpolation degree.
* @return interpolationDegree default interpolation degree to use while parsing
* @see #withInterpolationDegree(int)
......@@ -347,6 +390,9 @@ public class AEMParser extends ADMParser implements AttitudeEphemerisFileParser
break;
case META_STOP:
// Set attitude reference frame
parseReferenceFrame(pi);
// Read attitude ephemeris data lines
parseEphemeridesDataLines(reader, pi);
break;
......@@ -405,6 +451,7 @@ public class AEMParser extends ADMParser implements AttitudeEphemerisFileParser
pi.lastEphemeridesBlock.isFirst(),
rotationOrder);
pi.lastEphemeridesBlock.getAttitudeDataLines().add(epDataLine);
pi.lastEphemeridesBlock.updateAngularDerivativesFilter(attType.getAngularDerivativesFilter());
} catch (NumberFormatException nfe) {
throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
pi.lineNumber, pi.fileName, line);
......@@ -438,6 +485,60 @@ public class AEMParser extends ADMParser implements AttitudeEphemerisFileParser
}
}
/**
* Parse the reference attitude frame.
* @param pi the parser info
*/
private void parseReferenceFrame(final ParseInfo pi) {
// Reference frame A
final String frameAString = DASH.matcher(j2000Check(pi.lastEphemeridesBlock.getRefFrameAString())).replaceAll("");
final Frame frameA = isDefinedFrame(frameAString) ?
CCSDSFrame.valueOf(frameAString).getFrame(getConventions(), isSimpleEOP(), getDataContext()) :
localScBodyReferenceFrameA;
// Reference frame B
final String frameBString = DASH.matcher(j2000Check(pi.lastEphemeridesBlock.getRefFrameBString())).replaceAll("");
final Frame frameB = isDefinedFrame(frameBString) ?
CCSDSFrame.valueOf(frameBString).getFrame(getConventions(), isSimpleEOP(), getDataContext()) :
localScBodyReferenceFrameB;
// Set the attitude reference frame
final String direction = pi.lastEphemeridesBlock.getAttitudeDirection();
pi.lastEphemeridesBlock.setReferenceFrame("A2B".equals(direction) ? frameA : frameB);
}
/**
* Check if frame name is "J2000".
* <p>
* If yes, the name is changed to "EME2000" in order to match
* predefined CCSDS frame names.
* </p>
* @param frameName frame name
* @return the nex name
*/
private static String j2000Check(final String frameName) {
return "J2000".equals(frameName) ? "EME2000" : frameName;
}
/**
* Verify if the given frame is defined in predefined CCSDS frames.
* @param frameName frame name
* @return true is the frame is known
*/
private static boolean isDefinedFrame(final String frameName) {
// Loop on CCSDS frames
for (CCSDSFrame ccsdsFrame : CCSDSFrame.values()) {
// CCSDS frame name is defined in enumerate
if (ccsdsFrame.name().equals(frameName)) {
return true;
}
}
// No match found
return false;
}
/** Private class used to stock AEM parsing info. */
private static class ParseInfo {
......
......@@ -208,7 +208,7 @@ public class AEMWriter implements AttitudeEphemerisFileWriter {
}
/**
* Write the passed in {@link ephemerisFile} to a file at the output path specified.
* 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
* @param aemFile a populated aem file to serialize into the buffer
* @throws IOException if any file writing operations fail or if the underlying
......
......@@ -20,8 +20,10 @@ import java.util.List;
import java.util.Map;
import org.hipparchus.geometry.euclidean.threed.RotationOrder;
import org.orekit.attitudes.AttitudeProvider;
import org.orekit.time.AbsoluteDate;
import org.orekit.time.TimeScale;
import org.orekit.utils.AngularDerivativesFilter;
import org.orekit.utils.TimeStampedAngularCoordinates;
/**
......@@ -57,7 +59,7 @@ public interface AttitudeEphemerisFile {
*
* @author Raphaël Fermé
* @see AttitudeEphemerisFile
* @see AttitudephemerisSegment
* @see AttitudeEphemerisSegment
* @since 10.3
*/
interface SatelliteAttitudeEphemeris {
......@@ -210,6 +212,21 @@ public interface AttitudeEphemerisFile {
*/
int getInterpolationSamples();
/**
* Get which derivatives of angular data are available in this attitude ephemeris segment.
*
* @return a value indicating if the file contains rotation and/or rotation rate
* and/or acceleration data.
*/
AngularDerivativesFilter getAvailableDerivatives();
/**
* Get the attitude provider for this attitude ephemeris segment.
*
* @return the attitude provider for this attitude ephemeris segment.
*/
AttitudeProvider getAttitudeProvider();
}
}
......@@ -24,13 +24,18 @@ import java.util.concurrent.ConcurrentHashMap;
import org.hipparchus.geometry.euclidean.threed.RotationOrder;
import org.orekit.annotation.DefaultDataContext;
import org.orekit.attitudes.AttitudeProvider;
import org.orekit.attitudes.TabulatedProvider;
import org.orekit.bodies.CelestialBody;
import org.orekit.data.DataContext;
import org.orekit.errors.OrekitIllegalArgumentException;
import org.orekit.errors.OrekitMessages;
import org.orekit.files.ccsds.AEMAttitudeType;
import org.orekit.frames.Frame;
import org.orekit.propagation.SpacecraftState;
import org.orekit.time.AbsoluteDate;
import org.orekit.time.TimeScale;
import org.orekit.utils.AngularDerivativesFilter;
import org.orekit.utils.TimeStampedAngularCoordinates;
/**
......@@ -163,7 +168,6 @@ public class OrekitAttitudeEphemerisFile implements AttitudeEphemerisFile {
* a list of {@link SpacecraftState} that will comprise this
* new unit.
* @return the generated {@link OrekitAttitudeEphemerisSegment}
* @see #addNewSegment(List, CelestialBody, int, TimeScale)
*/
@DefaultDataContext
public OrekitAttitudeEphemerisSegment addNewSegment(final List<SpacecraftState> states) {
......@@ -214,7 +218,8 @@ public class OrekitAttitudeEphemerisFile implements AttitudeEphemerisFile {
* the number of interpolation samples that should be used
* when processed by another system
* @param attitudeType
* type of attitude for the attitude ephemeris segment.
* type of attitude for the attitude ephemeris segment. Must correspond
* to the names in {@link AEMAttitudeType} enumerate.
* @param isFirst
* flag for placement of the scalar part of the quaternion
* (used if quaternions are chosen in the attitude type)
......@@ -249,7 +254,8 @@ public class OrekitAttitudeEphemerisFile implements AttitudeEphemerisFile {
* the number of interpolation samples that should be used
* when processed by another system
* @param attitudeType
* type of attitude for the attitude ephemeris segment.
* type of attitude for the attitude ephemeris segment. Must correspond
* to the names in {@link AEMAttitudeType} enumerate.
* @param isFirst
* flag for placement of the scalar part of the quaternion
* (used if quaternions are chosen in the attitude type)
......@@ -292,7 +298,8 @@ public class OrekitAttitudeEphemerisFile implements AttitudeEphemerisFile {
* the number of interpolation samples that should be used
* when processed by another system
* @param attitudeType
* type of attitude for the attitude ephemeris segment.
* type of attitude for the attitude ephemeris segment. Must correspond
* to the names in {@link AEMAttitudeType} enumerate.
* @param isFirst
* flag for placement of the scalar part of the quaternion
* (used if quaternions are chosen in the attitude type)
......@@ -388,7 +395,7 @@ public class OrekitAttitudeEphemerisFile implements AttitudeEphemerisFile {
final OrekitAttitudeEphemerisSegment newSeg = new OrekitAttitudeEphemerisSegment(attitudeDataLines, body.getName(), refFrameA,
refFrameB, attitudeDir, attitudeType, isFirst, rotationOrder,
timeScale, interpolationMethod, interpolationSamples);
timeScale, interpolationMethod, interpolationSamples, states.get(0).getFrame());
this.segments.add(newSeg);
return newSeg;
}
......@@ -432,6 +439,12 @@ public class OrekitAttitudeEphemerisFile implements AttitudeEphemerisFile {
/** The number of interpolation samples. */
private int interpolationSamples;
/** Enumerate for selecting which derivatives to use in {@link #attitudeDataLines} interpolation. */
private AngularDerivativesFilter angularDerivativesFilter;
/** Reference frame from which attitude is defined. */
private Frame referenceFrame;
/**
* Constructor for OrekitAttitudeEphemerisSegment.
*
......@@ -457,23 +470,28 @@ public class OrekitAttitudeEphemerisFile implements AttitudeEphemerisFile {
* the interpolation method to use.
* @param interpolationSamples
* the number of samples to use during interpolation.
* @param referenceFrame
* reference frame from which the attitude is defined
*/
public OrekitAttitudeEphemerisSegment(final List<TimeStampedAngularCoordinates> attitudeDataLines, final String frameCenterString,
final String refFrameAString, final String refFrameBString, final String attitudeDir, final String attitudeType,
final boolean isFirst, final RotationOrder rotationOrder, final TimeScale timeScale,
final String interpolationMethod, final int interpolationSamples) {
this.attitudeDataLines = attitudeDataLines;
this.frameCenterString = frameCenterString;
this.refFrameAString = refFrameAString;
this.refFrameBString = refFrameBString;
this.attitudeDir = attitudeDir;
this.attitudeType = attitudeType;
this.isFirst = isFirst;
this.rotationOrder = rotationOrder;
this.timeScaleString = timeScale.getName();
this.timeScale = timeScale;
this.interpolationMethod = interpolationMethod;
this.interpolationSamples = interpolationSamples;
final String interpolationMethod, final int interpolationSamples,
final Frame referenceFrame) {
this.attitudeDataLines = attitudeDataLines;
this.frameCenterString = frameCenterString;
this.refFrameAString = refFrameAString;
this.refFrameBString = refFrameBString;
this.attitudeDir = attitudeDir;
this.attitudeType = attitudeType;
this.isFirst = isFirst;
this.rotationOrder = rotationOrder;
this.timeScaleString = timeScale.getName();
this.timeScale = timeScale;
this.interpolationMethod = interpolationMethod;
this.interpolationSamples = interpolationSamples;
this.referenceFrame = referenceFrame;
this.angularDerivativesFilter = AEMAttitudeType.getAttitudeType(attitudeType).getAngularDerivativesFilter();
}
/** {@inheritDoc} */
......@@ -560,6 +578,19 @@ public class OrekitAttitudeEphemerisFile implements AttitudeEphemerisFile {
return interpolationSamples;
}
/** {@inheritDoc} */
@Override
public AngularDerivativesFilter getAvailableDerivatives() {
return angularDerivativesFilter;
}
/** {@inheritDoc} */
@Override
public AttitudeProvider getAttitudeProvider() {
return new TabulatedProvider(referenceFrame, getAngularCoordinates(),
getInterpolationSamples(), getAvailableDerivatives());
}
}
}
......@@ -26,6 +26,7 @@ import org.orekit.Utils;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.time.AbsoluteDate;
import org.orekit.utils.AngularDerivativesFilter;
import org.orekit.utils.TimeStampedAngularCoordinates;
public class AEMAttitudeTypeTest {
......@@ -61,6 +62,13 @@ public class AEMAttitudeTypeTest {
Assert.assertEquals(OrekitMessages.CCSDS_AEM_ATTITUDE_TYPE_NOT_IMPLEMENTED, oe.getSpecifier());
Assert.assertEquals(AEMAttitudeType.SPIN.getName(), oe.getParts()[0]);
}
// Test exception on the third method
try {
spin.getAngularDerivativesFilter();
} catch (OrekitException oe) {
Assert.assertEquals(OrekitMessages.CCSDS_AEM_ATTITUDE_TYPE_NOT_IMPLEMENTED, oe.getSpecifier());
Assert.assertEquals(AEMAttitudeType.SPIN.getName(), oe.getParts()[0]);
}
}
/**
......@@ -85,6 +93,13 @@ public class AEMAttitudeTypeTest {
Assert.assertEquals(OrekitMessages.CCSDS_AEM_ATTITUDE_TYPE_NOT_IMPLEMENTED, oe.getSpecifier());
Assert.assertEquals(AEMAttitudeType.SPIN_NUTATION.getName(), oe.getParts()[0]);
}
// Test exception on the third method
try {
spinNutation.getAngularDerivativesFilter();
} catch (OrekitException oe) {
Assert.assertEquals(OrekitMessages.CCSDS_AEM_ATTITUDE_TYPE_NOT_IMPLEMENTED, oe.getSpecifier());
Assert.assertEquals(AEMAttitudeType.SPIN_NUTATION.getName(), oe.getParts()[0]);
}
}
@Test
......@@ -108,6 +123,9 @@ public class AEMAttitudeTypeTest {
for (int i = 0; i < attitudeData.length; i++) {
Assert.assertEquals(attitudeData[i], attitudeDataBis[i], QUATERNION_PRECISION);
}