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.FieldRotation;
22  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
23  import org.hipparchus.geometry.euclidean.threed.Rotation;
24  import org.hipparchus.geometry.euclidean.threed.RotationConvention;
25  import org.hipparchus.geometry.euclidean.threed.Vector3D;
26  import org.orekit.frames.FieldTransform;
27  import org.orekit.frames.Frame;
28  import org.orekit.frames.Transform;
29  import org.orekit.time.AbsoluteDate;
30  import org.orekit.time.FieldAbsoluteDate;
31  import org.orekit.utils.FieldPVCoordinates;
32  import org.orekit.utils.FieldPVCoordinatesProvider;
33  import org.orekit.utils.PVCoordinates;
34  import org.orekit.utils.PVCoordinatesProvider;
35  import org.orekit.utils.TimeStampedAngularCoordinates;
36  import org.orekit.utils.TimeStampedFieldAngularCoordinates;
37  import org.orekit.utils.TimeStampedFieldPVCoordinates;
38  import org.orekit.utils.TimeStampedPVCoordinates;
39  
40  
41  /**
42   * This class handles yaw compensation attitude provider.
43  
44   * <p>
45   * Yaw compensation is mainly used for Earth observation satellites. As a
46   * satellites moves along its track, the image of ground points move on
47   * the focal point of the optical sensor. This motion is a combination of
48   * the satellite motion, but also on the Earth rotation and on the current
49   * attitude (in particular if the pointing includes Roll or Pitch offset).
50   * In order to reduce geometrical distortion, the yaw angle is changed a
51   * little from the simple ground pointing attitude such that the apparent
52   * motion of ground points is along a prescribed axis (orthogonal to the
53   * optical sensors rows), taking into account all effects.
54   * </p>
55   * <p>
56   * This attitude is implemented as a wrapper on top of an underlying ground
57   * pointing law that defines the roll and pitch angles.
58   * </p>
59   * <p>
60   * Instances of this class are guaranteed to be immutable.
61   * </p>
62   * @see     GroundPointing
63   * @author V&eacute;ronique Pommier-Maurussane
64   */
65  public class YawCompensation extends GroundPointing implements AttitudeProviderModifier {
66  
67      /** J axis. */
68      private static final PVCoordinates PLUS_J =
69              new PVCoordinates(Vector3D.PLUS_J, Vector3D.ZERO, Vector3D.ZERO);
70  
71      /** K axis. */
72      private static final PVCoordinates PLUS_K =
73              new PVCoordinates(Vector3D.PLUS_K, Vector3D.ZERO, Vector3D.ZERO);
74  
75      /** Underlying ground pointing attitude provider.  */
76      private final GroundPointing groundPointingLaw;
77  
78      /** Creates a new instance.
79       * @param inertialFrame frame in which orbital velocities are computed
80       * @param groundPointingLaw ground pointing attitude provider without yaw compensation
81       * @since 7.1
82       */
83      public YawCompensation(final Frame inertialFrame, final GroundPointing groundPointingLaw) {
84          super(inertialFrame, groundPointingLaw.getBodyFrame());
85          this.groundPointingLaw = groundPointingLaw;
86      }
87  
88      /** Get the underlying (ground pointing) attitude provider.
89       * @return underlying attitude provider, which in this case is a {@link GroundPointing} instance
90       */
91      public AttitudeProvider getUnderlyingAttitudeProvider() {
92          return groundPointingLaw;
93      }
94  
95      /** {@inheritDoc} */
96      public TimeStampedPVCoordinates getTargetPV(final PVCoordinatesProvider pvProv,
97                                                  final AbsoluteDate date, final Frame frame) {
98          return groundPointingLaw.getTargetPV(pvProv, date, frame);
99      }
100 
101     /** {@inheritDoc} */
102     public <T extends CalculusFieldElement<T>> TimeStampedFieldPVCoordinates<T> getTargetPV(final FieldPVCoordinatesProvider<T> pvProv,
103                                                                                         final FieldAbsoluteDate<T> date,
104                                                                                         final Frame frame) {
105         return groundPointingLaw.getTargetPV(pvProv, date, frame);
106     }
107 
108     /** Compute the base system state at given date, without compensation.
109      * @param pvProv provider for PV coordinates
110      * @param date date at which state is requested
111      * @param frame reference frame from which attitude is computed
112      * @return satellite base attitude state, i.e without compensation.
113      */
114     public Attitude getBaseState(final PVCoordinatesProvider pvProv,
115                                  final AbsoluteDate date, final Frame frame) {
116         return groundPointingLaw.getAttitude(pvProv, date, frame);
117     }
118 
119     /** Compute the base system state at given date, without compensation.
120      * @param pvProv provider for PV coordinates
121      * @param date date at which state is requested
122      * @param frame reference frame from which attitude is computed
123      * @param <T> type of the field elements
124      * @return satellite base attitude state, i.e without compensation.
125      * @since 9.0
126      */
127     public <T extends CalculusFieldElement<T>> FieldAttitude<T> getBaseState(final FieldPVCoordinatesProvider<T> pvProv,
128                                                                          final FieldAbsoluteDate<T> date, final Frame frame) {
129         return groundPointingLaw.getAttitude(pvProv, date, frame);
130     }
131 
132     /** {@inheritDoc} */
133     @Override
134     public Attitude getAttitude(final PVCoordinatesProvider pvProv,
135                                 final AbsoluteDate date, final Frame frame) {
136 
137         final Transform bodyToRef = getBodyFrame().getTransformTo(frame, date);
138 
139         // compute sliding target ground point
140         final PVCoordinates slidingRef  = getTargetPV(pvProv, date, frame);
141         final PVCoordinates slidingBody = bodyToRef.getInverse().transformPVCoordinates(slidingRef);
142 
143         // compute relative position of sliding ground point with respect to satellite
144         final PVCoordinates relativePosition =
145                 new PVCoordinates(pvProv.getPVCoordinates(date, frame), slidingRef);
146 
147         // compute relative velocity of fixed ground point with respect to sliding ground point
148         // the velocity part of the transformPVCoordinates for the sliding point ps
149         // from body frame to reference frame is:
150         //     d(ps_ref)/dt = r(d(ps_body)/dt + dq/dt) - Ω ⨯ ps_ref
151         // where r is the rotation part of the transform, Ω is the corresponding
152         // angular rate, and dq/dt is the derivative of the translation part of the
153         // transform (the translation itself, without derivative, is hidden in the
154         // ps_ref part in the cross product).
155         // The sliding point ps is co-located to a fixed ground point pf (i.e. they have the
156         // same position at time of computation), but this fixed point as zero velocity
157         // with respect to the ground. So the velocity part of the transformPVCoordinates
158         // for this fixed point can be computed using the same formula as above:
159         // from body frame to reference frame is:
160         //     d(pf_ref)/dt = r(0 + dq/dt) - Ω ⨯ pf_ref
161         // so remembering that the two points are at the same location at computation time,
162         // i.e. that at t=0 pf_ref=ps_ref, the relative velocity between the fixed point
163         // and the sliding point is given by the simple expression:
164         //     d(ps_ref)/dt - d(pf_ref)/dt = r(d(ps_body)/dt)
165         // the acceleration is computed by differentiating the expression, which gives:
166         //    d²(ps_ref)/dt² - d²(pf_ref)/dt² = r(d²(ps_body)/dt²) - Ω ⨯ [d(ps_ref)/dt - d(pf_ref)/dt]
167         final Vector3D v = bodyToRef.getRotation().applyTo(slidingBody.getVelocity());
168         final Vector3D a = new Vector3D(+1, bodyToRef.getRotation().applyTo(slidingBody.getAcceleration()),
169                                         -1, Vector3D.crossProduct(bodyToRef.getRotationRate(), v));
170         final PVCoordinates relativeVelocity = new PVCoordinates(v, a, Vector3D.ZERO);
171 
172         final PVCoordinates relativeNormal =
173                 PVCoordinates.crossProduct(relativePosition, relativeVelocity).normalize();
174 
175         // attitude definition :
176         //  . Z satellite axis points to sliding target
177         //  . target relative velocity is in (Z, X) plane, in the -X half plane part
178         return new Attitude(frame,
179                             new TimeStampedAngularCoordinates(date,
180                                                               relativePosition.normalize(),
181                                                               relativeNormal.normalize(),
182                                                               PLUS_K, PLUS_J,
183                                                               1.0e-9));
184 
185     }
186 
187     /** {@inheritDoc} */
188     @Override
189     public <T extends CalculusFieldElement<T>> FieldAttitude<T> getAttitude(final FieldPVCoordinatesProvider<T> pvProv,
190                                                                         final FieldAbsoluteDate<T> date, final Frame frame) {
191 
192         final Field<T>              field = date.getField();
193         final FieldVector3D<T>      zero  = FieldVector3D.getZero(field);
194         final FieldPVCoordinates<T> plusJ = new FieldPVCoordinates<>(FieldVector3D.getPlusJ(field), zero, zero);
195         final FieldPVCoordinates<T> plusK = new FieldPVCoordinates<>(FieldVector3D.getPlusK(field), zero, zero);
196 
197         final FieldTransform<T> bodyToRef = getBodyFrame().getTransformTo(frame, date);
198 
199         // compute sliding target ground point
200         final FieldPVCoordinates<T> slidingRef  = getTargetPV(pvProv, date, frame);
201         final FieldPVCoordinates<T> slidingBody = bodyToRef.getInverse().transformPVCoordinates(slidingRef);
202 
203         // compute relative position of sliding ground point with respect to satellite
204         final FieldPVCoordinates<T> relativePosition =
205                 new FieldPVCoordinates<>(pvProv.getPVCoordinates(date, frame), slidingRef);
206 
207         // compute relative velocity of fixed ground point with respect to sliding ground point
208         // the velocity part of the transformPVCoordinates for the sliding point ps
209         // from body frame to reference frame is:
210         //     d(ps_ref)/dt = r(d(ps_body)/dt + dq/dt) - Ω ⨯ ps_ref
211         // where r is the rotation part of the transform, Ω is the corresponding
212         // angular rate, and dq/dt is the derivative of the translation part of the
213         // transform (the translation itself, without derivative, is hidden in the
214         // ps_ref part in the cross product).
215         // The sliding point ps is co-located to a fixed ground point pf (i.e. they have the
216         // same position at time of computation), but this fixed point as zero velocity
217         // with respect to the ground. So the velocity part of the transformPVCoordinates
218         // for this fixed point can be computed using the same formula as above:
219         // from body frame to reference frame is:
220         //     d(pf_ref)/dt = r(0 + dq/dt) - Ω ⨯ pf_ref
221         // so remembering that the two points are at the same location at computation time,
222         // i.e. that at t=0 pf_ref=ps_ref, the relative velocity between the fixed point
223         // and the sliding point is given by the simple expression:
224         //     d(ps_ref)/dt - d(pf_ref)/dt = r(d(ps_body)/dt)
225         // the acceleration is computed by differentiating the expression, which gives:
226         //    d²(ps_ref)/dt² - d²(pf_ref)/dt² = r(d²(ps_body)/dt²) - Ω ⨯ [d(ps_ref)/dt - d(pf_ref)/dt]
227         final FieldVector3D<T> v = bodyToRef.getRotation().applyTo(slidingBody.getVelocity());
228         final FieldVector3D<T> a = new FieldVector3D<>(+1, bodyToRef.getRotation().applyTo(slidingBody.getAcceleration()),
229                                                        -1, FieldVector3D.crossProduct(bodyToRef.getRotationRate(), v));
230         final FieldPVCoordinates<T> relativeVelocity = new FieldPVCoordinates<>(v, a, FieldVector3D.getZero(date.getField()));
231 
232         final FieldPVCoordinates<T> relativeNormal =
233                         relativePosition.crossProduct(relativeVelocity).normalize();
234 
235         // attitude definition :
236         //  . Z satellite axis points to sliding target
237         //  . target relative velocity is in (Z, X) plane, in the -X half plane part
238         return new FieldAttitude<>(frame,
239                                    new TimeStampedFieldAngularCoordinates<>(date,
240                                                                             relativePosition.normalize(),
241                                                                             relativeNormal.normalize(),
242                                                                             plusK, plusJ,
243                                                                             1.0e-9));
244 
245     }
246 
247     /** Compute the yaw compensation angle at date.
248      * @param pvProv provider for PV coordinates
249      * @param date date at which compensation is requested
250      * @param frame reference frame from which attitude is computed
251      * @return yaw compensation angle for orbit.
252      */
253     public double getYawAngle(final PVCoordinatesProvider pvProv,
254                               final AbsoluteDate date, final Frame frame) {
255         final Rotation rBase        = getBaseState(pvProv, date, frame).getRotation();
256         final Rotation rCompensated = getAttitude(pvProv, date, frame).getRotation();
257         final Rotation compensation = rCompensated.compose(rBase.revert(), RotationConvention.VECTOR_OPERATOR);
258         return -compensation.applyTo(Vector3D.PLUS_I).getAlpha();
259     }
260 
261     /** Compute the yaw compensation angle at date.
262      * @param pvProv provider for PV coordinates
263      * @param date date at which compensation is requested
264      * @param frame reference frame from which attitude is computed
265      * @param <T> type of the field elements
266      * @return yaw compensation angle for orbit.
267      * @since 9.0
268      */
269     public <T extends CalculusFieldElement<T>> T getYawAngle(final FieldPVCoordinatesProvider<T> pvProv,
270                                                          final FieldAbsoluteDate<T> date,
271                                                          final Frame frame) {
272         final FieldRotation<T> rBase        = getBaseState(pvProv, date, frame).getRotation();
273         final FieldRotation<T> rCompensated = getAttitude(pvProv, date, frame).getRotation();
274         final FieldRotation<T> compensation = rCompensated.compose(rBase.revert(), RotationConvention.VECTOR_OPERATOR);
275         return compensation.applyTo(Vector3D.PLUS_I).getAlpha().negate();
276     }
277 
278 }