FixedPointTleGenerationAlgorithm.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.propagation.analytical.tle.generation;
import org.hipparchus.CalculusFieldElement;
import org.hipparchus.Field;
import org.hipparchus.geometry.euclidean.threed.Rotation;
import org.hipparchus.util.FastMath;
import org.hipparchus.util.MathUtils;
import org.orekit.annotation.DefaultDataContext;
import org.orekit.attitudes.FrameAlignedProvider;
import org.orekit.data.DataContext;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.frames.Frame;
import org.orekit.orbits.EquinoctialOrbit;
import org.orekit.orbits.FieldEquinoctialOrbit;
import org.orekit.orbits.FieldKeplerianOrbit;
import org.orekit.orbits.FieldOrbit;
import org.orekit.orbits.KeplerianOrbit;
import org.orekit.orbits.Orbit;
import org.orekit.orbits.OrbitType;
import org.orekit.orbits.PositionAngleType;
import org.orekit.propagation.FieldSpacecraftState;
import org.orekit.propagation.SpacecraftState;
import org.orekit.propagation.analytical.tle.FieldTLE;
import org.orekit.propagation.analytical.tle.FieldTLEPropagator;
import org.orekit.propagation.analytical.tle.TLE;
import org.orekit.propagation.analytical.tle.TLEConstants;
import org.orekit.propagation.analytical.tle.TLEPropagator;
import org.orekit.time.AbsoluteDate;
import org.orekit.time.TimeScale;
import org.orekit.utils.ParameterDriver;
/**
* Fixed Point method to reverse SGP4 and SDP4 propagation algorithm
* and generate a usable TLE from a spacecraft state.
* <p>
* Using this algorithm, the B* value is not computed. In other words,
* the B* value from the template TLE is set to the generated one.
* </p>
* @author Thomas Paulet
* @author Bryan Cazabonne
* @since 12.0
*/
public class FixedPointTleGenerationAlgorithm implements TleGenerationAlgorithm {
/** Default value for epsilon. */
public static final double EPSILON_DEFAULT = 1.0e-10;
/** Default value for maxIterations. */
public static final int MAX_ITERATIONS_DEFAULT = 100;
/** Default value for scale. */
public static final double SCALE_DEFAULT = 1.0;
/** Used to compute threshold for convergence check. */
private final double epsilon;
/** Maximum number of iterations for convergence. */
private final int maxIterations;
/** Scale factor of the Fixed Point algorithm. */
private final double scale;
/** UTC scale. */
private final TimeScale utc;
/** TEME frame. */
private final Frame teme;
/**
* Default constructor.
* <p>
* Uses the {@link DataContext#getDefault() default data context}
* as well as {@link #EPSILON_DEFAULT}, {@link #MAX_ITERATIONS_DEFAULT},
* {@link #SCALE_DEFAULT} for method convergence.
* </p>
*/
@DefaultDataContext
public FixedPointTleGenerationAlgorithm() {
this(EPSILON_DEFAULT, MAX_ITERATIONS_DEFAULT, SCALE_DEFAULT);
}
/**
* Constructor.
* <p>
* Uses the {@link DataContext#getDefault() default data context}.
* </p>
* @param epsilon used to compute threshold for convergence check
* @param maxIterations maximum number of iterations for convergence
* @param scale scale factor of the Fixed Point algorithm
*/
@DefaultDataContext
public FixedPointTleGenerationAlgorithm(final double epsilon, final int maxIterations,
final double scale) {
this(epsilon, maxIterations, scale,
DataContext.getDefault().getTimeScales().getUTC(),
DataContext.getDefault().getFrames().getTEME());
}
/**
* Constructor.
* @param epsilon used to compute threshold for convergence check
* @param maxIterations maximum number of iterations for convergence
* @param scale scale factor of the Fixed Point algorithm
* @param utc UTC time scale
* @param teme TEME frame
*/
public FixedPointTleGenerationAlgorithm(final double epsilon, final int maxIterations,
final double scale, final TimeScale utc,
final Frame teme) {
this.epsilon = epsilon;
this.maxIterations = maxIterations;
this.scale = scale;
this.utc = utc;
this.teme = teme;
}
/** {@inheritDoc} */
@Override
public TLE generate(final SpacecraftState state, final TLE templateTLE) {
// Generation epoch
final AbsoluteDate epoch = state.getDate();
// gets equinoctial parameters in TEME frame and with TLE gravity parameter from state
final EquinoctialOrbit equinoctialOrbit = convert(state.getOrbit());
double sma = equinoctialOrbit.getA();
double ex = equinoctialOrbit.getEquinoctialEx();
double ey = equinoctialOrbit.getEquinoctialEy();
double hx = equinoctialOrbit.getHx();
double hy = equinoctialOrbit.getHy();
double lv = equinoctialOrbit.getLv();
// rough initialization of the TLE
final KeplerianOrbit keplerianOrbit = (KeplerianOrbit) OrbitType.KEPLERIAN.convertType(equinoctialOrbit);
TLE current = TleGenerationUtil.newTLE(keplerianOrbit, templateTLE, templateTLE.getBStar(epoch), utc);
// threshold for each parameter
final double thrA = epsilon * (1 + sma);
final double thrE = epsilon * (1 + FastMath.hypot(ex, ey));
final double thrH = epsilon * (1 + FastMath.hypot(hx, hy));
final double thrV = epsilon * FastMath.PI;
int k = 0;
while (k++ < maxIterations) {
// recompute the state from the current TLE
final TLEPropagator propagator = TLEPropagator.selectExtrapolator(current,
new FrameAlignedProvider(Rotation.IDENTITY, teme),
state.getMass(), teme);
final Orbit recoveredOrbit = propagator.getInitialState().getOrbit();
final EquinoctialOrbit recoveredEquiOrbit = (EquinoctialOrbit) OrbitType.EQUINOCTIAL.convertType(recoveredOrbit);
// adapted parameters residuals
final double deltaSma = equinoctialOrbit.getA() - recoveredEquiOrbit.getA();
final double deltaEx = equinoctialOrbit.getEquinoctialEx() - recoveredEquiOrbit.getEquinoctialEx();
final double deltaEy = equinoctialOrbit.getEquinoctialEy() - recoveredEquiOrbit.getEquinoctialEy();
final double deltaHx = equinoctialOrbit.getHx() - recoveredEquiOrbit.getHx();
final double deltaHy = equinoctialOrbit.getHy() - recoveredEquiOrbit.getHy();
final double deltaLv = MathUtils.normalizeAngle(equinoctialOrbit.getLv() - recoveredEquiOrbit.getLv(), 0.0);
// check convergence
if (FastMath.abs(deltaSma) < thrA &&
FastMath.abs(deltaEx) < thrE &&
FastMath.abs(deltaEy) < thrE &&
FastMath.abs(deltaHx) < thrH &&
FastMath.abs(deltaHy) < thrH &&
FastMath.abs(deltaLv) < thrV) {
// verify if parameters are estimated
for (final ParameterDriver templateDrivers : templateTLE.getParametersDrivers()) {
if (templateDrivers.isSelected()) {
// set to selected for the new TLE
current.getParameterDriver(templateDrivers.getName()).setSelected(true);
}
}
// return
return current;
}
// update state
sma += scale * deltaSma;
ex += scale * deltaEx;
ey += scale * deltaEy;
hx += scale * deltaHx;
hy += scale * deltaHy;
lv += scale * deltaLv;
final EquinoctialOrbit newEquinoctialOrbit =
new EquinoctialOrbit(sma, ex, ey, hx, hy, lv, PositionAngleType.TRUE,
equinoctialOrbit.getFrame(), equinoctialOrbit.getDate(), equinoctialOrbit.getMu());
final KeplerianOrbit newKeplerianOrbit = (KeplerianOrbit) OrbitType.KEPLERIAN.convertType(newEquinoctialOrbit);
// update TLE
current = TleGenerationUtil.newTLE(newKeplerianOrbit, templateTLE, templateTLE.getBStar(epoch), utc);
}
// unable to generate a TLE
throw new OrekitException(OrekitMessages.UNABLE_TO_COMPUTE_TLE, k);
}
/** {@inheritDoc} */
@Override
public <T extends CalculusFieldElement<T>> FieldTLE<T> generate(final FieldSpacecraftState<T> state,
final FieldTLE<T> templateTLE) {
// gets equinoctial parameters in TEME frame and with TLE gravity parameter from state
final FieldEquinoctialOrbit<T> equinoctialOrbit = convert(state.getOrbit());
T sma = equinoctialOrbit.getA();
T ex = equinoctialOrbit.getEquinoctialEx();
T ey = equinoctialOrbit.getEquinoctialEy();
T hx = equinoctialOrbit.getHx();
T hy = equinoctialOrbit.getHy();
T lv = equinoctialOrbit.getLv();
// rough initialization of the TLE
final T bStar = state.getA().getField().getZero().newInstance(templateTLE.getBStar());
final FieldKeplerianOrbit<T> keplerianOrbit = (FieldKeplerianOrbit<T>) OrbitType.KEPLERIAN.convertType(equinoctialOrbit);
FieldTLE<T> current = TleGenerationUtil.newTLE(keplerianOrbit, templateTLE, bStar, utc);
// field
final Field<T> field = state.getDate().getField();
// threshold for each parameter
final T thrA = sma.add(1).multiply(epsilon);
final T thrE = FastMath.hypot(ex, ey).add(1).multiply(epsilon);
final T thrH = FastMath.hypot(hx, hy).add(1).multiply(epsilon);
final T thrV = sma.getPi().multiply(epsilon);
int k = 0;
while (k++ < maxIterations) {
// recompute the state from the current TLE
final FieldTLEPropagator<T> propagator = FieldTLEPropagator.selectExtrapolator(current, new FrameAlignedProvider(Rotation.IDENTITY, teme),
state.getMass(), teme, templateTLE.getParameters(field));
final FieldOrbit<T> recoveredOrbit = propagator.getInitialState().getOrbit();
final FieldEquinoctialOrbit<T> recoveredEquinoctialOrbit = (FieldEquinoctialOrbit<T>) OrbitType.EQUINOCTIAL.convertType(recoveredOrbit);
// adapted parameters residuals
final T deltaSma = equinoctialOrbit.getA().subtract(recoveredEquinoctialOrbit.getA());
final T deltaEx = equinoctialOrbit.getEquinoctialEx().subtract(recoveredEquinoctialOrbit.getEquinoctialEx());
final T deltaEy = equinoctialOrbit.getEquinoctialEy().subtract(recoveredEquinoctialOrbit.getEquinoctialEy());
final T deltaHx = equinoctialOrbit.getHx().subtract(recoveredEquinoctialOrbit.getHx());
final T deltaHy = equinoctialOrbit.getHy().subtract(recoveredEquinoctialOrbit.getHy());
final T deltaLv = MathUtils.normalizeAngle(equinoctialOrbit.getLv().subtract(recoveredEquinoctialOrbit.getLv()), field.getZero());
// check convergence
if (FastMath.abs(deltaSma.getReal()) < thrA.getReal() &&
FastMath.abs(deltaEx.getReal()) < thrE.getReal() &&
FastMath.abs(deltaEy.getReal()) < thrE.getReal() &&
FastMath.abs(deltaHx.getReal()) < thrH.getReal() &&
FastMath.abs(deltaHy.getReal()) < thrH.getReal() &&
FastMath.abs(deltaLv.getReal()) < thrV.getReal()) {
// return
return current;
}
// update state
sma = sma.add(deltaSma.multiply(scale));
ex = ex.add(deltaEx.multiply(scale));
ey = ey.add(deltaEy.multiply(scale));
hx = hx.add(deltaHx.multiply(scale));
hy = hy.add(deltaHy.multiply(scale));
lv = lv.add(deltaLv.multiply(scale));
final FieldEquinoctialOrbit<T> newEquinoctialOrbit =
new FieldEquinoctialOrbit<>(sma, ex, ey, hx, hy, lv, PositionAngleType.TRUE,
equinoctialOrbit.getFrame(), equinoctialOrbit.getDate(), equinoctialOrbit.getMu());
final FieldKeplerianOrbit<T> newKeplerianOrbit = (FieldKeplerianOrbit<T>) OrbitType.KEPLERIAN.convertType(newEquinoctialOrbit);
// update TLE
current = TleGenerationUtil.newTLE(newKeplerianOrbit, templateTLE, bStar, utc);
}
throw new OrekitException(OrekitMessages.UNABLE_TO_COMPUTE_TLE, k);
}
/**
* Converts an orbit into an equinoctial orbit expressed in TEME frame with the TLE gravity parameter.
* @param orbitIn the orbit to convert
* @return the converted orbit, i.e. equinoctial in TEME frame
*/
private EquinoctialOrbit convert(final Orbit orbitIn) {
return new EquinoctialOrbit(orbitIn.getPVCoordinates(teme), teme, TLEConstants.MU);
}
/**
* Converts an orbit into an equinoctial orbit expressed in TEME frame with the TLE gravity parameter.
* @param orbitIn the orbit to convert
* @param <T> type of the element
* @return the converted orbit, i.e. equinoctial in TEME frame
*/
private <T extends CalculusFieldElement<T>> FieldEquinoctialOrbit<T> convert(final FieldOrbit<T> orbitIn) {
return new FieldEquinoctialOrbit<T>(orbitIn.getPVCoordinates(teme), teme, orbitIn.getMu().newInstance(TLEConstants.MU));
}
}