1 /* Copyright 2002-2026 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.estimation.measurements;
18
19 import java.util.Map;
20
21 import org.hipparchus.CalculusFieldElement;
22 import org.hipparchus.Field;
23 import org.hipparchus.analysis.differentiation.Gradient;
24 import org.hipparchus.geometry.euclidean.threed.FieldRotation;
25 import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
26 import org.hipparchus.geometry.euclidean.threed.RotationConvention;
27 import org.hipparchus.geometry.euclidean.threed.Vector3D;
28 import org.hipparchus.util.FastMath;
29 import org.orekit.bodies.BodyShape;
30 import org.orekit.bodies.FieldGeodeticPoint;
31 import org.orekit.bodies.GeodeticPoint;
32 import org.orekit.data.BodiesElements;
33 import org.orekit.data.FundamentalNutationArguments;
34 import org.orekit.errors.OrekitException;
35 import org.orekit.errors.OrekitMessages;
36 import org.orekit.frames.EOPHistory;
37 import org.orekit.frames.FieldStaticTransform;
38 import org.orekit.frames.FieldTransform;
39 import org.orekit.frames.Frame;
40 import org.orekit.frames.FramesFactory;
41 import org.orekit.frames.KinematicTransform;
42 import org.orekit.frames.StaticTransform;
43 import org.orekit.frames.TopocentricFrame;
44 import org.orekit.frames.Transform;
45 import org.orekit.frames.TransformProvider;
46 import org.orekit.models.earth.displacement.StationDisplacement;
47 import org.orekit.time.AbsoluteDate;
48 import org.orekit.time.FieldAbsoluteDate;
49 import org.orekit.time.UT1Scale;
50 import org.orekit.time.clocks.QuadraticClockModel;
51 import org.orekit.utils.AngularCoordinates;
52 import org.orekit.utils.FieldAngularCoordinates;
53 import org.orekit.utils.FieldPVCoordinates;
54 import org.orekit.utils.FieldPVCoordinatesProvider;
55 import org.orekit.utils.PVCoordinates;
56 import org.orekit.utils.PVCoordinatesProvider;
57 import org.orekit.utils.ParameterDriver;
58 import org.orekit.utils.TimeStampedFieldPVCoordinates;
59 import org.orekit.utils.TimeStampedPVCoordinates;
60
61 /** Class modeling a ground station that can perform some measurements.
62 * <p>
63 * This class adds a position offset parameter to a base {@link TopocentricFrame
64 * topocentric frame}.
65 * </p>
66 * <p>
67 * Since 9.0, this class also adds parameters for an additional polar motion
68 * and an additional prime meridian orientation. Since these parameters will
69 * have the same name for all ground stations, they will be managed consistently
70 * and allow to estimate Earth orientation precisely (this is needed for precise
71 * orbit determination). The polar motion and prime meridian orientation will
72 * be applied <em>after</em> regular Earth orientation parameters, so the value
73 * of the estimated parameters will be correction to EOP, they will not be the
74 * complete EOP values by themselves. Basically, this means that for Earth, the
75 * following transforms are applied in order, between inertial frame and ground
76 * station frame (for non-Earth based ground stations, different precession nutation
77 * models and associated planet oritentation parameters would be applied, if available):
78 * </p>
79 * <p>
80 * Since 9.3, this class also adds a station clock offset parameter, which manages
81 * the value that must be subtracted from the observed measurement date to get the real
82 * physical date at which the measurement was performed (i.e. the offset is negative
83 * if the ground station clock is slow and positive if it is fast).
84 * </p>
85 * <ol>
86 * <li>precession/nutation, as theoretical model plus celestial pole EOP parameters</li>
87 * <li>body rotation, as theoretical model plus prime meridian EOP parameters</li>
88 * <li>polar motion, which is only from EOP parameters (no theoretical models)</li>
89 * <li>additional body rotation, controlled by {@link #getPrimeMeridianOffsetDriver()} and {@link #getPrimeMeridianDriftDriver()}</li>
90 * <li>additional polar motion, controlled by {@link #getPolarOffsetXDriver()}, {@link #getPolarDriftXDriver()},
91 * {@link #getPolarOffsetYDriver()} and {@link #getPolarDriftYDriver()}</li>
92 * <li>station clock offset, controlled by {@link #getClockBiasDriver()}</li>
93 * <li>station position offset, controlled by {@link #getEastOffsetDriver()},
94 * {@link #getNorthOffsetDriver()} and {@link #getZenithOffsetDriver()}</li>
95 * </ol>
96 * @author Luc Maisonobe
97 * @since 8.0
98 */
99 public class GroundStation extends AbstractParticipant implements Observer {
100
101 /** Position offsets scaling factor.
102 * <p>
103 * We use a power of 2 (in fact really 1.0 here) to avoid numeric noise introduction
104 * in the multiplications/divisions sequences.
105 * </p>
106 */
107 private static final double POSITION_OFFSET_SCALE = FastMath.scalb(1.0, 0);
108
109 /** Provider for Earth frame whose EOP parameters can be estimated. */
110 private final EstimatedEarthFrameProvider estimatedEarthFrameProvider;
111
112 /** Earth frame whose EOP parameters can be estimated. */
113 private final Frame estimatedEarthFrame;
114
115 /** Base frame associated with the station. */
116 private final TopocentricFrame baseFrame;
117
118 /** Fundamental nutation arguments. */
119 private final FundamentalNutationArguments arguments;
120
121 /** Displacement models. */
122 private final StationDisplacement[] displacements;
123
124 /** Driver for position offset along the East axis. */
125 private final ParameterDriver eastOffsetDriver;
126
127 /** Driver for position offset along the North axis. */
128 private final ParameterDriver northOffsetDriver;
129
130 /** Driver for position offset along the zenith axis. */
131 private final ParameterDriver zenithOffsetDriver;
132
133 /**
134 * Build a ground station ignoring {@link StationDisplacement station displacements}.
135 * <p>
136 * The initial values for the pole and prime meridian parametric linear models
137 * ({@link #getPrimeMeridianOffsetDriver()}, {@link #getPrimeMeridianDriftDriver()},
138 * {@link #getPolarOffsetXDriver()}, {@link #getPolarDriftXDriver()}, {@link #getPolarOffsetXDriver()},
139 * {@link #getPolarDriftXDriver()}) are set to 0. The initial values for the station offset model
140 * ({@link #getClockBiasDriver()}, {@link #getEastOffsetDriver()}, {@link #getNorthOffsetDriver()},
141 * {@link #getZenithOffsetDriver()}) are set to 0. This implies that as long as these values are not changed, the
142 * offset frame is the same as the {@link #getBaseFrame() base frame}. As soon as some of these models are changed,
143 * the offset frame moves away from the {@link #getBaseFrame() base frame}.
144 * </p>
145 *
146 * @param baseFrame base frame associated with the station, without *any* parametric model
147 * (no station offset, no polar motion, no meridian shift)
148 * @see #GroundStation(TopocentricFrame, EOPHistory, StationDisplacement...)
149 * @since 13.0
150 */
151 public GroundStation(final TopocentricFrame baseFrame) {
152 this(baseFrame, FramesFactory.findEOP(baseFrame));
153 }
154
155 /**
156 * Build a ground station ignoring {@link StationDisplacement station displacements}.
157 * <p>
158 * The initial values for the pole and prime meridian parametric linear models
159 * ({@link #getPrimeMeridianOffsetDriver()}, {@link #getPrimeMeridianDriftDriver()},
160 * {@link #getPolarOffsetXDriver()}, {@link #getPolarDriftXDriver()}, {@link #getPolarOffsetXDriver()},
161 * {@link #getPolarDriftXDriver()}) are set to 0. The initial values for the station offset model
162 * ({@link #getClockBiasDriver()}, {@link #getEastOffsetDriver()}, {@link #getNorthOffsetDriver()},
163 * {@link #getZenithOffsetDriver()}) are set to 0. This implies that as long as these values are not changed, the
164 * offset frame is the same as the {@link #getBaseFrame() base frame}. As soon as some of these models are changed,
165 * the offset frame moves away from the {@link #getBaseFrame() base frame}.
166 * </p>
167 *
168 * @param baseFrame base frame associated with the station, without *any* parametric model
169 * (no station offset, no polar motion, no meridian shift)
170 * @param clock new quadratic clock model with user-supplied displacements
171 * @see #GroundStation(TopocentricFrame, EOPHistory, StationDisplacement...)
172 * @since 13.0
173 */
174 public GroundStation(final TopocentricFrame baseFrame, final QuadraticClockModel clock) {
175 this(baseFrame, FramesFactory.findEOP(baseFrame), clock);
176 }
177
178 /**
179 * Simple constructor.
180 * <p>
181 * The initial values for the pole and prime meridian parametric linear models
182 * ({@link #getPrimeMeridianOffsetDriver()}, {@link #getPrimeMeridianDriftDriver()},
183 * {@link #getPolarOffsetXDriver()}, {@link #getPolarDriftXDriver()}, {@link #getPolarOffsetXDriver()},
184 * {@link #getPolarDriftXDriver()}) are set to 0. The initial values for the station offset model
185 * ({@link #getClockBiasDriver()}, {@link #getEastOffsetDriver()}, {@link #getNorthOffsetDriver()},
186 * {@link #getZenithOffsetDriver()}, {@link #getClockBiasDriver()}) are set to 0. This implies that as long as
187 * these values are not changed, the offset frame is the same as the {@link #getBaseFrame() base frame}. As soon as
188 * some of these models are changed, the offset frame moves away from the {@link #getBaseFrame() base frame}.
189 * </p>
190 *
191 * @param baseFrame base frame associated with the station, without *any* parametric model (no station offset,
192 * no polar motion, no meridian shift)
193 * @param eopHistory EOP history associated with Earth frames
194 * @param displacements ground station displacement model (tides, ocean loading, atmospheric loading, thermal
195 * effects...)
196 * @since 12.1
197 */
198 public GroundStation(final TopocentricFrame baseFrame, final EOPHistory eopHistory,
199 final StationDisplacement... displacements) {
200 this(baseFrame, eopHistory, createEmptyQuadraticClock(baseFrame.getName()), displacements);
201 }
202
203 /**
204 * Simple constructor.
205 * <p>
206 * The initial values for the pole and prime meridian parametric linear models
207 * ({@link #getPrimeMeridianOffsetDriver()}, {@link #getPrimeMeridianDriftDriver()},
208 * {@link #getPolarOffsetXDriver()}, {@link #getPolarDriftXDriver()}, {@link #getPolarOffsetXDriver()},
209 * {@link #getPolarDriftXDriver()}) are set to 0. The initial values for the station offset model
210 * ({@link #getClockBiasDriver()}, {@link #getEastOffsetDriver()}, {@link #getNorthOffsetDriver()},
211 * {@link #getZenithOffsetDriver()}, {@link #getClockBiasDriver()}) are set to 0. This implies that as long as
212 * these values are not changed, the offset frame is the same as the {@link #getBaseFrame() base frame}. As soon as
213 * some of these models are changed, the offset frame moves away from the {@link #getBaseFrame() base frame}.
214 * </p>
215 *
216 * @param baseFrame base frame associated with the station, without *any* parametric model (no station offset,
217 * no polar motion, no meridian shift)
218 * @param eopHistory EOP history associated with Earth frames
219 * @param clock new quadratic clock model with user-supplied displacements
220 * @param displacements ground station displacement model (tides, ocean loading, atmospheric loading, thermal
221 * effects...)
222 * @since 12.1
223 */
224 public GroundStation(final TopocentricFrame baseFrame, final EOPHistory eopHistory,
225 final QuadraticClockModel clock, final StationDisplacement... displacements) {
226 super(baseFrame.getName(), clock);
227 this.baseFrame = baseFrame;
228
229 if (eopHistory == null) {
230 throw new OrekitException(OrekitMessages.NO_EARTH_ORIENTATION_PARAMETERS);
231 }
232
233 final UT1Scale baseUT1 = eopHistory.getTimeScales()
234 .getUT1(eopHistory.getConventions(), eopHistory.isSimpleEop());
235 this.estimatedEarthFrameProvider = new EstimatedEarthFrameProvider(baseUT1);
236 this.estimatedEarthFrame = new Frame(baseFrame.getParent(), estimatedEarthFrameProvider,
237 baseFrame.getParent() + "-estimated");
238
239 if (displacements.length == 0) {
240 arguments = null;
241 } else {
242 arguments = eopHistory.getConventions().getNutationArguments(
243 estimatedEarthFrameProvider.getEstimatedUT1(),
244 eopHistory.getTimeScales());
245 }
246
247 this.displacements = displacements.clone();
248
249 this.eastOffsetDriver = new ParameterDriver(baseFrame.getName() + OFFSET_SUFFIX + "-East",
250 0.0, POSITION_OFFSET_SCALE,
251 Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
252
253 this.northOffsetDriver = new ParameterDriver(baseFrame.getName() + OFFSET_SUFFIX + "-North",
254 0.0, POSITION_OFFSET_SCALE,
255 Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
256
257 this.zenithOffsetDriver = new ParameterDriver(baseFrame.getName() + OFFSET_SUFFIX + "-Zenith",
258 0.0, POSITION_OFFSET_SCALE,
259 Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
260
261 // Add the ground station parameters to the master list.
262 addParameterDriver(this.eastOffsetDriver);
263 addParameterDriver(this.northOffsetDriver);
264 addParameterDriver(this.zenithOffsetDriver);
265 addParameterDriver(this.estimatedEarthFrameProvider.getPrimeMeridianOffsetDriver());
266 addParameterDriver(this.estimatedEarthFrameProvider.getPrimeMeridianDriftDriver());
267 addParameterDriver(this.estimatedEarthFrameProvider.getPolarOffsetXDriver());
268 addParameterDriver(this.estimatedEarthFrameProvider.getPolarDriftXDriver());
269 addParameterDriver(this.estimatedEarthFrameProvider.getPolarOffsetYDriver());
270 addParameterDriver(this.estimatedEarthFrameProvider.getPolarDriftYDriver());
271
272 }
273
274 /** {@inheritDoc} */
275 @Override
276 public final boolean isSpaceBased() {
277 return false;
278 }
279
280 /** Get the displacement models.
281 * @return displacement models (empty if no model has been set up)
282 * @since 9.1
283 */
284 public StationDisplacement[] getDisplacements() {
285 return displacements.clone();
286 }
287
288 /** Get a driver allowing to change station position along East axis.
289 * @return driver for station position offset along East axis
290 */
291 public ParameterDriver getEastOffsetDriver() {
292 return eastOffsetDriver;
293 }
294
295 /** Get a driver allowing to change station position along North axis.
296 * @return driver for station position offset along North axis
297 */
298 public ParameterDriver getNorthOffsetDriver() {
299 return northOffsetDriver;
300 }
301
302 /** Get a driver allowing to change station position along Zenith axis.
303 * @return driver for station position offset along Zenith axis
304 */
305 public ParameterDriver getZenithOffsetDriver() {
306 return zenithOffsetDriver;
307 }
308
309 /** Get a driver allowing to add a prime meridian rotation.
310 * <p>
311 * The parameter is an angle in radians. In order to convert this
312 * value to a DUT1 in seconds, the value must be divided by
313 * {@code ave = 7.292115146706979e-5} (which is the nominal Angular Velocity
314 * of Earth from the TIRF model).
315 * </p>
316 * @return driver for prime meridian rotation
317 */
318 public ParameterDriver getPrimeMeridianOffsetDriver() {
319 return estimatedEarthFrameProvider.getPrimeMeridianOffsetDriver();
320 }
321
322 /** Get a driver allowing to add a prime meridian rotation rate.
323 * <p>
324 * The parameter is an angle rate in radians per second. In order to convert this
325 * value to a LOD in seconds, the value must be multiplied by -86400 and divided by
326 * {@code ave = 7.292115146706979e-5} (which is the nominal Angular Velocity
327 * of Earth from the TIRF model).
328 * </p>
329 * @return driver for prime meridian rotation rate
330 */
331 public ParameterDriver getPrimeMeridianDriftDriver() {
332 return estimatedEarthFrameProvider.getPrimeMeridianDriftDriver();
333 }
334
335 /** Get a driver allowing to add a polar offset along X.
336 * <p>
337 * The parameter is an angle in radians
338 * </p>
339 * @return driver for polar offset along X
340 */
341 public ParameterDriver getPolarOffsetXDriver() {
342 return estimatedEarthFrameProvider.getPolarOffsetXDriver();
343 }
344
345 /** Get a driver allowing to add a polar drift along X.
346 * <p>
347 * The parameter is an angle rate in radians per second
348 * </p>
349 * @return driver for polar drift along X
350 */
351 public ParameterDriver getPolarDriftXDriver() {
352 return estimatedEarthFrameProvider.getPolarDriftXDriver();
353 }
354
355 /** Get a driver allowing to add a polar offset along Y.
356 * <p>
357 * The parameter is an angle in radians
358 * </p>
359 * @return driver for polar offset along Y
360 */
361 public ParameterDriver getPolarOffsetYDriver() {
362 return estimatedEarthFrameProvider.getPolarOffsetYDriver();
363 }
364
365 /** Get a driver allowing to add a polar drift along Y.
366 * <p>
367 * The parameter is an angle rate in radians per second
368 * </p>
369 * @return driver for polar drift along Y
370 */
371 public ParameterDriver getPolarDriftYDriver() {
372 return estimatedEarthFrameProvider.getPolarDriftYDriver();
373 }
374
375 /** Get the base frame associated with the station.
376 * <p>
377 * The base frame corresponds to a null position offset, null
378 * polar motion, null meridian shift
379 * </p>
380 * @return base frame associated with the station
381 */
382 public TopocentricFrame getBaseFrame() {
383 return baseFrame;
384 }
385
386 /** Get the estimated Earth frame, including the estimated linear models for pole and prime meridian.
387 * <p>
388 * This frame is bound to the {@link #getPrimeMeridianOffsetDriver() driver for prime meridian offset},
389 * {@link #getPrimeMeridianDriftDriver() driver prime meridian drift},
390 * {@link #getPolarOffsetXDriver() driver for polar offset along X},
391 * {@link #getPolarDriftXDriver() driver for polar drift along X},
392 * {@link #getPolarOffsetYDriver() driver for polar offset along Y},
393 * {@link #getPolarDriftYDriver() driver for polar drift along Y}, so its orientation changes when
394 * the {@link ParameterDriver#setValue(double) setValue} methods of the drivers are called.
395 * </p>
396 * @return estimated Earth frame
397 * @since 9.1
398 */
399 public Frame getEstimatedEarthFrame() {
400 return estimatedEarthFrame;
401 }
402
403 /** Get the estimated UT1 scale, including the estimated linear models for prime meridian.
404 * <p>
405 * This time scale is bound to the {@link #getPrimeMeridianOffsetDriver() driver for prime meridian offset},
406 * and {@link #getPrimeMeridianDriftDriver() driver prime meridian drift}, so its offset from UTC changes when
407 * the {@link ParameterDriver#setValue(double) setValue} methods of the drivers are called.
408 * </p>
409 * @return estimated Earth frame
410 * @since 9.1
411 */
412 public UT1Scale getEstimatedUT1() {
413 return estimatedEarthFrameProvider.getEstimatedUT1();
414 }
415
416 /** Get the station displacement.
417 * @param date current date
418 * @param position raw position of the station in Earth frame
419 * before displacement is applied
420 * @return station displacement
421 * @since 9.1
422 */
423 private Vector3D computeDisplacement(final AbsoluteDate date, final Vector3D position) {
424 Vector3D displacement = Vector3D.ZERO;
425 if (arguments != null) {
426 final BodiesElements elements = arguments.evaluateAll(date);
427 for (final StationDisplacement sd : displacements) {
428 // we consider all displacements apply to the same initial position,
429 // i.e. they apply simultaneously, not according to some order
430 displacement = displacement.add(sd.displacement(elements, estimatedEarthFrame, position));
431 }
432 }
433 return displacement;
434 }
435
436 /** Get the geodetic point at the center of the offset frame.
437 * @param date current date (may be null if displacements are ignored)
438 * @return geodetic point at the center of the offset frame
439 * @since 9.1
440 */
441 public GeodeticPoint getOffsetGeodeticPoint(final AbsoluteDate date) {
442
443 // take station offset into account
444 final double x = eastOffsetDriver.getValue();
445 final double y = northOffsetDriver.getValue();
446 final double z = zenithOffsetDriver.getValue();
447 final BodyShape baseShape = baseFrame.getParentShape();
448 final StaticTransform baseToBody = baseFrame.getStaticTransformTo(baseShape.getBodyFrame(), date);
449 Vector3D origin = baseToBody.transformPosition(new Vector3D(x, y, z));
450
451 if (date != null) {
452 origin = origin.add(computeDisplacement(date, origin));
453 }
454
455 return baseShape.transform(origin, baseShape.getBodyFrame(), date);
456
457 }
458
459 /** Get the geodetic point at the center of the offset frame.
460 * @param <T> type of the field elements
461 * @param date current date(<em>must</em> be non-null, which is a more stringent condition
462 * * than in {@link #getOffsetGeodeticPoint(AbsoluteDate)}
463 * @return geodetic point at the center of the offset frame
464 * @since 12.1
465 */
466 public <T extends CalculusFieldElement<T>> FieldGeodeticPoint<T> getOffsetGeodeticPoint(final FieldAbsoluteDate<T> date) {
467
468 // take station offset into account
469 final double x = eastOffsetDriver.getValue();
470 final double y = northOffsetDriver.getValue();
471 final double z = zenithOffsetDriver.getValue();
472 final BodyShape baseShape = baseFrame.getParentShape();
473 final FieldStaticTransform<T> baseToBody = baseFrame.getStaticTransformTo(baseShape.getBodyFrame(), date);
474 FieldVector3D<T> origin = baseToBody.transformPosition(new Vector3D(x, y, z));
475 origin = origin.add(computeDisplacement(date.toAbsoluteDate(), origin.toVector3D()));
476
477 return baseShape.transform(origin, baseShape.getBodyFrame(), date);
478
479 }
480
481 /**
482 * Get the transform provider associated with the station.
483 * @param frame target frame for the transform provider
484 * @return transform provider
485 * @since 14.0
486 */
487 public TransformProvider getTransformProvider(final Frame frame) {
488 return new GroundStationTransformProvider(frame, baseFrame, eastOffsetDriver, northOffsetDriver,
489 zenithOffsetDriver, estimatedEarthFrameProvider, arguments, displacements);
490 }
491
492 /** {@inheritDoc} */
493 @Override
494 public final PVCoordinatesProvider getPVCoordinatesProvider() {
495 return new PVCoordinatesProvider() {
496 @Override
497 public TimeStampedPVCoordinates getPVCoordinates(final AbsoluteDate date, final Frame frame) {
498 final TransformProvider transformProvider = getTransformProvider(frame);
499 return transformProvider.getTransform(date)
500 .transformPVCoordinates(new TimeStampedPVCoordinates(date, PVCoordinates.ZERO));
501 }
502
503 @Override
504 public Vector3D getVelocity(final AbsoluteDate date, final Frame frame) {
505 final TransformProvider transformProvider = getTransformProvider(frame);
506 return transformProvider.getKinematicTransform(date).transformOnlyPV(PVCoordinates.ZERO).getVelocity();
507 }
508
509 @Override
510 public Vector3D getPosition(final AbsoluteDate date, final Frame frame) {
511 final TransformProvider transformProvider = getTransformProvider(frame);
512 return transformProvider.getStaticTransform(date).transformPosition(Vector3D.ZERO);
513 }
514 };
515 }
516
517 /** {@inheritDoc} */
518 @Override
519 public FieldPVCoordinatesProvider<Gradient> getFieldPVCoordinatesProvider(final int freeParameters,
520 final Map<String, Integer> parameterIndices) {
521 return new FieldPVCoordinatesProvider<Gradient>() {
522 @Override
523 public TimeStampedFieldPVCoordinates<Gradient> getPVCoordinates(final FieldAbsoluteDate<Gradient> date,
524 final Frame frame) {
525 // take Earth offsets into account
526 final FieldTransform<Gradient> intermediateToBody = estimatedEarthFrameProvider.getTransform(date,
527 freeParameters, parameterIndices).getInverse();
528
529 // take station offsets into account
530 final FieldVector3D<Gradient> origin = getOrigin(date, parameterIndices);
531
532 // Earth-fixed Earth-centered to target (with linear approximation for performance)
533 final Transform bodyToInertNonField = baseFrame.getParent().getTransformTo(frame, date.toAbsoluteDate());
534 final FieldTransform<Gradient> bodyToInert = new FieldTransform<>(date.getField(),
535 bodyToInertNonField).shiftedBy(date.durationFrom(date.toAbsoluteDate()));
536
537 final TimeStampedFieldPVCoordinates<Gradient> zeroPV = new TimeStampedFieldPVCoordinates<>(date,
538 new FieldPVCoordinates<>(origin, FieldVector3D.getZero(date.getField())));
539 return new FieldTransform<>(date, intermediateToBody, bodyToInert).transformPVCoordinates(zeroPV);
540 }
541
542 @Override
543 public FieldVector3D<Gradient> getPosition(final FieldAbsoluteDate<Gradient> date, final Frame frame) {
544 // take Earth offsets into account
545 final FieldRotation<Gradient> bodyToIntermediateRotation = estimatedEarthFrameProvider.getStaticTransform(date,
546 freeParameters, parameterIndices).getRotation();
547
548 // take station offsets into account
549 final FieldVector3D<Gradient> origin = getOrigin(date, parameterIndices);
550
551 // Earth-fixed Earth-centered to target (with linear approximation for performance)
552 final KinematicTransform bodyToInertNonField = baseFrame.getParent().getKinematicTransformTo(frame,
553 date.toAbsoluteDate());
554 final FieldStaticTransform<Gradient> bodyToInert = shiftKinematicTransform(bodyToInertNonField,
555 date.durationFrom(date.toAbsoluteDate()));
556
557 // combine by hand for performance reasons
558 final FieldRotation<Gradient> rotation = bodyToIntermediateRotation.composeInverse(bodyToInert.getRotation(),
559 RotationConvention.FRAME_TRANSFORM);
560 return rotation.applyTo(bodyToInert.getTranslation().add(origin));
561 }
562 };
563 }
564
565 /**
566 * Shift a kinematic transform by a Gradient time into a FieldStaticTransform<Gradient>.
567 * @param kinematicTransform kinematic transform to shift
568 * @param dt time to shift by
569 * @return Field static transform shifted by dt
570 * @since 14.0
571 */
572 private FieldStaticTransform<Gradient> shiftKinematicTransform(final KinematicTransform kinematicTransform,
573 final Gradient dt) {
574 // shift translation
575 final Field<Gradient> field = dt.getField();
576 final AbsoluteDate date = kinematicTransform.getDate();
577 final FieldVector3D<Gradient> fieldVelocity = new FieldVector3D<>(field, kinematicTransform.getVelocity());
578 final FieldVector3D<Gradient> shiftedTranslation = fieldVelocity.scalarMultiply(dt).add(kinematicTransform.getTranslation());
579 // shift rotation
580 final FieldAngularCoordinates<Gradient> fieldAngularCoordinates = new FieldAngularCoordinates<>(field,
581 new AngularCoordinates(kinematicTransform.getRotation(), kinematicTransform.getRotationRate()));
582 final FieldVector3D<Gradient> rotationRate = fieldAngularCoordinates.getRotationRate();
583 final Gradient rate = rotationRate.getNorm();
584 final FieldRotation<Gradient> shiftedRotation = (rate.getReal() == 0.0) ?
585 fieldAngularCoordinates.getRotation() :
586 new FieldRotation<>(rotationRate, rate.multiply(dt), RotationConvention.FRAME_TRANSFORM)
587 .compose(fieldAngularCoordinates.getRotation(), RotationConvention.VECTOR_OPERATOR);
588 return FieldStaticTransform.of(new FieldAbsoluteDate<>(field, date).shiftedBy(dt), shiftedTranslation,
589 shiftedRotation);
590 }
591
592 /**
593 * Retrieve station's position in body shape frame.
594 * @param date date
595 * @param indices mapping from parameters' name to derivatives' index.
596 * @return origin position
597 */
598 private FieldVector3D<Gradient> getOrigin(final FieldAbsoluteDate<Gradient> date,
599 final Map<String, Integer> indices) {
600 // compute position in topocentric frame
601 final int freeParameters = date.getField().getZero().getFreeParameters();
602 final AbsoluteDate absoluteDate = date.toAbsoluteDate();
603 final Gradient x = eastOffsetDriver.getValue(freeParameters, indices, absoluteDate);
604 final Gradient y = northOffsetDriver.getValue(freeParameters, indices, absoluteDate);
605 final Gradient z = zenithOffsetDriver.getValue(freeParameters, indices, absoluteDate);
606 final FieldVector3D<Gradient> position = new FieldVector3D<>(x, y, z);
607 // approximate linearly (for performance) static transform from topocentric to body shape frame
608 final Frame bodyFrame = baseFrame.getParentShape().getBodyFrame();
609 final KinematicTransform kinematicTopoToBody = baseFrame.getKinematicTransformTo(bodyFrame, absoluteDate);
610 final FieldStaticTransform<Gradient> staticTopoToBody = shiftKinematicTransform(kinematicTopoToBody,
611 date.durationFrom(absoluteDate));
612 // apply transform and displacement
613 final FieldVector3D<Gradient> originBeforeDisplacement = staticTopoToBody.transformPosition(position);
614 return originBeforeDisplacement.add(computeDisplacement(absoluteDate, originBeforeDisplacement.toVector3D()));
615 }
616
617 /** {@inheritDoc} */
618 @Override
619 public Transform getOffsetToInertial(final Frame inertial, final AbsoluteDate date, final boolean clockOffsetAlreadyApplied) {
620
621 // take clock offset into account
622 final AbsoluteDate offsetCompensatedDate = clockOffsetAlreadyApplied ?
623 date :
624 new AbsoluteDate(date, -getOffsetValue(date));
625
626 final TransformProvider transformProvider = getTransformProvider(inertial);
627 return transformProvider.getTransform(offsetCompensatedDate);
628 }
629
630 /** {@inheritDoc} */
631 @Override
632 public FieldTransform<Gradient> getOffsetToInertial(final Frame inertial,
633 final FieldAbsoluteDate<Gradient> offsetCompensatedDate,
634 final int freeParameters,
635 final Map<String, Integer> indices) {
636 // take Earth offsets into account
637 final FieldTransform<Gradient> intermediateToBody =
638 estimatedEarthFrameProvider.getTransform(offsetCompensatedDate, freeParameters, indices).getInverse();
639
640 // take station offsets into account
641 final FieldVector3D<Gradient> origin = getOrigin(offsetCompensatedDate, indices);
642
643 final GroundStationTransformProvider transformProvider = new GroundStationTransformProvider(inertial, baseFrame,
644 eastOffsetDriver, northOffsetDriver, zenithOffsetDriver, estimatedEarthFrameProvider, arguments, displacements);
645 return transformProvider.getTransform(offsetCompensatedDate, origin, intermediateToBody);
646 }
647
648 }