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.frames;
18  
19  import java.util.concurrent.atomic.AtomicReference;
20  
21  import org.hipparchus.CalculusFieldElement;
22  import org.orekit.errors.FrameAncestorException;
23  import org.orekit.errors.OrekitMessages;
24  import org.orekit.time.AbsoluteDate;
25  import org.orekit.time.FieldAbsoluteDate;
26  
27  
28  /** Frame whose transform from its parent can be updated.
29   * <p>This class allows to control the relative position of two parts
30   * of the global frames tree using any two frames in each part as
31   * control handles. Consider the following simplified frames tree as an
32   * example:</p>
33   * <pre>
34   *              GCRF
35   *                |
36   *  --------------------------------
37   *  |             |                |
38   * Sun        satellite          Earth
39   *                |                |
40   *        on-board antenna   ground station
41   *                                 |
42   *                          tracking antenna
43   * </pre>
44   * <p>Tracking measurements really correspond to the link between the ground
45   * and on-board antennas. This is tightly linked to the transform between
46   * these two frames, however neither frame is the direct parent frame of the
47   * other one: the path involves four intermediate frames. When we process a
48   * measurement, what we really want to update is the transform that defines
49   * the satellite frame with respect to its parent GCRF frame.</p>
50   * <p>In order to implement the above case, the satellite frame is defined
51   * as an instance of this class and its {@link #updateTransform(Frame, Frame,
52   * Transform, AbsoluteDate) updateTransform} would be called each time we want
53   * to adjust the frame, i.e. each time we get a new measurement between the
54   * two antennas.</p>
55   * @author Luc Maisonobe
56   */
57  public class UpdatableFrame extends Frame {
58  
59      /** Build a non-inertial frame from its transform with respect to its parent.
60       * <p>calling this constructor is equivalent to call
61       * {@link #UpdatableFrame(Frame, Transform, String, boolean)
62       * UpdatableFrame(parent, transform, name, false)}.</p>
63       * @param parent parent frame (must be non-null)
64       * @param transform transform from parent frame to instance
65       * @param name name of the frame
66       * @exception IllegalArgumentException if the parent frame is null
67       */
68      public UpdatableFrame(final Frame parent, final Transform transform, final String name)
69          throws IllegalArgumentException {
70          this(parent, transform, name, false);
71      }
72  
73      /** Build a frame from its transform with respect to its parent.
74       * <p>The convention for the transform is that it is from parent
75       * frame to instance. This means that the two following frames
76       * are similar:</p>
77       * <pre>
78       * Frame frame1 = new Frame(FramesFactory.getGCRF(), new Transform(t1, t2));
79       * Frame frame2 = new Frame(new Frame(FramesFactory.getGCRF(), t1), t2);
80       * </pre>
81       * @param parent parent frame (must be non-null)
82       * @param transform transform from parent frame to instance
83       * @param name name of the frame
84       * @param pseudoInertial true if frame is considered pseudo-inertial
85       * (i.e. suitable for propagating orbit)
86       * @exception IllegalArgumentException if the parent frame is null
87       */
88      public UpdatableFrame(final Frame parent, final Transform transform, final String name,
89                            final boolean pseudoInertial)
90          throws IllegalArgumentException {
91          super(parent, new UpdatableProvider(transform), name, pseudoInertial);
92      }
93  
94      /** Update the transform from parent frame implicitly according to two other
95       * frames.
96  
97       * <p>This method allows to control the relative position of two parts
98       * of the global frames tree using any two frames in each part as
99       * control handles. Consider the following simplified frames tree as an
100      * example:</p>
101      * <pre>
102      *              GCRF
103      *                |
104      *  --------------------------------
105      *  |             |                |
106      * Sun        satellite          Earth
107      *                |                |
108      *        on-board antenna   ground station
109      *                                 |
110      *                          tracking antenna
111      * </pre>
112      * <p>Tracking measurements really correspond to the link between the ground
113      * and on-board antennas. This is tightly linked to the transform between
114      * these two frames, however neither frame is the direct parent frame of the
115      * other one: the path involves four intermediate frames. When we process a
116      * measurement, what we really want to update is the transform that defines
117      * the satellite frame with respect to its parent GCRF frame. This
118      * is the purpose of this method. This update is done by the following call,
119      * where <code>measurementTransform</code> represents the measurement as a
120      * simple translation transform between the two antenna frames:</p>
121      * <pre><code>
122      * satellite.updateTransform(onBoardAntenna, trackingAntenna,
123      *                           measurementTransform, date);
124      * </code></pre>
125      * <p>One way to represent the behavior of the method is to consider the
126      * sub-tree rooted at the instance on one hand (satellite and on-board antenna
127      * in the example above) and the tree containing all the other frames on the
128      * other hand (GCRF, Sun, Earth, ground station, tracking antenna).
129      * Both tree are considered as two solid sets linked together by a flexible
130      * spring, which is the transform we want to update. The method stretches the
131      * spring to make sure the transform between the two specified frames (one in
132      * each tree part) matches the specified transform.</p>
133      * @param f1 first control frame (may be the instance itself)
134      * @param f2 second control frame (may be the instance itself)
135      * @param f1Tof2 desired transform from first to second control frame
136      * @param date date of the transform
137      */
138     public void updateTransform(final Frame f1, final Frame f2, final Transform f1Tof2,
139                                 final AbsoluteDate date) {
140 
141         Frame fA = f1;
142         Frame fB = f2;
143         Transform fAtoB = f1Tof2;
144 
145         // make sure f1 is not a child of the instance
146         if (fA.isChildOf(this) || fA == this) {
147 
148             if (fB.isChildOf(this) || fB == this) {
149                 throw new FrameAncestorException(OrekitMessages.FRAME_ANCESTOR_OF_BOTH_FRAMES,
150                                                  getName(), fA.getName(), fB.getName());
151             }
152 
153             // swap f1 and f2 to make sure the child is f2
154             final Frame tmp = fA;
155             fA = fB;
156             fB = tmp;
157             fAtoB = fAtoB.getInverse();
158 
159         } else  if (!(fB.isChildOf(this) || fB == this)) {
160             throw new FrameAncestorException(OrekitMessages.FRAME_ANCESTOR_OF_NEITHER_FRAME,
161                                              getName(), fA.getName(), fB.getName());
162         }
163 
164         // rebuild the transform by traveling from parent to self
165         // WITHOUT using the existing provider from parent to self that will be updated
166         final Transform parentTofA   = getParent().getTransformTo(fA, date);
167         final Transform fBtoSelf     = fB.getTransformTo(this, date);
168         final Transform fAtoSelf     = new Transform(date, fAtoB, fBtoSelf);
169         final Transform parentToSelf = new Transform(date, parentTofA, fAtoSelf);
170 
171         // update the existing provider from parent to self
172         ((UpdatableProvider) getTransformProvider()).setTransform(parentToSelf);
173 
174     }
175 
176     /** Local provider for transforms. */
177     private static class UpdatableProvider implements TransformProvider {
178 
179         /** Current transform. */
180         private AtomicReference<Transform> transform;
181 
182         /** Simple constructor.
183          * @param transform initial value of the transform
184          */
185         UpdatableProvider(final Transform transform) {
186             this.transform = new AtomicReference<>(transform);
187         }
188 
189         /** Update the transform from the parent frame to the instance.
190          * @param transform new transform from parent frame to instance
191          */
192         public void setTransform(final Transform transform) {
193             this.transform.set(transform);
194         }
195 
196         /** {@inheritDoc} */
197         public Transform getTransform(final AbsoluteDate date) {
198             return transform.get();
199         }
200 
201         /** {@inheritDoc} */
202         @Override
203         public <T extends CalculusFieldElement<T>> FieldTransform<T> getTransform(final FieldAbsoluteDate<T> date) {
204             return new FieldTransform<>(date.getField(), transform.get());
205         }
206     }
207 
208 }