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