CartesianOrbit.java

  1. /* Copyright 2002-2025 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. import org.hipparchus.analysis.differentiation.UnivariateDerivative2;
  19. import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
  20. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  21. import org.hipparchus.linear.MatrixUtils;
  22. import org.hipparchus.util.FastMath;
  23. import org.orekit.frames.Frame;
  24. import org.orekit.frames.KinematicTransform;
  25. import org.orekit.time.AbsoluteDate;
  26. import org.orekit.time.TimeOffset;
  27. import org.orekit.utils.FieldPVCoordinates;
  28. import org.orekit.utils.PVCoordinates;
  29. import org.orekit.utils.TimeStampedPVCoordinates;


  30. /** This class holds Cartesian orbital parameters.

  31.  * <p>
  32.  * The parameters used internally are the Cartesian coordinates:
  33.  *   <ul>
  34.  *     <li>x</li>
  35.  *     <li>y</li>
  36.  *     <li>z</li>
  37.  *     <li>xDot</li>
  38.  *     <li>yDot</li>
  39.  *     <li>zDot</li>
  40.  *   </ul>
  41.  * contained in {@link PVCoordinates}.
  42.  *

  43.  * <p>
  44.  * Note that the implementation of this class delegates all non-Cartesian related
  45.  * computations ({@link #getA()}, {@link #getEquinoctialEx()}, ...) to an underlying
  46.  * instance of the {@link EquinoctialOrbit} class. This implies that using this class
  47.  * only for analytical computations which are always based on non-Cartesian
  48.  * parameters is perfectly possible but somewhat sub-optimal.
  49.  * </p>
  50.  * <p>
  51.  * The instance <code>CartesianOrbit</code> is guaranteed to be immutable.
  52.  * </p>
  53.  * @see    Orbit
  54.  * @see    KeplerianOrbit
  55.  * @see    CircularOrbit
  56.  * @see    EquinoctialOrbit
  57.  * @author Luc Maisonobe
  58.  * @author Guylaine Prat
  59.  * @author Fabien Maussion
  60.  * @author V&eacute;ronique Pommier-Maurussane
  61.  * @author Andrew Goetz
  62.  */
  63. public class CartesianOrbit extends Orbit {

  64.     /** 6x6 identity matrix. */
  65.     private static final double[][] SIX_BY_SIX_IDENTITY = MatrixUtils.createRealIdentityMatrix(6).getData();

  66.     /** Indicator for non-Keplerian derivatives. */
  67.     private final transient boolean hasNonKeplerianAcceleration;

  68.     /** Underlying equinoctial orbit to which high-level methods are delegated. */
  69.     private transient EquinoctialOrbit equinoctial;

  70.     /** Constructor from Cartesian parameters.
  71.      *
  72.      * <p> The acceleration provided in {@code pvCoordinates} is accessible using
  73.      * {@link #getPVCoordinates()} and {@link #getPVCoordinates(Frame)}. All other methods
  74.      * use {@code mu} and the position to compute the acceleration, including
  75.      * {@link #shiftedBy(double)} and {@link #getPVCoordinates(AbsoluteDate, Frame)}.
  76.      *
  77.      * @param pvaCoordinates the position, velocity and acceleration of the satellite.
  78.      * @param frame the frame in which the {@link PVCoordinates} are defined
  79.      * (<em>must</em> be a {@link Frame#isPseudoInertial pseudo-inertial frame})
  80.      * @param mu central attraction coefficient (m³/s²)
  81.      * @exception IllegalArgumentException if frame is not a {@link
  82.      * Frame#isPseudoInertial pseudo-inertial frame}
  83.      */
  84.     public CartesianOrbit(final TimeStampedPVCoordinates pvaCoordinates,
  85.                           final Frame frame, final double mu)
  86.         throws IllegalArgumentException {
  87.         super(pvaCoordinates, frame, mu);
  88.         hasNonKeplerianAcceleration = hasNonKeplerianAcceleration(pvaCoordinates, mu);
  89.         equinoctial = null;
  90.     }

  91.     /** Constructor from Cartesian parameters.
  92.      *
  93.      * <p> The acceleration provided in {@code pvCoordinates} is accessible using
  94.      * {@link #getPVCoordinates()} and {@link #getPVCoordinates(Frame)}. All other methods
  95.      * use {@code mu} and the position to compute the acceleration, including
  96.      * {@link #shiftedBy(double)} and {@link #getPVCoordinates(AbsoluteDate, Frame)}.
  97.      *
  98.      * @param pvaCoordinates the position and velocity of the satellite.
  99.      * @param frame the frame in which the {@link PVCoordinates} are defined
  100.      * (<em>must</em> be a {@link Frame#isPseudoInertial pseudo-inertial frame})
  101.      * @param date date of the orbital parameters
  102.      * @param mu central attraction coefficient (m³/s²)
  103.      * @exception IllegalArgumentException if frame is not a {@link
  104.      * Frame#isPseudoInertial pseudo-inertial frame}
  105.      */
  106.     public CartesianOrbit(final PVCoordinates pvaCoordinates, final Frame frame,
  107.                           final AbsoluteDate date, final double mu)
  108.         throws IllegalArgumentException {
  109.         this(new TimeStampedPVCoordinates(date, pvaCoordinates), frame, mu);
  110.     }

  111.     /** Constructor from any kind of orbital parameters.
  112.      * @param op orbital parameters to copy
  113.      */
  114.     public CartesianOrbit(final Orbit op) {
  115.         super(op.getPVCoordinates(), op.getFrame(), op.getMu());
  116.         hasNonKeplerianAcceleration = op.hasNonKeplerianAcceleration();
  117.         if (op instanceof EquinoctialOrbit) {
  118.             equinoctial = (EquinoctialOrbit) op;
  119.         } else if (op instanceof CartesianOrbit) {
  120.             equinoctial = ((CartesianOrbit) op).equinoctial;
  121.         } else {
  122.             equinoctial = null;
  123.         }
  124.     }

  125.     /** {@inheritDoc} */
  126.     public OrbitType getType() {
  127.         return OrbitType.CARTESIAN;
  128.     }

  129.     /** {@inheritDoc} */
  130.     @Override
  131.     protected Vector3D nonKeplerianAcceleration() {
  132.         final double norm = getPosition().getNorm();
  133.         return getPVCoordinates().getAcceleration().add(new Vector3D(getMu() / (norm * norm * norm), getPosition()));
  134.     }

  135.     /** Lazy evaluation of equinoctial parameters. */
  136.     private void initEquinoctial() {
  137.         if (equinoctial == null) {
  138.             if (hasNonKeplerianAcceleration()) {
  139.                 // getPVCoordinates includes accelerations that will be interpreted as derivatives
  140.                 equinoctial = new EquinoctialOrbit(getPVCoordinates(), getFrame(), getDate(), getMu());
  141.             } else {
  142.                 // get rid of Keplerian acceleration so we don't assume
  143.                 // we have derivatives when in fact we don't have them
  144.                 equinoctial = new EquinoctialOrbit(new PVCoordinates(getPosition(),
  145.                                                                      getPVCoordinates().getVelocity()),
  146.                                                    getFrame(), getDate(), getMu());
  147.             }
  148.         }
  149.     }

  150.     /** Get the position/velocity with derivatives.
  151.      * @return position/velocity with derivatives
  152.      * @since 10.2
  153.      */
  154.     private FieldPVCoordinates<UnivariateDerivative2> getPVDerivatives() {
  155.         // PVA coordinates
  156.         final PVCoordinates pva = getPVCoordinates();
  157.         final Vector3D      p   = pva.getPosition();
  158.         final Vector3D      v   = pva.getVelocity();
  159.         final Vector3D      a   = pva.getAcceleration();
  160.         // Field coordinates
  161.         final FieldVector3D<UnivariateDerivative2> pG = new FieldVector3D<>(new UnivariateDerivative2(p.getX(), v.getX(), a.getX()),
  162.                                                                new UnivariateDerivative2(p.getY(), v.getY(), a.getY()),
  163.                                                                new UnivariateDerivative2(p.getZ(), v.getZ(), a.getZ()));
  164.         final FieldVector3D<UnivariateDerivative2> vG = new FieldVector3D<>(new UnivariateDerivative2(v.getX(), a.getX(), 0.0),
  165.                                                                new UnivariateDerivative2(v.getY(), a.getY(), 0.0),
  166.                                                                new UnivariateDerivative2(v.getZ(), a.getZ(), 0.0));
  167.         return new FieldPVCoordinates<>(pG, vG);
  168.     }

  169.     /** {@inheritDoc} */
  170.     public double getA() {
  171.         final double r  = getPosition().getNorm();
  172.         final double V2 = getPVCoordinates().getVelocity().getNormSq();
  173.         return r / (2 - r * V2 / getMu());
  174.     }

  175.     /** {@inheritDoc} */
  176.     public double getADot() {
  177.         if (hasNonKeplerianAcceleration) {
  178.             final FieldPVCoordinates<UnivariateDerivative2> pv = getPVDerivatives();
  179.             final UnivariateDerivative2 r  = pv.getPosition().getNorm();
  180.             final UnivariateDerivative2 V2 = pv.getVelocity().getNormSq();
  181.             final UnivariateDerivative2 a  = r.divide(r.multiply(V2).divide(getMu()).subtract(2).negate());
  182.             return a.getDerivative(1);
  183.         } else {
  184.             return 0.;
  185.         }
  186.     }

  187.     /** {@inheritDoc} */
  188.     public double getE() {
  189.         final double muA = getMu() * getA();
  190.         if (isElliptical()) {
  191.             // elliptic or circular orbit
  192.             final Vector3D pvP   = getPosition();
  193.             final Vector3D pvV   = getPVCoordinates().getVelocity();
  194.             final double rV2OnMu = pvP.getNorm() * pvV.getNormSq() / getMu();
  195.             final double eSE     = Vector3D.dotProduct(pvP, pvV) / FastMath.sqrt(muA);
  196.             final double eCE     = rV2OnMu - 1;
  197.             return FastMath.sqrt(eCE * eCE + eSE * eSE);
  198.         } else {
  199.             // hyperbolic orbit
  200.             final Vector3D pvM = getPVCoordinates().getMomentum();
  201.             return FastMath.sqrt(1 - pvM.getNormSq() / muA);
  202.         }
  203.     }

  204.     /** {@inheritDoc} */
  205.     public double getEDot() {
  206.         if (hasNonKeplerianAcceleration) {
  207.             final FieldPVCoordinates<UnivariateDerivative2> pv = getPVDerivatives();
  208.             final FieldVector3D<UnivariateDerivative2> pvP   = pv.getPosition();
  209.             final FieldVector3D<UnivariateDerivative2> pvV   = pv.getVelocity();
  210.             final UnivariateDerivative2 r       = pvP.getNorm();
  211.             final UnivariateDerivative2 V2      = pvV.getNormSq();
  212.             final UnivariateDerivative2 rV2OnMu = r.multiply(V2).divide(getMu());
  213.             final UnivariateDerivative2 a       = r.divide(rV2OnMu.negate().add(2));
  214.             final UnivariateDerivative2 eSE     = FieldVector3D.dotProduct(pvP, pvV).divide(a.multiply(getMu()).sqrt());
  215.             final UnivariateDerivative2 eCE     = rV2OnMu.subtract(1);
  216.             final UnivariateDerivative2 e       = eCE.multiply(eCE).add(eSE.multiply(eSE)).sqrt();
  217.             return e.getDerivative(1);
  218.         } else {
  219.             return 0.;
  220.         }
  221.     }

  222.     /** {@inheritDoc} */
  223.     public double getI() {
  224.         return Vector3D.angle(Vector3D.PLUS_K, getPVCoordinates().getMomentum());
  225.     }

  226.     /** {@inheritDoc} */
  227.     public double getIDot() {
  228.         if (hasNonKeplerianAcceleration) {
  229.             final FieldPVCoordinates<UnivariateDerivative2> pv = getPVDerivatives();
  230.             final FieldVector3D<UnivariateDerivative2> momentum =
  231.                             FieldVector3D.crossProduct(pv.getPosition(), pv.getVelocity());
  232.             final UnivariateDerivative2 i = FieldVector3D.angle(Vector3D.PLUS_K, momentum);
  233.             return i.getDerivative(1);
  234.         } else {
  235.             return 0.;
  236.         }
  237.     }

  238.     /** {@inheritDoc} */
  239.     public double getEquinoctialEx() {
  240.         initEquinoctial();
  241.         return equinoctial.getEquinoctialEx();
  242.     }

  243.     /** {@inheritDoc} */
  244.     public double getEquinoctialExDot() {
  245.         initEquinoctial();
  246.         return equinoctial.getEquinoctialExDot();
  247.     }

  248.     /** {@inheritDoc} */
  249.     public double getEquinoctialEy() {
  250.         initEquinoctial();
  251.         return equinoctial.getEquinoctialEy();
  252.     }

  253.     /** {@inheritDoc} */
  254.     public double getEquinoctialEyDot() {
  255.         initEquinoctial();
  256.         return equinoctial.getEquinoctialEyDot();
  257.     }

  258.     /** {@inheritDoc} */
  259.     public double getHx() {
  260.         final Vector3D w = getPVCoordinates().getMomentum().normalize();
  261.         // Check for equatorial retrograde orbit
  262.         if ((w.getX() * w.getX() + w.getY() * w.getY()) == 0 && w.getZ() < 0) {
  263.             return Double.NaN;
  264.         }
  265.         return -w.getY() / (1 + w.getZ());
  266.     }

  267.     /** {@inheritDoc} */
  268.     public double getHxDot() {
  269.         if (hasNonKeplerianAcceleration) {
  270.             final FieldPVCoordinates<UnivariateDerivative2> pv = getPVDerivatives();
  271.             final FieldVector3D<UnivariateDerivative2> w =
  272.                             FieldVector3D.crossProduct(pv.getPosition(), pv.getVelocity()).normalize();
  273.             // Check for equatorial retrograde orbit
  274.             final double x = w.getX().getValue();
  275.             final double y = w.getY().getValue();
  276.             final double z = w.getZ().getValue();
  277.             if ((x * x + y * y) == 0 && z < 0) {
  278.                 return Double.NaN;
  279.             }
  280.             final UnivariateDerivative2 hx = w.getY().negate().divide(w.getZ().add(1));
  281.             return hx.getDerivative(1);
  282.         } else {
  283.             return 0.;
  284.         }
  285.     }

  286.     /** {@inheritDoc} */
  287.     public double getHy() {
  288.         final Vector3D w = getPVCoordinates().getMomentum().normalize();
  289.         // Check for equatorial retrograde orbit
  290.         if ((w.getX() * w.getX() + w.getY() * w.getY()) == 0 && w.getZ() < 0) {
  291.             return Double.NaN;
  292.         }
  293.         return  w.getX() / (1 + w.getZ());
  294.     }

  295.     /** {@inheritDoc} */
  296.     public double getHyDot() {
  297.         if (hasNonKeplerianAcceleration) {
  298.             final FieldPVCoordinates<UnivariateDerivative2> pv = getPVDerivatives();
  299.             final FieldVector3D<UnivariateDerivative2> w =
  300.                             FieldVector3D.crossProduct(pv.getPosition(), pv.getVelocity()).normalize();
  301.             // Check for equatorial retrograde orbit
  302.             final double x = w.getX().getValue();
  303.             final double y = w.getY().getValue();
  304.             final double z = w.getZ().getValue();
  305.             if ((x * x + y * y) == 0 && z < 0) {
  306.                 return Double.NaN;
  307.             }
  308.             final UnivariateDerivative2 hy = w.getX().divide(w.getZ().add(1));
  309.             return hy.getDerivative(1);
  310.         } else {
  311.             return 0.;
  312.         }
  313.     }

  314.     /** {@inheritDoc} */
  315.     public double getLv() {
  316.         initEquinoctial();
  317.         return equinoctial.getLv();
  318.     }

  319.     /** {@inheritDoc} */
  320.     public double getLvDot() {
  321.         initEquinoctial();
  322.         return equinoctial.getLvDot();
  323.     }

  324.     /** {@inheritDoc} */
  325.     public double getLE() {
  326.         initEquinoctial();
  327.         return equinoctial.getLE();
  328.     }

  329.     /** {@inheritDoc} */
  330.     public double getLEDot() {
  331.         initEquinoctial();
  332.         return equinoctial.getLEDot();
  333.     }

  334.     /** {@inheritDoc} */
  335.     public double getLM() {
  336.         initEquinoctial();
  337.         return equinoctial.getLM();
  338.     }

  339.     /** {@inheritDoc} */
  340.     public double getLMDot() {
  341.         initEquinoctial();
  342.         return equinoctial.getLMDot();
  343.     }

  344.     /** {@inheritDoc} */
  345.     @Override
  346.     public boolean hasNonKeplerianAcceleration() {
  347.         return hasNonKeplerianAcceleration;
  348.     }

  349.     /** {@inheritDoc} */
  350.     protected Vector3D initPosition() {
  351.         // nothing to do here, as the canonical elements are already the Cartesian ones
  352.         return getPVCoordinates().getPosition();
  353.     }

  354.     /** {@inheritDoc} */
  355.     protected TimeStampedPVCoordinates initPVCoordinates() {
  356.         // nothing to do here, as the canonical elements are already the Cartesian ones
  357.         return getPVCoordinates();
  358.     }

  359.     /** {@inheritDoc} */
  360.     @Override
  361.     public CartesianOrbit inFrame(final Frame inertialFrame) {
  362.         if (hasNonKeplerianAcceleration()) {
  363.             return new CartesianOrbit(getPVCoordinates(inertialFrame), inertialFrame, getMu());
  364.         } else {
  365.             final KinematicTransform transform = getFrame().getKinematicTransformTo(inertialFrame, getDate());
  366.             return new CartesianOrbit(transform.transformOnlyPV(getPVCoordinates()), inertialFrame, getDate(), getMu());
  367.         }
  368.     }

  369.     /** {@inheritDoc} */
  370.     public CartesianOrbit shiftedBy(final double dt) {
  371.         final PVCoordinates shiftedPV = shiftPV(dt);
  372.         return new CartesianOrbit(shiftedPV, getFrame(), getDate().shiftedBy(dt), getMu());
  373.     }

  374.     /** {@inheritDoc} */
  375.     public CartesianOrbit shiftedBy(final TimeOffset dt) {
  376.         final PVCoordinates shiftedPV = shiftPV(dt.toDouble());
  377.         return new CartesianOrbit(shiftedPV, getFrame(), getDate().shiftedBy(dt), getMu());
  378.     }

  379.     /** Compute shifted position and velocity.
  380.      * @param dt time shift
  381.      * @return shifted position and velocity
  382.      */
  383.     private PVCoordinates shiftPV(final double dt) {

  384.         final Vector3D pvP = getPosition();
  385.         final PVCoordinates shiftedPV = KeplerianMotionCartesianUtility.predictPositionVelocity(dt, pvP,
  386.             getPVCoordinates().getVelocity(), getMu());

  387.         if (dt != 0. && hasNonKeplerianAcceleration) {

  388.             // extract non-Keplerian part of the initial acceleration
  389.             final double r2 = pvP.getNormSq();
  390.             final double r = FastMath.sqrt(r2);
  391.             final Vector3D nonKeplerianAcceleration = new Vector3D(1, getPVCoordinates().getAcceleration(),
  392.                                                                    getMu() / (r2 * r), pvP);

  393.             // add the quadratic motion due to the non-Keplerian acceleration to the Keplerian motion
  394.             final Vector3D shiftedP = shiftedPV.getPosition();
  395.             final Vector3D shiftedV = shiftedPV.getVelocity();
  396.             final Vector3D fixedP   = new Vector3D(1, shiftedP,
  397.                                                    0.5 * dt * dt, nonKeplerianAcceleration);
  398.             final double   fixedR2 = fixedP.getNormSq();
  399.             final double   fixedR  = FastMath.sqrt(fixedR2);
  400.             final Vector3D fixedV  = new Vector3D(1, shiftedV,
  401.                                                   dt, nonKeplerianAcceleration);
  402.             final Vector3D fixedA  = new Vector3D(-getMu() / (fixedR2 * fixedR), shiftedP,
  403.                                                   1, nonKeplerianAcceleration);

  404.             return new PVCoordinates(fixedP, fixedV, fixedA);

  405.         } else {
  406.             // don't include acceleration,
  407.             // so the shifted orbit is not considered to have derivatives
  408.             return shiftedPV;
  409.         }

  410.     }

  411.     @Override
  412.     protected double[][] computeJacobianMeanWrtCartesian() {
  413.         return SIX_BY_SIX_IDENTITY;
  414.     }

  415.     @Override
  416.     protected double[][] computeJacobianEccentricWrtCartesian() {
  417.         return SIX_BY_SIX_IDENTITY;
  418.     }

  419.     @Override
  420.     protected double[][] computeJacobianTrueWrtCartesian() {
  421.         return SIX_BY_SIX_IDENTITY;
  422.     }

  423.     /** {@inheritDoc} */
  424.     public void addKeplerContribution(final PositionAngleType type, final double gm,
  425.                                       final double[] pDot) {

  426.         final PVCoordinates pv = getPVCoordinates();

  427.         // position derivative is velocity
  428.         final Vector3D velocity = pv.getVelocity();
  429.         pDot[0] += velocity.getX();
  430.         pDot[1] += velocity.getY();
  431.         pDot[2] += velocity.getZ();

  432.         // velocity derivative is Newtonian acceleration
  433.         final Vector3D position = pv.getPosition();
  434.         final double r2         = position.getNormSq();
  435.         final double coeff      = -gm / (r2 * FastMath.sqrt(r2));
  436.         pDot[3] += coeff * position.getX();
  437.         pDot[4] += coeff * position.getY();
  438.         pDot[5] += coeff * position.getZ();

  439.     }

  440.     /**  Returns a string representation of this Orbit object.
  441.      * @return a string representation of this object
  442.      */
  443.     public String toString() {
  444.         // use only the six defining elements, like the other Orbit.toString() methods
  445.         final String comma = ", ";
  446.         final PVCoordinates pv = getPVCoordinates();
  447.         final Vector3D p = pv.getPosition();
  448.         final Vector3D v = pv.getVelocity();
  449.         return "Cartesian parameters: {P(" +
  450.                 p.getX() + comma +
  451.                 p.getY() + comma +
  452.                 p.getZ() + "), V(" +
  453.                 v.getX() + comma +
  454.                 v.getY() + comma +
  455.                 v.getZ() + ")}";
  456.     }

  457. }