AbstractRelativeManeuver.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.propagation.relative.maneuver;

import org.hipparchus.geometry.euclidean.threed.Vector3D;
import org.hipparchus.ode.events.Action;
import org.orekit.annotation.DefaultDataContext;
import org.orekit.propagation.SpacecraftState;
import org.orekit.propagation.events.DateDetector;
import org.orekit.propagation.events.EventDetector;
import org.orekit.propagation.events.handlers.EventHandler;
import org.orekit.propagation.relative.RelativeProvider;
import org.orekit.time.AbsoluteDate;
import org.orekit.time.TimeScale;
import org.orekit.time.TimeScalesFactory;
import org.orekit.utils.DataDictionary;
import org.orekit.utils.DoubleArrayDictionary;
import org.orekit.utils.TimeStampedPVCoordinates;

import java.util.Objects;

/**
 * Abstract class for implementing an impulse maneuver for a chaser spacecraft.
 *
 * @param <P> type of the relative motion provider
 * @author Romain Cuvillon
 * @since 14.0
 */
public abstract class AbstractRelativeManeuver<P extends RelativeProvider> implements RelativeManeuver {

    /** Trigger event. */
    private final EventDetector trigger;

    /** ΔV vector in the target's LOF. */
    private final Vector3D deltaV;

    /** True if the propagation is forward in time. */
    private boolean forward;

    /** Relative Provider. */
    private final P relativeProvider;

    /** Event Handler. */
    private final EventHandler handler;

    /**
     * Creates a new {@link AbstractRelativeManeuver} from an event detector, a ΔV vector, and a
     * {@link RelativeProvider}.
     *
     * @param trigger          Triggering event detector.
     * @param deltaV           ΔV vector in the local orbital frame of the theory used by the given
     *                         {@link RelativeProvider}.
     * @param relativeProvider Relative motion equations provider.
     */
    public AbstractRelativeManeuver(final EventDetector trigger, final Vector3D deltaV, final P relativeProvider) {
        this.trigger          = trigger;
        this.deltaV           = deltaV;
        this.relativeProvider = relativeProvider;
        this.handler          = new Handler();
    }

    /** {@inheritDoc} */
    @Override
    public void init(final SpacecraftState s0, final AbsoluteDate t) {
        getDetector().init(s0, t);
        forward = t.durationFrom(s0.getDate()) >= 0;
    }

    /** {@inheritDoc} */
    @Override
    public EventDetector getDetector() {
        return trigger;
    }

    /** {@inheritDoc} */
    @Override
    public EventHandler getHandler() {
        return handler;
    }

    /** String representation in UTC.
     *
     * @return string representation with date in UTC timescale
     */
    @Override
    @DefaultDataContext
    public String toString() {
        return toString(TimeScalesFactory.getUTC());
    }

    /**
     * String representation with timescale.
     *
     * @param timeScale timescale to use for date formatting
     * @return string representation with timescale
     */
    public String toString(final TimeScale timeScale) {
        final String triggerString =
                        (getDetector() instanceof DateDetector) ?
                        ((DateDetector) getDetector()).getDate().toString(timeScale) :
                        getDetector().toString();
        return "[trigger = " + triggerString + " ; ΔV = " + deltaV + " ; |ΔV| = " + deltaV.getNorm() + "]";
    }

    /** Getter for the forward boolean.
     * @return forward boolean.
     */
    public boolean getForward() {
        return forward;
    }

    /** {@inheritDoc} */
    @Override
    public Vector3D getDeltaV() {
        return deltaV;
    }

    /** {@inheritDoc} */
    @Override
    public P getRelativeProvider() {
        return this.relativeProvider;
    }

    /** Local handler.
     * <p>
     * Apply the maneuver to the chaser S/C in the additional state.
     * </p>
     */
    private static class Handler implements EventHandler {

        /** {@inheritDoc} */
        public Action eventOccurred(final SpacecraftState s, final EventDetector detector, final boolean increasing) {
            final AbstractRelativeManeuver<?> im = (AbstractRelativeManeuver<?>) detector;
            final Action underlyingAction = im.getDetector().getHandler().eventOccurred(s, im.getDetector(), increasing);
            return (underlyingAction == Action.STOP) ? Action.RESET_STATE : Action.CONTINUE;
        }

        /** {@inheritDoc} */
        @Override
        public SpacecraftState resetState(final EventDetector detector, final SpacecraftState oldState) {
            final AbstractRelativeManeuver<?> maneuver = (AbstractRelativeManeuver<?>) detector;

            // Get velocity increment from maneuver
            final Vector3D deltaV = maneuver.getDeltaV();
            final double sign = maneuver.getForward() ? +1 : -1;

            // Apply increment to velocity of the chaser
            final double[] chaserPV = oldState.getAdditionalState(maneuver.getRelativeProvider().getName());

            chaserPV[3] += sign * deltaV.getX();
            chaserPV[4] += sign * deltaV.getY();
            chaserPV[5] += sign * deltaV.getZ();

            // Reset the equations provider with the new chaser state
            maneuver.getRelativeProvider().setInitialChaserPVTLof(
                            new TimeStampedPVCoordinates(oldState.getDate(),
                                                         new Vector3D(chaserPV[0],
                                                                      chaserPV[1],
                                                                      chaserPV[2]),
                                                         new Vector3D(chaserPV[3],
                                                                      chaserPV[4],
                                                                      chaserPV[5])));

            // Reset the TrueAnomaly of the spacecraft to the current true anomaly if YA is used / do nothing if CW.
            maneuver.resetTrueAnomalyAtManeuver(oldState.getOrbit());

            // Pack everything in a new state
            SpacecraftState newState = new SpacecraftState(oldState.getOrbit(), oldState.getAttitude()).withMass(
                            oldState.getMass());

            // Add additional equations
            for (final DataDictionary.Entry entry : oldState.getAdditionalDataValues().getData()) {
                if (!Objects.equals(entry.getKey(), maneuver.getRelativeProvider().getName())) {
                    newState = newState.addAdditionalData(entry.getKey(), entry.getValue());
                } else {
                    // Use modified chaser PV if the additional state is the currently used YA provider
                    newState = newState.addAdditionalData(maneuver.getRelativeProvider().getName(), chaserPV);
                }
            }

            // Add additional equations' derivatives
            for (final DoubleArrayDictionary.Entry entry : oldState.getAdditionalStatesDerivatives().getData()) {
                newState = newState.addAdditionalStateDerivative(entry.getKey(), entry.getValue());
            }

            return newState;
        }
    }
}