1   /* Copyright 2002-2026 CS GROUP
2    * Licensed to CS GROUP (CS) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * CS licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.orekit.files.ccsds.definitions;
18  
19  import java.util.Optional;
20  
21  import org.hipparchus.geometry.euclidean.threed.Rotation;
22  import org.orekit.annotation.Nullable;
23  import org.orekit.data.DataContext;
24  import org.orekit.errors.OrekitException;
25  import org.orekit.errors.OrekitMessages;
26  import org.orekit.frames.Frame;
27  import org.orekit.frames.LOFType;
28  import org.orekit.frames.Transform;
29  import org.orekit.time.AbsoluteDate;
30  import org.orekit.utils.IERSConventions;
31  import org.orekit.utils.PVCoordinatesProvider;
32  
33  /** Facade in front of several frames types in CCSDS messages.
34   *
35   * @author Luc Maisonobe
36   * @author Vincent Cucchietti
37   * @since 11.0
38   */
39  public class FrameFacade {
40  
41      /** Reference to node in Orekit frames tree. */
42      @Nullable
43      private final Frame frame;
44  
45      /** Reference to celestial body centered frame. */
46      @Nullable
47      private final CelestialBodyFrame celestialBodyFrame;
48  
49      /** Reference to orbit-relative frame. */
50      @Nullable
51      private final OrbitRelativeFrame orbitRelativeFrame;
52  
53      /** Reference to spacecraft body frame. */
54      @Nullable
55      private final SpacecraftBodyFrame spacecraftBodyFrame;
56  
57      /** Name of the frame. */
58      private final String name;
59  
60      /**
61       * Simple constructor.
62       * <p>
63       * At most one of {@code celestialBodyFrame}, {@code orbitRelativeFrame} or {@code spacecraftBodyFrame} may be non
64       * null. They may all be null if frame is unknown, in which case only the name will be available.
65       * </p>
66       *
67       * @param frame reference to node in Orekit frames tree (may be null)
68       * @param celestialBodyFrame reference to celestial body centered frame (may be null)
69       * @param orbitRelativeFrame reference to orbit-relative frame (may be null)
70       * @param spacecraftBodyFrame reference to spacecraft body frame (may be null)
71       * @param name name of the frame
72       */
73      public FrameFacade(final Frame frame,
74                         final CelestialBodyFrame celestialBodyFrame,
75                         final OrbitRelativeFrame orbitRelativeFrame,
76                         final SpacecraftBodyFrame spacecraftBodyFrame,
77                         final String name) {
78          this.frame = frame;
79          this.celestialBodyFrame = celestialBodyFrame;
80          this.orbitRelativeFrame = orbitRelativeFrame;
81          this.spacecraftBodyFrame = spacecraftBodyFrame;
82          this.name = name;
83      }
84  
85      /**
86       * Get the associated frame tree node.
87       *
88       * @return associated frame tree node, or empty if none exists
89       */
90      public Optional<Frame> asFrame() {
91          return Optional.ofNullable(frame);
92      }
93  
94      /**
95       * Get the associated {@link CelestialBodyFrame celestial body frame}.
96       *
97       * @return associated celestial body frame, or empty if frame is associated to a
98       * {@link #asOrbitRelativeFrame() orbit}, a {@link #asSpacecraftBodyFrame spacecraft} or is not supported
99       */
100     public Optional<CelestialBodyFrame> asCelestialBodyFrame() {
101         return Optional.ofNullable(celestialBodyFrame);
102     }
103 
104     /**
105      * Get the associated {@link OrbitRelativeFrame orbit relative frame}.
106      *
107      * @return associated orbit relative frame, or empty if frame is associated to a
108      * {@link #asCelestialBodyFrame() celestial body}, a {@link #asSpacecraftBodyFrame spacecraft} or is not supported
109      */
110     public Optional<OrbitRelativeFrame> asOrbitRelativeFrame() {
111         return Optional.ofNullable(orbitRelativeFrame);
112     }
113 
114     /**
115      * Get the associated {@link SpacecraftBodyFrame spacecraft body frame}.
116      *
117      * @return associated spacecraft body frame, or empty if frame is associated to a
118      * {@link #asCelestialBodyFrame() celestial body}, an {@link #asOrbitRelativeFrame orbit} or is not supported
119      */
120     public Optional<SpacecraftBodyFrame> asSpacecraftBodyFrame() {
121         return Optional.ofNullable(spacecraftBodyFrame);
122     }
123 
124     /**
125      * Get the CCSDS name for the frame.
126      *
127      * @return CCSDS name
128      */
129     public String getName() {
130         return name;
131     }
132 
133     /**
134      * Map an Orekit frame to a CCSDS frame facade.
135      *
136      * @param frame a reference frame.
137      * @return the CCSDS frame corresponding to the Orekit frame
138      */
139     public static FrameFacade map(final Frame frame) {
140         final CelestialBodyFrame cbf = CelestialBodyFrame.map(frame);
141         return new FrameFacade(frame, cbf, null, null, cbf.getName());
142     }
143 
144     /**
145      * Simple constructor.
146      *
147      * @param name name of the frame
148      * @param conventions IERS conventions to use
149      * @param simpleEOP if true, tidal effects are ignored when interpolating EOP
150      * @param dataContext to use when creating the frame
151      * @param allowCelestial if true, {@link CelestialBodyFrame} are allowed
152      * @param allowOrbit if true, {@link OrbitRelativeFrame} are allowed
153      * @param allowSpacecraft if true, {@link SpacecraftBodyFrame} are allowed
154      * @return frame facade corresponding to the CCSDS name
155      */
156     public static FrameFacade parse(final String name,
157                                     final IERSConventions conventions,
158                                     final boolean simpleEOP,
159                                     final DataContext dataContext,
160                                     final boolean allowCelestial,
161                                     final boolean allowOrbit,
162                                     final boolean allowSpacecraft) {
163         try {
164             final CelestialBodyFrame cbf = CelestialBodyFrame.parse(name);
165             if (allowCelestial) {
166                 return new FrameFacade(cbf.getFrame(conventions, simpleEOP, dataContext),
167                                        cbf, null, null, cbf.getName());
168             }
169         } catch (IllegalArgumentException iaeC) {
170             try {
171                 final OrbitRelativeFrame orf = OrbitRelativeFrame.valueOf(name.replace(' ', '_'));
172                 if (allowOrbit) {
173                     return new FrameFacade(null, null, orf, null, orf.name());
174                 }
175             } catch (IllegalArgumentException iaeO) {
176                 try {
177                     final SpacecraftBodyFrame sbf = SpacecraftBodyFrame.parse(name.replace(' ', '_'));
178                     if (allowSpacecraft) {
179                         return new FrameFacade(null, null, null, sbf, sbf.toString());
180                     }
181                 } catch (OrekitException | IllegalArgumentException e) {
182                     // nothing to do here, use fallback below
183                 }
184             }
185         }
186 
187         // we don't know any frame with this name, just store the name itself
188         return new FrameFacade(null, null, null, null, name);
189 
190     }
191 
192     /**
193      * Get the transform between {@link FrameFacade CCSDS frames}.
194      * <p>
195      * In case both input and output frames are {@link OrbitRelativeFrame orbit relative frame}, the returned transform
196      * will only be composed of a {@link Rotation rotation}. Only {@link LOFType commonly used orbit relative frames}
197      * will be recognized.
198      * <p>
199      * Note that if the input/output {@link FrameFacade CCSDS frame} is defined using a :
200      * <ul>
201      * <li><b>{@link CelestialBodyFrame celestial body frame}</b></li>
202      * <li><b>{@link SpacecraftBodyFrame spacecraft body frame}</b></li>
203      * </ul>
204      * then <b>an exception will be thrown</b> (currently not supported).
205      * <p>
206      * Note that the pivot frame provided <b>must be inertial</b> and <b>consistent</b> to what you are working with
207      * (i.e GCRF if around Earth for example).
208      *
209      * @param frameIn the input {@link FrameFacade CCSDS frame} to convert from
210      * @param frameOut the output {@link FrameFacade CCSDS frame} to convert to
211      * @param inertialPivotFrame <b>inertial</b> frame used as a pivot to create the transform
212      * @param date the date for the transform
213      * @param pv the position and velocity coordinates provider (required in case one of the frames is an
214      * {@link OrbitRelativeFrame orbit relative frame})
215      * @return the transform between {@link FrameFacade CCSDS frames}.
216      */
217     public static Transform getTransform(final FrameFacade frameIn, final FrameFacade frameOut,
218                                          final Frame inertialPivotFrame,
219                                          final AbsoluteDate date, final PVCoordinatesProvider pv) {
220 
221         if (inertialPivotFrame.isPseudoInertial()) {
222             final Transform frameInToPivot = getTransformToPivot(frameIn, inertialPivotFrame, date, pv);
223 
224             final Transform pivotToFrameOut = getTransformToPivot(frameOut, inertialPivotFrame, date, pv).getInverse();
225 
226             return new Transform(date, frameInToPivot, pivotToFrameOut);
227         }
228         else {
229             throw new OrekitException(OrekitMessages.NON_PSEUDO_INERTIAL_FRAME, inertialPivotFrame.getName());
230         }
231 
232     }
233 
234     /**
235      * Get the transform between input {@link FrameFacade CCSDS frame} and an <b>inertial</b>
236      * {@link Frame Orekit frame}.
237      *
238      * @param frameIn the input {@link FrameFacade CCSDS frame} to convert from
239      * @param inertialPivotFrame <b>inertial</b> {@link Frame Orekit frame} to convert to
240      * @param date the date for the transform
241      * @param pv the position and velocity coordinates provider (required in case the input
242      * {@link FrameFacade CCSDS frame} is an {@link OrbitRelativeFrame orbit relative frame})
243      * @return the transform between input {@link FrameFacade CCSDS frame} and an inertial {@link Frame Orekit frame}
244      */
245     private static Transform getTransformToPivot(final FrameFacade frameIn, final Frame inertialPivotFrame,
246                                                  final AbsoluteDate date, final PVCoordinatesProvider pv) {
247         final Transform frameInToPivot;
248 
249         // Orekit frame
250         if (frameIn.asFrame().isPresent()) {
251             frameInToPivot = frameIn.asFrame().get().getTransformTo(inertialPivotFrame, date);
252         }
253 
254         // Local orbital frame
255         else if (frameIn.asOrbitRelativeFrame().isPresent()) {
256 
257             final LOFType lofIn = frameIn.asOrbitRelativeFrame().get().getLofType();
258 
259             if (lofIn != null) {
260                 frameInToPivot =
261                         lofIn.transformToInertial(date, pv.getPVCoordinates(date, inertialPivotFrame));
262             }
263             else {
264                 throw new OrekitException(OrekitMessages.UNSUPPORTED_TRANSFORM, frameIn.getName(),
265                                           inertialPivotFrame.getName());
266             }
267         }
268 
269         //Celestial body frame
270         else if (frameIn.asCelestialBodyFrame().isPresent()) {
271             throw new OrekitException(OrekitMessages.UNSUPPORTED_TRANSFORM, frameIn.asCelestialBodyFrame().get().getName(),
272                                       inertialPivotFrame.getName());
273         }
274 
275         // Spacecraft body frame
276         else {
277             throw new OrekitException(OrekitMessages.UNSUPPORTED_TRANSFORM, frameIn.getName(),
278                                       inertialPivotFrame.getName());
279         }
280 
281         return frameInToPivot;
282     }
283 
284 }