EquinoctialOrbit.java

  1. /* Copyright 2002-2016 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. import java.io.Serializable;
  19. import java.util.Collection;

  20. import org.apache.commons.math3.analysis.interpolation.HermiteInterpolator;
  21. import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
  22. import org.apache.commons.math3.util.FastMath;
  23. import org.apache.commons.math3.util.MathUtils;
  24. import org.orekit.errors.OrekitIllegalArgumentException;
  25. import org.orekit.errors.OrekitInternalError;
  26. import org.orekit.errors.OrekitMessages;
  27. import org.orekit.frames.Frame;
  28. import org.orekit.time.AbsoluteDate;
  29. import org.orekit.utils.PVCoordinates;
  30. import org.orekit.utils.TimeStampedPVCoordinates;


  31. /**
  32.  * This class handles equinoctial orbital parameters, which can support both
  33.  * circular and equatorial orbits.
  34.  * <p>
  35.  * The parameters used internally are the equinoctial elements which can be
  36.  * related to keplerian elements as follows:
  37.  *   <pre>
  38.  *     a
  39.  *     ex = e cos(ω + Ω)
  40.  *     ey = e sin(ω + Ω)
  41.  *     hx = tan(i/2) cos(Ω)
  42.  *     hy = tan(i/2) sin(Ω)
  43.  *     lv = v + ω + Ω
  44.  *   </pre>
  45.  * where ω stands for the Perigee Argument and Ω stands for the
  46.  * Right Ascension of the Ascending Node.
  47.  * </p>
  48.  * <p>
  49.  * The conversion equations from and to keplerian elements given above hold only
  50.  * when both sides are unambiguously defined, i.e. when orbit is neither equatorial
  51.  * nor circular. When orbit is either equatorial or circular, the equinoctial
  52.  * parameters are still unambiguously defined whereas some keplerian elements
  53.  * (more precisely ω and Ω) become ambiguous. For this reason, equinoctial
  54.  * parameters are the recommended way to represent orbits.
  55.  * </p>
  56.  * <p>
  57.  * The instance <code>EquinoctialOrbit</code> is guaranteed to be immutable.
  58.  * </p>
  59.  * @see    Orbit
  60.  * @see    KeplerianOrbit
  61.  * @see    CircularOrbit
  62.  * @see    CartesianOrbit
  63.  * @author Mathieu Rom&eacute;ro
  64.  * @author Luc Maisonobe
  65.  * @author Guylaine Prat
  66.  * @author Fabien Maussion
  67.  * @author V&eacute;ronique Pommier-Maurussane
  68.  */
  69. public class EquinoctialOrbit extends Orbit {

  70.     /** Serializable UID. */
  71.     private static final long serialVersionUID = 20141228L;

  72.     /** Semi-major axis (m). */
  73.     private final double a;

  74.     /** First component of the eccentricity vector. */
  75.     private final double ex;

  76.     /** Second component of the eccentricity vector. */
  77.     private final double ey;

  78.     /** First component of the inclination vector. */
  79.     private final double hx;

  80.     /** Second component of the inclination vector. */
  81.     private final double hy;

  82.     /** True longitude argument (rad). */
  83.     private final double lv;

  84.     /** Creates a new instance.
  85.      * @param a  semi-major axis (m)
  86.      * @param ex e cos(ω + Ω), first component of eccentricity vector
  87.      * @param ey e sin(ω + Ω), second component of eccentricity vector
  88.      * @param hx tan(i/2) cos(Ω), first component of inclination vector
  89.      * @param hy tan(i/2) sin(Ω), second component of inclination vector
  90.      * @param l  (M or E or v) + ω + Ω, mean, eccentric or true longitude argument (rad)
  91.      * @param type type of longitude argument
  92.      * @param frame the frame in which the parameters are defined
  93.      * (<em>must</em> be a {@link Frame#isPseudoInertial pseudo-inertial frame})
  94.      * @param date date of the orbital parameters
  95.      * @param mu central attraction coefficient (m³/s²)
  96.      * @exception IllegalArgumentException if eccentricity is equal to 1 or larger or
  97.      * if frame is not a {@link Frame#isPseudoInertial pseudo-inertial frame}
  98.      */
  99.     public EquinoctialOrbit(final double a, final double ex, final double ey,
  100.                             final double hx, final double hy,
  101.                             final double l, final PositionAngle type,
  102.                             final Frame frame, final AbsoluteDate date, final double mu)
  103.         throws IllegalArgumentException {
  104.         super(frame, date, mu);
  105.         if (ex * ex + ey * ey >= 1.0) {
  106.             throw new OrekitIllegalArgumentException(OrekitMessages.HYPERBOLIC_ORBIT_NOT_HANDLED_AS,
  107.                                                      getClass().getName());
  108.         }
  109.         this.a  =  a;
  110.         this.ex = ex;
  111.         this.ey = ey;
  112.         this.hx = hx;
  113.         this.hy = hy;

  114.         switch (type) {
  115.             case MEAN :
  116.                 this.lv = eccentricToTrue(meanToEccentric(l));
  117.                 break;
  118.             case ECCENTRIC :
  119.                 this.lv = eccentricToTrue(l);
  120.                 break;
  121.             case TRUE :
  122.                 this.lv = l;
  123.                 break;
  124.             default :
  125.                 throw new OrekitInternalError(null);
  126.         }

  127.     }

  128.     /** Constructor from cartesian parameters.
  129.      *
  130.      * <p> The acceleration provided in {@code pvCoordinates} is accessible using
  131.      * {@link #getPVCoordinates()} and {@link #getPVCoordinates(Frame)}. All other methods
  132.      * use {@code mu} and the position to compute the acceleration, including
  133.      * {@link #shiftedBy(double)} and {@link #getPVCoordinates(AbsoluteDate, Frame)}.
  134.      *
  135.      * @param pvCoordinates the position, velocity and acceleration
  136.      * @param frame the frame in which are defined the {@link PVCoordinates}
  137.      * (<em>must</em> be a {@link Frame#isPseudoInertial pseudo-inertial frame})
  138.      * @param mu central attraction coefficient (m³/s²)
  139.      * @exception IllegalArgumentException if eccentricity is equal to 1 or larger or
  140.      * if frame is not a {@link Frame#isPseudoInertial pseudo-inertial frame}
  141.      */
  142.     public EquinoctialOrbit(final TimeStampedPVCoordinates pvCoordinates,
  143.                             final Frame frame, final double mu)
  144.         throws IllegalArgumentException {
  145.         super(pvCoordinates, frame, mu);

  146.         //  compute semi-major axis
  147.         final Vector3D pvP = pvCoordinates.getPosition();
  148.         final Vector3D pvV = pvCoordinates.getVelocity();
  149.         final double r = pvP.getNorm();
  150.         final double V2 = pvV.getNormSq();
  151.         final double rV2OnMu = r * V2 / mu;

  152.         if (rV2OnMu > 2) {
  153.             throw new OrekitIllegalArgumentException(OrekitMessages.HYPERBOLIC_ORBIT_NOT_HANDLED_AS,
  154.                                                      getClass().getName());
  155.         }

  156.         // compute inclination vector
  157.         final Vector3D w = pvCoordinates.getMomentum().normalize();
  158.         final double d = 1.0 / (1 + w.getZ());
  159.         hx = -d * w.getY();
  160.         hy =  d * w.getX();

  161.         // compute true longitude argument
  162.         final double cLv = (pvP.getX() - d * pvP.getZ() * w.getX()) / r;
  163.         final double sLv = (pvP.getY() - d * pvP.getZ() * w.getY()) / r;
  164.         lv = FastMath.atan2(sLv, cLv);


  165.         // compute semi-major axis
  166.         a = r / (2 - rV2OnMu);

  167.         // compute eccentricity vector
  168.         final double eSE = Vector3D.dotProduct(pvP, pvV) / FastMath.sqrt(mu * a);
  169.         final double eCE = rV2OnMu - 1;
  170.         final double e2  = eCE * eCE + eSE * eSE;
  171.         final double f   = eCE - e2;
  172.         final double g   = FastMath.sqrt(1 - e2) * eSE;
  173.         ex = a * (f * cLv + g * sLv) / r;
  174.         ey = a * (f * sLv - g * cLv) / r;

  175.     }

  176.     /** Constructor from cartesian parameters.
  177.      *
  178.      * <p> The acceleration provided in {@code pvCoordinates} is accessible using
  179.      * {@link #getPVCoordinates()} and {@link #getPVCoordinates(Frame)}. All other methods
  180.      * use {@code mu} and the position to compute the acceleration, including
  181.      * {@link #shiftedBy(double)} and {@link #getPVCoordinates(AbsoluteDate, Frame)}.
  182.      *
  183.      * @param pvCoordinates the position end velocity
  184.      * @param frame the frame in which are defined the {@link PVCoordinates}
  185.      * (<em>must</em> be a {@link Frame#isPseudoInertial pseudo-inertial frame})
  186.      * @param date date of the orbital parameters
  187.      * @param mu central attraction coefficient (m³/s²)
  188.      * @exception IllegalArgumentException if eccentricity is equal to 1 or larger or
  189.      * if frame is not a {@link Frame#isPseudoInertial pseudo-inertial frame}
  190.      */
  191.     public EquinoctialOrbit(final PVCoordinates pvCoordinates, final Frame frame,
  192.                             final AbsoluteDate date, final double mu)
  193.         throws IllegalArgumentException {
  194.         this(new TimeStampedPVCoordinates(date, pvCoordinates), frame, mu);
  195.     }

  196.     /** Constructor from any kind of orbital parameters.
  197.      * @param op orbital parameters to copy
  198.      */
  199.     public EquinoctialOrbit(final Orbit op) {
  200.         super(op.getFrame(), op.getDate(), op.getMu());
  201.         a  = op.getA();
  202.         ex = op.getEquinoctialEx();
  203.         ey = op.getEquinoctialEy();
  204.         hx = op.getHx();
  205.         hy = op.getHy();
  206.         lv = op.getLv();
  207.     }

  208.     /** {@inheritDoc} */
  209.     public OrbitType getType() {
  210.         return OrbitType.EQUINOCTIAL;
  211.     }

  212.     /** {@inheritDoc} */
  213.     public double getA() {
  214.         return a;
  215.     }

  216.     /** {@inheritDoc} */
  217.     public double getEquinoctialEx() {
  218.         return ex;
  219.     }

  220.     /** {@inheritDoc} */
  221.     public double getEquinoctialEy() {
  222.         return ey;
  223.     }

  224.     /** {@inheritDoc} */
  225.     public double getHx() {
  226.         return hx;
  227.     }

  228.     /** {@inheritDoc} */
  229.     public double getHy() {
  230.         return hy;
  231.     }

  232.     /** Get the longitude argument.
  233.      * @param type type of the angle
  234.      * @return longitude argument (rad)
  235.      */
  236.     public double getL(final PositionAngle type) {
  237.         return (type == PositionAngle.MEAN) ? getLM() :
  238.                                               ((type == PositionAngle.ECCENTRIC) ? getLE() :
  239.                                                                                    getLv());
  240.     }

  241.     /** {@inheritDoc} */
  242.     public double getLv() {
  243.         return lv;
  244.     }

  245.     /** {@inheritDoc} */
  246.     public double getLE() {
  247.         final double epsilon = FastMath.sqrt(1 - ex * ex - ey * ey);
  248.         final double cosLv   = FastMath.cos(lv);
  249.         final double sinLv   = FastMath.sin(lv);
  250.         final double num     = ey * cosLv - ex * sinLv;
  251.         final double den     = epsilon + 1 + ex * cosLv + ey * sinLv;
  252.         return lv + 2 * FastMath.atan(num / den);
  253.     }

  254.     /** Computes the true longitude argument from the eccentric longitude argument.
  255.      * @param lE = E + ω + Ω eccentric longitude argument (rad)
  256.      * @return the true longitude argument
  257.      */
  258.     private double eccentricToTrue(final double lE) {
  259.         final double epsilon = FastMath.sqrt(1 - ex * ex - ey * ey);
  260.         final double cosLE   = FastMath.cos(lE);
  261.         final double sinLE   = FastMath.sin(lE);
  262.         final double num     = ex * sinLE - ey * cosLE;
  263.         final double den     = epsilon + 1 - ex * cosLE - ey * sinLE;
  264.         return lE + 2 * FastMath.atan(num / den);
  265.     }

  266.     /** {@inheritDoc} */
  267.     public double getLM() {
  268.         final double lE = getLE();
  269.         return lE - ex * FastMath.sin(lE) + ey * FastMath.cos(lE);
  270.     }

  271.     /** Computes the eccentric longitude argument from the mean longitude argument.
  272.      * @param lM = M + ω + Ω mean longitude argument (rad)
  273.      * @return the eccentric longitude argument
  274.      */
  275.     private double meanToEccentric(final double lM) {
  276.         // Generalization of Kepler equation to equinoctial parameters
  277.         // with lE = PA + RAAN + E and
  278.         //      lM = PA + RAAN + M = lE - ex.sin(lE) + ey.cos(lE)
  279.         double lE = lM;
  280.         double shift = 0.0;
  281.         double lEmlM = 0.0;
  282.         double cosLE = FastMath.cos(lE);
  283.         double sinLE = FastMath.sin(lE);
  284.         int iter = 0;
  285.         do {
  286.             final double f2 = ex * sinLE - ey * cosLE;
  287.             final double f1 = 1.0 - ex * cosLE - ey * sinLE;
  288.             final double f0 = lEmlM - f2;

  289.             final double f12 = 2.0 * f1;
  290.             shift = f0 * f12 / (f1 * f12 - f0 * f2);

  291.             lEmlM -= shift;
  292.             lE     = lM + lEmlM;
  293.             cosLE  = FastMath.cos(lE);
  294.             sinLE  = FastMath.sin(lE);

  295.         } while ((++iter < 50) && (FastMath.abs(shift) > 1.0e-12));

  296.         return lE;

  297.     }

  298.     /** {@inheritDoc} */
  299.     public double getE() {
  300.         return FastMath.sqrt(ex * ex + ey * ey);
  301.     }

  302.     /** {@inheritDoc} */
  303.     public double getI() {
  304.         return 2 * FastMath.atan(FastMath.sqrt(hx * hx + hy * hy));
  305.     }

  306.     /** {@inheritDoc} */
  307.     protected TimeStampedPVCoordinates initPVCoordinates() {

  308.         // get equinoctial parameters
  309.         final double lE = getLE();

  310.         // inclination-related intermediate parameters
  311.         final double hx2   = hx * hx;
  312.         final double hy2   = hy * hy;
  313.         final double factH = 1. / (1 + hx2 + hy2);

  314.         // reference axes defining the orbital plane
  315.         final double ux = (1 + hx2 - hy2) * factH;
  316.         final double uy =  2 * hx * hy * factH;
  317.         final double uz = -2 * hy * factH;

  318.         final double vx = uy;
  319.         final double vy = (1 - hx2 + hy2) * factH;
  320.         final double vz =  2 * hx * factH;

  321.         // eccentricity-related intermediate parameters
  322.         final double exey = ex * ey;
  323.         final double ex2  = ex * ex;
  324.         final double ey2  = ey * ey;
  325.         final double e2   = ex2 + ey2;
  326.         final double eta  = 1 + FastMath.sqrt(1 - e2);
  327.         final double beta = 1. / eta;

  328.         // eccentric longitude argument
  329.         final double cLe    = FastMath.cos(lE);
  330.         final double sLe    = FastMath.sin(lE);
  331.         final double exCeyS = ex * cLe + ey * sLe;

  332.         // coordinates of position and velocity in the orbital plane
  333.         final double x      = a * ((1 - beta * ey2) * cLe + beta * exey * sLe - ex);
  334.         final double y      = a * ((1 - beta * ex2) * sLe + beta * exey * cLe - ey);

  335.         final double factor = FastMath.sqrt(getMu() / a) / (1 - exCeyS);
  336.         final double xdot   = factor * (-sLe + beta * ey * exCeyS);
  337.         final double ydot   = factor * ( cLe - beta * ex * exCeyS);

  338.         final Vector3D position =
  339.             new Vector3D(x * ux + y * vx, x * uy + y * vy, x * uz + y * vz);
  340.         final double r2 = position.getNormSq();
  341.         final Vector3D velocity =
  342.             new Vector3D(xdot * ux + ydot * vx, xdot * uy + ydot * vy, xdot * uz + ydot * vz);

  343.         final Vector3D acceleration = new Vector3D(-getMu() / (r2 * FastMath.sqrt(r2)), position);

  344.         return new TimeStampedPVCoordinates(getDate(), position, velocity, acceleration);

  345.     }

  346.     /** {@inheritDoc} */
  347.     public EquinoctialOrbit shiftedBy(final double dt) {
  348.         return new EquinoctialOrbit(a, ex, ey, hx, hy,
  349.                                     getLM() + getKeplerianMeanMotion() * dt,
  350.                                     PositionAngle.MEAN, getFrame(),
  351.                                     getDate().shiftedBy(dt), getMu());
  352.     }

  353.     /** {@inheritDoc}
  354.      * <p>
  355.      * The interpolated instance is created by polynomial Hermite interpolation
  356.      * on equinoctial elements, without derivatives (which means the interpolation
  357.      * falls back to Lagrange interpolation only).
  358.      * </p>
  359.      * <p>
  360.      * As this implementation of interpolation is polynomial, it should be used only
  361.      * with small samples (about 10-20 points) in order to avoid <a
  362.      * href="http://en.wikipedia.org/wiki/Runge%27s_phenomenon">Runge's phenomenon</a>
  363.      * and numerical problems (including NaN appearing).
  364.      * </p>
  365.      * <p>
  366.      * If orbit interpolation on large samples is needed, using the {@link
  367.      * org.orekit.propagation.analytical.Ephemeris} class is a better way than using this
  368.      * low-level interpolation. The Ephemeris class automatically handles selection of
  369.      * a neighboring sub-sample with a predefined number of point from a large global sample
  370.      * in a thread-safe way.
  371.      * </p>
  372.      */
  373.     public EquinoctialOrbit interpolate(final AbsoluteDate date, final Collection<Orbit> sample) {

  374.         // set up an interpolator
  375.         final HermiteInterpolator interpolator = new HermiteInterpolator();

  376.         // add sample points
  377.         AbsoluteDate previousDate = null;
  378.         double previousLm = Double.NaN;
  379.         for (final Orbit orbit : sample) {
  380.             final EquinoctialOrbit equi = (EquinoctialOrbit) OrbitType.EQUINOCTIAL.convertType(orbit);
  381.             final double continuousLm;
  382.             if (previousDate == null) {
  383.                 continuousLm = equi.getLM();
  384.             } else {
  385.                 final double dt       = equi.getDate().durationFrom(previousDate);
  386.                 final double keplerLm = previousLm + equi.getKeplerianMeanMotion() * dt;
  387.                 continuousLm = MathUtils.normalizeAngle(equi.getLM(), keplerLm);
  388.             }
  389.             previousDate = equi.getDate();
  390.             previousLm   = continuousLm;
  391.             interpolator.addSamplePoint(equi.getDate().durationFrom(date),
  392.                                         new double[] {
  393.                                             equi.getA(),
  394.                                             equi.getEquinoctialEx(),
  395.                                             equi.getEquinoctialEy(),
  396.                                             equi.getHx(),
  397.                                             equi.getHy(),
  398.                                             continuousLm
  399.                                         });
  400.         }

  401.         // interpolate
  402.         final double[] interpolated = interpolator.value(0);

  403.         // build a new interpolated instance
  404.         return new EquinoctialOrbit(interpolated[0], interpolated[1], interpolated[2],
  405.                                     interpolated[3], interpolated[4], interpolated[5],
  406.                                     PositionAngle.MEAN, getFrame(), date, getMu());

  407.     }

  408.     /** {@inheritDoc} */
  409.     protected double[][] computeJacobianMeanWrtCartesian() {

  410.         final double[][] jacobian = new double[6][6];

  411.         // compute various intermediate parameters
  412.         final Vector3D position = getPVCoordinates().getPosition();
  413.         final Vector3D velocity = getPVCoordinates().getVelocity();
  414.         final double r2         = position.getNormSq();
  415.         final double r          = FastMath.sqrt(r2);
  416.         final double r3         = r * r2;

  417.         final double mu         = getMu();
  418.         final double sqrtMuA    = FastMath.sqrt(a * mu);
  419.         final double a2         = a * a;

  420.         final double e2         = ex * ex + ey * ey;
  421.         final double oMe2       = 1 - e2;
  422.         final double epsilon    = FastMath.sqrt(oMe2);
  423.         final double beta       = 1 / (1 + epsilon);
  424.         final double ratio      = epsilon * beta;

  425.         final double hx2        = hx * hx;
  426.         final double hy2        = hy * hy;
  427.         final double hxhy       = hx * hy;

  428.         // precomputing equinoctial frame unit vectors (f,g,w)
  429.         final Vector3D f  = new Vector3D(1 - hy2 + hx2, 2 * hxhy, -2 * hy).normalize();
  430.         final Vector3D g  = new Vector3D(2 * hxhy, 1 + hy2 - hx2, 2 * hx).normalize();
  431.         final Vector3D w  = Vector3D.crossProduct(position, velocity).normalize();

  432.         // coordinates of the spacecraft in the equinoctial frame
  433.         final double x    = Vector3D.dotProduct(position, f);
  434.         final double y    = Vector3D.dotProduct(position, g);
  435.         final double xDot = Vector3D.dotProduct(velocity, f);
  436.         final double yDot = Vector3D.dotProduct(velocity, g);

  437.         // drDot / dEx = dXDot / dEx * f + dYDot / dEx * g
  438.         final double c1 = a / (sqrtMuA * epsilon);
  439.         final double c2 = a * sqrtMuA * beta / r3;
  440.         final double c3 = sqrtMuA / (r3 * epsilon);
  441.         final Vector3D drDotSdEx = new Vector3D( c1 * xDot * yDot - c2 * ey * x - c3 * x * y, f,
  442.                                                 -c1 * xDot * xDot - c2 * ey * y + c3 * x * x, g);

  443.         // drDot / dEy = dXDot / dEy * f + dYDot / dEy * g
  444.         final Vector3D drDotSdEy = new Vector3D( c1 * yDot * yDot + c2 * ex * x - c3 * y * y, f,
  445.                                                 -c1 * xDot * yDot + c2 * ex * y + c3 * x * y, g);

  446.         // da
  447.         final Vector3D vectorAR = new Vector3D(2 * a2 / r3, position);
  448.         final Vector3D vectorARDot = new Vector3D(2 * a2 / mu, velocity);
  449.         fillHalfRow(1, vectorAR,    jacobian[0], 0);
  450.         fillHalfRow(1, vectorARDot, jacobian[0], 3);

  451.         // dEx
  452.         final double d1 = -a * ratio / r3;
  453.         final double d2 = (hy * xDot - hx * yDot) / (sqrtMuA * epsilon);
  454.         final double d3 = (hx * y - hy * x) / sqrtMuA;
  455.         final Vector3D vectorExRDot =
  456.             new Vector3D((2 * x * yDot - xDot * y) / mu, g, -y * yDot / mu, f, -ey * d3 / epsilon, w);
  457.         fillHalfRow(ex * d1, position, -ey * d2, w, epsilon / sqrtMuA, drDotSdEy, jacobian[1], 0);
  458.         fillHalfRow(1, vectorExRDot, jacobian[1], 3);

  459.         // dEy
  460.         final Vector3D vectorEyRDot =
  461.             new Vector3D((2 * xDot * y - x * yDot) / mu, f, -x * xDot / mu, g, ex * d3 / epsilon, w);
  462.         fillHalfRow(ey * d1, position, ex * d2, w, -epsilon / sqrtMuA, drDotSdEx, jacobian[2], 0);
  463.         fillHalfRow(1, vectorEyRDot, jacobian[2], 3);

  464.         // dHx
  465.         final double h = (1 + hx2 + hy2) / (2 * sqrtMuA * epsilon);
  466.         fillHalfRow(-h * xDot, w, jacobian[3], 0);
  467.         fillHalfRow( h * x,    w, jacobian[3], 3);

  468.        // dHy
  469.         fillHalfRow(-h * yDot, w, jacobian[4], 0);
  470.         fillHalfRow( h * y,    w, jacobian[4], 3);

  471.         // dLambdaM
  472.         final double l = -ratio / sqrtMuA;
  473.         fillHalfRow(-1 / sqrtMuA, velocity, d2, w, l * ex, drDotSdEx, l * ey, drDotSdEy, jacobian[5], 0);
  474.         fillHalfRow(-2 / sqrtMuA, position, ex * beta, vectorEyRDot, -ey * beta, vectorExRDot, d3, w, jacobian[5], 3);

  475.         return jacobian;

  476.     }

  477.     /** {@inheritDoc} */
  478.     protected double[][] computeJacobianEccentricWrtCartesian() {

  479.         // start by computing the Jacobian with mean angle
  480.         final double[][] jacobian = computeJacobianMeanWrtCartesian();

  481.         // Differentiating the Kepler equation lM = lE - ex sin lE + ey cos lE leads to:
  482.         // dlM = (1 - ex cos lE - ey sin lE) dE - sin lE dex + cos lE dey
  483.         // which is inverted and rewritten as:
  484.         // dlE = a/r dlM + sin lE a/r dex - cos lE a/r dey
  485.         final double le    = getLE();
  486.         final double cosLe = FastMath.cos(le);
  487.         final double sinLe = FastMath.sin(le);
  488.         final double aOr   = 1 / (1 - ex * cosLe - ey * sinLe);

  489.         // update longitude row
  490.         final double[] rowEx = jacobian[1];
  491.         final double[] rowEy = jacobian[2];
  492.         final double[] rowL  = jacobian[5];
  493.         for (int j = 0; j < 6; ++j) {
  494.             rowL[j] = aOr * (rowL[j] + sinLe * rowEx[j] - cosLe * rowEy[j]);
  495.         }

  496.         return jacobian;

  497.     }

  498.     /** {@inheritDoc} */
  499.     protected double[][] computeJacobianTrueWrtCartesian() {

  500.         // start by computing the Jacobian with eccentric angle
  501.         final double[][] jacobian = computeJacobianEccentricWrtCartesian();

  502.         // Differentiating the eccentric longitude equation
  503.         // tan((lV - lE)/2) = [ex sin lE - ey cos lE] / [sqrt(1-ex^2-ey^2) + 1 - ex cos lE - ey sin lE]
  504.         // leads to
  505.         // cT (dlV - dlE) = cE dlE + cX dex + cY dey
  506.         // with
  507.         // cT = [d^2 + (ex sin lE - ey cos lE)^2] / 2
  508.         // d  = 1 + sqrt(1-ex^2-ey^2) - ex cos lE - ey sin lE
  509.         // cE = (ex cos lE + ey sin lE) (sqrt(1-ex^2-ey^2) + 1) - ex^2 - ey^2
  510.         // cX =  sin lE (sqrt(1-ex^2-ey^2) + 1) - ey + ex (ex sin lE - ey cos lE) / sqrt(1-ex^2-ey^2)
  511.         // cY = -cos lE (sqrt(1-ex^2-ey^2) + 1) + ex + ey (ex sin lE - ey cos lE) / sqrt(1-ex^2-ey^2)
  512.         // which can be solved to find the differential of the true longitude
  513.         // dlV = (cT + cE) / cT dlE + cX / cT deX + cY / cT deX
  514.         final double le        = getLE();
  515.         final double cosLe     = FastMath.cos(le);
  516.         final double sinLe     = FastMath.sin(le);
  517.         final double eSinE     = ex * sinLe - ey * cosLe;
  518.         final double ecosE     = ex * cosLe + ey * sinLe;
  519.         final double e2        = ex * ex + ey * ey;
  520.         final double epsilon   = FastMath.sqrt(1 - e2);
  521.         final double onePeps   = 1 + epsilon;
  522.         final double d         = onePeps - ecosE;
  523.         final double cT        = (d * d + eSinE * eSinE) / 2;
  524.         final double cE        = ecosE * onePeps - e2;
  525.         final double cX        = ex * eSinE / epsilon - ey + sinLe * onePeps;
  526.         final double cY        = ey * eSinE / epsilon + ex - cosLe * onePeps;
  527.         final double factorLe  = (cT + cE) / cT;
  528.         final double factorEx  = cX / cT;
  529.         final double factorEy  = cY / cT;

  530.         // update longitude row
  531.         final double[] rowEx = jacobian[1];
  532.         final double[] rowEy = jacobian[2];
  533.         final double[] rowL = jacobian[5];
  534.         for (int j = 0; j < 6; ++j) {
  535.             rowL[j] = factorLe * rowL[j] + factorEx * rowEx[j] + factorEy * rowEy[j];
  536.         }

  537.         return jacobian;

  538.     }

  539.     /** {@inheritDoc} */
  540.     public void addKeplerContribution(final PositionAngle type, final double gm,
  541.                                       final double[] pDot) {
  542.         final double oMe2;
  543.         final double ksi;
  544.         final double n = FastMath.sqrt(gm / a) / a;
  545.         switch (type) {
  546.             case MEAN :
  547.                 pDot[5] += n;
  548.                 break;
  549.             case ECCENTRIC :
  550.                 oMe2 = 1 - ex * ex - ey * ey;
  551.                 ksi  = 1 + ex * FastMath.cos(lv) + ey * FastMath.sin(lv);
  552.                 pDot[5] += n * ksi / oMe2;
  553.                 break;
  554.             case TRUE :
  555.                 oMe2 = 1 - ex * ex - ey * ey;
  556.                 ksi  = 1 + ex * FastMath.cos(lv) + ey * FastMath.sin(lv);
  557.                 pDot[5] += n * ksi * ksi / (oMe2 * FastMath.sqrt(oMe2));
  558.                 break;
  559.             default :
  560.                 throw new OrekitInternalError(null);
  561.         }
  562.     }

  563.     /**  Returns a string representation of this equinoctial parameters object.
  564.      * @return a string representation of this object
  565.      */
  566.     public String toString() {
  567.         return new StringBuffer().append("equinoctial parameters: ").append('{').
  568.                                   append("a: ").append(a).
  569.                                   append("; ex: ").append(ex).append("; ey: ").append(ey).
  570.                                   append("; hx: ").append(hx).append("; hy: ").append(hy).
  571.                                   append("; lv: ").append(FastMath.toDegrees(lv)).
  572.                                   append(";}").toString();
  573.     }

  574.     /** Replace the instance with a data transfer object for serialization.
  575.      * @return data transfer object that will be serialized
  576.      */
  577.     private Object writeReplace() {
  578.         return new DTO(this);
  579.     }

  580.     /** Internal class used only for serialization. */
  581.     private static class DTO implements Serializable {

  582.         /** Serializable UID. */
  583.         private static final long serialVersionUID = 20140617L;

  584.         /** Double values. */
  585.         private double[] d;

  586.         /** Frame in which are defined the orbital parameters. */
  587.         private final Frame frame;

  588.         /** Simple constructor.
  589.          * @param orbit instance to serialize
  590.          */
  591.         private DTO(final EquinoctialOrbit orbit) {

  592.             final TimeStampedPVCoordinates pv = orbit.getPVCoordinates();

  593.             // decompose date
  594.             final double epoch  = FastMath.floor(pv.getDate().durationFrom(AbsoluteDate.J2000_EPOCH));
  595.             final double offset = pv.getDate().durationFrom(AbsoluteDate.J2000_EPOCH.shiftedBy(epoch));

  596.             this.d = new double[] {
  597.                 epoch, offset, orbit.getMu(),
  598.                 orbit.a, orbit.ex, orbit.ey,
  599.                 orbit.hx, orbit.hy, orbit.lv
  600.             };

  601.             this.frame = orbit.getFrame();

  602.         }

  603.         /** Replace the deserialized data transfer object with a {@link EquinoctialOrbit}.
  604.          * @return replacement {@link EquinoctialOrbit}
  605.          */
  606.         private Object readResolve() {
  607.             return new EquinoctialOrbit(d[3], d[4], d[5], d[6], d[7], d[8], PositionAngle.TRUE,
  608.                                         frame, AbsoluteDate.J2000_EPOCH.shiftedBy(d[0]).shiftedBy(d[1]),
  609.                                         d[2]);
  610.         }

  611.     }

  612. }