AttitudeType.java

  1. /* Copyright 2002-2025 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.Arrays;
  19. import java.util.Collections;
  20. import java.util.HashMap;
  21. import java.util.Map;

  22. import org.hipparchus.analysis.differentiation.UnivariateDerivative1;
  23. import org.hipparchus.analysis.differentiation.UnivariateDerivative2;
  24. import org.hipparchus.geometry.euclidean.threed.FieldRotation;
  25. import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
  26. import org.hipparchus.geometry.euclidean.threed.Rotation;
  27. import org.hipparchus.geometry.euclidean.threed.RotationConvention;
  28. import org.hipparchus.geometry.euclidean.threed.RotationOrder;
  29. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  30. import org.hipparchus.util.FastMath;
  31. import org.hipparchus.util.MathUtils;
  32. import org.hipparchus.util.SinCos;
  33. import org.orekit.attitudes.Attitude;
  34. import org.orekit.errors.OrekitException;
  35. import org.orekit.errors.OrekitMessages;
  36. import org.orekit.files.ccsds.definitions.Units;
  37. import org.orekit.files.ccsds.utils.ContextBinding;
  38. import org.orekit.time.AbsoluteDate;
  39. import org.orekit.utils.AccurateFormatter;
  40. import org.orekit.utils.AngularDerivativesFilter;
  41. import org.orekit.utils.Formatter;
  42. import org.orekit.utils.TimeStampedAngularCoordinates;
  43. import org.orekit.utils.units.Unit;

  44. /** Enumerate for ADM attitude type.
  45.  * @author Bryan Cazabonne
  46.  * @since 10.2
  47.  */
  48. public enum AttitudeType {

  49.     /** Quaternion. */
  50.     QUATERNION(Collections.singleton(new VersionedName(1.0, "QUATERNION")),
  51.                AngularDerivativesFilter.USE_R,
  52.                Unit.ONE, Unit.ONE, Unit.ONE, Unit.ONE) {

  53.         /** {@inheritDoc} */
  54.         @Override
  55.         public double[] generateData(final boolean isFirst, final boolean isExternal2SpacecraftBody,
  56.                                      final RotationOrder eulerRotSequence, final boolean isSpacecraftBodyRate,
  57.                                      final TimeStampedAngularCoordinates coordinates) {

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

  60.             Rotation rotation  = coordinates.getRotation();
  61.             if (!isExternal2SpacecraftBody) {
  62.                 rotation = rotation.revert();
  63.             }

  64.             // Fill the array, taking care of quaternion ordering
  65.             final double[] data = new double[4];
  66.             data[quaternionIndex[0]] = rotation.getQ0();
  67.             data[quaternionIndex[1]] = rotation.getQ1();
  68.             data[quaternionIndex[2]] = rotation.getQ2();
  69.             data[quaternionIndex[3]] = rotation.getQ3();

  70.             return data;

  71.         }

  72.         /** {@inheritDoc} */
  73.         @Override
  74.         public TimeStampedAngularCoordinates build(final boolean isFirst,
  75.                                                    final boolean isExternal2SpacecraftBody,
  76.                                                    final RotationOrder eulerRotSequence,
  77.                                                    final boolean isSpacecraftBodyRate,
  78.                                                    final AbsoluteDate date,
  79.                                                    final double... components) {

  80.             Rotation rotation = isFirst ?
  81.                                 new Rotation(components[0], components[1], components[2], components[3], true) :
  82.                                 new Rotation(components[3], components[0], components[1], components[2], true);
  83.             if (!isExternal2SpacecraftBody) {
  84.                 rotation = rotation.revert();
  85.             }

  86.             // Return
  87.             return new TimeStampedAngularCoordinates(date, rotation, Vector3D.ZERO, Vector3D.ZERO);

  88.         }

  89.     },

  90.     /** Quaternion and derivatives. */
  91.     QUATERNION_DERIVATIVE(Collections.singleton(new VersionedName(1.0, "QUATERNION/DERIVATIVE")),
  92.                           AngularDerivativesFilter.USE_RR,
  93.                           Unit.ONE, Unit.ONE, Unit.ONE, Unit.ONE,
  94.                           Units.ONE_PER_S, Units.ONE_PER_S, Units.ONE_PER_S, Units.ONE_PER_S) {

  95.         /** {@inheritDoc} */
  96.         @Override
  97.         public double[] generateData(final boolean isFirst, final boolean isExternal2SpacecraftBody,
  98.                                      final RotationOrder eulerRotSequence, final boolean isSpacecraftBodyRate,
  99.                                      final TimeStampedAngularCoordinates coordinates) {

  100.             FieldRotation<UnivariateDerivative1> rotation = coordinates.toUnivariateDerivative1Rotation();
  101.             if (!isExternal2SpacecraftBody) {
  102.                 rotation = rotation.revert();
  103.             }

  104.             // Data index
  105.             final int[] quaternionIndex = isFirst ?
  106.                                           new int[] {0, 1, 2, 3, 4, 5, 6, 7} :
  107.                                           new int[] {3, 0, 1, 2, 7, 4, 5, 6};

  108.             // Fill the array, taking care of quaternion ordering
  109.             final double[] data = new double[8];
  110.             data[quaternionIndex[0]] = rotation.getQ0().getValue();
  111.             data[quaternionIndex[1]] = rotation.getQ1().getValue();
  112.             data[quaternionIndex[2]] = rotation.getQ2().getValue();
  113.             data[quaternionIndex[3]] = rotation.getQ3().getValue();
  114.             data[quaternionIndex[4]] = rotation.getQ0().getFirstDerivative();
  115.             data[quaternionIndex[5]] = rotation.getQ1().getFirstDerivative();
  116.             data[quaternionIndex[6]] = rotation.getQ2().getFirstDerivative();
  117.             data[quaternionIndex[7]] = rotation.getQ3().getFirstDerivative();

  118.             return data;

  119.         }

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

  143.             return new TimeStampedAngularCoordinates(date, rotation);

  144.         }

  145.     },

  146.     /** Quaternion and Euler angles rates (only in ADM V1). */
  147.     QUATERNION_EULER_RATES(Collections.singleton(new VersionedName(1.0, "QUATERNION/RATE")),
  148.                            AngularDerivativesFilter.USE_RR,
  149.                            Unit.ONE, Unit.ONE, Unit.ONE, Unit.ONE,
  150.                            Units.DEG_PER_S, Units.DEG_PER_S, Units.DEG_PER_S) {

  151.         /** {@inheritDoc} */
  152.         @Override
  153.         public double[] generateData(final boolean isFirst, final boolean isExternal2SpacecraftBody,
  154.                                      final RotationOrder eulerRotSequence, final boolean isSpacecraftBodyRate,
  155.                                      final TimeStampedAngularCoordinates coordinates) {

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

  158.             // Attitude
  159.             FieldRotation<UnivariateDerivative1> rotation = coordinates.toUnivariateDerivative1Rotation();
  160.             if (!isExternal2SpacecraftBody) {
  161.                 rotation = rotation.revert();
  162.             }
  163.             final UnivariateDerivative1[] euler = rotation.getAngles(eulerRotSequence, RotationConvention.FRAME_TRANSFORM);

  164.             // Fill the array, taking care of quaternion ordering
  165.             final double[] data = new double[7];
  166.             data[quaternionIndex[0]] = rotation.getQ0().getValue();
  167.             data[quaternionIndex[1]] = rotation.getQ1().getValue();
  168.             data[quaternionIndex[2]] = rotation.getQ2().getValue();
  169.             data[quaternionIndex[3]] = rotation.getQ3().getValue();
  170.             data[4]                  = euler[0].getFirstDerivative();
  171.             data[5]                  = euler[1].getFirstDerivative();
  172.             data[6]                  = euler[2].getFirstDerivative();

  173.             return data;

  174.         }

  175.         /** {@inheritDoc} */
  176.         @Override
  177.         public TimeStampedAngularCoordinates build(final boolean isFirst,
  178.                                                    final boolean isExternal2SpacecraftBody,
  179.                                                    final RotationOrder eulerRotSequence,
  180.                                                    final boolean isSpacecraftBodyRate,
  181.                                                    final AbsoluteDate date,
  182.                                                    final double... components) {
  183.             // Build the needed objects
  184.             final Rotation rotation = isFirst ?
  185.                                       new Rotation(components[0], components[1], components[2], components[3], true) :
  186.                                       new Rotation(components[3], components[0], components[1], components[2], true);
  187.             final double[] euler = rotation.getAngles(eulerRotSequence, RotationConvention.FRAME_TRANSFORM);
  188.             final FieldRotation<UnivariateDerivative1> rUD1 =
  189.                             new FieldRotation<>(eulerRotSequence, RotationConvention.FRAME_TRANSFORM,
  190.                                                 new UnivariateDerivative1(euler[0], components[4]),
  191.                                                 new UnivariateDerivative1(euler[1], components[5]),
  192.                                                 new UnivariateDerivative1(euler[2], components[6]));

  193.             // Return
  194.             final TimeStampedAngularCoordinates ac = new TimeStampedAngularCoordinates(date, rUD1);
  195.             return isExternal2SpacecraftBody ? ac : ac.revert();

  196.         }

  197.     },

  198.     /** Quaternion and angular velocity. */
  199.     QUATERNION_ANGVEL(Collections.singleton(new VersionedName(2.0, "QUATERNION/ANGVEL")),
  200.                       AngularDerivativesFilter.USE_RR,
  201.                       Unit.ONE, Unit.ONE, Unit.ONE, Unit.ONE,
  202.                       Units.DEG_PER_S, Units.DEG_PER_S, Units.DEG_PER_S) {

  203.         /** {@inheritDoc} */
  204.         @Override
  205.         public double[] generateData(final boolean isFirst, final boolean isExternal2SpacecraftBody,
  206.                                      final RotationOrder eulerRotSequence, final boolean isSpacecraftBodyRate,
  207.                                      final TimeStampedAngularCoordinates coordinates) {

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

  210.             // Attitude
  211.             final TimeStampedAngularCoordinates c = isExternal2SpacecraftBody ? coordinates : coordinates.revert();
  212.             final Vector3D rotationRate = QUATERNION_ANGVEL.metadataRate(isSpacecraftBodyRate, c.getRotationRate(), c.getRotation());

  213.             // Fill the array, taking care of quaternion ordering
  214.             final double[] data = new double[7];
  215.             data[quaternionIndex[0]] = c.getRotation().getQ0();
  216.             data[quaternionIndex[1]] = c.getRotation().getQ1();
  217.             data[quaternionIndex[2]] = c.getRotation().getQ2();
  218.             data[quaternionIndex[3]] = c.getRotation().getQ3();
  219.             data[4] = rotationRate.getX();
  220.             data[5] = rotationRate.getY();
  221.             data[6] = rotationRate.getZ();

  222.             return data;

  223.         }

  224.         /** {@inheritDoc} */
  225.         @Override
  226.         public TimeStampedAngularCoordinates build(final boolean isFirst,
  227.                                                    final boolean isExternal2SpacecraftBody,
  228.                                                    final RotationOrder eulerRotSequence,
  229.                                                    final boolean isSpacecraftBodyRate,
  230.                                                    final AbsoluteDate date,
  231.                                                    final double... components) {
  232.             // Build the needed objects
  233.             final Rotation rotation = isFirst ?
  234.                                       new Rotation(components[0], components[1], components[2], components[3], true) :
  235.                                       new Rotation(components[3], components[0], components[1], components[2], true);
  236.             final Vector3D rotationRate = QUATERNION_ANGVEL.orekitRate(isSpacecraftBodyRate,
  237.                                                                        new Vector3D(components[4], components[5], components[6]),
  238.                                                                        rotation);

  239.             // Return
  240.             final TimeStampedAngularCoordinates ac =
  241.                             new TimeStampedAngularCoordinates(date, rotation, rotationRate, Vector3D.ZERO);
  242.             return isExternal2SpacecraftBody ? ac : ac.revert();

  243.         }

  244.     },

  245.     /** Euler angles. */
  246.     EULER_ANGLE(Collections.singleton(new VersionedName(1.0, "EULER_ANGLE")),
  247.                 AngularDerivativesFilter.USE_R,
  248.                 Unit.DEGREE, Unit.DEGREE, Unit.DEGREE) {

  249.         /** {@inheritDoc} */
  250.         @Override
  251.         public double[] generateData(final boolean isFirst, final boolean isExternal2SpacecraftBody,
  252.                                      final RotationOrder eulerRotSequence, final boolean isSpacecraftBodyRate,
  253.                                      final TimeStampedAngularCoordinates coordinates) {

  254.             // Attitude
  255.             Rotation rotation = coordinates.getRotation();
  256.             if (!isExternal2SpacecraftBody) {
  257.                 rotation = rotation.revert();
  258.             }

  259.             return rotation.getAngles(eulerRotSequence, RotationConvention.FRAME_TRANSFORM);

  260.         }

  261.         /** {@inheritDoc} */
  262.         @Override
  263.         public TimeStampedAngularCoordinates build(final boolean isFirst,
  264.                                                    final boolean isExternal2SpacecraftBody,
  265.                                                    final RotationOrder eulerRotSequence,
  266.                                                    final boolean isSpacecraftBodyRate,
  267.                                                    final AbsoluteDate date,
  268.                                                    final double... components) {

  269.             // Build the needed objects
  270.             Rotation rotation = new Rotation(eulerRotSequence, RotationConvention.FRAME_TRANSFORM,
  271.                                              components[0], components[1], components[2]);
  272.             if (!isExternal2SpacecraftBody) {
  273.                 rotation = rotation.revert();
  274.             }

  275.             // Return
  276.             return new TimeStampedAngularCoordinates(date, rotation, Vector3D.ZERO, Vector3D.ZERO);
  277.         }

  278.     },

  279.     /** Euler angles and rotation rate. */
  280.     EULER_ANGLE_DERIVATIVE(Arrays.asList(new VersionedName(1.0, "EULER_ANGLE/RATE"),
  281.                                          new VersionedName(2.0, "EULER_ANGLE/DERIVATIVE")),
  282.                            AngularDerivativesFilter.USE_RR,
  283.                            Unit.DEGREE, Unit.DEGREE, Unit.DEGREE,
  284.                            Units.DEG_PER_S, Units.DEG_PER_S, Units.DEG_PER_S) {

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

  290.             // Attitude
  291.             FieldRotation<UnivariateDerivative1> rotation = coordinates.toUnivariateDerivative1Rotation();
  292.             if (!isExternal2SpacecraftBody) {
  293.                 rotation = rotation.revert();
  294.             }

  295.             final UnivariateDerivative1[] angles = rotation.getAngles(eulerRotSequence, RotationConvention.FRAME_TRANSFORM);

  296.             return new double[] {
  297.                 angles[0].getValue(),
  298.                 angles[1].getValue(),
  299.                 angles[2].getValue(),
  300.                 angles[0].getFirstDerivative(),
  301.                 angles[1].getFirstDerivative(),
  302.                 angles[2].getFirstDerivative()
  303.             };

  304.         }

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

  313.             // Build the needed objects
  314.             FieldRotation<UnivariateDerivative1> rotation =
  315.                             new FieldRotation<>(eulerRotSequence, RotationConvention.FRAME_TRANSFORM,
  316.                                                 new UnivariateDerivative1(components[0], components[3]),
  317.                                                 new UnivariateDerivative1(components[1], components[4]),
  318.                                                 new UnivariateDerivative1(components[2], components[5]));
  319.             if (!isExternal2SpacecraftBody) {
  320.                 rotation = rotation.revert();
  321.             }

  322.             return new TimeStampedAngularCoordinates(date, rotation);

  323.         }

  324.     },

  325.     /** Euler angles and angular velocity.
  326.      * @since 12.0
  327.      */
  328.     EULER_ANGLE_ANGVEL(Collections.singleton(new VersionedName(2.0, "EULER_ANGLE/ANGVEL")),
  329.                        AngularDerivativesFilter.USE_RR,
  330.                        Unit.DEGREE, Unit.DEGREE, Unit.DEGREE,
  331.                        Units.DEG_PER_S, Units.DEG_PER_S, Units.DEG_PER_S) {

  332.         /** {@inheritDoc} */
  333.         @Override
  334.         public double[] generateData(final boolean isFirst, final boolean isExternal2SpacecraftBody,
  335.                                      final RotationOrder eulerRotSequence, final boolean isSpacecraftBodyRate,
  336.                                      final TimeStampedAngularCoordinates coordinates) {

  337.             // Attitude
  338.             final TimeStampedAngularCoordinates c = isExternal2SpacecraftBody ? coordinates : coordinates.revert();
  339.             final Vector3D rotationRate = EULER_ANGLE_ANGVEL.metadataRate(isSpacecraftBodyRate, c.getRotationRate(), c.getRotation());
  340.             final double[] angles       = c.getRotation().getAngles(eulerRotSequence, RotationConvention.FRAME_TRANSFORM);

  341.             return new double[] {
  342.                 angles[0],
  343.                 angles[1],
  344.                 angles[2],
  345.                 rotationRate.getX(),
  346.                 rotationRate.getY(),
  347.                 rotationRate.getZ()
  348.             };

  349.         }

  350.         /** {@inheritDoc} */
  351.         @Override
  352.         public TimeStampedAngularCoordinates build(final boolean isFirst,
  353.                                                    final boolean isExternal2SpacecraftBody,
  354.                                                    final RotationOrder eulerRotSequence,
  355.                                                    final boolean isSpacecraftBodyRate,
  356.                                                    final AbsoluteDate date,
  357.                                                    final double... components) {

  358.             // Build the needed objects
  359.             final Rotation rotation = new Rotation(eulerRotSequence,
  360.                                                    RotationConvention.FRAME_TRANSFORM,
  361.                                                    components[0],
  362.                                                    components[1],
  363.                                                    components[2]);
  364.             final Vector3D rotationRate = EULER_ANGLE_ANGVEL.orekitRate(isSpacecraftBodyRate,
  365.                                                                         new Vector3D(components[3], components[4], components[5]),
  366.                                                                         rotation);
  367.             // Return
  368.             final TimeStampedAngularCoordinates ac =
  369.                             new TimeStampedAngularCoordinates(date, rotation, rotationRate, Vector3D.ZERO);
  370.             return isExternal2SpacecraftBody ? ac : ac.revert();

  371.         }

  372.     },

  373.     /** Spin. */
  374.     SPIN(Collections.singleton(new VersionedName(1.0, "SPIN")),
  375.          AngularDerivativesFilter.USE_R,
  376.          Unit.DEGREE, Unit.DEGREE, Unit.DEGREE, Units.DEG_PER_S) {

  377.         /** {@inheritDoc} */
  378.         @Override
  379.         public double[] generateData(final boolean isFirst, final boolean isExternal2SpacecraftBody,
  380.                                      final RotationOrder eulerRotSequence, final boolean isSpacecraftBodyRate,
  381.                                      final TimeStampedAngularCoordinates coordinates) {

  382.             // spin axis is forced to Z (but it is not the instantaneous rotation rate as it also moves)
  383.             final TimeStampedAngularCoordinates c = isExternal2SpacecraftBody ? coordinates : coordinates.revert();
  384.             final SpinFinder sf = new SpinFinder(c);
  385.             final double spinAngleVel = coordinates.getRotationRate().getZ();

  386.             return new double[] {
  387.                 sf.getSpinAlpha(), sf.getSpinDelta(), sf.getSpinAngle(), spinAngleVel
  388.             };

  389.         }

  390.         /** {@inheritDoc} */
  391.         @Override
  392.         public TimeStampedAngularCoordinates build(final boolean isFirst,
  393.                                                    final boolean isExternal2SpacecraftBody,
  394.                                                    final RotationOrder eulerRotSequence,
  395.                                                    final boolean isSpacecraftBodyRate,
  396.                                                    final AbsoluteDate date,
  397.                                                    final double... components) {

  398.             // Build the needed objects
  399.             final Rotation rotation = new Rotation(RotationOrder.ZXZ,
  400.                                                    RotationConvention.FRAME_TRANSFORM,
  401.                                                    MathUtils.SEMI_PI + components[0],
  402.                                                    MathUtils.SEMI_PI - components[1],
  403.                                                    components[2]);
  404.             final Vector3D rotationRate = new Vector3D(0, 0, components[3]);

  405.             // Return
  406.             final TimeStampedAngularCoordinates ac =
  407.                             new TimeStampedAngularCoordinates(date, rotation, rotationRate, Vector3D.ZERO);
  408.             return isExternal2SpacecraftBody ? ac : ac.revert();

  409.         }

  410.     },

  411.     /** Spin and nutation. */
  412.     SPIN_NUTATION(Collections.singleton(new VersionedName(1.0, "SPIN/NUTATION")),
  413.                   AngularDerivativesFilter.USE_RR,
  414.                   Unit.DEGREE, Unit.DEGREE, Unit.DEGREE, Units.DEG_PER_S,
  415.                   Unit.DEGREE, Unit.SECOND, Unit.DEGREE) {

  416.         /** {@inheritDoc} */
  417.         @Override
  418.         public double[] generateData(final boolean isFirst, final boolean isExternal2SpacecraftBody,
  419.                                      final RotationOrder eulerRotSequence, final boolean isSpacecraftBodyRate,
  420.                                      final TimeStampedAngularCoordinates coordinates) {

  421.             // spin data
  422.             final TimeStampedAngularCoordinates c = isExternal2SpacecraftBody ? coordinates : coordinates.revert();
  423.             final SpinFinder sf = new SpinFinder(c);

  424.             // Orekit/CCSDS naming difference: for CCSDS this is nutation, for Orekit this is precession
  425.             final FieldRotation<UnivariateDerivative2> c2       = c.toUnivariateDerivative2Rotation();
  426.             final FieldVector3D<UnivariateDerivative2> spinAxis = c2.applyInverseTo(Vector3D.PLUS_K);
  427.             final PrecessionFinder                     pf       = new PrecessionFinder(spinAxis);

  428.             // intermediate inertial frame, with Z axis aligned with angular momentum
  429.             final Rotation intermediate2Inert = new Rotation(Vector3D.PLUS_K, pf.getAxis());

  430.             // recover Euler rotations starting from frame aligned with angular momentum
  431.             final FieldRotation<UnivariateDerivative2> intermediate2Body = c2.applyTo(intermediate2Inert);
  432.             final UnivariateDerivative2[] euler = intermediate2Body.
  433.                                                   getAngles(RotationOrder.ZXZ, RotationConvention.FRAME_TRANSFORM);

  434.             return new double[] {
  435.                 sf.getSpinAlpha(),
  436.                 sf.getSpinDelta(),
  437.                 sf.getSpinAngle(),
  438.                 euler[2].getFirstDerivative(),
  439.                 pf.getPrecessionAngle(),
  440.                 MathUtils.TWO_PI / pf.getAngularVelocity(),
  441.                 euler[2].getValue() - MathUtils.SEMI_PI
  442.             };

  443.         }

  444.         /** {@inheritDoc} */
  445.         @Override
  446.         public TimeStampedAngularCoordinates build(final boolean isFirst,
  447.                                                    final boolean isExternal2SpacecraftBody,
  448.                                                    final RotationOrder eulerRotSequence,
  449.                                                    final boolean isSpacecraftBodyRate,
  450.                                                    final AbsoluteDate date,
  451.                                                    final double... components) {

  452.             // Build the needed objects
  453.             final Rotation inert2Body0 = new Rotation(RotationOrder.ZXZ,
  454.                                                       RotationConvention.FRAME_TRANSFORM,
  455.                                                       MathUtils.SEMI_PI + components[0],
  456.                                                       MathUtils.SEMI_PI - components[1],
  457.                                                       components[2]);

  458.             // intermediate inertial frame, with Z axis aligned with angular momentum
  459.             final SinCos   scNutation         = FastMath.sinCos(components[4]);
  460.             final SinCos   scPhase            = FastMath.sinCos(components[6]);
  461.             final Vector3D momentumBody       = new Vector3D( scNutation.sin() * scPhase.cos(),
  462.                                                              -scNutation.sin() * scPhase.sin(),
  463.                                                               scNutation.cos());
  464.             final Vector3D momentumInert      = inert2Body0.applyInverseTo(momentumBody);
  465.             final Rotation inert2Intermediate = new Rotation(momentumInert, Vector3D.PLUS_K);

  466.             // base Euler angles from the intermediate frame to body
  467.             final Rotation intermediate2Body0 = inert2Body0.applyTo(inert2Intermediate.revert());
  468.             final double[] euler0             = intermediate2Body0.getAngles(RotationOrder.ZXZ,
  469.                                                                              RotationConvention.FRAME_TRANSFORM);

  470.             // add Euler angular rates to base Euler angles
  471.             final FieldRotation<UnivariateDerivative2> intermediate2Body =
  472.                             new FieldRotation<>(RotationOrder.ZXZ, RotationConvention.FRAME_TRANSFORM,
  473.                                                 new UnivariateDerivative2(euler0[0], MathUtils.TWO_PI / components[5], 0.0),
  474.                                                 new UnivariateDerivative2(euler0[1], 0.0,           0.0),
  475.                                                 new UnivariateDerivative2(euler0[2], components[3], 0.0));

  476.             // final rotation, including derivatives
  477.             final FieldRotation<UnivariateDerivative2> inert2Body = intermediate2Body.applyTo(inert2Intermediate);

  478.             final TimeStampedAngularCoordinates ac =
  479.                             new TimeStampedAngularCoordinates(date, inert2Body);
  480.             return isExternal2SpacecraftBody ? ac : ac.revert();

  481.         }

  482.     },

  483.     /** Spin and momentum.
  484.      * @since 12.0
  485.      */
  486.     SPIN_NUTATION_MOMENTUM(Collections.singleton(new VersionedName(2.0, "SPIN/NUTATION_MOM")),
  487.                            AngularDerivativesFilter.USE_RR,
  488.                            Unit.DEGREE, Unit.DEGREE, Unit.DEGREE, Units.DEG_PER_S,
  489.                            Unit.DEGREE, Unit.DEGREE, Units.DEG_PER_S) {

  490.         /** {@inheritDoc} */
  491.         @Override
  492.         public double[] generateData(final boolean isFirst, final boolean isExternal2SpacecraftBody,
  493.                                      final RotationOrder eulerRotSequence, final boolean isSpacecraftBodyRate,
  494.                                      final TimeStampedAngularCoordinates coordinates) {

  495.             // spin data
  496.             final TimeStampedAngularCoordinates c = isExternal2SpacecraftBody ? coordinates : coordinates.revert();
  497.             final SpinFinder sf = new SpinFinder(c);

  498.             // Orekit/CCSDS naming difference: for CCSDS this is nutation, for Orekit this is precession
  499.             final FieldRotation<UnivariateDerivative2> c2       = c.toUnivariateDerivative2Rotation();
  500.             final FieldVector3D<UnivariateDerivative2> spinAxis = c2.applyInverseTo(Vector3D.PLUS_K);
  501.             final PrecessionFinder                     pf       = new PrecessionFinder(spinAxis);

  502.             // intermediate inertial frame, with Z axis aligned with angular momentum
  503.             final Rotation intermediate2Inert = new Rotation(Vector3D.PLUS_K, pf.getAxis());

  504.             // recover spin angle velocity
  505.             final FieldRotation<UnivariateDerivative2> intermediate2Body = c2.applyTo(intermediate2Inert);
  506.             final double spinAngleVel = intermediate2Body.
  507.                                         getAngles(RotationOrder.ZXZ, RotationConvention.FRAME_TRANSFORM)[2].
  508.                                         getFirstDerivative();

  509.             return new double[] {
  510.                 sf.getSpinAlpha(),
  511.                 sf.getSpinDelta(),
  512.                 sf.getSpinAngle(),
  513.                 spinAngleVel,
  514.                 pf.getAxis().getAlpha(),
  515.                 pf.getAxis().getDelta(),
  516.                 pf.getAngularVelocity()
  517.             };

  518.         }

  519.         /** {@inheritDoc} */
  520.         @Override
  521.         public TimeStampedAngularCoordinates build(final boolean isFirst,
  522.                                                    final boolean isExternal2SpacecraftBody,
  523.                                                    final RotationOrder eulerRotSequence,
  524.                                                    final boolean isSpacecraftBodyRate,
  525.                                                    final AbsoluteDate date,
  526.                                                    final double... components) {

  527.             // Build the needed objects
  528.             final SinCos   scAlpha            = FastMath.sinCos(components[4]);
  529.             final SinCos   scDelta            = FastMath.sinCos(components[5]);
  530.             final Vector3D momentumInert      = new Vector3D(scAlpha.cos() * scDelta.cos(),
  531.                                                              scAlpha.sin() * scDelta.cos(),
  532.                                                              scDelta.sin());
  533.             final Rotation inert2Intermediate = new Rotation(momentumInert, Vector3D.PLUS_K);

  534.             // base Euler angles from the intermediate frame to body
  535.             final Rotation inert2Body0 = new Rotation(RotationOrder.ZXZ,
  536.                                                       RotationConvention.FRAME_TRANSFORM,
  537.                                                       MathUtils.SEMI_PI + components[0],
  538.                                                       MathUtils.SEMI_PI - components[1],
  539.                                                       components[2]);
  540.             final Rotation intermediate2Body0 = inert2Body0.applyTo(inert2Intermediate.revert());
  541.             final double[] euler0             = intermediate2Body0.getAngles(RotationOrder.ZXZ,
  542.                                                                              RotationConvention.FRAME_TRANSFORM);

  543.             // add Euler angular rates to base Euler angles
  544.             final FieldRotation<UnivariateDerivative2> intermediate2Body =
  545.                             new FieldRotation<>(RotationOrder.ZXZ, RotationConvention.FRAME_TRANSFORM,
  546.                                                 new UnivariateDerivative2(euler0[0], components[6], 0.0),
  547.                                                 new UnivariateDerivative2(euler0[1], 0.0,           0.0),
  548.                                                 new UnivariateDerivative2(euler0[2], components[3], 0.0));

  549.             // final rotation, including derivatives
  550.             final FieldRotation<UnivariateDerivative2> inert2Body = intermediate2Body.applyTo(inert2Intermediate);

  551.             // return
  552.             final TimeStampedAngularCoordinates ac =
  553.                             new TimeStampedAngularCoordinates(date, inert2Body);
  554.             return isExternal2SpacecraftBody ? ac : ac.revert();

  555.         }

  556.     };

  557.     /** Names map.
  558.      * @since 12.0
  559.      */
  560.     private static final Map<String, AttitudeType> MAP = new HashMap<>();
  561.     static {
  562.         for (final AttitudeType type : values()) {
  563.             for (final VersionedName vn : type.ccsdsNames) {
  564.                 MAP.put(vn.name, type);
  565.             }
  566.         }
  567.     }

  568.     /** CCSDS names of the attitude type. */
  569.     private final Iterable<VersionedName> ccsdsNames;

  570.     /** Derivatives filter. */
  571.     private final AngularDerivativesFilter filter;

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

  574.     /** Private constructor.
  575.      * @param ccsdsNames CCSDS names of the attitude type
  576.      * @param filter derivative filter
  577.      * @param units components units (used only for parsing)
  578.      */
  579.     AttitudeType(final Iterable<VersionedName> ccsdsNames, final AngularDerivativesFilter filter, final Unit... units) {
  580.         this.ccsdsNames = ccsdsNames;
  581.         this.filter     = filter;
  582.         this.units      = units.clone();
  583.     }

  584.     /** Get the type name for a given format version.
  585.      * @param formatVersion format version
  586.      * @return type name
  587.      * @since 12.0
  588.      */
  589.     public String getName(final double formatVersion) {
  590.         String name = null;
  591.         for (final VersionedName vn : ccsdsNames) {
  592.             if (name == null || formatVersion >= vn.since) {
  593.                 name = vn.name;
  594.             }
  595.         }
  596.         return name;
  597.     }

  598.     /** {@inheritDoc} */
  599.     @Override
  600.     public String toString() {
  601.         // use the most recent name by default
  602.         return getName(Double.POSITIVE_INFINITY);
  603.     }

  604.     /** Parse an attitude type.
  605.      * @param typeSpecification unnormalized type name
  606.      * @return parsed type
  607.      */
  608.     public static AttitudeType parseType(final String typeSpecification) {
  609.         final AttitudeType type = MAP.get(typeSpecification);
  610.         if (type == null) {
  611.             throw new OrekitException(OrekitMessages.CCSDS_UNKNOWN_ATTITUDE_TYPE, typeSpecification);
  612.         }
  613.         return type;
  614.     }

  615.     /**
  616.      * Get the attitude data fields corresponding to the attitude type.
  617.      * <p>
  618.      * This method returns the components in CCSDS units (i.e. degrees, degrees per seconds…).
  619.      * </p>
  620.      * @param isFirst if true the first quaternion component is the scalar component
  621.      * @param isExternal2SpacecraftBody true attitude is from external frame to spacecraft body frame
  622.      * @param eulerRotSequence sequance of Euler angles
  623.      * @param isSpacecraftBodyRate if true Euler rates are specified in spacecraft body frame
  624.      * @param attitude angular coordinates, using {@link Attitude Attitude} convention
  625.      * @param formatter used to format doubles and dates
  626.      * (i.e. from inertial frame to spacecraft frame)
  627.      * @return the attitude data in CCSDS units
  628.      */
  629.     public String[] createDataFields(final boolean isFirst, final boolean isExternal2SpacecraftBody,
  630.                                      final RotationOrder eulerRotSequence, final boolean isSpacecraftBodyRate,
  631.                                      final TimeStampedAngularCoordinates attitude, final Formatter formatter) {

  632.         // generate the double data
  633.         final double[] data = generateData(isFirst, isExternal2SpacecraftBody,
  634.                                            eulerRotSequence, isSpacecraftBodyRate, attitude);

  635.         // format as string array with CCSDS units
  636.         final String[] fields = new String[data.length];
  637.         for (int i = 0; i < data.length; ++i) {
  638.             fields[i] = formatter.toString(units[i].fromSI(data[i]));
  639.         }

  640.         return fields;

  641.     }

  642.     /**
  643.      * Get the attitude data fields corresponding to the attitude type.
  644.      * <p>
  645.      * This method returns the components in CCSDS units (i.e. degrees, degrees per seconds…).
  646.      * </p>
  647.      * @param isFirst if true the first quaternion component is the scalar component
  648.      * @param isExternal2SpacecraftBody true attitude is from external frame to spacecraft body frame
  649.      * @param eulerRotSequence sequance of Euler angles
  650.      * @param isSpacecraftBodyRate if true Euler rates are specified in spacecraft body frame
  651.      * @param attitude angular coordinates, using {@link Attitude Attitude} convention
  652.      * (i.e. from inertial frame to spacecraft frame)
  653.      * @return the attitude data in CCSDS units
  654.      */
  655.     public String[] createDataFields(final boolean isFirst, final boolean isExternal2SpacecraftBody,
  656.                                      final RotationOrder eulerRotSequence, final boolean isSpacecraftBodyRate,
  657.                                      final TimeStampedAngularCoordinates attitude) {

  658.         return this.createDataFields(isFirst, isExternal2SpacecraftBody, eulerRotSequence, isSpacecraftBodyRate, attitude, new AccurateFormatter());
  659.     }

  660.     /**
  661.      * Generate the attitude data corresponding to the attitude type.
  662.      * <p>
  663.      * This method returns the components in SI units.
  664.      * </p>
  665.      * @param isFirst if true the first quaternion component is the scalar component
  666.      * @param isExternal2SpacecraftBody true attitude is from external frame to spacecraft body frame
  667.      * @param eulerRotSequence sequance of Euler angles
  668.      * @param isSpacecraftBodyRate if true Euler rates are specified in spacecraft body frame
  669.      * @param attitude angular coordinates, using {@link Attitude Attitude} convention
  670.      * (i.e. from inertial frame to spacecraft frame)
  671.      * @return the attitude data in CCSDS units
  672.      * @since 12.0
  673.      */
  674.     public abstract double[] generateData(boolean isFirst, boolean isExternal2SpacecraftBody,
  675.                                           RotationOrder eulerRotSequence, boolean isSpacecraftBodyRate,
  676.                                           TimeStampedAngularCoordinates attitude);

  677.     /**
  678.      * Get the angular coordinates corresponding to the attitude data.
  679.      * <p>
  680.      * This method assumes the text fields are in CCSDS units and will convert to SI units.
  681.      * </p>
  682.      * @param isFirst if true the first quaternion component is the scalar component
  683.      * @param isExternal2SpacecraftBody true attitude is from external frame to spacecraft body frame
  684.      * @param eulerRotSequence sequance of Euler angles
  685.      * @param isSpacecraftBodyRate if true Euler rates are specified in spacecraft body frame
  686.      * @param context context binding
  687.      * @param fields raw data fields
  688.      * @return the angular coordinates, using {@link Attitude Attitude} convention
  689.      * (i.e. from inertial frame to spacecraft frame)
  690.      */
  691.     public TimeStampedAngularCoordinates parse(final boolean isFirst, final boolean isExternal2SpacecraftBody,
  692.                                                final RotationOrder eulerRotSequence, final boolean isSpacecraftBodyRate,
  693.                                                final ContextBinding context, final String[] fields) {

  694.         // parse the text fields
  695.         final AbsoluteDate date = context.getTimeSystem().getConverter(context).parse(fields[0]);
  696.         final double[] components = new double[fields.length - 1];
  697.         for (int i = 0; i < components.length; ++i) {
  698.             components[i] = units[i].toSI(Double.parseDouble(fields[i + 1]));
  699.         }

  700.         // build the coordinates
  701.         return build(isFirst, isExternal2SpacecraftBody, eulerRotSequence, isSpacecraftBodyRate,
  702.                      date, components);

  703.     }

  704.     /** Get the angular coordinates corresponding to the attitude data.
  705.      * @param isFirst if true the first quaternion component is the scalar component
  706.      * @param isExternal2SpacecraftBody true attitude is from external frame to spacecraft body frame
  707.      * @param eulerRotSequence sequance of Euler angles
  708.      * @param isSpacecraftBodyRate if true Euler rates are specified in spacecraft body frame
  709.      * @param date entry date
  710.      * @param components entry components with SI units, semantic depends on attitude type
  711.      * @return the angular coordinates, using {@link Attitude Attitude} convention
  712.      * (i.e. from inertial frame to spacecraft frame)
  713.      */
  714.     public abstract TimeStampedAngularCoordinates build(boolean isFirst, boolean isExternal2SpacecraftBody,
  715.                                                         RotationOrder eulerRotSequence, boolean isSpacecraftBodyRate,
  716.                                                         AbsoluteDate date, double... components);

  717.     /**
  718.      * Get the angular derivative filter corresponding to the attitude data.
  719.      * @return the angular derivative filter corresponding to the attitude data
  720.      */
  721.     public AngularDerivativesFilter getAngularDerivativesFilter() {
  722.         return filter;
  723.     }

  724.     /** Convert a rotation rate for Orekit convention to metadata convention.
  725.      * @param isSpacecraftBodyRate if true Euler rates are specified in spacecraft body frame
  726.      * @param rate rotation rate from Orekit attitude
  727.      * @param rotation corresponding rotation
  728.      * @return rotation rate in metadata convention
  729.      */
  730.     private Vector3D metadataRate(final boolean isSpacecraftBodyRate, final Vector3D rate, final Rotation rotation) {
  731.         return isSpacecraftBodyRate ? rate : rotation.applyInverseTo(rate);
  732.     }

  733.     /** Convert a rotation rate for metadata convention to Orekit convention.
  734.      * @param isSpacecraftBodyRate if true Euler rates are specified in spacecraft body frame
  735.      * @param rate rotation rate read from the data line
  736.      * @param rotation corresponding rotation
  737.      * @return rotation rate in Orekit convention (i.e. in spacecraft body local frame)
  738.      */
  739.     private Vector3D orekitRate(final boolean isSpacecraftBodyRate, final Vector3D rate, final Rotation rotation) {
  740.         return isSpacecraftBodyRate ? rate : rotation.applyTo(rate);
  741.     }

  742.     /** Container for a name associated to a format version.
  743.      * @since 12.0
  744.      */
  745.     private static class VersionedName {

  746.         /** Version at which this name was defined. */
  747.         private final double since;

  748.         /** Name. */
  749.         private final String name;

  750.         /** Simple constructor.
  751.          * @param since version at which this name was defined
  752.          * @param name name
  753.          */
  754.         VersionedName(final double since, final String name) {
  755.             this.since = since;
  756.             this.name  = name;
  757.         }

  758.     }

  759. }