I want to implement a custom panel for my BoxAndSolarArraySpacecraft that behaves like this:
- When the spacecraft is not in eclipse , the panel tracks the Sun.
- When it enters eclipse , the panel rotates back to a fixed angle.
below is a very basic custom Java class I wrote (I amm still pretty new to Java). The plan is to compile this into a JAR and use it with orekit_jpype.
import org.orekit.forces.Panel;
import org.orekit.propagation.SpacecraftState;
import org.orekit.propagation.FieldSpacecraftState;
import org.orekit.time.AbsoluteDate;
import org.orekit.utils.Constants;
import org.orekit.utils.PVCoordinatesProvider;
import org.hipparchus.geometry.euclidean.threed.Vector3D;
import org.hipparchus.CalculusFieldElement;
import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
import org.orekit.utils.ExtendedPositionProvider;
import org.hipparchus.util.FastMath;
import org.hipparchus.util.Precision;
public class CustomPanel extends Panel {
public enum Mode {
SUN_TRACKING,
REVERSE_ROTATION,
HOLD
}
private Mode mode = Mode.SUN_TRACKING;
private final Vector3D rotationAxis;
private final Vector3D referenceNormal;
private final double reverseRate; // rad/s
private double holdAngle; // rad
public double currentAngle = 0.0;
private AbsoluteDate lastDate = null;
private final ExtendedPositionProvider target;
public SmartPanel(Vector3D referenceNormal,
Vector3D rotationAxis,
double area,
boolean doubleSided,
double drag,
double liftRatio,
double absorption,
double reflection,
double reverseRate,
double holdAngle,
final ExtendedPositionProvider target) {
super(area, doubleSided, drag, liftRatio, absorption, reflection);
this.referenceNormal = referenceNormal.normalize();
this.rotationAxis = rotationAxis.normalize();
this.reverseRate = Math.abs(reverseRate); // speed of wing rotating back to holdAngle
this.holdAngle = holdAngle; // angle of rest when in eclipse
this.target = target;
}
// Event handler will call these when an EclipseDetector triggered
public void enterEclipse() {
mode = Mode.REVERSE_ROTATION;
}
public void exitEclipse() {
mode = Mode.SUN_TRACKING;
}
public void setHoldAngle(double newAngle) {
this.holdAngle = newAngle;
}
// update current angle if in eclipse - REVERSE_ROTATION
private void updateAngle(AbsoluteDate date) {
if (lastDate == null) {
lastDate = date;
return;
}
double dt = date.durationFrom(lastDate);
lastDate = date;
if (mode == Mode.REVERSE_ROTATION) {
currentAngle -= reverseRate * dt;
if (currentAngle <= holdAngle) {
currentAngle = holdAngle;
mode = Mode.HOLD;
}
}
}
@Override
public Vector3D getNormal(SpacecraftState state) {
updateAngle(state.getDate());
switch (mode) {
case SUN_TRACKING:
return getNormalForPointing(state);
case REVERSE_ROTATION:
case HOLD:
return getNormalForFixed(referenceNormal, rotationAxis, currentAngle);
default:
return referenceNormal;
}
}
@Override
public <T extends CalculusFieldElement<T>>
FieldVector3D<T> getNormal(FieldSpacecraftState<T> state) {
// just dummy implementation
Vector3D normal = getNormal(state.toSpacecraftState());
return new FieldVector3D<>(state.getDate().getField(), normal);
}
private Vector3D getNormalForFixed(Vector3D v, Vector3D axis, double angle) {
double c = Math.cos(angle);
double s = Math.sin(angle);
return v.scalarMultiply(c)
.add(axis.crossProduct(v).scalarMultiply(s))
.add(axis.scalarMultiply(axis.dotProduct(v) * (1 - c)));
}
private Vector3D getNormalForPointing(SpacecraftState state) {
// Same code as in the orekit PointingPanel
final Vector3D targetInert = target.getPosition(state.getDate(), state.getFrame()).
subtract(state.getPosition()).normalize();
final Vector3D targetSpacecraft = state.getAttitude().getRotation().applyTo(targetInert);
final double d = Vector3D.dotProduct(targetSpacecraft, rotationAxis);
final double f = 1 - d * d;
if (f < Precision.EPSILON) {
// extremely rare case: the target is along panel rotation axis
// (there will not be much output power if it is a solar array…)
// we set up an arbitrary normal
return rotationAxis.orthogonal();
}
final double s = 1.0 / FastMath.sqrt(f);
return new Vector3D(s, targetSpacecraft, -s * d, rotationAxis);
}
}
I also set up an EventHandler that calls enterEclipse and exitEclipse when the corresponding events fire. Although I am not sure this is correct or not, so I’d appreciate any feedback on the implementation. Another question: if I use this panel with a NumericalPropagator as aSolarRadiationPressure force, is overriding getNormal enough, or are there other methods I should also override?
Any suggestions or guidance would be greatly appreciated.