AttitudeType.java

  1. /* Copyright 2002-2022 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.adm;

  18. import java.util.regex.Pattern;

  19. import org.hipparchus.analysis.differentiation.UnivariateDerivative1;
  20. import org.hipparchus.geometry.euclidean.threed.FieldRotation;
  21. import org.hipparchus.geometry.euclidean.threed.Rotation;
  22. import org.hipparchus.geometry.euclidean.threed.RotationConvention;
  23. import org.hipparchus.geometry.euclidean.threed.RotationOrder;
  24. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  25. import org.hipparchus.util.FastMath;
  26. import org.orekit.attitudes.Attitude;
  27. import org.orekit.errors.OrekitException;
  28. import org.orekit.errors.OrekitMessages;
  29. import org.orekit.files.ccsds.definitions.Units;
  30. import org.orekit.files.ccsds.utils.ContextBinding;
  31. import org.orekit.time.AbsoluteDate;
  32. import org.orekit.utils.AccurateFormatter;
  33. import org.orekit.utils.AngularDerivativesFilter;
  34. import org.orekit.utils.TimeStampedAngularCoordinates;
  35. import org.orekit.utils.units.Unit;

  36. /** Enumerate for ADM attitude type.
  37.  * @author Bryan Cazabonne
  38.  * @since 10.2
  39.  */
  40. public enum AttitudeType {

  41.     /** Quaternion. */
  42.     QUATERNION("QUATERNION", AngularDerivativesFilter.USE_R,
  43.                Unit.ONE, Unit.ONE, Unit.ONE, Unit.ONE) {

  44.         /** {@inheritDoc} */
  45.         @Override
  46.         public String[] createDataFields(final boolean isFirst, final boolean isExternal2SpacecraftBody,
  47.                                          final RotationOrder eulerRotSequence, final boolean isSpacecraftBodyRate,
  48.                                          final TimeStampedAngularCoordinates coordinates) {

  49.             // Initialize the array of attitude data
  50.             final double[] data = new double[4];

  51.             // Data index
  52.             final int[] quaternionIndex = isFirst ? new int[] {0, 1, 2, 3} : new int[] {3, 0, 1, 2};

  53.             // Fill the array
  54.             Rotation rotation  = coordinates.getRotation();
  55.             if (!isExternal2SpacecraftBody) {
  56.                 rotation = rotation.revert();
  57.             }
  58.             data[quaternionIndex[0]] = rotation.getQ0();
  59.             data[quaternionIndex[1]] = rotation.getQ1();
  60.             data[quaternionIndex[2]] = rotation.getQ2();
  61.             data[quaternionIndex[3]] = rotation.getQ3();

  62.             // Convert units and format
  63.             return QUATERNION.formatData(data);

  64.         }

  65.         /** {@inheritDoc} */
  66.         @Override
  67.         public TimeStampedAngularCoordinates build(final boolean isFirst,
  68.                                                    final boolean isExternal2SpacecraftBody,
  69.                                                    final RotationOrder eulerRotSequence,
  70.                                                    final boolean isSpacecraftBodyRate,
  71.                                                    final AbsoluteDate date,
  72.                                                    final double... components) {

  73.             Rotation rotation = isFirst ?
  74.                                 new Rotation(components[0], components[1], components[2], components[3], true) :
  75.                                 new Rotation(components[3], components[0], components[1], components[2], true);
  76.             if (!isExternal2SpacecraftBody) {
  77.                 rotation = rotation.revert();
  78.             }

  79.             // Return
  80.             return new TimeStampedAngularCoordinates(date, rotation, Vector3D.ZERO, Vector3D.ZERO);

  81.         }

  82.     },

  83.     /** Quaternion and derivatives. */
  84.     QUATERNION_DERIVATIVE("QUATERNION/DERIVATIVE", AngularDerivativesFilter.USE_RR,
  85.                           Unit.ONE, Unit.ONE, Unit.ONE, Unit.ONE,
  86.                           Units.ONE_PER_S, Units.ONE_PER_S, Units.ONE_PER_S, Units.ONE_PER_S) {

  87.         /** {@inheritDoc} */
  88.         @Override
  89.         public String[] createDataFields(final boolean isFirst, final boolean isExternal2SpacecraftBody,
  90.                                          final RotationOrder eulerRotSequence, final boolean isSpacecraftBodyRate,
  91.                                          final TimeStampedAngularCoordinates coordinates) {

  92.             // Initialize the array of attitude data
  93.             final double[] data = new double[8];

  94.             FieldRotation<UnivariateDerivative1> rotation = coordinates.toUnivariateDerivative1Rotation();
  95.             if (!isExternal2SpacecraftBody) {
  96.                 rotation = rotation.revert();
  97.             }

  98.             // Data index
  99.             final int[] quaternionIndex = isFirst ?
  100.                                           new int[] {0, 1, 2, 3, 4, 5, 6, 7} :
  101.                                           new int[] {3, 0, 1, 2, 7, 4, 5, 6};

  102.             // Fill the array
  103.             data[quaternionIndex[0]] = rotation.getQ0().getValue();
  104.             data[quaternionIndex[1]] = rotation.getQ1().getValue();
  105.             data[quaternionIndex[2]] = rotation.getQ2().getValue();
  106.             data[quaternionIndex[3]] = rotation.getQ3().getValue();
  107.             data[quaternionIndex[4]] = rotation.getQ0().getFirstDerivative();
  108.             data[quaternionIndex[5]] = rotation.getQ1().getFirstDerivative();
  109.             data[quaternionIndex[6]] = rotation.getQ2().getFirstDerivative();
  110.             data[quaternionIndex[7]] = rotation.getQ3().getFirstDerivative();

  111.             // Convert units and format
  112.             return QUATERNION_DERIVATIVE.formatData(data);

  113.         }

  114.         /** {@inheritDoc} */
  115.         @Override
  116.         public TimeStampedAngularCoordinates build(final boolean isFirst,
  117.                                                    final boolean isExternal2SpacecraftBody,
  118.                                                    final RotationOrder eulerRotSequence,
  119.                                                    final boolean isSpacecraftBodyRate,
  120.                                                    final AbsoluteDate date,
  121.                                                    final double... components) {
  122.             FieldRotation<UnivariateDerivative1> rotation =
  123.                             isFirst ?
  124.                             new FieldRotation<>(new UnivariateDerivative1(components[0], components[4]),
  125.                                                 new UnivariateDerivative1(components[1], components[5]),
  126.                                                 new UnivariateDerivative1(components[2], components[6]),
  127.                                                 new UnivariateDerivative1(components[3], components[7]),
  128.                                                 true) :
  129.                             new FieldRotation<>(new UnivariateDerivative1(components[3], components[7]),
  130.                                                 new UnivariateDerivative1(components[0], components[4]),
  131.                                                 new UnivariateDerivative1(components[1], components[5]),
  132.                                                 new UnivariateDerivative1(components[2], components[6]),
  133.                                                 true);
  134.             if (!isExternal2SpacecraftBody) {
  135.                 rotation = rotation.revert();
  136.             }

  137.             return new TimeStampedAngularCoordinates(date, rotation);

  138.         }

  139.     },

  140.     /** Quaternion and rotation rate. */
  141.     QUATERNION_RATE("QUATERNION/RATE", AngularDerivativesFilter.USE_RR,
  142.                     Unit.ONE, Unit.ONE, Unit.ONE, Unit.ONE,
  143.                     Units.DEG_PER_S, Units.DEG_PER_S, Units.DEG_PER_S) {

  144.         /** {@inheritDoc} */
  145.         @Override
  146.         public String[] createDataFields(final boolean isFirst, final boolean isExternal2SpacecraftBody,
  147.                                          final RotationOrder eulerRotSequence, final boolean isSpacecraftBodyRate,
  148.                                          final TimeStampedAngularCoordinates coordinates) {

  149.             // Initialize the array of attitude data
  150.             final double[] data = new double[7];

  151.             // Data index
  152.             final int[] quaternionIndex = isFirst ? new int[] {0, 1, 2, 3} : new int[] {3, 0, 1, 2};

  153.             // Attitude
  154.             final TimeStampedAngularCoordinates c = isExternal2SpacecraftBody ? coordinates : coordinates.revert();
  155.             final Vector3D rotationRate = QUATERNION_RATE.metadataRate(isSpacecraftBodyRate, c.getRotationRate(), c.getRotation());

  156.             // Fill the array
  157.             data[quaternionIndex[0]] = c.getRotation().getQ0();
  158.             data[quaternionIndex[1]] = c.getRotation().getQ1();
  159.             data[quaternionIndex[2]] = c.getRotation().getQ2();
  160.             data[quaternionIndex[3]] = c.getRotation().getQ3();
  161.             data[4] = rotationRate.getX();
  162.             data[5] = rotationRate.getY();
  163.             data[6] = rotationRate.getZ();

  164.             // Convert units and format
  165.             return QUATERNION_RATE.formatData(data);

  166.         }

  167.         /** {@inheritDoc} */
  168.         @Override
  169.         public TimeStampedAngularCoordinates build(final boolean isFirst,
  170.                                                    final boolean isExternal2SpacecraftBody,
  171.                                                    final RotationOrder eulerRotSequence,
  172.                                                    final boolean isSpacecraftBodyRate,
  173.                                                    final AbsoluteDate date,
  174.                                                    final double... components) {
  175.             // Build the needed objects
  176.             final Rotation rotation = isFirst ?
  177.                                       new Rotation(components[0], components[1], components[2], components[3], true) :
  178.                                       new Rotation(components[3], components[0], components[1], components[2], true);
  179.             final Vector3D rotationRate = QUATERNION_RATE.orekitRate(isSpacecraftBodyRate,
  180.                                                                      new Vector3D(components[4],
  181.                                                                                   components[5],
  182.                                                                                   components[6]),
  183.                                                                      rotation);

  184.             // Return
  185.             final TimeStampedAngularCoordinates ac =
  186.                             new TimeStampedAngularCoordinates(date, rotation, rotationRate, Vector3D.ZERO);
  187.             return isExternal2SpacecraftBody ? ac : ac.revert();

  188.         }

  189.     },

  190.     /** Euler angles. */
  191.     EULER_ANGLE("EULER ANGLE", AngularDerivativesFilter.USE_R,
  192.                 Unit.DEGREE, Unit.DEGREE, Unit.DEGREE) {

  193.         /** {@inheritDoc} */
  194.         @Override
  195.         public String[] createDataFields(final boolean isFirst, final boolean isExternal2SpacecraftBody,
  196.                                          final RotationOrder eulerRotSequence, final boolean isSpacecraftBodyRate,
  197.                                          final TimeStampedAngularCoordinates coordinates) {

  198.             // Attitude
  199.             Rotation rotation = coordinates.getRotation();
  200.             if (!isExternal2SpacecraftBody) {
  201.                 rotation = rotation.revert();
  202.             }

  203.             final double[] data = rotation.getAngles(eulerRotSequence, RotationConvention.FRAME_TRANSFORM);

  204.             // Convert units and format
  205.             return EULER_ANGLE.formatData(data);

  206.         }

  207.         /** {@inheritDoc} */
  208.         @Override
  209.         public TimeStampedAngularCoordinates build(final boolean isFirst,
  210.                                                    final boolean isExternal2SpacecraftBody,
  211.                                                    final RotationOrder eulerRotSequence,
  212.                                                    final boolean isSpacecraftBodyRate,
  213.                                                    final AbsoluteDate date,
  214.                                                    final double... components) {

  215.             // Build the needed objects
  216.             Rotation rotation = new Rotation(eulerRotSequence, RotationConvention.FRAME_TRANSFORM,
  217.                                              components[0], components[1], components[2]);
  218.             if (!isExternal2SpacecraftBody) {
  219.                 rotation = rotation.revert();
  220.             }

  221.             // Return
  222.             return new TimeStampedAngularCoordinates(date, rotation, Vector3D.ZERO, Vector3D.ZERO);
  223.         }

  224.     },

  225.     /** Euler angles and rotation rate. */
  226.     EULER_ANGLE_RATE("EULER ANGLE/RATE", AngularDerivativesFilter.USE_RR,
  227.                      Unit.DEGREE, Unit.DEGREE, Unit.DEGREE,
  228.                      Units.DEG_PER_S, Units.DEG_PER_S, Units.DEG_PER_S) {

  229.         /** {@inheritDoc} */
  230.         @Override
  231.         public String[] createDataFields(final boolean isFirst, final boolean isExternal2SpacecraftBody,
  232.                                          final RotationOrder eulerRotSequence, final boolean isSpacecraftBodyRate,
  233.                                          final TimeStampedAngularCoordinates coordinates) {

  234.             // Initialize the array of attitude data
  235.             final double[] data = new double[6];

  236.             // Attitude
  237.             final TimeStampedAngularCoordinates c = isExternal2SpacecraftBody ? coordinates : coordinates.revert();
  238.             final Vector3D rotationRate = EULER_ANGLE_RATE.metadataRate(isSpacecraftBodyRate, c.getRotationRate(), c.getRotation());
  239.             final double[] angles       = c.getRotation().getAngles(eulerRotSequence, RotationConvention.FRAME_TRANSFORM);

  240.             // Fill the array
  241.             data[0] = angles[0];
  242.             data[1] = angles[1];
  243.             data[2] = angles[2];
  244.             data[3] = Vector3D.dotProduct(rotationRate, eulerRotSequence.getA1());
  245.             data[4] = Vector3D.dotProduct(rotationRate, eulerRotSequence.getA2());
  246.             data[5] = Vector3D.dotProduct(rotationRate, eulerRotSequence.getA3());

  247.             // Convert units and format
  248.             return EULER_ANGLE_RATE.formatData(data);

  249.         }

  250.         /** {@inheritDoc} */
  251.         @Override
  252.         public TimeStampedAngularCoordinates build(final boolean isFirst,
  253.                                                    final boolean isExternal2SpacecraftBody,
  254.                                                    final RotationOrder eulerRotSequence,
  255.                                                    final boolean isSpacecraftBodyRate,
  256.                                                    final AbsoluteDate date,
  257.                                                    final double... components) {

  258.             // Build the needed objects
  259.             final Rotation rotation = new Rotation(eulerRotSequence,
  260.                                                    RotationConvention.FRAME_TRANSFORM,
  261.                                                    components[0],
  262.                                                    components[1],
  263.                                                    components[2]);
  264.             final Vector3D rotationRate = EULER_ANGLE_RATE.orekitRate(isSpacecraftBodyRate,
  265.                                                                       new Vector3D(components[3], eulerRotSequence.getA1(),
  266.                                                                                    components[4], eulerRotSequence.getA2(),
  267.                                                                                    components[5], eulerRotSequence.getA3()),
  268.                                                                       rotation);
  269.             // Return
  270.             final TimeStampedAngularCoordinates ac =
  271.                             new TimeStampedAngularCoordinates(date, rotation, rotationRate, Vector3D.ZERO);
  272.             return isExternal2SpacecraftBody ? ac : ac.revert();

  273.         }

  274.     },

  275.     /** Spin.
  276.      * <p>
  277.      * CCSDS enforces that spin axis is +Z, so if {@link #createDataFields(boolean, boolean, RotationOrder, boolean,
  278.      * TimeStampedAngularCoordinates) createDataFields} is called with {@code coordinates} with {@link
  279.      * TimeStampedAngularCoordinates#getRotationRate() rotation rate} that is not along the Z axis, result is
  280.      * undefined.
  281.      * </p>
  282.      */
  283.     SPIN("SPIN", AngularDerivativesFilter.USE_RR,
  284.          Unit.DEGREE, Unit.DEGREE, Unit.DEGREE, Units.DEG_PER_S) {

  285.         /** {@inheritDoc} */
  286.         @Override
  287.         public String[] createDataFields(final boolean isFirst, final boolean isExternal2SpacecraftBody,
  288.                                          final RotationOrder eulerRotSequence, final boolean isSpacecraftBodyRate,
  289.                                          final TimeStampedAngularCoordinates coordinates) {

  290.             // Initialize the array of attitude data
  291.             final double[] data = new double[4];

  292.             // Attitude
  293.             final double[] angles = coordinates.getRotation().getAngles(RotationOrder.ZYZ, RotationConvention.FRAME_TRANSFORM);

  294.             // Fill the array
  295.             data[0] = angles[0];
  296.             data[1] = 0.5 * FastMath.PI - angles[1];
  297.             data[2] = angles[2];
  298.             data[3] = coordinates.getRotationRate().getZ();

  299.             // Convert units and format
  300.             return SPIN.formatData(data);

  301.         }

  302.         /** {@inheritDoc} */
  303.         @Override
  304.         public TimeStampedAngularCoordinates build(final boolean isFirst,
  305.                                                    final boolean isExternal2SpacecraftBody,
  306.                                                    final RotationOrder eulerRotSequence,
  307.                                                    final boolean isSpacecraftBodyRate,
  308.                                                    final AbsoluteDate date,
  309.                                                    final double... components) {

  310.             // Build the needed objects
  311.             final Rotation rotation = new Rotation(RotationOrder.ZYZ,
  312.                                                    RotationConvention.FRAME_TRANSFORM,
  313.                                                    components[0],
  314.                                                    0.5 * FastMath.PI - components[1],
  315.                                                    components[2]);
  316.             final Vector3D rotationRate = new Vector3D(0, 0, components[3]);

  317.             // Return
  318.             return new TimeStampedAngularCoordinates(date, rotation, rotationRate, Vector3D.ZERO);

  319.         }

  320.     },

  321.     /** Spin and nutation. */
  322.     SPIN_NUTATION("SPIN/NUTATION", AngularDerivativesFilter.USE_RR,
  323.                   Unit.DEGREE, Unit.DEGREE, Unit.DEGREE, Units.DEG_PER_S,
  324.                   Unit.DEGREE, Unit.SECOND, Unit.DEGREE) {

  325.         /** {@inheritDoc} */
  326.         @Override
  327.         public String[] createDataFields(final boolean isFirst, final boolean isExternal2SpacecraftBody,
  328.                                          final RotationOrder eulerRotSequence, final boolean isSpacecraftBodyRate,
  329.                                          final TimeStampedAngularCoordinates coordinates) {
  330.             // Attitude parameters in the Specified Reference Frame for a Spin Stabilized Satellite
  331.             // are optional in CCSDS AEM format. Support for this attitude type is not implemented
  332.             // yet in Orekit.
  333.             throw new OrekitException(OrekitMessages.CCSDS_AEM_ATTITUDE_TYPE_NOT_IMPLEMENTED, name());
  334.         }

  335.         /** {@inheritDoc} */
  336.         @Override
  337.         public TimeStampedAngularCoordinates build(final boolean isFirst,
  338.                                                    final boolean isExternal2SpacecraftBody,
  339.                                                    final RotationOrder eulerRotSequence,
  340.                                                    final boolean isSpacecraftBodyRate,
  341.                                                    final AbsoluteDate date,
  342.                                                    final double... components) {
  343.             // Attitude parameters in the Specified Reference Frame for a Spin Stabilized Satellite
  344.             // are optional in CCSDS AEM format. Support for this attitude type is not implemented
  345.             // yet in Orekit.
  346.             throw new OrekitException(OrekitMessages.CCSDS_AEM_ATTITUDE_TYPE_NOT_IMPLEMENTED, name());
  347.         }

  348.     };

  349.     /** Pattern for normalizing attitude types. */
  350.     private static final Pattern TYPE_SEPARATORS = Pattern.compile("[ _/]+");

  351.     /** CCSDS name of the attitude type. */
  352.     private final String ccsdsName;

  353.     /** Derivatives filter. */
  354.     private final AngularDerivativesFilter filter;

  355.     /** Components units (used only for parsing). */
  356.     private final Unit[] units;

  357.     /** Private constructor.
  358.      * @param ccsdsName CCSDS name of the attitude type
  359.      * @param filter derivative filter
  360.      * @param units components units (used only for parsing)
  361.      */
  362.     AttitudeType(final String ccsdsName, final AngularDerivativesFilter filter, final Unit... units) {
  363.         this.ccsdsName = ccsdsName;
  364.         this.filter    = filter;
  365.         this.units     = units.clone();
  366.     }

  367.     /** {@inheritDoc} */
  368.     @Override
  369.     public String toString() {
  370.         return ccsdsName;
  371.     }

  372.     /** Parse an attitude type.
  373.      * @param type unnormalized type name
  374.      * @return parsed type
  375.      */
  376.     public static AttitudeType parseType(final String type) {
  377.         return AttitudeType.valueOf(TYPE_SEPARATORS.matcher(type).replaceAll("_"));
  378.     }

  379.     /**
  380.      * Get the attitude data fields corresponding to the attitude type.
  381.      * <p>
  382.      * This method returns the components in CCSDS units (i.e. degrees, degrees per seconds…).
  383.      * </p>
  384.      * @param isFirst if true the first quaternion component is the scalar component
  385.      * @param isExternal2SpacecraftBody true attitude is from external frame to spacecraft body frame
  386.      * @param eulerRotSequence sequance of Euler angles
  387.      * @param isSpacecraftBodyRate if true Euler rates are specified in spacecraft body frame
  388.      * @param attitude angular coordinates, using {@link Attitude Attitude} convention
  389.      * (i.e. from inertial frame to spacecraft frame)
  390.      * @return the attitude data in CCSDS units
  391.      */
  392.     public abstract String[] createDataFields(boolean isFirst, boolean isExternal2SpacecraftBody,
  393.                                               RotationOrder eulerRotSequence, boolean isSpacecraftBodyRate,
  394.                                               TimeStampedAngularCoordinates attitude);

  395.     /**
  396.      * Get the angular coordinates corresponding to the attitude data.
  397.      * <p>
  398.      * This method assumes the text fields are in CCSDS units and will convert to SI units.
  399.      * </p>
  400.      * @param isFirst if true the first quaternion component is the scalar component
  401.      * @param isExternal2SpacecraftBody true attitude is from external frame to spacecraft body frame
  402.      * @param eulerRotSequence sequance of Euler angles
  403.      * @param isSpacecraftBodyRate if true Euler rates are specified in spacecraft body frame
  404.      * @param context context binding
  405.      * @param fields raw data fields
  406.      * @return the angular coordinates, using {@link Attitude Attitude} convention
  407.      * (i.e. from inertial frame to spacecraft frame)
  408.      */
  409.     public TimeStampedAngularCoordinates parse(final boolean isFirst, final boolean isExternal2SpacecraftBody,
  410.                                                final RotationOrder eulerRotSequence, final boolean isSpacecraftBodyRate,
  411.                                                final ContextBinding context, final String[] fields) {

  412.         // parse the text fields
  413.         final AbsoluteDate date = context.getTimeSystem().getConverter(context).parse(fields[0]);
  414.         final double[] components = new double[fields.length - 1];
  415.         for (int i = 0; i < components.length; ++i) {
  416.             components[i] = units[i].toSI(Double.parseDouble(fields[i + 1]));
  417.         }

  418.         // build the coordinates
  419.         return build(isFirst, isExternal2SpacecraftBody, eulerRotSequence, isSpacecraftBodyRate,
  420.                      date, components);

  421.     }

  422.     /** Get the angular coordinates corresponding to the attitude data.
  423.      * @param isFirst if true the first quaternion component is the scalar component
  424.      * @param isExternal2SpacecraftBody true attitude is from external frame to spacecraft body frame
  425.      * @param eulerRotSequence sequance of Euler angles
  426.      * @param isSpacecraftBodyRate if true Euler rates are specified in spacecraft body frame
  427.      * @param date entry date
  428.      * @param components entry components with CCSDS units (i.e. angles
  429.      * <em>must</em> still be in degrees here), semantic depends on attitude type
  430.      * @return the angular coordinates, using {@link Attitude Attitude} convention
  431.      * (i.e. from inertial frame to spacecraft frame)
  432.      */
  433.     public abstract TimeStampedAngularCoordinates build(boolean isFirst, boolean isExternal2SpacecraftBody,
  434.                                                         RotationOrder eulerRotSequence, boolean isSpacecraftBodyRate,
  435.                                                         AbsoluteDate date, double... components);

  436.     /**
  437.      * Get the angular derivative filter corresponding to the attitude data.
  438.      * @return the angular derivative filter corresponding to the attitude data
  439.      */
  440.     public AngularDerivativesFilter getAngularDerivativesFilter() {
  441.         return filter;
  442.     }

  443.     private String[] formatData(final double[] data) {
  444.         final String[] fields = new String[data.length];
  445.         for (int i = 0; i < data.length; ++i) {
  446.             fields[i] = AccurateFormatter.format(units[i].fromSI(data[i]));
  447.         }
  448.         return fields;
  449.     }

  450.     /** Convert a rotation rate for Orekit convention to metadata convention.
  451.      * @param isSpacecraftBodyRate if true Euler rates are specified in spacecraft body frame
  452.      * @param rate rotation rate from Orekit attitude
  453.      * @param rotation corresponding rotation
  454.      * @return rotation rate in metadata convention
  455.      */
  456.     private Vector3D metadataRate(final boolean isSpacecraftBodyRate, final Vector3D rate, final Rotation rotation) {
  457.         return isSpacecraftBodyRate ? rate : rotation.applyInverseTo(rate);
  458.     }

  459.     /** Convert a rotation rate for metadata convention to Orekit convention.
  460.      * @param isSpacecraftBodyRate if true Euler rates are specified in spacecraft body frame
  461.      * @param rate rotation rate read from the data line
  462.      * @param rotation corresponding rotation
  463.      * @return rotation rate in Orekit convention (i.e. in spacecraft body local frame)
  464.      */
  465.     private Vector3D orekitRate(final boolean isSpacecraftBodyRate, final Vector3D rate, final Rotation rotation) {
  466.         return isSpacecraftBodyRate ? rate : rotation.applyTo(rate);
  467.     }

  468. }