1 /* Copyright 2002-2025 CS GROUP
2 * Licensed to CS GROUP (CS) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * CS licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.orekit.attitudes;
18
19 import org.hipparchus.Field;
20 import org.hipparchus.CalculusFieldElement;
21 import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
22 import org.hipparchus.geometry.euclidean.threed.Vector3D;
23 import org.orekit.frames.Frame;
24 import org.orekit.time.AbsoluteDate;
25 import org.orekit.time.FieldAbsoluteDate;
26 import org.orekit.utils.ExtendedPositionProvider;
27 import org.orekit.utils.FieldPVCoordinates;
28 import org.orekit.utils.FieldPVCoordinatesProvider;
29 import org.orekit.utils.PVCoordinates;
30 import org.orekit.utils.PVCoordinatesProvider;
31 import org.orekit.utils.TimeStampedAngularCoordinates;
32 import org.orekit.utils.TimeStampedFieldAngularCoordinates;
33
34
35 /**
36 * This class handles yaw steering law.
37
38 * <p>
39 * Yaw steering is mainly used for low Earth orbiting satellites with no
40 * missions-related constraints on yaw angle. It sets the yaw angle in
41 * such a way the solar arrays have maximal lighting without changing the
42 * roll and pitch.
43 * </p>
44 * <p>
45 * The motion in yaw is smooth when the Sun is far from the orbital plane,
46 * but gets more and more <i>square like</i> as the Sun gets closer to the
47 * orbital plane. The degenerate extreme case with the Sun in the orbital
48 * plane leads to a yaw angle switching between two steady states, with
49 * instantaneous π radians rotations at each switch, two times per orbit.
50 * This degenerate case is clearly not operationally sound so another pointing
51 * mode is chosen when Sun comes closer than some predefined threshold to the
52 * orbital plane.
53 * </p>
54 * <p>
55 * This class can handle (for now) only a theoretically perfect yaw steering
56 * (i.e. the yaw angle is exactly the optimal angle). Smoothed yaw steering with a
57 * few sine waves approaching the optimal angle will be added in the future if
58 * needed.
59 * </p>
60 * <p>
61 * This attitude is implemented as a wrapper on top of an underlying ground
62 * pointing law that defines the roll and pitch angles.
63 * </p>
64 * <p>
65 * Instances of this class are guaranteed to be immutable.
66 * </p>
67 * @see GroundPointing
68 * @author Luc Maisonobe
69 */
70 public class YawSteering extends GroundPointingAttitudeModifier implements AttitudeProviderModifier {
71
72 /** Pointing axis. */
73 private static final PVCoordinates PLUS_Z =
74 new PVCoordinates(Vector3D.PLUS_K, Vector3D.ZERO, Vector3D.ZERO);
75
76 /** Sun motion model. */
77 private final ExtendedPositionProvider sun;
78
79 /** Normal to the plane where the Sun must remain. */
80 private final PVCoordinates phasingNormal;
81
82 /** Creates a new instance.
83 * @param inertialFrame frame in which orbital velocities are computed
84 * @param groundPointingLaw ground pointing attitude provider without yaw compensation
85 * @param sun sun motion model
86 * @param phasingAxis satellite axis that must be roughly in Sun direction
87 * (if solar arrays rotation axis is Y, then this axis should be either +X or -X)
88 * @since 7.1
89 */
90 public YawSteering(final Frame inertialFrame,
91 final GroundPointing groundPointingLaw,
92 final ExtendedPositionProvider sun,
93 final Vector3D phasingAxis) {
94 super(inertialFrame, groundPointingLaw.getBodyFrame(), groundPointingLaw);
95 this.sun = sun;
96 this.phasingNormal = new PVCoordinates(Vector3D.crossProduct(Vector3D.PLUS_K, phasingAxis).normalize(),
97 Vector3D.ZERO,
98 Vector3D.ZERO);
99 }
100
101 /** {@inheritDoc} */
102 @Override
103 public Attitude getAttitude(final PVCoordinatesProvider pvProv,
104 final AbsoluteDate date, final Frame frame) {
105
106 // attitude from base attitude provider
107 final Attitude base = getBaseState(pvProv, date, frame);
108
109 // Compensation rotation definition :
110 // . Z satellite axis is unchanged
111 // . phasing axis shall be aligned to sun direction
112 final PVCoordinates sunDirection = new PVCoordinates(pvProv.getPVCoordinates(date, frame),
113 sun.getPVCoordinates(date, frame));
114 final PVCoordinates sunNormal =
115 PVCoordinates.crossProduct(PLUS_Z, base.getOrientation().applyTo(sunDirection));
116 final TimeStampedAngularCoordinates compensation =
117 new TimeStampedAngularCoordinates(date,
118 PLUS_Z, sunNormal.normalize(),
119 PLUS_Z, phasingNormal,
120 1.0e-9);
121
122 // add compensation
123 return new Attitude(frame, compensation.addOffset(base.getOrientation()));
124
125 }
126
127 /** {@inheritDoc} */
128 @Override
129 public <T extends CalculusFieldElement<T>> FieldAttitude<T> getAttitude(final FieldPVCoordinatesProvider<T> pvProv,
130 final FieldAbsoluteDate<T> date,
131 final Frame frame) {
132
133 final Field<T> field = date.getField();
134 final FieldVector3D<T> zero = FieldVector3D.getZero(field);
135 final FieldPVCoordinates<T> plusZ = new FieldPVCoordinates<>(FieldVector3D.getPlusK(field), zero, zero);
136
137 // attitude from base attitude provider
138 final FieldAttitude<T> base = getBaseState(pvProv, date, frame);
139
140 // Compensation rotation definition :
141 // . Z satellite axis is unchanged
142 // . phasing axis shall be aligned to sun direction
143 final FieldPVCoordinates<T> sunDirection =
144 new FieldPVCoordinates<>(pvProv.getPVCoordinates(date, frame),
145 sun.getPVCoordinates(date, frame));
146 final FieldPVCoordinates<T> sunNormal =
147 plusZ.crossProduct(base.getOrientation().applyTo(sunDirection));
148 final TimeStampedFieldAngularCoordinates<T> compensation =
149 new TimeStampedFieldAngularCoordinates<>(date,
150 plusZ, sunNormal.normalize(),
151 plusZ, new FieldPVCoordinates<>(field, phasingNormal),
152 1.0e-9);
153
154 // add compensation
155 return new FieldAttitude<>(frame, compensation.addOffset(base.getOrientation()));
156
157 }
158
159 }