1   /* Copyright 2002-2025 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 org.hipparchus.geometry.euclidean.threed.Vector3D;
20  import org.hipparchus.linear.MatrixUtils;
21  import org.hipparchus.util.FastMath;
22  import org.junit.jupiter.api.Assertions;
23  import org.junit.jupiter.api.BeforeAll;
24  import org.junit.jupiter.api.BeforeEach;
25  import org.junit.jupiter.api.DisplayName;
26  import org.junit.jupiter.api.Test;
27  import org.mockito.Mockito;
28  import org.orekit.Utils;
29  import org.orekit.data.DataContext;
30  import org.orekit.errors.OrekitException;
31  import org.orekit.frames.Frame;
32  import org.orekit.frames.FramesFactory;
33  import org.orekit.frames.Transform;
34  import org.orekit.orbits.KeplerianOrbit;
35  import org.orekit.orbits.Orbit;
36  import org.orekit.orbits.PositionAngleType;
37  import org.orekit.time.AbsoluteDate;
38  import org.orekit.time.TimeScalesFactory;
39  import org.orekit.utils.Constants;
40  import org.orekit.utils.IERSConventions;
41  import org.orekit.utils.PVCoordinatesProvider;
42  
43  import java.util.Arrays;
44  
45  public class FrameFacadeTest {
46  
47      /**
48       * Configure access to Orekit data folder for simple unit tests.
49       */
50      @BeforeAll
51      public static void configureOrekitDataAccess() {
52          Utils.setDataRoot("regular-data");
53      }
54  
55      @Test
56      public void testMapCelestial() {
57          for (CelestialBodyFrame cbf : CelestialBodyFrame.values()) {
58              FrameFacade ff = FrameFacade.parse(cbf.name(),
59                                                 IERSConventions.IERS_2010, true, DataContext.getDefault(),
60                                                 true, true, true);
61              Assertions.assertSame(cbf, ff.asCelestialBodyFrame());
62              Assertions.assertNull(ff.asOrbitRelativeFrame());
63              Assertions.assertNull(ff.asSpacecraftBodyFrame());
64          }
65      }
66  
67      @Test
68      public void testMapLOF() {
69          for (OrbitRelativeFrame orf : OrbitRelativeFrame.values()) {
70              FrameFacade ff = FrameFacade.parse(orf.name(),
71                                                 IERSConventions.IERS_2010, true, DataContext.getDefault(),
72                                                 true, true, true);
73              Assertions.assertNull(ff.asCelestialBodyFrame());
74              Assertions.assertSame(orf, ff.asOrbitRelativeFrame());
75              Assertions.assertNull(ff.asSpacecraftBodyFrame());
76          }
77      }
78  
79      @Test
80      public void testMapSpacecraft() {
81          for (SpacecraftBodyFrame.BaseEquipment be : SpacecraftBodyFrame.BaseEquipment.values()) {
82              for (String label : Arrays.asList("1", "2", "A", "B")) {
83                  SpacecraftBodyFrame sbf = new SpacecraftBodyFrame(be, label);
84                  FrameFacade ff = FrameFacade.parse(sbf.toString(),
85                                                     IERSConventions.IERS_2010, true, DataContext.getDefault(),
86                                                     true, true, true);
87                  Assertions.assertNull(ff.asCelestialBodyFrame());
88                  Assertions.assertNull(ff.asOrbitRelativeFrame());
89                  Assertions.assertEquals(be, ff.asSpacecraftBodyFrame().getBaseEquipment());
90                  Assertions.assertEquals(label, ff.asSpacecraftBodyFrame().getLabel());
91              }
92          }
93      }
94  
95      @Test
96      public void testUnknownFrame() {
97          final String name = "unknown";
98          FrameFacade ff = FrameFacade.parse(name,
99                                             IERSConventions.IERS_2010, true, DataContext.getDefault(),
100                                            true, true, true);
101         Assertions.assertNull(ff.asFrame());
102         Assertions.assertNull(ff.asCelestialBodyFrame());
103         Assertions.assertNull(ff.asOrbitRelativeFrame());
104         Assertions.assertNull(ff.asSpacecraftBodyFrame());
105         Assertions.assertEquals(name, ff.getName());
106     }
107 
108     @BeforeEach
109     public void setUp() {
110         Utils.setDataRoot("regular-data");
111     }
112 
113     /**
114      * Test that the getTransform method returns expected rotation matrix with theta = 90° when asked the transform
115      * between RTN and TNW local orbital frame.
116      */
117     @Test
118     public void testGetTransformLofToLofAtPeriapsis() {
119 
120         // Given
121         final Frame pivotFrame = FramesFactory.getGCRF();
122         final Orbit orbit = new KeplerianOrbit(7.E6, 0.001, FastMath.toRadians(45.), 0., 0., 0.,
123                                                PositionAngleType.TRUE, FramesFactory.getEME2000(),
124                                                new AbsoluteDate(2000, 1, 1, TimeScalesFactory.getUTC()),
125                                                Constants.GRIM5C1_EARTH_MU);
126 
127         final FrameFacade RTN = new FrameFacade(null, null, OrbitRelativeFrame.QSW, null, "RTN");
128         final FrameFacade TNW = new FrameFacade(null, null, OrbitRelativeFrame.TNW, null, "RTN");
129 
130         // When
131         final Transform lofInToLofOut = FrameFacade.getTransform(RTN, TNW, pivotFrame, orbit.getDate(), orbit);
132 
133         // Then
134         final double[][] expectedRotationMatrix = new double[][] {
135                 { 0, 1, 0 },
136                 { -1, 0, 0 },
137                 { 0, 0, 1 } };
138 
139         validateMatrix(lofInToLofOut.getRotation().getMatrix(), expectedRotationMatrix, 1e-15);
140 
141     }
142 
143     @Test
144     @DisplayName("Test that an exception is thrown if the pivot frame is not pseudo-inertial")
145     void Should_throw_exception_when_given_non_inertial_pivot_frame() {
146 
147         // Given
148         final FrameFacade  frameFacadeInMock         = Mockito.mock(FrameFacade.class);
149         final FrameFacade  frameFacadeOutMock        = Mockito.mock(FrameFacade.class);
150         final Orbit        pvCoordinatesProviderMock = Mockito.mock(Orbit.class);
151         final AbsoluteDate dateMock                  = Mockito.mock(AbsoluteDate.class);
152 
153         final Frame nonInertialPivotFrame = FramesFactory.getITRF(IERSConventions.IERS_2010, true);
154 
155         // When & Then
156         Assertions.assertThrows(OrekitException.class, () -> {
157             FrameFacade.getTransform(frameFacadeInMock, frameFacadeOutMock, nonInertialPivotFrame, dateMock,
158                                      pvCoordinatesProviderMock);
159         });
160 
161     }
162 
163     @Test
164     @DisplayName("Test that an exception is thrown if the frame facade is defined using a SpacecraftBodyFrame or a CelestialBodyFrame")
165     void Should_throw_exception_when_given_unsupported_frame_facade() {
166 
167         // Given
168         final AbsoluteDate          dateMock       = Mockito.mock(AbsoluteDate.class);
169         final Frame                 pivot          = FramesFactory.getGCRF();
170         final PVCoordinatesProvider pvProviderMock = Mockito.mock(Orbit.class);
171 
172         final FrameFacade inputCelestial =
173                 new FrameFacade(null, Mockito.mock(CelestialBodyFrame.class),
174                                 null, null, "Celestial body frame");
175 
176         final FrameFacade inputSpacecraftBody =
177                 new FrameFacade(null, null,
178                                 null, Mockito.mock(SpacecraftBodyFrame.class), "Celestial body frame");
179 
180         final FrameFacade output =
181                 new FrameFacade(FramesFactory.getEME2000(), null,
182                                 null, null, "Celestial body frame");
183 
184         // When & Then
185         Assertions.assertThrows(OrekitException.class, () -> {
186             FrameFacade.getTransform(inputSpacecraftBody, output, pivot, dateMock, pvProviderMock);
187         });
188 
189         Assertions.assertThrows(OrekitException.class, () -> {
190             FrameFacade.getTransform(inputCelestial, output, pivot, dateMock, pvProviderMock);
191         });
192     }
193 
194     @Test
195     @DisplayName("Test that an exception is thrown if the frame facade is defined using an orbit relative frame returning a null LOFType")
196     void Should_throw_exception_when_given_null_LOFType() {
197 
198         // Given
199         final AbsoluteDate          dateMock       = Mockito.mock(AbsoluteDate.class);
200         final Frame                 pivot          = FramesFactory.getGCRF();
201         final PVCoordinatesProvider pvProviderMock = Mockito.mock(Orbit.class);
202 
203         final FrameFacade inputNullLOFType =
204                 new FrameFacade(null, null,
205                                 OrbitRelativeFrame.PQW_INERTIAL, null, "Celestial body frame");
206 
207         final FrameFacade output =
208                 new FrameFacade(FramesFactory.getEME2000(), null,
209                                 null, null, "Celestial body frame");
210 
211         // When & Then
212         Assertions.assertThrows(OrekitException.class, () -> {
213             FrameFacade.getTransform(inputNullLOFType, output, pivot, dateMock, pvProviderMock);
214         });
215     }
216 
217     @Test
218     @DisplayName("Test getTransform method")
219     void Should_return_identity_transform_after_multiple_transforms() {
220 
221         // Given
222         final AbsoluteDate date  = new AbsoluteDate();
223         final Frame        pivot = FramesFactory.getGCRF();
224         final Orbit orbit = new KeplerianOrbit(7.E6, 0.001, FastMath.toRadians(45.), 0., 0., 0.,
225                                                PositionAngleType.MEAN, FramesFactory.getEME2000(),
226                                                date, Constants.IERS2010_EARTH_MU);
227 
228         final FrameFacade initialFrameFacade =
229                 new FrameFacade(FramesFactory.getGCRF(),
230                                 null, null, null,
231                                 "Initial frame");
232 
233         final FrameFacade intermediaryQSWFrameFacade =
234                 new FrameFacade(null,
235                                 null, OrbitRelativeFrame.QSW, null,
236                                 "QSW");
237 
238         final FrameFacade intermediaryTNWFrameFacade =
239                 new FrameFacade(null,
240                                 null, OrbitRelativeFrame.QSW, null,
241                                 "TNW");
242 
243         final FrameFacade intermediaryNonInertialFrameFacade =
244                 new FrameFacade(FramesFactory.getITRF(IERSConventions.IERS_2010, true),
245                                 null, null, null,
246                                 "Non inertial frame");
247 
248         // When
249         final Transform initialToQSW =
250                 FrameFacade.getTransform(initialFrameFacade, intermediaryQSWFrameFacade, pivot, date, orbit);
251 
252         final Transform QSWToTNW =
253                 FrameFacade.getTransform(intermediaryQSWFrameFacade, intermediaryTNWFrameFacade, pivot, date, orbit);
254 
255         final Transform TNWToNonInertialFrame =
256                 FrameFacade.getTransform(intermediaryTNWFrameFacade, intermediaryNonInertialFrameFacade, pivot, date,
257                                          orbit);
258 
259         final Transform nonInertialFrameToInitial =
260                 FrameFacade.getTransform(intermediaryNonInertialFrameFacade, initialFrameFacade, pivot, date, orbit);
261 
262         final Transform composedTransform = composeTransform(date,
263                                                              initialToQSW,
264                                                              QSWToTNW,
265                                                              TNWToNonInertialFrame,
266                                                              nonInertialFrameToInitial);
267 
268         final Vector3D   computedTranslation        = composedTransform.getTranslation();
269         final double[][] computedRotationMatrixData = composedTransform.getRotation().getMatrix();
270 
271         // Then
272         final Vector3D   expectedTranslation        = new Vector3D(0, 0, 0);
273         final double[][] expectedRotationMatrixData = MatrixUtils.createRealIdentityMatrix(3).getData();
274 
275         validateVector3D(expectedTranslation, computedTranslation, 2e-9);
276         validateMatrix(expectedRotationMatrixData, computedRotationMatrixData, 2e-15);
277     }
278 
279     private Transform composeTransform(final AbsoluteDate date, final Transform... transforms) {
280         Transform composedTransform = null;
281 
282         for (Transform transform : transforms) {
283             if (composedTransform == null) {
284                 composedTransform = transform;
285             }
286             else {
287                 composedTransform = new Transform(date, composedTransform, transform);
288             }
289         }
290 
291         return composedTransform;
292     }
293 
294     private void validateVector3D(final Vector3D expected, final Vector3D computed, final double threshold) {
295         Assertions.assertEquals(expected.getX(), computed.getX(), threshold);
296         Assertions.assertEquals(expected.getY(), computed.getY(), threshold);
297         Assertions.assertEquals(expected.getZ(), computed.getZ(), threshold);
298 
299     }
300 
301     /**
302      * Assert that data double array is equals to expected.
303      *
304      * @param data input data to assert
305      * @param expected expected data
306      * @param threshold threshold for precision
307      */
308     private void validateMatrix(double[][] data, double[][] expected, double threshold) {
309         for (int i = 0; i < data.length; i++) {
310             for (int j = 0; j < data[0].length; j++) {
311                 Assertions.assertEquals(expected[i][j], data[i][j], threshold);
312             }
313         }
314     }
315 
316 }