1   /* Copyright 2002-2015 CS Systèmes d'Information
2    * Licensed to CS Systèmes d'Information (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  import java.util.ArrayList;
21  import java.util.Collection;
22  import java.util.List;
23  
24  import org.apache.commons.math3.exception.ConvergenceException;
25  import org.apache.commons.math3.geometry.euclidean.threed.Rotation;
26  import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
27  import org.apache.commons.math3.util.FastMath;
28  import org.orekit.errors.OrekitMessages;
29  import org.orekit.frames.Frame;
30  import org.orekit.time.AbsoluteDate;
31  import org.orekit.utils.CartesianDerivativesFilter;
32  import org.orekit.utils.PVCoordinates;
33  import org.orekit.utils.TimeStampedPVCoordinates;
34  
35  
36  /** This class holds cartesian orbital parameters.
37  
38   * <p>
39   * The parameters used internally are the cartesian coordinates:
40   *   <ul>
41   *     <li>x</li>
42   *     <li>y</li>
43   *     <li>z</li>
44   *     <li>xDot</li>
45   *     <li>yDot</li>
46   *     <li>zDot</li>
47   *   </ul>
48   * contained in {@link PVCoordinates}.
49   * </p>
50  
51   * <p>
52   * Note that the implementation of this class delegates all non-cartesian related
53   * computations ({@link #getA()}, {@link #getEquinoctialEx()}, ...) to an underlying
54   * instance of the {@link EquinoctialOrbit} class. This implies that using this class
55   * only for analytical computations which are always based on non-cartesian
56   * parameters is perfectly possible but somewhat sub-optimal.
57   * </p>
58   * <p>
59   * The instance <code>CartesianOrbit</code> is guaranteed to be immutable.
60   * </p>
61   * @see    Orbit
62   * @see    KeplerianOrbit
63   * @see    CircularOrbit
64   * @see    EquinoctialOrbit
65   * @author Luc Maisonobe
66   * @author Guylaine Prat
67   * @author Fabien Maussion
68   * @author V&eacute;ronique Pommier-Maurussane
69   */
70  public class CartesianOrbit extends Orbit {
71  
72      /** Serializable UID. */
73      private static final long serialVersionUID = 20140723L;
74  
75      /** Underlying equinoctial orbit to which high-level methods are delegated. */
76      private transient EquinoctialOrbit equinoctial;
77  
78      /** Constructor from Cartesian parameters.
79       *
80       * <p> The acceleration provided in {@code pvCoordinates} is accessible using
81       * {@link #getPVCoordinates()} and {@link #getPVCoordinates(Frame)}. All other methods
82       * use {@code mu} and the position to compute the acceleration, including
83       * {@link #shiftedBy(double)} and {@link #getPVCoordinates(AbsoluteDate, Frame)}.
84       *
85       * @param pvaCoordinates the position, velocity and acceleration of the satellite.
86       * @param frame the frame in which the {@link PVCoordinates} are defined
87       * (<em>must</em> be a {@link Frame#isPseudoInertial pseudo-inertial frame})
88       * @param mu central attraction coefficient (m³/s²)
89       * @exception IllegalArgumentException if frame is not a {@link
90       * Frame#isPseudoInertial pseudo-inertial frame}
91       */
92      public CartesianOrbit(final TimeStampedPVCoordinates pvaCoordinates,
93                            final Frame frame, final double mu)
94          throws IllegalArgumentException {
95          super(pvaCoordinates, frame, mu);
96          equinoctial = null;
97      }
98  
99      /** Constructor from Cartesian parameters.
100      *
101      * <p> The acceleration provided in {@code pvCoordinates} is accessible using
102      * {@link #getPVCoordinates()} and {@link #getPVCoordinates(Frame)}. All other methods
103      * use {@code mu} and the position to compute the acceleration, including
104      * {@link #shiftedBy(double)} and {@link #getPVCoordinates(AbsoluteDate, Frame)}.
105      *
106      * @param pvaCoordinates the position and velocity of the satellite.
107      * @param frame the frame in which the {@link PVCoordinates} are defined
108      * (<em>must</em> be a {@link Frame#isPseudoInertial pseudo-inertial frame})
109      * @param date date of the orbital parameters
110      * @param mu central attraction coefficient (m³/s²)
111      * @exception IllegalArgumentException if frame is not a {@link
112      * Frame#isPseudoInertial pseudo-inertial frame}
113      */
114     public CartesianOrbit(final PVCoordinates pvaCoordinates, final Frame frame,
115                           final AbsoluteDate date, final double mu)
116         throws IllegalArgumentException {
117         this(new TimeStampedPVCoordinates(date, pvaCoordinates), frame, mu);
118     }
119 
120     /** Constructor from any kind of orbital parameters.
121      * @param op orbital parameters to copy
122      */
123     public CartesianOrbit(final Orbit op) {
124         super(op.getPVCoordinates(), op.getFrame(), op.getMu());
125         if (op instanceof EquinoctialOrbit) {
126             equinoctial = (EquinoctialOrbit) op;
127         } else if (op instanceof CartesianOrbit) {
128             equinoctial = ((CartesianOrbit) op).equinoctial;
129         } else {
130             equinoctial = null;
131         }
132     }
133 
134     /** {@inheritDoc} */
135     public OrbitType getType() {
136         return OrbitType.CARTESIAN;
137     }
138 
139     /** Lazy evaluation of equinoctial parameters. */
140     private void initEquinoctial() {
141         if (equinoctial == null) {
142             equinoctial = new EquinoctialOrbit(getPVCoordinates(), getFrame(), getDate(), getMu());
143         }
144     }
145 
146     /** {@inheritDoc} */
147     public double getA() {
148         // lazy evaluation of semi-major axis
149         final double r  = getPVCoordinates().getPosition().getNorm();
150         final double V2 = getPVCoordinates().getVelocity().getNormSq();
151         return r / (2 - r * V2 / getMu());
152     }
153 
154     /** {@inheritDoc} */
155     public double getE() {
156         final Vector3D pvP   = getPVCoordinates().getPosition();
157         final Vector3D pvV   = getPVCoordinates().getVelocity();
158         final double rV2OnMu = pvP.getNorm() * pvV.getNormSq() / getMu();
159         final double eSE     = Vector3D.dotProduct(pvP, pvV) / FastMath.sqrt(getMu() * getA());
160         final double eCE     = rV2OnMu - 1;
161         return FastMath.sqrt(eCE * eCE + eSE * eSE);
162     }
163 
164     /** {@inheritDoc} */
165     public double getI() {
166         return Vector3D.angle(Vector3D.PLUS_K, getPVCoordinates().getMomentum());
167     }
168 
169     /** {@inheritDoc} */
170     public double getEquinoctialEx() {
171         initEquinoctial();
172         return equinoctial.getEquinoctialEx();
173     }
174 
175     /** {@inheritDoc} */
176     public double getEquinoctialEy() {
177         initEquinoctial();
178         return equinoctial.getEquinoctialEy();
179     }
180 
181     /** {@inheritDoc} */
182     public double getHx() {
183         final Vector3D w = getPVCoordinates().getMomentum().normalize();
184         // Check for equatorial retrograde orbit
185         if (((w.getX() * w.getX() + w.getY() * w.getY()) == 0) && w.getZ() < 0) {
186             return Double.NaN;
187         }
188         return -w.getY() / (1 + w.getZ());
189     }
190 
191     /** {@inheritDoc} */
192     public double getHy() {
193         final Vector3D w = getPVCoordinates().getMomentum().normalize();
194         // Check for equatorial retrograde orbit
195         if (((w.getX() * w.getX() + w.getY() * w.getY()) == 0) && w.getZ() < 0) {
196             return Double.NaN;
197         }
198         return  w.getX() / (1 + w.getZ());
199     }
200 
201     /** {@inheritDoc} */
202     public double getLv() {
203         initEquinoctial();
204         return equinoctial.getLv();
205     }
206 
207     /** {@inheritDoc} */
208     public double getLE() {
209         initEquinoctial();
210         return equinoctial.getLE();
211     }
212 
213     /** {@inheritDoc} */
214     public double getLM() {
215         initEquinoctial();
216         return equinoctial.getLM();
217     }
218 
219     /** {@inheritDoc} */
220     protected TimeStampedPVCoordinates initPVCoordinates() {
221         // nothing to do here, as the canonical elements are already the cartesian ones
222         return getPVCoordinates();
223     }
224 
225     /** {@inheritDoc} */
226     public CartesianOrbit shiftedBy(final double dt) {
227         final PVCoordinates shiftedPV = (getA() < 0) ? shiftPVHyperbolic(dt) : shiftPVElliptic(dt);
228         return new CartesianOrbit(shiftedPV, getFrame(), getDate().shiftedBy(dt), getMu());
229     }
230 
231     /** {@inheritDoc}
232      * <p>
233      * The interpolated instance is created by polynomial Hermite interpolation
234      * ensuring velocity remains the exact derivative of position.
235      * </p>
236      * <p>
237      * As this implementation of interpolation is polynomial, it should be used only
238      * with small samples (about 10-20 points) in order to avoid <a
239      * href="http://en.wikipedia.org/wiki/Runge%27s_phenomenon">Runge's phenomenon</a>
240      * and numerical problems (including NaN appearing).
241      * </p>
242      * <p>
243      * If orbit interpolation on large samples is needed, using the {@link
244      * org.orekit.propagation.analytical.Ephemeris} class is a better way than using this
245      * low-level interpolation. The Ephemeris class automatically handles selection of
246      * a neighboring sub-sample with a predefined number of point from a large global sample
247      * in a thread-safe way.
248      * </p>
249      */
250     public CartesianOrbit interpolate(final AbsoluteDate date, final Collection<Orbit> sample) {
251         final List<TimeStampedPVCoordinates> datedPV = new ArrayList<TimeStampedPVCoordinates>(sample.size());
252         for (final Orbit o : sample) {
253             datedPV.add(new TimeStampedPVCoordinates(o.getDate(),
254                                                      o.getPVCoordinates().getPosition(),
255                                                      o.getPVCoordinates().getVelocity(),
256                                                      o.getPVCoordinates().getAcceleration()));
257         }
258         final TimeStampedPVCoordinates interpolated =
259                 TimeStampedPVCoordinates.interpolate(date, CartesianDerivativesFilter.USE_PVA, datedPV);
260         return new CartesianOrbit(interpolated, getFrame(), date, getMu());
261     }
262 
263     /** Compute shifted position and velocity in elliptic case.
264      * @param dt time shift
265      * @return shifted position and velocity
266      */
267     private PVCoordinates shiftPVElliptic(final double dt) {
268 
269         // preliminary computation
270         final Vector3D pvP   = getPVCoordinates().getPosition();
271         final Vector3D pvV   = getPVCoordinates().getVelocity();
272         final double r       = pvP.getNorm();
273         final double rV2OnMu = r * pvV.getNormSq() / getMu();
274         final double a       = getA();
275         final double eSE     = Vector3D.dotProduct(pvP, pvV) / FastMath.sqrt(getMu() * a);
276         final double eCE     = rV2OnMu - 1;
277         final double e2      = eCE * eCE + eSE * eSE;
278 
279         // we can use any arbitrary reference 2D frame in the orbital plane
280         // in order to simplify some equations below, we use the current position as the u axis
281         final Vector3D u     = pvP.normalize();
282         final Vector3D v     = Vector3D.crossProduct(getPVCoordinates().getMomentum(), u).normalize();
283 
284         // the following equations rely on the specific choice of u explained above,
285         // some coefficients that vanish to 0 in this case have already been removed here
286         final double ex      = (eCE - e2) * a / r;
287         final double ey      = -FastMath.sqrt(1 - e2) * eSE * a / r;
288         final double beta    = 1 / (1 + FastMath.sqrt(1 - e2));
289         final double thetaE0 = FastMath.atan2(ey + eSE * beta * ex, r / a + ex - eSE * beta * ey);
290         final double thetaM0 = thetaE0 - ex * FastMath.sin(thetaE0) + ey * FastMath.cos(thetaE0);
291 
292         // compute in-plane shifted eccentric argument
293         final double thetaM1 = thetaM0 + getKeplerianMeanMotion() * dt;
294         final double thetaE1 = meanToEccentric(thetaM1, ex, ey);
295         final double cTE     = FastMath.cos(thetaE1);
296         final double sTE     = FastMath.sin(thetaE1);
297 
298         // compute shifted in-plane cartesian coordinates
299         final double exey   = ex * ey;
300         final double exCeyS = ex * cTE + ey * sTE;
301         final double x      = a * ((1 - beta * ey * ey) * cTE + beta * exey * sTE - ex);
302         final double y      = a * ((1 - beta * ex * ex) * sTE + beta * exey * cTE - ey);
303         final double factor = FastMath.sqrt(getMu() / a) / (1 - exCeyS);
304         final double xDot   = factor * (-sTE + beta * ey * exCeyS);
305         final double yDot   = factor * ( cTE - beta * ex * exCeyS);
306 
307         final Vector3D shiftedP = new Vector3D(x, u, y, v);
308         final double   r2       = x * x + y * y;
309         final Vector3D shiftedV = new Vector3D(xDot, u, yDot, v);
310         final Vector3D shiftedA = new Vector3D(-getMu() / (r2 * FastMath.sqrt(r2)), shiftedP);
311         return new PVCoordinates(shiftedP, shiftedV, shiftedA);
312 
313     }
314 
315     /** Compute shifted position and velocity in hyperbolic case.
316      * @param dt time shift
317      * @return shifted position and velocity
318      */
319     private PVCoordinates shiftPVHyperbolic(final double dt) {
320 
321         final PVCoordinates pv = getPVCoordinates();
322         final Vector3D pvP   = pv.getPosition();
323         final Vector3D pvV   = pv.getVelocity();
324         final Vector3D pvM   = pv.getMomentum();
325         final double r       = pvP.getNorm();
326         final double rV2OnMu = r * pvV.getNormSq() / getMu();
327         final double a       = getA();
328         final double muA     = getMu() * a;
329         final double e       = FastMath.sqrt(1 - Vector3D.dotProduct(pvM, pvM) / muA);
330         final double sqrt    = FastMath.sqrt((e + 1) / (e - 1));
331 
332         // compute mean anomaly
333         final double eSH     = Vector3D.dotProduct(pvP, pvV) / FastMath.sqrt(-muA);
334         final double eCH     = rV2OnMu - 1;
335         final double H0      = FastMath.log((eCH + eSH) / (eCH - eSH)) / 2;
336         final double M0      = e * FastMath.sinh(H0) - H0;
337 
338         // find canonical 2D frame with p pointing to perigee
339         final double v0      = 2 * FastMath.atan(sqrt * FastMath.tanh(H0 / 2));
340         final Vector3D p     = new Rotation(pvM, -v0).applyTo(pvP).normalize();
341         final Vector3D q     = Vector3D.crossProduct(pvM, p).normalize();
342 
343         // compute shifted eccentric anomaly
344         final double M1      = M0 + getKeplerianMeanMotion() * dt;
345         final double H1      = meanToHyperbolicEccentric(M1, e);
346 
347         // compute shifted in-plane cartesian coordinates
348         final double cH     = FastMath.cosh(H1);
349         final double sH     = FastMath.sinh(H1);
350         final double sE2m1  = FastMath.sqrt((e - 1) * (e + 1));
351 
352         // coordinates of position and velocity in the orbital plane
353         final double x      = a * (cH - e);
354         final double y      = -a * sE2m1 * sH;
355         final double factor = FastMath.sqrt(getMu() / -a) / (e * cH - 1);
356         final double xDot   = -factor * sH;
357         final double yDot   =  factor * sE2m1 * cH;
358 
359         final Vector3D shiftedP = new Vector3D(x, p, y, q);
360         final double   r2       = x * x + y * y;
361         final Vector3D shiftedV = new Vector3D(xDot, p, yDot, q);
362         final Vector3D shiftedA = new Vector3D(-getMu() / (r2 * FastMath.sqrt(r2)), shiftedP);
363         return new PVCoordinates(shiftedP, shiftedV, shiftedA);
364 
365     }
366 
367     /** Computes the eccentric in-plane argument from the mean in-plane argument.
368      * @param thetaM = mean in-plane argument (rad)
369      * @param ex first component of eccentricity vector
370      * @param ey second component of eccentricity vector
371      * @return the eccentric in-plane argument.
372      */
373     private double meanToEccentric(final double thetaM, final double ex, final double ey) {
374         // Generalization of Kepler equation to in-plane parameters
375         // with thetaE = eta + E and
376         //      thetaM = eta + M = thetaE - ex.sin(thetaE) + ey.cos(thetaE)
377         // and eta being counted from an arbitrary reference in the orbital plane
378         double thetaE        = thetaM;
379         double thetaEMthetaM = 0.0;
380         int    iter          = 0;
381         do {
382             final double cosThetaE = FastMath.cos(thetaE);
383             final double sinThetaE = FastMath.sin(thetaE);
384 
385             final double f2 = ex * sinThetaE - ey * cosThetaE;
386             final double f1 = 1.0 - ex * cosThetaE - ey * sinThetaE;
387             final double f0 = thetaEMthetaM - f2;
388 
389             final double f12 = 2.0 * f1;
390             final double shift = f0 * f12 / (f1 * f12 - f0 * f2);
391 
392             thetaEMthetaM -= shift;
393             thetaE         = thetaM + thetaEMthetaM;
394 
395             if (FastMath.abs(shift) <= 1.0e-12) {
396                 return thetaE;
397             }
398 
399         } while (++iter < 50);
400 
401         throw new ConvergenceException();
402 
403     }
404 
405     /** Computes the hyperbolic eccentric anomaly from the mean anomaly.
406      * <p>
407      * The algorithm used here for solving hyperbolic Kepler equation is
408      * Danby's iterative method (3rd order) with Vallado's initial guess.
409      * </p>
410      * @param M mean anomaly (rad)
411      * @param ecc eccentricity
412      * @return the hyperbolic eccentric anomaly
413      */
414     private double meanToHyperbolicEccentric(final double M, final double ecc) {
415 
416         // Resolution of hyperbolic Kepler equation for keplerian parameters
417 
418         // Initial guess
419         double H;
420         if (ecc < 1.6) {
421             if ((-FastMath.PI < M && M < 0.) || M > FastMath.PI) {
422                 H = M - ecc;
423             } else {
424                 H = M + ecc;
425             }
426         } else {
427             if (ecc < 3.6 && FastMath.abs(M) > FastMath.PI) {
428                 H = M - FastMath.copySign(ecc, M);
429             } else {
430                 H = M / (ecc - 1.);
431             }
432         }
433 
434         // Iterative computation
435         int iter = 0;
436         do {
437             final double f3  = ecc * FastMath.cosh(H);
438             final double f2  = ecc * FastMath.sinh(H);
439             final double f1  = f3 - 1.;
440             final double f0  = f2 - H - M;
441             final double f12 = 2. * f1;
442             final double d   = f0 / f12;
443             final double fdf = f1 - d * f2;
444             final double ds  = f0 / fdf;
445 
446             final double shift = f0 / (fdf + ds * ds * f3 / 6.);
447 
448             H -= shift;
449 
450             if (FastMath.abs(shift) <= 1.0e-12) {
451                 return H;
452             }
453 
454         } while (++iter < 50);
455 
456         throw new ConvergenceException(OrekitMessages.UNABLE_TO_COMPUTE_HYPERBOLIC_ECCENTRIC_ANOMALY,
457                                        iter);
458     }
459 
460     @Override
461     public void getJacobianWrtCartesian(final PositionAngle type, final double[][] jacobian) {
462         // this is the fastest way to set the 6x6 identity matrix
463         for (int i = 0; i < 6; i++) {
464             for (int j = 0; j < 6; j++) {
465                 jacobian[i][j] = 0;
466             }
467             jacobian[i][i] = 1;
468         }
469     }
470 
471     @Override
472     protected double[][] computeJacobianMeanWrtCartesian() {
473         // not used
474         return null;
475     }
476     @Override
477     protected double[][] computeJacobianEccentricWrtCartesian() {
478         // not used
479         return null;
480     }
481 
482     @Override
483     protected double[][] computeJacobianTrueWrtCartesian() {
484         // not used
485         return null;
486     }
487 
488     /** {@inheritDoc} */
489     public void addKeplerContribution(final PositionAngle type, final double gm,
490                                       final double[] pDot) {
491 
492         final PVCoordinates pv = getPVCoordinates();
493 
494         // position derivative is velocity
495         final Vector3D velocity = pv.getVelocity();
496         pDot[0] += velocity.getX();
497         pDot[1] += velocity.getY();
498         pDot[2] += velocity.getZ();
499 
500         // velocity derivative is Newtonian acceleration
501         final Vector3D position = pv.getPosition();
502         final double r2         = position.getNormSq();
503         final double coeff      = -gm / (r2 * FastMath.sqrt(r2));
504         pDot[3] += coeff * position.getX();
505         pDot[4] += coeff * position.getY();
506         pDot[5] += coeff * position.getZ();
507 
508     }
509 
510     /**  Returns a string representation of this Orbit object.
511      * @return a string representation of this object
512      */
513     public String toString() {
514         return "cartesian parameters: " + getPVCoordinates().toString();
515     }
516 
517     /** Replace the instance with a data transfer object for serialization.
518      * <p>
519      * This intermediate class serializes all needed parameters,
520      * including position-velocity which are <em>not</em> serialized by parent
521      * {@link Orbit} class.
522      * </p>
523      * @return data transfer object that will be serialized
524      */
525     private Object writeReplace() {
526         return new DTO(this);
527     }
528 
529     /** Internal class used only for serialization. */
530     private static class DTO implements Serializable {
531 
532         /** Serializable UID. */
533         private static final long serialVersionUID = 20140723L;
534 
535         /** Double values. */
536         private double[] d;
537 
538         /** Frame in which are defined the orbital parameters. */
539         private final Frame frame;
540 
541         /** Simple constructor.
542          * @param orbit instance to serialize
543          */
544         private DTO(final CartesianOrbit orbit) {
545 
546             final TimeStampedPVCoordinates pv = orbit.getPVCoordinates();
547 
548             // decompose date
549             final double epoch  = FastMath.floor(pv.getDate().durationFrom(AbsoluteDate.J2000_EPOCH));
550             final double offset = pv.getDate().durationFrom(AbsoluteDate.J2000_EPOCH.shiftedBy(epoch));
551 
552             this.d = new double[] {
553                 epoch, offset, orbit.getMu(),
554                 pv.getPosition().getX(),     pv.getPosition().getY(),     pv.getPosition().getZ(),
555                 pv.getVelocity().getX(),     pv.getVelocity().getY(),     pv.getVelocity().getZ(),
556                 pv.getAcceleration().getX(), pv.getAcceleration().getY(), pv.getAcceleration().getZ(),
557             };
558 
559             this.frame = orbit.getFrame();
560 
561         }
562 
563         /** Replace the deserialized data transfer object with a {@link CartesianOrbit}.
564          * @return replacement {@link CartesianOrbit}
565          */
566         private Object readResolve() {
567             return new CartesianOrbit(new TimeStampedPVCoordinates(AbsoluteDate.J2000_EPOCH.shiftedBy(d[0]).shiftedBy(d[1]),
568                                                                    new Vector3D(d[3], d[ 4], d[ 5]),
569                                                                    new Vector3D(d[6], d[ 7], d[ 8]),
570                                                                    new Vector3D(d[9], d[10], d[11])),
571                                       frame, d[2]);
572         }
573 
574     }
575 
576 }