GLONASSNumericalPropagator.java
/* Copyright 2002-2020 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.propagation.numerical;
import java.util.Arrays;
import org.hipparchus.geometry.euclidean.threed.Vector3D;
import org.hipparchus.ode.ODEIntegrator;
import org.hipparchus.ode.nonstiff.ClassicalRungeKuttaIntegrator;
import org.hipparchus.util.FastMath;
import org.hipparchus.util.MathUtils;
import org.orekit.annotation.DefaultDataContext;
import org.orekit.attitudes.Attitude;
import org.orekit.attitudes.AttitudeProvider;
import org.orekit.data.DataContext;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.frames.Frame;
import org.orekit.gnss.GLONASSEphemeris;
import org.orekit.orbits.CartesianOrbit;
import org.orekit.orbits.Orbit;
import org.orekit.orbits.OrbitType;
import org.orekit.orbits.PositionAngle;
import org.orekit.propagation.PropagationType;
import org.orekit.propagation.Propagator;
import org.orekit.propagation.SpacecraftState;
import org.orekit.propagation.analytical.gnss.GLONASSOrbitalElements;
import org.orekit.propagation.integration.AbstractIntegratedPropagator;
import org.orekit.propagation.integration.StateMapper;
import org.orekit.time.AbsoluteDate;
import org.orekit.time.GLONASSDate;
import org.orekit.utils.AbsolutePVCoordinates;
import org.orekit.utils.Constants;
import org.orekit.utils.IERSConventions;
import org.orekit.utils.PVCoordinates;
import org.orekit.utils.TimeStampedPVCoordinates;
/**
* This class propagates GLONASS orbits using numerical integration.
* <p>
* As recommended by the GLONASS Interface Control Document (ICD),
* a {@link ClassicalRungeKuttaIntegrator 4th order Runge-Kutta technique}
* shall be used to integrate the equations.
* </p>
* <p>
* Classical used of this orbit propagator is to compute GLONASS satellite
* coordinates from the navigation message.
* </p>
* <p>
* If the projections of luni-solar accelerations to axes of
* Greenwich geocentric coordinates {@link GLONASSOrbitalElements#getXDotDot() X''(tb)},
* {@link GLONASSOrbitalElements#getYDotDot() Y''(tb)} and {@link GLONASSOrbitalElements#getZDotDot() Z''(tb)}
* are available in the navigation message; a transformation is performed to convert these
* accelerations into the correct coordinate system. In the case where they are not
* available into the navigation message, these accelerations are computed.
* </p>
*
* @see <a href="http://russianspacesystems.ru/wp-content/uploads/2016/08/ICD-GLONASS-CDMA-General.-Edition-1.0-2016.pdf">
* GLONASS Interface Control Document</a>
*
* @author Bryan Cazabonne
*
*/
public class GLONASSNumericalPropagator extends AbstractIntegratedPropagator {
/** Second degree coefficient of normal potential. */
private static final double GLONASS_J20 = 1.08262575e-3;
/** Equatorial radius of Earth (m). */
private static final double GLONASS_EARTH_EQUATORIAL_RADIUS = 6378136;
/** Value of the Earth's rotation rate in rad/s (See Ref). */
private static final double GLONASS_AV = 7.2921151467e-5;
// Data used to solve Kepler's equation
/** First coefficient to compute Kepler equation solver starter. */
private static final double A;
/** Second coefficient to compute Kepler equation solver starter. */
private static final double B;
static {
final double k1 = 3 * FastMath.PI + 2;
final double k2 = FastMath.PI - 1;
final double k3 = 6 * FastMath.PI - 1;
A = 3 * k2 * k2 / k1;
B = k3 * k3 / (6 * k1);
}
/** The GLONASS orbital elements used. */
private final GLONASSOrbitalElements glonassOrbit;
/** Initial date in GLONASS form. */
private final GLONASSDate initDate;
/** The spacecraft mass (kg). */
private final double mass;
/** The ECI frame used for GLONASS propagation. */
private final Frame eci;
/** Direction cosines and distance of perturbing body: Moon.
* <p>
* <ul>
* <li>double[0] = ξ<sub>m</sub></li>
* <li>double[1] = η<sub>m</sub></li>
* <li>double[2] = ψ<sub>m</sub></li>
* <li>double[3] = r<sub>m</sub></li>
* </ul>
* </p>
*/
private double[] moonElements;
/** Direction cosines and distance of perturbing body: Sun.
* <p>
* <ul>
* <li>double[0] = ξ<sub>s</sub></li>
* <li>double[1] = η<sub>s</sub></li>
* <li>double[2] = ψ<sub>s</sub></li>
* <li>double[3] = r<sub>s</sub></li>
* </ul>
* </p>
*/
private double[] sunElements;
/** Flag for availability of projections of acceleration transmitted within the navigation message. */
private final boolean isAccAvailable;
/** Data context used for propagation. */
private final DataContext dataContext;
/**
* This nested class aims at building a GLONASSNumericalPropagator.
* <p>It implements the classical builder pattern.</p>
*
*/
public static class Builder {
// Required parameter
/** The GLONASS orbital elements. */
private final GLONASSEphemeris orbit;
/** The 4th order Runge-Kutta integrator. */
private final ClassicalRungeKuttaIntegrator integrator;
/** Flag for availability of projections of acceleration transmitted within the navigation message. */
private final boolean isAccAvailable;
// Optional parameters
/** The attitude provider. */
private AttitudeProvider attitudeProvider;
/** The mass. */
private double mass = DEFAULT_MASS;
/** The ECI frame. */
private Frame eci = null;
/** Data context for the propagator. */
private DataContext dataContext;
/**
* Initializes the builder.
* <p>The attitude provider is set by default to the
* {@link org.orekit.propagation.Propagator#DEFAULT_LAW DEFAULT_LAW} in the
* default data context.<br>
* The mass is set by default to the
* {@link org.orekit.propagation.Propagator#DEFAULT_MASS DEFAULT_MASS}.<br>
* The data context is by default to the
* {@link DataContext#getDefault() default data context}.<br>
* The ECI frame is set by default to the
* {@link org.orekit.frames.Predefined#EME2000 EME2000 frame} in the default data
* context.<br>
* </p>
*
* @param integrator 4th order Runge-Kutta as recommended by GLONASS ICD
* @param glonassOrbElt the GLONASS orbital elements to be used by the GLONASSNumericalPropagator.
* @param isAccAvailable flag for availability of the projections of accelerations transmitted within
* the navigation message
* @see #attitudeProvider(AttitudeProvider provider)
* @see #mass(double mass)
* @see #eci(Frame inertial)
*/
@DefaultDataContext
public Builder(final ClassicalRungeKuttaIntegrator integrator,
final GLONASSEphemeris glonassOrbElt,
final boolean isAccAvailable) {
this.isAccAvailable = isAccAvailable;
this.integrator = integrator;
this.orbit = glonassOrbElt;
this.dataContext = DataContext.getDefault();
this.eci = dataContext.getFrames().getEME2000();
attitudeProvider = Propagator.getDefaultLaw(dataContext.getFrames());
}
/**
* Sets the attitude provider.
*
* @param userProvider the attitude provider
* @return the updated builder
*/
public Builder attitudeProvider(final AttitudeProvider userProvider) {
this.attitudeProvider = userProvider;
return this;
}
/**
* Sets the mass.
*
* @param userMass the mass (in kg)
* @return the updated builder
*/
public Builder mass(final double userMass) {
this.mass = userMass;
return this;
}
/**
* Sets the Earth Centered Inertial frame used for propagation.
*
* @param inertial the ECI frame
* @return the updated builder
*/
public Builder eci(final Frame inertial) {
this.eci = inertial;
return this;
}
/**
* Sets the data context used by the propagator. Does not update the ECI frame
* which must be done separately using {@link #eci(Frame)}.
*
* @param context used for propagation.
* @return the updated builder.
*/
public Builder dataContext(final DataContext context) {
this.dataContext = context;
return this;
}
/**
* Finalizes the build.
*
* @return the built GPSPropagator
*/
public GLONASSNumericalPropagator build() {
return new GLONASSNumericalPropagator(this);
}
}
/**
* Private constructor.
*
* @param builder the builder
*/
public GLONASSNumericalPropagator(final Builder builder) {
super(builder.integrator, PropagationType.MEAN);
this.dataContext = builder.dataContext;
this.isAccAvailable = builder.isAccAvailable;
// Stores the GLONASS orbital elements
this.glonassOrbit = builder.orbit;
// Sets the Earth Centered Inertial frame
this.eci = builder.eci;
// Sets the mass
this.mass = builder.mass;
this.initDate = new GLONASSDate(
glonassOrbit.getDate(),
dataContext.getTimeScales().getGLONASS());
// Initialize state mapper
initMapper();
setInitialState();
setAttitudeProvider(builder.attitudeProvider);
setOrbitType(OrbitType.CARTESIAN);
// It is not meaningful for propagation in Cartesian parameters
setPositionAngleType(PositionAngle.TRUE);
setMu(GLONASSOrbitalElements.GLONASS_MU);
// As recommended by GLONASS ICD (2016), the direction cosines and distance
// of perturbing body are calculated one time (at tb).
if (!isAccAvailable) {
computeMoonElements(initDate);
computeSunElements(initDate);
}
}
/**
* Gets the underlying GLONASS orbital elements.
*
* @return the underlying GLONASS orbital elements
*/
public GLONASSOrbitalElements getGLONASSOrbitalElements() {
return glonassOrbit;
}
/** {@inheritDoc} */
@Override
public SpacecraftState propagate(final AbsoluteDate date) {
// Spacecraft state in inertial frame
final SpacecraftState stateInInertial = super.propagate(date);
// Build the spacecraft state in inertial frame
final PVCoordinates pvInPZ90 = getPVInPZ90(stateInInertial);
final AbsolutePVCoordinates absPV = new AbsolutePVCoordinates(
dataContext.getFrames().getPZ9011(IERSConventions.IERS_2010, true),
stateInInertial.getDate(), pvInPZ90);
final TimeStampedPVCoordinates pvInInertial = absPV.getPVCoordinates(eci);
final SpacecraftState transformedState = new SpacecraftState(new CartesianOrbit(pvInInertial, eci, pvInInertial.getDate(), GLONASSOrbitalElements.GLONASS_MU),
stateInInertial.getAttitude(),
stateInInertial.getMass(), stateInInertial.getAdditionalStates());
return transformedState;
}
/**
* Set the initial state.
* <p>
* The initial conditions on position and velocity are in the ECEF coordinate system PZ-90.
* Previous to orbit integration, they must be transformed to an absolute inertial coordinate system.
* </p>
*/
private void setInitialState() {
// Transform initial PV coordinates to an absolute inertial coordinate system.
final PVCoordinates pvInInertial = getPVInInertial(initDate);
// Create a new orbit
final Orbit orbit = new CartesianOrbit(pvInInertial,
eci, initDate.getDate(),
GLONASSOrbitalElements.GLONASS_MU);
// Reset the initial state to apply the transformation
resetInitialState(new SpacecraftState(orbit, mass));
}
/**
* This method computes the direction cosines and the distance used to
* compute the gravitational perturbations of the Moon.
*
* @param date the computation date in GLONASS scale
*/
private void computeMoonElements(final GLONASSDate date) {
moonElements = new double[4];
// Constants
// Semi-major axis of the Moon's orbit (m)
final double am = 3.84385243e8;
// The Moon's orbit eccentricity
final double em = 0.054900489;
// Mean inclination of the Moon's orbit to the ecliptic (rad)
final double im = 0.0898041080;
// Computed parameters
// Time from epoch 2000 to the instant tb of GLONASS ephemeris broadcast
final double dtoJD = (glonassOrbit.getTime() - 10800.) / Constants.JULIAN_DAY;
final double t = (date.getJD0() + dtoJD - 2451545.0) / 36525;
final double t2 = t * t;
// Mean inclination of Earth equator to ecliptic (rad)
final double eps = 0.4090926006 - 0.0002270711 * t;
// Mean longitude of the Moon's orbit perigee (rad)
final double gammaM = 1.4547885346 + 71.0176852437 * t - 0.0001801481 * t2;
// Mean longitude of the ascending node of the Moon (rad)
final double omegaM = 2.1824391966 - 33.7570459536 * t + 0.0000362262 * t2;
// Mean anomaly of the Moon (rad)
final double qm = 2.3555557435 + 8328.6914257190 * t + 0.0001545547 * t2;
// Commons parameters
final double cosOm = FastMath.cos(omegaM);
final double sinOm = FastMath.sin(omegaM);
final double cosIm = FastMath.cos(im);
final double sinIm = FastMath.sin(im);
final double cosEs = FastMath.cos(eps);
final double sinEs = FastMath.sin(eps);
final double cosGm = FastMath.cos(gammaM);
final double sinGm = FastMath.sin(gammaM);
// Intermediate parameters
final double psiStar = cosOm * sinIm;
final double etaStar = sinOm * sinIm;
final double epsStar = 1. - cosOm * cosOm * (1. - cosIm);
final double eps11 = sinOm * cosOm * (1. - cosIm);
final double eps12 = 1. - sinOm * sinOm * (1. - cosIm);
final double eta11 = epsStar * cosEs - psiStar * sinEs;
final double eta12 = eps11 * cosEs + etaStar * sinEs;
final double psi11 = epsStar * sinEs + psiStar * cosEs;
final double psi12 = eps11 * sinEs - etaStar * cosEs;
// Eccentric Anomaly
final double ek = getEccentricAnomaly(qm, em);
// True Anomaly
final double vk = getTrueAnomaly(ek, em);
final double sinVk = FastMath.sin(vk);
final double cosVk = FastMath.cos(vk);
// Direction cosine
final double epsM = eps11 * (sinVk * cosGm + cosVk * sinGm) + eps12 * (cosVk * cosGm - sinVk * sinGm);
final double etaM = eta11 * (sinVk * cosGm + cosVk * sinGm) + eta12 * (cosVk * cosGm - sinVk * sinGm);
final double psiM = psi11 * (sinVk * cosGm + cosVk * sinGm) + psi12 * (cosVk * cosGm - sinVk * sinGm);
// Distance
final double rm = am * (1. - em * FastMath.cos(ek));
moonElements[0] = epsM;
moonElements[1] = etaM;
moonElements[2] = psiM;
moonElements[3] = rm;
}
/**
* This method computes the direction cosines and the distance used to
* compute the gravitational perturbations of the Sun.
*
* @param date the computation date in GLONASS scale
*/
private void computeSunElements(final GLONASSDate date) {
sunElements = new double[4];
// Constants
// Major semi-axis of the Earth’s orbit around the Sun (m)
final double as = 1.49598e11;
// The eccentricity of the Earth’s orbit around the Sun
final double es = 0.016719;
// Computed parameters
// Time from epoch 2000 to the instant tb of GLONASS ephemeris broadcast
final double dtoJD = (glonassOrbit.getTime() - 10800.) / Constants.JULIAN_DAY;
final double t = (date.getJD0() + dtoJD - 2451545.0) / 36525;
final double t2 = t * t;
// Mean inclination of Earth equator to ecliptic (rad)
final double eps = 0.4090926006 - 0.0002270711 * t;
// Mean tropic longitude of the Sun orbit perigee (rad)
final double ws = -7.6281824375 + 0.0300101976 * t + 0.0000079741 * t2;
// Mean anomaly of the Sun (rad)
final double qs = 6.2400601269 + 628.3019551714 * t - 0.0000026820 * t2;
// Eccentric Anomaly
final double ek = getEccentricAnomaly(qs, es);
// True Anomaly
final double vk = getTrueAnomaly(ek, es);
final double sinVk = FastMath.sin(vk);
final double cosVk = FastMath.cos(vk);
// Commons parameters
final double cosWs = FastMath.cos(ws);
final double sinWs = FastMath.sin(ws);
final double cosEs = FastMath.cos(eps);
final double sinEs = FastMath.sin(eps);
// Direction cosine
final double epsS = cosVk * cosWs - sinVk * sinWs;
final double etaS = cosEs * (sinVk * cosWs + cosVk * sinWs);
final double psiS = sinEs * (sinVk * cosWs + cosVk * sinWs);
// Distance
final double rs = as * (1. - es * FastMath.cos(ek));
sunElements[0] = epsS;
sunElements[1] = etaS;
sunElements[2] = psiS;
sunElements[3] = rs;
}
/**
* Computes the elliptic eccentric anomaly from the mean anomaly.
* <p>
* The algorithm used here for solving Kepler equation has been published
* in: "Procedures for solving Kepler's Equation", A. W. Odell and
* R. H. Gooding, Celestial Mechanics 38 (1986) 307-334
* </p>
* <p>It has been copied from the OREKIT library (KeplerianOrbit class).</p>
*
* @param M mean anomaly (rad)
* @param e eccentricity
* @return E the eccentric anomaly
*/
private double getEccentricAnomaly(final double M, final double e) {
// reduce M to [-PI PI) interval
final double reducedM = MathUtils.normalizeAngle(M, 0.0);
// compute start value according to A. W. Odell and R. H. Gooding S12 starter
double E;
if (FastMath.abs(reducedM) < 1.0 / 6.0) {
E = reducedM + e * (FastMath.cbrt(6 * reducedM) - reducedM);
} else {
if (reducedM < 0) {
final double w = FastMath.PI + reducedM;
E = reducedM + e * (A * w / (B - w) - FastMath.PI - reducedM);
} else {
final double w = FastMath.PI - reducedM;
E = reducedM + e * (FastMath.PI - A * w / (B - w) - reducedM);
}
}
final double e1 = 1 - e;
final boolean noCancellationRisk = (e1 + E * E / 6) >= 0.1;
// perform two iterations, each consisting of one Halley step and one Newton-Raphson step
for (int j = 0; j < 2; ++j) {
final double f;
double fd;
final double fdd = e * FastMath.sin(E);
final double fddd = e * FastMath.cos(E);
if (noCancellationRisk) {
f = (E - fdd) - reducedM;
fd = 1 - fddd;
} else {
f = eMeSinE(E, e) - reducedM;
final double s = FastMath.sin(0.5 * E);
fd = e1 + 2 * e * s * s;
}
final double dee = f * fd / (0.5 * f * fdd - fd * fd);
// update eccentric anomaly, using expressions that limit underflow problems
final double w = fd + 0.5 * dee * (fdd + dee * fddd / 3);
fd += dee * (fdd + 0.5 * dee * fddd);
E -= (f - dee * (fd - w)) / fd;
}
// expand the result back to original range
E += M - reducedM;
return E;
}
/**
* Accurate computation of E - e sin(E).
*
* @param E eccentric anomaly
* @param e eccentricity
* @return E - e sin(E)
*/
private static double eMeSinE(final double E, final double e) {
double x = (1 - e) * FastMath.sin(E);
final double mE2 = -E * E;
double term = E;
double d = 0;
// the inequality test below IS intentional and should NOT be replaced by a check with a small tolerance
for (double x0 = Double.NaN; !Double.valueOf(x).equals(Double.valueOf(x0));) {
d += 2;
term *= mE2 / (d * (d + 1));
x0 = x;
x = x - term;
}
return x;
}
/**
* Get true anomaly from eccentric anomaly and eccentricity.
*
* @param ek the eccentric anomaly (rad)
* @param ecc the eccentricity
* @return the true anomaly (rad)
*/
private double getTrueAnomaly(final double ek, final double ecc) {
final double svk = FastMath.sqrt(1. - ecc * ecc) * FastMath.sin(ek);
final double cvk = FastMath.cos(ek) - ecc;
return FastMath.atan2(svk, cvk);
}
/**
* This method transforms the PV coordinates obtained after the numerical
* integration in the ECEF PZ-90.
*
* @param state spacecraft state after integration
* @return the PV coordinates in the ECEF PZ-90.
*/
public PVCoordinates getPVInPZ90(final SpacecraftState state) {
// Compute time difference between start date and end date
final double dt = state.getDate().durationFrom(initDate.getDate());
// Position and velocity vectors
final PVCoordinates pv = state.getPVCoordinates();
final Vector3D pos = pv.getPosition();
final Vector3D vel = pv.getVelocity();
// Components of position and velocity vectors
final double x0 = pos.getX();
final double y0 = pos.getY();
final double z0 = pos.getZ();
final double vx0 = vel.getX();
final double vy0 = vel.getY();
final double vz0 = vel.getZ();
// Greenwich Mean Sidereal Time (GMST)
final GLONASSDate gloDate = new GLONASSDate(
state.getDate(),
dataContext.getTimeScales().getGLONASS());
final double gmst = gloDate.getGMST();
final double ti = glonassOrbit.getTime() + dt;
// We use the GMST instead of the GMT as it is recommended into GLONASS ICD (2016)
final double s = gmst + GLONASS_AV * (ti - 10800.);
// Commons Parameters
final double cosS = FastMath.cos(s);
final double sinS = FastMath.sin(s);
// Transformed coordinates
final double x = x0 * cosS + y0 * sinS;
final double y = -x0 * sinS + y0 * cosS;
final double z = z0;
final double vx = vx0 * cosS + vy0 * sinS + GLONASS_AV * y;
final double vy = -vx0 * sinS + vy0 * cosS - GLONASS_AV * x;
final double vz = vz0;
// Transformed orbit
return new PVCoordinates(new Vector3D(x, y, z),
new Vector3D(vx, vy, vz));
}
/**
* This method computes the PV coordinates of the spacecraft center of mass.
* The returned PV are expressed in inertial coordinates system at the instant tb.
*
* @param date the computation date in GLONASS scale
* @return the PV Coordinates in inertial coordinates system
*/
private PVCoordinates getPVInInertial(final GLONASSDate date) {
// Greenwich Mean Sidereal Time (GMST)
final double gmst = date.getGMST();
final double time = glonassOrbit.getTime();
final double dt = time - 10800.;
// We use the GMST instead of the GMT as it is recommended into GLONASS ICD (2016)
final double s = gmst + GLONASS_AV * dt;
// Commons Parameters
final double cosS = FastMath.cos(s);
final double sinS = FastMath.sin(s);
// PV coordinates in inertial frame
final double x0 = glonassOrbit.getX() * cosS - glonassOrbit.getY() * sinS;
final double y0 = glonassOrbit.getX() * sinS + glonassOrbit.getY() * cosS;
final double z0 = glonassOrbit.getZ();
final double vx0 = glonassOrbit.getXDot() * cosS - glonassOrbit.getYDot() * sinS - GLONASS_AV * y0;
final double vy0 = glonassOrbit.getXDot() * sinS + glonassOrbit.getYDot() * cosS + GLONASS_AV * x0;
final double vz0 = glonassOrbit.getZDot();
return new PVCoordinates(new Vector3D(x0, y0, z0),
new Vector3D(vx0, vy0, vz0));
}
@Override
protected StateMapper createMapper(final AbsoluteDate referenceDate, final double mu,
final OrbitType orbitType, final PositionAngle positionAngleType,
final AttitudeProvider attitudeProvider, final Frame frame) {
return new Mapper(referenceDate, mu, orbitType, positionAngleType, attitudeProvider, frame);
}
/** Internal mapper. */
private static class Mapper extends StateMapper {
/**
* Simple constructor.
*
* @param referenceDate reference date
* @param mu central attraction coefficient (m³/s²)
* @param orbitType orbit type to use for mapping
* @param positionAngleType angle type to use for propagation
* @param attitudeProvider attitude provider
* @param frame inertial frame
*/
Mapper(final AbsoluteDate referenceDate, final double mu,
final OrbitType orbitType, final PositionAngle positionAngleType,
final AttitudeProvider attitudeProvider, final Frame frame) {
super(referenceDate, mu, orbitType, positionAngleType, attitudeProvider, frame);
}
@Override
public SpacecraftState mapArrayToState(final AbsoluteDate date, final double[] y,
final double[] yDot, final PropagationType type) {
// The parameter meanOnly is ignored for the GLONASS Propagator
final double mass = y[6];
if (mass <= 0.0) {
throw new OrekitException(OrekitMessages.SPACECRAFT_MASS_BECOMES_NEGATIVE, mass);
}
final Orbit orbit = getOrbitType().mapArrayToOrbit(y, yDot, getPositionAngleType(), date, getMu(), getFrame());
final Attitude attitude = getAttitudeProvider().getAttitude(orbit, date, getFrame());
return new SpacecraftState(orbit, attitude, mass);
}
@Override
public void mapStateToArray(final SpacecraftState state, final double[] y,
final double[] yDot) {
getOrbitType().mapOrbitToArray(state.getOrbit(), getPositionAngleType(), y, yDot);
y[6] = state.getMass();
}
}
@Override
protected MainStateEquations getMainStateEquations(final ODEIntegrator integ) {
return new Main();
}
/** Internal class for orbital parameters integration. */
private class Main implements MainStateEquations {
/** Derivatives array. */
private final double[] yDot;
/**
* Simple constructor.
*/
Main() {
yDot = new double[7];
}
@Override
public double[] computeDerivatives(final SpacecraftState state) {
// Date in Glonass form
final GLONASSDate gloDate = new GLONASSDate(
state.getDate(),
dataContext.getTimeScales().getGLONASS());
// Position and Velocity vectors
final Vector3D vel = state.getPVCoordinates().getVelocity();
final Vector3D pos = state.getPVCoordinates().getPosition();
Arrays.fill(yDot, 0.0);
// dPos/dt = Vel
yDot[0] += vel.getX();
yDot[1] += vel.getY();
yDot[2] += vel.getZ();
// Components of position and velocity vectors
final double x0 = pos.getX();
final double y0 = pos.getY();
final double z0 = pos.getZ();
// Normalized values
final double r = pos.getNorm();
final double r2 = r * r;
final double oor = 1. / r;
final double oor2 = 1. / r2;
final double x = x0 * oor;
final double y = y0 * oor;
final double z = z0 * oor;
final double g = GLONASSOrbitalElements.GLONASS_MU * oor2;
final double ro = GLONASS_EARTH_EQUATORIAL_RADIUS * oor;
yDot[3] += x * (-g + (-1.5 * GLONASS_J20 * g * ro * ro * (1. - 5. * z * z)));
yDot[4] += y * (-g + (-1.5 * GLONASS_J20 * g * ro * ro * (1. - 5. * z * z)));
yDot[5] += z * (-g + (-1.5 * GLONASS_J20 * g * ro * ro * (3. - 5. * z * z)));
// Luni-Solar contribution
final Vector3D acc;
if (isAccAvailable) {
acc = getLuniSolarPerturbations(gloDate);
} else {
final Vector3D accMoon = computeLuniSolarPerturbations(
state, moonElements[0], moonElements[1], moonElements[2],
moonElements[3],
dataContext.getCelestialBodies().getMoon().getGM());
final Vector3D accSun = computeLuniSolarPerturbations(
state,
sunElements[0], sunElements[1], sunElements[2],
sunElements[3],
dataContext.getCelestialBodies().getSun().getGM());
acc = accMoon.add(accSun);
}
yDot[3] += acc.getX();
yDot[4] += acc.getY();
yDot[5] += acc.getZ();
return yDot.clone();
}
/**
* This method computes the accelerations induced by gravitational
* perturbations of the Sun and the Moon if they are not available into
* the navigation message data.
*
* @param state current state
* @param eps first direction cosine
* @param eta second direction cosine
* @param psi third direction cosine
* @param r distance of perturbing body
* @param g body gravitational field constant
* @return a vector containing the accelerations
*/
private Vector3D computeLuniSolarPerturbations(final SpacecraftState state, final double eps,
final double eta, final double psi,
final double r, final double g) {
// Current pv coordinates
final PVCoordinates pv = state.getPVCoordinates();
final double oor = 1. / r;
final double oor2 = oor * oor;
// Normalized variable
final double x = pv.getPosition().getX() * oor;
final double y = pv.getPosition().getY() * oor;
final double z = pv.getPosition().getZ() * oor;
final double gm = g * oor2;
final double epsmX = eps - x;
final double etamY = eta - y;
final double psimZ = psi - z;
final Vector3D vector = new Vector3D(epsmX, etamY, psimZ);
final double d2 = vector.getNormSq();
final double deltaM = FastMath.sqrt(d2) * d2;
// Accelerations
final double accX = gm * ((epsmX / deltaM) - eps);
final double accY = gm * ((etamY / deltaM) - eta);
final double accZ = gm * ((psimZ / deltaM) - psi);
return new Vector3D(accX, accY, accZ);
}
/**
* Get the accelerations induced by gravitational
* perturbations of the Sun and the Moon in a geocentric
* coordinate system.
* <p>
* The accelerations are obtained using projections of accelerations
* transmitted within navigation message data.
* </p>
*
* @param date the computation date in GLONASS scale
* @return a vector containing the sum of both accelerations
*/
private Vector3D getLuniSolarPerturbations(final GLONASSDate date) {
// Greenwich Mean Sidereal Time (GMST)
final double gmst = date.getGMST();
final double time = glonassOrbit.getTime();
final double dt = time - 10800.;
// We use the GMST instead of the GMT as it is recommended into GLONASS ICD (see Ref)
final double s = gmst + GLONASS_AV * dt;
// Commons Parameters
final double cosS = FastMath.cos(s);
final double sinS = FastMath.sin(s);
// Accelerations
final double accX = glonassOrbit.getXDotDot() * cosS - glonassOrbit.getYDotDot() * sinS;
final double accY = glonassOrbit.getXDotDot() * sinS + glonassOrbit.getYDotDot() * cosS;
final double accZ = glonassOrbit.getZDotDot();
return new Vector3D(accX, accY, accZ);
}
}
}