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é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 }