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 }