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