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.frames;
18  
19  import org.hamcrest.MatcherAssert;
20  import org.hipparchus.CalculusFieldElement;
21  import org.hipparchus.analysis.differentiation.UnivariateDerivative1;
22  import org.hipparchus.analysis.differentiation.UnivariateDerivative1Field;
23  import org.hipparchus.complex.Complex;
24  import org.hipparchus.complex.ComplexField;
25  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
26  import org.hipparchus.geometry.euclidean.threed.Rotation;
27  import org.hipparchus.geometry.euclidean.threed.Vector3D;
28  import org.hipparchus.util.FastMath;
29  import org.junit.jupiter.api.Assertions;
30  import org.junit.jupiter.api.BeforeEach;
31  import org.junit.jupiter.api.Test;
32  import org.mockito.Mockito;
33  import org.orekit.OrekitMatchers;
34  import org.orekit.Utils;
35  import org.orekit.bodies.GeodeticPoint;
36  import org.orekit.bodies.OneAxisEllipsoid;
37  import org.orekit.time.AbsoluteDate;
38  import org.orekit.time.FieldAbsoluteDate;
39  import org.orekit.time.TimeScalesFactory;
40  import org.orekit.utils.Constants;
41  import org.orekit.utils.IERSConventions;
42  import org.orekit.utils.PVCoordinates;
43  
44  import java.util.Random;
45  
46  class FrameTest {
47  
48      @Test
49      void testSameFrameRoot() {
50          Random random = new Random(0x29448c7d58b95565l);
51          Frame  frame  = FramesFactory.getEME2000();
52          checkNoTransform(frame.getTransformTo(frame, new AbsoluteDate()), random);
53          Assertions.assertTrue(frame.getDepth() > 0);
54          Assertions.assertEquals(frame.getParent().getDepth() + 1, frame.getDepth());
55      }
56  
57      @Test
58      void testSameFrameNoRoot() {
59          Random random = new Random(0xc6e88d0f53e29116l);
60          Transform t   = randomTransform(random);
61          Frame frame   = new Frame(FramesFactory.getEME2000(), t, null, true);
62          checkNoTransform(frame.getTransformTo(frame, new AbsoluteDate()), random);
63      }
64  
65      @Test
66      void testSimilarFrames() {
67          Random random = new Random(0x1b868f67a83666e5l);
68          Transform t   = randomTransform(random);
69          Frame frame1  = new Frame(FramesFactory.getEME2000(), t, null, true);
70          Frame frame2  = new Frame(FramesFactory.getEME2000(), t, null, false);
71          checkNoTransform(frame1.getTransformTo(frame2, new AbsoluteDate()), random);
72      }
73  
74      @Test
75      void testFromParent() {
76          Random random = new Random(0xb92fba1183fe11b8l);
77          Transform fromEME2000  = randomTransform(random);
78          Frame frame = new Frame(FramesFactory.getEME2000(), fromEME2000, null);
79          Transform toEME2000 = frame.getTransformTo(FramesFactory.getEME2000(), new AbsoluteDate());
80          checkNoTransform(new Transform(fromEME2000.getDate(), fromEME2000, toEME2000), random);
81      }
82  
83      @Test
84      void testDecomposedTransform() {
85          Random random = new Random(0xb7d1a155e726da57l);
86          Transform t1  = randomTransform(random);
87          Transform t2  = randomTransform(random);
88          Transform t3  = randomTransform(random);
89          Frame frame1 =
90              new Frame(FramesFactory.getEME2000(),
91                        new Transform(t1.getDate(), new Transform(t1.getDate(), t1, t2), t3),
92                        null);
93          Frame frame2 =
94              new Frame(new Frame(new Frame(FramesFactory.getEME2000(), t1, null), t2, null), t3, null);
95          checkNoTransform(frame1.getTransformTo(frame2, new AbsoluteDate()), random);
96      }
97  
98      @Test
99      void testFindCommon() {
100 
101         Random random = new Random(0xb7d1a155e726da57l);
102         Transform t1  = randomTransform(random);
103         Transform t2  = randomTransform(random);
104         Transform t3  = randomTransform(random);
105 
106         Frame R1 = new Frame(FramesFactory.getEME2000(), t1, "R1");
107         Frame R2 = new Frame(R1, t2, "R2");
108         Frame R3 = new Frame(R2, t3, "R3");
109         Assertions.assertTrue(R1.getDepth() > 0);
110         Assertions.assertEquals(R1.getDepth() + 1, R2.getDepth());
111         Assertions.assertEquals(R2.getDepth() + 1, R3.getDepth());
112 
113         Transform T = R1.getTransformTo(R3, new AbsoluteDate());
114 
115         Transform S = new Transform(t2.getDate(), t2, t3);
116 
117         checkNoTransform(new Transform(T.getDate(), T, S.getInverse()) , random);
118 
119     }
120 
121     @Test
122     void testDepthAndAncestor() {
123         Random random = new Random(0x01f8d3b944123044l);
124         Frame root = Frame.getRoot();
125 
126         Frame f1 = new Frame(root, randomTransform(random), "f1");
127         Frame f2 = new Frame(f1,   randomTransform(random), "f2");
128         Frame f3 = new Frame(f1,   randomTransform(random), "f3");
129         Frame f4 = new Frame(f2,   randomTransform(random), "f4");
130         Frame f5 = new Frame(f3,   randomTransform(random), "f5");
131         Frame f6 = new Frame(f5,   randomTransform(random), "f6");
132 
133         Assertions.assertEquals(0, root.getDepth());
134         Assertions.assertEquals(1, f1.getDepth());
135         Assertions.assertEquals(2, f2.getDepth());
136         Assertions.assertEquals(2, f3.getDepth());
137         Assertions.assertEquals(3, f4.getDepth());
138         Assertions.assertEquals(3, f5.getDepth());
139         Assertions.assertEquals(4, f6.getDepth());
140 
141         Assertions.assertTrue(root == f1.getAncestor(1));
142         Assertions.assertTrue(root == f6.getAncestor(4));
143         Assertions.assertTrue(f1   == f6.getAncestor(3));
144         Assertions.assertTrue(f3   == f6.getAncestor(2));
145         Assertions.assertTrue(f5   == f6.getAncestor(1));
146         Assertions.assertTrue(f6   == f6.getAncestor(0));
147 
148         try {
149             f6.getAncestor(5);
150             Assertions.fail("an exception should have been triggered");
151         } catch (IllegalArgumentException iae) {
152             // expected behavior
153         } catch (Exception e) {
154             Assertions.fail("wrong exception caught: " + e.getClass().getName());
155         }
156 
157     }
158 
159     @Test
160     void testIsChildOf() {
161         Random random = new Random(0xb7d1a155e726da78l);
162         Frame eme2000 = FramesFactory.getEME2000();
163 
164         Frame f1 = new Frame(eme2000, randomTransform(random), "f1");
165         Frame f2 = new Frame(f1     , randomTransform(random), "f2");
166         Frame f4 = new Frame(f2     , randomTransform(random), "f4");
167         Frame f5 = new Frame(f4     , randomTransform(random), "f5");
168         Frame f6 = new Frame(eme2000, randomTransform(random), "f6");
169         Frame f7 = new Frame(f6     , randomTransform(random), "f7");
170         Frame f8 = new Frame(f6     , randomTransform(random), "f8");
171         Frame f9 = new Frame(f7     , randomTransform(random), "f9");
172 
173         // check if the root frame can be an ancestor of another frame
174         Assertions.assertEquals(false, eme2000.isChildOf(f5));
175 
176         // check if a frame which belongs to the same branch than the 2nd frame is a branch of it
177         Assertions.assertEquals(true, f5.isChildOf(f1));
178 
179         // check if a random frame is the child of the root frame
180         Assertions.assertEquals(true, f9.isChildOf(eme2000));
181 
182         // check that a frame is not its own child
183         Assertions.assertEquals(false, f4.isChildOf(f4));
184 
185         // check if a frame which belongs to a different branch than the 2nd frame can be a child for it
186         Assertions.assertEquals(false, f9.isChildOf(f5));
187 
188         // check if the root frame is not a child of itself
189         Assertions.assertEquals(false, eme2000.isChildOf(eme2000));
190 
191         Assertions.assertEquals(false, f9.isChildOf(f8));
192 
193     }
194 
195     @Test
196     void testH0m9() {
197         AbsoluteDate h0         = new AbsoluteDate("2010-07-01T10:42:09", TimeScalesFactory.getUTC());
198         Frame itrf              = FramesFactory.getITRF(IERSConventions.IERS_2010, true);
199         Frame rotatingPadFrame  = new TopocentricFrame(new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
200                                                                             Constants.WGS84_EARTH_FLATTENING,
201                                                                             itrf),
202                                                        new GeodeticPoint(FastMath.toRadians(5.0),
203                                                                                               FastMath.toRadians(-100.0),
204                                                                                               0.0),
205                                                        "launch pad");
206 
207         // create a new inertially oriented frame that is aligned with ITRF at h0 - 9 seconds
208         AbsoluteDate h0M9       = h0.shiftedBy(-9.0);
209         Frame eme2000           = FramesFactory.getEME2000();
210         Frame frozenLaunchFrame = rotatingPadFrame.getFrozenFrame(eme2000, h0M9, "launch frame");
211 
212         // check velocity module is unchanged
213         Vector3D pEme2000 = new Vector3D(-29536113.0, 30329259.0, -100125.0);
214         Vector3D vEme2000 = new Vector3D(-2194.0, -2141.0, -8.0);
215         PVCoordinates pvEme2000 = new PVCoordinates(pEme2000, vEme2000);
216         PVCoordinates pvH0m9 = eme2000.getTransformTo(frozenLaunchFrame, h0M9).transformPVCoordinates(pvEme2000);
217         Assertions.assertEquals(vEme2000.getNorm(), pvH0m9.getVelocity().getNorm(), 1.0e-6);
218         Vector3D pH0m9 = eme2000.getStaticTransformTo(frozenLaunchFrame, h0M9)
219                 .transformPosition(pvEme2000.getPosition());
220         MatcherAssert.assertThat(pH0m9,
221                 OrekitMatchers.vectorCloseTo(pvH0m9.getPosition(), 1e-15));
222 
223         // this frame is fixed with respect to EME2000 but rotates with respect to the non-frozen one
224         // the following loop should have a fixed angle a1 and an evolving angle a2
225         double minA1 = Double.POSITIVE_INFINITY;
226         double maxA1 = Double.NEGATIVE_INFINITY;
227         double minA2 = Double.POSITIVE_INFINITY;
228         double maxA2 = Double.NEGATIVE_INFINITY;
229         double dt;
230         for (dt = 0; dt < 86164; dt += 300.0) {
231             AbsoluteDate date = h0M9.shiftedBy(dt);
232             double a1 = frozenLaunchFrame.getTransformTo(eme2000,          date).getRotation().getAngle();
233             double a2 = frozenLaunchFrame.getTransformTo(rotatingPadFrame, date).getRotation().getAngle();
234             minA1 = FastMath.min(minA1, a1);
235             maxA1 = FastMath.max(maxA1, a1);
236             minA2 = FastMath.min(minA2, a2);
237             maxA2 = FastMath.max(maxA2, a2);
238         }
239         Assertions.assertEquals(0, maxA1 - minA1, 1.0e-12);
240         Assertions.assertEquals(FastMath.PI, maxA2 - minA2, 0.01);
241 
242     }
243 
244     private Transform randomTransform(Random random) {
245         Transform transform = Transform.IDENTITY;
246         for (int i = random.nextInt(10); i > 0; --i) {
247             if (random.nextBoolean()) {
248                 Vector3D u = new Vector3D(random.nextDouble() * 1000.0,
249                                           random.nextDouble() * 1000.0,
250                                           random.nextDouble() * 1000.0);
251                 transform = new Transform(transform.getDate(), transform, new Transform(transform.getDate(), u));
252             } else {
253                 double q0 = random.nextDouble() * 2 - 1;
254                 double q1 = random.nextDouble() * 2 - 1;
255                 double q2 = random.nextDouble() * 2 - 1;
256                 double q3 = random.nextDouble() * 2 - 1;
257                 double q  = FastMath.sqrt(q0 * q0 + q1 * q1 + q2 * q2 + q3 * q3);
258                 Rotation r = new Rotation(q0 / q, q1 / q, q2 / q, q3 / q, false);
259                 transform = new Transform(transform.getDate(), transform, new Transform(transform.getDate(), r));
260             }
261         }
262         return transform;
263     }
264 
265     private void checkNoTransform(Transform transform, Random random) {
266         for (int i = 0; i < 100; ++i) {
267             Vector3D a = new Vector3D(random.nextDouble(),
268                                       random.nextDouble(),
269                                       random.nextDouble());
270             Vector3D b = transform.transformVector(a);
271             Assertions.assertEquals(0, a.subtract(b).getNorm(), 1.0e-10);
272             Vector3D c = transform.transformPosition(a);
273             Assertions.assertEquals(0, a.subtract(c).getNorm(), 1.0e-10);
274         }
275     }
276 
277     @Test
278     void testGetKinematicTransformTo() {
279         // GIVEN
280         final Frame oldFrame = FramesFactory.getEME2000();
281         final Frame newFrame = FramesFactory.getGCRF();
282         final AbsoluteDate date = AbsoluteDate.ARBITRARY_EPOCH;
283         // WHEN
284         final KinematicTransform kinematicTransform = oldFrame.getKinematicTransformTo(newFrame, date);
285         // THEN
286         final Transform transform = oldFrame.getTransformTo(newFrame, date);
287         Assertions.assertEquals(date, kinematicTransform.getDate());
288         Assertions.assertEquals(transform.getCartesian().getPosition(), kinematicTransform.getTranslation());
289         Assertions.assertEquals(transform.getCartesian().getVelocity(), kinematicTransform.getVelocity());
290         Assertions.assertEquals(0., Rotation.distance(transform.getRotation(), kinematicTransform.getRotation()));
291         Assertions.assertEquals(transform.getRotationRate(), kinematicTransform.getRotationRate());
292     }
293 
294     @Test
295     void testGetStaticTransformIdentity() {
296         // GIVEN
297         final AbsoluteDate date = AbsoluteDate.ARBITRARY_EPOCH;
298         final Frame mockedFrame = Mockito.mock(Frame.class);
299         Mockito.when(mockedFrame.getStaticTransformTo(mockedFrame, date)).thenCallRealMethod();
300         // WHEN
301         final StaticTransform staticTransform = mockedFrame.getStaticTransformTo(mockedFrame, date);
302         // THEN
303         Assertions.assertEquals(staticTransform, staticTransform.getStaticInverse());
304     }
305 
306     @Test
307     void testGetStaticTransformIdentityField() {
308         // GIVEN
309         final FieldAbsoluteDate<Complex> fieldDate = FieldAbsoluteDate.getArbitraryEpoch(ComplexField.getInstance());
310         final Frame frame = FramesFactory.getGCRF();
311         // WHEN
312         final FieldStaticTransform<Complex> staticTransform = frame.getStaticTransformTo(frame, fieldDate);
313         // THEN
314         Assertions.assertEquals(staticTransform.getClass(), staticTransform.getStaticInverse().getClass());
315     }
316 
317     @Test
318     void testFieldGetKinematicTransformToWithConstantDate() {
319         templateTestFieldGetKinematicTransformTo(getComplexDate());
320     }
321 
322     @Test
323     void testFieldGetKinematicTransformToWithNonConstantDate() {
324         templateTestFieldGetKinematicTransformTo(getComplexDate().shiftedBy(Complex.I));
325     }
326 
327     private void templateTestFieldGetKinematicTransformTo(final FieldAbsoluteDate<Complex> fieldDate) {
328         // GIVEN
329         final Frame oldFrame = FramesFactory.getEME2000();
330         final Frame newFrame = FramesFactory.getGCRF();
331         // WHEN
332         final FieldKinematicTransform<Complex> fieldKinematicTransform = oldFrame.getKinematicTransformTo(newFrame,
333                 fieldDate);
334         // THEN
335         final KinematicTransform kinematicTransform = oldFrame.getKinematicTransformTo(newFrame,
336                 fieldDate.toAbsoluteDate());
337         Assertions.assertEquals(kinematicTransform.getDate(), fieldKinematicTransform.getDate());
338         Assertions.assertEquals(kinematicTransform.getTranslation(), fieldKinematicTransform.getTranslation().toVector3D());
339         Assertions.assertEquals(kinematicTransform.getVelocity(), fieldKinematicTransform.getVelocity().toVector3D());
340         Assertions.assertEquals(0., Rotation.distance(kinematicTransform.getRotation(),
341                 fieldKinematicTransform.getRotation().toRotation()));
342         Assertions.assertEquals(kinematicTransform.getRotationRate(),
343                 fieldKinematicTransform.getRotationRate().toVector3D());
344     }
345 
346     private FieldAbsoluteDate<Complex> getComplexDate() {
347         return FieldAbsoluteDate.getArbitraryEpoch(ComplexField.getInstance());
348     }
349 
350     @Test
351     void testGetTransformTo() {
352         // GIVEN
353         final Frame oldFrame = FramesFactory.getEME2000();
354         final Frame newFrame = FramesFactory.getGTOD(true);
355         final UnivariateDerivative1Field field = UnivariateDerivative1Field.getInstance();
356         final FieldAbsoluteDate<UnivariateDerivative1> fieldDate = FieldAbsoluteDate.getArbitraryEpoch(field);
357         final FieldAbsoluteDate<UnivariateDerivative1> shiftedDate = fieldDate.shiftedBy(new UnivariateDerivative1(0., 1));
358         // WHEN
359         final FieldTransform<UnivariateDerivative1> fieldTransform = oldFrame.getTransformTo(newFrame, shiftedDate);
360         // THEN
361         Assertions.assertEquals(shiftedDate, fieldTransform.getFieldDate());
362         final FieldTransform<UnivariateDerivative1> referenceTransform = oldFrame.getTransformTo(newFrame, fieldDate);
363         Assertions.assertEquals(fieldTransform.getDate(), referenceTransform.getDate());
364         Assertions.assertEquals(fieldTransform.getTranslation().toVector3D(),
365                 referenceTransform.getTranslation().toVector3D());
366         compareFieldVectorWithMargin(fieldTransform.getRotationRate(), referenceTransform.getRotationRate());
367         compareFieldVectorWithMargin(fieldTransform.getRotationAcceleration(), referenceTransform.getRotationAcceleration());
368         Assertions.assertEquals(0., Rotation.distance(fieldTransform.getRotation().toRotation(),
369                 referenceTransform.getRotation().toRotation()));
370     }
371 
372     private static <T extends CalculusFieldElement<T>> void compareFieldVectorWithMargin(final FieldVector3D<T> expectedVector,
373                                                                                          final FieldVector3D<T> actualVector) {
374         Assertions.assertEquals(0., actualVector.toVector3D().subtract(expectedVector.toVector3D()).getNorm(),
375                 1e-12);
376     }
377 
378     @BeforeEach
379     public void setUp() {
380         Utils.setDataRoot("compressed-data");
381     }
382 
383 }