RPO.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.Field;
import org.hipparchus.geometry.euclidean.threed.FieldRotation;
import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
import org.hipparchus.geometry.euclidean.threed.Rotation;
import org.hipparchus.geometry.euclidean.threed.RotationConvention;
import org.hipparchus.geometry.euclidean.threed.Vector3D;
import org.hipparchus.util.FastMath;
import org.hipparchus.util.FieldSinCos;
import org.hipparchus.util.SinCos;
import org.orekit.forces.maneuvers.FieldImpulseManeuver;
import org.orekit.forces.maneuvers.ImpulseManeuver;
import org.orekit.frames.FieldTransform;
import org.orekit.frames.LOFType;
import org.orekit.frames.Transform;
import org.orekit.orbits.FieldOrbit;
import org.orekit.orbits.Orbit;
import org.orekit.propagation.analytical.FieldKeplerianPropagator;
import org.orekit.propagation.analytical.KeplerianPropagator;
import org.orekit.propagation.events.DateDetector;
import org.orekit.propagation.events.FieldDateDetector;
import org.orekit.propagation.relative.FieldRelativeProvider;
import org.orekit.propagation.relative.RelativeProvider;
import org.orekit.propagation.relative.maneuver.FieldRelativeManeuver;
import org.orekit.propagation.relative.maneuver.RelativeManeuver;
import org.orekit.time.AbsoluteDate;
import org.orekit.time.FieldAbsoluteDate;
import org.orekit.utils.FieldPVCoordinates;
import org.orekit.utils.PVCoordinates;
import org.orekit.utils.TimeStampedFieldPVCoordinates;
import org.orekit.utils.TimeStampedPVCoordinates;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * Interface used for the computation of the waypoints of the following relative maneuver sequences:
 * <ul>
 *     <li>Linear transfer,</li>
 *     <li>Forced Circular Motion,</li>
 *     <li>Natural Circumnavigation.</li>
 * </ul>
 * The computeWaypoints are default methods shared by the different models.
 * The only differences between two models in the computation of these waypoints come from the local orbital frame
 * used. This is handled by the getters defined in enum:{@link RPOModel RPOModel }.
 *
 * @author Romain Cuvillon
 * @since 14.0
 */
public interface RPO {

    /**
     * Get the radial unit vector direction of the target local orbital frame.
     *
     * @return RBar direction
     */
    Vector3D getRBarDirection();

    /**
     * Get the velocity unit vector direction of the target local orbital frame.
     *
     * @return VBar direction
     */
    Vector3D getVBarDirection();

    /**
     * Get the OutOfPlane unit vector direction of the target local orbital frame.
     *
     * @return OutOfPlane direction
     */
    Vector3D getOutOfPlaneDirection();

    /**
     * Get the LOFType used by the RPOModel.
     *
     * @return LOFType used by the RPOModel.
     */
    LOFType getLOFType();

    /**
     * Get the rotation for the waypoint in the local orbital frame to be used in the
     * computeForcedCircularMotionWaypoints method.
     *
     * @param inclination inclination of the relative orbit.
     * @param raan        raan of the relative orbit.
     * @return rotation to be applied to the waypoints.
     */
    default Rotation rotationInLof(final double inclination, final double raan) {

        final Vector3D vBar = getVBarDirection();
        final Vector3D localVerticalDirection = getOutOfPlaneDirection();

        // Create rotation for the waypoints.
        final Rotation inclinationRotation =
                        new Rotation(vBar.scalarMultiply(-1), inclination, RotationConvention.VECTOR_OPERATOR);
        final Rotation raanRotation = new Rotation(localVerticalDirection, raan, RotationConvention.VECTOR_OPERATOR);
        return raanRotation.compose(inclinationRotation, RotationConvention.VECTOR_OPERATOR);
    }

    /**
     * Get the rotation for the waypoint in the local orbital frame to be used in the
     * computeForcedCircularMotionWaypoints method.
     *
     * @param inclination inclination of the relative orbit.
     * @param raan        raan of the relative orbit.
     * @param <T>         type of the field elements.
     * @return rotation to be applied to the waypoints.
     */
    default <T extends CalculusFieldElement<T>> FieldRotation<T> rotationInLof(final T inclination, final T raan) {

        final FieldVector3D<T> vBar = new FieldVector3D<>(inclination.getField(), getVBarDirection());
        final FieldVector3D<T> localVerticalDirection =
                        new FieldVector3D<>(inclination.getField(), getOutOfPlaneDirection());

        // Create rotation for the waypoints.
        final FieldRotation<T> inclinationRotation =
                        new FieldRotation<>(vBar.scalarMultiply(-1), inclination, RotationConvention.VECTOR_OPERATOR);
        final FieldRotation<T> raanRotation =
                        new FieldRotation<>(localVerticalDirection, raan, RotationConvention.VECTOR_OPERATOR);
        return raanRotation.compose(inclinationRotation, RotationConvention.VECTOR_OPERATOR);
    }

    /**
     * Computes the coordinates of a point on inPlane circle in the associated LocalOrbitalFrame.
     *
     * @param radius radius of the circle.
     * @param angle  angle of the point relative to the inPlane axis of the LocalOrbitalFrame.
     * @return position of the circle point in the related LocalOrbitalFrame.
     */
    default Vector3D circularPosition(final double radius, final double angle) {

        final Vector3D rBar = getRBarDirection();
        final Vector3D vBar = getVBarDirection();
        return vBar.scalarMultiply(-radius * FastMath.cos(angle))
                   .add(rBar.scalarMultiply(radius * FastMath.sin(angle)));
    }

    /**
     * Computes the coordinates of a point on inPlane circle in the associated LocalOrbitalFrame.
     *
     * @param radius radius of the circle.
     * @param angle  angle of the point relative to the inPlane axis of the LocalOrbitalFrame.
     * @param <T>    type of the field elements.
     * @return position of the circle point in the related LocalOrbitalFrame.
     */
    default <T extends CalculusFieldElement<T>> FieldVector3D<T> circularPosition(final T radius, final T angle) {

        final FieldVector3D<T> rBar = new FieldVector3D<>(radius.getField(), getRBarDirection());
        final FieldVector3D<T> vBar = new FieldVector3D<>(radius.getField(), getVBarDirection());
        return vBar.scalarMultiply(radius.negate().multiply(angle.cos()))
                   .add(rBar.scalarMultiply(radius.multiply(angle.sin())));
    }

    /**
     * Computes the waypoints along the linear path.
     *
     * @param initialPVT     initial TimeStampedPVCoordinates of the chaser.
     * @param finalPVT       last waypoint on the line path.
     * @param numberOfPoints number of waypoints along the linear path (including start and end points).
     * @return list of waypoints along the linear path.
     */
    default List<TimeStampedPVCoordinates> computeForcedLinearWaypoints(final TimeStampedPVCoordinates initialPVT,
                                                                        final TimeStampedPVCoordinates finalPVT,
                                                                        final int numberOfPoints) {
        // Create a list of waypoints.
        final List<TimeStampedPVCoordinates> waypointsList = new ArrayList<>();

        // Add the initial TimeStampedPVCoordinates of the chaser to the list.
        waypointsList.add(initialPVT);

        if (numberOfPoints > 1) {
            final double timeStep = finalPVT.getDate().durationFrom(initialPVT.getDate()) / (numberOfPoints - 1);

            // Compute the waypoints along the linear path.
            for (int i = 1; i < numberOfPoints - 1; i++) {

                final Vector3D hopLength = finalPVT.getPosition().subtract(initialPVT.getPosition())
                                                   .scalarMultiply(1. / (numberOfPoints - 1));
                final TimeStampedPVCoordinates waypoint =
                                new TimeStampedPVCoordinates(initialPVT.getDate().shiftedBy(i * timeStep),
                                                             initialPVT.getPosition().add(hopLength.scalarMultiply(i)),
                                                             Vector3D.ZERO);
                waypointsList.add(waypoint);
            }
        }

        waypointsList.add(finalPVT);
        return waypointsList;
    }

    /**
     * Computes the waypoints to perform a forced circular orbit.
     *
     * @param startDate           Start date of the first waypoint of the forced circular motion.
     * @param centerOffset        Offset of the forced circular orbit center relative to the target. If no Offset, the
     *                            center is the target position.
     * @param radius              Radius of the forced circular orbit.
     * @param inclination         Inclination of the relative orbit.
     * @param raan                Relative Longitude of ascending node of the relative circular orbit in radians. If
     *                            raan = 0, the line of apsides is along the positive RBar direction (X+ axis in QSW
     *                            frame, Z- in LVLH CCSDS frame).
     * @param orbitDuration       Relative orbital Period, the time it takes to the chaser to perform a complete force
     *                            circular orbit.
     * @param pointsOnOrbit       Number of waypoints along the circular orbit.
     * @param numberOfRevolutions Number of revolutions.
     * @param startAngle          Angle of the first waypoints.
     * @param retrograde          Make the relative circular motion retrograde (clockwise viewed from the target's
     *                            angular momentum direction).
     * @return list of waypoints.
     */
    default List<TimeStampedPVCoordinates> computeForcedCircularMotionWaypoints(final AbsoluteDate startDate,
                                                                                final Vector3D centerOffset,
                                                                                final double radius,
                                                                                final double inclination,
                                                                                final double raan,
                                                                                final double orbitDuration,
                                                                                final int pointsOnOrbit,
                                                                                final int numberOfRevolutions,
                                                                                final double startAngle,
                                                                                final boolean retrograde) {
        final double angleStep = 2 * FastMath.PI / pointsOnOrbit;
        final double timeStep = orbitDuration / pointsOnOrbit;
        final List<PVCoordinates> points = new ArrayList<>();

        // Compute the rotation depending on the set of equations used.
        final Rotation rotation = rotationInLof(inclination, raan);
        for (int i = 0; i < pointsOnOrbit; i++) {

            // Computes the point position in plane, accounting for injection angle. (0° aligned with vBar)
            final Vector3D position = circularPosition(radius, i * angleStep + startAngle);

            // Rotate the point using inclination and raan and add orbit center offset
            final Vector3D finalPosition = rotation.applyTo(position).add(centerOffset);
            points.add(new PVCoordinates(finalPosition, Vector3D.ZERO));
        }

        // Add the first waypoint of the circle to the end in order to close the path.
        points.add(points.getFirst());
        if (retrograde) {
            Collections.reverse(points);
        }
        final List<TimeStampedPVCoordinates> waypoints = new ArrayList<>();

        // Reorder the waypoints on the circular path. The first waypoint is the previous closest waypoint.
        for (int i = 0; i < points.size(); i++) {
            final TimeStampedPVCoordinates pvt =
                            new TimeStampedPVCoordinates(startDate.shiftedBy(timeStep * i), points.get(i));
            waypoints.add(pvt);
        }

        // Add the waypoints with modified date for the successive orbits.
        if (numberOfRevolutions > 1) {

            // Extract the waypoints circle excluding the first to avoid having twice this point at the beginning of each revolution.
            final List<TimeStampedPVCoordinates> revolutionWaypoints = waypoints.subList(1, waypoints.size());
            final List<TimeStampedPVCoordinates> revPoints = new ArrayList<>();
            for (int i = 1; i < numberOfRevolutions; i++) {
                for (TimeStampedPVCoordinates revolutionWaypoint : revolutionWaypoints) {
                    final AbsoluteDate waypointDate = revolutionWaypoint.getDate().shiftedBy(i * orbitDuration);
                    final TimeStampedPVCoordinates waypoint =
                                    new TimeStampedPVCoordinates(waypointDate, revolutionWaypoint.getPosition(),
                                                                 revolutionWaypoint.getVelocity());
                    revPoints.add(waypoint);
                }
            }
            waypoints.addAll(revPoints);
        }
        return waypoints;
    }

    /**
     * Computes the injection PVT for a natural circumnavigation around a target in a <b>circular</b> orbit.
     * <p>The inclination must be in the range ]-90° ; +90°[.</p>
     * <p>For i = ±60°, the circumnavigation orbit is circular. Otherwise, it is an ellipse.</p>
     *
     * @param startDate         Date of the injection into the natural circumnavigation orbit.
     * @param distanceAlongVBar Distance between the target and the point of the relative orbit lying on vBar.
     * @param inclination       Inclination of the relative orbit.
     * @param targetMeanMotion  Mean motion of the target orbit.
     * @return injection PVT (i.e. after the injection maneuver).
     */
    default TimeStampedPVCoordinates computeNaturalCircumnavigationInjectionCircular(final AbsoluteDate startDate,
                                                                                     final double distanceAlongVBar,
                                                                                     final double inclination,
                                                                                     final double targetMeanMotion) {
        final Vector3D rBar = getRBarDirection();
        final Vector3D vBar = getVBarDirection();
        final TimeStampedPVCoordinates injectionPoint =
                        new TimeStampedPVCoordinates(startDate, vBar.scalarMultiply(-distanceAlongVBar), Vector3D.ZERO);
        final double deltaV = distanceAlongVBar / 2. * targetMeanMotion;

        // Create the impulse vector along rBar to perform the NaturalCircumnavigation.
        final Vector3D delta = rBar.scalarMultiply(-deltaV);
        Vector3D velocityAfterMan = injectionPoint.getVelocity().add(delta);
        if (inclination != 0.0) {
            // Compute sine and cosine of the relative inclination
            final SinCos sci = FastMath.sinCos(inclination);

            // Compute the rotation of the inclination around the vBar axis of the target's LOF
            // The scale factor 1/cos(i) ensures that the starting distance is the one specified when the object was constructed
            final Rotation rotation = new Rotation(vBar, inclination, RotationConvention.VECTOR_OPERATOR);
            velocityAfterMan = rotation.applyTo(velocityAfterMan).scalarMultiply(1.0 / sci.cos());
        }
        return new TimeStampedPVCoordinates(startDate, injectionPoint.getPosition(), velocityAfterMan);
    }

    /**
     * Computes teardrop waypoints.
     *
     * <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        orbit of the target.
     * @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.
     */
    List<TimeStampedPVCoordinates> computeTeardropWaypoints(AbsoluteDate injectionDate, Orbit targetOrbit,
                                                            double turnAroundDistance, double maneuverDistance,
                                                            int numberOfTeardrops);

    /**
     * Computes the waypoints along the linear path.
     *
     * @param initialPVT     initial TimeStampedPVCoordinates of the chaser.
     * @param finalPVT       last waypoint on the line path.
     * @param numberOfPoints number of waypoints along the linear path (including start and end points).
     * @param <T>            type of the field elements.
     * @return list of waypoints along the linear path.
     */
    default <T extends CalculusFieldElement<T>> List<TimeStampedFieldPVCoordinates<T>> computeForcedLinearWaypoints(
                    final TimeStampedFieldPVCoordinates<T> initialPVT, final TimeStampedFieldPVCoordinates<T> finalPVT,
                    final int numberOfPoints) {
        // Create a list of waypoints.
        final List<TimeStampedFieldPVCoordinates<T>> waypointsList = new ArrayList<>();

        // Add the initial TimeStampedPVCoordinates of the chaser to the list.
        waypointsList.add(initialPVT);

        if (numberOfPoints > 1) {
            final T timeStep = (finalPVT.getDate().durationFrom(initialPVT.getDate())).divide(numberOfPoints - 1);
            final FieldVector3D<T> zeroVector = FieldVector3D.getZero(finalPVT.getDate().getField());

            // Compute the waypoints along the linear path.
            for (int i = 1; i < numberOfPoints - 1; i++) {

                final FieldVector3D<T> hopLength = finalPVT.getPosition().subtract(initialPVT.getPosition())
                                                           .scalarMultiply(1. / (numberOfPoints - 1));
                final TimeStampedFieldPVCoordinates<T> waypoint =
                                new TimeStampedFieldPVCoordinates<>(initialPVT.getDate().shiftedBy(timeStep.multiply(i)),
                                                                    new FieldPVCoordinates<>(
                                                                                    initialPVT.getPosition()
                                                                                              .add(hopLength.scalarMultiply(i)),
                                                                                    zeroVector));
                waypointsList.add(waypoint);
            }
        }


        waypointsList.add(finalPVT);
        return waypointsList;
    }

    /**
     * Computes the waypoints to perform a forced circular orbit.
     *
     * @param startDate           Start date of the first waypoint of the forced circular motion.
     * @param centerOffset        Offset of the forced circular orbit center relative to the target. If no Offset, the
     *                            center is the target position.
     * @param radius              Radius of the forced circular orbit.
     * @param inclination         Inclination of the relative orbit.
     * @param raan                Relative Longitude of ascending node of the relative circular orbit in radians. If
     *                            raan = 0, the line of apsides is along the positive RBar direction (X+ axis in QSW
     *                            frame, Z- in LVLH CCSDS frame).
     * @param orbitDuration       Relative orbital Period, the time it takes to the chaser to perform a complete force
     *                            circular orbit.
     * @param pointsOnOrbit       Number of waypoints along the circular orbit.
     * @param numberOfRevolutions Number of revolutions.
     * @param startAngle          Angle of the first waypoints.
     * @param retrograde          Make the relative circular motion retrograde (clockwise viewed from the target's
     *                            angular momentum direction).
     * @param <T>                 type of the field elements.
     * @return list of waypoints.
     */
    default <T extends CalculusFieldElement<T>> List<TimeStampedFieldPVCoordinates<T>> computeForcedCircularMotionWaypoints(
                    final FieldAbsoluteDate<T> startDate, final FieldVector3D<T> centerOffset, final T radius,
                    final T inclination, final T raan, final T orbitDuration, final int pointsOnOrbit,
                    final int numberOfRevolutions, final T startAngle, final boolean retrograde) {

        final T angleStep = radius.getPi().multiply(2).divide(pointsOnOrbit);
        final T timeStep = orbitDuration.divide(pointsOnOrbit);
        final List<FieldPVCoordinates<T>> points = new ArrayList<>();

        // Compute the rotation depending on the set of equations used.
        final FieldRotation<T> rotation = rotationInLof(inclination, raan);
        for (int i = 0; i < pointsOnOrbit; i++) {

            // Computes the point position in plane, accounting for injection angle. (0° aligned with vBar)
            final FieldVector3D<T> position = circularPosition(radius, angleStep.multiply(i).add(startAngle));

            // Rotate the point using inclination and raan and add orbit center offset
            final FieldVector3D<T> finalPosition = rotation.applyTo(position).add(centerOffset);
            points.add(new FieldPVCoordinates<>(finalPosition, FieldVector3D.getZero(startDate.getDate().getField())));
        }

        // Add the first waypoint of the circle to the end in order to close the path.
        points.add(points.getFirst());
        if (retrograde) {
            Collections.reverse(points);
        }
        final List<TimeStampedFieldPVCoordinates<T>> waypoints = new ArrayList<>();

        // Reorder the waypoints on the circular path. The first waypoint is the previous closest waypoint.
        for (int i = 0; i < points.size(); i++) {
            final TimeStampedFieldPVCoordinates<T> pvt =
                            new TimeStampedFieldPVCoordinates<>(startDate.shiftedBy(timeStep.multiply(i)),
                                                                points.get(i));
            waypoints.add(pvt);
        }

        // Add the waypoints with modified date for the successive orbits.
        if (numberOfRevolutions > 1) {

            // Extract the waypoints circle excluding the first to avoid having twice this point at the beginning of each revolution.
            final List<TimeStampedFieldPVCoordinates<T>> revolutionWaypoints = waypoints.subList(1, waypoints.size());
            final List<TimeStampedFieldPVCoordinates<T>> revPoints = new ArrayList<>();
            for (int i = 1; i < numberOfRevolutions; i++) {
                for (TimeStampedFieldPVCoordinates<T> revolutionWaypoint : revolutionWaypoints) {
                    final FieldAbsoluteDate<T> waypointDate =
                                    revolutionWaypoint.getDate().shiftedBy(orbitDuration.multiply(i));
                    final TimeStampedFieldPVCoordinates<T> waypoint = new TimeStampedFieldPVCoordinates<>(waypointDate,
                                                                                                          new FieldPVCoordinates<>(
                                                                                                                          revolutionWaypoint.getPosition(),
                                                                                                                          revolutionWaypoint.getVelocity()));
                    revPoints.add(waypoint);
                }
            }
            waypoints.addAll(revPoints);
        }
        return waypoints;

    }

    /**
     * Computes the injection PVT for a natural circumnavigation around a target in a <b>circular</b> orbit.
     *
     * <p>The inclination must be in the range ]-90° ; +90°[.</p>
     * <p>For i = ±60°, the circumnavigation orbit is circular. Otherwise, it is an ellipse.</p>
     *
     * @param startDate         Date of the injection into the natural circumnavigation orbit.
     * @param distanceAlongVBar Distance between the target and the point of the relative orbit lying on vBar.
     * @param inclination       Inclination of the relative orbit.
     * @param targetMeanMotion  Mean motion of the target orbit.
     * @param <T>               type of the field elements.
     * @return injection PVT (i.e. after the injection maneuver).
     */
    default <T extends CalculusFieldElement<T>> TimeStampedFieldPVCoordinates<T> computeNaturalCircumnavigationInjectionCircular(
                    final FieldAbsoluteDate<T> startDate, final T distanceAlongVBar, final T inclination,
                    final T targetMeanMotion) {

        final Field<T> field = startDate.getField();
        final FieldVector3D<T> rBar = new FieldVector3D<>(field, getRBarDirection());
        final FieldVector3D<T> vBar = new FieldVector3D<>(field, getVBarDirection());
        final TimeStampedFieldPVCoordinates<T> injectionPoint =
                        new TimeStampedFieldPVCoordinates<>(startDate,
                                                            new FieldPVCoordinates<>(
                                                                            vBar.scalarMultiply(
                                                                                            distanceAlongVBar.negate()),
                                                                            FieldVector3D.getZero(field)));
        final T deltaV = distanceAlongVBar.divide(2.).multiply(targetMeanMotion);

        // Create the impulse vector along rBar to perform the NaturalCircumnavigation.
        final FieldVector3D<T> delta = rBar.scalarMultiply(deltaV.negate());
        FieldVector3D<T> velocityAfterMan = injectionPoint.getVelocity().add(delta);
        if (inclination.getReal() != 0) {
            final FieldSinCos<T> sci = inclination.sinCos();
            final FieldRotation<T> rotation =
                            new FieldRotation<>(vBar, inclination, RotationConvention.VECTOR_OPERATOR);

            // Compute the rotation of the inclination around the vBar axis of the target's LOF
            // The scale factor 1/cos(i) ensures that the starting distance is the one specified when the object was constructed
            velocityAfterMan = rotation.applyTo(velocityAfterMan).scalarMultiply(field.getOne().divide(sci.cos()));
        }
        return new TimeStampedFieldPVCoordinates<>(startDate, new FieldPVCoordinates<>(injectionPoint.getPosition(),
                                                                                       velocityAfterMan));
    }

    /**
     * Computes teardrop waypoints.
     *
     * <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        orbit of the target.
     * @param turnAroundDistance Turn-around distance.
     *                           <br> This is the "round" end of the orbit.
     *                           <br> 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.
     *                           <br> This is the "pointy" end of the orbit.
     *                           <br> 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.
     */
    <T extends CalculusFieldElement<T>> List<TimeStampedFieldPVCoordinates<T>> computeTeardropWaypoints(
                    FieldAbsoluteDate<T> injectionDate, FieldOrbit<T> targetOrbit, T turnAroundDistance,
                    T maneuverDistance, int numberOfTeardrops);

    /**
     * Compute relative maneuvers to realize a forced trajectory defined by the waypoints
     * (ForcedLinear/ForcedCircular).
     *
     * @param waypoints       Waypoints of the trajectory in the correct LocalOrbitalFrame.
     * @param initialVelocity Initial velocity in the local orbital frame.
     * @param targetOrbit     Orbit of the target.
     * @param provider        Relative motion provider.
     * @return list of relative maneuvers in the chosen RPOModel local orbital frame.
     */
    List<RelativeManeuver> computeForcedManeuvers(List<TimeStampedPVCoordinates> waypoints, Vector3D initialVelocity,
                                                  Orbit targetOrbit, RelativeProvider provider);

    /**
     * Compute relative maneuvers to realize a forced trajectory defined by the waypoints
     * (ForcedLinear/ForcedCircular).
     *
     * @param waypoints       Waypoints of the trajectory in the correct LocalOrbitalFrame.
     * @param initialVelocity Initial velocity in the local orbital frame.
     * @param targetOrbit     Orbit of the target.
     * @param provider        Relative motion provider.
     * @param <T>             field.
     * @return list of relative maneuvers in the chosen RPOModel local orbital frame.
     */
    <T extends CalculusFieldElement<T>> List<FieldRelativeManeuver<T>> computeForcedManeuvers(
                    List<TimeStampedFieldPVCoordinates<T>> waypoints, FieldVector3D<T> initialVelocity,
                    FieldOrbit<T> targetOrbit, FieldRelativeProvider<T> provider);

    /**
     * Convert the relative maneuvers into Impulse maneuvers in the targetOrbit frame.
     * <p><b>Warning</b>: EventDetector of the maneuvers must be DateDetector.</p>
     *
     * @param maneuvers   Relative maneuvers.
     * @param targetOrbit orbit of the target.
     * @param isp         specific impulse of the chaser.
     * @return list of impulse maneuvers in target's orbit frame.
     */
    default List<ImpulseManeuver> convertToImpulseManeuver(final List<RelativeManeuver> maneuvers,
                                                           final Orbit targetOrbit, final double isp) {

        final List<ImpulseManeuver> impulseManeuvers = new ArrayList<>();
        final KeplerianPropagator targetPropagator = new KeplerianPropagator(targetOrbit);

        for (RelativeManeuver maneuver : maneuvers) {
            final AbsoluteDate maneuverDate = ((DateDetector) maneuver.getDetector()).getDate();
            final PVCoordinates pvTarget = targetPropagator.propagate(maneuverDate).getPVCoordinates();
            final Transform lofToInertial = getLOFType().transformFromInertial(maneuverDate, pvTarget).getInverse();
            final Vector3D deltaVInertial = lofToInertial.transformVector(maneuver.getDeltaV());
            impulseManeuvers.add(new ImpulseManeuver(maneuver.getDetector(), deltaVInertial, isp));
        }
        return impulseManeuvers;
    }

    /**
     * Convert the relative maneuvers into Impulse maneuvers in the targetOrbit frame.
     * <p><b>Warning</b>: EventDetector of the maneuvers must be DateDetector.</p>
     *
     * @param maneuvers   Relative maneuvers.
     * @param targetOrbit orbit of the target.
     * @param isp         specific impulse of the chaser.
     * @param <T>         field.
     * @return list of impulse maneuvers in target's orbit frame.
     */
    default <T extends CalculusFieldElement<T>> List<FieldImpulseManeuver<T>> convertToImpulseManeuver(
                    final List<FieldRelativeManeuver<T>> maneuvers, final FieldOrbit<T> targetOrbit, final T isp) {

        final List<FieldImpulseManeuver<T>> impulseManeuvers = new ArrayList<>();
        final FieldKeplerianPropagator<T> targetPropagator = new FieldKeplerianPropagator<>(targetOrbit);

        for (FieldRelativeManeuver<T> maneuver : maneuvers) {
            final FieldAbsoluteDate<T> maneuverDate = ((FieldDateDetector<T>) maneuver.getDetector()).getDate();
            final FieldPVCoordinates<T> pvTarget = targetPropagator.propagate(maneuverDate).getPVCoordinates();
            final FieldTransform<T> lofToInertial =
                            getLOFType().transformFromInertial(maneuverDate, pvTarget).getInverse();
            final FieldVector3D<T> deltaVInertial = lofToInertial.transformVector(maneuver.getDeltaV());
            impulseManeuvers.add(new FieldImpulseManeuver<>(maneuver.getDetector(), deltaVInertial, isp));
        }
        return impulseManeuvers;
    }

    /**
     * Computes the relative maneuvers of the teardrop relative orbit in the RPOModel 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 relativeProvider relative motion provider of the RPOModel.
     * @return list of relative maneuvers.
     */
    List<RelativeManeuver> computeTeardropManeuvers(List<TimeStampedPVCoordinates> waypoints,
                                                    RelativeProvider relativeProvider);

    /**
     * Computes the relative maneuvers of the teardrop relative orbit in the RPOModel 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 relativeProvider relative motion provider of the RPOModel.
     * @param <T>              field.
     * @return list of relative maneuvers.
     */
    <T extends CalculusFieldElement<T>> List<FieldRelativeManeuver<T>> computeTeardropManeuvers(
                    List<TimeStampedFieldPVCoordinates<T>> waypoints, FieldRelativeProvider<T> relativeProvider);

}