1   /* Copyright 2002-2026 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.files.ccsds.ndm.odm.ocm;
18  
19  import java.util.List;
20  import java.util.stream.Collectors;
21  import java.util.stream.Stream;
22  
23  import org.hipparchus.geometry.euclidean.threed.Vector3D;
24  import org.hipparchus.util.FastMath;
25  import org.hipparchus.util.MathUtils;
26  import org.hipparchus.util.SinCos;
27  import org.orekit.annotation.DefaultDataContext;
28  import org.orekit.bodies.GeodeticPoint;
29  import org.orekit.bodies.OneAxisEllipsoid;
30  import org.orekit.errors.OrekitException;
31  import org.orekit.errors.OrekitMessages;
32  import org.orekit.frames.Frame;
33  import org.orekit.frames.FramesFactory;
34  import org.orekit.orbits.EquinoctialOrbit;
35  import org.orekit.orbits.KeplerianOrbit;
36  import org.orekit.orbits.PositionAngleType;
37  import org.orekit.time.AbsoluteDate;
38  import org.orekit.utils.TimeStampedPVCoordinates;
39  import org.orekit.utils.units.Unit;
40  
41  /** Orbit element set type used in CCSDS {@link Ocm Orbit Comprehensive Messages}.
42   * @see <a href="https://sanaregistry.org/r/orbital_elements">SANA registry for orbital elements</a>
43   * @author Luc Maisonobe
44   * @since 11.0
45   */
46  public enum OrbitElementsType {
47  
48      // CHECKSTYLE: stop MultipleStringLiterals check
49  
50      /** Spherical 6-element set (α,δ,β,A,r,v). */
51      ADBARV("Spherical 6-element set (α,δ,β,A,r,v)",
52             "°", "°", "°", "°", "km", "km/s"),
53  
54      /** Cartesian 3-element position (X, Y, Z). */
55      CARTP("Cartesian 3-element position (X, Y, Z)",
56            "km", "km", "km") {
57  
58          /** {@inheritDoc} */
59          @Override
60          public TimeStampedPVCoordinates toCartesian(final AbsoluteDate date, final double[] elements,
61                                                      final OneAxisEllipsoid body, final double mu) {
62              return new TimeStampedPVCoordinates(date,
63                                                  new Vector3D(elements[0], elements[1], elements[2]),
64                                                  Vector3D.ZERO,
65                                                  Vector3D.ZERO);
66          }
67  
68          /** {@inheritDoc} */
69          @Override
70          public double[] toRawElements(final TimeStampedPVCoordinates pv, final Frame frame,
71                                        final OneAxisEllipsoid body, final double mu) {
72              return new double[] {
73                  pv.getPosition().getX(), pv.getPosition().getY(), pv.getPosition().getZ()
74              };
75          }
76  
77      },
78  
79      /** Cartesian 6-element position and velocity (X, Y, Z, XD, YD, ZD). */
80      CARTPV("Cartesian 6-element position and velocity (X, Y, Z, XD, YD, ZD)",
81             "km", "km", "km", "km/s", "km/s", "km/s") {
82  
83          /** {@inheritDoc} */
84          @Override
85          public TimeStampedPVCoordinates toCartesian(final AbsoluteDate date, final double[] elements,
86                                                      final OneAxisEllipsoid body, final double mu) {
87              return new TimeStampedPVCoordinates(date,
88                                                  new Vector3D(elements[0], elements[1], elements[2]),
89                                                  new Vector3D(elements[3], elements[4], elements[5]),
90                                                  Vector3D.ZERO);
91          }
92  
93          /** {@inheritDoc} */
94          @Override
95          public double[] toRawElements(final TimeStampedPVCoordinates pv, final Frame frame,
96                                        final OneAxisEllipsoid body, final double mu) {
97              return new double[] {
98                  pv.getPosition().getX(), pv.getPosition().getY(), pv.getPosition().getZ(),
99                  pv.getVelocity().getX(), pv.getVelocity().getY(), pv.getVelocity().getZ()
100             };
101         }
102 
103     },
104 
105     /** Cartesian 9-element position, velocity and acceleration (X, Y, Z, XD, YD, ZD, XDD, YDD, ZDD). */
106     CARTPVA("Cartesian 9-element position, velocity and acceleration (X, Y, Z, XD, YD, ZD, XDD, YDD, ZDD)",
107             "km", "km", "km", "km/s", "km/s", "km/s", "km/s²", "km/s²", "km/s²") {
108 
109         /** {@inheritDoc} */
110         @Override
111         public TimeStampedPVCoordinates toCartesian(final AbsoluteDate date, final double[] elements,
112                                                     final OneAxisEllipsoid body, final double mu) {
113             return new TimeStampedPVCoordinates(date,
114                                                 new Vector3D(elements[0], elements[1], elements[2]),
115                                                 new Vector3D(elements[3], elements[4], elements[5]),
116                                                 new Vector3D(elements[6], elements[7], elements[8]));
117         }
118 
119         /** {@inheritDoc} */
120         @Override
121         public double[] toRawElements(final TimeStampedPVCoordinates pv, final Frame frame,
122                                       final OneAxisEllipsoid body, final double mu) {
123             return new double[] {
124                 pv.getPosition().getX(),     pv.getPosition().getY(),     pv.getPosition().getZ(),
125                 pv.getVelocity().getX(),     pv.getVelocity().getY(),     pv.getVelocity().getZ(),
126                 pv.getAcceleration().getX(), pv.getAcceleration().getY(), pv.getAcceleration().getZ()
127             };
128         }
129 
130     },
131 
132     /** Delaunay elements (L, G, H, l, g, h). */
133     DELAUNAY("Delaunay elements (L, G, H, l, g, h)",
134              "km²/s", "km²/s", "km²/s", "°", "°", "°"),
135 
136     /** Modified Delaunay elements (Lm, Gm, Hm, lm, gm, hm). */
137     DELAUNAYMOD("Delaunay elements (Lm, Gm, Hm, lm, gm, hm)",
138                 "√km", "√km", "√km", "°", "°", "°"),
139 
140     /** 12 elements eigenvalue/eigenvectors (EigMaj, EigMed, EigMin, EigVecMaj, EigVecMed, EigVecMin). */
141     EIGVAL3EIGVEC3("12 elements eigenvalue/eigenvectors (EigMaj, EigMed, EigMin, EigVecMaj, EigVecMed, EigVecMin)",
142                    "km", "km", "km", "n/a", "n/a", "n/a", "n/a", "n/a", "n/a", "n/a", "n/a", "n/a"),
143 
144     /** Equinoctial elements (a, af, ag, L=M+ω+frΩ, χ, ψ, fr). */
145     EQUINOCTIAL("Equinoctial elements (a, af, ag, L=M+ω+frΩ, χ, ψ, fr)",
146                 "km", "n/a", "n/a", "°", "n/a", "n/a", "n/a") {
147 
148         /** {@inheritDoc} */
149         @Override
150         @DefaultDataContext
151         public TimeStampedPVCoordinates toCartesian(final AbsoluteDate date, final double[] elements,
152                                                     final OneAxisEllipsoid body, final double mu) {
153             if (elements[6] < 0) {
154                 // retrograde
155                 throw new OrekitException(OrekitMessages.CCSDS_UNSUPPORTED_RETROGRADE_EQUINOCTIAL,
156                                           EQUINOCTIAL.name());
157             }
158             return new EquinoctialOrbit(elements[0], elements[1], elements[2],
159                                         elements[5], elements[4], // BEWARE! the inversion here is intentional
160                                         elements[3], PositionAngleType.MEAN,
161                                         FramesFactory.getGCRF(), date, mu).
162                             getPVCoordinates();
163         }
164 
165         /** {@inheritDoc} */
166         @Override
167         public double[] toRawElements(final TimeStampedPVCoordinates pv, final Frame frame,
168                                       final OneAxisEllipsoid body, final double mu) {
169             final EquinoctialOrbit orbit = new EquinoctialOrbit(pv, frame, mu);
170             return new double[] {
171                 orbit.getA(), orbit.getEquinoctialEx(), orbit.getEquinoctialEy(),
172                 orbit.getLM(), orbit.getHy(), orbit.getHx(), +1
173             };
174         }
175 
176     },
177 
178     /** Modified equinoctial elements (p=a(1−e²), af, ag, L'=υ+ω+frΩ, χ, ψ, fr). */
179     EQUINOCTIALMOD("Modified equinoctial elements (p=a(1−e²), af, ag, L'=υ+ω+frΩ, χ, ψ, fr)",
180                    "km", "n/a", "n/a", "°", "n/a", "n/a", "n/a") {
181 
182         /** {@inheritDoc} */
183         @Override
184         @DefaultDataContext
185         public TimeStampedPVCoordinates toCartesian(final AbsoluteDate date, final double[] elements,
186                                                     final OneAxisEllipsoid body, final double mu) {
187             if (elements[6] < 0) {
188                 // retrograde
189                 throw new OrekitException(OrekitMessages.CCSDS_UNSUPPORTED_RETROGRADE_EQUINOCTIAL,
190                                           EQUINOCTIALMOD.name());
191             }
192             final double oMe2 = 1.0 - (elements[1] * elements[1] + elements[2] * elements[2]);
193             return new EquinoctialOrbit(elements[0] / oMe2, elements[1], elements[2],
194                                         elements[5], elements[4], // BEWARE! the inversion here is intentional
195                                         elements[3], PositionAngleType.TRUE,
196                                         FramesFactory.getGCRF(), date, mu).
197                             getPVCoordinates();
198         }
199 
200         /** {@inheritDoc} */
201         @Override
202         public double[] toRawElements(final TimeStampedPVCoordinates pv, final Frame frame,
203                                       final OneAxisEllipsoid body, final double mu) {
204             final EquinoctialOrbit orbit = new EquinoctialOrbit(pv, frame, mu);
205             final double           ex    = orbit.getEquinoctialEx();
206             final double           ey    = orbit.getEquinoctialEy();
207             return new double[] {
208                 orbit.getA() * (1 - (ex * ex + ey * ey)), ex, ey,
209                 orbit.getLv(), orbit.getHy(), orbit.getHx(), +1
210             };
211         }
212 
213     },
214 
215     /** Geodetic elements (λ, ΦGD, β, A, h, vre). */
216     GEODETIC("Geodetic elements (λ, ΦGD, β, A, h, vre)",
217              "°", "°", "°", "°", "km", "km/s") {
218 
219         /** {@inheritDoc} */
220         @Override
221         @DefaultDataContext
222         public TimeStampedPVCoordinates toCartesian(final AbsoluteDate date, final double[] elements,
223                                                     final OneAxisEllipsoid body, final double mu) {
224             final GeodeticPoint gp       = new GeodeticPoint(elements[1], elements[0], elements[4]);
225             final Vector3D      position = body.transform(gp);
226             final SinCos        scBeta   = FastMath.sinCos(elements[2]);
227             final SinCos        scAzi    = FastMath.sinCos(elements[3]);
228             final Vector3D      velocity = new Vector3D(elements[5] * scBeta.cos() * scAzi.sin(), gp.getEast(),
229                                                         elements[5] * scBeta.cos() * scAzi.cos(), gp.getNorth(),
230                                                         elements[5] * scBeta.sin(), gp.getZenith());
231             return new TimeStampedPVCoordinates(date, position, velocity);
232         }
233 
234         /** {@inheritDoc} */
235         @Override
236         public double[] toRawElements(final TimeStampedPVCoordinates pv, final Frame frame,
237                                       final OneAxisEllipsoid body, final double mu) {
238             final GeodeticPoint gp = body.transform(pv.getPosition(), frame, pv.getDate());
239             return new double[] {
240                 gp.getLongitude(), gp.getLatitude(),
241                 MathUtils.SEMI_PI - Vector3D.angle(pv.getVelocity(), gp.getZenith()),
242                 FastMath.atan2(Vector3D.dotProduct(pv.getVelocity(), gp.getEast()),
243                                Vector3D.dotProduct(pv.getVelocity(), gp.getNorth())),
244                 gp.getAltitude(),
245                 pv.getVelocity().getNorm()
246             };
247         }
248 
249     },
250 
251     /** Keplerian 6-element classical set (a, e, i, Ω, ω, ν). */
252     KEPLERIAN("Keplerian 6-elemnt classical set (a, e, i, Ω, ω, ν)",
253               "km", "n/a", "°", "°", "°", "°") {
254 
255         /** {@inheritDoc} */
256         @Override
257         @DefaultDataContext
258         public TimeStampedPVCoordinates toCartesian(final AbsoluteDate date, final double[] elements,
259                                                     final OneAxisEllipsoid body, final double mu) {
260             return new KeplerianOrbit(elements[0], elements[1], elements[2],
261                                       elements[4], elements[3], // BEWARE! the inversion here is intentional
262                                       elements[5], PositionAngleType.TRUE,
263                                       Frame.getRoot(), date, mu).
264                    getPVCoordinates();
265         }
266 
267         /** {@inheritDoc} */
268         @Override
269         public double[] toRawElements(final TimeStampedPVCoordinates pv, final Frame frame,
270                                       final OneAxisEllipsoid body, final double mu) {
271             final KeplerianOrbit orbit = new KeplerianOrbit(pv, frame, mu);
272             return new double[] {
273                 orbit.getA(), orbit.getE(), orbit.getI(),
274                 orbit.getRightAscensionOfAscendingNode(),
275                 orbit.getPeriapsisArgument(), orbit.getTrueAnomaly()
276             };
277         }
278 
279     },
280 
281     /** Keplerian 6-element classical set (a, e, i, Ω, ω, M). */
282     KEPLERIANMEAN("Keplerian 6-elemnt classical set (a, e, i, Ω, ω, M)",
283                   "km", "n/a", "°", "°", "°", "°") {
284 
285         /** {@inheritDoc} */
286         @Override
287         @DefaultDataContext
288         public TimeStampedPVCoordinates toCartesian(final AbsoluteDate date, final double[] elements,
289                                                     final OneAxisEllipsoid body, final double mu) {
290             return new KeplerianOrbit(elements[0], elements[1], elements[2],
291                                       elements[4], elements[3], // BEWARE! the inversion here is intentional
292                                       elements[5], PositionAngleType.MEAN,
293                                       FramesFactory.getGCRF(), date, mu).
294                    getPVCoordinates();
295         }
296 
297         /** {@inheritDoc} */
298         @Override
299         public double[] toRawElements(final TimeStampedPVCoordinates pv, final Frame frame,
300                                       final OneAxisEllipsoid body, final double mu) {
301             final KeplerianOrbit orbit = new KeplerianOrbit(pv, frame, mu);
302             return new double[] {
303                 orbit.getA(), orbit.getE(), orbit.getI(),
304                 orbit.getRightAscensionOfAscendingNode(),
305                 orbit.getPeriapsisArgument(), orbit.getMeanAnomaly()
306             };
307         }
308 
309     },
310 
311     /** Modified spherical 6-element set (λ, δ, β, A, r, v). */
312     LDBARV("Modified spherical 6-element set (λ, δ, β, A, r, v)",
313            "°", "°", "°", "°", "km", "km/s"),
314 
315     /** Geosynchronous on-station tailored set (a, ex, ey, ix, iy, λ). */
316     ONSTATION("Geosynchronous on-station tailored set (a, ex, ey, ix, iy, λ)",
317               "km", "n/a", "n/a", "n/a", "n/a", "°"),
318 
319     /** Canonical counterpart of equinoctial 6-element set (λM=M+ω+Ω, gp, hp, Lp, Gp, Hp). */
320     POINCARE("Canonical counterpart of equinoctial 6-element set (λM=M+ω+Ω, gp, hp, Lp, Gp, Hp)",
321              "°", "km/√s", "km/√s", "km²/s", "km/√s", "km/√s");
322 
323     // CHECKSTYLE: resume MultipleStringLiterals check
324 
325     /** Description. */
326     private final String description;
327 
328     /** Elements units. */
329     private final List<Unit> units;
330 
331     /** Simple constructor.
332      * @param description description
333      * @param unitsSpecifications elements units specifications
334      */
335     OrbitElementsType(final String description, final String... unitsSpecifications) {
336         this.description = description;
337         this.units       = Stream.of(unitsSpecifications).
338                            map(Unit::parse).
339                            collect(Collectors.toList());
340     }
341 
342     /** Get the elements units.
343      * @return elements units
344      */
345     public List<Unit> getUnits() {
346         return units;
347     }
348 
349     /** Convert to Cartesian coordinates.
350      * @param date elements date
351      * @param elements elements values in SI units
352      * @param body central body
353      * (may be null if type is <em>not</em> {@link OrbitElementsType#GEODETIC})
354      * @param mu gravitational parameter in m³/s²
355      * @return Cartesian coordinates
356      */
357     public TimeStampedPVCoordinates toCartesian(final AbsoluteDate date, final double[] elements,
358                                                 final OneAxisEllipsoid body, final double mu) {
359         throw new OrekitException(OrekitMessages.CCSDS_UNSUPPORTED_ELEMENT_SET_TYPE, name(), toString());
360     }
361 
362     /** Convert to raw elements array.
363      * @param pv Cartesian coordinates
364      * @param frame inertial frame where elements are defined
365      * @param body central body
366      * (may be null if type is <em>not</em> {@link OrbitElementsType#GEODETIC})
367      * @param mu gravitational parameter in m³/s²
368      * @return elements elements values in SI units
369      * @since 12.0
370      */
371     public double[] toRawElements(final TimeStampedPVCoordinates pv, final Frame frame,
372                                   final OneAxisEllipsoid body, final double mu) {
373         throw new OrekitException(OrekitMessages.CCSDS_UNSUPPORTED_ELEMENT_SET_TYPE, name(), toString());
374     }
375 
376     /** {@inheritDoc} */
377     @Override
378     public String toString() {
379         return description;
380     }
381 
382 }