1   /* Copyright 2002-2026 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 = new PVCoordinates(Vector3D.PLUS_K);
74  
75      /** Sun motion model. */
76      private final ExtendedPositionProvider sun;
77  
78      /** Normal to the plane where the Sun must remain. */
79      private final PVCoordinates phasingNormal;
80  
81      /** Creates a new instance.
82       * @param inertialFrame frame in which orbital velocities are computed
83       * @param groundPointingLaw ground pointing attitude provider without yaw compensation
84       * @param sun sun motion model
85       * @param phasingAxis satellite axis that must be roughly in Sun direction
86       * (if solar arrays rotation axis is Y, then this axis should be either +X or -X)
87       * @since 7.1
88       */
89      public YawSteering(final Frame inertialFrame,
90                         final GroundPointing groundPointingLaw,
91                         final ExtendedPositionProvider sun,
92                         final Vector3D phasingAxis) {
93          super(inertialFrame, groundPointingLaw.getBodyFrame(), groundPointingLaw);
94          this.sun = sun;
95          this.phasingNormal = new PVCoordinates(Vector3D.crossProduct(Vector3D.PLUS_K, phasingAxis).normalize(),
96                                                 Vector3D.ZERO,
97                                                 Vector3D.ZERO);
98      }
99  
100     /** {@inheritDoc} */
101     @Override
102     public Attitude getAttitude(final PVCoordinatesProvider pvProv,
103                                 final AbsoluteDate date, final Frame frame) {
104 
105         // attitude from base attitude provider
106         final Attitude base = getBaseState(pvProv, date, frame);
107 
108         // Compensation rotation definition :
109         //  . Z satellite axis is unchanged
110         //  . phasing axis shall be aligned to sun direction
111         final PVCoordinates sunDirection = new PVCoordinates(pvProv.getPVCoordinates(date, frame),
112                                                              sun.getPVCoordinates(date, frame));
113         final PVCoordinates sunNormal =
114                 PVCoordinates.crossProduct(PLUS_Z, base.getOrientation().applyTo(sunDirection));
115         final TimeStampedAngularCoordinates compensation =
116                 new TimeStampedAngularCoordinates(date,
117                                                   PLUS_Z, sunNormal.normalize(),
118                                                   PLUS_Z, phasingNormal,
119                                                   1.0e-9);
120 
121         // add compensation
122         return new Attitude(frame, compensation.addOffset(base.getOrientation()));
123 
124     }
125 
126     /** {@inheritDoc} */
127     @Override
128     public <T extends CalculusFieldElement<T>> FieldAttitude<T> getAttitude(final FieldPVCoordinatesProvider<T> pvProv,
129                                                                             final FieldAbsoluteDate<T> date,
130                                                                             final Frame frame) {
131 
132         final Field<T>              field = date.getField();
133         final FieldVector3D<T>      zero  = FieldVector3D.getZero(field);
134         final FieldPVCoordinates<T> plusZ = new FieldPVCoordinates<>(FieldVector3D.getPlusK(field), zero, zero);
135 
136         // attitude from base attitude provider
137         final FieldAttitude<T> base = getBaseState(pvProv, date, frame);
138 
139         // Compensation rotation definition :
140         //  . Z satellite axis is unchanged
141         //  . phasing axis shall be aligned to sun direction
142         final FieldPVCoordinates<T> sunDirection =
143                         new FieldPVCoordinates<>(pvProv.getPVCoordinates(date, frame),
144                                                  sun.getPVCoordinates(date, frame));
145         final FieldPVCoordinates<T> sunNormal =
146                 plusZ.crossProduct(base.getOrientation().applyTo(sunDirection));
147         final TimeStampedFieldAngularCoordinates<T> compensation =
148                 new TimeStampedFieldAngularCoordinates<>(date,
149                                                          plusZ, sunNormal.normalize(),
150                                                          plusZ, new FieldPVCoordinates<>(field, phasingNormal),
151                                                          1.0e-9);
152 
153         // add compensation
154         return new FieldAttitude<>(frame, compensation.addOffset(base.getOrientation()));
155 
156     }
157 
158 }