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.io.Serializable;
20  import java.util.Map;
21  
22  import org.hipparchus.CalculusFieldElement;
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.RotationConvention;
28  import org.hipparchus.geometry.euclidean.threed.Vector3D;
29  import org.hipparchus.util.FastMath;
30  import org.orekit.errors.OrekitException;
31  import org.orekit.errors.OrekitInternalError;
32  import org.orekit.errors.OrekitMessages;
33  import org.orekit.frames.FieldStaticTransform;
34  import org.orekit.frames.FieldTransform;
35  import org.orekit.frames.StaticTransform;
36  import org.orekit.frames.Transform;
37  import org.orekit.frames.TransformProvider;
38  import org.orekit.time.AbsoluteDate;
39  import org.orekit.time.FieldAbsoluteDate;
40  import org.orekit.time.UT1Scale;
41  import org.orekit.utils.IERSConventions;
42  import org.orekit.utils.ParameterDriver;
43  
44  /** Class modeling an Earth frame whose Earth Orientation Parameters can be estimated.
45   * <p>
46   * This class adds parameters for an additional polar motion
47   * and an additional prime meridian orientation on top of an underlying regular Earth
48   * frame like {@link org.orekit.frames.FramesFactory#getITRF(IERSConventions, boolean) ITRF}.
49   * The polar motion and prime meridian orientation are applied <em>after</em> regular Earth
50   * orientation parameters, so the value of the estimated parameters will be correction to EOP,
51   * they will not be the complete EOP values by themselves. Basically, this means that for
52   * Earth, the following transforms are applied in order, between inertial frame and this frame:
53   * </p>
54   * <ol>
55   *   <li>precession/nutation, as theoretical model plus celestial pole EOP parameters</li>
56   *   <li>body rotation, as theoretical model plus prime meridian EOP parameters</li>
57   *   <li>polar motion, which is only from EOP parameters (no theoretical models)</li>
58   *   <li>additional body rotation, controlled by {@link #getPrimeMeridianOffsetDriver()} and {@link #getPrimeMeridianDriftDriver()}</li>
59   *   <li>additional polar motion, controlled by {@link #getPolarOffsetXDriver()}, {@link #getPolarDriftXDriver()},
60   *   {@link #getPolarOffsetYDriver()} and {@link #getPolarDriftYDriver()}</li>
61   * </ol>
62   * @author Luc Maisonobe
63   * @since 9.1
64   */
65  public class EstimatedEarthFrameProvider implements TransformProvider {
66  
67      /** Earth Angular Velocity, in rad/s, from TIRF model. */
68      public static final double EARTH_ANGULAR_VELOCITY = 7.292115146706979e-5;
69  
70      /** Serializable UID. */
71      private static final long serialVersionUID = 20170922L;
72  
73      /** Angular scaling factor.
74       * <p>
75       * We use a power of 2 to avoid numeric noise introduction
76       * in the multiplications/divisions sequences.
77       * </p>
78       */
79      private static final double ANGULAR_SCALE = FastMath.scalb(1.0, -22);
80  
81      /** Underlying raw UT1. */
82      private final UT1Scale baseUT1;
83  
84      /** Estimated UT1. */
85      private final transient UT1Scale estimatedUT1;
86  
87      /** Driver for prime meridian offset. */
88      private final transient ParameterDriver primeMeridianOffsetDriver;
89  
90      /** Driver for prime meridian drift. */
91      private final transient ParameterDriver primeMeridianDriftDriver;
92  
93      /** Driver for pole offset along X. */
94      private final transient ParameterDriver polarOffsetXDriver;
95  
96      /** Driver for pole drift along X. */
97      private final transient ParameterDriver polarDriftXDriver;
98  
99      /** Driver for pole offset along Y. */
100     private final transient ParameterDriver polarOffsetYDriver;
101 
102     /** Driver for pole drift along Y. */
103     private final transient ParameterDriver polarDriftYDriver;
104 
105     /** Build an estimated Earth frame.
106      * <p>
107      * The initial values for the pole and prime meridian parametric linear models
108      * ({@link #getPrimeMeridianOffsetDriver()}, {@link #getPrimeMeridianDriftDriver()},
109      * {@link #getPolarOffsetXDriver()}, {@link #getPolarDriftXDriver()},
110      * {@link #getPolarOffsetXDriver()}, {@link #getPolarDriftXDriver()}) are set to 0.
111      * </p>
112      * @param baseUT1 underlying base UT1
113      * @since 9.1
114      */
115     public EstimatedEarthFrameProvider(final UT1Scale baseUT1) {
116 
117         this.primeMeridianOffsetDriver = new ParameterDriver("prime-meridian-offset",
118                                                              0.0, ANGULAR_SCALE,
119                                                             -FastMath.PI, FastMath.PI);
120 
121         this.primeMeridianDriftDriver = new ParameterDriver("prime-meridian-drift",
122                                                             0.0, ANGULAR_SCALE,
123                                                             Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
124 
125         this.polarOffsetXDriver = new ParameterDriver("polar-offset-X",
126                                                       0.0, ANGULAR_SCALE,
127                                                       -FastMath.PI, FastMath.PI);
128 
129         this.polarDriftXDriver = new ParameterDriver("polar-drift-X",
130                                                      0.0, ANGULAR_SCALE,
131                                                      Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
132 
133         this.polarOffsetYDriver = new ParameterDriver("polar-offset-Y",
134                                                       0.0, ANGULAR_SCALE,
135                                                       -FastMath.PI, FastMath.PI);
136 
137         this.polarDriftYDriver = new ParameterDriver("polar-drift-Y",
138                                                      0.0, ANGULAR_SCALE,
139                                                      Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
140 
141         this.baseUT1      = baseUT1;
142         this.estimatedUT1 = new EstimatedUT1Scale();
143 
144     }
145 
146     /** Get a driver allowing to add a prime meridian rotation.
147      * <p>
148      * The parameter is an angle in radians. In order to convert this
149      * value to a DUT1 in seconds, the value must be divided by
150      * {@link #EARTH_ANGULAR_VELOCITY} (nominal Angular Velocity of Earth).
151      * </p>
152      * @return driver for prime meridian rotation
153      */
154     public ParameterDriver getPrimeMeridianOffsetDriver() {
155         return primeMeridianOffsetDriver;
156     }
157 
158     /** Get a driver allowing to add a prime meridian rotation rate.
159      * <p>
160      * The parameter is an angle rate in radians per second. In order to convert this
161      * value to a LOD in seconds, the value must be multiplied by -86400 and divided by
162      * {@link #EARTH_ANGULAR_VELOCITY} (nominal Angular Velocity of Earth).
163      * </p>
164      * @return driver for prime meridian rotation rate
165      */
166     public ParameterDriver getPrimeMeridianDriftDriver() {
167         return primeMeridianDriftDriver;
168     }
169 
170     /** Get a driver allowing to add a polar offset along X.
171      * <p>
172      * The parameter is an angle in radians
173      * </p>
174      * @return driver for polar offset along X
175      */
176     public ParameterDriver getPolarOffsetXDriver() {
177         return polarOffsetXDriver;
178     }
179 
180     /** Get a driver allowing to add a polar drift along X.
181      * <p>
182      * The parameter is an angle rate in radians per second
183      * </p>
184      * @return driver for polar drift along X
185      */
186     public ParameterDriver getPolarDriftXDriver() {
187         return polarDriftXDriver;
188     }
189 
190     /** Get a driver allowing to add a polar offset along Y.
191      * <p>
192      * The parameter is an angle in radians
193      * </p>
194      * @return driver for polar offset along Y
195      */
196     public ParameterDriver getPolarOffsetYDriver() {
197         return polarOffsetYDriver;
198     }
199 
200     /** Get a driver allowing to add a polar drift along Y.
201      * <p>
202      * The parameter is an angle rate in radians per second
203      * </p>
204      * @return driver for polar drift along Y
205      */
206     public ParameterDriver getPolarDriftYDriver() {
207         return polarDriftYDriver;
208     }
209 
210     /** Get the estimated UT1 time scale.
211      * @return estimated UT1 time scale
212      */
213     public UT1Scale getEstimatedUT1() {
214         return estimatedUT1;
215     }
216 
217     /** {@inheritDoc} */
218     @Override
219     public Transform getTransform(final AbsoluteDate date) {
220 
221         // take parametric prime meridian shift into account
222         final double theta    = linearModel(date, primeMeridianOffsetDriver, primeMeridianDriftDriver);
223         final double thetaDot = primeMeridianDriftDriver.getValue();
224         final Transform meridianShift =
225                         new Transform(date,
226                                       new Rotation(Vector3D.PLUS_K, theta, RotationConvention.FRAME_TRANSFORM),
227                                       new Vector3D(0, 0, thetaDot));
228 
229         // take parametric pole shift into account
230         final double xpNeg     = -linearModel(date, polarOffsetXDriver, polarDriftXDriver);
231         final double ypNeg     = -linearModel(date, polarOffsetYDriver, polarDriftYDriver);
232         final double xpNegDot  = -polarDriftXDriver.getValue();
233         final double ypNegDot  = -polarDriftYDriver.getValue();
234         final Transform poleShift =
235                         new Transform(date,
236                                       new Transform(date,
237                                                     new Rotation(Vector3D.PLUS_J, xpNeg, RotationConvention.FRAME_TRANSFORM),
238                                                     new Vector3D(0.0, xpNegDot, 0.0)),
239                                       new Transform(date,
240                                                     new Rotation(Vector3D.PLUS_I, ypNeg, RotationConvention.FRAME_TRANSFORM),
241                                                     new Vector3D(ypNegDot, 0.0, 0.0)));
242 
243         return new Transform(date, meridianShift, poleShift);
244 
245     }
246 
247     /** {@inheritDoc} */
248     @Override
249     public StaticTransform getStaticTransform(final AbsoluteDate date) {
250 
251         // take parametric prime meridian shift into account
252         final double theta    = linearModel(date, primeMeridianOffsetDriver, primeMeridianDriftDriver);
253         final StaticTransform meridianShift = StaticTransform.of(
254                 date,
255                 new Rotation(Vector3D.PLUS_K, theta, RotationConvention.FRAME_TRANSFORM)
256         );
257 
258         // take parametric pole shift into account
259         final double xpNeg     = -linearModel(date, polarOffsetXDriver, polarDriftXDriver);
260         final double ypNeg     = -linearModel(date, polarOffsetYDriver, polarDriftYDriver);
261         final StaticTransform poleShift = StaticTransform.compose(
262                 date,
263                 StaticTransform.of(
264                         date,
265                         new Rotation(Vector3D.PLUS_J, xpNeg, RotationConvention.FRAME_TRANSFORM)),
266                 StaticTransform.of(
267                         date,
268                         new Rotation(Vector3D.PLUS_I, ypNeg, RotationConvention.FRAME_TRANSFORM)));
269 
270         return StaticTransform.compose(date, meridianShift, poleShift);
271 
272     }
273 
274     /** {@inheritDoc} */
275     @Override
276     public <T extends CalculusFieldElement<T>> FieldTransform<T> getTransform(final FieldAbsoluteDate<T> date) {
277 
278         final T zero = date.getField().getZero();
279 
280         // prime meridian shift parameters
281         final T theta    = linearModel(date, primeMeridianOffsetDriver, primeMeridianDriftDriver);
282         final T thetaDot = zero.newInstance(primeMeridianDriftDriver.getValue());
283 
284         // pole shift parameters
285         final T xpNeg    = linearModel(date, polarOffsetXDriver, polarDriftXDriver).negate();
286         final T ypNeg    = linearModel(date, polarOffsetYDriver, polarDriftYDriver).negate();
287         final T xpNegDot = zero.subtract(polarDriftXDriver.getValue());
288         final T ypNegDot = zero.subtract(polarDriftYDriver.getValue());
289 
290         return getTransform(date, theta, thetaDot, xpNeg, xpNegDot, ypNeg, ypNegDot);
291 
292     }
293 
294     /** {@inheritDoc} */
295     @Override
296     public <T extends CalculusFieldElement<T>> FieldStaticTransform<T> getStaticTransform(final FieldAbsoluteDate<T> date) {
297 
298         // take parametric prime meridian shift into account
299         final T theta    = linearModel(date, primeMeridianOffsetDriver, primeMeridianDriftDriver);
300         final FieldStaticTransform<T> meridianShift = FieldStaticTransform.of(
301                 date,
302                 new FieldRotation<>(FieldVector3D.getPlusK(date.getField()), theta, RotationConvention.FRAME_TRANSFORM)
303         );
304 
305         // take parametric pole shift into account
306         final T xpNeg     = linearModel(date, polarOffsetXDriver, polarDriftXDriver).negate();
307         final T ypNeg     = linearModel(date, polarOffsetYDriver, polarDriftYDriver).negate();
308         final FieldStaticTransform<T> poleShift = FieldStaticTransform.compose(
309                 date,
310                 FieldStaticTransform.of(
311                         date,
312                         new FieldRotation<>(FieldVector3D.getPlusJ(date.getField()), xpNeg, RotationConvention.FRAME_TRANSFORM)),
313                 FieldStaticTransform.of(
314                         date,
315                         new FieldRotation<>(FieldVector3D.getPlusI(date.getField()), ypNeg, RotationConvention.FRAME_TRANSFORM)));
316 
317         return FieldStaticTransform.compose(date, meridianShift, poleShift);
318 
319     }
320 
321     /** Get the transform with derivatives.
322      * @param date date of the transform
323      * @param freeParameters total number of free parameters in the gradient
324      * @param indices indices of the estimated parameters in derivatives computations
325      * @return computed transform with derivatives
326      * @since 10.2
327      */
328     public FieldTransform<Gradient> getTransform(final FieldAbsoluteDate<Gradient> date,
329                                                  final int freeParameters,
330                                                  final Map<String, Integer> indices) {
331 
332         // prime meridian shift parameters
333         final Gradient theta    = linearModel(freeParameters, date,
334                                               primeMeridianOffsetDriver, primeMeridianDriftDriver,
335                                               indices);
336         final Gradient thetaDot = primeMeridianDriftDriver.getValue(freeParameters, indices, date.toAbsoluteDate());
337 
338         // pole shift parameters
339         final Gradient xpNeg    = linearModel(freeParameters, date,
340                                                          polarOffsetXDriver, polarDriftXDriver, indices).negate();
341         final Gradient ypNeg    = linearModel(freeParameters, date,
342                                                          polarOffsetYDriver, polarDriftYDriver, indices).negate();
343         final Gradient xpNegDot = polarDriftXDriver.getValue(freeParameters, indices, date.toAbsoluteDate()).negate();
344         final Gradient ypNegDot = polarDriftYDriver.getValue(freeParameters, indices, date.toAbsoluteDate()).negate();
345 
346         return getTransform(date, theta, thetaDot, xpNeg, xpNegDot, ypNeg, ypNegDot);
347 
348     }
349 
350     /** Get the transform with derivatives.
351      * @param date date of the transform
352      * @param theta angle of the prime meridian
353      * @param thetaDot angular rate of the prime meridian
354      * @param xpNeg opposite of the angle of the pole motion along X
355      * @param xpNegDot opposite of the angular rate of the pole motion along X
356      * @param ypNeg opposite of the angle of the pole motion along Y
357      * @param ypNegDot opposite of the angular rate of the pole motion along Y
358      * @param <T> type of the field elements
359      * @return computed transform with derivatives
360      */
361     private <T extends CalculusFieldElement<T>> FieldTransform<T> getTransform(final FieldAbsoluteDate<T> date,
362                                                                            final T theta, final T thetaDot,
363                                                                            final T xpNeg, final T xpNegDot,
364                                                                            final T ypNeg, final T ypNegDot) {
365 
366         final T                zero  = date.getField().getZero();
367         final FieldVector3D<T> plusI = FieldVector3D.getPlusI(date.getField());
368         final FieldVector3D<T> plusJ = FieldVector3D.getPlusJ(date.getField());
369         final FieldVector3D<T> plusK = FieldVector3D.getPlusK(date.getField());
370 
371         // take parametric prime meridian shift into account
372         final FieldTransform<T> meridianShift =
373                         new FieldTransform<>(date,
374                                              new FieldRotation<>(plusK, theta, RotationConvention.FRAME_TRANSFORM),
375                                              new FieldVector3D<>(zero, zero, thetaDot));
376 
377         // take parametric pole shift into account
378         final FieldTransform<T> poleShift =
379                         new FieldTransform<>(date,
380                                       new FieldTransform<>(date,
381                                                            new FieldRotation<>(plusJ, xpNeg, RotationConvention.FRAME_TRANSFORM),
382                                                            new FieldVector3D<>(zero, xpNegDot, zero)),
383                                       new FieldTransform<>(date,
384                                                            new FieldRotation<>(plusI, ypNeg, RotationConvention.FRAME_TRANSFORM),
385                                                            new FieldVector3D<>(ypNegDot, zero, zero)));
386 
387         return new FieldTransform<>(date, meridianShift, poleShift);
388 
389     }
390 
391     /** Evaluate a parametric linear model.
392      * @param date current date
393      * @param offsetDriver driver for the offset parameter
394      * @param driftDriver driver for the drift parameter
395      * @return current value of the linear model
396      */
397     private double linearModel(final AbsoluteDate date,
398                                final ParameterDriver offsetDriver, final ParameterDriver driftDriver) {
399         if (offsetDriver.getReferenceDate() == null) {
400             throw new OrekitException(OrekitMessages.NO_REFERENCE_DATE_FOR_PARAMETER,
401                                       offsetDriver.getName());
402         }
403         final double dt     = date.durationFrom(offsetDriver.getReferenceDate());
404         final double offset = offsetDriver.getValue();
405         final double drift  = driftDriver.getValue();
406         return dt * drift + offset;
407     }
408 
409     /** Evaluate a parametric linear model.
410      * @param date current date
411      * @param offsetDriver driver for the offset parameter
412      * @param driftDriver driver for the drift parameter
413      * @return current value of the linear model
414      * @param <T> type of the filed elements
415      */
416     private <T extends CalculusFieldElement<T>> T linearModel(final FieldAbsoluteDate<T> date,
417                                                           final ParameterDriver offsetDriver,
418                                                           final ParameterDriver driftDriver) {
419         if (offsetDriver.getReferenceDate() == null) {
420             throw new OrekitException(OrekitMessages.NO_REFERENCE_DATE_FOR_PARAMETER,
421                                       offsetDriver.getName());
422         }
423         final T dt          = date.durationFrom(offsetDriver.getReferenceDate());
424         final double offset = offsetDriver.getValue();
425         final double drift  = driftDriver.getValue();
426         return dt.multiply(drift).add(offset);
427     }
428 
429     /** Evaluate a parametric linear model.
430      * @param freeParameters total number of free parameters in the gradient
431      * @param date current date
432      * @param offsetDriver driver for the offset parameter
433      * @param driftDriver driver for the drift parameter
434      * @param indices indices of the estimated parameters in derivatives computations
435      * @return current value of the linear model
436      * @since 10.2
437      */
438     private Gradient linearModel(final int freeParameters, final FieldAbsoluteDate<Gradient> date,
439                                  final ParameterDriver offsetDriver, final ParameterDriver driftDriver,
440                                  final Map<String, Integer> indices) {
441         if (offsetDriver.getReferenceDate() == null) {
442             throw new OrekitException(OrekitMessages.NO_REFERENCE_DATE_FOR_PARAMETER,
443                                       offsetDriver.getName());
444         }
445         final Gradient dt     = date.durationFrom(offsetDriver.getReferenceDate());
446         final Gradient offset = offsetDriver.getValue(freeParameters, indices, date.toAbsoluteDate());
447         final Gradient drift  = driftDriver.getValue(freeParameters, indices, date.toAbsoluteDate());
448         return dt.multiply(drift).add(offset);
449     }
450 
451     /** Replace the instance with a data transfer object for serialization.
452      * <p>
453      * This intermediate class serializes the files supported names, the ephemeris type
454      * and the body name.
455      * </p>
456      * @return data transfer object that will be serialized
457      */
458     private Object writeReplace() {
459         return new DataTransferObject(baseUT1,
460                                       primeMeridianOffsetDriver.getValue(),
461                                       primeMeridianDriftDriver.getValue(),
462                                       polarOffsetXDriver.getValue(),
463                                       polarDriftXDriver.getValue(),
464                                       polarOffsetYDriver.getValue(),
465                                       polarDriftYDriver.getValue());
466     }
467 
468     /** Local time scale for estimated UT1. */
469     private class EstimatedUT1Scale extends UT1Scale {
470 
471         /** Serializable UID. */
472         private static final long serialVersionUID = 20170922L;
473 
474         /** Simple constructor.
475          */
476         EstimatedUT1Scale() {
477             super(baseUT1.getEOPHistory(), baseUT1.getUTCScale());
478         }
479 
480         /** {@inheritDoc} */
481         @Override
482         public <T extends CalculusFieldElement<T>> T offsetFromTAI(final FieldAbsoluteDate<T> date) {
483             final T dut1 = linearModel(date, primeMeridianOffsetDriver, primeMeridianDriftDriver).divide(EARTH_ANGULAR_VELOCITY);
484             return baseUT1.offsetFromTAI(date).add(dut1);
485         }
486 
487         /** {@inheritDoc} */
488         @Override
489         public double offsetFromTAI(final AbsoluteDate date) {
490             final double dut1 = linearModel(date, primeMeridianOffsetDriver, primeMeridianDriftDriver) / EARTH_ANGULAR_VELOCITY;
491             return baseUT1.offsetFromTAI(date) + dut1;
492         }
493 
494         /** {@inheritDoc} */
495         @Override
496         public String getName() {
497             return baseUT1.getName() + "/estimated";
498         }
499 
500     }
501 
502     /** Internal class used only for serialization. */
503     private static class DataTransferObject implements Serializable {
504 
505         /** Serializable UID. */
506         private static final long serialVersionUID = 20171124L;
507 
508         /** Underlying raw UT1. */
509         private final UT1Scale baseUT1;
510 
511         /** Current prime meridian offset. */
512         private final double primeMeridianOffset;
513 
514         /** Current prime meridian drift. */
515         private final double primeMeridianDrift;
516 
517         /** Current pole offset along X. */
518         private final double polarOffsetX;
519 
520         /** Current pole drift along X. */
521         private final double polarDriftX;
522 
523         /** Current pole offset along Y. */
524         private final double polarOffsetY;
525 
526         /** Current pole drift along Y. */
527         private final double polarDriftY;
528 
529         /** Simple constructor.
530          * @param baseUT1 underlying raw UT1
531          * @param primeMeridianOffset current prime meridian offset
532          * @param primeMeridianDrift current prime meridian drift
533          * @param polarOffsetX current pole offset along X
534          * @param polarDriftX current pole drift along X
535          * @param polarOffsetY current pole offset along Y
536          * @param polarDriftY current pole drift along Y
537          */
538         DataTransferObject(final  UT1Scale baseUT1,
539                            final double primeMeridianOffset, final double primeMeridianDrift,
540                            final double polarOffsetX,        final double polarDriftX,
541                            final double polarOffsetY,        final double polarDriftY) {
542             this.baseUT1             = baseUT1;
543             this.primeMeridianOffset = primeMeridianOffset;
544             this.primeMeridianDrift  = primeMeridianDrift;
545             this.polarOffsetX        = polarOffsetX;
546             this.polarDriftX         = polarDriftX;
547             this.polarOffsetY        = polarOffsetY;
548             this.polarDriftY         = polarDriftY;
549         }
550 
551         /** Replace the deserialized data transfer object with a {@link EstimatedEarthFrameProvider}.
552          * @return replacement {@link EstimatedEarthFrameProvider}
553          */
554         private Object readResolve() {
555             try {
556                 final EstimatedEarthFrameProvider provider = new EstimatedEarthFrameProvider(baseUT1);
557                 provider.getPrimeMeridianOffsetDriver().setValue(primeMeridianOffset);
558                 provider.getPrimeMeridianDriftDriver().setValue(primeMeridianDrift);
559                 provider.getPolarOffsetXDriver().setValue(polarOffsetX);
560                 provider.getPolarDriftXDriver().setValue(polarDriftX);
561                 provider.getPolarOffsetYDriver().setValue(polarOffsetY);
562                 provider.getPolarDriftYDriver().setValue(polarDriftY);
563                 return provider;
564             } catch (OrekitException oe) {
565                 // this should never happen as values already come from previous drivers
566                 throw new OrekitInternalError(oe);
567             }
568         }
569 
570     }
571 
572 }