1   /* Copyright 2002-2021 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.FieldPVCoordinates;
27  import org.orekit.utils.FieldPVCoordinatesProvider;
28  import org.orekit.utils.PVCoordinates;
29  import org.orekit.utils.PVCoordinatesProvider;
30  import org.orekit.utils.TimeStampedAngularCoordinates;
31  import org.orekit.utils.TimeStampedFieldAngularCoordinates;
32  import org.orekit.utils.TimeStampedFieldPVCoordinates;
33  import org.orekit.utils.TimeStampedPVCoordinates;
34  
35  
36  /**
37   * This class handles yaw steering law.
38  
39   * <p>
40   * Yaw steering is mainly used for low Earth orbiting satellites with no
41   * missions-related constraints on yaw angle. It sets the yaw angle in
42   * such a way the solar arrays have maximal lighting without changing the
43   * roll and pitch.
44   * </p>
45   * <p>
46   * The motion in yaw is smooth when the Sun is far from the orbital plane,
47   * but gets more and more <i>square like</i> as the Sun gets closer to the
48   * orbital plane. The degenerate extreme case with the Sun in the orbital
49   * plane leads to a yaw angle switching between two steady states, with
50   * instantaneaous π radians rotations at each switch, two times per orbit.
51   * This degenerate case is clearly not operationally sound so another pointing
52   * mode is chosen when Sun comes closer than some predefined threshold to the
53   * orbital plane.
54   * </p>
55   * <p>
56   * This class can handle (for now) only a theoretically perfect yaw steering
57   * (i.e. the yaw angle is exactly the optimal angle). Smoothed yaw steering with a
58   * few sine waves approaching the optimal angle will be added in the future if
59   * needed.
60   * </p>
61   * <p>
62   * This attitude is implemented as a wrapper on top of an underlying ground
63   * pointing law that defines the roll and pitch angles.
64   * </p>
65   * <p>
66   * Instances of this class are guaranteed to be immutable.
67   * </p>
68   * @see    GroundPointing
69   * @author Luc Maisonobe
70   */
71  public class YawSteering extends GroundPointing implements AttitudeProviderModifier {
72  
73      /** Pointing axis. */
74      private static final PVCoordinates PLUS_Z =
75              new PVCoordinates(Vector3D.PLUS_K, Vector3D.ZERO, Vector3D.ZERO);
76  
77      /** Underlying ground pointing attitude provider.  */
78      private final GroundPointing groundPointingLaw;
79  
80      /** Sun motion model. */
81      private final PVCoordinatesProvider sun;
82  
83      /** Normal to the plane where the Sun must remain. */
84      private final PVCoordinates phasingNormal;
85  
86      /** Creates a new instance.
87       * @param inertialFrame frame in which orbital velocities are computed
88       * @param groundPointingLaw ground pointing attitude provider without yaw compensation
89       * @param sun sun motion model
90       * @param phasingAxis satellite axis that must be roughly in Sun direction
91       * (if solar arrays rotation axis is Y, then this axis should be either +X or -X)
92       * @since 7.1
93       */
94      public YawSteering(final Frame inertialFrame,
95                         final GroundPointing groundPointingLaw,
96                         final PVCoordinatesProvider sun,
97                         final Vector3D phasingAxis) {
98          super(inertialFrame, groundPointingLaw.getBodyFrame());
99          this.groundPointingLaw = groundPointingLaw;
100         this.sun = sun;
101         this.phasingNormal = new PVCoordinates(Vector3D.crossProduct(Vector3D.PLUS_K, phasingAxis).normalize(),
102                                                Vector3D.ZERO,
103                                                Vector3D.ZERO);
104     }
105 
106     /** Get the underlying (ground pointing) attitude provider.
107      * @return underlying attitude provider, which in this case is a {@link GroundPointing} instance
108      */
109     public AttitudeProvider getUnderlyingAttitudeProvider() {
110         return groundPointingLaw;
111     }
112 
113     /** {@inheritDoc} */
114     public TimeStampedPVCoordinates getTargetPV(final PVCoordinatesProvider pvProv,
115                                                 final AbsoluteDate date, final Frame frame) {
116         return groundPointingLaw.getTargetPV(pvProv, date, frame);
117     }
118 
119     /** {@inheritDoc} */
120     public <T extends CalculusFieldElement<T>> TimeStampedFieldPVCoordinates<T> getTargetPV(final FieldPVCoordinatesProvider<T> pvProv,
121                                                                                         final FieldAbsoluteDate<T> date,
122                                                                                         final Frame frame) {
123         return groundPointingLaw.getTargetPV(pvProv, date, frame);
124     }
125 
126     /** Compute the base system state at given date, without compensation.
127      * @param pvProv provider for PV coordinates
128      * @param date date at which state is requested
129      * @param frame reference frame from which attitude is computed
130      * @return satellite base attitude state, i.e without compensation.
131      */
132     public Attitude getBaseState(final PVCoordinatesProvider pvProv,
133                                  final AbsoluteDate date, final Frame frame) {
134         return groundPointingLaw.getAttitude(pvProv, date, frame);
135     }
136 
137     /** Compute the base system state at given date, without compensation.
138      * @param pvProv provider for PV coordinates
139      * @param date date at which state is requested
140      * @param frame reference frame from which attitude is computed
141      * @param <T> type of the field elements
142      * @return satellite base attitude state, i.e without compensation.
143      * @since 9.0
144      */
145     public <T extends CalculusFieldElement<T>> FieldAttitude<T> getBaseState(final FieldPVCoordinatesProvider<T> pvProv,
146                                                                          final FieldAbsoluteDate<T> date, final Frame frame) {
147         return groundPointingLaw.getAttitude(pvProv, date, frame);
148     }
149 
150     /** {@inheritDoc} */
151     @Override
152     public Attitude getAttitude(final PVCoordinatesProvider pvProv,
153                                 final AbsoluteDate date, final Frame frame) {
154 
155         // attitude from base attitude provider
156         final Attitude base = getBaseState(pvProv, date, frame);
157 
158         // Compensation rotation definition :
159         //  . Z satellite axis is unchanged
160         //  . phasing axis shall be aligned to sun direction
161         final PVCoordinates sunDirection = new PVCoordinates(pvProv.getPVCoordinates(date, frame),
162                                                              sun.getPVCoordinates(date, frame));
163         final PVCoordinates sunNormal =
164                 PVCoordinates.crossProduct(PLUS_Z, base.getOrientation().applyTo(sunDirection));
165         final TimeStampedAngularCoordinates compensation =
166                 new TimeStampedAngularCoordinates(date,
167                                                   PLUS_Z, sunNormal.normalize(),
168                                                   PLUS_Z, phasingNormal,
169                                                   1.0e-9);
170 
171         // add compensation
172         return new Attitude(frame, compensation.addOffset(base.getOrientation()));
173 
174     }
175 
176     /** {@inheritDoc} */
177     @Override
178     public <T extends CalculusFieldElement<T>> FieldAttitude<T> getAttitude(final FieldPVCoordinatesProvider<T> pvProv,
179                                                                         final FieldAbsoluteDate<T> date, final Frame frame) {
180 
181         final Field<T>              field = date.getField();
182         final FieldVector3D<T>      zero  = FieldVector3D.getZero(field);
183         final FieldPVCoordinates<T> plusZ = new FieldPVCoordinates<>(FieldVector3D.getPlusK(field), zero, zero);
184 
185         // attitude from base attitude provider
186         final FieldAttitude<T> base = getBaseState(pvProv, date, frame);
187 
188         // Compensation rotation definition :
189         //  . Z satellite axis is unchanged
190         //  . phasing axis shall be aligned to sun direction
191         final FieldPVCoordinates<T> sunDirection =
192                         new FieldPVCoordinates<>(pvProv.getPVCoordinates(date, frame),
193                                                  new FieldPVCoordinates<>(field,
194                                                                           sun.getPVCoordinates(date.toAbsoluteDate(), frame)));
195         final FieldPVCoordinates<T> sunNormal =
196                 plusZ.crossProduct(base.getOrientation().applyTo(sunDirection));
197         final TimeStampedFieldAngularCoordinates<T> compensation =
198                 new TimeStampedFieldAngularCoordinates<>(date,
199                                                          plusZ, sunNormal.normalize(),
200                                                          plusZ, new FieldPVCoordinates<>(field, phasingNormal),
201                                                          1.0e-9);
202 
203         // add compensation
204         return new FieldAttitude<>(frame, compensation.addOffset(base.getOrientation()));
205 
206     }
207 
208 }