1   /* Copyright 2002-2024 CS GROUP
2    * Licensed to CS GROUP (CS) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * CS licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.orekit.orbits;
18  
19  import java.io.Serializable;
20  
21  import org.hipparchus.analysis.differentiation.UnivariateDerivative2;
22  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
23  import org.hipparchus.geometry.euclidean.threed.Vector3D;
24  import org.hipparchus.linear.MatrixUtils;
25  import org.hipparchus.util.FastMath;
26  import org.orekit.annotation.DefaultDataContext;
27  import org.orekit.data.DataContext;
28  import org.orekit.frames.Frame;
29  import org.orekit.time.AbsoluteDate;
30  import org.orekit.utils.FieldPVCoordinates;
31  import org.orekit.utils.PVCoordinates;
32  import org.orekit.utils.TimeStampedPVCoordinates;
33  
34  
35  /** This class holds Cartesian orbital parameters.
36  
37   * <p>
38   * The parameters used internally are the Cartesian coordinates:
39   *   <ul>
40   *     <li>x</li>
41   *     <li>y</li>
42   *     <li>z</li>
43   *     <li>xDot</li>
44   *     <li>yDot</li>
45   *     <li>zDot</li>
46   *   </ul>
47   * contained in {@link PVCoordinates}.
48   *
49  
50   * <p>
51   * Note that the implementation of this class delegates all non-Cartesian related
52   * computations ({@link #getA()}, {@link #getEquinoctialEx()}, ...) to an underlying
53   * instance of the {@link EquinoctialOrbit} class. This implies that using this class
54   * only for analytical computations which are always based on non-Cartesian
55   * parameters is perfectly possible but somewhat sub-optimal.
56   * </p>
57   * <p>
58   * The instance <code>CartesianOrbit</code> is guaranteed to be immutable.
59   * </p>
60   * @see    Orbit
61   * @see    KeplerianOrbit
62   * @see    CircularOrbit
63   * @see    EquinoctialOrbit
64   * @author Luc Maisonobe
65   * @author Guylaine Prat
66   * @author Fabien Maussion
67   * @author V&eacute;ronique Pommier-Maurussane
68   * @author Andrew Goetz
69   */
70  public class CartesianOrbit extends Orbit {
71  
72      /** Serializable UID. */
73      private static final long serialVersionUID = 20170414L;
74  
75      /** 6x6 identity matrix. */
76      private static final double[][] SIX_BY_SIX_IDENTITY = MatrixUtils.createRealIdentityMatrix(6).getData();
77  
78      /** Indicator for non-Keplerian derivatives. */
79      private final transient boolean hasNonKeplerianAcceleration;
80  
81      /** Underlying equinoctial orbit to which high-level methods are delegated. */
82      private transient EquinoctialOrbit equinoctial;
83  
84      /** Constructor from Cartesian parameters.
85       *
86       * <p> The acceleration provided in {@code pvCoordinates} is accessible using
87       * {@link #getPVCoordinates()} and {@link #getPVCoordinates(Frame)}. All other methods
88       * use {@code mu} and the position to compute the acceleration, including
89       * {@link #shiftedBy(double)} and {@link #getPVCoordinates(AbsoluteDate, Frame)}.
90       *
91       * @param pvaCoordinates the position, velocity and acceleration of the satellite.
92       * @param frame the frame in which the {@link PVCoordinates} are defined
93       * (<em>must</em> be a {@link Frame#isPseudoInertial pseudo-inertial frame})
94       * @param mu central attraction coefficient (m³/s²)
95       * @exception IllegalArgumentException if frame is not a {@link
96       * Frame#isPseudoInertial pseudo-inertial frame}
97       */
98      public CartesianOrbit(final TimeStampedPVCoordinates pvaCoordinates,
99                            final Frame frame, final double mu)
100         throws IllegalArgumentException {
101         super(pvaCoordinates, frame, mu);
102         hasNonKeplerianAcceleration = hasNonKeplerianAcceleration(pvaCoordinates, mu);
103         equinoctial = null;
104     }
105 
106     /** Constructor from Cartesian parameters.
107      *
108      * <p> The acceleration provided in {@code pvCoordinates} is accessible using
109      * {@link #getPVCoordinates()} and {@link #getPVCoordinates(Frame)}. All other methods
110      * use {@code mu} and the position to compute the acceleration, including
111      * {@link #shiftedBy(double)} and {@link #getPVCoordinates(AbsoluteDate, Frame)}.
112      *
113      * @param pvaCoordinates the position and velocity of the satellite.
114      * @param frame the frame in which the {@link PVCoordinates} are defined
115      * (<em>must</em> be a {@link Frame#isPseudoInertial pseudo-inertial frame})
116      * @param date date of the orbital parameters
117      * @param mu central attraction coefficient (m³/s²)
118      * @exception IllegalArgumentException if frame is not a {@link
119      * Frame#isPseudoInertial pseudo-inertial frame}
120      */
121     public CartesianOrbit(final PVCoordinates pvaCoordinates, final Frame frame,
122                           final AbsoluteDate date, final double mu)
123         throws IllegalArgumentException {
124         this(new TimeStampedPVCoordinates(date, pvaCoordinates), frame, mu);
125     }
126 
127     /** Constructor from any kind of orbital parameters.
128      * @param op orbital parameters to copy
129      */
130     public CartesianOrbit(final Orbit op) {
131         super(op.getPVCoordinates(), op.getFrame(), op.getMu());
132         hasNonKeplerianAcceleration = op.hasDerivatives();
133         if (op instanceof EquinoctialOrbit) {
134             equinoctial = (EquinoctialOrbit) op;
135         } else if (op instanceof CartesianOrbit) {
136             equinoctial = ((CartesianOrbit) op).equinoctial;
137         } else {
138             equinoctial = null;
139         }
140     }
141 
142     /** {@inheritDoc} */
143     public OrbitType getType() {
144         return OrbitType.CARTESIAN;
145     }
146 
147     /** Lazy evaluation of equinoctial parameters. */
148     private void initEquinoctial() {
149         if (equinoctial == null) {
150             if (hasDerivatives()) {
151                 // getPVCoordinates includes accelerations that will be interpreted as derivatives
152                 equinoctial = new EquinoctialOrbit(getPVCoordinates(), getFrame(), getDate(), getMu());
153             } else {
154                 // get rid of Keplerian acceleration so we don't assume
155                 // we have derivatives when in fact we don't have them
156                 equinoctial = new EquinoctialOrbit(new PVCoordinates(getPosition(),
157                                                                      getPVCoordinates().getVelocity()),
158                                                    getFrame(), getDate(), getMu());
159             }
160         }
161     }
162 
163     /** Get the position/velocity with derivatives.
164      * @return position/velocity with derivatives
165      * @since 10.2
166      */
167     private FieldPVCoordinates<UnivariateDerivative2> getPVDerivatives() {
168         // PVA coordinates
169         final PVCoordinates pva = getPVCoordinates();
170         final Vector3D      p   = pva.getPosition();
171         final Vector3D      v   = pva.getVelocity();
172         final Vector3D      a   = pva.getAcceleration();
173         // Field coordinates
174         final FieldVector3D<UnivariateDerivative2> pG = new FieldVector3D<>(new UnivariateDerivative2(p.getX(), v.getX(), a.getX()),
175                                                                new UnivariateDerivative2(p.getY(), v.getY(), a.getY()),
176                                                                new UnivariateDerivative2(p.getZ(), v.getZ(), a.getZ()));
177         final FieldVector3D<UnivariateDerivative2> vG = new FieldVector3D<>(new UnivariateDerivative2(v.getX(), a.getX(), 0.0),
178                                                                new UnivariateDerivative2(v.getY(), a.getY(), 0.0),
179                                                                new UnivariateDerivative2(v.getZ(), a.getZ(), 0.0));
180         return new FieldPVCoordinates<>(pG, vG);
181     }
182 
183     /** {@inheritDoc} */
184     public double getA() {
185         final double r  = getPosition().getNorm();
186         final double V2 = getPVCoordinates().getVelocity().getNormSq();
187         return r / (2 - r * V2 / getMu());
188     }
189 
190     /** {@inheritDoc} */
191     public double getADot() {
192         if (hasDerivatives()) {
193             final FieldPVCoordinates<UnivariateDerivative2> pv = getPVDerivatives();
194             final UnivariateDerivative2 r  = pv.getPosition().getNorm();
195             final UnivariateDerivative2 V2 = pv.getVelocity().getNormSq();
196             final UnivariateDerivative2 a  = r.divide(r.multiply(V2).divide(getMu()).subtract(2).negate());
197             return a.getDerivative(1);
198         } else {
199             return Double.NaN;
200         }
201     }
202 
203     /** {@inheritDoc} */
204     public double getE() {
205         final double muA = getMu() * getA();
206         if (isElliptical()) {
207             // elliptic or circular orbit
208             final Vector3D pvP   = getPosition();
209             final Vector3D pvV   = getPVCoordinates().getVelocity();
210             final double rV2OnMu = pvP.getNorm() * pvV.getNormSq() / getMu();
211             final double eSE     = Vector3D.dotProduct(pvP, pvV) / FastMath.sqrt(muA);
212             final double eCE     = rV2OnMu - 1;
213             return FastMath.sqrt(eCE * eCE + eSE * eSE);
214         } else {
215             // hyperbolic orbit
216             final Vector3D pvM = getPVCoordinates().getMomentum();
217             return FastMath.sqrt(1 - pvM.getNormSq() / muA);
218         }
219     }
220 
221     /** {@inheritDoc} */
222     public double getEDot() {
223         if (hasDerivatives()) {
224             final FieldPVCoordinates<UnivariateDerivative2> pv = getPVDerivatives();
225             final FieldVector3D<UnivariateDerivative2> pvP   = pv.getPosition();
226             final FieldVector3D<UnivariateDerivative2> pvV   = pv.getVelocity();
227             final UnivariateDerivative2 r       = pvP.getNorm();
228             final UnivariateDerivative2 V2      = pvV.getNormSq();
229             final UnivariateDerivative2 rV2OnMu = r.multiply(V2).divide(getMu());
230             final UnivariateDerivative2 a       = r.divide(rV2OnMu.negate().add(2));
231             final UnivariateDerivative2 eSE     = FieldVector3D.dotProduct(pvP, pvV).divide(a.multiply(getMu()).sqrt());
232             final UnivariateDerivative2 eCE     = rV2OnMu.subtract(1);
233             final UnivariateDerivative2 e       = eCE.multiply(eCE).add(eSE.multiply(eSE)).sqrt();
234             return e.getDerivative(1);
235         } else {
236             return Double.NaN;
237         }
238     }
239 
240     /** {@inheritDoc} */
241     public double getI() {
242         return Vector3D.angle(Vector3D.PLUS_K, getPVCoordinates().getMomentum());
243     }
244 
245     /** {@inheritDoc} */
246     public double getIDot() {
247         if (hasDerivatives()) {
248             final FieldPVCoordinates<UnivariateDerivative2> pv = getPVDerivatives();
249             final FieldVector3D<UnivariateDerivative2> momentum =
250                             FieldVector3D.crossProduct(pv.getPosition(), pv.getVelocity());
251             final UnivariateDerivative2 i = FieldVector3D.angle(Vector3D.PLUS_K, momentum);
252             return i.getDerivative(1);
253         } else {
254             return Double.NaN;
255         }
256     }
257 
258     /** {@inheritDoc} */
259     public double getEquinoctialEx() {
260         initEquinoctial();
261         return equinoctial.getEquinoctialEx();
262     }
263 
264     /** {@inheritDoc} */
265     public double getEquinoctialExDot() {
266         initEquinoctial();
267         return equinoctial.getEquinoctialExDot();
268     }
269 
270     /** {@inheritDoc} */
271     public double getEquinoctialEy() {
272         initEquinoctial();
273         return equinoctial.getEquinoctialEy();
274     }
275 
276     /** {@inheritDoc} */
277     public double getEquinoctialEyDot() {
278         initEquinoctial();
279         return equinoctial.getEquinoctialEyDot();
280     }
281 
282     /** {@inheritDoc} */
283     public double getHx() {
284         final Vector3D w = getPVCoordinates().getMomentum().normalize();
285         // Check for equatorial retrograde orbit
286         if ((w.getX() * w.getX() + w.getY() * w.getY()) == 0 && w.getZ() < 0) {
287             return Double.NaN;
288         }
289         return -w.getY() / (1 + w.getZ());
290     }
291 
292     /** {@inheritDoc} */
293     public double getHxDot() {
294         if (hasDerivatives()) {
295             final FieldPVCoordinates<UnivariateDerivative2> pv = getPVDerivatives();
296             final FieldVector3D<UnivariateDerivative2> w =
297                             FieldVector3D.crossProduct(pv.getPosition(), pv.getVelocity()).normalize();
298             // Check for equatorial retrograde orbit
299             final double x = w.getX().getValue();
300             final double y = w.getY().getValue();
301             final double z = w.getZ().getValue();
302             if ((x * x + y * y) == 0 && z < 0) {
303                 return Double.NaN;
304             }
305             final UnivariateDerivative2 hx = w.getY().negate().divide(w.getZ().add(1));
306             return hx.getDerivative(1);
307         } else {
308             return Double.NaN;
309         }
310     }
311 
312     /** {@inheritDoc} */
313     public double getHy() {
314         final Vector3D w = getPVCoordinates().getMomentum().normalize();
315         // Check for equatorial retrograde orbit
316         if ((w.getX() * w.getX() + w.getY() * w.getY()) == 0 && w.getZ() < 0) {
317             return Double.NaN;
318         }
319         return  w.getX() / (1 + w.getZ());
320     }
321 
322     /** {@inheritDoc} */
323     public double getHyDot() {
324         if (hasDerivatives()) {
325             final FieldPVCoordinates<UnivariateDerivative2> pv = getPVDerivatives();
326             final FieldVector3D<UnivariateDerivative2> w =
327                             FieldVector3D.crossProduct(pv.getPosition(), pv.getVelocity()).normalize();
328             // Check for equatorial retrograde orbit
329             final double x = w.getX().getValue();
330             final double y = w.getY().getValue();
331             final double z = w.getZ().getValue();
332             if ((x * x + y * y) == 0 && z < 0) {
333                 return Double.NaN;
334             }
335             final UnivariateDerivative2 hy = w.getX().divide(w.getZ().add(1));
336             return hy.getDerivative(1);
337         } else {
338             return Double.NaN;
339         }
340     }
341 
342     /** {@inheritDoc} */
343     public double getLv() {
344         initEquinoctial();
345         return equinoctial.getLv();
346     }
347 
348     /** {@inheritDoc} */
349     public double getLvDot() {
350         initEquinoctial();
351         return equinoctial.getLvDot();
352     }
353 
354     /** {@inheritDoc} */
355     public double getLE() {
356         initEquinoctial();
357         return equinoctial.getLE();
358     }
359 
360     /** {@inheritDoc} */
361     public double getLEDot() {
362         initEquinoctial();
363         return equinoctial.getLEDot();
364     }
365 
366     /** {@inheritDoc} */
367     public double getLM() {
368         initEquinoctial();
369         return equinoctial.getLM();
370     }
371 
372     /** {@inheritDoc} */
373     public double getLMDot() {
374         initEquinoctial();
375         return equinoctial.getLMDot();
376     }
377 
378     /** {@inheritDoc} */
379     @Override
380     public boolean hasDerivatives() {
381         return hasNonKeplerianAcceleration;
382     }
383 
384     /** {@inheritDoc} */
385     protected Vector3D initPosition() {
386         // nothing to do here, as the canonical elements are already the Cartesian ones
387         return getPVCoordinates().getPosition();
388     }
389 
390     /** {@inheritDoc} */
391     protected TimeStampedPVCoordinates initPVCoordinates() {
392         // nothing to do here, as the canonical elements are already the Cartesian ones
393         return getPVCoordinates();
394     }
395 
396     /** {@inheritDoc} */
397     public CartesianOrbit shiftedBy(final double dt) {
398         final PVCoordinates shiftedPV = shiftPV(dt);
399         return new CartesianOrbit(shiftedPV, getFrame(), getDate().shiftedBy(dt), getMu());
400     }
401 
402     /** Compute shifted position and velocity.
403      * @param dt time shift
404      * @return shifted position and velocity
405      */
406     private PVCoordinates shiftPV(final double dt) {
407 
408         final Vector3D pvP = getPosition();
409         final PVCoordinates shiftedPV = KeplerianMotionCartesianUtility.predictPositionVelocity(dt, pvP,
410             getPVCoordinates().getVelocity(), getMu());
411 
412         if (hasNonKeplerianAcceleration) {
413 
414             // extract non-Keplerian part of the initial acceleration
415             final double r2 = pvP.getNormSq();
416             final double r = FastMath.sqrt(r2);
417             final Vector3D nonKeplerianAcceleration = new Vector3D(1, getPVCoordinates().getAcceleration(),
418                                                                    getMu() / (r2 * r), pvP);
419 
420             // add the quadratic motion due to the non-Keplerian acceleration to the Keplerian motion
421             final Vector3D shiftedP = shiftedPV.getPosition();
422             final Vector3D shiftedV = shiftedPV.getVelocity();
423             final Vector3D fixedP   = new Vector3D(1, shiftedP,
424                                                    0.5 * dt * dt, nonKeplerianAcceleration);
425             final double   fixedR2 = fixedP.getNormSq();
426             final double   fixedR  = FastMath.sqrt(fixedR2);
427             final Vector3D fixedV  = new Vector3D(1, shiftedV,
428                                                   dt, nonKeplerianAcceleration);
429             final Vector3D fixedA  = new Vector3D(-getMu() / (fixedR2 * fixedR), shiftedP,
430                                                   1, nonKeplerianAcceleration);
431 
432             return new PVCoordinates(fixedP, fixedV, fixedA);
433 
434         } else {
435             // don't include acceleration,
436             // so the shifted orbit is not considered to have derivatives
437             return shiftedPV;
438         }
439 
440     }
441 
442     @Override
443     protected double[][] computeJacobianMeanWrtCartesian() {
444         return SIX_BY_SIX_IDENTITY;
445     }
446 
447     @Override
448     protected double[][] computeJacobianEccentricWrtCartesian() {
449         return SIX_BY_SIX_IDENTITY;
450     }
451 
452     @Override
453     protected double[][] computeJacobianTrueWrtCartesian() {
454         return SIX_BY_SIX_IDENTITY;
455     }
456 
457     /** {@inheritDoc} */
458     public void addKeplerContribution(final PositionAngleType type, final double gm,
459                                       final double[] pDot) {
460 
461         final PVCoordinates pv = getPVCoordinates();
462 
463         // position derivative is velocity
464         final Vector3D velocity = pv.getVelocity();
465         pDot[0] += velocity.getX();
466         pDot[1] += velocity.getY();
467         pDot[2] += velocity.getZ();
468 
469         // velocity derivative is Newtonian acceleration
470         final Vector3D position = pv.getPosition();
471         final double r2         = position.getNormSq();
472         final double coeff      = -gm / (r2 * FastMath.sqrt(r2));
473         pDot[3] += coeff * position.getX();
474         pDot[4] += coeff * position.getY();
475         pDot[5] += coeff * position.getZ();
476 
477     }
478 
479     /**  Returns a string representation of this Orbit object.
480      * @return a string representation of this object
481      */
482     public String toString() {
483         // use only the six defining elements, like the other Orbit.toString() methods
484         final String comma = ", ";
485         final PVCoordinates pv = getPVCoordinates();
486         final Vector3D p = pv.getPosition();
487         final Vector3D v = pv.getVelocity();
488         return "Cartesian parameters: {P(" +
489                 p.getX() + comma +
490                 p.getY() + comma +
491                 p.getZ() + "), V(" +
492                 v.getX() + comma +
493                 v.getY() + comma +
494                 v.getZ() + ")}";
495     }
496 
497     /** Replace the instance with a data transfer object for serialization.
498      * <p>
499      * This intermediate class serializes all needed parameters,
500      * including position-velocity which are <em>not</em> serialized by parent
501      * {@link Orbit} class.
502      * </p>
503      * @return data transfer object that will be serialized
504      */
505     @DefaultDataContext
506     private Object writeReplace() {
507         return new DTO(this);
508     }
509 
510     /** Internal class used only for serialization. */
511     @DefaultDataContext
512     private static class DTO implements Serializable {
513 
514         /** Serializable UID. */
515         private static final long serialVersionUID = 20170414L;
516 
517         /** Double values. */
518         private double[] d;
519 
520         /** Frame in which are defined the orbital parameters. */
521         private final Frame frame;
522 
523         /** Simple constructor.
524          * @param orbit instance to serialize
525          */
526         private DTO(final CartesianOrbit orbit) {
527 
528             final TimeStampedPVCoordinates pv = orbit.getPVCoordinates();
529 
530             // decompose date
531             final AbsoluteDate j2000Epoch =
532                     DataContext.getDefault().getTimeScales().getJ2000Epoch();
533             final double epoch  = FastMath.floor(pv.getDate().durationFrom(j2000Epoch));
534             final double offset = pv.getDate().durationFrom(j2000Epoch.shiftedBy(epoch));
535 
536             if (orbit.hasDerivatives()) {
537                 this.d = new double[] {
538                     epoch, offset, orbit.getMu(),
539                     pv.getPosition().getX(),     pv.getPosition().getY(),     pv.getPosition().getZ(),
540                     pv.getVelocity().getX(),     pv.getVelocity().getY(),     pv.getVelocity().getZ(),
541                     pv.getAcceleration().getX(), pv.getAcceleration().getY(), pv.getAcceleration().getZ()
542                 };
543             } else {
544                 this.d = new double[] {
545                     epoch, offset, orbit.getMu(),
546                     pv.getPosition().getX(),     pv.getPosition().getY(),     pv.getPosition().getZ(),
547                     pv.getVelocity().getX(),     pv.getVelocity().getY(),     pv.getVelocity().getZ()
548                 };
549             }
550 
551             this.frame = orbit.getFrame();
552 
553         }
554 
555         /** Replace the deserialized data transfer object with a {@link CartesianOrbit}.
556          * @return replacement {@link CartesianOrbit}
557          */
558         private Object readResolve() {
559             final AbsoluteDate j2000Epoch =
560                     DataContext.getDefault().getTimeScales().getJ2000Epoch();
561             if (d.length >= 12) {
562                 // we have derivatives
563                 return new CartesianOrbit(new TimeStampedPVCoordinates(j2000Epoch.shiftedBy(d[0]).shiftedBy(d[1]),
564                                                                        new Vector3D(d[3], d[ 4], d[ 5]),
565                                                                        new Vector3D(d[6], d[ 7], d[ 8]),
566                                                                        new Vector3D(d[9], d[10], d[11])),
567                                           frame, d[2]);
568             } else {
569                 // we don't have derivatives
570                 return new CartesianOrbit(new TimeStampedPVCoordinates(j2000Epoch.shiftedBy(d[0]).shiftedBy(d[1]),
571                                                                        new Vector3D(d[3], d[ 4], d[ 5]),
572                                                                        new Vector3D(d[6], d[ 7], d[ 8])),
573                                           frame, d[2]);
574             }
575         }
576 
577     }
578 
579 }