1   /* Copyright 2022-2026 Romain Serra
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.estimation.measurements;
18  
19  import org.hipparchus.CalculusFieldElement;
20  import org.hipparchus.Field;
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.Vector3D;
25  import org.orekit.bodies.BodyShape;
26  import org.orekit.bodies.FieldGeodeticPoint;
27  import org.orekit.bodies.GeodeticPoint;
28  import org.orekit.data.BodiesElements;
29  import org.orekit.data.FundamentalNutationArguments;
30  import org.orekit.frames.FieldStaticTransform;
31  import org.orekit.frames.FieldTransform;
32  import org.orekit.frames.Frame;
33  import org.orekit.frames.StaticTransform;
34  import org.orekit.frames.TopocentricFrame;
35  import org.orekit.frames.Transform;
36  import org.orekit.frames.TransformProvider;
37  import org.orekit.models.earth.displacement.StationDisplacement;
38  import org.orekit.time.AbsoluteDate;
39  import org.orekit.time.FieldAbsoluteDate;
40  import org.orekit.utils.ParameterDriver;
41  
42  /** Class modeling a ground station frame transform.
43   * <p>
44   * This class considers a position offset parameter w.r.t. a base {@link TopocentricFrame
45   * topocentric frame}.
46   * </p>
47   * <p>
48   * This class also adds parameters for an additional polar motion
49   * and an additional prime meridian orientation. Since these parameters will
50   * have the same name for all ground stations, they will be managed consistently
51   * and allow to estimate Earth orientation precisely (this is needed for precise
52   * orbit determination). The polar motion and prime meridian orientation will
53   * be applied <em>after</em> regular Earth orientation parameters, so the value
54   * of the estimated parameters will be correction to EOP, they will not be the
55   * complete EOP values by themselves. Basically, this means that for Earth, the
56   * following transforms are applied in order, between inertial frame and ground
57   * station frame (for non-Earth based ground stations, different precession nutation
58   * models and associated planet orientation parameters would be applied, if available):
59   * </p>
60   * @author Romain Serra
61   * @since 14.0
62   */
63  class GroundStationTransformProvider implements TransformProvider {
64  
65      /**
66       * Target frame.
67       */
68      private final Frame frame;
69  
70      /** Provider for Earth frame whose EOP parameters can be estimated. */
71      private final EstimatedEarthFrameProvider estimatedEarthFrameProvider;
72  
73      /** Earth frame whose EOP parameters can be estimated. */
74      private final Frame estimatedEarthFrame;
75  
76      /** Base frame associated with the station. */
77      private final TopocentricFrame baseFrame;
78  
79      /** Fundamental nutation arguments. */
80      private final FundamentalNutationArguments arguments;
81  
82      /** Displacement models. */
83      private final StationDisplacement[] displacements;
84  
85      /** Driver for position offset along the East axis. */
86      private final ParameterDriver eastOffsetDriver;
87  
88      /** Driver for position offset along the North axis. */
89      private final ParameterDriver northOffsetDriver;
90  
91      /** Driver for position offset along the zenith axis. */
92      private final ParameterDriver zenithOffsetDriver;
93  
94       /**
95       * Constructor.
96       * @param frame target frame
97       * @param baseFrame     base frame associated with the station, without *any* parametric model (no station offset,
98       *                      no polar motion, no meridian shift)
99       * @param eastOffsetDriver driver for position offset along the East axis
100      * @param northOffsetDriver driver for position offset along the North axis
101      * @param zenithOffsetDriver driver for position offset along the zenith axis
102      * @param estimatedEarthFrameProvider provider for Earth frame whose EOP parameters can be estimated
103      * @param fundamentalNutationArguments fundamental nutation arguments
104      * @param displacements ground station displacement model (tides, ocean loading, atmospheric loading, thermal
105      *                      effects...)
106      */
107     GroundStationTransformProvider(final Frame frame, final TopocentricFrame baseFrame,
108                                    final ParameterDriver eastOffsetDriver,
109                                    final ParameterDriver northOffsetDriver,
110                                    final ParameterDriver zenithOffsetDriver,
111                                    final EstimatedEarthFrameProvider estimatedEarthFrameProvider,
112                                    final FundamentalNutationArguments fundamentalNutationArguments,
113                                    final StationDisplacement... displacements) {
114         this.frame = frame;
115         this.baseFrame = baseFrame;
116 
117         this.estimatedEarthFrameProvider = estimatedEarthFrameProvider;
118         this.estimatedEarthFrame = new Frame(baseFrame.getParent(), estimatedEarthFrameProvider,
119                                              baseFrame.getParent() + "-estimated");
120         this.arguments = fundamentalNutationArguments;
121 
122         this.displacements = displacements.clone();
123         this.eastOffsetDriver   = eastOffsetDriver;
124         this.northOffsetDriver  = northOffsetDriver;
125         this.zenithOffsetDriver = zenithOffsetDriver;
126     }
127 
128     /** {@inheritDoc} */
129     @Override
130     public Transform getTransform(final AbsoluteDate date) {
131         // take Earth offsets into account
132         final Transform intermediateToBody = estimatedEarthFrameProvider.getTransform(date).getInverse();
133 
134         // take station offsets into account
135         final BodyShape baseShape = baseFrame.getParentShape();
136         final Vector3D  origin    = getOrigin(date);
137 
138         final GeodeticPoint originGP = baseShape.transform(origin, baseShape.getBodyFrame(), date);
139         final Transform offsetToIntermediate =
140                         new Transform(date,
141                                       new Transform(date,
142                                                     new Rotation(Vector3D.PLUS_I, Vector3D.PLUS_K,
143                                                                  originGP.getEast(), originGP.getZenith()),
144                                                     Vector3D.ZERO),
145                                       new Transform(date, origin));
146         if (baseFrame.getParent() == frame) {
147             return new Transform(date, offsetToIntermediate, intermediateToBody);
148         }
149 
150         // combine all transforms together
151         final Transform bodyToInert = baseFrame.getParent().getTransformTo(frame, date);
152 
153         return new Transform(date, offsetToIntermediate, new Transform(date, intermediateToBody, bodyToInert));
154     }
155 
156     /** {@inheritDoc} */
157     @Override
158     public StaticTransform getStaticTransform(final AbsoluteDate date) {
159         // take Earth offsets into account
160         final StaticTransform intermediateToBody = estimatedEarthFrameProvider.getStaticTransform(date).getInverse();
161 
162         // take station offsets into account
163         final BodyShape baseShape = baseFrame.getParentShape();
164         final Vector3D  origin    = getOrigin(date);
165 
166         final GeodeticPoint originGP = baseShape.transform(origin, baseShape.getBodyFrame(), date);
167         final StaticTransform offsetToIntermediate = StaticTransform.compose(date,
168                         StaticTransform.of(date,
169                                 new Rotation(Vector3D.PLUS_I, Vector3D.PLUS_K,
170                                         originGP.getEast(), originGP.getZenith())),
171                         StaticTransform.of(date, origin));
172         if (baseFrame.getParent() == frame) {
173             return StaticTransform.compose(date, offsetToIntermediate, intermediateToBody);
174         }
175 
176         // combine all transforms together
177         final StaticTransform bodyToInert = baseFrame.getParent().getStaticTransformTo(frame, date);
178 
179         return StaticTransform.compose(date, offsetToIntermediate, StaticTransform.compose(date, intermediateToBody, bodyToInert));
180     }
181 
182     /**
183      * Retrieve station's position in body shape frame.
184      * @param date date
185      * @return origin position
186      */
187     private Vector3D getOrigin(final AbsoluteDate date) {
188         final double    x          = eastOffsetDriver.getValue(date);
189         final double    y          = northOffsetDriver.getValue(date);
190         final double    z          = zenithOffsetDriver.getValue(date);
191         final Frame bodyFrame = baseFrame.getParentShape().getBodyFrame();
192         final StaticTransform staticTopoToBody = baseFrame.getStaticTransformTo(bodyFrame, date);
193         final Vector3D        originBeforeDisplacement     = staticTopoToBody.transformPosition(new Vector3D(x, y, z));
194         return originBeforeDisplacement.add(computeDisplacement(date, originBeforeDisplacement));
195     }
196 
197     /** Get the station displacement.
198      * @param date current date
199      * @param position raw position of the station in Earth frame
200      * before displacement is applied
201      * @return station displacement
202      */
203     private Vector3D computeDisplacement(final AbsoluteDate date, final Vector3D position) {
204         Vector3D displacement = Vector3D.ZERO;
205         if (arguments != null) {
206             final BodiesElements elements = arguments.evaluateAll(date);
207             for (final StationDisplacement sd : displacements) {
208                 // we consider all displacements apply to the same initial position,
209                 // i.e. they apply simultaneously, not according to some order
210                 displacement = displacement.add(sd.displacement(elements, estimatedEarthFrame, position));
211             }
212         }
213         return displacement;
214     }
215 
216     /** {@inheritDoc} */
217     @Override
218     public <T extends CalculusFieldElement<T>> FieldTransform<T> getTransform(final FieldAbsoluteDate<T> date) {
219 
220         // take Earth offsets into account
221         final FieldTransform<T> intermediateToBody = estimatedEarthFrameProvider.getTransform(date).getInverse();
222 
223         // take station offsets into account
224         final FieldVector3D<T> origin = getOrigin(date);
225         return getTransform(date, origin, intermediateToBody);
226 
227     }
228 
229     <T extends CalculusFieldElement<T>> FieldTransform<T> getTransform(final FieldAbsoluteDate<T> date,
230                                                                        final FieldVector3D<T> origin,
231                                                                        final FieldTransform<T> intermediateToBody) {
232         final Field<T>         field = date.getField();
233         final FieldVector3D<T> zero  = FieldVector3D.getZero(field);
234         final FieldVector3D<T> plusI = FieldVector3D.getPlusI(field);
235         final FieldVector3D<T> plusK = FieldVector3D.getPlusK(field);
236         final FieldGeodeticPoint<T> originGP = baseFrame.getParentShape().transform(origin,
237                 baseFrame.getParentShape().getBodyFrame(), date);
238         final FieldTransform<T> offsetToIntermediate =
239                 new FieldTransform<>(date,
240                         new FieldTransform<>(date,
241                                 new FieldRotation<>(plusI, plusK,
242                                         originGP.getEast(), originGP.getZenith()),
243                                 zero),
244                         new FieldTransform<>(date, origin));
245 
246         // combine all transforms together
247         if (baseFrame.getParent() == frame) {
248             return new FieldTransform<>(date, offsetToIntermediate, intermediateToBody);
249         }
250         final FieldTransform<T> bodyToInert = baseFrame.getParent().getTransformTo(frame, date);
251 
252         return new FieldTransform<>(date, offsetToIntermediate,
253                 new FieldTransform<>(date, intermediateToBody, bodyToInert));
254 
255     }
256 
257     /** {@inheritDoc} */
258     @Override
259     public <T extends CalculusFieldElement<T>> FieldStaticTransform<T> getStaticTransform(final FieldAbsoluteDate<T> date) {
260 
261         // take Earth offsets into account
262         final FieldStaticTransform<T> intermediateToBody = estimatedEarthFrameProvider.getStaticTransform(date).getInverse();
263 
264         final FieldVector3D<T> origin = new FieldVector3D<>(date.getField(), getOrigin(date.toAbsoluteDate()));
265         // take station offsets into account
266         final Field<T>         field = date.getField();
267         final FieldVector3D<T> plusI = FieldVector3D.getPlusI(field);
268         final FieldVector3D<T> plusK = FieldVector3D.getPlusK(field);
269         final FieldGeodeticPoint<T> originGP = baseFrame.getParentShape().transform(origin,
270                 baseFrame.getParentShape().getBodyFrame(), date);
271         final FieldStaticTransform<T> offsetToIntermediate = FieldStaticTransform.compose(date,
272                 FieldStaticTransform.of(date,
273                         new FieldRotation<>(plusI, plusK, originGP.getEast(), originGP.getZenith())),
274                 FieldStaticTransform.of(date, origin));
275 
276         // combine all transforms together
277         if (baseFrame.getParent() == frame) {
278             return FieldStaticTransform.compose(date, offsetToIntermediate, intermediateToBody);
279         }
280         final FieldStaticTransform<T> bodyToInert = baseFrame.getParent().getStaticTransformTo(frame, date);
281 
282         return FieldStaticTransform.compose(date, offsetToIntermediate,
283                 FieldStaticTransform.compose(date, intermediateToBody, bodyToInert));
284     }
285 
286     /**
287      * Retrieve station's position in body shape frame.
288      * @param <T> field type
289      * @param date date
290      * @return origin position
291      */
292     private <T extends CalculusFieldElement<T>> FieldVector3D<T> getOrigin(final FieldAbsoluteDate<T> date) {
293         final AbsoluteDate absoluteDate = date.toAbsoluteDate();
294         final Field<T> field = date.getField();
295         final T x          = field.getZero().newInstance(eastOffsetDriver.getValue(absoluteDate));
296         final T                       y          = field.getZero().newInstance(northOffsetDriver.getValue(absoluteDate));
297         final T                       z          = field.getZero().newInstance(zenithOffsetDriver.getValue(absoluteDate));
298         final Frame bodyFrame = baseFrame.getParentShape().getBodyFrame();
299         final FieldStaticTransform<T> staticTopoToBody = baseFrame.getStaticTransformTo(bodyFrame, date);
300         final FieldVector3D<T>        originBeforeDisplacement     = staticTopoToBody.transformPosition(new FieldVector3D<>(x, y, z));
301         return originBeforeDisplacement.add(computeDisplacement(absoluteDate, originBeforeDisplacement.toVector3D()));
302     }
303 
304 }