1   /* Contributed in the public domain.
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.frames;
18  
19  import java.io.Serial;
20  import java.io.Serializable;
21  import java.util.HashMap;
22  import java.util.Map;
23  import java.util.function.Supplier;
24  
25  import org.orekit.bodies.CelestialBodies;
26  import org.orekit.errors.OrekitInternalError;
27  import org.orekit.time.AbsoluteDate;
28  import org.orekit.time.TimeScales;
29  import org.orekit.time.UT1Scale;
30  import org.orekit.utils.AngularDerivativesFilter;
31  import org.orekit.utils.CartesianDerivativesFilter;
32  import org.orekit.utils.Constants;
33  import org.orekit.utils.IERSConventions;
34  import org.orekit.utils.OrekitConfiguration;
35  
36  /**
37   * This class is an implementation of {@link Frames} that creates frames when they are
38   * first used and uses synchronization to ensure that each frame is only created once.
39   *
40   * @author Guylaine Prat
41   * @author Luc Maisonobe
42   * @author Pascal Parraud
43   * @author Evan Ward
44   * @see LazyLoadedFrames
45   * @see #getEOPHistory(IERSConventions, boolean)
46   * @since 10.1
47   */
48  public abstract class AbstractFrames implements Frames {
49  
50      /** Provider of common time scales. */
51      private final TimeScales timeScales;
52      /** Provider of the ICRF frame, usually delegated to {@link CelestialBodies}. */
53      private final Supplier<Frame> icrfSupplier;
54      /** Predefined frames. */
55      private final Map<Predefined, FactoryManagedFrame> frames;
56      /** Predefined versioned ITRF frames. */
57      private final Map<ITRFKey, VersionedITRF> versionedItrfFrames;
58  
59      /**
60       * Simple constructor.
61       *
62       * @param timeScales   to use when creating frames.
63       * @param icrfSupplier used to implement {@link #getICRF()};
64       */
65      protected AbstractFrames(final TimeScales timeScales,
66                               final Supplier<Frame> icrfSupplier) {
67          this.timeScales = timeScales;
68          this.icrfSupplier = icrfSupplier;
69          this.frames = new HashMap<>();
70          this.versionedItrfFrames = new HashMap<>();
71      }
72  
73      @Override
74      public Frame getFrame(final Predefined factoryKey) {
75          return switch (factoryKey) {
76              case GCRF -> getGCRF();
77              case ICRF -> getICRF();
78              case ECLIPTIC_CONVENTIONS_1996 -> getEcliptic(IERSConventions.IERS_1996);
79              case ECLIPTIC_CONVENTIONS_2003 -> getEcliptic(IERSConventions.IERS_2003);
80              case ECLIPTIC_CONVENTIONS_2010 -> getEcliptic(IERSConventions.IERS_2010);
81              case EME2000 -> getEME2000();
82              case ITRF_CIO_CONV_2010_SIMPLE_EOP -> getITRF(IERSConventions.IERS_2010, true);
83              case ITRF_CIO_CONV_2010_ACCURATE_EOP -> getITRF(IERSConventions.IERS_2010, false);
84              case ITRF_CIO_CONV_2003_SIMPLE_EOP -> getITRF(IERSConventions.IERS_2003, true);
85              case ITRF_CIO_CONV_2003_ACCURATE_EOP -> getITRF(IERSConventions.IERS_2003, false);
86              case ITRF_CIO_CONV_1996_SIMPLE_EOP -> getITRF(IERSConventions.IERS_1996, true);
87              case ITRF_CIO_CONV_1996_ACCURATE_EOP -> getITRF(IERSConventions.IERS_1996, false);
88              case ITRF_EQUINOX_CONV_2010_SIMPLE_EOP -> getITRFEquinox(IERSConventions.IERS_2010, true);
89              case ITRF_EQUINOX_CONV_2010_ACCURATE_EOP -> getITRFEquinox(IERSConventions.IERS_2010, false);
90              case ITRF_EQUINOX_CONV_2003_SIMPLE_EOP -> getITRFEquinox(IERSConventions.IERS_2003, true);
91              case ITRF_EQUINOX_CONV_2003_ACCURATE_EOP -> getITRFEquinox(IERSConventions.IERS_2003, false);
92              case ITRF_EQUINOX_CONV_1996_SIMPLE_EOP -> getITRFEquinox(IERSConventions.IERS_1996, true);
93              case ITRF_EQUINOX_CONV_1996_ACCURATE_EOP -> getITRFEquinox(IERSConventions.IERS_1996, false);
94              case TIRF_CONVENTIONS_2010_SIMPLE_EOP -> getTIRF(IERSConventions.IERS_2010, true);
95              case TIRF_CONVENTIONS_2010_ACCURATE_EOP -> getTIRF(IERSConventions.IERS_2010, false);
96              case TIRF_CONVENTIONS_2003_SIMPLE_EOP -> getTIRF(IERSConventions.IERS_2003, true);
97              case TIRF_CONVENTIONS_2003_ACCURATE_EOP -> getTIRF(IERSConventions.IERS_2003, false);
98              case TIRF_CONVENTIONS_1996_SIMPLE_EOP -> getTIRF(IERSConventions.IERS_1996, true);
99              case TIRF_CONVENTIONS_1996_ACCURATE_EOP -> getTIRF(IERSConventions.IERS_1996, false);
100             case CIRF_CONVENTIONS_2010_ACCURATE_EOP -> getCIRF(IERSConventions.IERS_2010, false);
101             case CIRF_CONVENTIONS_2010_SIMPLE_EOP -> getCIRF(IERSConventions.IERS_2010, true);
102             case CIRF_CONVENTIONS_2003_ACCURATE_EOP -> getCIRF(IERSConventions.IERS_2003, false);
103             case CIRF_CONVENTIONS_2003_SIMPLE_EOP -> getCIRF(IERSConventions.IERS_2003, true);
104             case CIRF_CONVENTIONS_1996_ACCURATE_EOP -> getCIRF(IERSConventions.IERS_1996, false);
105             case CIRF_CONVENTIONS_1996_SIMPLE_EOP -> getCIRF(IERSConventions.IERS_1996, true);
106             case VEIS_1950 -> getVeis1950();
107             case GTOD_WITHOUT_EOP_CORRECTIONS -> getGTOD(IERSConventions.IERS_1996, false, true);
108             case GTOD_CONVENTIONS_2010_ACCURATE_EOP -> getGTOD(IERSConventions.IERS_2010, true, false);
109             case GTOD_CONVENTIONS_2010_SIMPLE_EOP -> getGTOD(IERSConventions.IERS_2010, true, true);
110             case GTOD_CONVENTIONS_2003_ACCURATE_EOP -> getGTOD(IERSConventions.IERS_2003, true, false);
111             case GTOD_CONVENTIONS_2003_SIMPLE_EOP -> getGTOD(IERSConventions.IERS_2003, true, true);
112             case GTOD_CONVENTIONS_1996_ACCURATE_EOP -> getGTOD(IERSConventions.IERS_1996, true, false);
113             case GTOD_CONVENTIONS_1996_SIMPLE_EOP -> getGTOD(IERSConventions.IERS_1996, true, true);
114             case TOD_WITHOUT_EOP_CORRECTIONS -> getTOD(IERSConventions.IERS_1996, false, true);
115             case TOD_CONVENTIONS_2010_ACCURATE_EOP -> getTOD(IERSConventions.IERS_2010, true, false);
116             case TOD_CONVENTIONS_2010_SIMPLE_EOP -> getTOD(IERSConventions.IERS_2010, true, true);
117             case TOD_CONVENTIONS_2003_ACCURATE_EOP -> getTOD(IERSConventions.IERS_2003, true, false);
118             case TOD_CONVENTIONS_2003_SIMPLE_EOP -> getTOD(IERSConventions.IERS_2003, true, true);
119             case TOD_CONVENTIONS_1996_ACCURATE_EOP -> getTOD(IERSConventions.IERS_1996, true, false);
120             case TOD_CONVENTIONS_1996_SIMPLE_EOP -> getTOD(IERSConventions.IERS_1996, true, true);
121             case MOD_WITHOUT_EOP_CORRECTIONS -> getMOD(IERSConventions.IERS_1996, false);
122             case MOD_CONVENTIONS_2010 -> getMOD(IERSConventions.IERS_2010, true);
123             case MOD_CONVENTIONS_2003 -> getMOD(IERSConventions.IERS_2003, true);
124             case MOD_CONVENTIONS_1996 -> getMOD(IERSConventions.IERS_1996, true);
125             case TEME -> getTEME();
126             case PZ90_11 -> getPZ9011(IERSConventions.IERS_2010, true);
127         };
128     }
129 
130     @Override
131     public Frame getGCRF() {
132         return Frame.getRoot();
133     }
134 
135     @Override
136     public Frame getICRF() {
137         return icrfSupplier.get();
138     }
139 
140     @Override
141     public Frame getEcliptic(final IERSConventions conventions) {
142         synchronized (this) {
143 
144             final Predefined factoryKey = switch (conventions) {
145                 case IERS_1996 -> Predefined.ECLIPTIC_CONVENTIONS_1996;
146                 case IERS_2003 -> Predefined.ECLIPTIC_CONVENTIONS_2003;
147                 case IERS_2010 -> Predefined.ECLIPTIC_CONVENTIONS_2010;
148             };
149             final Frame parent = getMOD(conventions);
150 
151             // try to find an already built frame
152             FactoryManagedFrame frame = frames.get(factoryKey);
153 
154             if (frame == null) {
155                 // it's the first time we need this frame, build it and store it
156                 final EclipticProvider provider =
157                         new EclipticProvider(conventions, getTimeScales());
158                 frame = new FactoryManagedFrame(parent, provider, true, factoryKey);
159                 frames.put(factoryKey, frame);
160             }
161 
162             return frame;
163 
164         }
165     }
166 
167     @Override
168     public FactoryManagedFrame getEME2000() {
169         synchronized (this) {
170 
171             // try to find an already built frame
172             FactoryManagedFrame frame = frames.get(Predefined.EME2000);
173 
174             if (frame == null) {
175                 // it's the first time we need this frame, build it and store it
176                 frame = new FactoryManagedFrame(getGCRF(), new EME2000Provider(), true, Predefined.EME2000);
177                 frames.put(Predefined.EME2000, frame);
178             }
179 
180             return frame;
181 
182         }
183     }
184 
185     @Override
186     public FactoryManagedFrame getITRF(final IERSConventions conventions,
187                                        final boolean simpleEOP) {
188         synchronized (this) {
189 
190             // try to find an already built frame
191             final Predefined factoryKey = switch (conventions) {
192                 case IERS_1996 -> simpleEOP ?
193                         Predefined.ITRF_CIO_CONV_1996_SIMPLE_EOP :
194                         Predefined.ITRF_CIO_CONV_1996_ACCURATE_EOP;
195                 case IERS_2003 -> simpleEOP ?
196                         Predefined.ITRF_CIO_CONV_2003_SIMPLE_EOP :
197                         Predefined.ITRF_CIO_CONV_2003_ACCURATE_EOP;
198                 case IERS_2010 -> simpleEOP ?
199                         Predefined.ITRF_CIO_CONV_2010_SIMPLE_EOP :
200                         Predefined.ITRF_CIO_CONV_2010_ACCURATE_EOP;
201             };
202             FactoryManagedFrame frame = frames.get(factoryKey);
203 
204             if (frame == null) {
205                 // it's the first time we need this frame, build it and store it
206                 final Frame tirfFrame = getTIRF(conventions, simpleEOP);
207                 final TIRFProvider tirfProvider = (TIRFProvider) tirfFrame.getTransformProvider();
208                 frame = new FactoryManagedFrame(tirfFrame,
209                         new ITRFProvider(tirfProvider.getEOPHistory()),
210                         false, factoryKey);
211                 frames.put(factoryKey, frame);
212             }
213 
214             return frame;
215 
216         }
217     }
218 
219     @Override
220     public VersionedITRF getITRF(final ITRFVersion version,
221                                  final IERSConventions conventions,
222                                  final boolean simpleEOP) {
223         synchronized (this) {
224             // try to find an already built frame
225             final ITRFKey key = new ITRFKey(version, conventions, simpleEOP);
226             VersionedITRF frame = versionedItrfFrames.get(key);
227 
228             if (frame == null) {
229                 // it's the first time we need this frame, build it and store it
230                 final FactoryManagedFrame rawITRF = getITRF(conventions, simpleEOP);
231                 frame = new VersionedITRF(rawITRF.getParent(), version,
232                         (ITRFProvider) rawITRF.getTransformProvider(),
233                         version.toString().replace('_', '-') +
234                                 "/" +
235                                 rawITRF.getName(),
236                         getTimeScales().getTT());
237                 versionedItrfFrames.put(key, frame);
238             }
239 
240             return frame;
241 
242         }
243     }
244 
245     @Override
246     public Frame buildUncachedITRF(final UT1Scale ut1) {
247 
248         // extract EOP history
249         final EOPHistory eopHistory = ut1.getEOPHistory();
250 
251         // build CIRF
252         final TransformProvider shifting =
253                         new ShiftingTransformProvider(new CIRFProvider(eopHistory),
254                                 CartesianDerivativesFilter.USE_PVA,
255                                 AngularDerivativesFilter.USE_R,
256                                 6, Constants.JULIAN_DAY / 24,
257                                 OrekitConfiguration.getCacheSlotsNumber(),
258                                 Constants.JULIAN_YEAR, 30 * Constants.JULIAN_DAY);
259         final Frame cirf = new Frame(getGCRF(), shifting, "CIRF (uncached)", true);
260 
261         // build TIRF
262         final Frame tirf = new Frame(cirf, new TIRFProvider(eopHistory, ut1),
263                                           "TIRF (uncached)", false);
264 
265         // build ITRF
266         return new Frame(tirf, new ITRFProvider(eopHistory),
267                          "ITRF (uncached)", false);
268 
269     }
270 
271     @Override
272     public FactoryManagedFrame getTIRF(final IERSConventions conventions) {
273         return getTIRF(conventions, true);
274     }
275 
276     @Override
277     public FactoryManagedFrame getTIRF(final IERSConventions conventions,
278                                        final boolean simpleEOP) {
279         synchronized (this) {
280 
281             // try to find an already built frame
282             final Predefined factoryKey = switch (conventions) {
283                 case IERS_1996 -> simpleEOP ?
284                         Predefined.TIRF_CONVENTIONS_1996_SIMPLE_EOP :
285                         Predefined.TIRF_CONVENTIONS_1996_ACCURATE_EOP;
286                 case IERS_2003 -> simpleEOP ?
287                         Predefined.TIRF_CONVENTIONS_2003_SIMPLE_EOP :
288                         Predefined.TIRF_CONVENTIONS_2003_ACCURATE_EOP;
289                 case IERS_2010 -> simpleEOP ?
290                         Predefined.TIRF_CONVENTIONS_2010_SIMPLE_EOP :
291                         Predefined.TIRF_CONVENTIONS_2010_ACCURATE_EOP;
292             };
293             FactoryManagedFrame frame = frames.get(factoryKey);
294 
295             if (frame == null) {
296                 // it's the first time we need this frame, build it and store it
297                 final Frame cirf = getCIRF(conventions, simpleEOP);
298                 final ShiftingTransformProvider cirfInterpolating =
299                         (ShiftingTransformProvider) cirf.getTransformProvider();
300                 final CIRFProvider cirfRaw = (CIRFProvider) cirfInterpolating.getRawProvider();
301                 final EOPHistory eopHistory = cirfRaw.getEOPHistory();
302                 final TIRFProvider provider =
303                         new TIRFProvider(eopHistory, getTimeScales().getUT1(conventions, simpleEOP));
304                 frame = new FactoryManagedFrame(cirf, provider, false, factoryKey);
305                 frames.put(factoryKey, frame);
306             }
307 
308             return frame;
309 
310         }
311     }
312 
313     @Override
314     public FactoryManagedFrame getCIRF(final IERSConventions conventions,
315                                        final boolean simpleEOP) {
316         synchronized (this) {
317 
318             // try to find an already built frame
319             final Predefined factoryKey = switch (conventions) {
320                 case IERS_1996 -> simpleEOP ?
321                         Predefined.CIRF_CONVENTIONS_1996_SIMPLE_EOP :
322                         Predefined.CIRF_CONVENTIONS_1996_ACCURATE_EOP;
323                 case IERS_2003 -> simpleEOP ?
324                         Predefined.CIRF_CONVENTIONS_2003_SIMPLE_EOP :
325                         Predefined.CIRF_CONVENTIONS_2003_ACCURATE_EOP;
326                 case IERS_2010 -> simpleEOP ?
327                         Predefined.CIRF_CONVENTIONS_2010_SIMPLE_EOP :
328                         Predefined.CIRF_CONVENTIONS_2010_ACCURATE_EOP;
329             };
330             FactoryManagedFrame frame = frames.get(factoryKey);
331 
332             if (frame == null) {
333                 // it's the first time we need this frame, build it and store it
334                 final EOPHistory eopHistory = getEOPHistory(conventions, simpleEOP);
335                 final TransformProvider shifting =
336                         new ShiftingTransformProvider(new CIRFProvider(eopHistory),
337                                 CartesianDerivativesFilter.USE_PVA,
338                                 AngularDerivativesFilter.USE_R,
339                                 6, Constants.JULIAN_DAY / 24,
340                                 OrekitConfiguration.getCacheSlotsNumber(),
341                                 Constants.JULIAN_YEAR, 30 * Constants.JULIAN_DAY);
342                 frame = new FactoryManagedFrame(getGCRF(), shifting, true, factoryKey);
343                 frames.put(factoryKey, frame);
344             }
345 
346             return frame;
347 
348         }
349     }
350 
351     @Override
352     public FactoryManagedFrame getVeis1950() {
353         synchronized (this) {
354 
355             // try to find an already built frame
356             final Predefined factoryKey = Predefined.VEIS_1950;
357             FactoryManagedFrame frame = frames.get(factoryKey);
358 
359             if (frame == null) {
360                 // it's the first time we need this frame, build it and store it
361                 frame = new FactoryManagedFrame(getGTOD(IERSConventions.IERS_1996, false, true),
362                         new VEISProvider(getTimeScales()), true, factoryKey);
363                 frames.put(factoryKey, frame);
364             }
365 
366             return frame;
367 
368         }
369     }
370 
371     @Override
372     public FactoryManagedFrame getITRFEquinox(final IERSConventions conventions,
373                                               final boolean simpleEOP) {
374         synchronized (this) {
375 
376             // try to find an already built frame
377             final Predefined factoryKey = switch (conventions) {
378                 case IERS_1996 -> simpleEOP ?
379                         Predefined.ITRF_EQUINOX_CONV_1996_SIMPLE_EOP :
380                         Predefined.ITRF_EQUINOX_CONV_1996_ACCURATE_EOP;
381                 case IERS_2003 -> simpleEOP ?
382                         Predefined.ITRF_EQUINOX_CONV_2003_SIMPLE_EOP :
383                         Predefined.ITRF_EQUINOX_CONV_2003_ACCURATE_EOP;
384                 case IERS_2010 -> simpleEOP ?
385                         Predefined.ITRF_EQUINOX_CONV_2010_SIMPLE_EOP :
386                         Predefined.ITRF_EQUINOX_CONV_2010_ACCURATE_EOP;
387             };
388             FactoryManagedFrame frame = frames.get(factoryKey);
389 
390             if (frame == null) {
391                 // it's the first time we need this frame, build it and store it
392                 final Frame gtod = getGTOD(conventions, true, simpleEOP);
393                 final ShiftingTransformProvider gtodShifting =
394                         (ShiftingTransformProvider) gtod.getTransformProvider();
395                 final GTODProvider gtodRaw    = (GTODProvider) gtodShifting.getRawProvider();
396                 final EOPHistory   eopHistory = gtodRaw.getEOPHistory();
397                 frame = new FactoryManagedFrame(gtod, new ITRFProvider(eopHistory), false, factoryKey);
398                 frames.put(factoryKey, frame);
399             }
400 
401             return frame;
402 
403         }
404     }
405 
406     @Override
407     public FactoryManagedFrame getGTOD(final boolean applyEOPCorr) {
408         return getGTOD(IERSConventions.IERS_1996, applyEOPCorr, true);
409     }
410 
411     @Override
412     public FactoryManagedFrame getGTOD(final IERSConventions conventions,
413                                        final boolean simpleEOP) {
414         return getGTOD(conventions, true, simpleEOP);
415     }
416 
417     /** Get the GTOD reference frame.
418      * <p>
419      * The applyEOPCorr parameter is available mainly for testing purposes or for
420      * consistency with legacy software that don't handle EOP correction parameters.
421      * Beware that setting this parameter to {@code false} leads to crude accuracy
422      * (order of magnitudes for errors might be above 250m in LEO and 1400m in GEO).
423      * For this reason, setting this parameter to false is restricted to {@link
424      * IERSConventions#IERS_1996 IERS 1996} conventions, and hence this method is private.
425      * </p>
426      * @param conventions IERS conventions to apply
427      * @param applyEOPCorr if true, EOP corrections are applied (here, dut1 and lod)
428      * @param simpleEOP if true, tidal effects are ignored when interpolating EOP
429      * @return the selected reference frame singleton.
430      */
431     private FactoryManagedFrame getGTOD(final IERSConventions conventions,
432                                                final boolean applyEOPCorr,
433                                                final boolean simpleEOP) {
434 
435         synchronized (this) {
436 
437             // try to find an already built frame
438             final Predefined factoryKey = switch (conventions) {
439                 case IERS_1996 -> applyEOPCorr ?
440                         (simpleEOP ? Predefined.GTOD_CONVENTIONS_1996_SIMPLE_EOP : Predefined.GTOD_CONVENTIONS_1996_ACCURATE_EOP) :
441                         Predefined.GTOD_WITHOUT_EOP_CORRECTIONS;
442                 case IERS_2003 -> simpleEOP ?
443                         Predefined.GTOD_CONVENTIONS_2003_SIMPLE_EOP :
444                         Predefined.GTOD_CONVENTIONS_2003_ACCURATE_EOP;
445                 case IERS_2010 -> simpleEOP ? Predefined.GTOD_CONVENTIONS_2010_SIMPLE_EOP :
446                         Predefined.GTOD_CONVENTIONS_2010_ACCURATE_EOP;
447             };
448             FactoryManagedFrame frame = frames.get(factoryKey);
449 
450             if (frame == null) {
451                 // it's the first time we need this frame, build it and store it
452                 final Frame tod = getTOD(conventions, applyEOPCorr, simpleEOP);
453                 final ShiftingTransformProvider todInterpolating =
454                         (ShiftingTransformProvider) tod.getTransformProvider();
455                 final TODProvider       todRaw     = (TODProvider) todInterpolating.getRawProvider();
456                 final EOPHistory        eopHistory = todRaw.getEOPHistory();
457                 final GTODProvider      gtodRaw    =
458                         new GTODProvider(conventions, eopHistory, getTimeScales());
459                 final TransformProvider gtodShifting =
460                         new ShiftingTransformProvider(gtodRaw,
461                                 CartesianDerivativesFilter.USE_PVA,
462                                 AngularDerivativesFilter.USE_R,
463                                 todInterpolating.getGridPoints(), todInterpolating.getStep(),
464                                 OrekitConfiguration.getCacheSlotsNumber(),
465                                 Constants.JULIAN_YEAR, 30 * Constants.JULIAN_DAY);
466                 frame = new FactoryManagedFrame(tod, gtodShifting, false, factoryKey);
467                 frames.put(factoryKey, frame);
468             }
469 
470             return frame;
471 
472         }
473     }
474 
475     @Override
476     public FactoryManagedFrame getTOD(final boolean applyEOPCorr) {
477         return getTOD(IERSConventions.IERS_1996, applyEOPCorr, false);
478     }
479 
480     @Override
481     public FactoryManagedFrame getTOD(final IERSConventions conventions,
482                                       final boolean simpleEOP) {
483         return getTOD(conventions, true, simpleEOP);
484     }
485 
486     /** Get the TOD reference frame.
487      * <p>
488      * The applyEOPCorr parameter is available mainly for testing purposes or for
489      * consistency with legacy software that don't handle EOP correction parameters.
490      * Beware that setting this parameter to {@code false} leads to crude accuracy
491      * (order of magnitudes for errors might be above 1m in LEO and 10m in GEO).
492      * For this reason, setting this parameter to false is restricted to {@link
493      * IERSConventions#IERS_1996 IERS 1996} conventions, and hence this method is private.
494      * </p>
495      * @param conventions IERS conventions to apply
496      * @param applyEOPCorr if true, EOP corrections are applied (here, nutation)
497      * @param simpleEOP if true, tidal effects are ignored when interpolating EOP
498      * @return the selected reference frame singleton.
499      */
500     private FactoryManagedFrame getTOD(final IERSConventions conventions,
501                                               final boolean applyEOPCorr,
502                                               final boolean simpleEOP) {
503 
504         synchronized (this) {
505 
506             // try to find an already built frame
507             final Predefined factoryKey = switch (conventions) {
508                 case IERS_1996 -> applyEOPCorr ?
509                         (simpleEOP ? Predefined.TOD_CONVENTIONS_1996_SIMPLE_EOP : Predefined.TOD_CONVENTIONS_1996_ACCURATE_EOP) :
510                         Predefined.TOD_WITHOUT_EOP_CORRECTIONS;
511                 case IERS_2003 -> simpleEOP ?
512                         Predefined.TOD_CONVENTIONS_2003_SIMPLE_EOP :
513                         Predefined.TOD_CONVENTIONS_2003_ACCURATE_EOP;
514                 case IERS_2010 -> simpleEOP ?
515                         Predefined.TOD_CONVENTIONS_2010_SIMPLE_EOP :
516                         Predefined.TOD_CONVENTIONS_2010_ACCURATE_EOP;
517             };
518             final int interpolationPoints;
519             final int pointsPerDay;
520             if (applyEOPCorr) {
521                 interpolationPoints = 6;
522                 pointsPerDay        = 24;
523             } else {
524                 interpolationPoints = 6;
525                 pointsPerDay        = 8;
526             }
527             FactoryManagedFrame frame = frames.get(factoryKey);
528 
529             if (frame == null) {
530                 // it's the first time we need this frame, build it and store it
531                 final EOPHistory eopHistory = applyEOPCorr ?
532                         getEOPHistory(conventions, simpleEOP) :
533                         null;
534                 final TransformProvider shifting =
535                         new ShiftingTransformProvider(
536                                 new TODProvider(conventions, eopHistory, getTimeScales()),
537                                 CartesianDerivativesFilter.USE_PVA,
538                                 AngularDerivativesFilter.USE_R,
539                                 interpolationPoints, Constants.JULIAN_DAY / pointsPerDay,
540                                 OrekitConfiguration.getCacheSlotsNumber(),
541                                 Constants.JULIAN_YEAR, 30 * Constants.JULIAN_DAY);
542                 frame = new FactoryManagedFrame(getMOD(conventions, applyEOPCorr), shifting, true, factoryKey);
543                 frames.put(factoryKey, frame);
544             }
545 
546             return frame;
547 
548         }
549     }
550 
551     @Override
552     public FactoryManagedFrame getMOD(final boolean applyEOPCorr) {
553         return getMOD(IERSConventions.IERS_1996, applyEOPCorr);
554     }
555 
556     @Override
557     public FactoryManagedFrame getMOD(final IERSConventions conventions) {
558         return getMOD(conventions, true);
559     }
560 
561     /** Get the MOD reference frame.
562      * <p>
563      * The applyEOPCorr parameter is available mainly for testing purposes or for
564      * consistency with legacy software that don't handle EOP correction parameters.
565      * Beware that setting this parameter to {@code false} leads to crude accuracy
566      * (order of magnitudes for errors might be above 1m in LEO and 10m in GEO).
567      * For this reason, setting this parameter to false is restricted to {@link
568      * IERSConventions#IERS_1996 IERS 1996} conventions, and hence this method is private.
569      * </p>
570      * @param conventions IERS conventions to apply
571      * @param applyEOPCorr if true, EOP corrections are applied (EME2000/GCRF bias compensation)
572      * @return the selected reference frame singleton.
573      */
574     private FactoryManagedFrame getMOD(final IERSConventions conventions, final boolean applyEOPCorr) {
575 
576         synchronized (this) {
577 
578             final Predefined factoryKey;
579             final Frame parent = switch (conventions) {
580                 case IERS_1996 -> {
581                     factoryKey = applyEOPCorr ? Predefined.MOD_CONVENTIONS_1996 : Predefined.MOD_WITHOUT_EOP_CORRECTIONS;
582                     yield applyEOPCorr ? getGCRF() : getEME2000();
583                 }
584                 case IERS_2003 -> {
585                     factoryKey = Predefined.MOD_CONVENTIONS_2003;
586                     // in IERS conventions 2003, the precession angles zetaA, thetaA and zA
587                     // from equation 33 are computed from EME2000, not from GCRF
588                     yield getEME2000();
589                 }
590                 case IERS_2010 -> {
591                     factoryKey = Predefined.MOD_CONVENTIONS_2010;
592                     // precession angles epsilon0, psiA, omegaA and chiA
593                     // from equations 5.39 and 5.40 are computed from EME2000
594                     yield getEME2000();
595                 }
596                 default ->
597                     // this should never happen
598                         throw new OrekitInternalError(null);
599             };
600 
601             // try to find an already built frame
602             FactoryManagedFrame frame = frames.get(factoryKey);
603 
604             if (frame == null) {
605                 // it's the first time we need this frame, build it and store it
606                 final MODProvider provider = new MODProvider(conventions, getTimeScales());
607                 frame = new FactoryManagedFrame(parent, provider, true, factoryKey);
608                 frames.put(factoryKey, frame);
609             }
610 
611             return frame;
612 
613         }
614     }
615 
616     @Override
617     public FactoryManagedFrame getTEME() {
618         synchronized (this) {
619 
620             // try to find an already built frame
621             final Predefined factoryKey = Predefined.TEME;
622             FactoryManagedFrame frame = frames.get(factoryKey);
623 
624             if (frame == null) {
625                 // it's the first time we need this frame, build it and store it
626                 final Frame tod = getTOD(IERSConventions.IERS_1996, false, true);
627                 final ShiftingTransformProvider todShifting =
628                         (ShiftingTransformProvider) tod.getTransformProvider();
629                 final TEMEProvider temeRaw =
630                         new TEMEProvider(IERSConventions.IERS_1996, null, getTimeScales());
631                 final TransformProvider temeShifting =
632                         new ShiftingTransformProvider(temeRaw,
633                                 CartesianDerivativesFilter.USE_PVA,
634                                 AngularDerivativesFilter.USE_R,
635                                 todShifting.getGridPoints(), todShifting.getStep(),
636                                 OrekitConfiguration.getCacheSlotsNumber(),
637                                 Constants.JULIAN_YEAR, 30 * Constants.JULIAN_DAY);
638 
639                 frame = new FactoryManagedFrame(tod, temeShifting, true, factoryKey);
640                 frames.put(factoryKey, frame);
641             }
642 
643             return frame;
644 
645         }
646     }
647 
648     @Override
649     public FactoryManagedFrame getPZ9011(final IERSConventions convention,
650                                          final boolean simpleEOP) {
651         synchronized (this) {
652 
653             // try to find an already built frame
654             final Predefined factoryKey = Predefined.PZ90_11;
655             FactoryManagedFrame frame = frames.get(factoryKey);
656 
657             if (frame == null) {
658                 // it's the first time we need this frame, build it and store it
659                 final Frame itrf = getITRF(ITRFVersion.ITRF_2008, convention, simpleEOP);
660                 final HelmertTransformation pz90Raw = new HelmertTransformation(new AbsoluteDate(2010, 1, 1, 12, 0, 0, getTimeScales().getTT()),
661                         +3.0, +1.0, -0.0, +0.019, -0.042, +0.002, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
662                 frame = new FactoryManagedFrame(itrf, pz90Raw, false, factoryKey);
663                 frames.put(factoryKey, frame);
664             }
665 
666             return frame;
667 
668         }
669     }
670 
671     /**
672      * Get the time scales.
673      *
674      * @return time scales used to define these frames.
675      */
676     protected TimeScales getTimeScales() {
677         return timeScales;
678     }
679 
680     /**
681      * Local record for different ITRF versions keys.
682      *
683      * @param version     ITRF version.
684      * @param conventions IERS conventions to apply.
685      * @param simpleEOP   Tidal effects flag.
686      * @since 9.2
687      */
688     private record ITRFKey(ITRFVersion version, IERSConventions conventions,
689                            boolean simpleEOP) implements Serializable {
690 
691         /**
692          * Serialized UID.
693          */
694         @Serial
695         private static final long serialVersionUID = 20180412L;
696 
697     }
698 }