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