1   /* Copyright 2002-2024 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.hipparchus.CalculusFieldElement;
20  import org.hipparchus.Field;
21  import org.hipparchus.geometry.euclidean.threed.FieldRotation;
22  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
23  import org.hipparchus.geometry.euclidean.threed.Rotation;
24  import org.hipparchus.geometry.euclidean.threed.RotationConvention;
25  import org.hipparchus.geometry.euclidean.threed.Vector3D;
26  import org.hipparchus.linear.BlockFieldMatrix;
27  import org.hipparchus.linear.BlockRealMatrix;
28  import org.hipparchus.linear.FieldMatrix;
29  import org.hipparchus.linear.MatrixUtils;
30  import org.hipparchus.linear.RealMatrix;
31  import org.hipparchus.util.Binary64;
32  import org.hipparchus.util.Binary64Field;
33  import org.junit.jupiter.api.Assertions;
34  import org.junit.jupiter.api.BeforeEach;
35  import org.junit.jupiter.api.DisplayName;
36  import org.junit.jupiter.api.Test;
37  import org.orekit.TestUtils;
38  import org.orekit.orbits.KeplerianOrbit;
39  import org.orekit.orbits.Orbit;
40  import org.orekit.orbits.PositionAngleType;
41  import org.orekit.propagation.analytical.KeplerianPropagator;
42  import org.orekit.time.AbsoluteDate;
43  import org.orekit.time.FieldAbsoluteDate;
44  import org.orekit.utils.FieldPVCoordinates;
45  import org.orekit.utils.PVCoordinates;
46  import org.orekit.utils.PVCoordinatesProvider;
47  
48  public class LocalOrbitalFrameTest {
49  
50      @Test
51      public void testIssue1282() {
52          // GIVEN
53          final Vector3D position = new Vector3D(0,0,1);
54          final Vector3D velocity = new Vector3D(0,1,0);
55          final PVCoordinates pvCoordinates  = new PVCoordinates(position, velocity);
56  
57          // WHEN
58          final Rotation actualRotation = LOFType.EQW.rotationFromInertial(pvCoordinates);
59  
60          // THEN
61          final Rotation expectedRotation = new Rotation(Vector3D.MINUS_J,Vector3D.MINUS_I, Vector3D.PLUS_I, Vector3D.PLUS_K);
62          Assertions.assertArrayEquals(expectedRotation.getMatrix(), actualRotation.getMatrix());
63      }
64  
65      @Test
66      public void testIssue977() {
67          LOFType type = LOFType.TNW;
68          LocalOrbitalFrame lof = new LocalOrbitalFrame(FramesFactory.getGCRF(), type, provider, type.name());
69          Assertions.assertThrows(UnsupportedOperationException.class, () -> {
70              lof.getTransformProvider().getTransform(FieldAbsoluteDate.getJ2000Epoch(Binary64Field.getInstance()));
71          });
72      }
73  
74      @Test
75      public void testTNW() {
76          AbsoluteDate  date = initDate.shiftedBy(400);
77          PVCoordinates pv   = provider.getPVCoordinates(date, inertialFrame);
78          checkFrame(LOFType.TNW, date,
79                     pv.getVelocity(),
80                     Vector3D.crossProduct(pv.getMomentum(), pv.getVelocity()),
81                     pv.getMomentum(),
82                     pv.getMomentum().negate());
83      }
84  
85      @Test
86      public void testQSW() {
87          AbsoluteDate  date = initDate.shiftedBy(400);
88          PVCoordinates pv   = provider.getPVCoordinates(date, inertialFrame);
89          checkFrame(LOFType.QSW, date,
90                     pv.getPosition(),
91                     Vector3D.crossProduct(pv.getMomentum(), pv.getPosition()),
92                     pv.getMomentum(),
93                     pv.getMomentum().negate());
94      }
95  
96      @Test
97      public void testLVLH() {
98          AbsoluteDate  date = initDate.shiftedBy(400);
99          PVCoordinates pv   = provider.getPVCoordinates(date, inertialFrame);
100         checkFrame(LOFType.LVLH, date,
101                    pv.getPosition(),
102                    Vector3D.crossProduct(pv.getMomentum(), pv.getPosition()),
103                    pv.getMomentum(),
104                    pv.getMomentum().negate());
105     }
106 
107     @Test
108     public void testLVLH_CCSDS() {
109         AbsoluteDate  date = initDate.shiftedBy(400);
110         PVCoordinates pv   = provider.getPVCoordinates(date, inertialFrame);
111         checkFrame(LOFType.LVLH_CCSDS, date,
112                    Vector3D.crossProduct(pv.getMomentum(), pv.getPosition()),
113                    pv.getMomentum().negate(),
114                    pv.getPosition().negate(),
115                    pv.getMomentum().negate());
116     }
117 
118     @Test
119     public void testVVLH() {
120         AbsoluteDate  date = initDate.shiftedBy(400);
121         PVCoordinates pv   = provider.getPVCoordinates(date, inertialFrame);
122         checkFrame(LOFType.VVLH, date,
123                    Vector3D.crossProduct(pv.getMomentum(), pv.getPosition()),
124                    pv.getMomentum().negate(),
125                    pv.getPosition().negate(),
126                    pv.getMomentum().negate());
127     }
128 
129     @Test
130     public void testVNC() {
131         AbsoluteDate  date = initDate.shiftedBy(400);
132         PVCoordinates pv   = provider.getPVCoordinates(date, inertialFrame);
133         checkFrame(LOFType.VNC, date,
134                    pv.getVelocity(),
135                    pv.getMomentum(),
136                    Vector3D.crossProduct(pv.getVelocity(), pv.getMomentum()),
137                    pv.getMomentum().negate());
138     }
139 
140     @Test
141     @DisplayName("Test transformFromLOFInToLOFOut method")
142     void should_return_expected_transform_from_LOFIn_To_LOFOut() {
143         // Given
144         final AbsoluteDate date = new AbsoluteDate();
145         final PVCoordinates pv = new PVCoordinates(new Vector3D(6378000 + 400000, 0, 0),
146                                                    new Vector3D(0, 7669, 0));
147 
148         // When
149         final Transform transformFromTNWToQSW = LOF.transformFromLOFInToLOFOut(LOFType.TNW, LOFType.QSW, date, pv);
150         final Transform transformFromQSWToNTW = LOF.transformFromLOFInToLOFOut(LOFType.QSW, LOFType.NTW, date, pv);
151         final Transform transformFromNTWToTNW = LOF.transformFromLOFInToLOFOut(LOFType.NTW, LOFType.TNW, date, pv);
152         final Transform composedTransform = composeTransform(date,
153                                                              transformFromTNWToQSW,
154                                                              transformFromQSWToNTW,
155                                                              transformFromNTWToTNW);
156 
157         final Vector3D        computedTranslation = composedTransform.getTranslation();
158         final BlockRealMatrix computedRotation    = new BlockRealMatrix(composedTransform.getRotation().getMatrix());
159 
160         // Then
161         final Vector3D expectedTranslation = new Vector3D(0, 0, 0);
162         final RealMatrix expectedRotation = MatrixUtils.createRealIdentityMatrix(3);
163 
164         TestUtils.validateVector3D(expectedTranslation, computedTranslation, 1e-15);
165         TestUtils.validateRealMatrix(expectedRotation, computedRotation, 1e-15);
166 
167     }
168 
169     @Test
170     @DisplayName("Test transformFromLOFInToLOFOut (field version) method")
171     void should_return_expected_field_transform_from_LOFIn_To_LOFOut() {
172         // Given
173         final Field<Binary64> field = Binary64Field.getInstance();
174         final FieldAbsoluteDate<Binary64> date = new FieldAbsoluteDate<>(field);
175         final FieldPVCoordinates<Binary64> pv =
176                 new FieldPVCoordinates<>(new FieldVector3D<>(new Binary64(6378000 + 400000),
177                                                              new Binary64(0),
178                                                              new Binary64(0)),
179                                          new FieldVector3D<>(new Binary64(0),
180                                                              new Binary64(7669),
181                                                              new Binary64(0)));
182 
183         // When
184         final FieldTransform<Binary64> transformFromTNWToQSW =
185                 LOF.transformFromLOFInToLOFOut(LOFType.TNW, LOFType.QSW, date, pv);
186         final FieldTransform<Binary64> transformFromQSWToNTW =
187                 LOF.transformFromLOFInToLOFOut(LOFType.QSW, LOFType.NTW, date, pv);
188         final FieldTransform<Binary64> transformFromNTWToTNW =
189                 LOF.transformFromLOFInToLOFOut(LOFType.NTW, LOFType.TNW, date, pv);
190         final FieldTransform<Binary64> composedTransform = composeFieldTransform(date,
191                                                                                   transformFromTNWToQSW,
192                                                                                   transformFromQSWToNTW,
193                                                                                   transformFromNTWToTNW);
194 
195         final FieldVector3D<Binary64>    computedTranslation = composedTransform.getTranslation();
196         final BlockFieldMatrix<Binary64> computedRotation    =
197                 new BlockFieldMatrix<>(composedTransform.getRotation().getMatrix());
198 
199         // Then
200         final Vector3D expectedTranslation = new Vector3D(0, 0, 0);
201         final RealMatrix expectedRotation = MatrixUtils.createRealIdentityMatrix(3);
202 
203         TestUtils.validateFieldVector3D(expectedTranslation, computedTranslation, 1e-15);
204         TestUtils.validateFieldMatrix(expectedRotation, computedRotation, 1e-15);
205 
206     }
207 
208     @Test
209     @DisplayName("Test transformFromInertial method")
210     void should_return_expected_transform_from_inertial() {
211         // Given
212         final AbsoluteDate date = new AbsoluteDate();
213         final PVCoordinates pv = new PVCoordinates(new Vector3D(6378000 + 400000, 0, 0),
214                                                    new Vector3D(0, 7669, 0));
215 
216         // When
217         final Transform  transformFromInertialToLOF = LOFType.TNW.transformFromInertial(date, pv);
218         final Vector3D   computedTranslation        = transformFromInertialToLOF.getTranslation();
219         final RealMatrix computedRotation           =
220                 new BlockRealMatrix(transformFromInertialToLOF.getRotation().getMatrix());
221 
222         // Then
223         final Vector3D expectedTranslation = new Vector3D(-(6378000 + 400000), 0, 0);
224         final RealMatrix expectedRotation = new BlockRealMatrix(new double[][] {
225                 { 0, 1, 0 },
226                 { -1, 0, 0 },
227                 { 0, 0, 1 }
228         });
229 
230         TestUtils.validateVector3D(expectedTranslation, computedTranslation, 1e-15);
231         TestUtils.validateRealMatrix(expectedRotation, computedRotation, 1e-15);
232 
233     }
234 
235     @Test
236     @DisplayName("Test transformFromInertial (field version) method")
237     void should_return_expected_field_transform_from_inertial() {
238         // Given
239         final Field<Binary64>             field = Binary64Field.getInstance();
240         final FieldAbsoluteDate<Binary64> date  = new FieldAbsoluteDate<>(field);
241         final FieldPVCoordinates<Binary64> pv =
242                 new FieldPVCoordinates<>(new FieldVector3D<>(new Binary64(6378000 + 400000),
243                                                              new Binary64(0),
244                                                              new Binary64(0)),
245                                          new FieldVector3D<>(new Binary64(0),
246                                                              new Binary64(7669),
247                                                              new Binary64(0)));
248 
249         // When
250         final FieldTransform<Binary64> transformFromInertialToLOF = LOFType.TNW.transformFromInertial(date, pv);
251         final FieldVector3D<Binary64>  computedTranslation        = transformFromInertialToLOF.getTranslation();
252         final BlockFieldMatrix<Binary64> computedRotation =
253                 new BlockFieldMatrix<>(transformFromInertialToLOF.getRotation().getMatrix());
254 
255         // Then
256         final Vector3D expectedTranslation = new Vector3D(-(6378000 + 400000), 0, 0);
257         final RealMatrix expectedRotation = new BlockRealMatrix(new double[][] {
258                 { 0, 1, 0 },
259                 { -1, 0, 0 },
260                 { 0, 0, 1 }
261         });
262 
263         TestUtils.validateFieldVector3D(expectedTranslation, computedTranslation, 1e-15);
264         TestUtils.validateFieldMatrix(expectedRotation, computedRotation, 1e-15);
265 
266     }
267 
268     @Test
269     @DisplayName("Test all rotation methods using default method of the interface")
270     void should_return_initial_value_after_multiple_rotations_default_method() {
271         // Given
272         final AbsoluteDate arbitraryEpoch = AbsoluteDate.ARBITRARY_EPOCH;
273         final PVCoordinates pv = new PVCoordinates(new Vector3D(6378000 + 400000, 0, 0),
274                                                    new Vector3D(0, 5422.8, 5422.8));
275 
276         // When
277         final Rotation rotationFromTNWToTNWInertial =
278                 LOFType.TNW_INERTIAL.rotationFromLOF(LOFType.TNW, arbitraryEpoch, pv);
279 
280         final Rotation rotationFromTNWInertialToQSW =
281                 LOFType.QSW.rotationFromLOF(LOFType.TNW_INERTIAL, arbitraryEpoch, pv);
282 
283         final Rotation rotationFromQSWToQSWInertial =
284                 LOFType.QSW_INERTIAL.rotationFromLOF(LOFType.QSW, arbitraryEpoch, pv);
285 
286         final Rotation rotationFromQSWInertialToLVLH =
287                 LOFType.LVLH.rotationFromLOF(LOFType.QSW_INERTIAL, arbitraryEpoch, pv);
288 
289         final Rotation rotationFromLVLHToLVLHInertial =
290                 LOFType.LVLH_INERTIAL.rotationFromLOF(LOFType.LVLH, arbitraryEpoch, pv);
291 
292         final Rotation rotationFromLVLHInertialToLVLH_CCSDS =
293                 LOFType.LVLH_CCSDS.rotationFromLOF(LOFType.LVLH_INERTIAL, arbitraryEpoch, pv);
294 
295         final Rotation rotationFromLVLH_CCSDSToLVLH_CCSDSInertial =
296                 LOFType.LVLH_CCSDS_INERTIAL.rotationFromLOF(LOFType.LVLH_CCSDS, arbitraryEpoch, pv);
297 
298         final Rotation rotationFromLVLH_CCSDSInertialToVVLH =
299                 LOFType.VVLH.rotationFromLOF(LOFType.LVLH_CCSDS_INERTIAL, arbitraryEpoch, pv);
300 
301         final Rotation rotationFromVVLHToVVLHInertial =
302                 LOFType.VVLH_INERTIAL.rotationFromLOF(LOFType.VVLH, arbitraryEpoch, pv);
303 
304         final Rotation rotationFromVVLHInertialToVNC =
305                 LOFType.VNC.rotationFromLOF(LOFType.VVLH_INERTIAL, arbitraryEpoch, pv);
306 
307         final Rotation rotationFromVNCToVNCInertial =
308                 LOFType.VNC_INERTIAL.rotationFromLOF(LOFType.VNC, arbitraryEpoch, pv);
309 
310         final Rotation rotationFromVNCInertialToNTW =
311                 LOFType.NTW.rotationFromLOF(LOFType.VNC_INERTIAL, arbitraryEpoch, pv);
312 
313         final Rotation rotationFromNTWToNTWInertial =
314                 LOFType.NTW_INERTIAL.rotationFromLOF(LOFType.NTW, arbitraryEpoch, pv);
315 
316         final Rotation rotationFromNTWInertialToEQW =
317                 LOFType.EQW.rotationFromLOF(LOFType.NTW_INERTIAL, arbitraryEpoch, pv);
318 
319         final Rotation rotationFromEQWToTNW =
320                 LOF.rotationFromLOFInToLOFOut(LOFType.EQW, LOFType.TNW, arbitraryEpoch, pv);
321 
322         final Rotation rotationFromTNWToTNW =
323                 composeRotations(rotationFromTNWToTNWInertial,
324                                  rotationFromTNWInertialToQSW,
325                                  rotationFromQSWToQSWInertial,
326                                  rotationFromQSWInertialToLVLH,
327                                  rotationFromLVLHToLVLHInertial,
328                                  rotationFromLVLHInertialToLVLH_CCSDS,
329                                  rotationFromLVLH_CCSDSToLVLH_CCSDSInertial,
330                                  rotationFromLVLH_CCSDSInertialToVVLH,
331                                  rotationFromVVLHToVVLHInertial,
332                                  rotationFromVVLHInertialToVNC,
333                                  rotationFromVNCToVNCInertial,
334                                  rotationFromVNCInertialToNTW,
335                                  rotationFromNTWToNTWInertial,
336                                  rotationFromNTWInertialToEQW,
337                                  rotationFromEQWToTNW);
338 
339         final RealMatrix rotationMatrixFromTNWToTNW =
340                 new BlockRealMatrix(rotationFromTNWToTNW.getMatrix());
341 
342         // Then
343         final RealMatrix identityMatrix = MatrixUtils.createRealIdentityMatrix(3);
344 
345         TestUtils.validateRealMatrix(identityMatrix, rotationMatrixFromTNWToTNW, 1e-15);
346 
347     }
348 
349     @Test
350     @DisplayName("Test all rotation methods (field version) using default method of the interface")
351     void should_return_initial_value_after_multiple_field_rotations_default_method() {
352         // Given
353         final Binary64Field               field          = Binary64Field.getInstance();
354         final FieldAbsoluteDate<Binary64> arbitraryEpoch = new FieldAbsoluteDate<>(field, AbsoluteDate.ARBITRARY_EPOCH);
355         final FieldPVCoordinates<Binary64> pv =
356                 new FieldPVCoordinates<>(new FieldVector3D<>(new Binary64(6378000 + 400000),
357                                                              new Binary64(0),
358                                                              new Binary64(0)),
359                                          new FieldVector3D<>(new Binary64(0),
360                                                              new Binary64(5422.8),
361                                                              new Binary64(5422.8)));
362 
363         // When
364         final FieldRotation<Binary64> rotationFromTNWToTNWInertial =
365                 LOFType.TNW_INERTIAL.rotationFromLOF(field, LOFType.TNW, arbitraryEpoch, pv);
366 
367         final FieldRotation<Binary64> rotationFromTNWInertialToQSW =
368                 LOFType.QSW.rotationFromLOF(field, LOFType.TNW_INERTIAL, arbitraryEpoch, pv);
369 
370         final FieldRotation<Binary64> rotationFromQSWToQSWInertial =
371                 LOFType.QSW_INERTIAL.rotationFromLOF(field, LOFType.QSW, arbitraryEpoch, pv);
372 
373         final FieldRotation<Binary64> rotationFromQSWInertialToLVLH =
374                 LOFType.LVLH.rotationFromLOF(field, LOFType.QSW_INERTIAL, arbitraryEpoch, pv);
375 
376         final FieldRotation<Binary64> rotationFromLVLHToLVLHInertial =
377                 LOFType.LVLH_INERTIAL.rotationFromLOF(field, LOFType.LVLH, arbitraryEpoch, pv);
378 
379         final FieldRotation<Binary64> rotationFromLVLHInertialToLVLH_CCSDS =
380                 LOFType.LVLH_CCSDS.rotationFromLOF(field, LOFType.LVLH_INERTIAL, arbitraryEpoch, pv);
381 
382         final FieldRotation<Binary64> rotationFromLVLH_CCSDSToLVLH_CCSDSInertial =
383                 LOFType.LVLH_CCSDS_INERTIAL.rotationFromLOF(field, LOFType.LVLH_CCSDS, arbitraryEpoch, pv);
384 
385         final FieldRotation<Binary64> rotationFromLVLH_CCSDSInertialToVVLH =
386                 LOFType.VVLH.rotationFromLOF(field, LOFType.LVLH_CCSDS_INERTIAL, arbitraryEpoch, pv);
387 
388         final FieldRotation<Binary64> rotationFromVVLHToVVLHInertial =
389                 LOFType.VVLH_INERTIAL.rotationFromLOF(field, LOFType.VVLH, arbitraryEpoch, pv);
390 
391         final FieldRotation<Binary64> rotationFromVVLHInertialToVNC =
392                 LOFType.VNC.rotationFromLOF(field, LOFType.VVLH_INERTIAL, arbitraryEpoch, pv);
393 
394         final FieldRotation<Binary64> rotationFromVNCToVNCInertial =
395                 LOFType.VNC_INERTIAL.rotationFromLOF(field, LOFType.VNC, arbitraryEpoch, pv);
396 
397         final FieldRotation<Binary64> rotationFromVNCInertialToNTW =
398                 LOFType.NTW.rotationFromLOF(field, LOFType.VNC_INERTIAL, arbitraryEpoch, pv);
399 
400         final FieldRotation<Binary64> rotationFromNTWToNTWInertial =
401                 LOFType.NTW_INERTIAL.rotationFromLOF(field, LOFType.NTW, arbitraryEpoch, pv);
402 
403         final FieldRotation<Binary64> rotationFromNTWInertialToEQW =
404                 LOFType.EQW.rotationFromLOF(field, LOFType.NTW_INERTIAL, arbitraryEpoch, pv);
405 
406         final FieldRotation<Binary64> rotationFromEQWToTNW =
407                 LOF.rotationFromLOFInToLOFOut(field, LOFType.EQW, LOFType.TNW, arbitraryEpoch, pv);
408 
409         final FieldRotation<Binary64> rotationFromTNWToTNW =
410                 composeFieldRotations(rotationFromTNWToTNWInertial,
411                                       rotationFromTNWInertialToQSW,
412                                       rotationFromQSWToQSWInertial,
413                                       rotationFromQSWInertialToLVLH,
414                                       rotationFromLVLHToLVLHInertial,
415                                       rotationFromLVLHInertialToLVLH_CCSDS,
416                                       rotationFromLVLH_CCSDSToLVLH_CCSDSInertial,
417                                       rotationFromLVLH_CCSDSInertialToVVLH,
418                                       rotationFromVVLHToVVLHInertial,
419                                       rotationFromVVLHInertialToVNC,
420                                       rotationFromVNCToVNCInertial,
421                                       rotationFromVNCInertialToNTW,
422                                       rotationFromNTWToNTWInertial,
423                                       rotationFromNTWInertialToEQW,
424                                       rotationFromEQWToTNW);
425 
426         final FieldMatrix<Binary64> rotationMatrixFromTNWToTNW =
427                 new BlockFieldMatrix<>(rotationFromTNWToTNW.getMatrix());
428 
429         // Then
430         final RealMatrix identityMatrix = MatrixUtils.createRealIdentityMatrix(3);
431 
432         TestUtils.validateFieldMatrix(identityMatrix, rotationMatrixFromTNWToTNW, 1e-15);
433 
434     }
435 
436     @Test
437     @DisplayName("Test all rotation methods")
438     void should_return_initial_value_after_multiple_rotations() {
439         // Given
440         final PVCoordinates pv = new PVCoordinates(new Vector3D(6378000 + 400000, 0, 0),
441                                                    new Vector3D(0, 5422.8, 5422.8));
442 
443         // When
444         final Rotation rotationFromTNWToTNWInertial =
445                 LOFType.TNW_INERTIAL.rotationFromLOF(LOFType.TNW, pv);
446 
447         final Rotation rotationFromTNWInertialToQSW =
448                 LOFType.QSW.rotationFromLOF(LOFType.TNW_INERTIAL, pv);
449 
450         final Rotation rotationFromQSWToQSWInertial =
451                 LOFType.QSW_INERTIAL.rotationFromLOF(LOFType.QSW, pv);
452 
453         final Rotation rotationFromQSWInertialToLVLH =
454                 LOFType.LVLH.rotationFromLOF(LOFType.QSW_INERTIAL, pv);
455 
456         final Rotation rotationFromLVLHToLVLHInertial =
457                 LOFType.LVLH_INERTIAL.rotationFromLOF(LOFType.LVLH, pv);
458 
459         final Rotation rotationFromLVLHInertialToLVLH_CCSDS =
460                 LOFType.LVLH_CCSDS.rotationFromLOF(LOFType.LVLH_INERTIAL, pv);
461 
462         final Rotation rotationFromLVLH_CCSDSToLVLH_CCSDSInertial =
463                 LOFType.LVLH_CCSDS_INERTIAL.rotationFromLOF(LOFType.LVLH_CCSDS, pv);
464 
465         final Rotation rotationFromLVLH_CCSDSInertialToVVLH =
466                 LOFType.VVLH.rotationFromLOF(LOFType.LVLH_CCSDS_INERTIAL, pv);
467 
468         final Rotation rotationFromVVLHToVVLHInertial =
469                 LOFType.VVLH_INERTIAL.rotationFromLOF(LOFType.VVLH, pv);
470 
471         final Rotation rotationFromVVLHInertialToVNC =
472                 LOFType.VNC.rotationFromLOF(LOFType.VVLH_INERTIAL, pv);
473 
474         final Rotation rotationFromVNCToVNCInertial =
475                 LOFType.VNC_INERTIAL.rotationFromLOF(LOFType.VNC, pv);
476 
477         final Rotation rotationFromVNCInertialToNTW =
478                 LOFType.NTW.rotationFromLOF(LOFType.VNC_INERTIAL, pv);
479 
480         final Rotation rotationFromNTWToNTWInertial =
481                 LOFType.NTW_INERTIAL.rotationFromLOF(LOFType.NTW, pv);
482 
483         final Rotation rotationFromNTWInertialToEQW =
484                 LOFType.EQW.rotationFromLOF(LOFType.NTW_INERTIAL, pv);
485 
486         final Rotation rotationFromEQWToTNW =
487                 LOFType.rotationFromLOFInToLOFOut(LOFType.EQW, LOFType.TNW, pv);
488 
489         final Rotation rotationFromTNWToTNW =
490                 composeRotations(rotationFromTNWToTNWInertial,
491                         rotationFromTNWInertialToQSW,
492                         rotationFromQSWToQSWInertial,
493                         rotationFromQSWInertialToLVLH,
494                         rotationFromLVLHToLVLHInertial,
495                         rotationFromLVLHInertialToLVLH_CCSDS,
496                         rotationFromLVLH_CCSDSToLVLH_CCSDSInertial,
497                         rotationFromLVLH_CCSDSInertialToVVLH,
498                         rotationFromVVLHToVVLHInertial,
499                         rotationFromVVLHInertialToVNC,
500                         rotationFromVNCToVNCInertial,
501                         rotationFromVNCInertialToNTW,
502                         rotationFromNTWToNTWInertial,
503                         rotationFromNTWInertialToEQW,
504                         rotationFromEQWToTNW);
505 
506         final RealMatrix rotationMatrixFromTNWToTNW =
507                 new BlockRealMatrix(rotationFromTNWToTNW.getMatrix());
508 
509         // Then
510         final RealMatrix identityMatrix = MatrixUtils.createRealIdentityMatrix(3);
511 
512         TestUtils.validateRealMatrix(identityMatrix, rotationMatrixFromTNWToTNW, 1e-15);
513 
514     }
515 
516     @Test
517     @DisplayName("Test all rotation methods (field version)")
518     void should_return_initial_value_after_multiple_field_rotations() {
519         // Given
520         final Binary64Field               field = Binary64Field.getInstance();
521         final FieldPVCoordinates<Binary64> pv =
522                 new FieldPVCoordinates<>(new FieldVector3D<>(new Binary64(6378000 + 400000),
523                                                              new Binary64(0),
524                                                              new Binary64(0)),
525                                          new FieldVector3D<>(new Binary64(0),
526                                                              new Binary64(5422.8),
527                                                              new Binary64(5422.8)));
528 
529         // When
530         final FieldRotation<Binary64> rotationFromTNWToTNWInertial =
531                 LOFType.TNW_INERTIAL.rotationFromLOF(field, LOFType.TNW, pv);
532 
533         final FieldRotation<Binary64> rotationFromTNWInertialToQSW =
534                 LOFType.QSW.rotationFromLOF(field, LOFType.TNW_INERTIAL, pv);
535 
536         final FieldRotation<Binary64> rotationFromQSWToQSWInertial =
537                 LOFType.QSW_INERTIAL.rotationFromLOF(field, LOFType.QSW, pv);
538 
539         final FieldRotation<Binary64> rotationFromQSWInertialToLVLH =
540                 LOFType.LVLH.rotationFromLOF(field, LOFType.QSW_INERTIAL, pv);
541 
542         final FieldRotation<Binary64> rotationFromLVLHToLVLHInertial =
543                 LOFType.LVLH_INERTIAL.rotationFromLOF(field, LOFType.LVLH, pv);
544 
545         final FieldRotation<Binary64> rotationFromLVLHInertialToLVLH_CCSDS =
546                 LOFType.LVLH_CCSDS.rotationFromLOF(field, LOFType.LVLH_INERTIAL, pv);
547 
548         final FieldRotation<Binary64> rotationFromLVLH_CCSDSToLVLH_CCSDSInertial =
549                 LOFType.LVLH_CCSDS_INERTIAL.rotationFromLOF(field, LOFType.LVLH_CCSDS, pv);
550 
551         final FieldRotation<Binary64> rotationFromLVLH_CCSDSInertialToVVLH =
552                 LOFType.VVLH.rotationFromLOF(field, LOFType.LVLH_CCSDS_INERTIAL, pv);
553 
554         final FieldRotation<Binary64> rotationFromVVLHToVVLHInertial =
555                 LOFType.VVLH_INERTIAL.rotationFromLOF(field, LOFType.VVLH, pv);
556 
557         final FieldRotation<Binary64> rotationFromVVLHInertialToVNC =
558                 LOFType.VNC.rotationFromLOF(field, LOFType.VVLH_INERTIAL, pv);
559 
560         final FieldRotation<Binary64> rotationFromVNCToVNCInertial =
561                 LOFType.VNC_INERTIAL.rotationFromLOF(field, LOFType.VNC, pv);
562 
563         final FieldRotation<Binary64> rotationFromVNCInertialToNTW =
564                 LOFType.NTW.rotationFromLOF(field, LOFType.VNC_INERTIAL, pv);
565 
566         final FieldRotation<Binary64> rotationFromNTWToNTWInertial =
567                 LOFType.NTW_INERTIAL.rotationFromLOF(field, LOFType.NTW, pv);
568 
569         final FieldRotation<Binary64> rotationFromNTWInertialToEQW =
570                 LOFType.EQW.rotationFromLOF(field, LOFType.NTW_INERTIAL, pv);
571 
572         final FieldRotation<Binary64> rotationFromEQWToTNW =
573                 LOFType.rotationFromLOFInToLOFOut(field, LOFType.EQW, LOFType.TNW, pv);
574 
575         final FieldRotation<Binary64> rotationFromTNWToTNW =
576                 composeFieldRotations(rotationFromTNWToTNWInertial,
577                                       rotationFromTNWInertialToQSW,
578                                       rotationFromQSWToQSWInertial,
579                                       rotationFromQSWInertialToLVLH,
580                                       rotationFromLVLHToLVLHInertial,
581                                       rotationFromLVLHInertialToLVLH_CCSDS,
582                                       rotationFromLVLH_CCSDSToLVLH_CCSDSInertial,
583                                       rotationFromLVLH_CCSDSInertialToVVLH,
584                                       rotationFromVVLHToVVLHInertial,
585                                       rotationFromVVLHInertialToVNC,
586                                       rotationFromVNCToVNCInertial,
587                                       rotationFromVNCInertialToNTW,
588                                       rotationFromNTWToNTWInertial,
589                                       rotationFromNTWInertialToEQW,
590                                       rotationFromEQWToTNW);
591 
592         final FieldMatrix<Binary64> rotationMatrixFromTNWToTNW =
593                 new BlockFieldMatrix<>(rotationFromTNWToTNW.getMatrix());
594 
595         // Then
596         final RealMatrix identityMatrix = MatrixUtils.createRealIdentityMatrix(3);
597 
598         TestUtils.validateFieldMatrix(identityMatrix, rotationMatrixFromTNWToTNW, 1e-15);
599 
600     }
601 
602     @Test
603     @DisplayName("Test isQuasiInertialMethod")
604     void should_return_expected_boolean() {
605 
606         // Local orbital frame considered as pseudo-inertial
607         Assertions.assertTrue(LOFType.TNW_INERTIAL.isQuasiInertial());
608         Assertions.assertTrue(LOFType.NTW_INERTIAL.isQuasiInertial());
609         Assertions.assertTrue(LOFType.QSW_INERTIAL.isQuasiInertial());
610         Assertions.assertTrue(LOFType.VNC_INERTIAL.isQuasiInertial());
611         Assertions.assertTrue(LOFType.LVLH_INERTIAL.isQuasiInertial());
612         Assertions.assertTrue(LOFType.LVLH_CCSDS_INERTIAL.isQuasiInertial());
613         Assertions.assertTrue(LOFType.EQW.isQuasiInertial());
614         Assertions.assertTrue(LOFType.VVLH_INERTIAL.isQuasiInertial());
615 
616         // Local orbital frame considered as non pseudo-inertial
617         Assertions.assertFalse(LOFType.TNW.isQuasiInertial());
618         Assertions.assertFalse(LOFType.NTW.isQuasiInertial());
619         Assertions.assertFalse(LOFType.QSW.isQuasiInertial());
620         Assertions.assertFalse(LOFType.VNC.isQuasiInertial());
621         Assertions.assertFalse(LOFType.LVLH.isQuasiInertial());
622         Assertions.assertFalse(LOFType.LVLH_CCSDS.isQuasiInertial());
623         Assertions.assertFalse(LOFType.VVLH.isQuasiInertial());
624 
625     }
626 
627     private Rotation composeRotations(final Rotation... rotations) {
628 
629         Rotation composedRotations = null;
630 
631         for (Rotation rotation : rotations) {
632             if (composedRotations == null) {
633                 composedRotations = rotation;
634             }
635             else {
636                 composedRotations = composedRotations.compose(rotation, RotationConvention.FRAME_TRANSFORM);
637             }
638         }
639 
640         return composedRotations;
641     }
642 
643     @SafeVarargs private final <T extends CalculusFieldElement<T>> FieldRotation<T> composeFieldRotations(
644             final FieldRotation<T>... rotations) {
645 
646         FieldRotation<T> composedRotations = null;
647 
648         for (FieldRotation<T> rotation : rotations) {
649             if (composedRotations == null) {
650                 composedRotations = rotation;
651             }
652             else {
653                 composedRotations = composedRotations.compose(rotation, RotationConvention.FRAME_TRANSFORM);
654             }
655         }
656 
657         return composedRotations;
658     }
659 
660     private Transform composeTransform(final AbsoluteDate date, final Transform... transforms){
661         Transform composedTransform = null;
662 
663         for (Transform transform : transforms) {
664             if (composedTransform == null) {
665                 composedTransform = transform;
666             }
667             else {
668                 composedTransform = new Transform(date, composedTransform, transform);
669             }
670         }
671 
672         return composedTransform;
673     }
674 
675     @SafeVarargs
676     private final <T extends CalculusFieldElement<T>> FieldTransform<T> composeFieldTransform(
677             final FieldAbsoluteDate<T> date,
678             final FieldTransform<T>... transforms) {
679         FieldTransform<T> composedTransform = null;
680 
681         for (FieldTransform<T> transform : transforms) {
682             if (composedTransform == null) {
683                 composedTransform = transform;
684             }
685             else {
686                 composedTransform = new FieldTransform<>(date, composedTransform, transform);
687             }
688         }
689 
690         return composedTransform;
691     }
692 
693     private void checkFrame(LOFType type, AbsoluteDate date,
694                             Vector3D expectedXDirection, Vector3D expectedYDirection,
695                             Vector3D expectedZDirection, Vector3D expectedRotationDirection) {
696         LocalOrbitalFrame lof = new LocalOrbitalFrame(FramesFactory.getGCRF(), type, provider, type.name());
697 
698         Transform     t   = lof.getTransformTo(FramesFactory.getGCRF(), date);
699         PVCoordinates pv1 = t.transformPVCoordinates(PVCoordinates.ZERO);
700         Vector3D      p1  = pv1.getPosition();
701         Vector3D      v1  = pv1.getVelocity();
702         PVCoordinates pv2 = provider.getPVCoordinates(date, FramesFactory.getGCRF());
703         Vector3D      p2  = pv2.getPosition();
704         Vector3D      v2  = pv2.getVelocity();
705         Assertions.assertEquals(0, p1.subtract(p2).getNorm(), 1.0e-14 * p1.getNorm());
706         Assertions.assertEquals(0, v1.subtract(v2).getNorm(), 1.0e-14 * v1.getNorm());
707 
708         Vector3D xDirection = t.transformVector(Vector3D.PLUS_I);
709         Vector3D yDirection = t.transformVector(Vector3D.PLUS_J);
710         Vector3D zDirection = t.transformVector(Vector3D.PLUS_K);
711         Assertions.assertEquals(0, Vector3D.angle(expectedXDirection, xDirection), 2.0e-15);
712         Assertions.assertEquals(0, Vector3D.angle(expectedYDirection, yDirection), 1.0e-15);
713         Assertions.assertEquals(0, Vector3D.angle(expectedZDirection, zDirection), 1.0e-15);
714         Assertions.assertEquals(0, Vector3D.angle(expectedRotationDirection, t.getRotationRate()), 1.0e-15);
715 
716         Assertions.assertEquals(initialOrbit.getKeplerianMeanMotion(), t.getRotationRate().getNorm(), 1.0e-7);
717 
718     }
719 
720     @BeforeEach
721     public void setUp() {
722         inertialFrame = FramesFactory.getGCRF();
723         initDate = AbsoluteDate.J2000_EPOCH.shiftedBy(584.);
724         initialOrbit =
725                 new KeplerianOrbit(7209668.0, 0.5e-4, 1.7, 2.1, 2.9, 6.2, PositionAngleType.TRUE,
726                                    inertialFrame, initDate, 3.986004415e14);
727         provider = new KeplerianPropagator(initialOrbit);
728 
729     }
730 
731     private Frame                 inertialFrame;
732     private AbsoluteDate          initDate;
733     private Orbit                 initialOrbit;
734     private PVCoordinatesProvider provider;
735 
736 }