RPOModel.java

/* Copyright 2002-2026 CS GROUP
 * Licensed to CS GROUP (CS) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * CS licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.orekit.control.relative;

import org.hipparchus.CalculusFieldElement;
import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
import org.hipparchus.geometry.euclidean.threed.Vector3D;
import org.hipparchus.linear.FieldVector;
import org.hipparchus.linear.MatrixUtils;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.frames.LOFType;
import org.orekit.frames.LocalOrbitalFrame;
import org.orekit.orbits.FieldKeplerianOrbit;
import org.orekit.orbits.FieldOrbit;
import org.orekit.orbits.KeplerianOrbit;
import org.orekit.orbits.Orbit;
import org.orekit.orbits.OrbitType;
import org.orekit.orbits.PositionAngleType;
import org.orekit.propagation.FieldSpacecraftState;
import org.orekit.propagation.SpacecraftState;
import org.orekit.propagation.analytical.FieldKeplerianPropagator;
import org.orekit.propagation.analytical.KeplerianPropagator;
import org.orekit.propagation.events.DateDetector;
import org.orekit.propagation.events.EventDetector;
import org.orekit.propagation.events.FieldDateDetector;
import org.orekit.propagation.events.FieldEventDetector;
import org.orekit.propagation.relative.FieldRelativeProvider;
import org.orekit.propagation.relative.RelativeProvider;
import org.orekit.propagation.relative.clohessywiltshire.ClohessyWiltshireEquations;
import org.orekit.propagation.relative.clohessywiltshire.ClohessyWiltshireMatrices;
import org.orekit.propagation.relative.clohessywiltshire.ClohessyWiltshireProvider;
import org.orekit.propagation.relative.clohessywiltshire.ClohessyWiltshireRendezVous;
import org.orekit.propagation.relative.clohessywiltshire.FieldClohessyWiltshireEquations;
import org.orekit.propagation.relative.clohessywiltshire.FieldClohessyWiltshireMatrices;
import org.orekit.propagation.relative.clohessywiltshire.FieldClohessyWiltshireProvider;
import org.orekit.propagation.relative.clohessywiltshire.FieldClohessyWiltshireRendezVous;
import org.orekit.propagation.relative.maneuver.ClohessyWiltshireManeuver;
import org.orekit.propagation.relative.maneuver.FieldClohessyWiltshireManeuver;
import org.orekit.propagation.relative.maneuver.FieldRelativeManeuver;
import org.orekit.propagation.relative.maneuver.FieldYamanakaAnkersenManeuver;
import org.orekit.propagation.relative.maneuver.RelativeManeuver;
import org.orekit.propagation.relative.maneuver.YamanakaAnkersenManeuver;
import org.orekit.propagation.relative.yamanakaankersen.FieldYamanakaAnkersenProvider;
import org.orekit.propagation.relative.yamanakaankersen.FieldYamanakaAnkersenRendezVous;
import org.orekit.propagation.relative.yamanakaankersen.YamanakaAnkersenProvider;
import org.orekit.propagation.relative.yamanakaankersen.YamanakaAnkersenRendezVous;
import org.orekit.time.AbsoluteDate;
import org.orekit.time.FieldAbsoluteDate;
import org.orekit.utils.FieldPVCoordinates;
import org.orekit.utils.TimeStampedFieldPVCoordinates;
import org.orekit.utils.TimeStampedPVCoordinates;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Enumeration used to compute the relative maneuvers based on Clohessy-Wiltshire equations (only Circular cases) or
 * Yamanaka-Ankersen equations.
 *
 * @author Romain Cuvillon
 * @since 14.0
 */
public enum RPOModel implements RPO {
    /**
     * CW: Clohessy-Wiltshire.
     */
    CW {
        /** {@inheritDoc} */
        @Override
        public Vector3D getRBarDirection() {
            return Vector3D.PLUS_I;
        }

        /** {@inheritDoc} */
        @Override
        public Vector3D getVBarDirection() {
            return Vector3D.PLUS_J;
        }

        /** {@inheritDoc} */
        @Override
        public Vector3D getOutOfPlaneDirection() {
            return Vector3D.PLUS_K;
        }

        /** {@inheritDoc} */
        @Override
        public LOFType getLOFType() {
            return LOFType.QSW;
        }

        /**
         ** Computes the waypoints of the teardrop relative orbit in QSW Local Orbital Frame to use them with Clohessy-Wiltshire maneuvers.
         * A teardrop is analytically feasible only if the target's orbit is circular.
         *
         * <p>The injection point is the turn-around point of the teardrop (the round end).</p>
         * <p>All maneuvers are performed at the pointy end of the teardrop.</p>
         *
         * @param injectionDate Date of injection in the teardrop.
         * @param targetOrbit target's orbit.
         * @param turnAroundDistance Turn-around distance. This is the "round" end of the orbit. Note that this distance is signed : negative means below the target spacecraft (in between the planet and the target), while positive means above the target (target is in between the chaser and the planet).
         * @param maneuverDistance Maneuver distance of the teardrop orbit. This is the "pointy" end of the orbit. Note that this distance is signed : negative means below the target spacecraft (in between the planet and the target), while positive means above the target (target is in between the chaser and the planet).
         * @param numberOfTeardrops Number of teardrop orbits to perform. Must be ≥ 1.
         * @return List of waypoints in time. Date, position, and velocity are non-zero.
         */
        @Override
        public List<TimeStampedPVCoordinates> computeTeardropWaypoints(final AbsoluteDate injectionDate,
                                                                       final Orbit targetOrbit,
                                                                       final double turnAroundDistance,
                                                                       final double maneuverDistance,
                                                                       final int numberOfTeardrops) {
            return new TeardropCircularWaypointCalculator(targetOrbit.getKeplerianMeanMotion(), turnAroundDistance,
                                                          maneuverDistance, numberOfTeardrops).computeTearDropWaypoints(
                            injectionDate);
        }

        /**
         ** Computes the waypoints of the teardrop relative orbit in QSW Local Orbital Frame to use them with Clohessy-Wiltshire maneuvers.
         * A teardrop is analytically feasible only if the target's orbit is circular.
         *
         * <p>The injection point is the turn-around point of the teardrop (the round end).</p>
         * <p>All maneuvers are performed at the pointy end of the teardrop.</p>
         *
         * @param injectionDate Date of injection in the teardrop.
         * @param targetOrbit target's orbit.
         * @param turnAroundDistance Turn-around distance. This is the "round" end of the orbit. Note that this distance is signed : negative means below the target spacecraft (in between the planet and the target), while positive means above the target (target is in between the chaser and the planet).
         * @param maneuverDistance Maneuver distance of the teardrop orbit. This is the "pointy" end of the orbit. Note that this distance is signed : negative means below the target spacecraft (in between the planet and the target), while positive means above the target (target is in between the chaser and the planet).
         * @param numberOfTeardrops Number of teardrop orbits to perform. Must be ≥ 1.
         * @param <T> field.
         * @return List of waypoints in time. Date, position, and velocity are non-zero.
         */
        @Override
        public <T extends CalculusFieldElement<T>> List<TimeStampedFieldPVCoordinates<T>> computeTeardropWaypoints(
                        final FieldAbsoluteDate<T> injectionDate, final FieldOrbit<T> targetOrbit,
                        final T turnAroundDistance, final T maneuverDistance, final int numberOfTeardrops) {
            return new FieldTeardropCircularWaypointCalculator<T>(targetOrbit.getKeplerianMeanMotion(),
                                                                  turnAroundDistance, maneuverDistance,
                                                                  numberOfTeardrops).computeTearDropWaypoints(
                            injectionDate);
        }

        /**
         * Compute relative maneuvers to realize a forced trajectory defined by the waypoints (ForcedLinear/ForcedCircular)
         * using Clohessy-Wiltshire model.
         * @param waypoints Waypoints of the trajectory in QSW frame.
         * @param initialVelocity Initial velocity in the Clohessy-Wiltshire (QSW) frame.
         * @param targetOrbit Orbit of the target.
         * @param cwProvider Clohessy-Wiltshire provider.
         * @return list of relative maneuvers in Clohessy-Wiltshire frame (QSW).
         */
        @Override
        public List<RelativeManeuver> computeForcedManeuvers(final List<TimeStampedPVCoordinates> waypoints,
                                                             final Vector3D initialVelocity, final Orbit targetOrbit,
                                                             final RelativeProvider cwProvider) {
            final List<ClohessyWiltshireManeuver> maneuvers = new ArrayList<>();
            Vector3D velocityBeforeManeuver = initialVelocity;
            final LocalOrbitalFrame targetLof = new LocalOrbitalFrame(targetOrbit.getFrame(), LOFType.QSW, targetOrbit,
                                                                      LOFType.QSW.getName());
            for (int i = 0; i < waypoints.size() - 1; i++) {

                // Define Current waypoint and next waypoint in QSW
                final TimeStampedPVCoordinates currentWaypoint = waypoints.get(i);
                final TimeStampedPVCoordinates nextWaypoint = waypoints.get(i + 1);

                // Define Date Detector to trigger the maneuver
                final EventDetector firstImpulseTrigger = new DateDetector(currentWaypoint.getDate());

                // Compute the velocity after the maneuver to arrive at the following waypoint at the correct date using RendezVous.
                final Vector3D velocityAfterManeuver =
                                ClohessyWiltshireRendezVous.computeRendezVous(currentWaypoint, nextWaypoint, targetLof,
                                                                              targetOrbit).getPvt1().getVelocity();
                // Compute the impulsion at the current waypoint to reach the next waypoint.
                final Vector3D deltaV = velocityAfterManeuver.subtract(velocityBeforeManeuver);

                // Create the Clohessy-Wiltshire maneuver and add it to the list.
                final ClohessyWiltshireManeuver maneuver = new ClohessyWiltshireManeuver(firstImpulseTrigger, deltaV,
                                                                                         (ClohessyWiltshireProvider) cwProvider);
                maneuvers.add(maneuver);

                // Compute the velocity before the maneuver at the next waypoint.
                final double transferDuration = nextWaypoint.getDate().durationFrom(currentWaypoint.getDate());
                final ClohessyWiltshireMatrices matrices = ClohessyWiltshireEquations.computeMatrices(transferDuration,
                                                                                                      targetOrbit.getKeplerianMeanMotion());
                velocityBeforeManeuver = new Vector3D(matrices.getPhiVR().operate(MatrixUtils.createRealVector(
                                                                              currentWaypoint.getPosition().toArray())).add(matrices.getPhiVV()
                                                                                                                                    .operate(MatrixUtils.createRealVector(
                                                                                                                                                    velocityAfterManeuver.toArray())))
                                                              .toArray());
            }
            return new ArrayList<>(maneuvers);
        }

        /**
         * Compute relative maneuvers to realize a forced trajectory defined by the waypoints (ForcedLinear/ForcedCircular)
         * using Clohessy-Wiltshire model.
         * @param waypoints Waypoints of the trajectory in QSW frame.
         * @param initialVelocity Initial velocity in the Clohessy-Wiltshire (QSW) frame.
         * @param targetOrbit Orbit of the target.
         * @param cwProvider Clohessy-Wiltshire provider.
         * @param <T> field.
         * @return list of relative maneuvers in Clohessy-Wiltshire frame (QSW).
         */
        @Override
        public <T extends CalculusFieldElement<T>> List<FieldRelativeManeuver<T>> computeForcedManeuvers(
                        final List<TimeStampedFieldPVCoordinates<T>> waypoints, final FieldVector3D<T> initialVelocity,
                        final FieldOrbit<T> targetOrbit, final FieldRelativeProvider<T> cwProvider) {

            final List<FieldClohessyWiltshireManeuver<T>> maneuvers = new ArrayList<>();
            FieldVector3D<T> velocityBeforeManeuver = initialVelocity;

            final LocalOrbitalFrame targetLof =
                            new LocalOrbitalFrame(targetOrbit.getFrame(), LOFType.QSW, targetOrbit.toOrbit(),
                                                  LOFType.QSW.getName());

            for (int i = 0; i < waypoints.size() - 1; i++) {

                // Define Current waypoint and next waypoint in QSW
                final TimeStampedFieldPVCoordinates<T> currentWaypoint = waypoints.get(i);
                final TimeStampedFieldPVCoordinates<T> nextWaypoint = waypoints.get(i + 1);

                // Define Date Detector to trigger the maneuver
                final FieldEventDetector<T> firstImpulseTrigger = new FieldDateDetector<>(currentWaypoint.getDate());

                // Compute the velocity after the maneuver to arrive at the following waypoint at the correct date using RendezVous.
                final FieldVector3D<T> velocityAfterManeuver =
                                (new FieldClohessyWiltshireRendezVous<T>()).computeRendezVous(currentWaypoint,
                                                                                              nextWaypoint, targetLof,
                                                                                              targetOrbit).getPvt1()
                                                                           .getVelocity();

                // Compute the impulsion at the current waypoint to reach the next waypoint.
                final FieldVector3D<T> deltaV = velocityAfterManeuver.subtract(velocityBeforeManeuver);

                // Create the Clohessy-Wiltshire maneuver and add it to the list.
                final FieldClohessyWiltshireManeuver<T> maneuver =
                                new FieldClohessyWiltshireManeuver<>(firstImpulseTrigger, deltaV,
                                                                     (FieldClohessyWiltshireProvider<T>) cwProvider);
                maneuvers.add(maneuver);

                // Compute the velocity before the maneuver at the next waypoint.
                final T transferDuration = nextWaypoint.getDate().durationFrom(currentWaypoint.getDate());
                final FieldClohessyWiltshireMatrices<T> matrices =
                                (new FieldClohessyWiltshireEquations<T>()).computeMatrices(transferDuration,
                                                                                           targetOrbit.getKeplerianMeanMotion());
                final FieldVector<T> position = MatrixUtils.createFieldVector(currentWaypoint.getPosition().toArray());
                final FieldVector<T> velocity = MatrixUtils.createFieldVector(velocityAfterManeuver.toArray());
                velocityBeforeManeuver =
                                new FieldVector3D<>(matrices.getPhiVR().operate(position).
                                                add(matrices.getPhiVV().operate(velocity)).toArray());
            }
            return new ArrayList<>(maneuvers);
        }

        /**
         * Computes the Clohessy-Wilstshire based maneuvers of the teardrop relative orbit in QSW Local Orbital Frame.
         *
         * <p>The injection point is the turn-around point of the teardrop (the round end).</p>
         * <p>All maneuvers happen at the pointy end of the teardrop.</p>
         *
         * @param waypoints List of the successive waypoints of the target.
         * @param cwProvider Clohessy-Wiltshire provider.
         * @return list of Clohessy-Wiltshire maneuvers.
         */
        @Override
        public List<RelativeManeuver> computeTeardropManeuvers(final List<TimeStampedPVCoordinates> waypoints,
                                                               final RelativeProvider cwProvider) {

            final List<RelativeManeuver> maneuvers = new ArrayList<>();

            // Creation of the maneuvers at the maneuver point of the teardrop.
            for (int i = 1; i < waypoints.size(); i++) {
                final TimeStampedPVCoordinates maneuverWaypoint = waypoints.get(i);
                final Vector3D maneuverVelocity = maneuverWaypoint.getVelocity();
                final EventDetector maneuverDate = new DateDetector(maneuverWaypoint.getDate());
                final TimeStampedPVCoordinates pvtBeforeMan =
                                new TimeStampedPVCoordinates(maneuverWaypoint.getDate(), maneuverWaypoint.getPosition(),
                                                             new Vector3D(-maneuverVelocity.getX(),
                                                                          maneuverVelocity.getY(),
                                                                          maneuverVelocity.getZ()));
                final Vector3D deltaV = maneuverVelocity.subtract(pvtBeforeMan.getVelocity());
                final RelativeManeuver maneuver =
                                new ClohessyWiltshireManeuver(maneuverDate, deltaV,
                                                              (ClohessyWiltshireProvider) cwProvider);
                maneuvers.add(maneuver);
            }
            return maneuvers;
        }

        /**
         * Computes the Clohessy-Wilstshire based maneuvers of the teardrop relative orbit in QSW Local Orbital Frame.
         *
         * <p>The injection point is the turn-around point of the teardrop (the round end).</p>
         * <p>All maneuvers happen at the pointy end of the teardrop.</p>
         *
         * @param waypoints List of the successive waypoints of the target.
         * @param cwProvider Clohessy-Wiltshire provider.
         * @param <T> field.
         * @return list of Clohessy-Wiltshire maneuvers.
         */
        @Override
        public <T extends CalculusFieldElement<T>> List<FieldRelativeManeuver<T>> computeTeardropManeuvers(
                        final List<TimeStampedFieldPVCoordinates<T>> waypoints,
                        final FieldRelativeProvider<T> cwProvider) {

            final List<FieldRelativeManeuver<T>> maneuvers = new ArrayList<>();

            // Creation of the maneuvers at the maneuver point of the teardrop.
            for (int i = 1; i < waypoints.size(); i++) {
                final TimeStampedFieldPVCoordinates<T> maneuverWaypoint = waypoints.get(i);
                final FieldVector3D<T> maneuverVelocity = maneuverWaypoint.getVelocity();
                final FieldEventDetector<T> maneuverDate = new FieldDateDetector<>(maneuverWaypoint.getDate());
                final TimeStampedFieldPVCoordinates<T> pvtBeforeMan =
                                new TimeStampedFieldPVCoordinates<>(maneuverWaypoint.getDate(),
                                                                    new FieldPVCoordinates<>(
                                                                                    maneuverWaypoint.getPosition(),
                                                                                    new FieldVector3D<>(
                                                                                                    maneuverVelocity.getX()
                                                                                                                    .negate(),
                                                                                                    maneuverVelocity.getY(),
                                                                                                    maneuverVelocity.getZ())));
                final FieldVector3D<T> deltaV = maneuverVelocity.subtract(pvtBeforeMan.getVelocity());
                final FieldRelativeManeuver<T> maneuver =
                                new FieldClohessyWiltshireManeuver<>(maneuverDate, deltaV,
                                                                     (FieldClohessyWiltshireProvider<T>) cwProvider);
                maneuvers.add(maneuver);
            }
            return maneuvers;
        }
    },
    /**
     * YA : Yamanaka-Ankersen.
     */
    YA {
        /** {@inheritDoc} */
        @Override
        public Vector3D getRBarDirection() {
            return Vector3D.MINUS_K;
        }

        /** {@inheritDoc} */
        @Override
        public Vector3D getVBarDirection() {
            return Vector3D.PLUS_I;
        }

        /** {@inheritDoc} */
        @Override
        public Vector3D getOutOfPlaneDirection() {
            return Vector3D.MINUS_J;
        }

        /** {@inheritDoc} */
        @Override
        public LOFType getLOFType() {
            return LOFType.LVLH_CCSDS;
        }

        /**
         ** Computes the waypoints of the teardrop relative orbit in LVLH Local Orbital Frame to use them with Yamanaka-Ankersen maneuvers.
         * A teardrop is analytically feasible only if the target's orbit is circular.
         * If the orbit of the target is eccentric, an error is thrown to prevent it.
         *
         * <p>The injection point is the turn-around point of the teardrop (the round end).</p>
         * <p>All maneuvers are performed at the pointy end of the teardrop.</p>
         *
         * @param injectionDate Date of injection in the teardrop.
         * @param targetOrbit target's orbit.
         * @param turnAroundDistance Turn-around distance. This is the "round" end of the orbit. Note that this distance is signed : negative means below the target spacecraft (in between the planet and the target), while positive means above the target (target is in between the chaser and the planet).
         * @param maneuverDistance Maneuver distance of the teardrop orbit. This is the "pointy" end of the orbit. Note that this distance is signed : negative means below the target spacecraft (in between the planet and the target), while positive means above the target (target is in between the chaser and the planet).
         * @param numberOfTeardrops Number of teardrop orbits to perform. Must be ≥ 1.
         * @return List of waypoints in time. Date, position, and velocity are non-zero.
         */
        @Override
        public List<TimeStampedPVCoordinates> computeTeardropWaypoints(final AbsoluteDate injectionDate,
                                                                       final Orbit targetOrbit,
                                                                       final double turnAroundDistance,
                                                                       final double maneuverDistance,
                                                                       final int numberOfTeardrops) {
            if (targetOrbit.getE() >= 1e-4) {
                throw new OrekitException(OrekitMessages.TOO_LARGE_ECCENTRICITY_FOR_TEARDROP_MOTION);
            } else {
                final List<TimeStampedPVCoordinates> waypointsQSW =
                                new TeardropCircularWaypointCalculator(targetOrbit.getKeplerianMeanMotion(),
                                                                       turnAroundDistance, maneuverDistance,
                                                                       numberOfTeardrops).computeTearDropWaypoints(
                                                injectionDate);
                final List<TimeStampedPVCoordinates> waypointsLVLH = new ArrayList<>();
                for (TimeStampedPVCoordinates waypoint : waypointsQSW) {
                    final TimeStampedPVCoordinates waypointLVLH =
                                    LOFType.LVLH_CCSDS.transformFromLOF(LOFType.QSW, waypoint.getDate(), waypoint)
                                                      .transformPVCoordinates(waypoint);
                    waypointsLVLH.add(waypointLVLH);
                }
                return waypointsLVLH;
            }
        }

        /**
         ** Computes the waypoints of the teardrop relative orbit in LVLH Local Orbital Frame to use them with Yamanaka-Ankersen maneuvers.
         * A teardrop is analytically feasible only if the target's orbit is circular.
         * If the orbit of the target is eccentric, an error is thrown to prevent it.
         *
         * <p>The injection point is the turn-around point of the teardrop (the round end).</p>
         * <p>All maneuvers are performed at the pointy end of the teardrop.</p>
         *
         * @param injectionDate Date of injection in the teardrop.
         * @param targetOrbit target's orbit.
         * @param turnAroundDistance Turn-around distance. This is the "round" end of the orbit. Note that this distance is signed : negative means below the target spacecraft (in between the planet and the target), while positive means above the target (target is in between the chaser and the planet).
         * @param maneuverDistance Maneuver distance of the teardrop orbit. This is the "pointy" end of the orbit. Note that this distance is signed : negative means below the target spacecraft (in between the planet and the target), while positive means above the target (target is in between the chaser and the planet).
         * @param numberOfTeardrops Number of teardrop orbits to perform. Must be ≥ 1.
         * @param <T> field.
         * @return List of waypoints in time. Date, position, and velocity are non-zero.
         */
        @Override
        public <T extends CalculusFieldElement<T>> List<TimeStampedFieldPVCoordinates<T>> computeTeardropWaypoints(
                        final FieldAbsoluteDate<T> injectionDate, final FieldOrbit<T> targetOrbit,
                        final T turnAroundDistance, final T maneuverDistance, final int numberOfTeardrops) {

            if (targetOrbit.getE().getReal() >= 1e-4) {
                throw new OrekitException(OrekitMessages.TOO_LARGE_ECCENTRICITY_FOR_TEARDROP_MOTION);
            } else {
                final List<TimeStampedFieldPVCoordinates<T>> waypointsQSW =
                                new FieldTeardropCircularWaypointCalculator<>(targetOrbit.getKeplerianMeanMotion(),
                                                                              turnAroundDistance, maneuverDistance,
                                                                              numberOfTeardrops)
                                                .computeTearDropWaypoints(injectionDate);

                final List<TimeStampedFieldPVCoordinates<T>> waypointsLVLH = new ArrayList<>();
                for (TimeStampedFieldPVCoordinates<T> waypoint : waypointsQSW) {
                    final TimeStampedFieldPVCoordinates<T> waypointLVLH =
                                    LOFType.LVLH_CCSDS.transformFromLOF(LOFType.QSW, waypoint.getDate(), waypoint)
                                                      .transformPVCoordinates(waypoint);
                    waypointsLVLH.add(waypointLVLH);
                }
                return waypointsLVLH;
            }
        }

        /**
         * Compute relative maneuvers to realize a forced trajectory defined by the waypoints (ForcedLinear/ForcedCircular)
         * using Yamanaka-Ankersen model.
         *
         * @param waypoints Waypoints of the trajectory in LVLH_CCSDS frame.
         * @param initialVelocity Initial velocity in the Yamanaka-Ankersen (LVLH_CCSDS) frame.
         * @param targetOrbit Orbit of the target.
         * @param yaProvider Yamanaka-Ankersen provider.
         * @return list of relative maneuvers in Yamanaka-Ankersen model's frame (LVLH_CCSDS).
         */
        @Override
        public List<RelativeManeuver> computeForcedManeuvers(final List<TimeStampedPVCoordinates> waypoints,
                                                             final Vector3D initialVelocity, final Orbit targetOrbit,
                                                             final RelativeProvider yaProvider) {

            final List<RelativeManeuver> maneuvers = new ArrayList<>();
            Vector3D velocityBeforeManeuver = initialVelocity;

            KeplerianOrbit orbit = (KeplerianOrbit) OrbitType.KEPLERIAN.convertType(targetOrbit);
            yaProvider.setTargetOrbit(orbit);

            final KeplerianPropagator targetPropagator = new KeplerianPropagator(targetOrbit);
            for (int i = 0; i < waypoints.size() - 1; i++) {
                final LocalOrbitalFrame lofUpdated =
                                new LocalOrbitalFrame(targetOrbit.getFrame(), LOFType.LVLH_CCSDS, orbit,
                                                      LOFType.LVLH_CCSDS.getName());

                // Define Current waypoint and next waypoint in QSW
                final TimeStampedPVCoordinates currentWaypoint = waypoints.get(i);
                final TimeStampedPVCoordinates nextWaypoint = waypoints.get(i + 1);

                // Define Date Detector to trigger the maneuver
                final EventDetector firstImpulseTrigger = new DateDetector(currentWaypoint.getDate());

                // Compute the velocity after the maneuver to arrive at the following waypoint at the correct date using RendezVous.
                final Vector3D velocityAfterManeuver =
                                YamanakaAnkersenRendezVous.computeRendezVous(currentWaypoint, nextWaypoint, lofUpdated,
                                                                             orbit, targetPropagator).getPvt1()
                                                          .getVelocity();

                // Compute the impulsion at the current waypoint to reach the next waypoint.
                final Vector3D deltaV = velocityAfterManeuver.subtract(velocityBeforeManeuver);

                // Create the Yamanaka-Ankersen maneuver and add it to the list.
                final RelativeManeuver maneuver = new YamanakaAnkersenManeuver(firstImpulseTrigger, deltaV,
                                                                               (YamanakaAnkersenProvider) yaProvider);
                maneuvers.add(maneuver);

                // Create a Yamanaka-Ankersen provider to propagate to the next waypoint.
                final YamanakaAnkersenProvider yaProviderManeuver = new YamanakaAnkersenProvider(orbit,
                                                                                                 new TimeStampedPVCoordinates(
                                                                                                                 currentWaypoint.getDate(),
                                                                                                                 currentWaypoint.getPosition(),
                                                                                                                 velocityAfterManeuver),
                                                                                                 "Provider_" + i);
                targetPropagator.addAdditionalDataProvider(yaProviderManeuver);
                // Propagate the waypoint.
                final SpacecraftState propagated = targetPropagator.propagate(nextWaypoint.getDate());
                // Update the target orbit and the velocity before the next maneuver.
                final double trueAnomaly = ((KeplerianOrbit) OrbitType.KEPLERIAN.convertType(
                                propagated.getOrbit())).getTrueAnomaly();
                orbit = new KeplerianOrbit(orbit.getA(), orbit.getE(), orbit.getI(), orbit.getPerigeeArgument(),
                                           orbit.getRightAscensionOfAscendingNode(), trueAnomaly,
                                           PositionAngleType.TRUE, orbit.getFrame(), nextWaypoint.getDate(),
                                           orbit.getMu());
                velocityBeforeManeuver =
                                new Vector3D(Arrays.copyOfRange(yaProviderManeuver.getAdditionalData(propagated), 3,
                                                                6));
            }
            return maneuvers;
        }

        /**
         * Compute relative maneuvers to realize a forced trajectory defined by the waypoints (ForcedLinear/ForcedCircular)
         * using Yamanaka-Ankersen model.
         *
         * @param waypoints Waypoints of the trajectory in LVLH_CCSDS frame.
         * @param initialVelocity Initial velocity in the Yamanaka-Ankersen (LVLH_CCSDS) frame.
         * @param targetOrbit Orbit of the target.
         * @param yaProvider Yamanaka-Ankersen provider.
         * @param <T> field.
         * @return list of relative maneuvers in Yamanaka-Ankersen model's frame (LVLH_CCSDS).
         */
        @Override
        public <T extends CalculusFieldElement<T>> List<FieldRelativeManeuver<T>> computeForcedManeuvers(
                        final List<TimeStampedFieldPVCoordinates<T>> waypoints, final FieldVector3D<T> initialVelocity,
                        final FieldOrbit<T> targetOrbit, final FieldRelativeProvider<T> yaProvider) {

            final List<FieldRelativeManeuver<T>> maneuvers = new ArrayList<>();
            FieldVector3D<T> velocityBeforeManeuver = initialVelocity;

            FieldKeplerianOrbit<T> orbit = (FieldKeplerianOrbit<T>) OrbitType.KEPLERIAN.convertType(targetOrbit);
            yaProvider.setTargetOrbit(orbit);
            final FieldKeplerianPropagator<T> targetPropagator = new FieldKeplerianPropagator<>(targetOrbit);

            for (int i = 0; i < waypoints.size() - 1; i++) {
                final LocalOrbitalFrame lofUpdated =
                                new LocalOrbitalFrame(targetOrbit.getFrame(), LOFType.LVLH_CCSDS, orbit.toOrbit(),
                                                      LOFType.LVLH_CCSDS.getName());

                // Define Current waypoint and next waypoint in QSW
                final TimeStampedFieldPVCoordinates<T> currentWaypoint = waypoints.get(i);
                final TimeStampedFieldPVCoordinates<T> nextWaypoint = waypoints.get(i + 1);

                // Define Date Detector to trigger the maneuver
                final FieldEventDetector<T> firstImpulseTrigger = new FieldDateDetector<>(currentWaypoint.getDate());

                // Compute the velocity after the maneuver to arrive at the following waypoint at the correct date using RendezVous.
                final FieldVector3D<T> velocityAfterManeuver =
                                (new FieldYamanakaAnkersenRendezVous<T>()).computeRendezVous(currentWaypoint,
                                                                                             nextWaypoint, lofUpdated,
                                                                                             orbit, targetPropagator)
                                                                          .getPvt1().getVelocity();
                // Compute the impulsion at the current waypoint to reach the next waypoint.
                final FieldVector3D<T> deltaV = velocityAfterManeuver.subtract(velocityBeforeManeuver);

                // Create the Yamanaka-Ankersen maneuver and add it to the list.
                final FieldRelativeManeuver<T> maneuver =
                                new FieldYamanakaAnkersenManeuver<>(firstImpulseTrigger, deltaV,
                                                                    (FieldYamanakaAnkersenProvider<T>) yaProvider);
                maneuvers.add(maneuver);

                // Create a Yamanaka-Ankersen provider to propagate to the next waypoint.
                final FieldYamanakaAnkersenProvider<T> yaProviderManeuver =
                                new FieldYamanakaAnkersenProvider<>(orbit,
                                                                    new TimeStampedFieldPVCoordinates<>(
                                                                                    currentWaypoint.getDate(),
                                                                                    new FieldPVCoordinates<>(
                                                                                                    currentWaypoint.getPosition(),
                                                                                                    velocityAfterManeuver)),
                                                                    "FieldProvider_" + i);
                targetPropagator.addAdditionalDataProvider(yaProviderManeuver);

                // Propagate the waypoint.
                final FieldSpacecraftState<T> propagated = targetPropagator.propagate(nextWaypoint.getDate());

                // Update the target orbit and the velocity before the next maneuver.
                final T trueAnomaly = ((FieldKeplerianOrbit<T>) OrbitType.KEPLERIAN.convertType(
                                propagated.getOrbit())).getTrueAnomaly();
                orbit = new FieldKeplerianOrbit<>(orbit.getA(), orbit.getE(), orbit.getI(),
                                                  orbit.getPerigeeArgument(),
                                                  orbit.getRightAscensionOfAscendingNode(),
                                                  trueAnomaly, PositionAngleType.TRUE,
                                                  orbit.getFrame(), nextWaypoint.getDate(),
                                                  orbit.getMu());
                velocityBeforeManeuver = new FieldVector3D<>(
                                Arrays.copyOfRange(yaProviderManeuver.getAdditionalData(propagated), 3, 6));
            }
            return maneuvers;
        }

        /**
         * Computes the Yamanaka-Ankersen based maneuvers of the teardrop relative orbit in LVLH_CCSDS Local Orbital Frame.
         *
         * <p>The injection point is the turn-around point of the teardrop (the round end).</p>
         * <p>All maneuvers happen at the pointy end of the teardrop.</p>
         *
         * @param waypoints List of the successive waypoints of the target.
         * @param yaProvider Yamanaka-Ankersen provider.
         * @return list of Yamanaka-Ankersen maneuvers.
         */
        @Override
        public List<RelativeManeuver> computeTeardropManeuvers(final List<TimeStampedPVCoordinates> waypoints,
                                                               final RelativeProvider yaProvider) {
            final List<RelativeManeuver> maneuvers = new ArrayList<>();

            // Creation of the maneuvers at the maneuver point of the teardrop.
            for (int i = 1; i < waypoints.size(); i++) {
                final TimeStampedPVCoordinates maneuverWaypoint = waypoints.get(i);
                final Vector3D maneuverVelocity = maneuverWaypoint.getVelocity();
                final EventDetector maneuverDate = new DateDetector(maneuverWaypoint.getDate());
                final TimeStampedPVCoordinates pvtBeforeMan =
                                new TimeStampedPVCoordinates(maneuverWaypoint.getDate(), maneuverWaypoint.getPosition(),
                                                             new Vector3D(maneuverVelocity.getX(),
                                                                          -maneuverVelocity.getY(),
                                                                          -maneuverVelocity.getZ()));
                final Vector3D deltaV = maneuverVelocity.subtract(pvtBeforeMan.getVelocity());
                final RelativeManeuver maneuver = new YamanakaAnkersenManeuver(maneuverDate, deltaV,
                                                                               (YamanakaAnkersenProvider) yaProvider);
                maneuvers.add(maneuver);
            }
            return maneuvers;
        }

        /**
         * Computes the Yamanaka-Ankersen based maneuvers of the teardrop relative orbit in LVLH_CCSDS Local Orbital Frame.
         *
         * <p>The injection point is the turn-around point of the teardrop (the round end).</p>
         * <p>All maneuvers happen at the pointy end of the teardrop.</p>
         *
         * @param waypoints List of the successive waypoints of the target.
         * @param yaProvider Yamanaka-Ankersen provider.
         * @param <T> field.
         * @return list of Yamanaka-Ankersen maneuvers.
         */
        @Override
        public <T extends CalculusFieldElement<T>> List<FieldRelativeManeuver<T>> computeTeardropManeuvers(
                        final List<TimeStampedFieldPVCoordinates<T>> waypoints,
                        final FieldRelativeProvider<T> yaProvider) {

            final List<FieldRelativeManeuver<T>> maneuvers = new ArrayList<>();

            // Creation of the maneuvers at the maneuver point of the teardrop.
            for (int i = 1; i < waypoints.size(); i++) {
                final TimeStampedFieldPVCoordinates<T> maneuverWaypoint = waypoints.get(i);
                final FieldVector3D<T> maneuverVelocity = maneuverWaypoint.getVelocity();
                final FieldEventDetector<T> maneuverDate = new FieldDateDetector<>(maneuverWaypoint.getDate());
                final TimeStampedFieldPVCoordinates<T> pvtBeforeMan =
                                new TimeStampedFieldPVCoordinates<>(maneuverWaypoint.getDate(),
                                                                    new FieldPVCoordinates<>(
                                                                                    maneuverWaypoint.getPosition(),
                                                                                    new FieldVector3D<>(
                                                                                                    maneuverVelocity.getX(),
                                                                                                    maneuverVelocity.getY()
                                                                                                                    .negate(),
                                                                                                    maneuverVelocity.getZ()
                                                                                                                    .negate())));
                final FieldVector3D<T> deltaV = maneuverVelocity.subtract(pvtBeforeMan.getVelocity());
                final FieldRelativeManeuver<T> maneuver =
                                new FieldYamanakaAnkersenManeuver<>(maneuverDate, deltaV,
                                                                    (FieldYamanakaAnkersenProvider<T>) yaProvider);
                maneuvers.add(maneuver);
            }
            return maneuvers;
        }
    }
}