FieldCartesianOrbit.java
/* Copyright 2002-2024 CS GROUP
* Licensed to CS GROUP (CS) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* CS licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.orekit.orbits;
import java.util.Arrays;
import org.hipparchus.CalculusFieldElement;
import org.hipparchus.Field;
import org.hipparchus.analysis.differentiation.FieldUnivariateDerivative2;
import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
import org.hipparchus.geometry.euclidean.threed.Vector3D;
import org.hipparchus.util.MathArrays;
import org.orekit.frames.FieldKinematicTransform;
import org.orekit.frames.Frame;
import org.orekit.time.AbsoluteDate;
import org.orekit.time.FieldAbsoluteDate;
import org.orekit.utils.FieldPVCoordinates;
import org.orekit.utils.PVCoordinates;
import org.orekit.utils.TimeStampedFieldPVCoordinates;
/** This class holds Cartesian orbital parameters.
* <p>
* The parameters used internally are the Cartesian coordinates:
* <ul>
* <li>x</li>
* <li>y</li>
* <li>z</li>
* <li>xDot</li>
* <li>yDot</li>
* <li>zDot</li>
* </ul>
* contained in {@link PVCoordinates}.
* <p>
* Note that the implementation of this class delegates all non-Cartesian related
* computations ({@link #getA()}, {@link #getEquinoctialEx()}, ...) to an underlying
* instance of the {@link EquinoctialOrbit} class. This implies that using this class
* only for analytical computations which are always based on non-Cartesian
* parameters is perfectly possible but somewhat sub-optimal.
* </p>
* <p>
* The instance <code>CartesianOrbit</code> is guaranteed to be immutable.
* </p>
* @see Orbit
* @see KeplerianOrbit
* @see CircularOrbit
* @see EquinoctialOrbit
* @author Luc Maisonobe
* @author Guylaine Prat
* @author Fabien Maussion
* @author Véronique Pommier-Maurussane
* @author Andrew Goetz
* @since 9.0
* @param <T> type of the field elements
*/
public class FieldCartesianOrbit<T extends CalculusFieldElement<T>> extends FieldOrbit<T> {
/** Indicator for non-Keplerian acceleration. */
private final transient boolean hasNonKeplerianAcceleration;
/** Underlying equinoctial orbit to which high-level methods are delegated. */
private transient FieldEquinoctialOrbit<T> equinoctial;
/** Constructor from Cartesian parameters.
*
* <p> The acceleration provided in {@code pvCoordinates} is accessible using
* {@link #getPVCoordinates()} and {@link #getPVCoordinates(Frame)}. All other methods
* use {@code mu} and the position to compute the acceleration, including
* {@link #shiftedBy(CalculusFieldElement)} and {@link #getPVCoordinates(FieldAbsoluteDate, Frame)}.
*
* @param pvaCoordinates the position, velocity and acceleration of the satellite.
* @param frame the frame in which the {@link PVCoordinates} are defined
* (<em>must</em> be a {@link Frame#isPseudoInertial pseudo-inertial frame})
* @param mu central attraction coefficient (m³/s²)
* @exception IllegalArgumentException if frame is not a {@link
* Frame#isPseudoInertial pseudo-inertial frame}
*/
public FieldCartesianOrbit(final TimeStampedFieldPVCoordinates<T> pvaCoordinates,
final Frame frame, final T mu)
throws IllegalArgumentException {
super(pvaCoordinates, frame, mu);
hasNonKeplerianAcceleration = hasNonKeplerianAcceleration(pvaCoordinates, mu);
equinoctial = null;
}
/** Constructor from Cartesian parameters.
*
* <p> The acceleration provided in {@code pvCoordinates} is accessible using
* {@link #getPVCoordinates()} and {@link #getPVCoordinates(Frame)}. All other methods
* use {@code mu} and the position to compute the acceleration, including
* {@link #shiftedBy(CalculusFieldElement)} and {@link #getPVCoordinates(FieldAbsoluteDate, Frame)}.
*
* @param pvaCoordinates the position and velocity of the satellite.
* @param frame the frame in which the {@link PVCoordinates} are defined
* (<em>must</em> be a {@link Frame#isPseudoInertial pseudo-inertial frame})
* @param date date of the orbital parameters
* @param mu central attraction coefficient (m³/s²)
* @exception IllegalArgumentException if frame is not a {@link
* Frame#isPseudoInertial pseudo-inertial frame}
*/
public FieldCartesianOrbit(final FieldPVCoordinates<T> pvaCoordinates, final Frame frame,
final FieldAbsoluteDate<T> date, final T mu)
throws IllegalArgumentException {
this(new TimeStampedFieldPVCoordinates<>(date, pvaCoordinates), frame, mu);
}
/** Constructor from any kind of orbital parameters.
* @param op orbital parameters to copy
*/
public FieldCartesianOrbit(final FieldOrbit<T> op) {
super(op.getPVCoordinates(), op.getFrame(), op.getMu());
hasNonKeplerianAcceleration = op.hasNonKeplerianAcceleration();
if (op instanceof FieldEquinoctialOrbit) {
equinoctial = (FieldEquinoctialOrbit<T>) op;
} else if (op instanceof FieldCartesianOrbit) {
equinoctial = ((FieldCartesianOrbit<T>) op).equinoctial;
} else {
equinoctial = null;
}
}
/** Constructor from Field and CartesianOrbit.
* <p>Build a FieldCartesianOrbit from non-Field CartesianOrbit.</p>
* @param field CalculusField to base object on
* @param op non-field orbit with only "constant" terms
* @since 12.0
*/
public FieldCartesianOrbit(final Field<T> field, final CartesianOrbit op) {
super(new TimeStampedFieldPVCoordinates<>(field, op.getPVCoordinates()), op.getFrame(),
field.getZero().newInstance(op.getMu()));
hasNonKeplerianAcceleration = op.hasNonKeplerianAcceleration();
if (op.isElliptical()) {
equinoctial = new FieldEquinoctialOrbit<>(field, new EquinoctialOrbit(op));
} else {
equinoctial = null;
}
}
/** Constructor from Field and Orbit.
* <p>Build a FieldCartesianOrbit from any non-Field Orbit.</p>
* @param field CalculusField to base object on
* @param op non-field orbit with only "constant" terms
* @since 12.0
*/
public FieldCartesianOrbit(final Field<T> field, final Orbit op) {
this(field, new CartesianOrbit(op));
}
/** {@inheritDoc} */
public OrbitType getType() {
return OrbitType.CARTESIAN;
}
/** Lazy evaluation of equinoctial parameters. */
private void initEquinoctial() {
if (equinoctial == null) {
if (hasNonKeplerianAcceleration()) {
// getPVCoordinates includes accelerations that will be interpreted as derivatives
equinoctial = new FieldEquinoctialOrbit<>(getPVCoordinates(), getFrame(), getDate(), getMu());
} else {
// get rid of Keplerian acceleration so we don't assume
// we have derivatives when in fact we don't have them
final FieldPVCoordinates<T> pva = getPVCoordinates();
equinoctial = new FieldEquinoctialOrbit<>(new FieldPVCoordinates<>(pva.getPosition(), pva.getVelocity()),
getFrame(), getDate(), getMu());
}
}
}
/** Get the position/velocity with derivatives.
* @return position/velocity with derivatives
* @since 10.2
*/
private FieldPVCoordinates<FieldUnivariateDerivative2<T>> getPVDerivatives() {
// PVA coordinates
final FieldPVCoordinates<T> pva = getPVCoordinates();
final FieldVector3D<T> p = pva.getPosition();
final FieldVector3D<T> v = pva.getVelocity();
final FieldVector3D<T> a = pva.getAcceleration();
// Field coordinates
final FieldVector3D<FieldUnivariateDerivative2<T>> pG = new FieldVector3D<>(new FieldUnivariateDerivative2<>(p.getX(), v.getX(), a.getX()),
new FieldUnivariateDerivative2<>(p.getY(), v.getY(), a.getY()),
new FieldUnivariateDerivative2<>(p.getZ(), v.getZ(), a.getZ()));
final FieldVector3D<FieldUnivariateDerivative2<T>> vG = new FieldVector3D<>(new FieldUnivariateDerivative2<>(v.getX(), a.getX(), getZero()),
new FieldUnivariateDerivative2<>(v.getY(), a.getY(), getZero()),
new FieldUnivariateDerivative2<>(v.getZ(), a.getZ(), getZero()));
return new FieldPVCoordinates<>(pG, vG);
}
/** {@inheritDoc} */
public T getA() {
// lazy evaluation of semi-major axis
final FieldPVCoordinates<T> pva = getPVCoordinates();
final T r = pva.getPosition().getNorm();
final T V2 = pva.getVelocity().getNormSq();
return r.divide(r.negate().multiply(V2).divide(getMu()).add(2));
}
/** {@inheritDoc} */
public T getADot() {
if (hasNonKeplerianAcceleration()) {
final FieldPVCoordinates<FieldUnivariateDerivative2<T>> pv = getPVDerivatives();
final FieldUnivariateDerivative2<T> r = pv.getPosition().getNorm();
final FieldUnivariateDerivative2<T> V2 = pv.getVelocity().getNormSq();
final FieldUnivariateDerivative2<T> a = r.divide(r.multiply(V2).divide(getMu()).subtract(2).negate());
return a.getDerivative(1);
} else {
return getZero();
}
}
/** {@inheritDoc} */
public T getE() {
final T muA = getA().multiply(getMu());
if (isElliptical()) {
// elliptic or circular orbit
final FieldPVCoordinates<T> pva = getPVCoordinates();
final FieldVector3D<T> pvP = pva.getPosition();
final FieldVector3D<T> pvV = pva.getVelocity();
final T rV2OnMu = pvP.getNorm().multiply(pvV.getNormSq()).divide(getMu());
final T eSE = FieldVector3D.dotProduct(pvP, pvV).divide(muA.sqrt());
final T eCE = rV2OnMu.subtract(1);
return (eCE.square().add(eSE.square())).sqrt();
} else {
// hyperbolic orbit
final FieldVector3D<T> pvM = getPVCoordinates().getMomentum();
return pvM.getNormSq().divide(muA).negate().add(1).sqrt();
}
}
/** {@inheritDoc} */
public T getEDot() {
if (hasNonKeplerianAcceleration()) {
final FieldPVCoordinates<FieldUnivariateDerivative2<T>> pv = getPVDerivatives();
final FieldUnivariateDerivative2<T> r = pv.getPosition().getNorm();
final FieldUnivariateDerivative2<T> V2 = pv.getVelocity().getNormSq();
final FieldUnivariateDerivative2<T> rV2OnMu = r.multiply(V2).divide(getMu());
final FieldUnivariateDerivative2<T> a = r.divide(rV2OnMu.negate().add(2));
final FieldUnivariateDerivative2<T> eSE = FieldVector3D.dotProduct(pv.getPosition(), pv.getVelocity()).divide(a.multiply(getMu()).sqrt());
final FieldUnivariateDerivative2<T> eCE = rV2OnMu.subtract(1);
final FieldUnivariateDerivative2<T> e = eCE.square().add(eSE.square()).sqrt();
return e.getDerivative(1);
} else {
return getZero();
}
}
/** {@inheritDoc} */
public T getI() {
return FieldVector3D.angle(new FieldVector3D<>(getZero(), getZero(), getOne()), getPVCoordinates().getMomentum());
}
/** {@inheritDoc} */
public T getIDot() {
if (hasNonKeplerianAcceleration()) {
final FieldPVCoordinates<FieldUnivariateDerivative2<T>> pv = getPVDerivatives();
final FieldVector3D<FieldUnivariateDerivative2<T>> momentum =
FieldVector3D.crossProduct(pv.getPosition(), pv.getVelocity());
final FieldUnivariateDerivative2<T> i = FieldVector3D.angle(Vector3D.PLUS_K, momentum);
return i.getDerivative(1);
} else {
return getZero();
}
}
/** {@inheritDoc} */
public T getEquinoctialEx() {
initEquinoctial();
return equinoctial.getEquinoctialEx();
}
/** {@inheritDoc} */
public T getEquinoctialExDot() {
initEquinoctial();
return equinoctial.getEquinoctialExDot();
}
/** {@inheritDoc} */
public T getEquinoctialEy() {
initEquinoctial();
return equinoctial.getEquinoctialEy();
}
/** {@inheritDoc} */
public T getEquinoctialEyDot() {
initEquinoctial();
return equinoctial.getEquinoctialEyDot();
}
/** {@inheritDoc} */
public T getHx() {
final FieldVector3D<T> w = getPVCoordinates().getMomentum().normalize();
// Check for equatorial retrograde orbit
final double x = w.getX().getReal();
final double y = w.getY().getReal();
final double z = w.getZ().getReal();
if ((x * x + y * y) == 0 && z < 0) {
return getZero().add(Double.NaN);
}
return w.getY().negate().divide(w.getZ().add(1));
}
/** {@inheritDoc} */
public T getHxDot() {
if (hasNonKeplerianAcceleration()) {
final FieldPVCoordinates<FieldUnivariateDerivative2<T>> pv = getPVDerivatives();
final FieldVector3D<FieldUnivariateDerivative2<T>> w =
FieldVector3D.crossProduct(pv.getPosition(), pv.getVelocity()).normalize();
// Check for equatorial retrograde orbit
final double x = w.getX().getValue().getReal();
final double y = w.getY().getValue().getReal();
final double z = w.getZ().getValue().getReal();
if ((x * x + y * y) == 0 && z < 0) {
return getZero().add(Double.NaN);
}
final FieldUnivariateDerivative2<T> hx = w.getY().negate().divide(w.getZ().add(1));
return hx.getDerivative(1);
} else {
return getZero();
}
}
/** {@inheritDoc} */
public T getHy() {
final FieldVector3D<T> w = getPVCoordinates().getMomentum().normalize();
// Check for equatorial retrograde orbit
final double x = w.getX().getReal();
final double y = w.getY().getReal();
final double z = w.getZ().getReal();
if ((x * x + y * y) == 0 && z < 0) {
return getZero().add(Double.NaN);
}
return w.getX().divide(w.getZ().add(1));
}
/** {@inheritDoc} */
public T getHyDot() {
if (hasNonKeplerianAcceleration()) {
final FieldPVCoordinates<FieldUnivariateDerivative2<T>> pv = getPVDerivatives();
final FieldVector3D<FieldUnivariateDerivative2<T>> w =
FieldVector3D.crossProduct(pv.getPosition(), pv.getVelocity()).normalize();
// Check for equatorial retrograde orbit
final double x = w.getX().getValue().getReal();
final double y = w.getY().getValue().getReal();
final double z = w.getZ().getValue().getReal();
if ((x * x + y * y) == 0 && z < 0) {
return getZero().add(Double.NaN);
}
final FieldUnivariateDerivative2<T> hy = w.getX().divide(w.getZ().add(1));
return hy.getDerivative(1);
} else {
return getZero();
}
}
/** {@inheritDoc} */
public T getLv() {
initEquinoctial();
return equinoctial.getLv();
}
/** {@inheritDoc} */
public T getLvDot() {
initEquinoctial();
return equinoctial.getLvDot();
}
/** {@inheritDoc} */
public T getLE() {
initEquinoctial();
return equinoctial.getLE();
}
/** {@inheritDoc} */
public T getLEDot() {
initEquinoctial();
return equinoctial.getLEDot();
}
/** {@inheritDoc} */
public T getLM() {
initEquinoctial();
return equinoctial.getLM();
}
/** {@inheritDoc} */
public T getLMDot() {
initEquinoctial();
return equinoctial.getLMDot();
}
/** {@inheritDoc} */
@Override
public boolean hasNonKeplerianAcceleration() {
return hasNonKeplerianAcceleration;
}
/** {@inheritDoc} */
protected FieldVector3D<T> initPosition() {
// nothing to do here, as the canonical elements are already the Cartesian ones
return getPVCoordinates().getPosition();
}
/** {@inheritDoc} */
protected TimeStampedFieldPVCoordinates<T> initPVCoordinates() {
// nothing to do here, as the canonical elements are already the Cartesian ones
return getPVCoordinates();
}
/** {@inheritDoc} */
@Override
public FieldCartesianOrbit<T> withFrame(final Frame inertialFrame) {
if (hasNonKeplerianAcceleration()) {
return new FieldCartesianOrbit<>(getPVCoordinates(inertialFrame), inertialFrame, getMu());
} else {
final FieldKinematicTransform<T> transform = getFrame().getKinematicTransformTo(inertialFrame, getDate());
return new FieldCartesianOrbit<>(transform.transformOnlyPV(getPVCoordinates()), inertialFrame, getDate(), getMu());
}
}
/** {@inheritDoc} */
public FieldCartesianOrbit<T> shiftedBy(final double dt) {
return shiftedBy(getZero().newInstance(dt));
}
/** {@inheritDoc} */
public FieldCartesianOrbit<T> shiftedBy(final T dt) {
final FieldPVCoordinates<T> shiftedPV = shiftPV(dt);
return new FieldCartesianOrbit<>(shiftedPV, getFrame(), getDate().shiftedBy(dt), getMu());
}
/** Compute shifted position and velocity.
* @param dt time shift
* @return shifted position and velocity
*/
private FieldPVCoordinates<T> shiftPV(final T dt) {
final FieldPVCoordinates<T> pvCoordinates = getPVCoordinates();
final FieldPVCoordinates<T> shiftedPV = KeplerianMotionCartesianUtility.predictPositionVelocity(dt,
pvCoordinates.getPosition(), pvCoordinates.getVelocity(), getMu());
if (hasNonKeplerianAcceleration) {
final FieldVector3D<T> pvP = getPosition();
final T r2 = pvP.getNormSq();
final T r = r2.sqrt();
// extract non-Keplerian part of the initial acceleration
final FieldVector3D<T> nonKeplerianAcceleration = new FieldVector3D<>(getOne(), getPVCoordinates().getAcceleration(),
r.multiply(r2).reciprocal().multiply(getMu()), pvP);
// add the quadratic motion due to the non-Keplerian acceleration to the Keplerian motion
final FieldVector3D<T> shiftedP = shiftedPV.getPosition();
final FieldVector3D<T> shiftedV = shiftedPV.getVelocity();
final FieldVector3D<T> fixedP = new FieldVector3D<>(getOne(), shiftedP,
dt.square().multiply(0.5), nonKeplerianAcceleration);
final T fixedR2 = fixedP.getNormSq();
final T fixedR = fixedR2.sqrt();
final FieldVector3D<T> fixedV = new FieldVector3D<>(getOne(), shiftedV,
dt, nonKeplerianAcceleration);
final FieldVector3D<T> fixedA = new FieldVector3D<>(fixedR.multiply(fixedR2).reciprocal().multiply(getMu().negate()), shiftedP,
getOne(), nonKeplerianAcceleration);
return new FieldPVCoordinates<>(fixedP, fixedV, fixedA);
} else {
// don't include acceleration,
// so the shifted orbit is not considered to have derivatives
return shiftedPV;
}
}
/** Create a 6x6 identity matrix.
* @return 6x6 identity matrix
*/
private T[][] create6x6Identity() {
// this is the fastest way to set the 6x6 identity matrix
final T[][] identity = MathArrays.buildArray(getField(), 6, 6);
for (int i = 0; i < 6; i++) {
Arrays.fill(identity[i], getZero());
identity[i][i] = getOne();
}
return identity;
}
@Override
protected T[][] computeJacobianMeanWrtCartesian() {
return create6x6Identity();
}
@Override
protected T[][] computeJacobianEccentricWrtCartesian() {
return create6x6Identity();
}
@Override
protected T[][] computeJacobianTrueWrtCartesian() {
return create6x6Identity();
}
/** {@inheritDoc} */
public void addKeplerContribution(final PositionAngleType type, final T gm,
final T[] pDot) {
final FieldPVCoordinates<T> pv = getPVCoordinates();
// position derivative is velocity
final FieldVector3D<T> velocity = pv.getVelocity();
pDot[0] = pDot[0].add(velocity.getX());
pDot[1] = pDot[1].add(velocity.getY());
pDot[2] = pDot[2].add(velocity.getZ());
// velocity derivative is Newtonian acceleration
final FieldVector3D<T> position = pv.getPosition();
final T r2 = position.getNormSq();
final T coeff = r2.multiply(r2.sqrt()).reciprocal().negate().multiply(gm);
pDot[3] = pDot[3].add(coeff.multiply(position.getX()));
pDot[4] = pDot[4].add(coeff.multiply(position.getY()));
pDot[5] = pDot[5].add(coeff.multiply(position.getZ()));
}
/** Returns a string representation of this Orbit object.
* @return a string representation of this object
*/
public String toString() {
// use only the six defining elements, like the other Orbit.toString() methods
final String comma = ", ";
final PVCoordinates pv = getPVCoordinates().toPVCoordinates();
final Vector3D p = pv.getPosition();
final Vector3D v = pv.getVelocity();
return "Cartesian parameters: {P(" +
p.getX() + comma +
p.getY() + comma +
p.getZ() + "), V(" +
v.getX() + comma +
v.getY() + comma +
v.getZ() + ")}";
}
@Override
public CartesianOrbit toOrbit() {
final PVCoordinates pv = getPVCoordinates().toPVCoordinates();
final AbsoluteDate date = getPVCoordinates().getDate().toAbsoluteDate();
if (hasNonKeplerianAcceleration()) {
// getPVCoordinates includes accelerations that will be interpreted as derivatives
return new CartesianOrbit(pv, getFrame(), date, getMu().getReal());
} else {
// get rid of Keplerian acceleration so we don't assume
// we have derivatives when in fact we don't have them
return new CartesianOrbit(new PVCoordinates(pv.getPosition(), pv.getVelocity()),
getFrame(), date, getMu().getReal());
}
}
}