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