AttitudeType.java

  1. /* Copyright 2002-2023 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.TimeStampedAngularCoordinates;
  42. import org.orekit.utils.units.Unit;

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

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

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

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

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

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

  69.             return data;

  70.         }

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

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

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

  87.         }

  88.     },

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

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

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

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

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

  117.             return data;

  118.         }

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

  142.             return new TimeStampedAngularCoordinates(date, rotation);

  143.         }

  144.     },

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

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

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

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

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

  172.             return data;

  173.         }

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

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

  195.         }

  196.     },

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

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

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

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

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

  221.             return data;

  222.         }

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

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

  242.         }

  243.     },

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

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

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

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

  259.         }

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

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

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

  277.     },

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

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

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

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

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

  303.         }

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

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

  321.             return new TimeStampedAngularCoordinates(date, rotation);

  322.         }

  323.     },

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

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

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

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

  348.         }

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

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

  370.         }

  371.     },

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

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

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

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

  388.         }

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

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

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

  408.         }

  409.     },

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

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

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

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

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

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

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

  442.         }

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

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

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

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

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

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

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

  480.         }

  481.     },

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

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

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

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

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

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

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

  517.         }

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

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

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

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

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

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

  554.         }

  555.     };

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

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

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

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

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

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

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

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

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

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

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

  638.         return fields;

  639.     }

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

  657.     /**
  658.      * Get the angular coordinates corresponding to the attitude data.
  659.      * <p>
  660.      * This method assumes the text fields are in CCSDS units and will convert to SI units.
  661.      * </p>
  662.      * @param isFirst if true the first quaternion component is the scalar component
  663.      * @param isExternal2SpacecraftBody true attitude is from external frame to spacecraft body frame
  664.      * @param eulerRotSequence sequance of Euler angles
  665.      * @param isSpacecraftBodyRate if true Euler rates are specified in spacecraft body frame
  666.      * @param context context binding
  667.      * @param fields raw data fields
  668.      * @return the angular coordinates, using {@link Attitude Attitude} convention
  669.      * (i.e. from inertial frame to spacecraft frame)
  670.      */
  671.     public TimeStampedAngularCoordinates parse(final boolean isFirst, final boolean isExternal2SpacecraftBody,
  672.                                                final RotationOrder eulerRotSequence, final boolean isSpacecraftBodyRate,
  673.                                                final ContextBinding context, final String[] fields) {

  674.         // parse the text fields
  675.         final AbsoluteDate date = context.getTimeSystem().getConverter(context).parse(fields[0]);
  676.         final double[] components = new double[fields.length - 1];
  677.         for (int i = 0; i < components.length; ++i) {
  678.             components[i] = units[i].toSI(Double.parseDouble(fields[i + 1]));
  679.         }

  680.         // build the coordinates
  681.         return build(isFirst, isExternal2SpacecraftBody, eulerRotSequence, isSpacecraftBodyRate,
  682.                      date, components);

  683.     }

  684.     /** Get the angular coordinates corresponding to the attitude data.
  685.      * @param isFirst if true the first quaternion component is the scalar component
  686.      * @param isExternal2SpacecraftBody true attitude is from external frame to spacecraft body frame
  687.      * @param eulerRotSequence sequance of Euler angles
  688.      * @param isSpacecraftBodyRate if true Euler rates are specified in spacecraft body frame
  689.      * @param date entry date
  690.      * @param components entry components with SI units, semantic depends on attitude type
  691.      * @return the angular coordinates, using {@link Attitude Attitude} convention
  692.      * (i.e. from inertial frame to spacecraft frame)
  693.      */
  694.     public abstract TimeStampedAngularCoordinates build(boolean isFirst, boolean isExternal2SpacecraftBody,
  695.                                                         RotationOrder eulerRotSequence, boolean isSpacecraftBodyRate,
  696.                                                         AbsoluteDate date, double... components);

  697.     /**
  698.      * Get the angular derivative filter corresponding to the attitude data.
  699.      * @return the angular derivative filter corresponding to the attitude data
  700.      */
  701.     public AngularDerivativesFilter getAngularDerivativesFilter() {
  702.         return filter;
  703.     }

  704.     /** Convert a rotation rate for Orekit convention to metadata convention.
  705.      * @param isSpacecraftBodyRate if true Euler rates are specified in spacecraft body frame
  706.      * @param rate rotation rate from Orekit attitude
  707.      * @param rotation corresponding rotation
  708.      * @return rotation rate in metadata convention
  709.      */
  710.     private Vector3D metadataRate(final boolean isSpacecraftBodyRate, final Vector3D rate, final Rotation rotation) {
  711.         return isSpacecraftBodyRate ? rate : rotation.applyInverseTo(rate);
  712.     }

  713.     /** Convert a rotation rate for metadata convention to Orekit convention.
  714.      * @param isSpacecraftBodyRate if true Euler rates are specified in spacecraft body frame
  715.      * @param rate rotation rate read from the data line
  716.      * @param rotation corresponding rotation
  717.      * @return rotation rate in Orekit convention (i.e. in spacecraft body local frame)
  718.      */
  719.     private Vector3D orekitRate(final boolean isSpacecraftBodyRate, final Vector3D rate, final Rotation rotation) {
  720.         return isSpacecraftBodyRate ? rate : rotation.applyTo(rate);
  721.     }

  722.     /** Container for a name associated to a format version.
  723.      * @since 12.0
  724.      */
  725.     private static class VersionedName {

  726.         /** Version at which this name was defined. */
  727.         private final double since;

  728.         /** Name. */
  729.         private final String name;

  730.         /** Simple constructor.
  731.          * @param since version at which this name was defined
  732.          * @param name name
  733.          */
  734.         VersionedName(final double since, final String name) {
  735.             this.since = since;
  736.             this.name  = name;
  737.         }

  738.     }

  739. }