LOFType.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.frames;

  18. import org.hipparchus.CalculusFieldElement;
  19. import org.hipparchus.Field;
  20. import org.hipparchus.geometry.euclidean.threed.FieldRotation;
  21. import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
  22. import org.hipparchus.geometry.euclidean.threed.Rotation;
  23. import org.hipparchus.geometry.euclidean.threed.RotationConvention;
  24. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  25. import org.orekit.errors.OrekitException;
  26. import org.orekit.errors.OrekitMessages;
  27. import org.orekit.files.ccsds.definitions.OrbitRelativeFrame;
  28. import org.orekit.time.AbsoluteDate;
  29. import org.orekit.time.FieldAbsoluteDate;
  30. import org.orekit.utils.FieldPVCoordinates;
  31. import org.orekit.utils.PVCoordinates;

  32. /**
  33.  * Enumerate for different types of Local Orbital Frames.
  34.  *
  35.  * @author Luc Maisonobe
  36.  * @author Maxime Journot
  37.  * @author Vincent Cucchietti
  38.  */
  39. public enum LOFType implements LOF {

  40.     /** Constant for TNW frame
  41.      * (X axis aligned with velocity, Z axis aligned with orbital momentum).
  42.      * <p>
  43.      * The axes of this frame are parallel to the axes of the {@link #VNC}
  44.      * and {@link #NTW} frames:
  45.      * </p>
  46.      * <ul>
  47.      *   <li>X<sub>TNW</sub> =  X<sub>VNC</sub> =  Y<sub>NTW</sub></li>
  48.      *   <li>Y<sub>TNW</sub> = -Z<sub>VNC</sub> = -X<sub>NTW</sub></li>
  49.      *   <li>Z<sub>TNW</sub> =  Y<sub>VNC</sub> =  Z<sub>NTW</sub></li>
  50.      * </ul>
  51.      *
  52.      * @see #VNC
  53.      * @see #NTW
  54.      */
  55.     TNW {
  56.         /** {@inheritDoc} */
  57.         @Override
  58.         public Rotation rotationFromInertial(final PVCoordinates pv) {
  59.             return new Rotation(pv.getVelocity(), pv.getMomentum(),
  60.                                 Vector3D.PLUS_I, Vector3D.PLUS_K);
  61.         }

  62.         /** {@inheritDoc} */
  63.         @Override
  64.         public <T extends CalculusFieldElement<T>> FieldRotation<T> rotationFromInertial(final Field<T> field,
  65.                                                                                          final FieldPVCoordinates<T> pv) {
  66.             return new FieldRotation<>(pv.getVelocity(), pv.getMomentum(),
  67.                                        new FieldVector3D<>(field, Vector3D.PLUS_I),
  68.                                        new FieldVector3D<>(field, Vector3D.PLUS_K));
  69.         }

  70.         /** {@inheritDoc} */
  71.         @Override
  72.         public OrbitRelativeFrame toOrbitRelativeFrame() {
  73.             return OrbitRelativeFrame.TNW;
  74.         }

  75.     },

  76.     /**
  77.      * Constant for TNW frame considered inertial (X axis aligned with velocity, Z axis aligned with orbital momentum).
  78.      * <p>
  79.      * The axes of this frame are parallel to the axes of the {@link #VNC} and {@link #NTW} frames:
  80.      * </p>
  81.      * <ul>
  82.      *   <li>X<sub>TNW</sub> =  X<sub>VNC</sub> =  Y<sub>NTW</sub></li>
  83.      *   <li>Y<sub>TNW</sub> = -Z<sub>VNC</sub> = -X<sub>NTW</sub></li>
  84.      *   <li>Z<sub>TNW</sub> =  Y<sub>VNC</sub> =  Z<sub>NTW</sub></li>
  85.      * </ul>
  86.      *
  87.      * @see #VNC
  88.      * @see #NTW
  89.      */
  90.     TNW_INERTIAL {
  91.         /** {@inheritDoc} */
  92.         @Override
  93.         public Rotation rotationFromInertial(final PVCoordinates pv) {
  94.             return TNW.rotationFromInertial(pv);
  95.         }

  96.         /** {@inheritDoc} */
  97.         @Override
  98.         public <T extends CalculusFieldElement<T>> FieldRotation<T> rotationFromInertial(final Field<T> field,
  99.                                                                                          final FieldPVCoordinates<T> pv) {
  100.             return TNW.rotationFromInertial(field, pv);
  101.         }

  102.         /** {@inheritDoc} */
  103.         @Override
  104.         public boolean isQuasiInertial() {
  105.             return true;
  106.         }

  107.         /** {@inheritDoc} */
  108.         @Override
  109.         public OrbitRelativeFrame toOrbitRelativeFrame() {
  110.             return OrbitRelativeFrame.TNW_INERTIAL;
  111.         }

  112.     },

  113.     /** Constant for QSW frame
  114.      * (X axis aligned with position, Z axis aligned with orbital momentum).
  115.      * <p>
  116.      * This frame is also known as the {@link #LVLH} frame, both constants are equivalent.
  117.      * </p>
  118.      * <p>
  119.      * The axes of these frames are parallel to the axes of the {@link #VVLH} frame:
  120.      * </p>
  121.      * <ul>
  122.      *   <li>X<sub>QSW/LVLH</sub> = -Z<sub>VVLH</sub></li>
  123.      *   <li>Y<sub>QSW/LVLH</sub> =  X<sub>VVLH</sub></li>
  124.      *   <li>Z<sub>QSW/LVLH</sub> = -Y<sub>VVLH</sub></li>
  125.      * </ul>
  126.      *
  127.      * @see #LVLH
  128.      * @see #VVLH
  129.      */
  130.     QSW {
  131.         /** {@inheritDoc} */
  132.         @Override
  133.         public Rotation rotationFromInertial(final PVCoordinates pv) {
  134.             return new Rotation(pv.getPosition(), pv.getMomentum(),
  135.                                 Vector3D.PLUS_I, Vector3D.PLUS_K);
  136.         }

  137.         /** {@inheritDoc} */
  138.         @Override
  139.         public <T extends CalculusFieldElement<T>> FieldRotation<T> rotationFromInertial(final Field<T> field,
  140.                                                                                          final FieldPVCoordinates<T> pv) {
  141.             return new FieldRotation<>(pv.getPosition(), pv.getMomentum(),
  142.                                        new FieldVector3D<>(field, Vector3D.PLUS_I),
  143.                                        new FieldVector3D<>(field, Vector3D.PLUS_K));
  144.         }

  145.         /** {@inheritDoc} */
  146.         @Override
  147.         public OrbitRelativeFrame toOrbitRelativeFrame() {
  148.             return OrbitRelativeFrame.QSW;
  149.         }

  150.     },

  151.     /**
  152.      * Constant for QSW frame considered inertial (X axis aligned with position, Z axis aligned with orbital momentum).
  153.      * <p>
  154.      * This frame is also known as the {@link #LVLH} frame, both constants are equivalent.
  155.      * </p>
  156.      * <p>
  157.      * The axes of these frames are parallel to the axes of the {@link #VVLH} frame:
  158.      * </p>
  159.      * <ul>
  160.      *   <li>X<sub>QSW/LVLH</sub> = -Z<sub>VVLH</sub></li>
  161.      *   <li>Y<sub>QSW/LVLH</sub> =  X<sub>VVLH</sub></li>
  162.      *   <li>Z<sub>QSW/LVLH</sub> = -Y<sub>VVLH</sub></li>
  163.      * </ul>
  164.      *
  165.      * @see #LVLH
  166.      * @see #VVLH
  167.      */
  168.     QSW_INERTIAL {
  169.         /** {@inheritDoc} */
  170.         @Override
  171.         public Rotation rotationFromInertial(final PVCoordinates pv) {
  172.             return QSW.rotationFromInertial(pv);
  173.         }

  174.         /** {@inheritDoc} */
  175.         @Override
  176.         public <T extends CalculusFieldElement<T>> FieldRotation<T> rotationFromInertial(final Field<T> field,
  177.                                                                                          final FieldPVCoordinates<T> pv) {
  178.             return QSW.rotationFromInertial(field, pv);
  179.         }

  180.         /** {@inheritDoc} */
  181.         @Override
  182.         public boolean isQuasiInertial() {
  183.             return true;
  184.         }

  185.         /** {@inheritDoc} */
  186.         @Override
  187.         public OrbitRelativeFrame toOrbitRelativeFrame() {
  188.             return OrbitRelativeFrame.RSW_INERTIAL;
  189.         }

  190.     },

  191.     /** Constant for Local Vertical, Local Horizontal frame
  192.      * (X axis aligned with position, Z axis aligned with orbital momentum).
  193.      * <p>
  194.      * BEWARE! Depending on the background (software used, textbook, community),
  195.      * different incompatible definitions for LVLH are used. This one is consistent
  196.      * with Vallado's book and with AGI's STK. However CCSDS standard, Wertz, and
  197.      * a.i. solutions' FreeFlyer use another definition (see {@link #LVLH_CCSDS}).
  198.      * </p>
  199.      * <p>
  200.      * This frame is also known as the {@link #QSW} frame, both constants are equivalent.
  201.      * </p>
  202.      * <p>
  203.      * The axes of these frames are parallel to the axes of the {@link #LVLH_CCSDS} frame:
  204.      * </p>
  205.      * <ul>
  206.      *   <li>X<sub>LVLH/QSW</sub> = -Z<sub>LVLH_CCSDS</sub></li>
  207.      *   <li>Y<sub>LVLH/QSW</sub> =  X<sub>LVLH_CCSDS</sub></li>
  208.      *   <li>Z<sub>LVLH/QSW</sub> = -Y<sub>LVLH_CCSDS</sub></li>
  209.      * </ul>
  210.      *
  211.      * @see #QSW
  212.      * @see #VVLH
  213.      */
  214.     LVLH {
  215.         /** {@inheritDoc} */
  216.         @Override
  217.         public Rotation rotationFromInertial(final PVCoordinates pv) {
  218.             return new Rotation(pv.getPosition(), pv.getMomentum(),
  219.                                 Vector3D.PLUS_I, Vector3D.PLUS_K);
  220.         }

  221.         /** {@inheritDoc} */
  222.         @Override
  223.         public <T extends CalculusFieldElement<T>> FieldRotation<T> rotationFromInertial(final Field<T> field,
  224.                                                                                          final FieldPVCoordinates<T> pv) {
  225.             return new FieldRotation<>(pv.getPosition(), pv.getMomentum(),
  226.                                        new FieldVector3D<>(field, Vector3D.PLUS_I),
  227.                                        new FieldVector3D<>(field, Vector3D.PLUS_K));
  228.         }

  229.         /** {@inheritDoc} */
  230.         @Override
  231.         public OrbitRelativeFrame toOrbitRelativeFrame() {
  232.             throw new OrekitException(OrekitMessages.CCSDS_DIFFERENT_LVLH_DEFINITION);
  233.         }

  234.     },

  235.     /**
  236.      * Constant for Local Vertical, Local Horizontal frame considered inertial (X axis aligned with position, Z axis
  237.      * aligned with orbital momentum).
  238.      * <p>
  239.      * BEWARE! Depending on the background (software used, textbook, community), different incompatible definitions for
  240.      * LVLH are used. This one is consistent with Vallado's book and with AGI's STK. However CCSDS standard, Wertz, and
  241.      * a.i. solutions' FreeFlyer use another definition (see {@link #LVLH_CCSDS}).
  242.      * </p>
  243.      * <p>
  244.      * This frame is also known as the {@link #QSW} frame, both constants are equivalent.
  245.      * </p>
  246.      * <p>
  247.      * The axes of these frames are parallel to the axes of the {@link #LVLH_CCSDS} frame:
  248.      * </p>
  249.      * <ul>
  250.      *   <li>X<sub>LVLH/QSW</sub> = -Z<sub>LVLH_CCSDS</sub></li>
  251.      *   <li>Y<sub>LVLH/QSW</sub> =  X<sub>LVLH_CCSDS</sub></li>
  252.      *   <li>Z<sub>LVLH/QSW</sub> = -Y<sub>LVLH_CCSDS</sub></li>
  253.      * </ul>
  254.      *
  255.      * @see #QSW
  256.      * @see #VVLH
  257.      */
  258.     LVLH_INERTIAL {
  259.         /** {@inheritDoc} */
  260.         @Override
  261.         public Rotation rotationFromInertial(final PVCoordinates pv) {
  262.             return LVLH.rotationFromInertial(pv);
  263.         }

  264.         /** {@inheritDoc} */
  265.         @Override
  266.         public <T extends CalculusFieldElement<T>> FieldRotation<T> rotationFromInertial(final Field<T> field,
  267.                                                                                          final FieldPVCoordinates<T> pv) {
  268.             return LVLH.rotationFromInertial(field, pv);
  269.         }

  270.         /** {@inheritDoc} */
  271.         @Override
  272.         public boolean isQuasiInertial() {
  273.             return true;
  274.         }

  275.         /** {@inheritDoc} */
  276.         @Override
  277.         public OrbitRelativeFrame toOrbitRelativeFrame() {
  278.             throw new OrekitException(OrekitMessages.CCSDS_DIFFERENT_LVLH_DEFINITION);
  279.         }

  280.     },

  281.     /** Constant for Local Vertical, Local Horizontal frame as defined by CCSDS
  282.      * (Z axis aligned with opposite of position, Y axis aligned with opposite of orbital momentum).
  283.      * <p>
  284.      * BEWARE! Depending on the background (software used, textbook, community),
  285.      * different incompatible definitions for LVLH are used. This one is consistent
  286.      * with CCSDS standard, Wertz, and a.i. solutions' FreeFlyer. However Vallado's
  287.      * book and with AGI's STK use another definition (see {@link #LVLH}).
  288.      * </p>
  289.      * <p>
  290.      * The axes of this frame are parallel to the axes of both the {@link #QSW} and {@link #LVLH} frames:
  291.      * </p>
  292.      * <ul>
  293.      *   <li>X<sub>LVLH_CCSDS/VVLH</sub> =  Y<sub>QSW/LVLH</sub></li>
  294.      *   <li>Y<sub>LVLH_CCSDS/VVLH</sub> = -Z<sub>QSW/LVLH</sub></li>
  295.      *   <li>Z<sub>LVLH_CCSDS/VVLH</sub> = -X<sub>QSW/LVLH</sub></li>
  296.      * </ul>
  297.      *
  298.      * @see #QSW
  299.      * @see #LVLH
  300.      * @since 11.0
  301.      */
  302.     LVLH_CCSDS {
  303.         /** {@inheritDoc} */
  304.         @Override
  305.         public Rotation rotationFromInertial(final PVCoordinates pv) {
  306.             return new Rotation(pv.getPosition(), pv.getMomentum(),
  307.                                 Vector3D.MINUS_K, Vector3D.MINUS_J);
  308.         }

  309.         /** {@inheritDoc} */
  310.         @Override
  311.         public <T extends CalculusFieldElement<T>> FieldRotation<T> rotationFromInertial(final Field<T> field,
  312.                                                                                          final FieldPVCoordinates<T> pv) {
  313.             return new FieldRotation<>(pv.getPosition(), pv.getMomentum(),
  314.                                        new FieldVector3D<>(field, Vector3D.MINUS_K),
  315.                                        new FieldVector3D<>(field, Vector3D.MINUS_J));
  316.         }

  317.         /** {@inheritDoc} */
  318.         @Override
  319.         public OrbitRelativeFrame toOrbitRelativeFrame() {
  320.             return OrbitRelativeFrame.LVLH;
  321.         }

  322.     },

  323.     /**
  324.      * Constant for Local Vertical, Local Horizontal frame as defined by CCSDS considered inertial (Z axis aligned with
  325.      * opposite of position, Y axis aligned with opposite of orbital momentum).
  326.      * <p>
  327.      * BEWARE! Depending on the background (software used, textbook, community), different incompatible definitions for
  328.      * LVLH are used. This one is consistent with CCSDS standard, Wertz, and a.i. solutions' FreeFlyer. However
  329.      * Vallado's book and with AGI's STK use another definition (see {@link #LVLH}).
  330.      * </p>
  331.      * <p>
  332.      * The axes of this frame are parallel to the axes of both the {@link #QSW} and {@link #LVLH} frames:
  333.      * </p>
  334.      * <ul>
  335.      *   <li>X<sub>LVLH_CCSDS/VVLH</sub> =  Y<sub>QSW/LVLH</sub></li>
  336.      *   <li>Y<sub>LVLH_CCSDS/VVLH</sub> = -Z<sub>QSW/LVLH</sub></li>
  337.      *   <li>Z<sub>LVLH_CCSDS/VVLH</sub> = -X<sub>QSW/LVLH</sub></li>
  338.      * </ul>
  339.      *
  340.      * @see #QSW
  341.      * @see #LVLH
  342.      * @since 11.0
  343.      */
  344.     LVLH_CCSDS_INERTIAL {
  345.         /** {@inheritDoc} */
  346.         @Override
  347.         public Rotation rotationFromInertial(final PVCoordinates pv) {
  348.             return LVLH_CCSDS.rotationFromInertial(pv);
  349.         }

  350.         /** {@inheritDoc} */
  351.         @Override
  352.         public <T extends CalculusFieldElement<T>> FieldRotation<T> rotationFromInertial(final Field<T> field,
  353.                                                                                          final FieldPVCoordinates<T> pv) {
  354.             return LVLH_CCSDS.rotationFromInertial(field, pv);
  355.         }

  356.         /** {@inheritDoc} */
  357.         @Override
  358.         public boolean isQuasiInertial() {
  359.             return true;
  360.         }

  361.         /** {@inheritDoc} */
  362.         @Override
  363.         public OrbitRelativeFrame toOrbitRelativeFrame() {
  364.             return OrbitRelativeFrame.LVLH_INERTIAL;
  365.         }

  366.     },

  367.     /** Constant for Vehicle Velocity, Local Horizontal frame
  368.      * (Z axis aligned with opposite of position, Y axis aligned with opposite of orbital momentum).
  369.      * <p>
  370.      * This is another name for {@link #LVLH_CCSDS}, kept here for compatibility with STK.
  371.      * </p>
  372.      * <p>
  373.      * Beware that the name is misleading: in the general case (i.e. not perfectly circular),
  374.      * none of the axes is perfectly aligned with velocity! The preferred name for this
  375.      * should be {@link #LVLH_CCSDS}.
  376.      * </p>
  377.      * <p>
  378.      * The axes of this frame are parallel to the axes of both the {@link #QSW} and {@link #LVLH} frames:
  379.      * </p>
  380.      * <ul>
  381.      *   <li>X<sub>LVLH_CCSDS/VVLH</sub> =  Y<sub>QSW/LVLH</sub></li>
  382.      *   <li>Y<sub>LVLH_CCSDS/VVLH</sub> = -Z<sub>QSW/LVLH</sub></li>
  383.      *   <li>Z<sub>LVLH_CCSDS/VVLH</sub> = -X<sub>QSW/LVLH</sub></li>
  384.      * </ul>
  385.      * @see #LVLH_CCSDS
  386.      */
  387.     VVLH {
  388.         /** {@inheritDoc} */
  389.         @Override
  390.         public Rotation rotationFromInertial(final PVCoordinates pv) {
  391.             return LVLH_CCSDS.rotationFromInertial(pv);
  392.         }

  393.         /** {@inheritDoc} */
  394.         @Override
  395.         public <T extends CalculusFieldElement<T>> FieldRotation<T> rotationFromInertial(final Field<T> field,
  396.                                                                                          final FieldPVCoordinates<T> pv) {
  397.             return LVLH_CCSDS.rotationFromInertial(field, pv);
  398.         }

  399.         /** {@inheritDoc} */
  400.         @Override
  401.         public OrbitRelativeFrame toOrbitRelativeFrame() {
  402.             return OrbitRelativeFrame.LVLH;
  403.         }

  404.     },

  405.     /**
  406.      * Constant for Vehicle Velocity, Local Horizontal frame considered inertial (Z axis aligned with opposite of
  407.      * position, Y axis aligned with opposite of orbital momentum).
  408.      * <p>
  409.      * This is another name for {@link #LVLH_CCSDS}, kept here for compatibility with STK.
  410.      * </p>
  411.      * <p>
  412.      * Beware that the name is misleading: in the general case (i.e. not perfectly circular), none of the axes is
  413.      * perfectly aligned with velocity! The preferred name for this should be {@link #LVLH_CCSDS}.
  414.      * </p>
  415.      * <p>
  416.      * The axes of this frame are parallel to the axes of both the {@link #QSW} and {@link #LVLH} frames:
  417.      * </p>
  418.      * <ul>
  419.      *   <li>X<sub>LVLH_CCSDS/VVLH</sub> =  Y<sub>QSW/LVLH</sub></li>
  420.      *   <li>Y<sub>LVLH_CCSDS/VVLH</sub> = -Z<sub>QSW/LVLH</sub></li>
  421.      *   <li>Z<sub>LVLH_CCSDS/VVLH</sub> = -X<sub>QSW/LVLH</sub></li>
  422.      * </ul>
  423.      *
  424.      * @see #LVLH_CCSDS
  425.      */
  426.     VVLH_INERTIAL {
  427.         /** {@inheritDoc} */
  428.         @Override
  429.         public Rotation rotationFromInertial(final PVCoordinates pv) {
  430.             return VVLH.rotationFromInertial(pv);
  431.         }

  432.         /** {@inheritDoc} */
  433.         @Override
  434.         public <T extends CalculusFieldElement<T>> FieldRotation<T> rotationFromInertial(final Field<T> field,
  435.                                                                                          final FieldPVCoordinates<T> pv) {
  436.             return VVLH.rotationFromInertial(field, pv);
  437.         }

  438.         /** {@inheritDoc} */
  439.         @Override
  440.         public boolean isQuasiInertial() {
  441.             return true;
  442.         }

  443.         /** {@inheritDoc} */
  444.         @Override
  445.         public OrbitRelativeFrame toOrbitRelativeFrame() {
  446.             return OrbitRelativeFrame.LVLH_INERTIAL;
  447.         }

  448.     },

  449.     /** Constant for Velocity - Normal - Co-normal frame
  450.      * (X axis aligned with velocity, Y axis aligned with orbital momentum).
  451.      * <p>
  452.      * The axes of this frame are parallel to the axes of the {@link #TNW}
  453.      * and {@link #NTW} frames:
  454.      * </p>
  455.      * <ul>
  456.      *   <li>X<sub>VNC</sub> =  X<sub>TNW</sub> = Y<sub>NTW</sub></li>
  457.      *   <li>Y<sub>VNC</sub> =  Z<sub>TNW</sub> = Z<sub>NTW</sub></li>
  458.      *   <li>Z<sub>VNC</sub> = -Y<sub>TNW</sub> = X<sub>NTW</sub></li>
  459.      * </ul>
  460.      *
  461.      * @see #TNW
  462.      * @see #NTW
  463.      */
  464.     VNC {
  465.         /** {@inheritDoc} */
  466.         @Override
  467.         public Rotation rotationFromInertial(final PVCoordinates pv) {
  468.             return new Rotation(pv.getVelocity(), pv.getMomentum(),
  469.                                 Vector3D.PLUS_I, Vector3D.PLUS_J);
  470.         }

  471.         /** {@inheritDoc} */
  472.         @Override
  473.         public <T extends CalculusFieldElement<T>> FieldRotation<T> rotationFromInertial(final Field<T> field,
  474.                                                                                          final FieldPVCoordinates<T> pv) {
  475.             return new FieldRotation<>(pv.getVelocity(), pv.getMomentum(),
  476.                                        new FieldVector3D<>(field, Vector3D.PLUS_I),
  477.                                        new FieldVector3D<>(field, Vector3D.PLUS_J));
  478.         }

  479.         /** {@inheritDoc} */
  480.         @Override
  481.         public OrbitRelativeFrame toOrbitRelativeFrame() {
  482.             return OrbitRelativeFrame.VNC_ROTATING;
  483.         }

  484.     },

  485.     /**
  486.      * Constant for Velocity - Normal - Co-normal frame considered inertial (X axis aligned with velocity, Y axis
  487.      * aligned with orbital momentum).
  488.      * <p>
  489.      * The axes of this frame are parallel to the axes of the {@link #TNW} and {@link #NTW} frames:
  490.      * </p>
  491.      * <ul>
  492.      *   <li>X<sub>VNC</sub> =  X<sub>TNW</sub> = Y<sub>NTW</sub></li>
  493.      *   <li>Y<sub>VNC</sub> =  Z<sub>TNW</sub> = Z<sub>NTW</sub></li>
  494.      *   <li>Z<sub>VNC</sub> = -Y<sub>TNW</sub> = X<sub>NTW</sub></li>
  495.      * </ul>
  496.      *
  497.      * @see #TNW
  498.      * @see #NTW
  499.      */
  500.     VNC_INERTIAL {
  501.         /** {@inheritDoc} */
  502.         @Override
  503.         public Rotation rotationFromInertial(final PVCoordinates pv) {
  504.             return VNC.rotationFromInertial(pv);
  505.         }

  506.         /** {@inheritDoc} */
  507.         @Override
  508.         public <T extends CalculusFieldElement<T>> FieldRotation<T> rotationFromInertial(final Field<T> field,
  509.                                                                                          final FieldPVCoordinates<T> pv) {
  510.             return VNC.rotationFromInertial(field, pv);
  511.         }

  512.         /** {@inheritDoc} */
  513.         @Override
  514.         public boolean isQuasiInertial() {
  515.             return true;
  516.         }

  517.         /** {@inheritDoc} */
  518.         @Override
  519.         public OrbitRelativeFrame toOrbitRelativeFrame() {
  520.             return OrbitRelativeFrame.VNC_INERTIAL;
  521.         }

  522.     },

  523.     /**
  524.      * Constant for Equinoctial Coordinate System (X axis aligned with ascending node, Z axis aligned with orbital
  525.      * momentum).
  526.      *
  527.      * @since 11.0
  528.      */
  529.     EQW {
  530.         /** {@inheritDoc} */
  531.         @Override
  532.         public Rotation rotationFromInertial(final PVCoordinates pv) {
  533.             final Vector3D m = pv.getMomentum();
  534.             return new Rotation(new Vector3D(-m.getY(), m.getX(), 0), m,
  535.                                 Vector3D.PLUS_I, Vector3D.PLUS_K);
  536.         }

  537.         /** {@inheritDoc} */
  538.         @Override
  539.         public <T extends CalculusFieldElement<T>> FieldRotation<T> rotationFromInertial(final Field<T> field,
  540.                                                                                          final FieldPVCoordinates<T> pv) {
  541.             final FieldVector3D<T> m = pv.getMomentum();
  542.             return new FieldRotation<>(new FieldVector3D<>(m.getY().negate(), m.getX(), field.getZero()),
  543.                                        m,
  544.                                        new FieldVector3D<>(field, Vector3D.PLUS_I),
  545.                                        new FieldVector3D<>(field, Vector3D.PLUS_K));
  546.         }

  547.         /** {@inheritDoc} */
  548.         @Override
  549.         public boolean isQuasiInertial() {
  550.             return true;
  551.         }

  552.         /** {@inheritDoc} */
  553.         @Override
  554.         public OrbitRelativeFrame toOrbitRelativeFrame() {
  555.             return OrbitRelativeFrame.EQW_INERTIAL;
  556.         }

  557.     },

  558.     /** Constant for Transverse Velocity Normal coordinate system
  559.      * (Y axis aligned with velocity, Z axis aligned with orbital momentum).
  560.      * <p>
  561.      * The axes of this frame are parallel to the axes of the {@link #TNW}
  562.      * and {@link #VNC} frames:
  563.      * </p>
  564.      * <ul>
  565.      *   <li>X<sub>NTW</sub> = -Y<sub>TNW</sub> = Z<sub>VNC</sub></li>
  566.      *   <li>Y<sub>NTW</sub> =  X<sub>TNW</sub> = X<sub>VNC</sub></li>
  567.      *   <li>Z<sub>NTW</sub> =  Z<sub>TNW</sub> = Y<sub>VNC</sub></li>
  568.      * </ul>
  569.      * @see #TNW
  570.      * @see #VNC
  571.      * @since 11.0
  572.      */
  573.     NTW {
  574.         /** {@inheritDoc} */
  575.         @Override
  576.         public Rotation rotationFromInertial(final PVCoordinates pv) {
  577.             return new Rotation(pv.getVelocity(), pv.getMomentum(),
  578.                                 Vector3D.PLUS_J, Vector3D.PLUS_K);
  579.         }

  580.         /** {@inheritDoc} */
  581.         @Override
  582.         public <T extends CalculusFieldElement<T>> FieldRotation<T> rotationFromInertial(final Field<T> field,
  583.                                                                                          final FieldPVCoordinates<T> pv) {
  584.             return new FieldRotation<>(pv.getVelocity(), pv.getMomentum(),
  585.                                        new FieldVector3D<>(field, Vector3D.PLUS_J),
  586.                                        new FieldVector3D<>(field, Vector3D.PLUS_K));
  587.         }

  588.         /** {@inheritDoc} */
  589.         @Override
  590.         public OrbitRelativeFrame toOrbitRelativeFrame() {
  591.             return OrbitRelativeFrame.NTW_ROTATING;
  592.         }

  593.     },

  594.     /**
  595.      * Constant for Transverse Velocity Normal coordinate system considered inertial (Y axis aligned with velocity, Z
  596.      * axis aligned with orbital momentum).
  597.      * <p>
  598.      * The axes of this frame are parallel to the axes of the {@link #TNW} and {@link #VNC} frames:
  599.      * </p>
  600.      * <ul>
  601.      *   <li>X<sub>NTW</sub> = -Y<sub>TNW</sub> = Z<sub>VNC</sub></li>
  602.      *   <li>Y<sub>NTW</sub> =  X<sub>TNW</sub> = X<sub>VNC</sub></li>
  603.      *   <li>Z<sub>NTW</sub> =  Z<sub>TNW</sub> = Y<sub>VNC</sub></li>
  604.      * </ul>
  605.      *
  606.      * @see #TNW
  607.      * @see #VNC
  608.      * @since 11.0
  609.      */
  610.     NTW_INERTIAL {
  611.         /** {@inheritDoc} */
  612.         @Override
  613.         public Rotation rotationFromInertial(final PVCoordinates pv) {
  614.             return NTW.rotationFromInertial(pv);
  615.         }

  616.         /** {@inheritDoc} */
  617.         @Override
  618.         public <T extends CalculusFieldElement<T>> FieldRotation<T> rotationFromInertial(final Field<T> field,
  619.                                                                                          final FieldPVCoordinates<T> pv) {
  620.             return NTW.rotationFromInertial(field, pv);
  621.         }

  622.         /** {@inheritDoc} */
  623.         @Override
  624.         public boolean isQuasiInertial() {
  625.             return true;
  626.         }

  627.         /** {@inheritDoc} */
  628.         @Override
  629.         public OrbitRelativeFrame toOrbitRelativeFrame() {
  630.             return OrbitRelativeFrame.NTW_INERTIAL;
  631.         }

  632.     },

  633.     /**
  634.      * Constant for East-North-Up frame.
  635.      * (Z aligned with position, North Pole in the (+Y, ±Z) half-plane)
  636.      * @see #NED
  637.      * @since 13.0
  638.      */
  639.     ENU {
  640.         /** {@inheritDoc} */
  641.         @Override
  642.         public Rotation rotationFromInertial(final PVCoordinates pv) {
  643.             return new Rotation(pv.getPosition(), east(pv),
  644.                                 Vector3D.PLUS_K, Vector3D.PLUS_I);
  645.         }

  646.         /** {@inheritDoc} */
  647.         @Override
  648.         public <T extends CalculusFieldElement<T>> FieldRotation<T> rotationFromInertial(final Field<T> field,
  649.                                                                                          final FieldPVCoordinates<T> pv) {
  650.             return new FieldRotation<>(pv.getPosition(), east(pv),
  651.                                        FieldVector3D.getPlusK(field), FieldVector3D.getPlusI(field));
  652.         }

  653.         /** {@inheritDoc} */
  654.         @Override
  655.         public boolean isQuasiInertial() {
  656.             return false;
  657.         }

  658.         /** {@inheritDoc} */
  659.         @Override
  660.         public OrbitRelativeFrame toOrbitRelativeFrame() {
  661.             return null;
  662.         }

  663.     },

  664.     /**
  665.      * Constant for North-East-Down frame.
  666.      * (Z aligned with opposite of position, North Pole in the (+X, ±Z) half-plane)
  667.      * @see #ENU
  668.      * @since 13.0
  669.      */
  670.     NED {
  671.         /** {@inheritDoc} */
  672.         @Override
  673.         public Rotation rotationFromInertial(final PVCoordinates pv) {
  674.             return new Rotation(pv.getPosition(), east(pv),
  675.                                 Vector3D.MINUS_K, Vector3D.PLUS_J);
  676.         }

  677.         /** {@inheritDoc} */
  678.         @Override
  679.         public <T extends CalculusFieldElement<T>> FieldRotation<T> rotationFromInertial(final Field<T> field,
  680.                                                                                          final FieldPVCoordinates<T> pv) {
  681.             return new FieldRotation<>(pv.getPosition(), east(pv),
  682.                                        FieldVector3D.getMinusK(field), FieldVector3D.getPlusJ(field));
  683.         }

  684.         /** {@inheritDoc} */
  685.         @Override
  686.         public boolean isQuasiInertial() {
  687.             return false;
  688.         }

  689.         /** {@inheritDoc} */
  690.         @Override
  691.         public OrbitRelativeFrame toOrbitRelativeFrame() {
  692.             return null;
  693.         }

  694.     };

  695.     /** {@inheritDoc} */
  696.     public String getName() {
  697.         return this.name();
  698.     }

  699.     /**
  700.      * Get the rotation from input to output {@link LOFType local orbital frame}.
  701.      * <p>
  702.      * This rotation does not include any time derivatives. If first time derivatives (i.e. rotation rate) is needed as well,
  703.      * the full {@link #transformFromLOFInToLOFOut(LOF, LOF, AbsoluteDate, PVCoordinates)} method must be called and
  704.      * the complete rotation transform must be extracted from it.
  705.      *
  706.      * @param in input commonly used local orbital frame
  707.      * @param out output commonly used local orbital frame
  708.      * @param pv position-velocity of the spacecraft in some inertial frame
  709.      *
  710.      * @return rotation from input to output local orbital frame
  711.      */
  712.     static Rotation rotationFromLOFInToLOFOut(final LOFType in, final LOFType out, final PVCoordinates pv) {
  713.         return out.rotationFromLOF(in, pv);
  714.     }

  715.     /**
  716.      * Get the rotation from input to output {@link LOFType local orbital frame}.
  717.      * <p>
  718.      * This rotation does not include any time derivatives. If first time derivatives (i.e. rotation rate) is needed as well,
  719.      * the full {@link #transformFromLOFInToLOFOut(LOF, LOF, FieldAbsoluteDate, FieldPVCoordinates)}  method must be called and
  720.      * the complete rotation transform must be extracted from it.
  721.      *
  722.      * @param field field to which the elements belong
  723.      * @param in input commonly used local orbital frame
  724.      * @param out output commonly used local orbital frame
  725.      * @param pv position-velocity of the spacecraft in some inertial frame
  726.      * @param <T> type of the field elements
  727.      *
  728.      * @return rotation from input to output local orbital frame
  729.      */
  730.     static <T extends CalculusFieldElement<T>> FieldRotation<T> rotationFromLOFInToLOFOut(final Field<T> field,
  731.                                                                                           final LOFType in,
  732.                                                                                           final LOFType out,
  733.                                                                                           final FieldPVCoordinates<T> pv) {
  734.         return out.rotationFromLOF(field, in, pv);
  735.     }

  736.     /**
  737.      * Get the rotation from input {@link LOF local orbital frame} to the instance.
  738.      * <p>
  739.      * This rotation does not include any time derivatives. If first time derivatives (i.e. rotation rate) is needed as well,
  740.      * the full {@link #transformFromLOF(LOF, AbsoluteDate, PVCoordinates)} method must be called and the complete rotation
  741.      * transform must be extracted from it.
  742.      *
  743.      * @param fromLOF input local orbital frame
  744.      * @param pv position-velocity of the spacecraft in some inertial frame
  745.      *
  746.      * @return rotation from input local orbital frame to the instance
  747.      */
  748.     public Rotation rotationFromLOF(final LOFType fromLOF, final PVCoordinates pv) {

  749.         // First compute the rotation from the input LOF to the pivot inertial
  750.         final Rotation fromLOFToInertial = fromLOF.rotationFromInertial(pv).revert();

  751.         // Then compute the rotation from the pivot inertial to the output LOF
  752.         final Rotation inertialToThis = this.rotationFromInertial(pv);

  753.         // Output composed rotation
  754.         return fromLOFToInertial.compose(inertialToThis, RotationConvention.FRAME_TRANSFORM);
  755.     }

  756.     /**
  757.      * Get the rotation from input {@link LOFType local orbital frame} to the instance.
  758.      * <p>
  759.      * This rotation does not include any time derivatives. If first time derivatives (i.e. rotation rate) is needed as well,
  760.      * the full {@link #transformFromLOF(LOF, FieldAbsoluteDate, FieldPVCoordinates)} method must be called and the complete
  761.      * rotation transform must be extracted from it.
  762.      *
  763.      * @param field field to which the elements belong
  764.      * @param fromLOF input local orbital frame
  765.      * @param pv position-velocity of the spacecraft in some inertial frame
  766.      * @param <T> type of the field elements
  767.      *
  768.      * @return rotation from input local orbital frame to the instance
  769.      */
  770.     public <T extends CalculusFieldElement<T>> FieldRotation<T> rotationFromLOF(final Field<T> field,
  771.                                                                                 final LOFType fromLOF,
  772.                                                                                 final FieldPVCoordinates<T> pv) {

  773.         // First compute the rotation from the input LOF to the pivot inertial
  774.         final FieldRotation<T> fromLOFToInertial = fromLOF.rotationFromInertial(field, pv).revert();

  775.         // Then compute the rotation from the pivot inertial to the output LOF
  776.         final FieldRotation<T> inertialToThis = this.rotationFromInertial(field, pv);

  777.         // Output composed rotation
  778.         return fromLOFToInertial.compose(inertialToThis, RotationConvention.FRAME_TRANSFORM);
  779.     }

  780.     /**
  781.      * {@inheritDoc} It is unnecessary to use this method when dealing with {@link LOFType}, use
  782.      * {@link #rotationFromInertial(PVCoordinates)} instead.
  783.      */
  784.     @Override
  785.     public Rotation rotationFromInertial(final AbsoluteDate date, final PVCoordinates pv) {
  786.         return rotationFromInertial(pv);
  787.     }

  788.     /**
  789.      * Get the rotation from inertial frame to local orbital frame.
  790.      * <p>
  791.      * This rotation does not include any time derivatives. If first time derivatives (i.e. rotation rate) is needed as well,
  792.      * the full {@link #transformFromInertial(AbsoluteDate, PVCoordinates)} method must be called and
  793.      * the complete rotation transform must be extracted from it.
  794.      * </p>
  795.      *
  796.      * @param pv position-velocity of the spacecraft in some inertial frame
  797.      *
  798.      * @return rotation from inertial frame to local orbital frame
  799.      */
  800.     public abstract Rotation rotationFromInertial(PVCoordinates pv);

  801.     /**
  802.      * {@inheritDoc} It is unnecessary to use this method when dealing with {@link LOFType}, use
  803.      * {@link #rotationFromInertial(Field, FieldPVCoordinates)} instead.
  804.      */
  805.     @Override
  806.     public <T extends CalculusFieldElement<T>> FieldRotation<T> rotationFromInertial(final Field<T> field,
  807.                                                                                      final FieldAbsoluteDate<T> date,
  808.                                                                                      final FieldPVCoordinates<T> pv) {
  809.         return rotationFromInertial(field, pv);
  810.     }

  811.     /**
  812.      * Get the rotation from inertial frame to local orbital frame.
  813.      * <p>
  814.      * This rotation does not include any time derivatives. If first time derivatives (i.e. rotation rate) is needed as well,
  815.      * the full {@link #transformFromInertial(FieldAbsoluteDate, FieldPVCoordinates)} method must be
  816.      * called and the complete rotation transform must be extracted from it.
  817.      * </p>
  818.      *
  819.      * @param field field to which the elements belong
  820.      * @param pv position-velocity of the spacecraft in some inertial frame
  821.      * @param <T> type of the field elements
  822.      *
  823.      * @return rotation from inertial frame to local orbital frame
  824.      */
  825.     public abstract <T extends CalculusFieldElement<T>> FieldRotation<T> rotationFromInertial(Field<T> field,
  826.                                                                                               FieldPVCoordinates<T> pv);

  827.     /**
  828.      * Convert current local orbital frame to CCSDS equivalent orbit relative frame when possible, null otherwise.
  829.      *
  830.      * @return CCSDS equivalent orbit relative frame when possible, null otherwise
  831.      *
  832.      * @see OrbitRelativeFrame
  833.      */
  834.     public abstract OrbitRelativeFrame toOrbitRelativeFrame();

  835.     /** Compute East direction.
  836.      * @param pv position-velocity of the spacecraft in some inertial frame
  837.      * @return East direction
  838.      * @since 13.0
  839.      */
  840.     private static Vector3D east(final PVCoordinates pv) {
  841.         final Vector3D p = pv.getPosition();
  842.         final double px = p.getX();
  843.         final double py = p.getY();
  844.         return (px == 0.0 && py == 0.0) ? Vector3D.PLUS_J : new Vector3D(-py, px, 0);
  845.     }

  846.     /** Compute East direction.
  847.      * @param <T> type of the field elements
  848.      * @param pv position-velocity of the spacecraft in some inertial frame
  849.      * @return East direction
  850.      * @since 13.0
  851.      */
  852.     private static <T extends CalculusFieldElement<T>> FieldVector3D<T> east(final FieldPVCoordinates<T> pv) {
  853.         final FieldVector3D<T> p = pv.getPosition();
  854.         final T px = p.getX();
  855.         final T py = p.getY();
  856.         return (px.isZero() && py.isZero()) ?
  857.                FieldVector3D.getPlusJ(px.getField()) :
  858.                new FieldVector3D<>(py.negate(), px, px.getField().getZero());
  859.     }

  860. }