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 }