1   /* Copyright 2022-2026 Romain Serra
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.orbits;
18  
19  import java.lang.reflect.Array;
20  
21  import org.hipparchus.CalculusFieldElement;
22  import org.hipparchus.Field;
23  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
24  import org.hipparchus.util.FastMath;
25  import org.hipparchus.util.FieldSinCos;
26  import org.orekit.utils.FieldPVCoordinates;
27  
28  /**
29   * Class for converting between Keplerian elements and Cartesian coordinates (Field version).
30   * It works for all types of orbits (elliptical or not).
31   * @author Romain Serra
32   * @see FieldKeplerianParameters
33   * @see KeplerianParametersConverter
34   * @since 14.0
35   */
36  public class FieldKeplerianParametersConverter<T extends CalculusFieldElement<T>> {
37  
38      /** Central body gravitational parameter. */
39      private final T mu;
40  
41      /**
42       * Constructor.
43       * @param mu central body gravitational parameter
44       */
45      public FieldKeplerianParametersConverter(final T mu) {
46          this.mu = mu;
47      }
48  
49      /**
50       * Convert Cartesian coordinates to Keplerian elements.
51       * @param cartesian position and velocity in inertial frame
52       * @param positionAngleType type of position angle to use
53       * @return Keplerian elements
54       */
55      public FieldKeplerianParameters<T> toParameters(final FieldPVCoordinates<T> cartesian,
56                                                      final PositionAngleType positionAngleType) {
57          // third canonical vector
58          final Field<T> field = cartesian.getPosition().getX().getField();
59          final FieldVector3D<T> plusK = FieldVector3D.getPlusK(field);
60  
61          // compute inclination
62          final FieldVector3D<T> momentum = cartesian.getMomentum();
63          final T m2 = momentum.getNorm2Sq();
64  
65          final T i = FieldVector3D.angle(momentum, plusK);
66          // compute right ascension of ascending node
67          final T raan = FieldVector3D.crossProduct(plusK, momentum).getAlpha();
68          // preliminary computations for parameters depending on orbit shape (elliptic or hyperbolic)
69          final FieldVector3D<T> pvP     = cartesian.getPosition();
70          final FieldVector3D<T> pvV     = cartesian.getVelocity();
71  
72          final T   r2      = pvP.getNorm2Sq();
73          final T   r       = r2.sqrt();
74          final T   v2      = pvV.getNorm2Sq();
75          final T   rV2OnMu = r.multiply(v2).divide(mu);
76  
77          // compute semi-major axis (will be negative for hyperbolic orbits)
78          final T a = r.divide(rV2OnMu.negate().add(2.0));
79          final T muA = a.multiply(mu);
80  
81          // compute true anomaly
82          final T e;
83          final T eccentricAnomaly;
84          final PositionAngleType intermediateType = PositionAngleType.ECCENTRIC;
85          if (a.getReal() > 0.) {
86              // elliptic or circular orbit
87              final T eSE = FieldVector3D.dotProduct(pvP, pvV).divide(muA.sqrt());
88              final T eCE = rV2OnMu.subtract(1);
89              e = (eSE.multiply(eSE).add(eCE.multiply(eCE))).sqrt();
90              eccentricAnomaly = eSE.atan2(eCE);
91          } else {
92              // hyperbolic orbit
93              final T eSH = FieldVector3D.dotProduct(pvP, pvV).divide(muA.negate().sqrt());
94              final T eCH = rV2OnMu.subtract(1);
95              e = (m2.negate().divide(muA).add(1)).sqrt();
96              eccentricAnomaly = (eCH.add(eSH)).divide(eCH.subtract(eSH)).log().divide(2);
97          }
98  
99          // compute perigee argument
100         final FieldVector3D<T> node = new FieldVector3D<>(raan, field.getZero());
101         final T px = FieldVector3D.dotProduct(pvP, node);
102         final T py = FieldVector3D.dotProduct(pvP, FieldVector3D.crossProduct(momentum, node)).divide(m2.sqrt());
103         final T trueAnomaly = FieldKeplerianAnomalyUtility.convertAnomaly(intermediateType, eccentricAnomaly, e, PositionAngleType.TRUE);
104         final T pa = py.atan2(px).subtract(trueAnomaly);
105 
106         return switch (positionAngleType) {
107             case PositionAngleType.MEAN -> {
108                 final T meanAnomaly = FieldKeplerianAnomalyUtility.convertAnomaly(intermediateType, eccentricAnomaly, e, positionAngleType);
109                 yield new FieldKeplerianParameters<>(a, e, i, pa, raan, meanAnomaly, positionAngleType);
110             }
111             case PositionAngleType.TRUE -> new FieldKeplerianParameters<>(a, e, i, pa, raan, trueAnomaly, positionAngleType);
112             case PositionAngleType.ECCENTRIC -> new FieldKeplerianParameters<>(a, e, i, pa, raan, eccentricAnomaly, positionAngleType);
113         };
114     }
115 
116     /**
117      * Convert Keplerian elements to Cartesian coordinates.
118      * @param elements Keplerian elements
119      * @return position and velocity in inertial frame
120      */
121     public FieldPVCoordinates<T> toCartesian(final FieldKeplerianParameters<T> elements) {
122         final FieldVector3D<T> position;
123         final FieldVector3D<T> velocity;
124         final FieldVector3D<T>[] axes = referenceAxes(elements.i(), elements.pa(), elements.raan());
125 
126         final T e = elements.e();
127         final T a = elements.a();
128         if (elements.a().getReal() > 0.) {
129             // elliptic eccentric anomaly
130             final T uME2             = e.negate().add(1).multiply(e.add(1));
131             final T s1Me2            = uME2.sqrt();
132             final FieldKeplerianParameters<T> elementsWithEccentricAnomaly = elements.withPositionAngleType(PositionAngleType.ECCENTRIC);
133             final T eccentricAnomaly = elementsWithEccentricAnomaly.anomaly();
134             final FieldSinCos<T> scE = FastMath.sinCos(eccentricAnomaly);
135             final T cosE             = scE.cos();
136             final T sinE             = scE.sin();
137 
138             // coordinates of position and velocity in the orbital plane
139             final T x      = a.multiply(cosE.subtract(e));
140             final T y      = a.multiply(sinE).multiply(s1Me2);
141             final T factor = FastMath.sqrt(mu.divide(a)).divide(e.negate().multiply(cosE).add(1));
142             final T xDot   = sinE.negate().multiply(factor);
143             final T yDot   = cosE.multiply(s1Me2).multiply(factor);
144 
145             position = new FieldVector3D<>(x, axes[0], y, axes[1]);
146             velocity = new FieldVector3D<>(xDot, axes[0], yDot, axes[1]);
147 
148         } else {
149             // hyperbolic case
150 
151             // compute position and velocity factors
152             final FieldKeplerianParameters<T> elementsWithTrueAnomaly = elements.withPositionAngleType(PositionAngleType.TRUE);
153             final FieldSinCos<T> scV = FastMath.sinCos(elementsWithTrueAnomaly.anomaly());
154             final T sinV             = scV.sin();
155             final T cosV             = scV.cos();
156             final T f                = a.multiply(e.square().negate().add(1));
157             final T posFactor        = f.divide(e.multiply(cosV).add(1));
158             final T velFactor        = FastMath.sqrt(mu.divide(f));
159 
160             position     = new FieldVector3D<>(posFactor.multiply(cosV), axes[0], posFactor.multiply(sinV), axes[1]);
161             velocity     = new FieldVector3D<>(velFactor.multiply(sinV).negate(), axes[0], velFactor.multiply(e.add(cosV)), axes[1]);
162 
163         }
164         return new FieldPVCoordinates<>(position, velocity);
165     }
166 
167     /** Compute reference axes.
168      * @param <W> type of the field elements
169      * @param i inclination
170      * @param pa perigee argument
171      * @param raan right ascension of ascending node
172      * @return reference axes
173      */
174     static <W extends CalculusFieldElement<W>> FieldVector3D<W>[] referenceAxes(final W i, final W pa, final W raan) {
175         // preliminary variables
176         final FieldSinCos<W> scRaan = FastMath.sinCos(raan);
177         final FieldSinCos<W> scPa   = FastMath.sinCos(pa);
178         final FieldSinCos<W> scI    = FastMath.sinCos(i);
179         final W cosRaan = scRaan.cos();
180         final W sinRaan = scRaan.sin();
181         final W cosPa   = scPa.cos();
182         final W sinPa   = scPa.sin();
183         final W cosI    = scI.cos();
184         final W sinI    = scI.sin();
185         final W crcp    = cosRaan.multiply(cosPa);
186         final W crsp    = cosRaan.multiply(sinPa);
187         final W srcp    = sinRaan.multiply(cosPa);
188         final W srsp    = sinRaan.multiply(sinPa);
189 
190         // reference axes defining the orbital plane
191         @SuppressWarnings("unchecked")
192         final FieldVector3D<W>[] axes = (FieldVector3D<W>[]) Array.newInstance(FieldVector3D.class, 2);
193         axes[0] = new FieldVector3D<>(crcp.subtract(cosI.multiply(srsp)),  srcp.add(cosI.multiply(crsp)), sinI.multiply(sinPa));
194         axes[1] = new FieldVector3D<>(crsp.add(cosI.multiply(srcp)).negate(), cosI.multiply(crcp).subtract(srsp), sinI.multiply(cosPa));
195 
196         return axes;
197     }
198 }