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.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.errors.OrekitException;
39  import org.orekit.errors.OrekitMessages;
40  import org.orekit.files.ccsds.definitions.OrbitRelativeFrame;
41  import org.orekit.orbits.KeplerianOrbit;
42  import org.orekit.orbits.Orbit;
43  import org.orekit.orbits.PositionAngleType;
44  import org.orekit.propagation.analytical.KeplerianPropagator;
45  import org.orekit.time.AbsoluteDate;
46  import org.orekit.time.FieldAbsoluteDate;
47  import org.orekit.utils.FieldPVCoordinates;
48  import org.orekit.utils.PVCoordinates;
49  import org.orekit.utils.PVCoordinatesProvider;
50  
51  public class LocalOrbitalFrameTest {
52  
53      @Test
54      public void testIssue1282() {
55          // GIVEN
56          final Vector3D position = new Vector3D(0, 0, 1);
57          final Vector3D velocity = new Vector3D(0, 1, 0);
58          final PVCoordinates pvCoordinates  = new PVCoordinates(position, velocity);
59  
60          // WHEN
61          final Rotation actualRotation = LOFType.EQW.rotationFromInertial(pvCoordinates);
62  
63          // THEN
64          final Rotation expectedRotation = new Rotation(Vector3D.MINUS_J,Vector3D.MINUS_I, Vector3D.PLUS_I, Vector3D.PLUS_K);
65          Assertions.assertArrayEquals(expectedRotation.getMatrix(), actualRotation.getMatrix());
66      }
67  
68      @Test
69      public void testIssue977() {
70          LOFType type = LOFType.TNW;
71          LocalOrbitalFrame lof = new LocalOrbitalFrame(FramesFactory.getGCRF(), type, provider, type.name());
72          Assertions.assertThrows(UnsupportedOperationException.class,
73                                  () -> lof.getTransformProvider().getTransform(FieldAbsoluteDate.getJ2000Epoch(Binary64Field.getInstance())));
74      }
75  
76      @Test
77      public void testTNW() {
78          AbsoluteDate  date = initDate.shiftedBy(400);
79          PVCoordinates pv   = provider.getPVCoordinates(date, inertialFrame);
80          checkFrame(LOFType.TNW, date,
81                     pv.getVelocity(),
82                     Vector3D.crossProduct(pv.getMomentum(), pv.getVelocity()),
83                     pv.getMomentum(),
84                     pv.getMomentum().negate());
85          Assertions.assertEquals(OrbitRelativeFrame.TNW, LOFType.TNW.toOrbitRelativeFrame());
86      }
87  
88      @Test
89      public void testQSW() {
90          AbsoluteDate  date = initDate.shiftedBy(400);
91          PVCoordinates pv   = provider.getPVCoordinates(date, inertialFrame);
92          checkFrame(LOFType.QSW, date,
93                     pv.getPosition(),
94                     Vector3D.crossProduct(pv.getMomentum(), pv.getPosition()),
95                     pv.getMomentum(),
96                     pv.getMomentum().negate());
97          Assertions.assertEquals(OrbitRelativeFrame.QSW, LOFType.QSW.toOrbitRelativeFrame());
98      }
99  
100     @Test
101     public void testLVLH() {
102         AbsoluteDate  date = initDate.shiftedBy(400);
103         PVCoordinates pv   = provider.getPVCoordinates(date, inertialFrame);
104         checkFrame(LOFType.LVLH, date,
105                    pv.getPosition(),
106                    Vector3D.crossProduct(pv.getMomentum(), pv.getPosition()),
107                    pv.getMomentum(),
108                    pv.getMomentum().negate());
109         try {
110             LOFType.LVLH.toOrbitRelativeFrame();
111             Assertions.fail("an exception should have been thrown");
112         } catch (OrekitException oe) {
113             Assertions.assertEquals(OrekitMessages.CCSDS_DIFFERENT_LVLH_DEFINITION, oe.getSpecifier());
114         }
115     }
116 
117     @Test
118     public void testLVLH_CCSDS() {
119         AbsoluteDate  date = initDate.shiftedBy(400);
120         PVCoordinates pv   = provider.getPVCoordinates(date, inertialFrame);
121         checkFrame(LOFType.LVLH_CCSDS, date,
122                    Vector3D.crossProduct(pv.getMomentum(), pv.getPosition()),
123                    pv.getMomentum().negate(),
124                    pv.getPosition().negate(),
125                    pv.getMomentum().negate());
126         Assertions.assertEquals(OrbitRelativeFrame.LVLH, LOFType.LVLH_CCSDS.toOrbitRelativeFrame());
127     }
128 
129     @Test
130     public void testVVLH() {
131         AbsoluteDate  date = initDate.shiftedBy(400);
132         PVCoordinates pv   = provider.getPVCoordinates(date, inertialFrame);
133         checkFrame(LOFType.VVLH, date,
134                    Vector3D.crossProduct(pv.getMomentum(), pv.getPosition()),
135                    pv.getMomentum().negate(),
136                    pv.getPosition().negate(),
137                    pv.getMomentum().negate());
138         Assertions.assertEquals(OrbitRelativeFrame.LVLH, LOFType.VVLH.toOrbitRelativeFrame());
139     }
140 
141     @Test
142     public void testVNC() {
143         AbsoluteDate  date = initDate.shiftedBy(400);
144         PVCoordinates pv   = provider.getPVCoordinates(date, inertialFrame);
145         checkFrame(LOFType.VNC, date,
146                    pv.getVelocity(),
147                    pv.getMomentum(),
148                    Vector3D.crossProduct(pv.getVelocity(), pv.getMomentum()),
149                    pv.getMomentum().negate());
150         Assertions.assertEquals(OrbitRelativeFrame.VNC_ROTATING, LOFType.VNC.toOrbitRelativeFrame());
151     }
152 
153     @Test
154     public void testENU() {
155         AbsoluteDate      date = initDate.shiftedBy(400);
156         PVCoordinates     pv   = provider.getPVCoordinates(date, inertialFrame);
157         Transform         t    = LOFType.ENU.transformFromInertial(date, pv).getInverse();
158         Assertions.assertEquals("ENU", LOFType.ENU.getName());
159         Assertions.assertNull(LOFType.ENU.toOrbitRelativeFrame());
160 
161         // Z aligned with position
162         Assertions.assertEquals(0.0,
163                                 Vector3D.angle(t.transformVector(Vector3D.PLUS_K), pv.getPosition()),
164                                 2.0e-16);
165 
166         // North Pole in the (+Y, ±Z) half-plane
167         Vector3D northPole = t.getInverse().transformVector(Vector3D.PLUS_K);
168         Assertions.assertEquals(0.0, northPole.getX(), 2.0e-16);
169         Assertions.assertTrue(northPole.getY() > 0.0);
170 
171     }
172 
173     @Test
174     public void testENUField() {
175         final Field<Binary64>        field = Binary64Field.getInstance();
176         FieldAbsoluteDate<Binary64>  date  = new FieldAbsoluteDate<>(field, initDate.shiftedBy(400));
177         FieldPVCoordinates<Binary64> pv    = new FieldPVCoordinates<>(field,
178                                                                       provider.getPVCoordinates(date.toAbsoluteDate(),
179                                                                                                 inertialFrame));
180         FieldTransform<Binary64>     t     = LOFType.ENU.transformFromInertial(date, pv).getInverse();
181         FieldVector3D<Binary64>      pK    = FieldVector3D.getPlusK(field);
182 
183         // Z aligned with position
184         Assertions.assertEquals(0.0,
185                                 FieldVector3D.angle(t.transformVector(pK), pv.getPosition()).getReal(),
186                                 2.0e-16);
187 
188         // North Pole in the (+Y, ±Z) half-plane
189         FieldVector3D<Binary64> northPole = t.getInverse().transformVector(pK);
190         Assertions.assertEquals(0.0, northPole.getX().getReal(), 2.0e-16);
191         Assertions.assertTrue(northPole.getY().getReal() > 0.0);
192 
193     }
194 
195     @Test
196     public void testENUAtNorthPole() {
197         AbsoluteDate  date    = initDate.shiftedBy(400);
198         PVCoordinates atNorth = new PVCoordinates(new Vector3D(0.0, 0.0, 8.0e6),
199                                                   new Vector3D(8.0e3, 0.0, 0.0));
200         Transform     t       = LOFType.ENU.transformFromInertial(date, atNorth).getInverse();
201         Assertions.assertEquals(0, Vector3D.angle(Vector3D.PLUS_J,  t.transformVector(Vector3D.PLUS_I)), 3.0e-16);
202         Assertions.assertEquals(0, Vector3D.angle(Vector3D.MINUS_I, t.transformVector(Vector3D.PLUS_J)), 3.0e-16);
203         Assertions.assertEquals(0, Vector3D.angle(Vector3D.PLUS_K,  t.transformVector(Vector3D.PLUS_K)), 3.0e-16);
204     }
205 
206     @Test
207     public void testENUAtNorthPoleField() {
208         final Field<Binary64>        field   = Binary64Field.getInstance();
209         FieldAbsoluteDate<Binary64>  date    = new FieldAbsoluteDate<>(field, initDate.shiftedBy(400));
210         FieldPVCoordinates<Binary64> atNorth = new FieldPVCoordinates<>(new FieldVector3D<>(new Binary64(0.0),
211                                                                                             new Binary64(0.0),
212                                                                                             new Binary64(8.0e6)),
213                                                                         new FieldVector3D<>(new Binary64(8.0e3),
214                                                                                             new Binary64(0.0),
215                                                                                             new Binary64(0.0)));
216         FieldTransform<Binary64>    t        = LOFType.ENU.transformFromInertial(date, atNorth).getInverse();
217         FieldVector3D<Binary64>     pI       = FieldVector3D.getPlusI(field);
218         FieldVector3D<Binary64>     mI       = FieldVector3D.getMinusI(field);
219         FieldVector3D<Binary64>     pJ       = FieldVector3D.getPlusJ(field);
220         FieldVector3D<Binary64>     pK       = FieldVector3D.getPlusK(field);
221         Assertions.assertEquals(0, FieldVector3D.angle(pJ, t.transformVector(pI)).getReal(), 3.0e-16);
222         Assertions.assertEquals(0, FieldVector3D.angle(mI, t.transformVector(pJ)).getReal(), 3.0e-16);
223         Assertions.assertEquals(0, FieldVector3D.angle(pK, t.transformVector(pK)).getReal(), 3.0e-16);
224     }
225 
226     @Test
227     public void testENUAtSouthPole() {
228         AbsoluteDate date     = initDate.shiftedBy(400);
229         PVCoordinates atSouth = new PVCoordinates(new Vector3D(0.0, 0.0, -8.0e6),
230                                                   new Vector3D(-8.0e3, 0.0, 0.0));
231         Transform     t       = LOFType.ENU.transformFromInertial(date, atSouth).getInverse();
232         Assertions.assertEquals(0, Vector3D.angle(Vector3D.PLUS_J,  t.transformVector(Vector3D.PLUS_I)), 3.0e-16);
233         Assertions.assertEquals(0, Vector3D.angle(Vector3D.PLUS_I,  t.transformVector(Vector3D.PLUS_J)), 3.0e-16);
234         Assertions.assertEquals(0, Vector3D.angle(Vector3D.MINUS_K, t.transformVector(Vector3D.PLUS_K)), 3.0e-16);
235     }
236 
237     @Test
238     public void testENUAtSouthPoleField() {
239         final Field<Binary64>        field   = Binary64Field.getInstance();
240         FieldAbsoluteDate<Binary64>  date    = new FieldAbsoluteDate<>(field, initDate.shiftedBy(400));
241         FieldPVCoordinates<Binary64> atSouth = new FieldPVCoordinates<>(new FieldVector3D<>(new Binary64(0.0),
242                                                                                             new Binary64(0.0),
243                                                                                             new Binary64(-8.0e6)),
244                                                                         new FieldVector3D<>(new Binary64(-8.0e3),
245                                                                                             new Binary64(0.0),
246                                                                                             new Binary64(0.0)));
247         FieldTransform<Binary64>    t        = LOFType.ENU.transformFromInertial(date, atSouth).getInverse();
248         FieldVector3D<Binary64>     pI       = FieldVector3D.getPlusI(field);
249         FieldVector3D<Binary64>     pJ       = FieldVector3D.getPlusJ(field);
250         FieldVector3D<Binary64>     pK       = FieldVector3D.getPlusK(field);
251         FieldVector3D<Binary64>     mK       = FieldVector3D.getMinusK(field);
252         Assertions.assertEquals(0, FieldVector3D.angle(pJ, t.transformVector(pI)).getReal(), 3.0e-16);
253         Assertions.assertEquals(0, FieldVector3D.angle(pI, t.transformVector(pJ)).getReal(), 3.0e-16);
254         Assertions.assertEquals(0, FieldVector3D.angle(mK, t.transformVector(pK)).getReal(), 3.0e-16);
255     }
256 
257     @Test
258     public void testNED() {
259         AbsoluteDate      date = initDate.shiftedBy(400);
260         PVCoordinates     pv   = provider.getPVCoordinates(date, inertialFrame);
261         Transform         t    = LOFType.NED.transformFromInertial(date, pv).getInverse();
262         Assertions.assertNull(LOFType.NED.toOrbitRelativeFrame());
263 
264         // Z aligned with opposite of position
265         Assertions.assertEquals(0.0,
266                                 Vector3D.angle(t.transformVector(Vector3D.PLUS_K), pv.getPosition().negate()),
267                                 2.0e-16);
268 
269         // North Pole in the (+X, ±Z) half-plane
270         Vector3D northPole = t.getInverse().transformVector(Vector3D.PLUS_K);
271         Assertions.assertEquals(0.0, northPole.getY(), 2.0e-16);
272         Assertions.assertTrue(northPole.getX() > 0.0);
273 
274     }
275 
276     @Test
277     public void testNEDField() {
278         final Field<Binary64>        field = Binary64Field.getInstance();
279         FieldAbsoluteDate<Binary64>  date  = new FieldAbsoluteDate<>(field, initDate.shiftedBy(400));
280         FieldPVCoordinates<Binary64> pv    = new FieldPVCoordinates<>(field,
281                                                                       provider.getPVCoordinates(date.toAbsoluteDate(),
282                                                                                                 inertialFrame));
283         FieldTransform<Binary64>     t     = LOFType.NED.transformFromInertial(date, pv).getInverse();
284         FieldVector3D<Binary64>      pK    = FieldVector3D.getPlusK(field);
285 
286         // Z aligned with opposite of position
287         Assertions.assertEquals(0.0,
288                                 FieldVector3D.angle(t.transformVector(pK), pv.getPosition().negate()).getReal(),
289                                 2.0e-16);
290 
291         // North Pole in the (+X, ±Z) half-plane
292         FieldVector3D<Binary64> northPole = t.getInverse().transformVector(pK);
293         Assertions.assertEquals(0.0, northPole.getY().getReal(), 2.0e-16);
294         Assertions.assertTrue(northPole.getX().getReal() > 0.0);
295 
296     }
297 
298     @Test
299     public void testNEDAtNorthPole() {
300         AbsoluteDate  date    = initDate.shiftedBy(400);
301         PVCoordinates atNorth = new PVCoordinates(new Vector3D(0.0, 0.0, 8.0e6),
302                                                   new Vector3D(8.0e3, 0.0, 0.0));
303         Transform     t       = LOFType.NED.transformFromInertial(date, atNorth).getInverse();
304         Assertions.assertEquals(0, Vector3D.angle(Vector3D.MINUS_I, t.transformVector(Vector3D.PLUS_I)), 3.0e-16);
305         Assertions.assertEquals(0, Vector3D.angle(Vector3D.PLUS_J,  t.transformVector(Vector3D.PLUS_J)), 3.0e-16);
306         Assertions.assertEquals(0, Vector3D.angle(Vector3D.MINUS_K, t.transformVector(Vector3D.PLUS_K)), 3.0e-16);
307     }
308 
309     @Test
310     public void testNEDAtNorthPoleField() {
311         final Field<Binary64>        field   = Binary64Field.getInstance();
312         FieldAbsoluteDate<Binary64>  date    = new FieldAbsoluteDate<>(field, initDate.shiftedBy(400));
313         FieldPVCoordinates<Binary64> atNorth = new FieldPVCoordinates<>(new FieldVector3D<>(new Binary64(0.0),
314                                                                                             new Binary64(0.0),
315                                                                                             new Binary64(8.0e6)),
316                                                                         new FieldVector3D<>(new Binary64(8.0e3),
317                                                                                             new Binary64(0.0),
318                                                                                             new Binary64(0.0)));
319         FieldTransform<Binary64>    t        = LOFType.NED.transformFromInertial(date, atNorth).getInverse();
320         FieldVector3D<Binary64>     pI       = FieldVector3D.getPlusI(field);
321         FieldVector3D<Binary64>     mI       = FieldVector3D.getMinusI(field);
322         FieldVector3D<Binary64>     pJ       = FieldVector3D.getPlusJ(field);
323         FieldVector3D<Binary64>     pK       = FieldVector3D.getPlusK(field);
324         FieldVector3D<Binary64>     mK       = FieldVector3D.getMinusK(field);
325         Assertions.assertEquals(0, FieldVector3D.angle(mI, t.transformVector(pI)).getReal(), 3.0e-16);
326         Assertions.assertEquals(0, FieldVector3D.angle(pJ, t.transformVector(pJ)).getReal(), 3.0e-16);
327         Assertions.assertEquals(0, FieldVector3D.angle(mK, t.transformVector(pK)).getReal(), 3.0e-16);
328     }
329 
330     @Test
331     public void testNEDAtSouthPole() {
332         AbsoluteDate  date    = initDate.shiftedBy(400);
333         PVCoordinates atSouth = new PVCoordinates(new Vector3D(0.0, 0.0, -8.0e6),
334                                                   new Vector3D(-8.0e3, 0.0, 0.0));
335         Transform     t       = LOFType.NED.transformFromInertial(date, atSouth).getInverse();
336         Assertions.assertEquals(0, Vector3D.angle(Vector3D.PLUS_I, t.transformVector(Vector3D.PLUS_I)), 3.0e-16);
337         Assertions.assertEquals(0, Vector3D.angle(Vector3D.PLUS_J, t.transformVector(Vector3D.PLUS_J)), 3.0e-16);
338         Assertions.assertEquals(0, Vector3D.angle(Vector3D.PLUS_K, t.transformVector(Vector3D.PLUS_K)), 3.0e-16);
339     }
340 
341     @Test
342     public void testNEDAtSouthPoleField() {
343         final Field<Binary64>        field   = Binary64Field.getInstance();
344         FieldAbsoluteDate<Binary64>  date    = new FieldAbsoluteDate<>(field, initDate.shiftedBy(400));
345         FieldPVCoordinates<Binary64> atSouth = new FieldPVCoordinates<>(new FieldVector3D<>(new Binary64(0.0),
346                                                                                             new Binary64(0.0),
347                                                                                             new Binary64(-8.0e6)),
348                                                                         new FieldVector3D<>(new Binary64(-8.0e3),
349                                                                                             new Binary64(0.0),
350                                                                                             new Binary64(0.0)));
351         FieldTransform<Binary64>    t        = LOFType.NED.transformFromInertial(date, atSouth).getInverse();
352         FieldVector3D<Binary64>     pI       = FieldVector3D.getPlusI(field);
353         FieldVector3D<Binary64>     pJ       = FieldVector3D.getPlusJ(field);
354         FieldVector3D<Binary64>     pK       = FieldVector3D.getPlusK(field);
355         Assertions.assertEquals(0, FieldVector3D.angle(pI, t.transformVector(pI)).getReal(), 3.0e-16);
356         Assertions.assertEquals(0, FieldVector3D.angle(pJ, t.transformVector(pJ)).getReal(), 3.0e-16);
357         Assertions.assertEquals(0, FieldVector3D.angle(pK, t.transformVector(pK)).getReal(), 3.0e-16);
358     }
359 
360     @Test
361     @DisplayName("Test transformFromLOFInToLOFOut method")
362     void should_return_expected_transform_from_LOFIn_To_LOFOut() {
363         // Given
364         final AbsoluteDate date = new AbsoluteDate();
365         final PVCoordinates pv = new PVCoordinates(new Vector3D(6378000 + 400000, 0, 0),
366                                                    new Vector3D(0, 7669, 0));
367 
368         // When
369         final Transform transformFromTNWToQSW = LOF.transformFromLOFInToLOFOut(LOFType.TNW, LOFType.QSW, date, pv);
370         final Transform transformFromQSWToNTW = LOF.transformFromLOFInToLOFOut(LOFType.QSW, LOFType.NTW, date, pv);
371         final Transform transformFromNTWToTNW = LOF.transformFromLOFInToLOFOut(LOFType.NTW, LOFType.TNW, date, pv);
372         final Transform composedTransform = composeTransform(date,
373                                                              transformFromTNWToQSW,
374                                                              transformFromQSWToNTW,
375                                                              transformFromNTWToTNW);
376 
377         final Vector3D        computedTranslation = composedTransform.getTranslation();
378         final BlockRealMatrix computedRotation    = new BlockRealMatrix(composedTransform.getRotation().getMatrix());
379 
380         // Then
381         final Vector3D expectedTranslation = new Vector3D(0, 0, 0);
382         final RealMatrix expectedRotation = MatrixUtils.createRealIdentityMatrix(3);
383 
384         TestUtils.validateVector3D(expectedTranslation, computedTranslation, 1e-15);
385         TestUtils.validateRealMatrix(expectedRotation, computedRotation, 1e-15);
386 
387     }
388 
389     @Test
390     @DisplayName("Test transformFromLOFInToLOFOut (field version) method")
391     void should_return_expected_field_transform_from_LOFIn_To_LOFOut() {
392         // Given
393         final Field<Binary64> field = Binary64Field.getInstance();
394         final FieldAbsoluteDate<Binary64> date = new FieldAbsoluteDate<>(field);
395         final FieldPVCoordinates<Binary64> pv =
396                 new FieldPVCoordinates<>(new FieldVector3D<>(new Binary64(6378000 + 400000),
397                                                              new Binary64(0),
398                                                              new Binary64(0)),
399                                          new FieldVector3D<>(new Binary64(0),
400                                                              new Binary64(7669),
401                                                              new Binary64(0)));
402 
403         // When
404         final FieldTransform<Binary64> transformFromTNWToQSW =
405                 LOF.transformFromLOFInToLOFOut(LOFType.TNW, LOFType.QSW, date, pv);
406         final FieldTransform<Binary64> transformFromQSWToNTW =
407                 LOF.transformFromLOFInToLOFOut(LOFType.QSW, LOFType.NTW, date, pv);
408         final FieldTransform<Binary64> transformFromNTWToTNW =
409                 LOF.transformFromLOFInToLOFOut(LOFType.NTW, LOFType.TNW, date, pv);
410         final FieldTransform<Binary64> composedTransform = composeFieldTransform(date,
411                                                                                   transformFromTNWToQSW,
412                                                                                   transformFromQSWToNTW,
413                                                                                   transformFromNTWToTNW);
414 
415         final FieldVector3D<Binary64>    computedTranslation = composedTransform.getTranslation();
416         final BlockFieldMatrix<Binary64> computedRotation    =
417                 new BlockFieldMatrix<>(composedTransform.getRotation().getMatrix());
418 
419         // Then
420         final Vector3D expectedTranslation = new Vector3D(0, 0, 0);
421         final RealMatrix expectedRotation = MatrixUtils.createRealIdentityMatrix(3);
422 
423         TestUtils.validateFieldVector3D(expectedTranslation, computedTranslation, 1e-15);
424         TestUtils.validateFieldMatrix(expectedRotation, computedRotation, 1e-15);
425 
426     }
427 
428     @Test
429     @DisplayName("Test transformFromInertial method")
430     void should_return_expected_transform_from_inertial() {
431         // Given
432         final AbsoluteDate date = new AbsoluteDate();
433         final PVCoordinates pv = new PVCoordinates(new Vector3D(6378000 + 400000, 0, 0),
434                                                    new Vector3D(0, 7669, 0));
435 
436         // When
437         final Transform  transformFromInertialToLOF = LOFType.TNW.transformFromInertial(date, pv);
438         final Vector3D   computedTranslation        = transformFromInertialToLOF.getTranslation();
439         final RealMatrix computedRotation           =
440                 new BlockRealMatrix(transformFromInertialToLOF.getRotation().getMatrix());
441 
442         // Then
443         final Vector3D expectedTranslation = new Vector3D(-(6378000 + 400000), 0, 0);
444         final RealMatrix expectedRotation = new BlockRealMatrix(new double[][] {
445                 {  0, 1, 0 },
446                 { -1, 0, 0 },
447                 {  0, 0, 1 }
448         });
449 
450         TestUtils.validateVector3D(expectedTranslation, computedTranslation, 1e-15);
451         TestUtils.validateRealMatrix(expectedRotation, computedRotation, 1e-15);
452 
453     }
454 
455     @Test
456     @DisplayName("Test transformFromInertial (field version) method")
457     void should_return_expected_field_transform_from_inertial() {
458         // Given
459         final Field<Binary64>             field = Binary64Field.getInstance();
460         final FieldAbsoluteDate<Binary64> date  = new FieldAbsoluteDate<>(field);
461         final FieldPVCoordinates<Binary64> pv =
462                 new FieldPVCoordinates<>(new FieldVector3D<>(new Binary64(6378000 + 400000),
463                                                              new Binary64(0),
464                                                              new Binary64(0)),
465                                          new FieldVector3D<>(new Binary64(0),
466                                                              new Binary64(7669),
467                                                              new Binary64(0)));
468 
469         // When
470         final FieldTransform<Binary64> transformFromInertialToLOF = LOFType.TNW.transformFromInertial(date, pv);
471         final FieldVector3D<Binary64>  computedTranslation        = transformFromInertialToLOF.getTranslation();
472         final BlockFieldMatrix<Binary64> computedRotation =
473                 new BlockFieldMatrix<>(transformFromInertialToLOF.getRotation().getMatrix());
474 
475         // Then
476         final Vector3D expectedTranslation = new Vector3D(-(6378000 + 400000), 0, 0);
477         final RealMatrix expectedRotation = new BlockRealMatrix(new double[][] {
478                 {  0, 1, 0 },
479                 { -1, 0, 0 },
480                 {  0, 0, 1 }
481         });
482 
483         TestUtils.validateFieldVector3D(expectedTranslation, computedTranslation, 1e-15);
484         TestUtils.validateFieldMatrix(expectedRotation, computedRotation, 1e-15);
485 
486     }
487 
488     @Test
489     @DisplayName("Test all rotation methods using default method of the interface")
490     void should_return_initial_value_after_multiple_rotations_default_method() {
491         // Given
492         final AbsoluteDate arbitraryEpoch = AbsoluteDate.ARBITRARY_EPOCH;
493         final PVCoordinates pv = new PVCoordinates(new Vector3D(6378000 + 400000, 0, 0),
494                                                    new Vector3D(0, 5422.8, 5422.8));
495 
496         // When
497         final Rotation rotationFromTNWToTNWInertial =
498                 LOFType.TNW_INERTIAL.rotationFromLOF(LOFType.TNW, arbitraryEpoch, pv);
499 
500         final Rotation rotationFromTNWInertialToQSW =
501                 LOFType.QSW.rotationFromLOF(LOFType.TNW_INERTIAL, arbitraryEpoch, pv);
502 
503         final Rotation rotationFromQSWToQSWInertial =
504                 LOFType.QSW_INERTIAL.rotationFromLOF(LOFType.QSW, arbitraryEpoch, pv);
505 
506         final Rotation rotationFromQSWInertialToLVLH =
507                 LOFType.LVLH.rotationFromLOF(LOFType.QSW_INERTIAL, arbitraryEpoch, pv);
508 
509         final Rotation rotationFromLVLHToLVLHInertial =
510                 LOFType.LVLH_INERTIAL.rotationFromLOF(LOFType.LVLH, arbitraryEpoch, pv);
511 
512         final Rotation rotationFromLVLHInertialToLVLH_CCSDS =
513                 LOFType.LVLH_CCSDS.rotationFromLOF(LOFType.LVLH_INERTIAL, arbitraryEpoch, pv);
514 
515         final Rotation rotationFromLVLH_CCSDSToLVLH_CCSDSInertial =
516                 LOFType.LVLH_CCSDS_INERTIAL.rotationFromLOF(LOFType.LVLH_CCSDS, arbitraryEpoch, pv);
517 
518         final Rotation rotationFromLVLH_CCSDSInertialToVVLH =
519                 LOFType.VVLH.rotationFromLOF(LOFType.LVLH_CCSDS_INERTIAL, arbitraryEpoch, pv);
520 
521         final Rotation rotationFromVVLHToVVLHInertial =
522                 LOFType.VVLH_INERTIAL.rotationFromLOF(LOFType.VVLH, arbitraryEpoch, pv);
523 
524         final Rotation rotationFromVVLHInertialToVNC =
525                 LOFType.VNC.rotationFromLOF(LOFType.VVLH_INERTIAL, arbitraryEpoch, pv);
526 
527         final Rotation rotationFromVNCToVNCInertial =
528                 LOFType.VNC_INERTIAL.rotationFromLOF(LOFType.VNC, arbitraryEpoch, pv);
529 
530         final Rotation rotationFromVNCInertialToNTW =
531                 LOFType.NTW.rotationFromLOF(LOFType.VNC_INERTIAL, arbitraryEpoch, pv);
532 
533         final Rotation rotationFromNTWToNTWInertial =
534                 LOFType.NTW_INERTIAL.rotationFromLOF(LOFType.NTW, arbitraryEpoch, pv);
535 
536         final Rotation rotationFromNTWInertialToEQW =
537                 LOFType.EQW.rotationFromLOF(LOFType.NTW_INERTIAL, arbitraryEpoch, pv);
538 
539         final Rotation rotationFromEQWToENU =
540                 LOFType.ENU.rotationFromLOF(LOFType.EQW, arbitraryEpoch, pv);
541 
542         final Rotation rotationFromENUToNED =
543                 LOFType.NED.rotationFromLOF(LOFType.ENU, arbitraryEpoch, pv);
544 
545         final Rotation rotationFromNEDToTNW =
546                 LOF.rotationFromLOFInToLOFOut(LOFType.NED, LOFType.TNW, arbitraryEpoch, pv);
547 
548         final Rotation rotationFromTNWToTNW =
549                 composeRotations(rotationFromTNWToTNWInertial,
550                                  rotationFromTNWInertialToQSW,
551                                  rotationFromQSWToQSWInertial,
552                                  rotationFromQSWInertialToLVLH,
553                                  rotationFromLVLHToLVLHInertial,
554                                  rotationFromLVLHInertialToLVLH_CCSDS,
555                                  rotationFromLVLH_CCSDSToLVLH_CCSDSInertial,
556                                  rotationFromLVLH_CCSDSInertialToVVLH,
557                                  rotationFromVVLHToVVLHInertial,
558                                  rotationFromVVLHInertialToVNC,
559                                  rotationFromVNCToVNCInertial,
560                                  rotationFromVNCInertialToNTW,
561                                  rotationFromNTWToNTWInertial,
562                                  rotationFromNTWInertialToEQW,
563                                  rotationFromEQWToENU,
564                                  rotationFromENUToNED,
565                                  rotationFromNEDToTNW);
566 
567         final RealMatrix rotationMatrixFromTNWToTNW =
568                 new BlockRealMatrix(rotationFromTNWToTNW.getMatrix());
569 
570         // Then
571         final RealMatrix identityMatrix = MatrixUtils.createRealIdentityMatrix(3);
572 
573         TestUtils.validateRealMatrix(identityMatrix, rotationMatrixFromTNWToTNW, 1e-15);
574 
575     }
576 
577     @Test
578     @DisplayName("Test all rotation methods (field version) using default method of the interface")
579     void should_return_initial_value_after_multiple_field_rotations_default_method() {
580         // Given
581         final Binary64Field               field          = Binary64Field.getInstance();
582         final FieldAbsoluteDate<Binary64> arbitraryEpoch = new FieldAbsoluteDate<>(field, AbsoluteDate.ARBITRARY_EPOCH);
583         final FieldPVCoordinates<Binary64> pv =
584                 new FieldPVCoordinates<>(new FieldVector3D<>(new Binary64(6378000 + 400000),
585                                                              new Binary64(0),
586                                                              new Binary64(0)),
587                                          new FieldVector3D<>(new Binary64(0),
588                                                              new Binary64(5422.8),
589                                                              new Binary64(5422.8)));
590 
591         // When
592         final FieldRotation<Binary64> rotationFromTNWToTNWInertial =
593                 LOFType.TNW_INERTIAL.rotationFromLOF(field, LOFType.TNW, arbitraryEpoch, pv);
594 
595         final FieldRotation<Binary64> rotationFromTNWInertialToQSW =
596                 LOFType.QSW.rotationFromLOF(field, LOFType.TNW_INERTIAL, arbitraryEpoch, pv);
597 
598         final FieldRotation<Binary64> rotationFromQSWToQSWInertial =
599                 LOFType.QSW_INERTIAL.rotationFromLOF(field, LOFType.QSW, arbitraryEpoch, pv);
600 
601         final FieldRotation<Binary64> rotationFromQSWInertialToLVLH =
602                 LOFType.LVLH.rotationFromLOF(field, LOFType.QSW_INERTIAL, arbitraryEpoch, pv);
603 
604         final FieldRotation<Binary64> rotationFromLVLHToLVLHInertial =
605                 LOFType.LVLH_INERTIAL.rotationFromLOF(field, LOFType.LVLH, arbitraryEpoch, pv);
606 
607         final FieldRotation<Binary64> rotationFromLVLHInertialToLVLH_CCSDS =
608                 LOFType.LVLH_CCSDS.rotationFromLOF(field, LOFType.LVLH_INERTIAL, arbitraryEpoch, pv);
609 
610         final FieldRotation<Binary64> rotationFromLVLH_CCSDSToLVLH_CCSDSInertial =
611                 LOFType.LVLH_CCSDS_INERTIAL.rotationFromLOF(field, LOFType.LVLH_CCSDS, arbitraryEpoch, pv);
612 
613         final FieldRotation<Binary64> rotationFromLVLH_CCSDSInertialToVVLH =
614                 LOFType.VVLH.rotationFromLOF(field, LOFType.LVLH_CCSDS_INERTIAL, arbitraryEpoch, pv);
615 
616         final FieldRotation<Binary64> rotationFromVVLHToVVLHInertial =
617                 LOFType.VVLH_INERTIAL.rotationFromLOF(field, LOFType.VVLH, arbitraryEpoch, pv);
618 
619         final FieldRotation<Binary64> rotationFromVVLHInertialToVNC =
620                 LOFType.VNC.rotationFromLOF(field, LOFType.VVLH_INERTIAL, arbitraryEpoch, pv);
621 
622         final FieldRotation<Binary64> rotationFromVNCToVNCInertial =
623                 LOFType.VNC_INERTIAL.rotationFromLOF(field, LOFType.VNC, arbitraryEpoch, pv);
624 
625         final FieldRotation<Binary64> rotationFromVNCInertialToNTW =
626                 LOFType.NTW.rotationFromLOF(field, LOFType.VNC_INERTIAL, arbitraryEpoch, pv);
627 
628         final FieldRotation<Binary64> rotationFromNTWToNTWInertial =
629                 LOFType.NTW_INERTIAL.rotationFromLOF(field, LOFType.NTW, arbitraryEpoch, pv);
630 
631         final FieldRotation<Binary64> rotationFromNTWInertialToEQW =
632                 LOFType.EQW.rotationFromLOF(field, LOFType.NTW_INERTIAL, arbitraryEpoch, pv);
633 
634         final FieldRotation<Binary64> rotationFromEQWToENU =
635                 LOFType.ENU.rotationFromLOF(field, LOFType.EQW, arbitraryEpoch, pv);
636 
637         final FieldRotation<Binary64> rotationFromENUToNED =
638                 LOFType.NED.rotationFromLOF(field, LOFType.ENU, arbitraryEpoch, pv);
639 
640         final FieldRotation<Binary64> rotationFromNEDToTNW =
641                 LOF.rotationFromLOFInToLOFOut(field, LOFType.NED, LOFType.TNW, arbitraryEpoch, pv);
642 
643         final FieldRotation<Binary64> rotationFromTNWToTNW =
644                 composeFieldRotations(rotationFromTNWToTNWInertial,
645                                       rotationFromTNWInertialToQSW,
646                                       rotationFromQSWToQSWInertial,
647                                       rotationFromQSWInertialToLVLH,
648                                       rotationFromLVLHToLVLHInertial,
649                                       rotationFromLVLHInertialToLVLH_CCSDS,
650                                       rotationFromLVLH_CCSDSToLVLH_CCSDSInertial,
651                                       rotationFromLVLH_CCSDSInertialToVVLH,
652                                       rotationFromVVLHToVVLHInertial,
653                                       rotationFromVVLHInertialToVNC,
654                                       rotationFromVNCToVNCInertial,
655                                       rotationFromVNCInertialToNTW,
656                                       rotationFromNTWToNTWInertial,
657                                       rotationFromNTWInertialToEQW,
658                                       rotationFromEQWToENU,
659                                       rotationFromENUToNED,
660                                       rotationFromNEDToTNW);
661 
662         final FieldMatrix<Binary64> rotationMatrixFromTNWToTNW =
663                 new BlockFieldMatrix<>(rotationFromTNWToTNW.getMatrix());
664 
665         // Then
666         final RealMatrix identityMatrix = MatrixUtils.createRealIdentityMatrix(3);
667 
668         TestUtils.validateFieldMatrix(identityMatrix, rotationMatrixFromTNWToTNW, 1e-15);
669 
670     }
671 
672     @Test
673     @DisplayName("Test all rotation methods")
674     void should_return_initial_value_after_multiple_rotations() {
675         // Given
676         final PVCoordinates pv = new PVCoordinates(new Vector3D(6378000 + 400000, 0, 0),
677                                                    new Vector3D(0, 5422.8, 5422.8));
678 
679         // When
680         final Rotation rotationFromTNWToTNWInertial =
681                 LOFType.TNW_INERTIAL.rotationFromLOF(LOFType.TNW, pv);
682 
683         final Rotation rotationFromTNWInertialToQSW =
684                 LOFType.QSW.rotationFromLOF(LOFType.TNW_INERTIAL, pv);
685 
686         final Rotation rotationFromQSWToQSWInertial =
687                 LOFType.QSW_INERTIAL.rotationFromLOF(LOFType.QSW, pv);
688 
689         final Rotation rotationFromQSWInertialToLVLH =
690                 LOFType.LVLH.rotationFromLOF(LOFType.QSW_INERTIAL, pv);
691 
692         final Rotation rotationFromLVLHToLVLHInertial =
693                 LOFType.LVLH_INERTIAL.rotationFromLOF(LOFType.LVLH, pv);
694 
695         final Rotation rotationFromLVLHInertialToLVLH_CCSDS =
696                 LOFType.LVLH_CCSDS.rotationFromLOF(LOFType.LVLH_INERTIAL, pv);
697 
698         final Rotation rotationFromLVLH_CCSDSToLVLH_CCSDSInertial =
699                 LOFType.LVLH_CCSDS_INERTIAL.rotationFromLOF(LOFType.LVLH_CCSDS, pv);
700 
701         final Rotation rotationFromLVLH_CCSDSInertialToVVLH =
702                 LOFType.VVLH.rotationFromLOF(LOFType.LVLH_CCSDS_INERTIAL, pv);
703 
704         final Rotation rotationFromVVLHToVVLHInertial =
705                 LOFType.VVLH_INERTIAL.rotationFromLOF(LOFType.VVLH, pv);
706 
707         final Rotation rotationFromVVLHInertialToVNC =
708                 LOFType.VNC.rotationFromLOF(LOFType.VVLH_INERTIAL, pv);
709 
710         final Rotation rotationFromVNCToVNCInertial =
711                 LOFType.VNC_INERTIAL.rotationFromLOF(LOFType.VNC, pv);
712 
713         final Rotation rotationFromVNCInertialToNTW =
714                 LOFType.NTW.rotationFromLOF(LOFType.VNC_INERTIAL, pv);
715 
716         final Rotation rotationFromNTWToNTWInertial =
717                 LOFType.NTW_INERTIAL.rotationFromLOF(LOFType.NTW, pv);
718 
719         final Rotation rotationFromNTWInertialToEQW =
720                 LOFType.EQW.rotationFromLOF(LOFType.NTW_INERTIAL, pv);
721 
722         final Rotation rotationFromEQWToENU =
723                 LOFType.ENU.rotationFromLOF(LOFType.EQW, pv);
724 
725         final Rotation rotationFromENUToNED =
726                 LOFType.NED.rotationFromLOF(LOFType.ENU, pv);
727 
728         final Rotation rotationFromNEDToTNW =
729                 LOFType.rotationFromLOFInToLOFOut(LOFType.NED, LOFType.TNW, pv);
730 
731         final Rotation rotationFromTNWToTNW =
732                 composeRotations(rotationFromTNWToTNWInertial,
733                         rotationFromTNWInertialToQSW,
734                         rotationFromQSWToQSWInertial,
735                         rotationFromQSWInertialToLVLH,
736                         rotationFromLVLHToLVLHInertial,
737                         rotationFromLVLHInertialToLVLH_CCSDS,
738                         rotationFromLVLH_CCSDSToLVLH_CCSDSInertial,
739                         rotationFromLVLH_CCSDSInertialToVVLH,
740                         rotationFromVVLHToVVLHInertial,
741                         rotationFromVVLHInertialToVNC,
742                         rotationFromVNCToVNCInertial,
743                         rotationFromVNCInertialToNTW,
744                         rotationFromNTWToNTWInertial,
745                         rotationFromNTWInertialToEQW,
746                         rotationFromEQWToENU,
747                         rotationFromENUToNED,
748                         rotationFromNEDToTNW);
749 
750         final RealMatrix rotationMatrixFromTNWToTNW =
751                 new BlockRealMatrix(rotationFromTNWToTNW.getMatrix());
752 
753         // Then
754         final RealMatrix identityMatrix = MatrixUtils.createRealIdentityMatrix(3);
755 
756         TestUtils.validateRealMatrix(identityMatrix, rotationMatrixFromTNWToTNW, 1e-15);
757 
758     }
759 
760     @Test
761     @DisplayName("Test all rotation methods (field version)")
762     void should_return_initial_value_after_multiple_field_rotations() {
763         // Given
764         final Binary64Field               field = Binary64Field.getInstance();
765         final FieldPVCoordinates<Binary64> pv =
766                 new FieldPVCoordinates<>(new FieldVector3D<>(new Binary64(6378000 + 400000),
767                                                              new Binary64(0),
768                                                              new Binary64(0)),
769                                          new FieldVector3D<>(new Binary64(0),
770                                                              new Binary64(5422.8),
771                                                              new Binary64(5422.8)));
772 
773         // When
774         final FieldRotation<Binary64> rotationFromTNWToTNWInertial =
775                 LOFType.TNW_INERTIAL.rotationFromLOF(field, LOFType.TNW, pv);
776 
777         final FieldRotation<Binary64> rotationFromTNWInertialToQSW =
778                 LOFType.QSW.rotationFromLOF(field, LOFType.TNW_INERTIAL, pv);
779 
780         final FieldRotation<Binary64> rotationFromQSWToQSWInertial =
781                 LOFType.QSW_INERTIAL.rotationFromLOF(field, LOFType.QSW, pv);
782 
783         final FieldRotation<Binary64> rotationFromQSWInertialToLVLH =
784                 LOFType.LVLH.rotationFromLOF(field, LOFType.QSW_INERTIAL, pv);
785 
786         final FieldRotation<Binary64> rotationFromLVLHToLVLHInertial =
787                 LOFType.LVLH_INERTIAL.rotationFromLOF(field, LOFType.LVLH, pv);
788 
789         final FieldRotation<Binary64> rotationFromLVLHInertialToLVLH_CCSDS =
790                 LOFType.LVLH_CCSDS.rotationFromLOF(field, LOFType.LVLH_INERTIAL, pv);
791 
792         final FieldRotation<Binary64> rotationFromLVLH_CCSDSToLVLH_CCSDSInertial =
793                 LOFType.LVLH_CCSDS_INERTIAL.rotationFromLOF(field, LOFType.LVLH_CCSDS, pv);
794 
795         final FieldRotation<Binary64> rotationFromLVLH_CCSDSInertialToVVLH =
796                 LOFType.VVLH.rotationFromLOF(field, LOFType.LVLH_CCSDS_INERTIAL, pv);
797 
798         final FieldRotation<Binary64> rotationFromVVLHToVVLHInertial =
799                 LOFType.VVLH_INERTIAL.rotationFromLOF(field, LOFType.VVLH, pv);
800 
801         final FieldRotation<Binary64> rotationFromVVLHInertialToVNC =
802                 LOFType.VNC.rotationFromLOF(field, LOFType.VVLH_INERTIAL, pv);
803 
804         final FieldRotation<Binary64> rotationFromVNCToVNCInertial =
805                 LOFType.VNC_INERTIAL.rotationFromLOF(field, LOFType.VNC, pv);
806 
807         final FieldRotation<Binary64> rotationFromVNCInertialToNTW =
808                 LOFType.NTW.rotationFromLOF(field, LOFType.VNC_INERTIAL, pv);
809 
810         final FieldRotation<Binary64> rotationFromNTWToNTWInertial =
811                 LOFType.NTW_INERTIAL.rotationFromLOF(field, LOFType.NTW, pv);
812 
813         final FieldRotation<Binary64> rotationFromNTWInertialToEQW =
814                 LOFType.EQW.rotationFromLOF(field, LOFType.NTW_INERTIAL, pv);
815 
816         final FieldRotation<Binary64> rotationFromEQWToENU =
817                 LOFType.ENU.rotationFromLOF(field, LOFType.EQW, pv);
818 
819         final FieldRotation<Binary64> rotationFromENUToNED =
820                 LOFType.NED.rotationFromLOF(field, LOFType.ENU, pv);
821 
822         final FieldRotation<Binary64> rotationFromNEDToTNW =
823                 LOFType.rotationFromLOFInToLOFOut(field, LOFType.NED, LOFType.TNW, pv);
824 
825         final FieldRotation<Binary64> rotationFromTNWToTNW =
826                 composeFieldRotations(rotationFromTNWToTNWInertial,
827                                       rotationFromTNWInertialToQSW,
828                                       rotationFromQSWToQSWInertial,
829                                       rotationFromQSWInertialToLVLH,
830                                       rotationFromLVLHToLVLHInertial,
831                                       rotationFromLVLHInertialToLVLH_CCSDS,
832                                       rotationFromLVLH_CCSDSToLVLH_CCSDSInertial,
833                                       rotationFromLVLH_CCSDSInertialToVVLH,
834                                       rotationFromVVLHToVVLHInertial,
835                                       rotationFromVVLHInertialToVNC,
836                                       rotationFromVNCToVNCInertial,
837                                       rotationFromVNCInertialToNTW,
838                                       rotationFromNTWToNTWInertial,
839                                       rotationFromNTWInertialToEQW,
840                                       rotationFromEQWToENU,
841                                       rotationFromENUToNED,
842                                       rotationFromNEDToTNW);
843 
844         final FieldMatrix<Binary64> rotationMatrixFromTNWToTNW =
845                 new BlockFieldMatrix<>(rotationFromTNWToTNW.getMatrix());
846 
847         // Then
848         final RealMatrix identityMatrix = MatrixUtils.createRealIdentityMatrix(3);
849 
850         TestUtils.validateFieldMatrix(identityMatrix, rotationMatrixFromTNWToTNW, 1e-15);
851 
852     }
853 
854     @Test
855     @DisplayName("Test isQuasiInertialMethod")
856     void should_return_expected_boolean() {
857 
858         // Local orbital frame considered as pseudo-inertial
859         Assertions.assertTrue(LOFType.TNW_INERTIAL.isQuasiInertial());
860         Assertions.assertTrue(LOFType.NTW_INERTIAL.isQuasiInertial());
861         Assertions.assertTrue(LOFType.QSW_INERTIAL.isQuasiInertial());
862         Assertions.assertTrue(LOFType.VNC_INERTIAL.isQuasiInertial());
863         Assertions.assertTrue(LOFType.LVLH_INERTIAL.isQuasiInertial());
864         Assertions.assertTrue(LOFType.LVLH_CCSDS_INERTIAL.isQuasiInertial());
865         Assertions.assertTrue(LOFType.EQW.isQuasiInertial());
866         Assertions.assertTrue(LOFType.VVLH_INERTIAL.isQuasiInertial());
867 
868         // Local orbital frame considered as non pseudo-inertial
869         Assertions.assertFalse(LOFType.TNW.isQuasiInertial());
870         Assertions.assertFalse(LOFType.NTW.isQuasiInertial());
871         Assertions.assertFalse(LOFType.QSW.isQuasiInertial());
872         Assertions.assertFalse(LOFType.VNC.isQuasiInertial());
873         Assertions.assertFalse(LOFType.LVLH.isQuasiInertial());
874         Assertions.assertFalse(LOFType.LVLH_CCSDS.isQuasiInertial());
875         Assertions.assertFalse(LOFType.VVLH.isQuasiInertial());
876         Assertions.assertFalse(LOFType.ENU.isQuasiInertial());
877         Assertions.assertFalse(LOFType.NED.isQuasiInertial());
878 
879     }
880 
881     private Rotation composeRotations(final Rotation... rotations) {
882 
883         Rotation composedRotations = null;
884 
885         for (Rotation rotation : rotations) {
886             if (composedRotations == null) {
887                 composedRotations = rotation;
888             }
889             else {
890                 composedRotations = composedRotations.compose(rotation, RotationConvention.FRAME_TRANSFORM);
891             }
892         }
893 
894         return composedRotations;
895     }
896 
897     @SafeVarargs private final <T extends CalculusFieldElement<T>> FieldRotation<T> composeFieldRotations(
898             final FieldRotation<T>... rotations) {
899 
900         FieldRotation<T> composedRotations = null;
901 
902         for (FieldRotation<T> rotation : rotations) {
903             if (composedRotations == null) {
904                 composedRotations = rotation;
905             }
906             else {
907                 composedRotations = composedRotations.compose(rotation, RotationConvention.FRAME_TRANSFORM);
908             }
909         }
910 
911         return composedRotations;
912     }
913 
914     private Transform composeTransform(final AbsoluteDate date, final Transform... transforms){
915         Transform composedTransform = null;
916 
917         for (Transform transform : transforms) {
918             if (composedTransform == null) {
919                 composedTransform = transform;
920             }
921             else {
922                 composedTransform = new Transform(date, composedTransform, transform);
923             }
924         }
925 
926         return composedTransform;
927     }
928 
929     @SafeVarargs
930     private final <T extends CalculusFieldElement<T>> FieldTransform<T> composeFieldTransform(
931             final FieldAbsoluteDate<T> date,
932             final FieldTransform<T>... transforms) {
933         FieldTransform<T> composedTransform = null;
934 
935         for (FieldTransform<T> transform : transforms) {
936             if (composedTransform == null) {
937                 composedTransform = transform;
938             }
939             else {
940                 composedTransform = new FieldTransform<>(date, composedTransform, transform);
941             }
942         }
943 
944         return composedTransform;
945     }
946 
947     private void checkFrame(LOFType type, AbsoluteDate date,
948                             Vector3D expectedXDirection, Vector3D expectedYDirection,
949                             Vector3D expectedZDirection, Vector3D expectedRotationDirection) {
950         LocalOrbitalFrame lof = new LocalOrbitalFrame(FramesFactory.getGCRF(), type, provider, type.name());
951 
952         Transform     t   = lof.getTransformTo(FramesFactory.getGCRF(), date);
953         PVCoordinates pv1 = t.transformPVCoordinates(PVCoordinates.ZERO);
954         Vector3D      p1  = pv1.getPosition();
955         Vector3D      v1  = pv1.getVelocity();
956         PVCoordinates pv2 = provider.getPVCoordinates(date, FramesFactory.getGCRF());
957         Vector3D      p2  = pv2.getPosition();
958         Vector3D      v2  = pv2.getVelocity();
959         Assertions.assertEquals(0, p1.subtract(p2).getNorm(), 1.0e-14 * p1.getNorm());
960         Assertions.assertEquals(0, v1.subtract(v2).getNorm(), 1.0e-14 * v1.getNorm());
961 
962         Vector3D xDirection = t.transformVector(Vector3D.PLUS_I);
963         Vector3D yDirection = t.transformVector(Vector3D.PLUS_J);
964         Vector3D zDirection = t.transformVector(Vector3D.PLUS_K);
965         Assertions.assertEquals(0, Vector3D.angle(expectedXDirection, xDirection), 2.0e-15);
966         Assertions.assertEquals(0, Vector3D.angle(expectedYDirection, yDirection), 1.0e-15);
967         Assertions.assertEquals(0, Vector3D.angle(expectedZDirection, zDirection), 1.0e-15);
968         Assertions.assertEquals(0, Vector3D.angle(expectedRotationDirection, t.getRotationRate()), 1.0e-15);
969 
970         Assertions.assertEquals(initialOrbit.getKeplerianMeanMotion(), t.getRotationRate().getNorm(), 1.0e-7);
971 
972     }
973 
974     @BeforeEach
975     public void setUp() {
976         inertialFrame = FramesFactory.getGCRF();
977         initDate = AbsoluteDate.J2000_EPOCH.shiftedBy(584.);
978         initialOrbit =
979                 new KeplerianOrbit(7209668.0, 0.5e-4, 1.7, 2.1, 2.9, 6.2, PositionAngleType.TRUE,
980                                    inertialFrame, initDate, 3.986004415e14);
981         provider = new KeplerianPropagator(initialOrbit);
982 
983     }
984 
985     private Frame                 inertialFrame;
986     private AbsoluteDate          initDate;
987     private Orbit                 initialOrbit;
988     private PVCoordinatesProvider provider;
989 
990 }