FrameFacade.java

/* Copyright 2002-2024 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.files.ccsds.definitions;

import org.hipparchus.geometry.euclidean.threed.Rotation;
import org.orekit.data.DataContext;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.frames.Frame;
import org.orekit.frames.LOFType;
import org.orekit.frames.Transform;
import org.orekit.time.AbsoluteDate;
import org.orekit.utils.IERSConventions;
import org.orekit.utils.PVCoordinatesProvider;

/** Facade in front of several frames types in CCSDS messages.
 *
 * @author Luc Maisonobe
 * @author Vincent Cucchietti
 * @since 11.0
 */
public class FrameFacade {

    /** Reference to node in Orekit frames tree. */
    private final Frame frame;

    /** Reference to celestial body centered frame. */
    private final CelestialBodyFrame celestialBodyFrame;

    /** Reference to orbit-relative frame. */
    private final OrbitRelativeFrame orbitRelativeFrame;

    /** Reference to spacecraft body frame. */
    private final SpacecraftBodyFrame spacecraftBodyFrame;

    /** Name of the frame. */
    private final String name;

    /**
     * Simple constructor.
     * <p>
     * At most one of {@code celestialBodyFrame}, {@code orbitRelativeFrame} or {@code spacecraftBodyFrame} may be non
     * null. They may all be null if frame is unknown, in which case only the name will be available.
     * </p>
     *
     * @param frame reference to node in Orekit frames tree (may be null)
     * @param celestialBodyFrame reference to celestial body centered frame (may be null)
     * @param orbitRelativeFrame reference to orbit-relative frame (may be null)
     * @param spacecraftBodyFrame reference to spacecraft body frame (may be null)
     * @param name name of the frame
     */
    public FrameFacade(final Frame frame,
                       final CelestialBodyFrame celestialBodyFrame,
                       final OrbitRelativeFrame orbitRelativeFrame,
                       final SpacecraftBodyFrame spacecraftBodyFrame,
                       final String name) {
        this.frame = frame;
        this.celestialBodyFrame = celestialBodyFrame;
        this.orbitRelativeFrame = orbitRelativeFrame;
        this.spacecraftBodyFrame = spacecraftBodyFrame;
        this.name = name;
    }

    /**
     * Get the associated frame tree node.
     *
     * @return associated frame tree node, or null if none exists
     */
    public Frame asFrame() {
        return frame;
    }

    /**
     * Get the associated {@link CelestialBodyFrame celestial body frame}.
     *
     * @return associated celestial body frame, or null if frame is associated to a
     * {@link #asOrbitRelativeFrame() orbit}, a {@link #asSpacecraftBodyFrame spacecraft} or is not supported
     */
    public CelestialBodyFrame asCelestialBodyFrame() {
        return celestialBodyFrame;
    }

    /**
     * Get the associated {@link OrbitRelativeFrame orbit relative frame}.
     *
     * @return associated orbit relative frame, or null if frame is associated to a
     * {@link #asCelestialBodyFrame() celestial body}, a {@link #asSpacecraftBodyFrame spacecraft} or is not supported
     */
    public OrbitRelativeFrame asOrbitRelativeFrame() {
        return orbitRelativeFrame;
    }

    /**
     * Get the associated {@link SpacecraftBodyFrame spacecraft body frame}.
     *
     * @return associated spacecraft body frame, or null if frame is associated to a
     * {@link #asCelestialBodyFrame() celestial body}, an {@link #asOrbitRelativeFrame orbit} or is not supported
     */
    public SpacecraftBodyFrame asSpacecraftBodyFrame() {
        return spacecraftBodyFrame;
    }

    /**
     * Get the CCSDS name for the frame.
     *
     * @return CCSDS name
     */
    public String getName() {
        return name;
    }

    /**
     * Map an Orekit frame to a CCSDS frame facade.
     *
     * @param frame a reference frame.
     * @return the CCSDS frame corresponding to the Orekit frame
     */
    public static FrameFacade map(final Frame frame) {
        final CelestialBodyFrame cbf = CelestialBodyFrame.map(frame);
        return new FrameFacade(frame, cbf, null, null, cbf.getName());
    }

    /**
     * Simple constructor.
     *
     * @param name name of the frame
     * @param conventions IERS conventions to use
     * @param simpleEOP if true, tidal effects are ignored when interpolating EOP
     * @param dataContext to use when creating the frame
     * @param allowCelestial if true, {@link CelestialBodyFrame} are allowed
     * @param allowOrbit if true, {@link OrbitRelativeFrame} are allowed
     * @param allowSpacecraft if true, {@link SpacecraftBodyFrame} are allowed
     * @return frame facade corresponding to the CCSDS name
     */
    public static FrameFacade parse(final String name,
                                    final IERSConventions conventions,
                                    final boolean simpleEOP,
                                    final DataContext dataContext,
                                    final boolean allowCelestial,
                                    final boolean allowOrbit,
                                    final boolean allowSpacecraft) {
        try {
            final CelestialBodyFrame cbf = CelestialBodyFrame.parse(name);
            if (allowCelestial) {
                return new FrameFacade(cbf.getFrame(conventions, simpleEOP, dataContext),
                                       cbf, null, null, cbf.getName());
            }
        } catch (IllegalArgumentException iaeC) {
            try {
                final OrbitRelativeFrame orf = OrbitRelativeFrame.valueOf(name.replace(' ', '_'));
                if (allowOrbit) {
                    return new FrameFacade(null, null, orf, null, orf.name());
                }
            } catch (IllegalArgumentException iaeO) {
                try {
                    final SpacecraftBodyFrame sbf = SpacecraftBodyFrame.parse(name.replace(' ', '_'));
                    if (allowSpacecraft) {
                        return new FrameFacade(null, null, null, sbf, sbf.toString());
                    }
                } catch (OrekitException | IllegalArgumentException e) {
                    // nothing to do here, use fallback below
                }
            }
        }

        // we don't know any frame with this name, just store the name itself
        return new FrameFacade(null, null, null, null, name);

    }

    /**
     * Get the transform between {@link FrameFacade CCSDS frames}.
     * <p>
     * In case both input and output frames are {@link OrbitRelativeFrame orbit relative frame}, the returned transform
     * will only be composed of a {@link Rotation rotation}. Only {@link LOFType commonly used orbit relative frames}
     * will be recognized.
     * <p>
     * Note that if the input/output {@link FrameFacade CCSDS frame} is defined using a :
     * <ul>
     * <li><b>{@link CelestialBodyFrame celestial body frame}</b></li>
     * <li><b>{@link SpacecraftBodyFrame spacecraft body frame}</b></li>
     * </ul>
     * then <b>an exception will be thrown</b> (currently not supported).
     * <p>
     * Note that the pivot frame provided <b>must be inertial</b> and <b>consistent</b> to what you are working with
     * (i.e GCRF if around Earth for example).
     *
     * @param frameIn the input {@link FrameFacade CCSDS frame} to convert from
     * @param frameOut the output {@link FrameFacade CCSDS frame} to convert to
     * @param inertialPivotFrame <b>inertial</b> frame used as a pivot to create the transform
     * @param date the date for the transform
     * @param pv the position and velocity coordinates provider (required in case one of the frames is an
     * {@link OrbitRelativeFrame orbit relative frame})
     * @return the transform between {@link FrameFacade CCSDS frames}.
     */
    public static Transform getTransform(final FrameFacade frameIn, final FrameFacade frameOut,
                                         final Frame inertialPivotFrame,
                                         final AbsoluteDate date, final PVCoordinatesProvider pv) {

        if (inertialPivotFrame.isPseudoInertial()) {
            final Transform frameInToPivot = getTransformToPivot(frameIn, inertialPivotFrame, date, pv);

            final Transform pivotToFrameOut = getTransformToPivot(frameOut, inertialPivotFrame, date, pv).getInverse();

            return new Transform(date, frameInToPivot, pivotToFrameOut);
        }
        else {
            throw new OrekitException(OrekitMessages.NON_PSEUDO_INERTIAL_FRAME, inertialPivotFrame.getName());
        }

    }

    /**
     * Get the transform between input {@link FrameFacade CCSDS frame} and an <b>inertial</b>
     * {@link Frame Orekit frame}.
     *
     * @param frameIn the input {@link FrameFacade CCSDS frame} to convert from
     * @param inertialPivotFrame <b>inertial</b> {@link Frame Orekit frame} to convert to
     * @param date the date for the transform
     * @param pv the position and velocity coordinates provider (required in case the input
     * {@link FrameFacade CCSDS frame} is an {@link OrbitRelativeFrame orbit relative frame})
     * @return the transform between input {@link FrameFacade CCSDS frame} and an inertial {@link Frame Orekit frame}
     */
    private static Transform getTransformToPivot(final FrameFacade frameIn, final Frame inertialPivotFrame,
                                                 final AbsoluteDate date, final PVCoordinatesProvider pv) {
        final Transform frameInToPivot;

        // Orekit frame
        if (frameIn.asFrame() != null) {
            frameInToPivot = frameIn.asFrame().getTransformTo(inertialPivotFrame, date);
        }

        // Local orbital frame
        else if (frameIn.asOrbitRelativeFrame() != null) {

            final LOFType lofIn = frameIn.asOrbitRelativeFrame().getLofType();

            if (lofIn != null) {
                frameInToPivot =
                        lofIn.transformFromInertial(date, pv.getPVCoordinates(date, inertialPivotFrame)).getInverse();
            }
            else {
                throw new OrekitException(OrekitMessages.UNSUPPORTED_TRANSFORM, frameIn.getName(),
                                          inertialPivotFrame.getName());
            }
        }

        //Celestial body frame
        else if (frameIn.asCelestialBodyFrame() != null) {
            throw new OrekitException(OrekitMessages.UNSUPPORTED_TRANSFORM, frameIn.asCelestialBodyFrame().getName(),
                                      inertialPivotFrame.getName());
        }

        // Spacecraft body frame
        else {
            throw new OrekitException(OrekitMessages.UNSUPPORTED_TRANSFORM, frameIn.getName(),
                                      inertialPivotFrame.getName());
        }

        return frameInToPivot;
    }

}