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 }