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.utils;
18  
19  import org.hipparchus.analysis.differentiation.Derivative;
20  import org.hipparchus.geometry.euclidean.threed.FieldRotation;
21  import org.hipparchus.geometry.euclidean.threed.Rotation;
22  import org.hipparchus.geometry.euclidean.threed.RotationConvention;
23  import org.hipparchus.geometry.euclidean.threed.Vector3D;
24  import org.orekit.time.AbsoluteDate;
25  import org.orekit.time.TimeOffset;
26  import org.orekit.time.TimeStamped;
27  
28  /** {@link TimeStamped time-stamped} version of {@link AngularCoordinates}.
29   * <p>Instances of this class are guaranteed to be immutable.</p>
30   * @author Luc Maisonobe
31   * @since 7.0
32   */
33  public class TimeStampedAngularCoordinates extends AngularCoordinates implements TimeStamped {
34  
35      /** The date. */
36      private final AbsoluteDate date;
37  
38      /** Builds a rotation/rotation rate pair.
39       * @param date coordinates date
40       * @param coordinates coordinates
41       * @since 14.0
42       */
43      public TimeStampedAngularCoordinates(final AbsoluteDate date,
44                                           final AngularCoordinates coordinates) {
45          this(date, coordinates.getRotation(), coordinates.getRotationRate(), coordinates.getRotationAcceleration());
46      }
47  
48      /** Builds a rotation/rotation rate pair.
49       * @param date coordinates date
50       * @param rotation rotation
51       * @param rotationRate rotation rate Ω (rad/s)
52       * @param rotationAcceleration rotation acceleration dΩ/dt (rad²/s²)
53       */
54      public TimeStampedAngularCoordinates(final AbsoluteDate date,
55                                           final Rotation rotation,
56                                           final Vector3D rotationRate,
57                                           final Vector3D rotationAcceleration) {
58          super(rotation, rotationRate, rotationAcceleration);
59          this.date = date;
60      }
61  
62      /** Build the rotation that transforms a pair of pv coordinates into another pair.
63  
64       * <p><em>WARNING</em>! This method requires much more stringent assumptions on
65       * its parameters than the similar {@link Rotation#Rotation(Vector3D, Vector3D,
66       * Vector3D, Vector3D) constructor} from the {@link Rotation Rotation} class.
67       * As far as the Rotation constructor is concerned, the {@code v₂} vector from
68       * the second pair can be slightly misaligned. The Rotation constructor will
69       * compensate for this misalignment and create a rotation that ensure {@code
70       * v₁ = r(u₁)} and {@code v₂ ∈ plane (r(u₁), r(u₂))}. <em>THIS IS NOT
71       * TRUE ANYMORE IN THIS CLASS</em>! As derivatives are involved and must be
72       * preserved, this constructor works <em>only</em> if the two pairs are fully
73       * consistent, i.e. if a rotation exists that fulfill all the requirements: {@code
74       * v₁ = r(u₁)}, {@code v₂ = r(u₂)}, {@code dv₁/dt = dr(u₁)/dt}, {@code dv₂/dt
75       * = dr(u₂)/dt}, {@code d²v₁/dt² = d²r(u₁)/dt²}, {@code d²v₂/dt² = d²r(u₂)/dt²}.</p>
76  
77       * @param date coordinates date
78       * @param u1 first vector of the origin pair
79       * @param u2 second vector of the origin pair
80       * @param v1 desired image of u1 by the rotation
81       * @param v2 desired image of u2 by the rotation
82       * @param tolerance relative tolerance factor used to check singularities
83       */
84      public TimeStampedAngularCoordinates(final AbsoluteDate date,
85                                           final PVCoordinates u1, final PVCoordinates u2,
86                                           final PVCoordinates v1, final PVCoordinates v2,
87                                           final double tolerance) {
88          super(u1, u2, v1, v2, tolerance);
89          this.date = date;
90      }
91  
92      /** Build one of the rotations that transform one pv coordinates into another one.
93  
94       * <p>Except for a possible scale factor, if the instance were
95       * applied to the vector u it will produce the vector v. There is an
96       * infinite number of such rotations, this constructor choose the
97       * one with the smallest associated angle (i.e. the one whose axis
98       * is orthogonal to the (u, v) plane). If u and v are collinear, an
99       * arbitrary rotation axis is chosen.</p>
100 
101      * @param date coordinates date
102      * @param u origin vector
103      * @param v desired image of u by the rotation
104      */
105     public TimeStampedAngularCoordinates(final AbsoluteDate date,
106                                          final PVCoordinates u, final PVCoordinates v) {
107         super(u, v);
108         this.date = date;
109     }
110 
111     /** Builds a TimeStampedAngularCoordinates from  a {@link FieldRotation}&lt;{@link Derivative}&gt;.
112      * <p>
113      * The rotation components must have time as their only derivation parameter and
114      * have consistent derivation orders.
115      * </p>
116      * @param date coordinates date
117      * @param r rotation with time-derivatives embedded within the coordinates
118      * @param <U> type of the derivative
119      */
120     public <U extends Derivative<U>>TimeStampedAngularCoordinates(final AbsoluteDate date,
121                                                                   final FieldRotation<U> r) {
122         super(r);
123         this.date = date;
124     }
125 
126     /** {@inheritDoc} */
127     public AbsoluteDate getDate() {
128         return date;
129     }
130 
131     /** Revert a rotation/rotation rate pair.
132      * Build a pair which reverse the effect of another pair.
133      * @return a new pair whose effect is the reverse of the effect
134      * of the instance
135      */
136     @Override
137     public TimeStampedAngularCoordinates revert() {
138         return new TimeStampedAngularCoordinates(date, super.revert());
139     }
140 
141     /** Get a time-shifted state.
142      * <p>
143      * The state can be slightly shifted to close dates. This shift is based on
144      * a simple linear model. It is <em>not</em> intended as a replacement for
145      * proper attitude propagation but should be sufficient for either small
146      * time shifts or coarse accuracy.
147      * </p>
148      * @param dt time shift in seconds
149      * @return a new state, shifted with respect to the instance (which is immutable)
150      */
151     @Override
152     public TimeStampedAngularCoordinates shiftedBy(final double dt) {
153         final AngularCoordinates sac = super.shiftedBy(dt);
154         return new TimeStampedAngularCoordinates(date.shiftedBy(dt),
155                                                  sac.getRotation(), sac.getRotationRate(), sac.getRotationAcceleration());
156 
157     }
158 
159     /** Get a time-shifted state.
160      * <p>
161      * The state can be slightly shifted to close dates. This shift is based on
162      * a simple linear model. It is <em>not</em> intended as a replacement for
163      * proper attitude propagation but should be sufficient for either small
164      * time shifts or coarse accuracy.
165      * </p>
166      * @param dt time shift in seconds
167      * @return a new state, shifted with respect to the instance (which is immutable)
168      * @since 13.0
169      */
170     @Override
171     public TimeStampedAngularCoordinates shiftedBy(final TimeOffset dt) {
172         final AngularCoordinates sac = super.shiftedBy(dt);
173         return new TimeStampedAngularCoordinates(date.shiftedBy(dt),
174                                                  sac.getRotation(), sac.getRotationRate(), sac.getRotationAcceleration());
175 
176     }
177 
178     /** Add an offset from the instance.
179      * <p>
180      * We consider here that the offset rotation is applied first and the
181      * instance is applied afterward. Note that angular coordinates do <em>not</em>
182      * commute under this operation, i.e. {@code a.addOffset(b)} and {@code
183      * b.addOffset(a)} lead to <em>different</em> results in most cases.
184      * </p>
185      * <p>
186      * The two methods {@link #addOffset(AngularCoordinates) addOffset} and
187      * {@link #subtractOffset(AngularCoordinates) subtractOffset} are designed
188      * so that round trip applications are possible. This means that both {@code
189      * ac1.subtractOffset(ac2).addOffset(ac2)} and {@code
190      * ac1.addOffset(ac2).subtractOffset(ac2)} return angular coordinates equal to ac1.
191      * </p>
192      * @param offset offset to subtract
193      * @return new instance, with offset subtracted
194      * @see #subtractOffset(AngularCoordinates)
195      */
196     @Override
197     public TimeStampedAngularCoordinates addOffset(final AngularCoordinates offset) {
198         final Vector3D rOmega    = getRotation().applyTo(offset.getRotationRate());
199         final Vector3D rOmegaDot = getRotation().applyTo(offset.getRotationAcceleration());
200         return new TimeStampedAngularCoordinates(date,
201                                                  getRotation().compose(offset.getRotation(), RotationConvention.VECTOR_OPERATOR),
202                                                  getRotationRate().add(rOmega),
203                                                  new Vector3D( 1.0, getRotationAcceleration(),
204                                                                1.0, rOmegaDot,
205                                                               -1.0, Vector3D.crossProduct(getRotationRate(), rOmega)));
206     }
207 
208     /** Subtract an offset from the instance.
209      * <p>
210      * We consider here that the offset rotation is applied first and the
211      * instance is applied afterward. Note that angular coordinates do <em>not</em>
212      * commute under this operation, i.e. {@code a.subtractOffset(b)} and {@code
213      * b.subtractOffset(a)} lead to <em>different</em> results in most cases.
214      * </p>
215      * <p>
216      * The two methods {@link #addOffset(AngularCoordinates) addOffset} and
217      * {@link #subtractOffset(AngularCoordinates) subtractOffset} are designed
218      * so that round trip applications are possible. This means that both {@code
219      * ac1.subtractOffset(ac2).addOffset(ac2)} and {@code
220      * ac1.addOffset(ac2).subtractOffset(ac2)} return angular coordinates equal to ac1.
221      * </p>
222      * @param offset offset to subtract
223      * @return new instance, with offset subtracted
224      * @see #addOffset(AngularCoordinates)
225      */
226     @Override
227     public TimeStampedAngularCoordinates subtractOffset(final AngularCoordinates offset) {
228         return addOffset(offset.revert());
229     }
230 
231 }