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.orekit.errors.OrekitException;
27  import org.orekit.errors.OrekitMessages;
28  import org.orekit.files.ccsds.definitions.OrbitRelativeFrame;
29  import org.orekit.time.AbsoluteDate;
30  import org.orekit.time.FieldAbsoluteDate;
31  import org.orekit.utils.FieldPVCoordinates;
32  import org.orekit.utils.PVCoordinates;
33  
34  /**
35   * Enumerate for different types of Local Orbital Frames.
36   *
37   * @author Luc Maisonobe
38   * @author Maxime Journot
39   * @author Vincent Cucchietti
40   */
41  public enum LOFType implements LOF {
42  
43      /** Constant for TNW frame
44       * (X axis aligned with velocity, Z axis aligned with orbital momentum).
45       * <p>
46       * The axes of this frame are parallel to the axes of the {@link #VNC}
47       * and {@link #NTW} frames:
48       * </p>
49       * <ul>
50       *   <li>X<sub>TNW</sub> =  X<sub>VNC</sub> =  Y<sub>NTW</sub></li>
51       *   <li>Y<sub>TNW</sub> = -Z<sub>VNC</sub> = -X<sub>NTW</sub></li>
52       *   <li>Z<sub>TNW</sub> =  Y<sub>VNC</sub> =  Z<sub>NTW</sub></li>
53       * </ul>
54       *
55       * @see #VNC
56       * @see #NTW
57       */
58      TNW {
59          /** {@inheritDoc} */
60          @Override
61          public Rotation rotationFromInertial(final PVCoordinates pv) {
62              return new Rotation(pv.getVelocity(), pv.getMomentum(),
63                                  Vector3D.PLUS_I, Vector3D.PLUS_K);
64          }
65  
66          /** {@inheritDoc} */
67          @Override
68          public <T extends CalculusFieldElement<T>> FieldRotation<T> rotationFromInertial(final Field<T> field,
69                                                                                           final FieldPVCoordinates<T> pv) {
70              return new FieldRotation<>(pv.getVelocity(), pv.getMomentum(),
71                                         new FieldVector3D<>(field, Vector3D.PLUS_I),
72                                         new FieldVector3D<>(field, Vector3D.PLUS_K));
73          }
74  
75          /** {@inheritDoc} */
76          @Override
77          public OrbitRelativeFrame toOrbitRelativeFrame() {
78              return OrbitRelativeFrame.TNW;
79          }
80  
81      },
82  
83      /**
84       * Constant for TNW frame considered inertial (X axis aligned with velocity, Z axis aligned with orbital momentum).
85       * <p>
86       * The axes of this frame are parallel to the axes of the {@link #VNC} and {@link #NTW} frames:
87       * </p>
88       * <ul>
89       *   <li>X<sub>TNW</sub> =  X<sub>VNC</sub> =  Y<sub>NTW</sub></li>
90       *   <li>Y<sub>TNW</sub> = -Z<sub>VNC</sub> = -X<sub>NTW</sub></li>
91       *   <li>Z<sub>TNW</sub> =  Y<sub>VNC</sub> =  Z<sub>NTW</sub></li>
92       * </ul>
93       *
94       * @see #VNC
95       * @see #NTW
96       */
97      TNW_INERTIAL {
98          /** {@inheritDoc} */
99          @Override
100         public Rotation rotationFromInertial(final PVCoordinates pv) {
101             return TNW.rotationFromInertial(pv);
102         }
103 
104         /** {@inheritDoc} */
105         @Override
106         public <T extends CalculusFieldElement<T>> FieldRotation<T> rotationFromInertial(final Field<T> field,
107                                                                                          final FieldPVCoordinates<T> pv) {
108             return TNW.rotationFromInertial(field, pv);
109         }
110 
111         /** {@inheritDoc} */
112         @Override
113         public boolean isQuasiInertial() {
114             return true;
115         }
116 
117         /** {@inheritDoc} */
118         @Override
119         public OrbitRelativeFrame toOrbitRelativeFrame() {
120             return OrbitRelativeFrame.TNW_INERTIAL;
121         }
122 
123     },
124 
125     /** Constant for QSW frame
126      * (X axis aligned with position, Z axis aligned with orbital momentum).
127      * <p>
128      * This frame is also known as the {@link #LVLH} frame, both constants are equivalent.
129      * </p>
130      * <p>
131      * The axes of these frames are parallel to the axes of the {@link #VVLH} frame:
132      * </p>
133      * <ul>
134      *   <li>X<sub>QSW/LVLH</sub> = -Z<sub>VVLH</sub></li>
135      *   <li>Y<sub>QSW/LVLH</sub> =  X<sub>VVLH</sub></li>
136      *   <li>Z<sub>QSW/LVLH</sub> = -Y<sub>VVLH</sub></li>
137      * </ul>
138      *
139      * @see #LVLH
140      * @see #VVLH
141      */
142     QSW {
143         /** {@inheritDoc} */
144         @Override
145         public Rotation rotationFromInertial(final PVCoordinates pv) {
146             return new Rotation(pv.getPosition(), pv.getMomentum(),
147                                 Vector3D.PLUS_I, Vector3D.PLUS_K);
148         }
149 
150         /** {@inheritDoc} */
151         @Override
152         public <T extends CalculusFieldElement<T>> FieldRotation<T> rotationFromInertial(final Field<T> field,
153                                                                                          final FieldPVCoordinates<T> pv) {
154             return new FieldRotation<>(pv.getPosition(), pv.getMomentum(),
155                                        new FieldVector3D<>(field, Vector3D.PLUS_I),
156                                        new FieldVector3D<>(field, Vector3D.PLUS_K));
157         }
158 
159         /** {@inheritDoc} */
160         @Override
161         public OrbitRelativeFrame toOrbitRelativeFrame() {
162             return OrbitRelativeFrame.QSW;
163         }
164 
165     },
166 
167     /**
168      * Constant for QSW frame considered inertial (X axis aligned with position, Z axis aligned with orbital momentum).
169      * <p>
170      * This frame is also known as the {@link #LVLH} frame, both constants are equivalent.
171      * </p>
172      * <p>
173      * The axes of these frames are parallel to the axes of the {@link #VVLH} frame:
174      * </p>
175      * <ul>
176      *   <li>X<sub>QSW/LVLH</sub> = -Z<sub>VVLH</sub></li>
177      *   <li>Y<sub>QSW/LVLH</sub> =  X<sub>VVLH</sub></li>
178      *   <li>Z<sub>QSW/LVLH</sub> = -Y<sub>VVLH</sub></li>
179      * </ul>
180      *
181      * @see #LVLH
182      * @see #VVLH
183      */
184     QSW_INERTIAL {
185         /** {@inheritDoc} */
186         @Override
187         public Rotation rotationFromInertial(final PVCoordinates pv) {
188             return QSW.rotationFromInertial(pv);
189         }
190 
191         /** {@inheritDoc} */
192         @Override
193         public <T extends CalculusFieldElement<T>> FieldRotation<T> rotationFromInertial(final Field<T> field,
194                                                                                          final FieldPVCoordinates<T> pv) {
195             return QSW.rotationFromInertial(field, pv);
196         }
197 
198         /** {@inheritDoc} */
199         @Override
200         public boolean isQuasiInertial() {
201             return true;
202         }
203 
204         /** {@inheritDoc} */
205         @Override
206         public OrbitRelativeFrame toOrbitRelativeFrame() {
207             return OrbitRelativeFrame.RSW_INERTIAL;
208         }
209 
210     },
211 
212     /** Constant for Local Vertical, Local Horizontal frame
213      * (X axis aligned with position, Z axis aligned with orbital momentum).
214      * <p>
215      * BEWARE! Depending on the background (software used, textbook, community),
216      * different incompatible definitions for LVLH are used. This one is consistent
217      * with Vallado's book and with AGI's STK. However CCSDS standard, Wertz, and
218      * a.i. solutions' FreeFlyer use another definition (see {@link #LVLH_CCSDS}).
219      * </p>
220      * <p>
221      * This frame is also known as the {@link #QSW} frame, both constants are equivalent.
222      * </p>
223      * <p>
224      * The axes of these frames are parallel to the axes of the {@link #LVLH_CCSDS} frame:
225      * </p>
226      * <ul>
227      *   <li>X<sub>LVLH/QSW</sub> = -Z<sub>LVLH_CCSDS</sub></li>
228      *   <li>Y<sub>LVLH/QSW</sub> =  X<sub>LVLH_CCSDS</sub></li>
229      *   <li>Z<sub>LVLH/QSW</sub> = -Y<sub>LVLH_CCSDS</sub></li>
230      * </ul>
231      *
232      * @see #QSW
233      * @see #VVLH
234      */
235     LVLH {
236         /** {@inheritDoc} */
237         @Override
238         public Rotation rotationFromInertial(final PVCoordinates pv) {
239             return new Rotation(pv.getPosition(), pv.getMomentum(),
240                                 Vector3D.PLUS_I, Vector3D.PLUS_K);
241         }
242 
243         /** {@inheritDoc} */
244         @Override
245         public <T extends CalculusFieldElement<T>> FieldRotation<T> rotationFromInertial(final Field<T> field,
246                                                                                          final FieldPVCoordinates<T> pv) {
247             return new FieldRotation<>(pv.getPosition(), pv.getMomentum(),
248                                        new FieldVector3D<>(field, Vector3D.PLUS_I),
249                                        new FieldVector3D<>(field, Vector3D.PLUS_K));
250         }
251 
252         /** {@inheritDoc} */
253         @Override
254         public OrbitRelativeFrame toOrbitRelativeFrame() {
255             throw new OrekitException(OrekitMessages.CCSDS_DIFFERENT_LVLH_DEFINITION);
256         }
257 
258     },
259 
260     /**
261      * Constant for Local Vertical, Local Horizontal frame considered inertial (X axis aligned with position, Z axis
262      * aligned with orbital momentum).
263      * <p>
264      * BEWARE! Depending on the background (software used, textbook, community), different incompatible definitions for
265      * LVLH are used. This one is consistent with Vallado's book and with AGI's STK. However CCSDS standard, Wertz, and
266      * a.i. solutions' FreeFlyer use another definition (see {@link #LVLH_CCSDS}).
267      * </p>
268      * <p>
269      * This frame is also known as the {@link #QSW} frame, both constants are equivalent.
270      * </p>
271      * <p>
272      * The axes of these frames are parallel to the axes of the {@link #LVLH_CCSDS} frame:
273      * </p>
274      * <ul>
275      *   <li>X<sub>LVLH/QSW</sub> = -Z<sub>LVLH_CCSDS</sub></li>
276      *   <li>Y<sub>LVLH/QSW</sub> =  X<sub>LVLH_CCSDS</sub></li>
277      *   <li>Z<sub>LVLH/QSW</sub> = -Y<sub>LVLH_CCSDS</sub></li>
278      * </ul>
279      *
280      * @see #QSW
281      * @see #VVLH
282      */
283     LVLH_INERTIAL {
284         /** {@inheritDoc} */
285         @Override
286         public Rotation rotationFromInertial(final PVCoordinates pv) {
287             return LVLH.rotationFromInertial(pv);
288         }
289 
290         /** {@inheritDoc} */
291         @Override
292         public <T extends CalculusFieldElement<T>> FieldRotation<T> rotationFromInertial(final Field<T> field,
293                                                                                          final FieldPVCoordinates<T> pv) {
294             return LVLH.rotationFromInertial(field, pv);
295         }
296 
297         /** {@inheritDoc} */
298         @Override
299         public boolean isQuasiInertial() {
300             return true;
301         }
302 
303         /** {@inheritDoc} */
304         @Override
305         public OrbitRelativeFrame toOrbitRelativeFrame() {
306             throw new OrekitException(OrekitMessages.CCSDS_DIFFERENT_LVLH_DEFINITION);
307         }
308 
309     },
310 
311     /** Constant for Local Vertical, Local Horizontal frame as defined by CCSDS
312      * (Z axis aligned with opposite of position, Y axis aligned with opposite of orbital momentum).
313      * <p>
314      * BEWARE! Depending on the background (software used, textbook, community),
315      * different incompatible definitions for LVLH are used. This one is consistent
316      * with CCSDS standard, Wertz, and a.i. solutions' FreeFlyer. However Vallado's
317      * book and with AGI's STK use another definition (see {@link #LVLH}).
318      * </p>
319      * <p>
320      * The axes of this frame are parallel to the axes of both the {@link #QSW} and {@link #LVLH} frames:
321      * </p>
322      * <ul>
323      *   <li>X<sub>LVLH_CCSDS/VVLH</sub> =  Y<sub>QSW/LVLH</sub></li>
324      *   <li>Y<sub>LVLH_CCSDS/VVLH</sub> = -Z<sub>QSW/LVLH</sub></li>
325      *   <li>Z<sub>LVLH_CCSDS/VVLH</sub> = -X<sub>QSW/LVLH</sub></li>
326      * </ul>
327      *
328      * @see #QSW
329      * @see #LVLH
330      * @since 11.0
331      */
332     LVLH_CCSDS {
333         /** {@inheritDoc} */
334         @Override
335         public Rotation rotationFromInertial(final PVCoordinates pv) {
336             return new Rotation(pv.getPosition(), pv.getMomentum(),
337                                 Vector3D.MINUS_K, Vector3D.MINUS_J);
338         }
339 
340         /** {@inheritDoc} */
341         @Override
342         public <T extends CalculusFieldElement<T>> FieldRotation<T> rotationFromInertial(final Field<T> field,
343                                                                                          final FieldPVCoordinates<T> pv) {
344             return new FieldRotation<>(pv.getPosition(), pv.getMomentum(),
345                                        new FieldVector3D<>(field, Vector3D.MINUS_K),
346                                        new FieldVector3D<>(field, Vector3D.MINUS_J));
347         }
348 
349         /** {@inheritDoc} */
350         @Override
351         public OrbitRelativeFrame toOrbitRelativeFrame() {
352             return OrbitRelativeFrame.LVLH;
353         }
354 
355     },
356 
357     /**
358      * Constant for Local Vertical, Local Horizontal frame as defined by CCSDS considered inertial (Z axis aligned with
359      * opposite of position, Y axis aligned with opposite of orbital momentum).
360      * <p>
361      * BEWARE! Depending on the background (software used, textbook, community), different incompatible definitions for
362      * LVLH are used. This one is consistent with CCSDS standard, Wertz, and a.i. solutions' FreeFlyer. However
363      * Vallado's book and with AGI's STK use another definition (see {@link #LVLH}).
364      * </p>
365      * <p>
366      * The axes of this frame are parallel to the axes of both the {@link #QSW} and {@link #LVLH} frames:
367      * </p>
368      * <ul>
369      *   <li>X<sub>LVLH_CCSDS/VVLH</sub> =  Y<sub>QSW/LVLH</sub></li>
370      *   <li>Y<sub>LVLH_CCSDS/VVLH</sub> = -Z<sub>QSW/LVLH</sub></li>
371      *   <li>Z<sub>LVLH_CCSDS/VVLH</sub> = -X<sub>QSW/LVLH</sub></li>
372      * </ul>
373      *
374      * @see #QSW
375      * @see #LVLH
376      * @since 11.0
377      */
378     LVLH_CCSDS_INERTIAL {
379         /** {@inheritDoc} */
380         @Override
381         public Rotation rotationFromInertial(final PVCoordinates pv) {
382             return LVLH_CCSDS.rotationFromInertial(pv);
383         }
384 
385         /** {@inheritDoc} */
386         @Override
387         public <T extends CalculusFieldElement<T>> FieldRotation<T> rotationFromInertial(final Field<T> field,
388                                                                                          final FieldPVCoordinates<T> pv) {
389             return LVLH_CCSDS.rotationFromInertial(field, pv);
390         }
391 
392         /** {@inheritDoc} */
393         @Override
394         public boolean isQuasiInertial() {
395             return true;
396         }
397 
398         /** {@inheritDoc} */
399         @Override
400         public OrbitRelativeFrame toOrbitRelativeFrame() {
401             return OrbitRelativeFrame.LVLH_INERTIAL;
402         }
403 
404     },
405 
406     /** Constant for Vehicle Velocity, Local Horizontal frame
407      * (Z axis aligned with opposite of position, Y axis aligned with opposite of orbital momentum).
408      * <p>
409      * This is another name for {@link #LVLH_CCSDS}, kept here for compatibility with STK.
410      * </p>
411      * <p>
412      * Beware that the name is misleading: in the general case (i.e. not perfectly circular),
413      * none of the axes is perfectly aligned with velocity! The preferred name for this
414      * should be {@link #LVLH_CCSDS}.
415      * </p>
416      * <p>
417      * The axes of this frame are parallel to the axes of both the {@link #QSW} and {@link #LVLH} frames:
418      * </p>
419      * <ul>
420      *   <li>X<sub>LVLH_CCSDS/VVLH</sub> =  Y<sub>QSW/LVLH</sub></li>
421      *   <li>Y<sub>LVLH_CCSDS/VVLH</sub> = -Z<sub>QSW/LVLH</sub></li>
422      *   <li>Z<sub>LVLH_CCSDS/VVLH</sub> = -X<sub>QSW/LVLH</sub></li>
423      * </ul>
424      * @see #LVLH_CCSDS
425      */
426     VVLH {
427         /** {@inheritDoc} */
428         @Override
429         public Rotation rotationFromInertial(final PVCoordinates pv) {
430             return LVLH_CCSDS.rotationFromInertial(pv);
431         }
432 
433         /** {@inheritDoc} */
434         @Override
435         public <T extends CalculusFieldElement<T>> FieldRotation<T> rotationFromInertial(final Field<T> field,
436                                                                                          final FieldPVCoordinates<T> pv) {
437             return LVLH_CCSDS.rotationFromInertial(field, pv);
438         }
439 
440         /** {@inheritDoc} */
441         @Override
442         public OrbitRelativeFrame toOrbitRelativeFrame() {
443             return OrbitRelativeFrame.LVLH;
444         }
445 
446     },
447 
448     /**
449      * Constant for Vehicle Velocity, Local Horizontal frame considered inertial (Z axis aligned with opposite of
450      * position, Y axis aligned with opposite of orbital momentum).
451      * <p>
452      * This is another name for {@link #LVLH_CCSDS}, kept here for compatibility with STK.
453      * </p>
454      * <p>
455      * Beware that the name is misleading: in the general case (i.e. not perfectly circular), none of the axes is
456      * perfectly aligned with velocity! The preferred name for this should be {@link #LVLH_CCSDS}.
457      * </p>
458      * <p>
459      * The axes of this frame are parallel to the axes of both the {@link #QSW} and {@link #LVLH} frames:
460      * </p>
461      * <ul>
462      *   <li>X<sub>LVLH_CCSDS/VVLH</sub> =  Y<sub>QSW/LVLH</sub></li>
463      *   <li>Y<sub>LVLH_CCSDS/VVLH</sub> = -Z<sub>QSW/LVLH</sub></li>
464      *   <li>Z<sub>LVLH_CCSDS/VVLH</sub> = -X<sub>QSW/LVLH</sub></li>
465      * </ul>
466      *
467      * @see #LVLH_CCSDS
468      */
469     VVLH_INERTIAL {
470         /** {@inheritDoc} */
471         @Override
472         public Rotation rotationFromInertial(final PVCoordinates pv) {
473             return VVLH.rotationFromInertial(pv);
474         }
475 
476         /** {@inheritDoc} */
477         @Override
478         public <T extends CalculusFieldElement<T>> FieldRotation<T> rotationFromInertial(final Field<T> field,
479                                                                                          final FieldPVCoordinates<T> pv) {
480             return VVLH.rotationFromInertial(field, pv);
481         }
482 
483         /** {@inheritDoc} */
484         @Override
485         public boolean isQuasiInertial() {
486             return true;
487         }
488 
489         /** {@inheritDoc} */
490         @Override
491         public OrbitRelativeFrame toOrbitRelativeFrame() {
492             return OrbitRelativeFrame.LVLH_INERTIAL;
493         }
494 
495     },
496 
497     /** Constant for Velocity - Normal - Co-normal frame
498      * (X axis aligned with velocity, Y axis aligned with orbital momentum).
499      * <p>
500      * The axes of this frame are parallel to the axes of the {@link #TNW}
501      * and {@link #NTW} frames:
502      * </p>
503      * <ul>
504      *   <li>X<sub>VNC</sub> =  X<sub>TNW</sub> = Y<sub>NTW</sub></li>
505      *   <li>Y<sub>VNC</sub> =  Z<sub>TNW</sub> = Z<sub>NTW</sub></li>
506      *   <li>Z<sub>VNC</sub> = -Y<sub>TNW</sub> = X<sub>NTW</sub></li>
507      * </ul>
508      *
509      * @see #TNW
510      * @see #NTW
511      */
512     VNC {
513         /** {@inheritDoc} */
514         @Override
515         public Rotation rotationFromInertial(final PVCoordinates pv) {
516             return new Rotation(pv.getVelocity(), pv.getMomentum(),
517                                 Vector3D.PLUS_I, Vector3D.PLUS_J);
518         }
519 
520         /** {@inheritDoc} */
521         @Override
522         public <T extends CalculusFieldElement<T>> FieldRotation<T> rotationFromInertial(final Field<T> field,
523                                                                                          final FieldPVCoordinates<T> pv) {
524             return new FieldRotation<>(pv.getVelocity(), pv.getMomentum(),
525                                        new FieldVector3D<>(field, Vector3D.PLUS_I),
526                                        new FieldVector3D<>(field, Vector3D.PLUS_J));
527         }
528 
529         /** {@inheritDoc} */
530         @Override
531         public OrbitRelativeFrame toOrbitRelativeFrame() {
532             return OrbitRelativeFrame.VNC_ROTATING;
533         }
534 
535     },
536 
537     /**
538      * Constant for Velocity - Normal - Co-normal frame considered inertial (X axis aligned with velocity, Y axis
539      * aligned with orbital momentum).
540      * <p>
541      * The axes of this frame are parallel to the axes of the {@link #TNW} and {@link #NTW} frames:
542      * </p>
543      * <ul>
544      *   <li>X<sub>VNC</sub> =  X<sub>TNW</sub> = Y<sub>NTW</sub></li>
545      *   <li>Y<sub>VNC</sub> =  Z<sub>TNW</sub> = Z<sub>NTW</sub></li>
546      *   <li>Z<sub>VNC</sub> = -Y<sub>TNW</sub> = X<sub>NTW</sub></li>
547      * </ul>
548      *
549      * @see #TNW
550      * @see #NTW
551      */
552     VNC_INERTIAL {
553         /** {@inheritDoc} */
554         @Override
555         public Rotation rotationFromInertial(final PVCoordinates pv) {
556             return VNC.rotationFromInertial(pv);
557         }
558 
559         /** {@inheritDoc} */
560         @Override
561         public <T extends CalculusFieldElement<T>> FieldRotation<T> rotationFromInertial(final Field<T> field,
562                                                                                          final FieldPVCoordinates<T> pv) {
563             return VNC.rotationFromInertial(field, pv);
564         }
565 
566         /** {@inheritDoc} */
567         @Override
568         public boolean isQuasiInertial() {
569             return true;
570         }
571 
572         /** {@inheritDoc} */
573         @Override
574         public OrbitRelativeFrame toOrbitRelativeFrame() {
575             return OrbitRelativeFrame.VNC_INERTIAL;
576         }
577 
578     },
579 
580     /**
581      * Constant for Equinoctial Coordinate System (X axis aligned with ascending node, Z axis aligned with orbital
582      * momentum).
583      *
584      * @since 11.0
585      */
586     EQW {
587         /** {@inheritDoc} */
588         @Override
589         public Rotation rotationFromInertial(final PVCoordinates pv) {
590             final Vector3D m = pv.getMomentum();
591             return new Rotation(new Vector3D(-m.getY(), m.getX(), 0), m,
592                                 Vector3D.PLUS_I, Vector3D.PLUS_K);
593         }
594 
595         /** {@inheritDoc} */
596         @Override
597         public <T extends CalculusFieldElement<T>> FieldRotation<T> rotationFromInertial(final Field<T> field,
598                                                                                          final FieldPVCoordinates<T> pv) {
599             final FieldVector3D<T> m = pv.getMomentum();
600             return new FieldRotation<>(new FieldVector3D<>(m.getY().negate(), m.getX(), field.getZero()),
601                                        m,
602                                        new FieldVector3D<>(field, Vector3D.PLUS_I),
603                                        new FieldVector3D<>(field, Vector3D.PLUS_K));
604         }
605 
606         /** {@inheritDoc} */
607         @Override
608         public boolean isQuasiInertial() {
609             return true;
610         }
611 
612         /** {@inheritDoc} */
613         @Override
614         public OrbitRelativeFrame toOrbitRelativeFrame() {
615             return OrbitRelativeFrame.EQW_INERTIAL;
616         }
617 
618     },
619 
620     /** Constant for Transverse Velocity Normal coordinate system
621      * (Y axis aligned with velocity, Z axis aligned with orbital momentum).
622      * <p>
623      * The axes of this frame are parallel to the axes of the {@link #TNW}
624      * and {@link #VNC} frames:
625      * </p>
626      * <ul>
627      *   <li>X<sub>NTW</sub> = -Y<sub>TNW</sub> = Z<sub>VNC</sub></li>
628      *   <li>Y<sub>NTW</sub> =  X<sub>TNW</sub> = X<sub>VNC</sub></li>
629      *   <li>Z<sub>NTW</sub> =  Z<sub>TNW</sub> = Y<sub>VNC</sub></li>
630      * </ul>
631      * @see #TNW
632      * @see #VNC
633      * @since 11.0
634      */
635     NTW {
636         /** {@inheritDoc} */
637         @Override
638         public Rotation rotationFromInertial(final PVCoordinates pv) {
639             return new Rotation(pv.getVelocity(), pv.getMomentum(),
640                                 Vector3D.PLUS_J, Vector3D.PLUS_K);
641         }
642 
643         /** {@inheritDoc} */
644         @Override
645         public <T extends CalculusFieldElement<T>> FieldRotation<T> rotationFromInertial(final Field<T> field,
646                                                                                          final FieldPVCoordinates<T> pv) {
647             return new FieldRotation<>(pv.getVelocity(), pv.getMomentum(),
648                                        new FieldVector3D<>(field, Vector3D.PLUS_J),
649                                        new FieldVector3D<>(field, Vector3D.PLUS_K));
650         }
651 
652         /** {@inheritDoc} */
653         @Override
654         public OrbitRelativeFrame toOrbitRelativeFrame() {
655             return OrbitRelativeFrame.NTW_ROTATING;
656         }
657 
658     },
659 
660     /**
661      * Constant for Transverse Velocity Normal coordinate system considered inertial (Y axis aligned with velocity, Z
662      * axis aligned with orbital momentum).
663      * <p>
664      * The axes of this frame are parallel to the axes of the {@link #TNW} and {@link #VNC} frames:
665      * </p>
666      * <ul>
667      *   <li>X<sub>NTW</sub> = -Y<sub>TNW</sub> = Z<sub>VNC</sub></li>
668      *   <li>Y<sub>NTW</sub> =  X<sub>TNW</sub> = X<sub>VNC</sub></li>
669      *   <li>Z<sub>NTW</sub> =  Z<sub>TNW</sub> = Y<sub>VNC</sub></li>
670      * </ul>
671      *
672      * @see #TNW
673      * @see #VNC
674      * @since 11.0
675      */
676     NTW_INERTIAL {
677         /** {@inheritDoc} */
678         @Override
679         public Rotation rotationFromInertial(final PVCoordinates pv) {
680             return NTW.rotationFromInertial(pv);
681         }
682 
683         /** {@inheritDoc} */
684         @Override
685         public <T extends CalculusFieldElement<T>> FieldRotation<T> rotationFromInertial(final Field<T> field,
686                                                                                          final FieldPVCoordinates<T> pv) {
687             return NTW.rotationFromInertial(field, pv);
688         }
689 
690         /** {@inheritDoc} */
691         @Override
692         public boolean isQuasiInertial() {
693             return true;
694         }
695 
696         /** {@inheritDoc} */
697         @Override
698         public OrbitRelativeFrame toOrbitRelativeFrame() {
699             return OrbitRelativeFrame.NTW_INERTIAL;
700         }
701 
702     },
703 
704     /**
705      * Constant for East-North-Up frame.
706      * (Z aligned with position, North Pole in the (+Y, ±Z) half-plane)
707      * @see #NED
708      * @since 13.0
709      */
710     ENU {
711         /** {@inheritDoc} */
712         @Override
713         public Rotation rotationFromInertial(final PVCoordinates pv) {
714             return new Rotation(pv.getPosition(), east(pv),
715                                 Vector3D.PLUS_K, Vector3D.PLUS_I);
716         }
717 
718         /** {@inheritDoc} */
719         @Override
720         public <T extends CalculusFieldElement<T>> FieldRotation<T> rotationFromInertial(final Field<T> field,
721                                                                                          final FieldPVCoordinates<T> pv) {
722             return new FieldRotation<>(pv.getPosition(), east(pv),
723                                        FieldVector3D.getPlusK(field), FieldVector3D.getPlusI(field));
724         }
725 
726         /** {@inheritDoc} */
727         @Override
728         public boolean isQuasiInertial() {
729             return false;
730         }
731 
732         /** {@inheritDoc} */
733         @Override
734         public OrbitRelativeFrame toOrbitRelativeFrame() {
735             return null;
736         }
737 
738     },
739 
740     /**
741      * Constant for North-East-Down frame.
742      * (Z aligned with opposite of position, North Pole in the (+X, ±Z) half-plane)
743      * @see #ENU
744      * @since 13.0
745      */
746     NED {
747         /** {@inheritDoc} */
748         @Override
749         public Rotation rotationFromInertial(final PVCoordinates pv) {
750             return new Rotation(pv.getPosition(), east(pv),
751                                 Vector3D.MINUS_K, Vector3D.PLUS_J);
752         }
753 
754         /** {@inheritDoc} */
755         @Override
756         public <T extends CalculusFieldElement<T>> FieldRotation<T> rotationFromInertial(final Field<T> field,
757                                                                                          final FieldPVCoordinates<T> pv) {
758             return new FieldRotation<>(pv.getPosition(), east(pv),
759                                        FieldVector3D.getMinusK(field), FieldVector3D.getPlusJ(field));
760         }
761 
762         /** {@inheritDoc} */
763         @Override
764         public boolean isQuasiInertial() {
765             return false;
766         }
767 
768         /** {@inheritDoc} */
769         @Override
770         public OrbitRelativeFrame toOrbitRelativeFrame() {
771             return null;
772         }
773 
774     };
775 
776     /** {@inheritDoc} */
777     public String getName() {
778         return this.name();
779     }
780 
781     /**
782      * Get the rotation from input to output {@link LOFType local orbital frame}.
783      * <p>
784      * This rotation does not include any time derivatives. If first time derivatives (i.e. rotation rate) is needed as well,
785      * the full {@link #transformFromLOFInToLOFOut(LOF, LOF, AbsoluteDate, PVCoordinates)} method must be called and
786      * the complete rotation transform must be extracted from it.
787      *
788      * @param in input commonly used local orbital frame
789      * @param out output commonly used local orbital frame
790      * @param pv position-velocity of the spacecraft in some inertial frame
791      *
792      * @return rotation from input to output local orbital frame
793      */
794     static Rotation rotationFromLOFInToLOFOut(final LOFType in, final LOFType out, final PVCoordinates pv) {
795         return out.rotationFromLOF(in, pv);
796     }
797 
798     /**
799      * Get the rotation from input to output {@link LOFType local orbital frame}.
800      * <p>
801      * This rotation does not include any time derivatives. If first time derivatives (i.e. rotation rate) is needed as well,
802      * the full {@link #transformFromLOFInToLOFOut(LOF, LOF, FieldAbsoluteDate, FieldPVCoordinates)}  method must be called and
803      * the complete rotation transform must be extracted from it.
804      *
805      * @param field field to which the elements belong
806      * @param in input commonly used local orbital frame
807      * @param out output commonly used local orbital frame
808      * @param pv position-velocity of the spacecraft in some inertial frame
809      * @param <T> type of the field elements
810      *
811      * @return rotation from input to output local orbital frame
812      */
813     static <T extends CalculusFieldElement<T>> FieldRotation<T> rotationFromLOFInToLOFOut(final Field<T> field,
814                                                                                           final LOFType in,
815                                                                                           final LOFType out,
816                                                                                           final FieldPVCoordinates<T> pv) {
817         return out.rotationFromLOF(field, in, pv);
818     }
819 
820     /**
821      * Get the rotation from input {@link LOF local orbital frame} to the instance.
822      * <p>
823      * This rotation does not include any time derivatives. If first time derivatives (i.e. rotation rate) is needed as well,
824      * the full {@link #transformFromLOF(LOF, AbsoluteDate, PVCoordinates)} method must be called and the complete rotation
825      * transform must be extracted from it.
826      *
827      * @param fromLOF input local orbital frame
828      * @param pv position-velocity of the spacecraft in some inertial frame
829      *
830      * @return rotation from input local orbital frame to the instance
831      */
832     public Rotation rotationFromLOF(final LOFType fromLOF, final PVCoordinates pv) {
833 
834         // First compute the rotation from the input LOF to the pivot inertial
835         final Rotation fromLOFToInertial = fromLOF.rotationFromInertial(pv).revert();
836 
837         // Then compute the rotation from the pivot inertial to the output LOF
838         final Rotation inertialToThis = this.rotationFromInertial(pv);
839 
840         // Output composed rotation
841         return fromLOFToInertial.compose(inertialToThis, RotationConvention.FRAME_TRANSFORM);
842     }
843 
844     /**
845      * Get the rotation from input {@link LOFType local orbital frame} to the instance.
846      * <p>
847      * This rotation does not include any time derivatives. If first time derivatives (i.e. rotation rate) is needed as well,
848      * the full {@link #transformFromLOF(LOF, FieldAbsoluteDate, FieldPVCoordinates)} method must be called and the complete
849      * rotation transform must be extracted from it.
850      *
851      * @param field field to which the elements belong
852      * @param fromLOF input local orbital frame
853      * @param pv position-velocity of the spacecraft in some inertial frame
854      * @param <T> type of the field elements
855      *
856      * @return rotation from input local orbital frame to the instance
857      */
858     public <T extends CalculusFieldElement<T>> FieldRotation<T> rotationFromLOF(final Field<T> field,
859                                                                                 final LOFType fromLOF,
860                                                                                 final FieldPVCoordinates<T> pv) {
861 
862         // First compute the rotation from the input LOF to the pivot inertial
863         final FieldRotation<T> fromLOFToInertial = fromLOF.rotationFromInertial(field, pv).revert();
864 
865         // Then compute the rotation from the pivot inertial to the output LOF
866         final FieldRotation<T> inertialToThis = this.rotationFromInertial(field, pv);
867 
868         // Output composed rotation
869         return fromLOFToInertial.compose(inertialToThis, RotationConvention.FRAME_TRANSFORM);
870     }
871 
872     /**
873      * {@inheritDoc} It is unnecessary to use this method when dealing with {@link LOFType}, use
874      * {@link #rotationFromInertial(PVCoordinates)} instead.
875      */
876     @Override
877     public Rotation rotationFromInertial(final AbsoluteDate date, final PVCoordinates pv) {
878         return rotationFromInertial(pv);
879     }
880 
881     /**
882      * Get the rotation from inertial frame to local orbital frame.
883      * <p>
884      * This rotation does not include any time derivatives. If first time derivatives (i.e. rotation rate) is needed as well,
885      * the full {@link #transformFromInertial(AbsoluteDate, PVCoordinates)} method must be called and
886      * the complete rotation transform must be extracted from it.
887      * </p>
888      *
889      * @param pv position-velocity of the spacecraft in some inertial frame
890      *
891      * @return rotation from inertial frame to local orbital frame
892      */
893     public abstract Rotation rotationFromInertial(PVCoordinates pv);
894 
895     /**
896      * {@inheritDoc} It is unnecessary to use this method when dealing with {@link LOFType}, use
897      * {@link #rotationFromInertial(Field, FieldPVCoordinates)} instead.
898      */
899     @Override
900     public <T extends CalculusFieldElement<T>> FieldRotation<T> rotationFromInertial(final Field<T> field,
901                                                                                      final FieldAbsoluteDate<T> date,
902                                                                                      final FieldPVCoordinates<T> pv) {
903         return rotationFromInertial(field, pv);
904     }
905 
906     /**
907      * Get the rotation from inertial frame to local orbital frame.
908      * <p>
909      * This rotation does not include any time derivatives. If first time derivatives (i.e. rotation rate) is needed as well,
910      * the full {@link #transformFromInertial(FieldAbsoluteDate, FieldPVCoordinates)} method must be
911      * called and the complete rotation transform must be extracted from it.
912      * </p>
913      *
914      * @param field field to which the elements belong
915      * @param pv position-velocity of the spacecraft in some inertial frame
916      * @param <T> type of the field elements
917      *
918      * @return rotation from inertial frame to local orbital frame
919      */
920     public abstract <T extends CalculusFieldElement<T>> FieldRotation<T> rotationFromInertial(Field<T> field,
921                                                                                               FieldPVCoordinates<T> pv);
922 
923     /**
924      * Convert current local orbital frame to CCSDS equivalent orbit relative frame when possible, null otherwise.
925      *
926      * @return CCSDS equivalent orbit relative frame when possible, null otherwise
927      *
928      * @see OrbitRelativeFrame
929      */
930     public abstract OrbitRelativeFrame toOrbitRelativeFrame();
931 
932     /** Compute East direction.
933      * @param pv position-velocity of the spacecraft in some inertial frame
934      * @return East direction
935      * @since 13.0
936      */
937     private static Vector3D east(final PVCoordinates pv) {
938         final Vector3D p = pv.getPosition();
939         final double px = p.getX();
940         final double py = p.getY();
941         return (px == 0.0 && py == 0.0) ? Vector3D.PLUS_J : new Vector3D(-py, px, 0);
942     }
943 
944     /** Compute East direction.
945      * @param <T> type of the field elements
946      * @param pv position-velocity of the spacecraft in some inertial frame
947      * @return East direction
948      * @since 13.0
949      */
950     private static <T extends CalculusFieldElement<T>> FieldVector3D<T> east(final FieldPVCoordinates<T> pv) {
951         final FieldVector3D<T> p = pv.getPosition();
952         final T px = p.getX();
953         final T py = p.getY();
954         return (px.isZero() && py.isZero()) ?
955                FieldVector3D.getPlusJ(px.getField()) :
956                new FieldVector3D<>(py.negate(), px, px.getField().getZero());
957     }
958 
959 }