1 /* Copyright 2002-2024 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.Rotation;
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.StaticTransform;
42 import org.orekit.frames.TopocentricFrame;
43 import org.orekit.frames.Transform;
44 import org.orekit.models.earth.displacement.StationDisplacement;
45 import org.orekit.models.earth.troposphere.TroposphericModelUtils;
46 import org.orekit.models.earth.weather.FieldPressureTemperatureHumidity;
47 import org.orekit.models.earth.weather.PressureTemperatureHumidity;
48 import org.orekit.models.earth.weather.PressureTemperatureHumidityProvider;
49 import org.orekit.time.AbsoluteDate;
50 import org.orekit.time.FieldAbsoluteDate;
51 import org.orekit.time.UT1Scale;
52 import org.orekit.utils.ParameterDriver;
53
54 /** Class modeling a ground station that can perform some measurements.
55 * <p>
56 * This class adds a position offset parameter to a base {@link TopocentricFrame
57 * topocentric frame}.
58 * </p>
59 * <p>
60 * Since 9.0, this class also adds parameters for an additional polar motion
61 * and an additional prime meridian orientation. Since these parameters will
62 * have the same name for all ground stations, they will be managed consistently
63 * and allow to estimate Earth orientation precisely (this is needed for precise
64 * orbit determination). The polar motion and prime meridian orientation will
65 * be applied <em>after</em> regular Earth orientation parameters, so the value
66 * of the estimated parameters will be correction to EOP, they will not be the
67 * complete EOP values by themselves. Basically, this means that for Earth, the
68 * following transforms are applied in order, between inertial frame and ground
69 * station frame (for non-Earth based ground stations, different precession nutation
70 * models and associated planet oritentation parameters would be applied, if available):
71 * </p>
72 * <p>
73 * Since 9.3, this class also adds a station clock offset parameter, which manages
74 * the value that must be subtracted from the observed measurement date to get the real
75 * physical date at which the measurement was performed (i.e. the offset is negative
76 * if the ground station clock is slow and positive if it is fast).
77 * </p>
78 * <ol>
79 * <li>precession/nutation, as theoretical model plus celestial pole EOP parameters</li>
80 * <li>body rotation, as theoretical model plus prime meridian EOP parameters</li>
81 * <li>polar motion, which is only from EOP parameters (no theoretical models)</li>
82 * <li>additional body rotation, controlled by {@link #getPrimeMeridianOffsetDriver()} and {@link #getPrimeMeridianDriftDriver()}</li>
83 * <li>additional polar motion, controlled by {@link #getPolarOffsetXDriver()}, {@link #getPolarDriftXDriver()},
84 * {@link #getPolarOffsetYDriver()} and {@link #getPolarDriftYDriver()}</li>
85 * <li>station clock offset, controlled by {@link #getClockOffsetDriver()}</li>
86 * <li>station position offset, controlled by {@link #getEastOffsetDriver()},
87 * {@link #getNorthOffsetDriver()} and {@link #getZenithOffsetDriver()}</li>
88 * </ol>
89 * @author Luc Maisonobe
90 * @since 8.0
91 */
92 public class GroundStation {
93
94 /** Suffix for ground station position and clock offset parameters names. */
95 public static final String OFFSET_SUFFIX = "-offset";
96
97 /** Suffix for ground clock drift parameters name. */
98 public static final String DRIFT_SUFFIX = "-drift-clock";
99
100 /** Suffix for ground clock drift parameters name.
101 * @since 12.1
102 */
103 public static final String ACCELERATION_SUFFIX = "-acceleration-clock";
104
105 /** Suffix for ground station intermediate frame name. */
106 public static final String INTERMEDIATE_SUFFIX = "-intermediate";
107
108 /** Clock offset scaling factor.
109 * <p>
110 * We use a power of 2 to avoid numeric noise introduction
111 * in the multiplications/divisions sequences.
112 * </p>
113 */
114 private static final double CLOCK_OFFSET_SCALE = FastMath.scalb(1.0, -10);
115
116 /** Position offsets scaling factor.
117 * <p>
118 * We use a power of 2 (in fact really 1.0 here) to avoid numeric noise introduction
119 * in the multiplications/divisions sequences.
120 * </p>
121 */
122 private static final double POSITION_OFFSET_SCALE = FastMath.scalb(1.0, 0);
123
124 /** Provider for Earth frame whose EOP parameters can be estimated. */
125 private final EstimatedEarthFrameProvider estimatedEarthFrameProvider;
126
127 /** Provider for weather parameters.
128 * @since 12.1
129 */
130 private final PressureTemperatureHumidityProvider pthProvider;
131
132 /** Earth frame whose EOP parameters can be estimated. */
133 private final Frame estimatedEarthFrame;
134
135 /** Base frame associated with the station. */
136 private final TopocentricFrame baseFrame;
137
138 /** Fundamental nutation arguments. */
139 private final FundamentalNutationArguments arguments;
140
141 /** Displacement models. */
142 private final StationDisplacement[] displacements;
143
144 /** Driver for clock offset. */
145 private final ParameterDriver clockOffsetDriver;
146
147 /** Driver for clock drift. */
148 private final ParameterDriver clockDriftDriver;
149
150 /** Driver for clock acceleration.
151 * @since 12.1
152 */
153 private final ParameterDriver clockAccelerationDriver;
154
155 /** Driver for position offset along the East axis. */
156 private final ParameterDriver eastOffsetDriver;
157
158 /** Driver for position offset along the North axis. */
159 private final ParameterDriver northOffsetDriver;
160
161 /** Driver for position offset along the zenith axis. */
162 private final ParameterDriver zenithOffsetDriver;
163
164 /** Build a ground station ignoring {@link StationDisplacement station displacements}.
165 * <p>
166 * Calls {@link #GroundStation(TopocentricFrame, PressureTemperatureHumidityProvider)}
167 * with {@link TroposphericModelUtils#STANDARD_ATMOSPHERE_PROVIDER} as the provider.
168 * </p>
169 * <p>
170 * The initial values for the pole and prime meridian parametric linear models
171 * ({@link #getPrimeMeridianOffsetDriver()}, {@link #getPrimeMeridianDriftDriver()},
172 * {@link #getPolarOffsetXDriver()}, {@link #getPolarDriftXDriver()},
173 * {@link #getPolarOffsetXDriver()}, {@link #getPolarDriftXDriver()}) are set to 0.
174 * The initial values for the station offset model ({@link #getClockOffsetDriver()},
175 * {@link #getEastOffsetDriver()}, {@link #getNorthOffsetDriver()},
176 * {@link #getZenithOffsetDriver()}) are set to 0.
177 * This implies that as long as these values are not changed, the offset frame is
178 * the same as the {@link #getBaseFrame() base frame}. As soon as some of these models
179 * are changed, the offset frame moves away from the {@link #getBaseFrame() base frame}.
180 * </p>
181 * @param baseFrame base frame associated with the station, without *any* parametric
182 * model (no station offset, no polar motion, no meridian shift)
183 * @see #GroundStation(TopocentricFrame, EOPHistory, StationDisplacement...)
184 */
185 public GroundStation(final TopocentricFrame baseFrame) {
186 this(baseFrame, TroposphericModelUtils.STANDARD_ATMOSPHERE_PROVIDER,
187 FramesFactory.findEOP(baseFrame), new StationDisplacement[0]);
188 }
189
190 /** Build a ground station ignoring {@link StationDisplacement station displacements}.
191 * <p>
192 * The initial values for the pole and prime meridian parametric linear models
193 * ({@link #getPrimeMeridianOffsetDriver()}, {@link #getPrimeMeridianDriftDriver()},
194 * {@link #getPolarOffsetXDriver()}, {@link #getPolarDriftXDriver()},
195 * {@link #getPolarOffsetXDriver()}, {@link #getPolarDriftXDriver()}) are set to 0.
196 * The initial values for the station offset model ({@link #getClockOffsetDriver()},
197 * {@link #getEastOffsetDriver()}, {@link #getNorthOffsetDriver()},
198 * {@link #getZenithOffsetDriver()}) are set to 0.
199 * This implies that as long as these values are not changed, the offset frame is
200 * the same as the {@link #getBaseFrame() base frame}. As soon as some of these models
201 * are changed, the offset frame moves away from the {@link #getBaseFrame() base frame}.
202 * </p>
203 * @param baseFrame base frame associated with the station, without *any* parametric
204 * model (no station offset, no polar motion, no meridian shift)
205 * @param pthProvider provider for weather parameters
206 * @see #GroundStation(TopocentricFrame, EOPHistory, StationDisplacement...)
207 * @since 12.1
208 */
209 public GroundStation(final TopocentricFrame baseFrame, final PressureTemperatureHumidityProvider pthProvider) {
210 this(baseFrame, pthProvider, FramesFactory.findEOP(baseFrame), new StationDisplacement[0]);
211 }
212
213 /** Simple constructor.
214 * <p>
215 * The initial values for the pole and prime meridian parametric linear models
216 * ({@link #getPrimeMeridianOffsetDriver()}, {@link #getPrimeMeridianDriftDriver()},
217 * {@link #getPolarOffsetXDriver()}, {@link #getPolarDriftXDriver()},
218 * {@link #getPolarOffsetXDriver()}, {@link #getPolarDriftXDriver()}) are set to 0.
219 * The initial values for the station offset model ({@link #getClockOffsetDriver()},
220 * {@link #getEastOffsetDriver()}, {@link #getNorthOffsetDriver()},
221 * {@link #getZenithOffsetDriver()}, {@link #getClockOffsetDriver()}) are set to 0.
222 * This implies that as long as these values are not changed, the offset frame is
223 * the same as the {@link #getBaseFrame() base frame}. As soon as some of these models
224 * are changed, the offset frame moves away from the {@link #getBaseFrame() base frame}.
225 * </p>
226 * @param baseFrame base frame associated with the station, without *any* parametric
227 * model (no station offset, no polar motion, no meridian shift)
228 * @param eopHistory EOP history associated with Earth frames
229 * @param displacements ground station displacement model (tides, ocean loading,
230 * atmospheric loading, thermal effects...)
231 * @since 9.1
232 * @deprecated as of 12.1, replaced by {@link #GroundStation(TopocentricFrame,
233 * PressureTemperatureHumidityProvider, EOPHistory, StationDisplacement...)}
234 */
235 @Deprecated
236 public GroundStation(final TopocentricFrame baseFrame, final EOPHistory eopHistory,
237 final StationDisplacement... displacements) {
238 this(baseFrame, TroposphericModelUtils.STANDARD_ATMOSPHERE_PROVIDER, eopHistory, displacements);
239 }
240
241 /** Simple constructor.
242 * <p>
243 * The initial values for the pole and prime meridian parametric linear models
244 * ({@link #getPrimeMeridianOffsetDriver()}, {@link #getPrimeMeridianDriftDriver()},
245 * {@link #getPolarOffsetXDriver()}, {@link #getPolarDriftXDriver()},
246 * {@link #getPolarOffsetXDriver()}, {@link #getPolarDriftXDriver()}) are set to 0.
247 * The initial values for the station offset model ({@link #getClockOffsetDriver()},
248 * {@link #getEastOffsetDriver()}, {@link #getNorthOffsetDriver()},
249 * {@link #getZenithOffsetDriver()}, {@link #getClockOffsetDriver()}) are set to 0.
250 * This implies that as long as these values are not changed, the offset frame is
251 * the same as the {@link #getBaseFrame() base frame}. As soon as some of these models
252 * are changed, the offset frame moves away from the {@link #getBaseFrame() base frame}.
253 * </p>
254 * @param baseFrame base frame associated with the station, without *any* parametric
255 * model (no station offset, no polar motion, no meridian shift)
256 * @param pthProvider provider for weather parameters
257 * @param eopHistory EOP history associated with Earth frames
258 * @param displacements ground station displacement model (tides, ocean loading,
259 * atmospheric loading, thermal effects...)
260 * @since 12.1
261 */
262 public GroundStation(final TopocentricFrame baseFrame,
263 final PressureTemperatureHumidityProvider pthProvider,
264 final EOPHistory eopHistory,
265 final StationDisplacement... displacements) {
266
267 this.baseFrame = baseFrame;
268 this.pthProvider = pthProvider;
269
270 if (eopHistory == null) {
271 throw new OrekitException(OrekitMessages.NO_EARTH_ORIENTATION_PARAMETERS);
272 }
273
274 final UT1Scale baseUT1 = eopHistory.getTimeScales()
275 .getUT1(eopHistory.getConventions(), eopHistory.isSimpleEop());
276 this.estimatedEarthFrameProvider = new EstimatedEarthFrameProvider(baseUT1);
277 this.estimatedEarthFrame = new Frame(baseFrame.getParent(), estimatedEarthFrameProvider,
278 baseFrame.getParent() + "-estimated");
279
280 if (displacements.length == 0) {
281 arguments = null;
282 } else {
283 arguments = eopHistory.getConventions().getNutationArguments(
284 estimatedEarthFrameProvider.getEstimatedUT1(),
285 eopHistory.getTimeScales());
286 }
287
288 this.displacements = displacements.clone();
289
290 this.clockOffsetDriver = new ParameterDriver(baseFrame.getName() + OFFSET_SUFFIX + "-clock",
291 0.0, CLOCK_OFFSET_SCALE,
292 Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
293
294 this.clockDriftDriver = new ParameterDriver(baseFrame.getName() + DRIFT_SUFFIX,
295 0.0, CLOCK_OFFSET_SCALE,
296 Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
297
298 this.clockAccelerationDriver = new ParameterDriver(baseFrame.getName() + ACCELERATION_SUFFIX,
299 0.0, CLOCK_OFFSET_SCALE,
300 Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
301
302 this.eastOffsetDriver = new ParameterDriver(baseFrame.getName() + OFFSET_SUFFIX + "-East",
303 0.0, POSITION_OFFSET_SCALE,
304 Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
305
306 this.northOffsetDriver = new ParameterDriver(baseFrame.getName() + OFFSET_SUFFIX + "-North",
307 0.0, POSITION_OFFSET_SCALE,
308 Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
309
310 this.zenithOffsetDriver = new ParameterDriver(baseFrame.getName() + OFFSET_SUFFIX + "-Zenith",
311 0.0, POSITION_OFFSET_SCALE,
312 Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
313
314 }
315
316 /** Get the weather parameters.
317 * @param date date at which weather parameters are requested
318 * @return weather parameters
319 * @since 12.1
320 */
321 public PressureTemperatureHumidity getPressureTemperatureHumidity(final AbsoluteDate date) {
322 return pthProvider.getWeatherParamerers(getOffsetGeodeticPoint(date), date);
323 }
324
325 /** Get the weather parameters.
326 * @param <T> type of the field elements
327 * @param date date at which weather parameters are requested
328 * @return weather parameters
329 * @since 12.1
330 */
331 public <T extends CalculusFieldElement<T>> FieldPressureTemperatureHumidity<T> getPressureTemperatureHumidity(final FieldAbsoluteDate<T> date) {
332 return pthProvider.getWeatherParamerers(getOffsetGeodeticPoint(date), date);
333 }
334
335 /** Get the displacement models.
336 * @return displacement models (empty if no model has been set up)
337 * @since 9.1
338 */
339 public StationDisplacement[] getDisplacements() {
340 return displacements.clone();
341 }
342
343 /** Get a driver allowing to change station clock (which is related to measurement date).
344 * @return driver for station clock offset
345 * @since 9.3
346 */
347 public ParameterDriver getClockOffsetDriver() {
348 return clockOffsetDriver;
349 }
350
351 /** Get a driver allowing to change station clock drift (which is related to measurement date).
352 * @return driver for station clock drift
353 * @since 10.3
354 */
355 public ParameterDriver getClockDriftDriver() {
356 return clockDriftDriver;
357 }
358
359 /** Get a driver allowing to change station clock acceleration (which is related to measurement date).
360 * @return driver for station clock acceleration
361 * @since 12.1
362 */
363 public ParameterDriver getClockAccelerationDriver() {
364 return clockAccelerationDriver;
365 }
366
367 /** Get a driver allowing to change station position along East axis.
368 * @return driver for station position offset along East axis
369 */
370 public ParameterDriver getEastOffsetDriver() {
371 return eastOffsetDriver;
372 }
373
374 /** Get a driver allowing to change station position along North axis.
375 * @return driver for station position offset along North axis
376 */
377 public ParameterDriver getNorthOffsetDriver() {
378 return northOffsetDriver;
379 }
380
381 /** Get a driver allowing to change station position along Zenith axis.
382 * @return driver for station position offset along Zenith axis
383 */
384 public ParameterDriver getZenithOffsetDriver() {
385 return zenithOffsetDriver;
386 }
387
388 /** Get a driver allowing to add a prime meridian rotation.
389 * <p>
390 * The parameter is an angle in radians. In order to convert this
391 * value to a DUT1 in seconds, the value must be divided by
392 * {@code ave = 7.292115146706979e-5} (which is the nominal Angular Velocity
393 * of Earth from the TIRF model).
394 * </p>
395 * @return driver for prime meridian rotation
396 */
397 public ParameterDriver getPrimeMeridianOffsetDriver() {
398 return estimatedEarthFrameProvider.getPrimeMeridianOffsetDriver();
399 }
400
401 /** Get a driver allowing to add a prime meridian rotation rate.
402 * <p>
403 * The parameter is an angle rate in radians per second. In order to convert this
404 * value to a LOD in seconds, the value must be multiplied by -86400 and divided by
405 * {@code ave = 7.292115146706979e-5} (which is the nominal Angular Velocity
406 * of Earth from the TIRF model).
407 * </p>
408 * @return driver for prime meridian rotation rate
409 */
410 public ParameterDriver getPrimeMeridianDriftDriver() {
411 return estimatedEarthFrameProvider.getPrimeMeridianDriftDriver();
412 }
413
414 /** Get a driver allowing to add a polar offset along X.
415 * <p>
416 * The parameter is an angle in radians
417 * </p>
418 * @return driver for polar offset along X
419 */
420 public ParameterDriver getPolarOffsetXDriver() {
421 return estimatedEarthFrameProvider.getPolarOffsetXDriver();
422 }
423
424 /** Get a driver allowing to add a polar drift along X.
425 * <p>
426 * The parameter is an angle rate in radians per second
427 * </p>
428 * @return driver for polar drift along X
429 */
430 public ParameterDriver getPolarDriftXDriver() {
431 return estimatedEarthFrameProvider.getPolarDriftXDriver();
432 }
433
434 /** Get a driver allowing to add a polar offset along Y.
435 * <p>
436 * The parameter is an angle in radians
437 * </p>
438 * @return driver for polar offset along Y
439 */
440 public ParameterDriver getPolarOffsetYDriver() {
441 return estimatedEarthFrameProvider.getPolarOffsetYDriver();
442 }
443
444 /** Get a driver allowing to add a polar drift along Y.
445 * <p>
446 * The parameter is an angle rate in radians per second
447 * </p>
448 * @return driver for polar drift along Y
449 */
450 public ParameterDriver getPolarDriftYDriver() {
451 return estimatedEarthFrameProvider.getPolarDriftYDriver();
452 }
453
454 /** Get the base frame associated with the station.
455 * <p>
456 * The base frame corresponds to a null position offset, null
457 * polar motion, null meridian shift
458 * </p>
459 * @return base frame associated with the station
460 */
461 public TopocentricFrame getBaseFrame() {
462 return baseFrame;
463 }
464
465 /** Get the estimated Earth frame, including the estimated linear models for pole and prime meridian.
466 * <p>
467 * This frame is bound to the {@link #getPrimeMeridianOffsetDriver() driver for prime meridian offset},
468 * {@link #getPrimeMeridianDriftDriver() driver prime meridian drift},
469 * {@link #getPolarOffsetXDriver() driver for polar offset along X},
470 * {@link #getPolarDriftXDriver() driver for polar drift along X},
471 * {@link #getPolarOffsetYDriver() driver for polar offset along Y},
472 * {@link #getPolarDriftYDriver() driver for polar drift along Y}, so its orientation changes when
473 * the {@link ParameterDriver#setValue(double) setValue} methods of the drivers are called.
474 * </p>
475 * @return estimated Earth frame
476 * @since 9.1
477 */
478 public Frame getEstimatedEarthFrame() {
479 return estimatedEarthFrame;
480 }
481
482 /** Get the estimated UT1 scale, including the estimated linear models for prime meridian.
483 * <p>
484 * This time scale is bound to the {@link #getPrimeMeridianOffsetDriver() driver for prime meridian offset},
485 * and {@link #getPrimeMeridianDriftDriver() driver prime meridian drift}, so its offset from UTC changes when
486 * the {@link ParameterDriver#setValue(double) setValue} methods of the drivers are called.
487 * </p>
488 * @return estimated Earth frame
489 * @since 9.1
490 */
491 public UT1Scale getEstimatedUT1() {
492 return estimatedEarthFrameProvider.getEstimatedUT1();
493 }
494
495 /** Get the station displacement.
496 * @param date current date
497 * @param position raw position of the station in Earth frame
498 * before displacement is applied
499 * @return station displacement
500 * @since 9.1
501 */
502 private Vector3D computeDisplacement(final AbsoluteDate date, final Vector3D position) {
503 Vector3D displacement = Vector3D.ZERO;
504 if (arguments != null) {
505 final BodiesElements elements = arguments.evaluateAll(date);
506 for (final StationDisplacement sd : displacements) {
507 // we consider all displacements apply to the same initial position,
508 // i.e. they apply simultaneously, not according to some order
509 displacement = displacement.add(sd.displacement(elements, estimatedEarthFrame, position));
510 }
511 }
512 return displacement;
513 }
514
515 /** Get the geodetic point at the center of the offset frame.
516 * @param date current date (may be null if displacements are ignored)
517 * @return geodetic point at the center of the offset frame
518 * @since 9.1
519 */
520 public GeodeticPoint getOffsetGeodeticPoint(final AbsoluteDate date) {
521
522 // take station offset into account
523 final double x = eastOffsetDriver.getValue();
524 final double y = northOffsetDriver.getValue();
525 final double z = zenithOffsetDriver.getValue();
526 final BodyShape baseShape = baseFrame.getParentShape();
527 final StaticTransform baseToBody = baseFrame.getStaticTransformTo(baseShape.getBodyFrame(), date);
528 Vector3D origin = baseToBody.transformPosition(new Vector3D(x, y, z));
529
530 if (date != null) {
531 origin = origin.add(computeDisplacement(date, origin));
532 }
533
534 return baseShape.transform(origin, baseShape.getBodyFrame(), date);
535
536 }
537
538 /** Get the geodetic point at the center of the offset frame.
539 * @param <T> type of the field elements
540 * @param date current date(<em>must</em> be non-null, which is a more stringent condition
541 * * than in {@link #getOffsetGeodeticPoint(AbsoluteDate)}
542 * @return geodetic point at the center of the offset frame
543 * @since 12.1
544 */
545 public <T extends CalculusFieldElement<T>> FieldGeodeticPoint<T> getOffsetGeodeticPoint(final FieldAbsoluteDate<T> date) {
546
547 // take station offset into account
548 final double x = eastOffsetDriver.getValue();
549 final double y = northOffsetDriver.getValue();
550 final double z = zenithOffsetDriver.getValue();
551 final BodyShape baseShape = baseFrame.getParentShape();
552 final FieldStaticTransform<T> baseToBody = baseFrame.getStaticTransformTo(baseShape.getBodyFrame(), date);
553 FieldVector3D<T> origin = baseToBody.transformPosition(new Vector3D(x, y, z));
554 origin = origin.add(computeDisplacement(date.toAbsoluteDate(), origin.toVector3D()));
555
556 return baseShape.transform(origin, baseShape.getBodyFrame(), date);
557
558 }
559
560 /** Get the transform between offset frame and inertial frame.
561 * <p>
562 * The offset frame takes the <em>current</em> position offset,
563 * polar motion and the meridian shift into account. The frame
564 * returned is disconnected from later changes in the parameters.
565 * When the {@link ParameterDriver parameters} managing these
566 * offsets are changed, the method must be called again to retrieve
567 * a new offset frame.
568 * </p>
569 * @param inertial inertial frame to transform to
570 * @param date date of the transform
571 * @param clockOffsetAlreadyApplied if true, the specified {@code date} is as read
572 * by the ground station clock (i.e. clock offset <em>not</em> compensated), if false,
573 * the specified {@code date} was already compensated and is a physical absolute date
574 * @return transform between offset frame and inertial frame, at <em>real</em> measurement
575 * date (i.e. with clock, Earth and station offsets applied)
576 */
577 public Transform getOffsetToInertial(final Frame inertial,
578 final AbsoluteDate date, final boolean clockOffsetAlreadyApplied) {
579
580 // take clock offset into account
581 final AbsoluteDate offsetCompensatedDate = clockOffsetAlreadyApplied ?
582 date :
583 new AbsoluteDate(date, -clockOffsetDriver.getValue());
584
585 // take Earth offsets into account
586 final Transform intermediateToBody = estimatedEarthFrameProvider.getTransform(offsetCompensatedDate).getInverse();
587
588 // take station offsets into account
589 final double x = eastOffsetDriver.getValue();
590 final double y = northOffsetDriver.getValue();
591 final double z = zenithOffsetDriver.getValue();
592 final BodyShape baseShape = baseFrame.getParentShape();
593 final StaticTransform baseToBody = baseFrame
594 .getStaticTransformTo(baseShape.getBodyFrame(), offsetCompensatedDate);
595 Vector3D origin = baseToBody.transformPosition(new Vector3D(x, y, z));
596 origin = origin.add(computeDisplacement(offsetCompensatedDate, origin));
597
598 final GeodeticPoint originGP = baseShape.transform(origin, baseShape.getBodyFrame(), offsetCompensatedDate);
599 final Transform offsetToIntermediate =
600 new Transform(offsetCompensatedDate,
601 new Transform(offsetCompensatedDate,
602 new Rotation(Vector3D.PLUS_I, Vector3D.PLUS_K,
603 originGP.getEast(), originGP.getZenith()),
604 Vector3D.ZERO),
605 new Transform(offsetCompensatedDate, origin));
606
607 // combine all transforms together
608 final Transform bodyToInert = baseFrame.getParent().getTransformTo(inertial, offsetCompensatedDate);
609
610 return new Transform(offsetCompensatedDate, offsetToIntermediate, new Transform(offsetCompensatedDate, intermediateToBody, bodyToInert));
611
612 }
613
614 /** Get the transform between offset frame and inertial frame with derivatives.
615 * <p>
616 * As the East and North vectors are not well defined at pole, the derivatives
617 * of these two vectors diverge to infinity as we get closer to the pole.
618 * So this method should not be used for stations less than 0.0001 degree from
619 * either poles.
620 * </p>
621 * @param inertial inertial frame to transform to
622 * @param clockDate date of the transform as read by the ground station clock (i.e. clock offset <em>not</em> compensated)
623 * @param freeParameters total number of free parameters in the gradient
624 * @param indices indices of the estimated parameters in derivatives computations, must be driver
625 * span name in map, not driver name or will not give right results (see {@link ParameterDriver#getValue(int, Map)})
626 * @return transform between offset frame and inertial frame, at <em>real</em> measurement
627 * date (i.e. with clock, Earth and station offsets applied)
628 * @see #getOffsetToInertial(Frame, FieldAbsoluteDate, int, Map)
629 * @since 10.2
630 */
631 public FieldTransform<Gradient> getOffsetToInertial(final Frame inertial,
632 final AbsoluteDate clockDate,
633 final int freeParameters,
634 final Map<String, Integer> indices) {
635 // take clock offset into account
636 final Gradient offset = clockOffsetDriver.getValue(freeParameters, indices, clockDate);
637 final FieldAbsoluteDate<Gradient> offsetCompensatedDate =
638 new FieldAbsoluteDate<>(clockDate, offset.negate());
639
640 return getOffsetToInertial(inertial, offsetCompensatedDate, freeParameters, indices);
641 }
642
643 /** Get the transform between offset frame and inertial frame with derivatives.
644 * <p>
645 * As the East and North vectors are not well defined at pole, the derivatives
646 * of these two vectors diverge to infinity as we get closer to the pole.
647 * So this method should not be used for stations less than 0.0001 degree from
648 * either poles.
649 * </p>
650 * @param inertial inertial frame to transform to
651 * @param offsetCompensatedDate date of the transform, clock offset and its derivatives already compensated
652 * @param freeParameters total number of free parameters in the gradient
653 * @param indices indices of the estimated parameters in derivatives computations, must be driver
654 * span name in map, not driver name or will not give right results (see {@link ParameterDriver#getValue(int, Map)})
655 * @return transform between offset frame and inertial frame, at specified date
656 * @since 10.2
657 */
658 public FieldTransform<Gradient> getOffsetToInertial(final Frame inertial,
659 final FieldAbsoluteDate<Gradient> offsetCompensatedDate,
660 final int freeParameters,
661 final Map<String, Integer> indices) {
662
663 final Field<Gradient> field = offsetCompensatedDate.getField();
664 final FieldVector3D<Gradient> zero = FieldVector3D.getZero(field);
665 final FieldVector3D<Gradient> plusI = FieldVector3D.getPlusI(field);
666 final FieldVector3D<Gradient> plusK = FieldVector3D.getPlusK(field);
667
668 // take Earth offsets into account
669 final FieldTransform<Gradient> intermediateToBody =
670 estimatedEarthFrameProvider.getTransform(offsetCompensatedDate, freeParameters, indices).getInverse();
671
672 // take station offsets into account
673 final Gradient x = eastOffsetDriver.getValue(freeParameters, indices);
674 final Gradient y = northOffsetDriver.getValue(freeParameters, indices);
675 final Gradient z = zenithOffsetDriver.getValue(freeParameters, indices);
676 final BodyShape baseShape = baseFrame.getParentShape();
677 final FieldStaticTransform<Gradient> baseToBody = baseFrame.getStaticTransformTo(baseShape.getBodyFrame(), offsetCompensatedDate);
678
679 FieldVector3D<Gradient> origin = baseToBody.transformPosition(new FieldVector3D<>(x, y, z));
680 origin = origin.add(computeDisplacement(offsetCompensatedDate.toAbsoluteDate(), origin.toVector3D()));
681 final FieldGeodeticPoint<Gradient> originGP = baseShape.transform(origin, baseShape.getBodyFrame(), offsetCompensatedDate);
682 final FieldTransform<Gradient> offsetToIntermediate =
683 new FieldTransform<>(offsetCompensatedDate,
684 new FieldTransform<>(offsetCompensatedDate,
685 new FieldRotation<>(plusI, plusK,
686 originGP.getEast(), originGP.getZenith()),
687 zero),
688 new FieldTransform<>(offsetCompensatedDate, origin));
689
690 // combine all transforms together
691 final FieldTransform<Gradient> bodyToInert = baseFrame.getParent().getTransformTo(inertial, offsetCompensatedDate);
692
693 return new FieldTransform<>(offsetCompensatedDate,
694 offsetToIntermediate,
695 new FieldTransform<>(offsetCompensatedDate, intermediateToBody, bodyToInert));
696
697 }
698
699 }