diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 8d99e023887b6cb138aa78cb06b880624a32e9c8..de8b9bdc77a2bc000f4b5162b4a9824a3ceac24a 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -21,6 +21,9 @@ </properties> <body> <release version="11.0" date="TBD" description="TBD"> + <action dev="thomas" type="fix" issue="702"> + Added possibility to take in account several bodies while computing SRP perturbation. + </action> <action dev="bryan" type="update" issue="793"> Updated SP3File visibility to public. </action> diff --git a/src/main/java/org/orekit/forces/radiation/AbstractRadiationForceModel.java b/src/main/java/org/orekit/forces/radiation/AbstractRadiationForceModel.java index 52775f4d47def0aaa0672e23c02fbadf314a88c9..aab9a7b1efad77423cca300224695ebeaf2898f5 100644 --- a/src/main/java/org/orekit/forces/radiation/AbstractRadiationForceModel.java +++ b/src/main/java/org/orekit/forces/radiation/AbstractRadiationForceModel.java @@ -16,6 +16,9 @@ */ package org.orekit.forces.radiation; +import java.lang.reflect.Array; +import java.util.HashMap; +import java.util.Map; import java.util.stream.Stream; import org.hipparchus.Field; @@ -56,14 +59,19 @@ public abstract class AbstractRadiationForceModel extends AbstractForceModel { /** Sun model. */ private final ExtendedPVCoordinatesProvider sun; + /** Other occulting bodies to consider. The Moon for instance. */ + private final Map<ExtendedPVCoordinatesProvider, Double> otherOccultingBodies; + /** - * Constructor. + * Default constructor. + * Only central body is considered. * @param sun Sun model - * @param equatorialRadius spherical shape model (for umbra/penumbra computation) + * @param equatorialRadius central body spherical shape model (for umbra/penumbra computation) */ protected AbstractRadiationForceModel(final ExtendedPVCoordinatesProvider sun, final double equatorialRadius) { - this.sun = sun; - this.equatorialRadius = equatorialRadius; + this.sun = sun; + this.equatorialRadius = equatorialRadius; + this.otherOccultingBodies = new HashMap<>(); } /** {@inheritDoc} */ @@ -75,13 +83,34 @@ public abstract class AbstractRadiationForceModel extends AbstractForceModel { /** {@inheritDoc} */ @Override public Stream<EventDetector> getEventsDetectors() { - return Stream.of(new UmbraDetector(), new PenumbraDetector()); + final EventDetector[] detectors = new EventDetector[2 + 2 * otherOccultingBodies.size()]; + detectors[0] = new UmbraDetector(); + detectors[1] = new PenumbraDetector(); + int i = 2; + for (Map.Entry<ExtendedPVCoordinatesProvider, Double> entry : otherOccultingBodies.entrySet()) { + detectors[i] = new GeneralUmbraDetector(entry.getKey(), entry.getValue()); + detectors[i + 1] = new GeneralPenumbraDetector(entry.getKey(), entry.getValue()); + i = i + 2; + } + return Stream.of(detectors); } /** {@inheritDoc} */ @Override public <T extends CalculusFieldElement<T>> Stream<FieldEventDetector<T>> getFieldEventsDetectors(final Field<T> field) { - return Stream.of(new FieldUmbraDetector<>(field), new FieldPenumbraDetector<>(field)); + final T zero = field.getZero(); + @SuppressWarnings("unchecked") + final FieldEventDetector<T>[] detectors = (FieldEventDetector<T>[]) Array.newInstance(FieldEventDetector.class, + 2 + 2 * otherOccultingBodies.size()); + detectors[0] = new FieldUmbraDetector<>(field); + detectors[1] = new FieldPenumbraDetector<>(field); + int i = 2; + for (Map.Entry<ExtendedPVCoordinatesProvider, Double> entry : otherOccultingBodies.entrySet()) { + detectors[i] = new FieldGeneralUmbraDetector<>(field, entry.getKey(), zero.newInstance(entry.getValue())); + detectors[i + 1] = new FieldGeneralPenumbraDetector<>(field, entry.getKey(), zero.newInstance(entry.getValue())); + i = i + 2; + } + return Stream.of(detectors); } /** @@ -111,6 +140,35 @@ public abstract class AbstractRadiationForceModel extends AbstractForceModel { return angle; } + + /** + * Get the useful angles for eclipse computation. + * @param position the satellite's position in the selected frame + * @param occultingPosition Oculting body position in the selected frame + * @param occultingRadius Occulting body mean radius + * @param occultedPosition Occulted body position in the selected frame + * @param occultedRadius Occulted body mean radius + * @return the 3 angles {(satOcculting, satOcculted), Occulting body apparent radius, Occulted body apparent radius} + */ + protected double[] getGeneralEclipseAngles(final Vector3D position, final Vector3D occultingPosition, final double occultingRadius, + final Vector3D occultedPosition, final double occultedRadius) { + final double[] angle = new double[3]; + + final Vector3D satOccultedVector = occultedPosition.subtract(position); + final Vector3D satOccultingVector = occultingPosition.subtract(position); + + // Sat-Occulted / Sat-Occulting angle + angle[0] = Vector3D.angle(satOccultedVector, satOccultingVector); + + // Occulting body apparent radius + angle[1] = FastMath.asin(occultingRadius / satOccultingVector.getNorm()); + + // Occulted body apparent radius + angle[2] = FastMath.asin(occultedRadius / satOccultedVector.getNorm()); + + return angle; + } + /** * Get the useful angles for eclipse computation. * @param sunPosition Sun position in the selected frame @@ -140,6 +198,62 @@ public abstract class AbstractRadiationForceModel extends AbstractForceModel { return angle; } + /** + * Get the useful angles for eclipse computation. + * @param occultingPosition Oculting body position in the selected frame + * @param occultingRadius Occulting body mean radius + * @param occultedPosition Occulted body position in the selected frame + * @param occultedRadius Occulted body mean radius + * @param position the satellite's position in the selected frame + * @param <T> extends RealFieldElement + * @return the 3 angles {(satOcculting, satOcculted), Occulting body apparent radius, Occulted body apparent radius} + */ + protected <T extends CalculusFieldElement<T>> T[] getGeneralEclipseAngles(final FieldVector3D<T> position, + final FieldVector3D<T> occultingPosition, final T occultingRadius, + final FieldVector3D<T> occultedPosition, final T occultedRadius) { + final T[] angle = MathArrays.buildArray(position.getX().getField(), 3); + + final FieldVector3D<T> satOccultedVector = occultedPosition.subtract(position); + final FieldVector3D<T> satOccultingVector = occultingPosition.subtract(position); + + // Sat-Occulted / Sat-Occulting angle + angle[0] = FieldVector3D.angle(satOccultedVector, satOccultingVector); + + // Occulting body apparent radius + angle[1] = satOccultingVector.getNorm().reciprocal().multiply(occultingRadius).asin(); + + // Occulted body apparent radius + angle[2] = satOccultedVector.getNorm().reciprocal().multiply(occultedRadius).asin(); + + return angle; + } + + /** + * Add a new occulting body. + * Central body is already considered, it shall not be added this way. + * @param provider body PV provider + * @param radius body mean radius + */ + public void addOccultingBody(final ExtendedPVCoordinatesProvider provider, final double radius) { + otherOccultingBodies.put(provider, radius); + } + + /** + * Getter for other occulting bodies to consider. + * @return the map of other occulting bodies and corresponding mean radiuses + */ + public Map<ExtendedPVCoordinatesProvider, Double> getOtherOccultingBodies() { + return otherOccultingBodies; + } + + /** + * Getter for equatorial radius. + * @return central body equatorial radius + */ + public double getEquatorialRadius() { + return equatorialRadius; + } + /** This class defines the umbra entry/exit detector. */ private class UmbraDetector extends AbstractDetector<UmbraDetector> { @@ -169,15 +283,15 @@ public abstract class AbstractRadiationForceModel extends AbstractForceModel { * @param handler event handler to call at event occurrences * @since 6.1 */ - private UmbraDetector(final double maxCheck, final double threshold, - final int maxIter, final EventHandler<? super UmbraDetector> handler) { + private UmbraDetector(final double maxCheck, final double threshold, final int maxIter, + final EventHandler<? super UmbraDetector> handler) { super(maxCheck, threshold, maxIter, handler); } /** {@inheritDoc} */ @Override - protected UmbraDetector create(final double newMaxCheck, final double newThreshold, - final int newMaxIter, final EventHandler<? super UmbraDetector> newHandler) { + protected UmbraDetector create(final double newMaxCheck, final double newThreshold, final int newMaxIter, + final EventHandler<? super UmbraDetector> newHandler) { return new UmbraDetector(newMaxCheck, newThreshold, newMaxIter, newHandler); } @@ -222,15 +336,15 @@ public abstract class AbstractRadiationForceModel extends AbstractForceModel { * @param handler event handler to call at event occurrences * @since 6.1 */ - private PenumbraDetector(final double maxCheck, final double threshold, - final int maxIter, final EventHandler<? super PenumbraDetector> handler) { + private PenumbraDetector(final double maxCheck, final double threshold, final int maxIter, + final EventHandler<? super PenumbraDetector> handler) { super(maxCheck, threshold, maxIter, handler); } /** {@inheritDoc} */ @Override - protected PenumbraDetector create(final double newMaxCheck, final double newThreshold, - final int newMaxIter, final EventHandler<? super PenumbraDetector> newHandler) { + protected PenumbraDetector create(final double newMaxCheck, final double newThreshold, final int newMaxIter, + final EventHandler<? super PenumbraDetector> newHandler) { return new PenumbraDetector(newMaxCheck, newThreshold, newMaxIter, newHandler); } @@ -279,16 +393,14 @@ public abstract class AbstractRadiationForceModel extends AbstractForceModel { * @param maxIter maximum number of iterations in the event time search * @param handler event handler to call at event occurrences */ - private FieldUmbraDetector(final T maxCheck, final T threshold, - final int maxIter, + private FieldUmbraDetector(final T maxCheck, final T threshold, final int maxIter, final FieldEventHandler<? super FieldUmbraDetector<T>, T> handler) { super(maxCheck, threshold, maxIter, handler); } /** {@inheritDoc} */ @Override - protected FieldUmbraDetector<T> create(final T newMaxCheck, final T newThreshold, - final int newMaxIter, + protected FieldUmbraDetector<T> create(final T newMaxCheck, final T newThreshold, final int newMaxIter, final FieldEventHandler<? super FieldUmbraDetector<T>, T> newHandler) { return new FieldUmbraDetector<>(newMaxCheck, newThreshold, newMaxIter, newHandler); } @@ -338,16 +450,14 @@ public abstract class AbstractRadiationForceModel extends AbstractForceModel { * @param maxIter maximum number of iterations in the event time search * @param handler event handler to call at event occurrences */ - private FieldPenumbraDetector(final T maxCheck, final T threshold, - final int maxIter, + private FieldPenumbraDetector(final T maxCheck, final T threshold, final int maxIter, final FieldEventHandler<? super FieldPenumbraDetector<T>, T> handler) { super(maxCheck, threshold, maxIter, handler); } /** {@inheritDoc} */ @Override - protected FieldPenumbraDetector<T> create(final T newMaxCheck, final T newThreshold, - final int newMaxIter, + protected FieldPenumbraDetector<T> create(final T newMaxCheck, final T newThreshold, final int newMaxIter, final FieldEventHandler<? super FieldPenumbraDetector<T>, T> newHandler) { return new FieldPenumbraDetector<>(newMaxCheck, newThreshold, newMaxIter, newHandler); } @@ -365,4 +475,274 @@ public abstract class AbstractRadiationForceModel extends AbstractForceModel { } + /** This class defines the umbra entry/exit detector. */ + private class GeneralUmbraDetector extends AbstractDetector<GeneralUmbraDetector> { + + /** Occulting body PV provider. */ + private ExtendedPVCoordinatesProvider provider; + + /** Occulting body mean radius. */ + private double radius; + + /** Build a new instance. + * @param provider occulting body PV provider + * @param radius occulting body mean radius + */ + GeneralUmbraDetector(final ExtendedPVCoordinatesProvider provider, final double radius) { + super(60.0, 1.0e-3, DEFAULT_MAX_ITER, new EventHandler<GeneralUmbraDetector>() { + + /** {@inheritDoc} */ + public Action eventOccurred(final SpacecraftState s, final GeneralUmbraDetector detector, + final boolean increasing) { + return Action.RESET_DERIVATIVES; + } + + }); + this.provider = provider; + this.radius = radius; + } + + /** Private constructor with full parameters. + * <p> + * This constructor is private as users are expected to use the builder + * API with the various {@code withXxx()} methods to set up the instance + * in a readable manner without using a huge amount of parameters. + * </p> + * @param maxCheck maximum checking interval (s) + * @param threshold convergence threshold (s) + * @param maxIter maximum number of iterations in the event time search + * @param handler event handler to call at event occurrences + * @since 6.1 + */ + private GeneralUmbraDetector(final double maxCheck, final double threshold, final int maxIter, + final EventHandler<? super GeneralUmbraDetector> handler) { + super(maxCheck, threshold, maxIter, handler); + } + + /** {@inheritDoc} */ + @Override + protected GeneralUmbraDetector create(final double newMaxCheck, final double newThreshold, final int newMaxIter, + final EventHandler<? super GeneralUmbraDetector> newHandler) { + return new GeneralUmbraDetector(newMaxCheck, newThreshold, newMaxIter, newHandler); + } + + /** The G-function is the difference between the Sun-Sat-Central-Body angle and + * the central body apparent radius. + * @param s the current state information : date, kinematics, attitude + * @return value of the g function + */ + public double g(final SpacecraftState s) { + final double[] angle = getGeneralEclipseAngles(s.getPVCoordinates().getPosition(), + provider.getPVCoordinates(s.getDate(), s.getFrame()).getPosition(), + radius, sun.getPVCoordinates(s.getDate(), s.getFrame()).getPosition(), + Constants.SUN_RADIUS); + return angle[0] - angle[1] + angle[2] - ANGULAR_MARGIN; + } + + } + + /** This class defines the umbra entry/exit detector. */ + private class GeneralPenumbraDetector extends AbstractDetector<GeneralPenumbraDetector> { + + /** Occulting body PV provider. */ + private ExtendedPVCoordinatesProvider provider; + + /** Occulting body mean radius. */ + private double radius; + + /** Build a new instance. + * @param provider occulting body PV provider + * @param radius occulting body mean radius + */ + GeneralPenumbraDetector(final ExtendedPVCoordinatesProvider provider, final double radius) { + super(60.0, 1.0e-3, DEFAULT_MAX_ITER, new EventHandler<GeneralPenumbraDetector>() { + + /** {@inheritDoc} */ + public Action eventOccurred(final SpacecraftState s, final GeneralPenumbraDetector detector, + final boolean increasing) { + return Action.RESET_DERIVATIVES; + } + + }); + this.provider = provider; + this.radius = radius; + } + + /** Private constructor with full parameters. + * <p> + * This constructor is private as users are expected to use the builder + * API with the various {@code withXxx()} methods to set up the instance + * in a readable manner without using a huge amount of parameters. + * </p> + * @param maxCheck maximum checking interval (s) + * @param threshold convergence threshold (s) + * @param maxIter maximum number of iterations in the event time search + * @param handler event handler to call at event occurrences + * @since 6.1 + */ + private GeneralPenumbraDetector(final double maxCheck, final double threshold, final int maxIter, + final EventHandler<? super GeneralPenumbraDetector> handler) { + super(maxCheck, threshold, maxIter, handler); + } + + /** {@inheritDoc} */ + @Override + protected GeneralPenumbraDetector create(final double newMaxCheck, final double newThreshold, final int newMaxIter, + final EventHandler<? super GeneralPenumbraDetector> newHandler) { + return new GeneralPenumbraDetector(newMaxCheck, newThreshold, newMaxIter, newHandler); + } + + /** The G-function is the difference between the Sun-Sat-Central-Body angle and + * the central body apparent radius. + * @param s the current state information : date, kinematics, attitude + * @return value of the g function + */ + public double g(final SpacecraftState s) { + final double[] angle = getGeneralEclipseAngles(s.getPVCoordinates().getPosition(), + provider.getPVCoordinates(s.getDate(), s.getFrame()).getPosition(), + radius, sun.getPVCoordinates(s.getDate(), s.getFrame()).getPosition(), + Constants.SUN_RADIUS); + return angle[0] - angle[1] - angle[2] + ANGULAR_MARGIN; + } + + } + + /** This class defines the umbra entry/exit detector. */ + private class FieldGeneralUmbraDetector<T extends CalculusFieldElement<T>> + extends FieldAbstractDetector<FieldGeneralUmbraDetector<T>, T> { + + /** Occulting body PV provider. */ + private ExtendedPVCoordinatesProvider provider; + + /** Occulting body mean radius. */ + private T radius; + + /** Build a new instance. + * @param field field to which elements belong + * @param provider occulting body PV provider + * @param radius occulting body mean radius + */ + FieldGeneralUmbraDetector(final Field<T> field, final ExtendedPVCoordinatesProvider provider, final T radius) { + super(field.getZero().add(60.0), field.getZero().add(1.0e-3), + DEFAULT_MAX_ITER, new FieldEventHandler<FieldGeneralUmbraDetector<T>, T>() { + + /** {@inheritDoc} */ + public Action eventOccurred(final FieldSpacecraftState<T> s, + final FieldGeneralUmbraDetector<T> detector, + final boolean increasing) { + return Action.RESET_DERIVATIVES; + } + + }); + this.provider = provider; + this.radius = radius; + } + + /** Private constructor with full parameters. + * <p> + * This constructor is private as users are expected to use the builder + * API with the various {@code withXxx()} methods to set up the instance + * in a readable manner without using a huge amount of parameters. + * </p> + * @param maxCheck maximum checking interval (s) + * @param threshold convergence threshold (s) + * @param maxIter maximum number of iterations in the event time search + * @param handler event handler to call at event occurrences + */ + private FieldGeneralUmbraDetector(final T maxCheck, final T threshold, + final int maxIter, + final FieldEventHandler<? super FieldGeneralUmbraDetector<T>, T> handler) { + super(maxCheck, threshold, maxIter, handler); + } + + /** {@inheritDoc} */ + @Override + protected FieldGeneralUmbraDetector<T> create(final T newMaxCheck, final T newThreshold, final int newMaxIter, + final FieldEventHandler<? super FieldGeneralUmbraDetector<T>, T> newHandler) { + return new FieldGeneralUmbraDetector<>(newMaxCheck, newThreshold, newMaxIter, newHandler); + } + + /** The G-function is the difference between the Sun-Sat-Central-Body angle and + * the central body apparent radius. + * @param s the current state information : date, kinematics, attitude + * @return value of the g function + */ + public T g(final FieldSpacecraftState<T> s) { + final T[] angle = getGeneralEclipseAngles(s.getPVCoordinates().getPosition(), + provider.getPVCoordinates(s.getDate(), s.getFrame()).getPosition(), + radius, sun.getPVCoordinates(s.getDate(), s.getFrame()).getPosition(), + s.getA().getField().getZero().add(Constants.SUN_RADIUS)); + return angle[0].subtract(angle[1]).add(angle[2]).subtract(ANGULAR_MARGIN); + } + + } + + /** This class defines the umbra entry/exit detector. */ + private class FieldGeneralPenumbraDetector<T extends CalculusFieldElement<T>> + extends FieldAbstractDetector<FieldGeneralPenumbraDetector<T>, T> { + + /** Occulting body PV provider. */ + private ExtendedPVCoordinatesProvider provider; + + /** Occulting body mean radius. */ + private T radius; + + /** Build a new instance. + * @param field field to which elements belong + * @param provider occulting body PV provider + * @param radius occulting body mean radius + */ + FieldGeneralPenumbraDetector(final Field<T> field, final ExtendedPVCoordinatesProvider provider, final T radius) { + super(field.getZero().add(60.0), field.getZero().add(1.0e-3), + DEFAULT_MAX_ITER, new FieldEventHandler<FieldGeneralPenumbraDetector<T>, T>() { + + /** {@inheritDoc} */ + public Action eventOccurred(final FieldSpacecraftState<T> s, + final FieldGeneralPenumbraDetector<T> detector, + final boolean increasing) { + return Action.RESET_DERIVATIVES; + } + + }); + this.provider = provider; + this.radius = radius; + } + + /** Private constructor with full parameters. + * <p> + * This constructor is private as users are expected to use the builder + * API with the various {@code withXxx()} methods to set up the instance + * in a readable manner without using a huge amount of parameters. + * </p> + * @param maxCheck maximum checking interval (s) + * @param threshold convergence threshold (s) + * @param maxIter maximum number of iterations in the event time search + * @param handler event handler to call at event occurrences + */ + private FieldGeneralPenumbraDetector(final T maxCheck, final T threshold, final int maxIter, + final FieldEventHandler<? super FieldGeneralPenumbraDetector<T>, T> handler) { + super(maxCheck, threshold, maxIter, handler); + } + + /** {@inheritDoc} */ + @Override + protected FieldGeneralPenumbraDetector<T> create(final T newMaxCheck, final T newThreshold, final int newMaxIter, + final FieldEventHandler<? super FieldGeneralPenumbraDetector<T>, T> newHandler) { + return new FieldGeneralPenumbraDetector<>(newMaxCheck, newThreshold, newMaxIter, newHandler); + } + + /** The G-function is the difference between the Sun-Sat-Central-Body angle and + * the central body apparent radius. + * @param s the current state information : date, kinematics, attitude + * @return value of the g function + */ + public T g(final FieldSpacecraftState<T> s) { + final T[] angle = getGeneralEclipseAngles(s.getPVCoordinates().getPosition(), + provider.getPVCoordinates(s.getDate(), s.getFrame()).getPosition(), + radius, sun.getPVCoordinates(s.getDate(), s.getFrame()).getPosition(), + s.getA().getField().getZero().add(Constants.SUN_RADIUS)); + return angle[0].subtract(angle[1]).subtract(angle[2]).add(ANGULAR_MARGIN); + } + + } } diff --git a/src/main/java/org/orekit/forces/radiation/SolarRadiationPressure.java b/src/main/java/org/orekit/forces/radiation/SolarRadiationPressure.java index ff00ce6bac0bceb608b66c8486851bef27a6825f..988c6c3010575ddabe7cb741eeb1b57a12af5b13 100644 --- a/src/main/java/org/orekit/forces/radiation/SolarRadiationPressure.java +++ b/src/main/java/org/orekit/forces/radiation/SolarRadiationPressure.java @@ -16,12 +16,15 @@ */ package org.orekit.forces.radiation; +import java.util.ArrayList; import java.util.List; +import java.util.Map; import org.hipparchus.CalculusFieldElement; import org.hipparchus.geometry.euclidean.threed.FieldVector3D; import org.hipparchus.geometry.euclidean.threed.Vector3D; import org.hipparchus.util.FastMath; +import org.hipparchus.util.MathArrays; import org.hipparchus.util.Precision; import org.orekit.frames.Frame; import org.orekit.propagation.FieldSpacecraftState; @@ -109,7 +112,7 @@ public class SolarRadiationPressure extends AbstractRadiationForceModel { final double r2 = sunSatVector.getNormSq(); // compute flux - final double ratio = getLightingRatio(position, frame, date); + final double ratio = getTotalLightingRatio(position, frame, date); final double rawP = ratio * kRef / r2; final Vector3D flux = new Vector3D(rawP / FastMath.sqrt(r2), sunSatVector); @@ -130,7 +133,7 @@ public class SolarRadiationPressure extends AbstractRadiationForceModel { final T r2 = sunSatVector.getNormSq(); // compute flux - final T ratio = getLightingRatio(position, frame, date); + final T ratio = getTotalLightingRatio(position, frame, date); final T rawP = ratio.divide(r2).multiply(kRef); final FieldVector3D<T> flux = new FieldVector3D<>(rawP.divide(r2.sqrt()), sunSatVector); @@ -140,6 +143,7 @@ public class SolarRadiationPressure extends AbstractRadiationForceModel { } /** Get the lighting ratio ([0-1]). + * Considers only central body as occulting body. * @param position the satellite's position in the selected frame. * @param frame in which is defined the position * @param date the date @@ -201,7 +205,163 @@ public class SolarRadiationPressure extends AbstractRadiationForceModel { } + /** Get eclipse ratio between to bodies seen from a specific object. + * Ratio is in [0-1]. + * @param position the satellite's position in the selected frame + * @param occultingPosition the position of the occulting object + * @param occultingRadius the mean radius of the occulting object + * @param occultedPosition the position of the occulted object + * @param occultedRadius the mean radius of the occulted object + * @return eclipse ratio + */ + public double getGeneralEclipseRatio(final Vector3D position, + final Vector3D occultingPosition, + final double occultingRadius, + final Vector3D occultedPosition, + final double occultedRadius) { + + + // Compute useful angles + final double[] angle = getGeneralEclipseAngles(position, occultingPosition, occultingRadius, occultedPosition, occultedRadius); + + // Sat-Occulted/ Sat-Occulting angle + final double occultedSatOcculting = angle[0]; + + // Occulting apparent radius + final double alphaOcculting = angle[1]; + + // Occulted apparent radius + final double alphaOcculted = angle[2]; + + double result = 1.0; + + // Is the satellite in complete umbra ? + if (occultedSatOcculting - alphaOcculting + alphaOcculted <= ANGULAR_MARGIN) { + result = 0.0; + } else if (occultedSatOcculting - alphaOcculting - alphaOcculted < -ANGULAR_MARGIN) { + // Compute an eclipse ratio in penumbra + final double sEA2 = occultedSatOcculting * occultedSatOcculting; + final double oo2sEA = 1.0 / (2. * occultedSatOcculting); + final double aS2 = alphaOcculted * alphaOcculted; + final double aE2 = alphaOcculting * alphaOcculting; + final double aE2maS2 = aE2 - aS2; + + final double alpha1 = (sEA2 - aE2maS2) * oo2sEA; + final double alpha2 = (sEA2 + aE2maS2) * oo2sEA; + + // Protection against numerical inaccuracy at boundaries + final double almost0 = Precision.SAFE_MIN; + final double almost1 = FastMath.nextDown(1.0); + final double a1oaS = FastMath.min(almost1, FastMath.max(-almost1, alpha1 / alphaOcculted)); + final double aS2ma12 = FastMath.max(almost0, aS2 - alpha1 * alpha1); + final double a2oaE = FastMath.min(almost1, FastMath.max(-almost1, alpha2 / alphaOcculting)); + final double aE2ma22 = FastMath.max(almost0, aE2 - alpha2 * alpha2); + + final double P1 = aS2 * FastMath.acos(a1oaS) - alpha1 * FastMath.sqrt(aS2ma12); + final double P2 = aE2 * FastMath.acos(a2oaE) - alpha2 * FastMath.sqrt(aE2ma22); + + result = 1. - (P1 + P2) / (FastMath.PI * aS2); + } + + return result; + } + + /** Get the total lighting ratio ([0-1]). + * This method considers every occulting bodies. + * @param position the satellite's position in the selected frame. + * @param frame in which is defined the position + * @param date the date + * @return lighting ratio + */ + public double getTotalLightingRatio(final Vector3D position, final Frame frame, final AbsoluteDate date) { + + double result = 0.0; + final Map<ExtendedPVCoordinatesProvider, Double> otherOccultingBodies = getOtherOccultingBodies(); + final Vector3D sunPosition = sun.getPVCoordinates(date, frame).getPosition(); + final int n = otherOccultingBodies.size() + 1; + + if (n > 1) { + + final Vector3D[] occultingBodyPositions = new Vector3D[n]; + final double[] occultingBodyRadiuses = new double[n]; + + // Central body + occultingBodyPositions[0] = new Vector3D(0.0, 0.0, 0.0); + occultingBodyRadiuses[0] = getEquatorialRadius(); + + // Other occulting bodies + int k = 1; + for (ExtendedPVCoordinatesProvider provider: otherOccultingBodies.keySet()) { + occultingBodyPositions[k] = provider.getPVCoordinates(date, frame).getPosition(); + occultingBodyRadiuses[k] = otherOccultingBodies.get(provider); + ++k; + } + for (int i = 0; i < n; ++i) { + + // Lighting ratio computations + final double eclipseRatioI = getGeneralEclipseRatio(position, + occultingBodyPositions[i], + occultingBodyRadiuses[i], + sunPosition, + Constants.SUN_RADIUS); + + // First body totaly occults Sun, full eclipse is occuring. + if (eclipseRatioI == 0.0) { + return 0.0; + } + + + result += eclipseRatioI; + + + // Mutual occulting body eclipse ratio computations between first and secondary bodies + for (int j = i + 1; j < n; ++j) { + + final double eclipseRatioJ = getGeneralEclipseRatio(position, + occultingBodyPositions[j], + occultingBodyRadiuses[j], + sunPosition, + Constants.SUN_RADIUS); + final double eclipseRatioIJ = getGeneralEclipseRatio(position, + occultingBodyPositions[i], + occultingBodyRadiuses[i], + occultingBodyPositions[j], + occultingBodyRadiuses[j]); + + final double alphaJ = getGeneralEclipseAngles(position, + occultingBodyPositions[i], + occultingBodyRadiuses[i], + occultingBodyPositions[j], + occultingBodyRadiuses[j])[2]; + + final double alphaSun = getEclipseAngles(sunPosition, position)[2]; + final double alphaJSq = alphaJ * alphaJ; + final double alphaSunSq = alphaSun * alphaSun; + final double mutualEclipseCorrection = (1 - eclipseRatioIJ) * alphaJSq / alphaSunSq; + + // Secondary body totaly occults Sun, no more computations are required, full eclipse is occuring. + if (eclipseRatioJ == 0.0 ) { + return 0.0; + } + + // Secondary body partially occults Sun + else if (eclipseRatioJ != 1) { + result -= mutualEclipseCorrection; + } + } + } + // Final term + result -= n - 1; + } else { + // only central body is considered + result = getLightingRatio(position, frame, date); + } + return result; + } + + /** Get the lighting ratio ([0-1]). + * Considers only central body as occulting body. * @param position the satellite's position in the selected frame. * @param frame in which is defined the position * @param date the date @@ -266,6 +426,166 @@ public class SolarRadiationPressure extends AbstractRadiationForceModel { return result; } + /** Get eclipse ratio between to bodies seen from a specific object. + * Ratio is in [0-1]. + * @param position the satellite's position in the selected frame + * @param occultingPosition the position of the occulting object + * @param occultingRadius the mean radius of the occulting object + * @param occultedPosition the position of the occulted object + * @param occultedRadius the mean radius of the occulted object + * @param <T> extends RealFieldElement + * @return eclipse ratio + */ + public <T extends CalculusFieldElement<T>> T getGeneralEclipseRatio(final FieldVector3D<T> position, + final FieldVector3D<T> occultingPosition, + final T occultingRadius, + final FieldVector3D<T> occultedPosition, + final T occultedRadius) { + + + final T one = occultingRadius.getField().getOne(); + + // Compute useful angles + final T[] angle = getGeneralEclipseAngles(position, occultingPosition, occultingRadius, occultedPosition, occultedRadius); + + // Sat-Occulted/ Sat-Occulting angle + final T occultedSatOcculting = angle[0]; + + // Occulting apparent radius + final T alphaOcculting = angle[1]; + + // Occulted apparent radius + final T alphaOcculted = angle[2]; + + T result = one; + + // Is the satellite in complete umbra ? + if (occultedSatOcculting.getReal() - alphaOcculting.getReal() + alphaOcculted.getReal() <= ANGULAR_MARGIN) { + result = occultingRadius.getField().getZero(); + } else if (occultedSatOcculting.getReal() - alphaOcculting.getReal() - alphaOcculted.getReal() < -ANGULAR_MARGIN) { + // Compute a lighting ratio in penumbra + final T sEA2 = occultedSatOcculting.multiply(occultedSatOcculting); + final T oo2sEA = occultedSatOcculting.multiply(2).reciprocal(); + final T aS2 = alphaOcculted.multiply(alphaOcculted); + final T aE2 = alphaOcculting.multiply(alphaOcculting); + final T aE2maS2 = aE2.subtract(aS2); + + final T alpha1 = sEA2.subtract(aE2maS2).multiply(oo2sEA); + final T alpha2 = sEA2.add(aE2maS2).multiply(oo2sEA); + + // Protection against numerical inaccuracy at boundaries + final double almost0 = Precision.SAFE_MIN; + final double almost1 = FastMath.nextDown(1.0); + final T a1oaS = min(almost1, max(-almost1, alpha1.divide(alphaOcculted))); + final T aS2ma12 = max(almost0, aS2.subtract(alpha1.multiply(alpha1))); + final T a2oaE = min(almost1, max(-almost1, alpha2.divide(alphaOcculting))); + final T aE2ma22 = max(almost0, aE2.subtract(alpha2.multiply(alpha2))); + + final T P1 = aS2.multiply(a1oaS.acos()).subtract(alpha1.multiply(aS2ma12.sqrt())); + final T P2 = aE2.multiply(a2oaE.acos()).subtract(alpha2.multiply(aE2ma22.sqrt())); + + result = one.subtract(P1.add(P2).divide(aS2.multiply(FastMath.PI))); + } + + return result; + } + + /** Get the total lighting ratio ([0-1]). + * This method considers every occulting bodies. + * @param position the satellite's position in the selected frame. + * @param frame in which is defined the position + * @param date the date + * @param <T> extends RealFieldElement + * @return lighting rati + */ + public <T extends CalculusFieldElement<T>> T getTotalLightingRatio(final FieldVector3D<T> position, final Frame frame, final FieldAbsoluteDate<T> date) { + + final T zero = position.getAlpha().getField().getZero(); + T result = zero; + final Map<ExtendedPVCoordinatesProvider, Double> otherOccultingBodies = getOtherOccultingBodies(); + final FieldVector3D<T> sunPosition = sun.getPVCoordinates(date, frame).getPosition(); + final int n = otherOccultingBodies.size() + 1; + + if (n > 1) { + + final List<FieldVector3D<T>> occultingBodyPositions = new ArrayList<FieldVector3D<T>>(n); + final T[] occultingBodyRadiuses = MathArrays.buildArray(zero.getField(), n); + + // Central body + occultingBodyPositions.add(0, new FieldVector3D<>(zero, zero, zero)); + occultingBodyRadiuses[0] = zero.add(getEquatorialRadius()); + + // Other occulting bodies + int k = 1; + for (ExtendedPVCoordinatesProvider provider: otherOccultingBodies.keySet()) { + occultingBodyPositions.add(k, provider.getPVCoordinates(date, frame).getPosition()); + occultingBodyRadiuses[k] = zero.add(otherOccultingBodies.get(provider)); + ++k; + } + for (int i = 0; i < n; ++i) { + + // Lighting ratio computations + final T eclipseRatioI = getGeneralEclipseRatio(position, + occultingBodyPositions.get(i), + occultingBodyRadiuses[i], + sunPosition, + zero.add(Constants.SUN_RADIUS)); + + // First body totaly occults Sun, full eclipse is occuring. + if (eclipseRatioI.getReal() == 0.0) { + return zero; + } + + + result = result.add(eclipseRatioI); + + + // Mutual occulting body eclipse ratio computations between first and secondary bodies + for (int j = i + 1; j < n; ++j) { + + final T eclipseRatioJ = getGeneralEclipseRatio(position, + occultingBodyPositions.get(i), + occultingBodyRadiuses[j], + sunPosition, + zero.add(Constants.SUN_RADIUS)); + final T eclipseRatioIJ = getGeneralEclipseRatio(position, + occultingBodyPositions.get(i), + occultingBodyRadiuses[i], + occultingBodyPositions.get(j), + occultingBodyRadiuses[j]); + + final T alphaJ = getGeneralEclipseAngles(position, + occultingBodyPositions.get(i), + occultingBodyRadiuses[i], + occultingBodyPositions.get(j), + occultingBodyRadiuses[j])[2]; + + final T alphaSun = getEclipseAngles(sunPosition, position)[2]; + final T alphaJSq = alphaJ.multiply(alphaJ); + final T alphaSunSq = alphaSun.multiply(alphaSun); + final T mutualEclipseCorrection = eclipseRatioIJ.negate().add(1).multiply(alphaJSq).divide(alphaSunSq); + + // Secondary body totaly occults Sun, no more computations are required, full eclipse is occuring. + if (eclipseRatioJ.getReal() == 0.0 ) { + return zero; + } + + // Secondary body partially occults Sun + else if (eclipseRatioJ.getReal() != 1) { + result = result.subtract(mutualEclipseCorrection); + } + } + } + // Final term + result = result.subtract(n - 1); + } else { + // only central body is considered + result = getLightingRatio(position, frame, date); + } + return result; + } + + /** {@inheritDoc} */ @Override public List<ParameterDriver> getParametersDrivers() { @@ -279,7 +599,7 @@ public class SolarRadiationPressure extends AbstractRadiationForceModel { * @return min value */ private <T extends CalculusFieldElement<T>> T min(final double d, final T f) { - return (f.getReal() > d) ? f.getField().getZero().add(d) : f; + return (f.getReal() > d) ? f.getField().getZero().newInstance(d) : f; } /** Compute max of two values, one double and one field element. @@ -289,7 +609,7 @@ public class SolarRadiationPressure extends AbstractRadiationForceModel { * @return max value */ private <T extends CalculusFieldElement<T>> T max(final double d, final T f) { - return (f.getReal() <= d) ? f.getField().getZero().add(d) : f; + return (f.getReal() <= d) ? f.getField().getZero().newInstance(d) : f; } } diff --git a/src/test/java/org/orekit/forces/radiation/SolarRadiationPressureTest.java b/src/test/java/org/orekit/forces/radiation/SolarRadiationPressureTest.java index 247b44385b25c8621c707e2a2c78bccb709728a5..166dcddd8d03ce30af1dca19cd640c4fb7ea23bf 100644 --- a/src/test/java/org/orekit/forces/radiation/SolarRadiationPressureTest.java +++ b/src/test/java/org/orekit/forces/radiation/SolarRadiationPressureTest.java @@ -17,6 +17,7 @@ package org.orekit.forces.radiation; +import java.io.File; import java.io.FileNotFoundException; import java.lang.reflect.InvocationTargetException; import java.text.ParseException; @@ -41,6 +42,9 @@ import org.orekit.Utils; import org.orekit.attitudes.LofOffset; import org.orekit.bodies.CelestialBodyFactory; import org.orekit.bodies.OneAxisEllipsoid; +import org.orekit.data.DataContext; +import org.orekit.data.DataProvidersManager; +import org.orekit.data.DirectoryCrawler; import org.orekit.errors.OrekitException; import org.orekit.forces.AbstractLegacyForceModelTest; import org.orekit.forces.BoxAndSolarArraySpacecraft; @@ -175,7 +179,7 @@ public class SolarRadiationPressureTest extends AbstractLegacyForceModelTest { ExtendedPVCoordinatesProvider sun = CelestialBodyFactory.getSun(); SolarRadiationPressure srp = new SolarRadiationPressure(sun, Constants.SUN_RADIUS, - (RadiationSensitive) new IsotropicRadiationClassicalConvention(50.0, 0.5, 0.5)); + new IsotropicRadiationClassicalConvention(50.0, 0.5, 0.5)); Assert.assertFalse(srp.dependsOnPositionOnly()); Vector3D position = orbit.getPVCoordinates().getPosition(); @@ -206,7 +210,7 @@ public class SolarRadiationPressureTest extends AbstractLegacyForceModelTest { FramesFactory.getITRF(IERSConventions.IERS_2010, true)); SolarRadiationPressure SRP = new SolarRadiationPressure(sun, earth.getEquatorialRadius(), - (RadiationSensitive) new IsotropicRadiationCNES95Convention(50.0, 0.5, 0.5)); + new IsotropicRadiationCNES95Convention(50.0, 0.5, 0.5)); double period = 2*FastMath.PI*FastMath.sqrt(orbit.getA()*orbit.getA()*orbit.getA()/orbit.getMu()); Assert.assertEquals(86164, period, 1); @@ -603,6 +607,7 @@ public class SolarRadiationPressureTest extends AbstractLegacyForceModelTest { private static class SolarStepHandler implements OrekitFixedStepHandler { + @Override public void handleStep(SpacecraftState currentState, boolean isLast) { final double dex = currentState.getEquinoctialEx() - 0.01071166; final double dey = currentState.getEquinoctialEy() - 0.00654848; @@ -656,7 +661,7 @@ public class SolarRadiationPressureTest extends AbstractLegacyForceModelTest { AdaptiveStepsizeFieldIntegrator<DerivativeStructure> integrator = new DormandPrince853FieldIntegrator<>(field, 0.001, 200, tolerance[0], tolerance[1]); integrator.setInitialStepSize(60); - + AdaptiveStepsizeIntegrator RIntegrator = new DormandPrince853Integrator(0.001, 200, tolerance[0], tolerance[1]); RIntegrator.setInitialStepSize(60); @@ -682,7 +687,7 @@ public class SolarRadiationPressureTest extends AbstractLegacyForceModelTest { FNP.addForceModel(forceModel); NP.addForceModel(forceModel); - + // Do the test checkRealFieldPropagation(FKO, PositionAngle.MEAN, 1000., NP, FNP, 1.0e-30, 5.0e-10, 3.0e-11, 3.0e-10, @@ -761,6 +766,129 @@ public class SolarRadiationPressureTest extends AbstractLegacyForceModelTest { Assert.assertFalse(FastMath.abs(finPVC_DS.toPVCoordinates().getPosition().getZ() - finPVC_R.getPosition().getZ()) < FastMath.abs(finPVC_R.getPosition().getZ()) * 1e-11); } + /** Testing if eclipses due to Moon are considered. + * Earth is artificially reduced to a single point to only consider Moon effect. + * Reference values are presented in "A Study of Solar Radiation Pressure acting on GPS Satellites" + * written by Laurent Olivier Froideval in 2009. + * Modifications of the step handler and time span able to print lighting ratios other a year and get a reference like graph. + */ + @Test + public void testMoonEclipse() { + + // Configure Orekit + final File home = new File(System.getProperty("user.home")); + final File orekitData = new File(home, "orekit-data"); + final DataProvidersManager manager = DataContext.getDefault().getDataProvidersManager(); + manager.addProvider(new DirectoryCrawler(orekitData)); + + final ExtendedPVCoordinatesProvider sun = CelestialBodyFactory.getSun(); + final ExtendedPVCoordinatesProvider moon = CelestialBodyFactory.getMoon(); + final Frame GCRF = FramesFactory.getGCRF(); + final AbsoluteDate date = new AbsoluteDate(2007, 01, 01, 6, 0, 0, TimeScalesFactory.getGPS()); + final double dt = 3600 * 24 * 180; + final AbsoluteDate target = date.shiftedBy(dt); + + //PV coordinates from sp3 file esa14081.sp3 PRN 03 + final PVCoordinates refPV = new PVCoordinates(new Vector3D(14986728.76145754, 14849687.258579938, -16523319.786690142), + new Vector3D(-3152.6722260146, 1005.6757515113, -1946.9273038773)); + final Orbit orbit = new CartesianOrbit(refPV, GCRF, date, Constants.EIGEN5C_EARTH_MU); + final SpacecraftState initialState = new SpacecraftState(orbit); + + // Create SRP perturbation with Moon and Earth + SolarRadiationPressure srp = + new SolarRadiationPressure(sun, Constants.EIGEN5C_EARTH_EQUATORIAL_RADIUS, + new IsotropicRadiationClassicalConvention(50.0, 0.5, 0.5)); + srp.addOccultingBody(moon, Constants.MOON_EQUATORIAL_RADIUS); + + // creation of the propagator + double[] absTolerance = { + 0.1, 1.0e-9, 1.0e-9, 1.0e-5, 1.0e-5, 1.0e-5, 0.001 + }; + double[] relTolerance = { + 1.0e-4, 1.0e-4, 1.0e-4, 1.0e-6, 1.0e-6, 1.0e-6, 1.0e-7 + }; + AdaptiveStepsizeIntegrator integrator = + new DormandPrince853Integrator(600, 1000000, absTolerance, relTolerance); + final NumericalPropagator propagator = new NumericalPropagator(integrator); + propagator.setInitialState(initialState); + propagator.addForceModel(srp); + propagator.setMasterMode(600, new MoonEclipseStepHandler(moon, sun, srp)); + propagator.propagate(target); + } + + /** Specialized step handler. + * <p>This class extends the step handler in order to print on the output stream at the given step.<p> + * @author Thomas Paulet + */ + private static class MoonEclipseStepHandler implements OrekitFixedStepHandler { + + final ExtendedPVCoordinatesProvider moon; + final ExtendedPVCoordinatesProvider sun; + final SolarRadiationPressure srp; + + /** Simple constructor. + */ + MoonEclipseStepHandler(final ExtendedPVCoordinatesProvider moon, final ExtendedPVCoordinatesProvider sun, + final SolarRadiationPressure srp) { + this.moon = moon; + this.sun = sun; + this.srp = srp; + + } + + /** {@inheritDoc} */ + @Override + public void init(final SpacecraftState s0, final AbsoluteDate t, final double step) { + } + + /** {@inheritDoc} */ + @Override + public void handleStep(final SpacecraftState currentState, final boolean isLast) { + final AbsoluteDate date = currentState.getDate(); + final Frame frame = currentState.getFrame(); + + final Vector3D moonPos = moon.getPVCoordinates(date, frame).getPosition(); + final Vector3D sunPos = sun.getPVCoordinates(date, frame).getPosition(); + final Vector3D statePos = currentState.getPVCoordinates().getPosition(); + + // Moon umbra and penumbra conditions + final double[] moonAngles = srp.getGeneralEclipseAngles(statePos, moonPos, Constants.MOON_EQUATORIAL_RADIUS, + sunPos, Constants.SUN_RADIUS); + final double moonUmbra = moonAngles[0] - moonAngles[1] + moonAngles[2]; + final boolean isInMoonUmbra = (moonUmbra < 1.0e-10); + + final double moonPenumbra = moonAngles[0] - moonAngles[1] - moonAngles[2]; + final boolean isInMoonPenumbra = (moonPenumbra < -1.0e-10); + + // Earth umbra and penumbra conditions + final double[] earthAngles = srp.getEclipseAngles(sunPos, statePos); + + final double earthUmbra = earthAngles[0] - earthAngles[1] + earthAngles[2]; + final boolean isInEarthUmbra = (earthUmbra < 1.0e-10); + + final double earthPenumbra = earthAngles[0] - earthAngles[1] - earthAngles[2]; + final boolean isInEarthPenumbra = (earthPenumbra < -1.0e-10); + + + // Compute lighting ration + final double lightingRatio = srp.getTotalLightingRatio(statePos, frame, date); + + // Check behaviour + if (isInMoonUmbra || isInEarthUmbra) { + Assert.assertEquals(0.0, lightingRatio, 1e-8); + } + else if (isInMoonPenumbra || isInEarthPenumbra) { + Assert.assertEquals(true, (lightingRatio < 1.0)); + Assert.assertEquals(true, (lightingRatio > 0.0)); + } + else { + Assert.assertEquals(1.0, lightingRatio, 1e-8); + } + } + } + + + @Before public void setUp() { Utils.setDataRoot("regular-data");