1   /* Copyright 2002-2025 CS GROUP
2    * Licensed to CS GROUP (CS) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * CS licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.orekit.frames;
18  
19  import org.hipparchus.CalculusFieldElement;
20  import org.orekit.annotation.DefaultDataContext;
21  import org.orekit.bodies.CelestialBodies;
22  import org.orekit.data.DataContext;
23  import org.orekit.time.AbsoluteDate;
24  import org.orekit.time.FieldAbsoluteDate;
25  import org.orekit.time.TimeScales;
26  import org.orekit.time.UT1Scale;
27  import org.orekit.time.UTCScale;
28  import org.orekit.utils.IERSConventions;
29  
30  
31  /** Factory for predefined reference frames.
32   *
33   * <h2> FramesFactory Presentation </h2>
34   * <p>
35   * Several predefined reference {@link Frame frames} are implemented in OREKIT.
36   * They are linked together in a tree with the <i>Geocentric
37   * Celestial Reference Frame</i> (GCRF) as the root of the tree.
38   * This factory is designed to:
39   * </p>
40   * <ul>
41   *   <li>build the frames tree consistently,</li>
42   *   <li>avoid rebuilding some frames that may be costly to recreate all the time,</li>
43   *   <li>set up interpolation/caching features for some frames that may induce costly computation</li>
44   *   <li>streamline the {@link EOPHistory Earth Orientation Parameters} history loading.</li>
45   * </ul>
46   * <h2> Reference Frames </h2>
47   * <p>
48   * The user can retrieve those reference frames using various static methods, the most
49   * important ones being: {@link #getFrame(Predefined)}, {@link #getGCRF()},
50   * {@link #getCIRF(IERSConventions, boolean)} {@link #getTIRF(IERSConventions, boolean)},
51   * {@link #getITRF(IERSConventions, boolean)}, {@link #getITRF(ITRFVersion, IERSConventions, boolean)},
52   * {@link #getEME2000()}, {@link #getMOD(IERSConventions)}, {@link #getTOD(IERSConventions, boolean)},
53   * {@link #getGTOD(IERSConventions, boolean)}, {@link #getITRFEquinox(IERSConventions, boolean)},
54   * {@link #getTEME()} and {@link #getVeis1950()}.
55   * </p>
56   * <h2> International Terrestrial Reference Frame</h2>
57   * <p>
58   * This frame is the current (as of 2013) reference realization of
59   * the International Terrestrial Reference System produced by IERS.
60   * It is described in <a href="ftp://tai.bipm.org/iers/conv2010/tn36.pdf">
61   * IERS conventions (2010)</a>. It replaces the Earth Centered Earth Fixed
62   * frame which is the reference frame for GPS satellites.
63   * </p>
64   * <p>
65   * This frame is used to define position on solid Earth. It rotates with
66   * the Earth and includes the pole motion with respect to Earth crust as
67   * provided by IERS {@link EOPHistory Earth Orientation Parameters}.
68   * Its pole axis is the IERS Reference Pole (IRP).
69   * </p>
70   * <p>
71   * Depending on the  {@link EOPHistory Earth Orientation Parameters} source,
72   * different ITRS realization may be returned by {@link #getITRF(IERSConventions, boolean)},
73   * and if EOP are mixed, the ITRF may even jump from one realization to another one.
74   * This is not a problem for most users as different ITRS realizations are very close
75   * to each other (a few millimeters at Earth surface). If however a specific ITRF version
76   * (i.e. an ITRS realization) is needed for very high accuracy, Orekit provides the
77   * {@link FramesFactory#getITRF(ITRFVersion, IERSConventions, boolean)} method
78   * to get it and take care of jumps in EOP.
79   * </p>
80   * <p>
81   * ITRF can be built using the new non-rotating origin paradigm
82   * mandated by IAU 2000 resolution B1.8 and any supported {@link IERSConventions
83   * IERS conventions} (even IERS 1996 can be used with non-rotating origin paradigm,
84   * despite the resolution was not yet adopted at conventions publication time).
85   * </p>
86   * <p>
87   * ITRF can also be built using the classical equinox paradigm used prior to IAU 2000
88   * resolution B1.8 and any supported {@link IERSConventions IERS conventions} (even
89   * IERS 2003 and 2010 can be used with equinox paradigm, despite the resolution is
90   * in effect now). The choice of paradigm (non-rotating origin or equinox) and the
91   * choice of IERS conventions (i.e. the choice of precession/nutation models) can
92   * be made independently by user, Orekit provides all alternatives.
93   * </p>
94   * <h2>Intermediate frames</h2>
95   * <p>
96   * Orekit also provides all the intermediate frames that are needed to transform
97   * between GCRF and ITRF, along the two paths: ITRF/TIRF/CIRF/GCRF for the
98   * non-rotating origin paradigm and ITRF/GTOD/TOD/MOD/EME2000/GCRF for the equinox
99   * paradigm.
100  * </p>
101  * <h2> Earth Orientation Parameters </h2>
102  * <p>
103  * This factory also handles loading of Earth Orientation Parameters (EOP) needed
104  * for accurate transformations between inertial and Earth fixed frames, using
105  * {@link org.orekit.data.DataProvidersManager} features. EOP are IERS conventions
106  * dependent, because they correspond to correction to the precession/nutation
107  * models. When EOP should be applied, but EOP data are not available, then a null
108  * (0.0) correction is used. This can occur when no EOP data is loaded, or when the
109  * requested date is beyond the time span of the loaded EOP data. Using a null
110  * correction can result in coarse accuracy. To check the time span covered by EOP data use
111  * {@link #getEOPHistory(IERSConventions, boolean)}, {@link EOPHistory#getStartDate()},
112  * and {@link EOPHistory#getEndDate()}.
113  * <p>
114  * For more information on configuring the EOP data Orekit uses see
115  * <a href="https://gitlab.orekit.org/orekit/orekit/blob/master/src/site/markdown/configuration.md">
116  * https://gitlab.orekit.org/orekit/orekit/blob/master/src/site/markdown/configuration.md</a>.
117  * <p>
118  * Here is a schematic representation of the predefined reference frames tree:
119  * </p>
120  * <pre>
121  *                                                                  GCRF
122  *                                                                    |
123  *                                                 |-----------------------------------------------
124  *                                                 |                         |     Frame bias     |
125  *                                                 |                         |                 EME2000
126  *                                                 |                         |                    |
127  *                                                 |                         | Precession effects |
128  *                                                 |                         |                    |
129  *           Bias, Precession and Nutation effects |                        MOD                  MOD  (Mean Equator Of Date)
130  *                                                 |                         |             w/o EOP corrections
131  *                                                 |                         |  Nutation effects  |
132  *    (Celestial Intermediate Reference Frame)   CIRF                        |                    |
133  *                                                 |                        TOD                  TOD  (True Equator Of Date)
134  *                          Earth natural rotation |                         |             w/o EOP corrections
135  *                                                 |-------------            |    Sidereal Time   |
136  *                                                 |            |            |                    |
137  *  (Terrestrial Intermediate Reference Frame)   TIRF         TIRF         GTOD                 GTOD  (Greenwich True Of Date)
138  *                                                 |    w/o tidal effects                  w/o EOP corrections
139  *                                     Pole motion |            |                                 |
140  *                                                 |            |                                 |-------------
141  *                                                 |            |                                 |            |
142  * (International Terrestrial Reference Frame)   ITRF         ITRF                              ITRF        VEIS1950
143  *                                                 |    w/o tidal effects                   equinox-based
144  *                                                 |            |
145  *                                           other ITRF     other ITRF
146  *                                                      w/o tidal effects
147  * </pre>
148  * <p>
149  * This is a utility class, so its constructor is private.
150  * </p>
151  * @author Guylaine Prat
152  * @author Luc Maisonobe
153  * @author Pascal Parraud
154  * @see Frames
155  */
156 public class FramesFactory {
157 
158     /* These constants were left here instead of being moved to LazyLoadedFrames because
159      * they are public.
160      */
161 
162     /** Default regular expression for the Rapid Data and Prediction EOP columns files (IAU1980 compatibles). */
163     public static final String RAPID_DATA_PREDICTION_COLUMNS_1980_FILENAME = "^finals\\.[^.]*$";
164 
165     /** Default regular expression for the EOP XML files (IAU1980 compatibles). */
166     public static final String XML_1980_FILENAME = "^(:finals|eopc04_\\d\\d)\\..*\\.xml$";
167 
168     /** Default regular expression for the EOPC04 files (IAU1980 compatibles). */
169     public static final String EOPC04_1980_FILENAME = "^eopc04(_\\d\\d)?\\.\\d\\d$";
170 
171     /** Default regular expression for the BulletinB files (IAU1980 compatibles). */
172     public static final String BULLETINB_1980_FILENAME = "^bulletinb(_IAU1980)?((-\\d\\d\\d\\.txt)|(\\.\\d\\d\\d))$";
173 
174     /** Default regular expression for the Rapid Data and Prediction EOP columns files (IAU2000 compatibles). */
175     public static final String RAPID_DATA_PREDICTION_COLUMNS_2000_FILENAME = "^finals2000A\\.[^.]*$";
176 
177     /** Default regular expression for the EOP XML files (IAU2000 compatibles). */
178     public static final String XML_2000_FILENAME = "^(:finals2000A|eopc04_\\d\\d_IAU2000)\\..*\\.xml$";
179 
180     /** Default regular expression for the EOPC04 files (IAU2000 compatibles). */
181     public static final String EOPC04_2000_FILENAME = "^eopc04(_\\d\\d_IAU2000)?\\.\\d\\d$";
182 
183     /** Default regular expression for the BulletinB files (IAU2000 compatibles). */
184     public static final String BULLETINB_2000_FILENAME = "^bulletinb(_IAU2000)?((-\\d\\d\\d\\.txt)|(\\.\\d\\d\\d))$";
185 
186     /** Default regular expression for the BulletinA files (IAU1980 and IAU2000 compatibles). */
187     public static final String BULLETINA_FILENAME = "^bulletina-[ivxlcdm]+-\\d\\d\\d\\.txt$";
188 
189     /** Default regular expression for the csv files (IAU1980 and IAU2000 compatibles).
190      * @since 12.0
191      */
192     public static final String CSV_FILENAME = "^(?:eopc04|bulletina|bulletinb).*\\.csv$";
193 
194     /** Private constructor.
195      * <p>This class is a utility class, it should neither have a public
196      * nor a default constructor. This private constructor prevents
197      * the compiler from generating one automatically.</p>
198      */
199     private FramesFactory() {
200     }
201 
202     /**
203      * Get the instance of {@link Frames} that is called by the static methods in this
204      * class.
205      *
206      * @return the reference frames used by this factory.
207      */
208     @DefaultDataContext
209     public static LazyLoadedFrames getFrames() {
210         return DataContext.getDefault().getFrames();
211     }
212 
213     /** Add the default loaders EOP history (IAU 1980 precession/nutation).
214      * <p>
215      * The default loaders look for IERS EOP C04 and bulletins B files. They
216      * correspond to {@link IERSConventions#IERS_1996 IERS 1996} conventions.
217      * </p>
218      * @param rapidDataColumnsSupportedNames regular expression for supported
219      * rapid data columns EOP files names
220      * (may be null if the default IERS file names are used)
221      * @param rapidDataXMLSupportedNames regular expression for supported XML EOP files names
222      * (may be null if the default IERS file names are used)
223      * @param eopC04SupportedNames regular expression for supported EOP C04 files names
224      * (may be null if the default IERS file names are used)
225      * @param bulletinBSupportedNames regular expression for supported bulletin B files names
226      * (may be null if the default IERS file names are used)
227      * @param bulletinASupportedNames regular expression for supported bulletin A files names
228      * (may be null if the default IERS file names are used)
229      * @param csvSupportedNames regular expression for supported csv files names
230      * (may be null if the default IERS file names are used)
231      * @see <a href="http://hpiers.obspm.fr/eoppc/eop/eopc04/">IERS EOP C04 files</a>
232      * @see #addEOPHistoryLoader(IERSConventions, EopHistoryLoader)
233      * @see #clearEOPHistoryLoaders()
234      * @see #addDefaultEOP2000HistoryLoaders(String, String, String, String, String, String)
235      * @since 12.0
236      */
237     @DefaultDataContext
238     public static void addDefaultEOP1980HistoryLoaders(final String rapidDataColumnsSupportedNames,
239                                                        final String rapidDataXMLSupportedNames,
240                                                        final String eopC04SupportedNames,
241                                                        final String bulletinBSupportedNames,
242                                                        final String bulletinASupportedNames,
243                                                        final String csvSupportedNames) {
244         getFrames().addDefaultEOP1980HistoryLoaders(
245                 rapidDataColumnsSupportedNames,
246                 rapidDataXMLSupportedNames,
247                 eopC04SupportedNames,
248                 bulletinBSupportedNames,
249                 bulletinASupportedNames,
250                 csvSupportedNames);
251     }
252 
253     /** Add the default loaders for EOP history (IAU 2000/2006 precession/nutation).
254      * <p>
255      * The default loaders look for IERS EOP C04 and bulletins B files. They
256      * correspond to both {@link IERSConventions#IERS_2003 IERS 2003} and {@link
257      * IERSConventions#IERS_2010 IERS 2010} conventions.
258      * </p>
259      * @param rapidDataColumnsSupportedNames regular expression for supported
260      * rapid data columns EOP files names
261      * (may be null if the default IERS file names are used)
262      * @param xmlSupportedNames regular expression for supported XML EOP files names
263      * (may be null if the default IERS file names are used)
264      * @param eopC04SupportedNames regular expression for supported EOP C04 files names
265      * (may be null if the default IERS file names are used)
266      * @param bulletinBSupportedNames regular expression for supported bulletin B files names
267      * (may be null if the default IERS file names are used)
268      * @param bulletinASupportedNames regular expression for supported bulletin A files names
269      * (may be null if the default IERS file names are used)
270      * @param csvSupportedNames regular expression for supported csv files names
271      * (may be null if the default IERS file names are used)
272      * @see <a href="http://hpiers.obspm.fr/eoppc/eop/eopc04/">IERS EOP C04 files</a>
273      * @see #addEOPHistoryLoader(IERSConventions, EopHistoryLoader)
274      * @see #clearEOPHistoryLoaders()
275      * @see #addDefaultEOP1980HistoryLoaders(String, String, String, String, String, String)
276      * @since 12.0
277      */
278     @DefaultDataContext
279     public static void addDefaultEOP2000HistoryLoaders(final String rapidDataColumnsSupportedNames,
280                                                        final String xmlSupportedNames,
281                                                        final String eopC04SupportedNames,
282                                                        final String bulletinBSupportedNames,
283                                                        final String bulletinASupportedNames,
284                                                        final String csvSupportedNames) {
285         getFrames().addDefaultEOP2000HistoryLoaders(
286                 rapidDataColumnsSupportedNames,
287                 xmlSupportedNames,
288                 eopC04SupportedNames,
289                 bulletinBSupportedNames,
290                 bulletinASupportedNames,
291                 csvSupportedNames);
292     }
293 
294     /** Add a loader for Earth Orientation Parameters history.
295      * @param conventions IERS conventions to which EOP history applies
296      * @param loader custom loader to add for the EOP history
297      * @see #addDefaultEOP1980HistoryLoaders(String, String, String, String, String, String)
298      * @see #clearEOPHistoryLoaders()
299      */
300     @DefaultDataContext
301     public static void addEOPHistoryLoader(final IERSConventions conventions, final EopHistoryLoader loader) {
302         getFrames().addEOPHistoryLoader(conventions, loader);
303     }
304 
305     /** Clear loaders for Earth Orientation Parameters history.
306      * @see #addEOPHistoryLoader(IERSConventions, EopHistoryLoader)
307      * @see #addDefaultEOP1980HistoryLoaders(String, String, String, String, String, String)
308      */
309     @DefaultDataContext
310     public static void clearEOPHistoryLoaders() {
311         getFrames().clearEOPHistoryLoaders();
312     }
313 
314     /** Set the threshold to check EOP continuity.
315      * <p>
316      * The default threshold (used if this method is never called)
317      * is 5 Julian days. If after loading EOP entries some holes
318      * between entries exceed this threshold, an exception will
319      * be triggered.
320      * </p>
321      * <p>
322      * One case when calling this method is really useful is for
323      * applications that use a single Bulletin A, as these bulletins
324      * have a roughly one month wide hole for the first bulletin of
325      * each month, which contains older final data in addition to the
326      * rapid data and the predicted data.
327      * </p>
328      * @param threshold threshold to use for checking EOP continuity (in seconds)
329      */
330     @DefaultDataContext
331     public static void setEOPContinuityThreshold(final double threshold) {
332         getFrames().setEOPContinuityThreshold(threshold);
333     }
334 
335     /** Get Earth Orientation Parameters history.
336      * <p>
337      * If no {@link EopHistoryLoader} has been added by calling {@link
338      * #addEOPHistoryLoader(IERSConventions, EopHistoryLoader) addEOPHistoryLoader}
339      * or if {@link #clearEOPHistoryLoaders() clearEOPHistoryLoaders} has been
340      * called afterwards, the {@link #addDefaultEOP1980HistoryLoaders(String, String,
341      * String, String, String, String)} and {@link #addDefaultEOP2000HistoryLoaders(String,
342      * String, String, String, String, String)} methods will be called automatically with
343      * supported file names parameters all set to null, in order to get the default
344      * loaders configuration.
345      * </p>
346      * @param conventions conventions for which EOP history is requested
347      * @param simpleEOP if true, tidal effects are ignored when interpolating EOP
348      * @return Earth Orientation Parameters history
349      */
350     @DefaultDataContext
351     public static EOPHistory getEOPHistory(final IERSConventions conventions, final boolean simpleEOP) {
352         return getFrames().getEOPHistory(conventions, simpleEOP);
353     }
354 
355     /** Get one of the predefined frames.
356      * @param factoryKey key of the frame within the factory
357      * @return the predefined frame
358      */
359     @DefaultDataContext
360     public static Frame getFrame(final Predefined factoryKey) {
361         return getFrames().getFrame(factoryKey);
362     }
363 
364     /** Get the unique GCRF frame.
365      * <p>The GCRF frame is the root frame in the frame tree.</p>
366      * @return the unique instance of the GCRF frame
367      */
368     @DefaultDataContext
369     public static Frame getGCRF() {
370         return getFrames().getGCRF();
371     }
372 
373     /** Get the unique ICRF frame.
374      * <p>The ICRF frame is centered at solar system barycenter and aligned
375      * with GCRF.</p>
376      * @return the unique instance of the ICRF frame
377      */
378     @DefaultDataContext
379     public static Frame getICRF() {
380         return getFrames().getICRF();
381     }
382 
383     /** Get the ecliptic frame.
384      * The IAU defines the ecliptic as "the plane perpendicular to the mean heliocentric
385      * orbital angular momentum vector of the Earth-Moon barycentre in the BCRS (IAU 2006
386      * Resolution B1)." The +z axis is aligned with the angular momentum vector, and the +x
387      * axis is aligned with +x axis of {@link FramesFactory#getMOD(IERSConventions) MOD}.
388      *
389      * <p> This implementation agrees with the JPL 406 ephemerides to within 0.5 arc seconds.
390      * @param conventions IERS conventions to apply
391      * @return the selected reference frame singleton.
392      */
393     @DefaultDataContext
394     public static Frame getEcliptic(final IERSConventions conventions) {
395         return getFrames().getEcliptic(conventions);
396     }
397 
398     /** Get the unique EME2000 frame.
399      * <p>The EME2000 frame is also called the J2000 frame.
400      * The former denomination is preferred in Orekit.</p>
401      * @return the unique instance of the EME2000 frame
402      */
403     @DefaultDataContext
404     public static FactoryManagedFrame getEME2000() {
405         return getFrames().getEME2000();
406     }
407 
408     /** Get an unspecified International Terrestrial Reference Frame.
409      * <p>
410      * The frame returned uses the {@link EOPEntry Earth Orientation Parameters}
411      * blindly. So if for example one loads only EOP 14 C04 files to retrieve
412      * the parameters, the frame will be an {@link ITRFVersion#ITRF_2014}. However,
413      * if parameters are loaded from different files types, or even for file
414      * types that changed their reference (like Bulletin A switching from
415      * {@link ITRFVersion#ITRF_2008} to {@link ITRFVersion#ITRF_2014} starting
416      * with Vol. XXXI No. 013 published on 2018-03-29), then the ITRF returned
417      * by this method will jump from one version to another version.
418      * </p>
419      * <p>
420      * IF a specific version of ITRF is needed, then {@link #getITRF(ITRFVersion,
421      * IERSConventions, boolean)} should be used instead.
422      * </p>
423      * @param conventions IERS conventions to apply
424      * @param simpleEOP if true, tidal effects are ignored when interpolating EOP
425      * @return the selected reference frame singleton.
426           * @see #getITRF(ITRFVersion, IERSConventions, boolean)
427      * @since 6.1
428      */
429     @DefaultDataContext
430     public static FactoryManagedFrame getITRF(final IERSConventions conventions,
431                                               final boolean simpleEOP) {
432         return getFrames().getITRF(conventions, simpleEOP);
433     }
434 
435     /** Get the TIRF reference frame, ignoring tidal effects.
436      * @param conventions IERS conventions to apply
437      * @return the selected reference frame singleton.
438           * library cannot be read.
439      */
440     @DefaultDataContext
441     public static FactoryManagedFrame getTIRF(final IERSConventions conventions) {
442         return getFrames().getTIRF(conventions);
443     }
444 
445     /** Get a specific International Terrestrial Reference Frame.
446      * <p>
447      * Note that if a specific version of ITRF is required, then {@code simpleEOP}
448      * should most probably be set to {@code false}, as ignoring tidal effects
449      * has an effect of the same order of magnitude as the differences between
450      * the various {@link ITRFVersion ITRF versions}.
451      * </p>
452      * @param version ITRF version
453      * @param conventions IERS conventions to apply
454      * @param simpleEOP if true, tidal effects are ignored when interpolating EOP
455      * @return the selected reference frame singleton.
456           * @since 9.2
457      */
458     @DefaultDataContext
459     public static VersionedITRF getITRF(final ITRFVersion version,
460                                         final IERSConventions conventions,
461                                         final boolean simpleEOP) {
462         return getFrames().getITRF(version, conventions, simpleEOP);
463     }
464 
465     /** Build an uncached International Terrestrial Reference Frame with specific {@link EOPHistory EOP history}.
466      * <p>
467      * This frame and its parent frames (TIRF and CIRF) will <em>not</em> be cached, they are
468      * rebuilt from scratch each time this method is called. This factory method is intended
469      * to be used when EOP history is changed at run time. For regular ITRF use, the
470      * {@link #getITRF(IERSConventions, boolean)} and {link {@link #getITRF(ITRFVersion, IERSConventions, boolean)}
471      * are more suitable.
472      * </p>
473      * @param eopHistory EOP history
474      * @param utc UTC time scale
475      * @return an ITRF frame with specified EOP history
476      * @since 12.0
477      */
478     public static Frame buildUncachedITRF(final EOPHistory eopHistory, final UTCScale utc) {
479         final TimeScales timeScales = TimeScales.of(utc.getBaseOffsets(),
480                                                     (conventions, timescales) -> eopHistory.getEntries());
481         final UT1Scale   ut1        = timeScales.getUT1(eopHistory.getConventions(), eopHistory.isSimpleEop());
482         final Frames     frames     = Frames.of(timeScales, (CelestialBodies) null);
483         return frames.buildUncachedITRF(ut1);
484     }
485 
486     /** Get the TIRF reference frame.
487      * @param conventions IERS conventions to apply
488      * @param simpleEOP if true, tidal effects are ignored when interpolating EOP
489      * @return the selected reference frame singleton.
490      * @since 6.1
491      */
492     @DefaultDataContext
493     public static FactoryManagedFrame getTIRF(final IERSConventions conventions,
494                                               final boolean simpleEOP) {
495         return getFrames().getTIRF(conventions, simpleEOP);
496     }
497 
498     /** Get the CIRF2000 reference frame.
499      * @param conventions IERS conventions to apply
500      * @param simpleEOP if true, tidal effects are ignored when interpolating EOP
501      * @return the selected reference frame singleton.
502      */
503     @DefaultDataContext
504     public static FactoryManagedFrame getCIRF(final IERSConventions conventions,
505                                               final boolean simpleEOP) {
506         return getFrames().getCIRF(conventions, simpleEOP);
507     }
508 
509     /** Get the VEIS 1950 reference frame.
510      * <p>Its parent frame is the GTOD frame with IERS 1996 conventions without EOP corrections.</p>
511      * @return the selected reference frame singleton.
512      */
513     @DefaultDataContext
514     public static FactoryManagedFrame getVeis1950() {
515         return getFrames().getVeis1950();
516     }
517 
518     /** Get the equinox-based ITRF reference frame.
519      * @param conventions IERS conventions to apply
520      * @param simpleEOP if true, tidal effects are ignored when interpolating EOP
521      * @return the selected reference frame singleton.
522      * @since 6.1
523      */
524     @DefaultDataContext
525     public static FactoryManagedFrame getITRFEquinox(final IERSConventions conventions,
526                                                      final boolean simpleEOP) {
527         return getFrames().getITRFEquinox(conventions, simpleEOP);
528     }
529 
530     /** Get the GTOD reference frame.
531      * <p>
532      * The applyEOPCorr parameter is available mainly for testing purposes or for
533      * consistency with legacy software that don't handle EOP correction parameters.
534      * Beware that setting this parameter to {@code false} leads to crude accuracy
535      * (order of magnitudes for errors might be above 250m in LEO and 1400m in GEO).
536      * For this reason, setting this parameter to false is restricted to {@link
537      * IERSConventions#IERS_1996 IERS 1996} conventions, and hence the {@link
538      * IERSConventions IERS conventions} cannot be freely chosen here.
539      * </p>
540      * @param applyEOPCorr if true, EOP corrections are applied (here, dut1 and lod)
541      * @return the selected reference frame singleton.
542      */
543     @DefaultDataContext
544     public static FactoryManagedFrame getGTOD(final boolean applyEOPCorr) {
545         return getFrames().getGTOD(applyEOPCorr);
546     }
547 
548     /** Get the GTOD reference frame.
549      * @param conventions IERS conventions to apply
550      * @param simpleEOP if true, tidal effects are ignored when interpolating EOP
551      * @return the selected reference frame singleton.
552      */
553     @DefaultDataContext
554     public static FactoryManagedFrame getGTOD(final IERSConventions conventions,
555                                               final boolean simpleEOP) {
556         return getFrames().getGTOD(conventions, simpleEOP);
557     }
558 
559     /** Get the TOD reference frame.
560      * <p>
561      * The applyEOPCorr parameter is available mainly for testing purposes or for
562      * consistency with legacy software that don't handle EOP correction parameters.
563      * Beware that setting this parameter to {@code false} leads to crude accuracy
564      * (order of magnitudes for errors might be above 1m in LEO and 10m in GEO).
565      * For this reason, setting this parameter to false is restricted to {@link
566      * IERSConventions#IERS_1996 IERS 1996} conventions, and hence the {@link
567      * IERSConventions IERS conventions} cannot be freely chosen here.
568      * </p>
569      * @param applyEOPCorr if true, EOP corrections are applied (here, nutation)
570      * @return the selected reference frame singleton.
571      */
572     @DefaultDataContext
573     public static FactoryManagedFrame getTOD(final boolean applyEOPCorr) {
574         return getFrames().getTOD(applyEOPCorr);
575     }
576 
577     /** Get the TOD reference frame.
578      * @param conventions IERS conventions to apply
579      * @param simpleEOP if true, tidal effects are ignored when interpolating EOP
580      * @return the selected reference frame singleton.
581      */
582     @DefaultDataContext
583     public static FactoryManagedFrame getTOD(final IERSConventions conventions,
584                                              final boolean simpleEOP) {
585         return getFrames().getTOD(conventions, simpleEOP);
586     }
587 
588     /** Get the MOD reference frame.
589      * <p>
590      * The applyEOPCorr parameter is available mainly for testing purposes or for
591      * consistency with legacy software that don't handle EOP correction parameters.
592      * Beware that setting this parameter to {@code false} leads to crude accuracy
593      * (order of magnitudes for errors might be above 1m in LEO and 10m in GEO).
594      * For this reason, setting this parameter to false is restricted to {@link
595      * IERSConventions#IERS_1996 IERS 1996} conventions, and hence the {@link
596      * IERSConventions IERS conventions} cannot be freely chosen here.
597      * </p>
598      * @param applyEOPCorr if true, EOP corrections are applied (EME2000/GCRF bias compensation)
599      * @return the selected reference frame singleton.
600      */
601     @DefaultDataContext
602     public static FactoryManagedFrame getMOD(final boolean applyEOPCorr) {
603         return getFrames().getMOD(applyEOPCorr);
604     }
605 
606     /** Get the MOD reference frame.
607      * @param conventions IERS conventions to apply
608      * @return the selected reference frame singleton.
609      */
610     @DefaultDataContext
611     public static FactoryManagedFrame getMOD(final IERSConventions conventions) {
612         return getFrames().getMOD(conventions);
613     }
614 
615     /** Get the TEME reference frame.
616      * <p>
617      * The TEME frame is used for the SGP4 model in TLE propagation. This frame has <em>no</em>
618      * official definition and there are some ambiguities about whether it should be used
619      * as "of date" or "of epoch". This frame should therefore be used <em>only</em> for
620      * TLE propagation and not for anything else, as recommended by the CCSDS Orbit Data Message
621      * blue book.
622      * </p>
623      * @return the selected reference frame singleton.
624      */
625     @DefaultDataContext
626     public static FactoryManagedFrame getTEME() {
627         return getFrames().getTEME();
628     }
629 
630     /** Get the PZ-90.11 (Parametry Zemly  – 1990.11) reference frame.
631      * <p>
632      * The PZ-90.11 reference system was updated on all operational
633      * GLONASS satellites starting from 3:00 pm on December 31, 2013.
634      * </p>
635      * <p>
636      * The transition between parent frame (ITRF-2008) and PZ-90.11 frame is performed using
637      * a seven parameters Helmert transformation.
638      * <pre>
639      *    From       To      ΔX(m)   ΔY(m)   ΔZ(m)   RX(mas)   RY(mas)  RZ(mas)   Epoch
640      * ITRF-2008  PZ-90.11  +0.003  +0.001  -0.000   +0.019    -0.042   +0.002     2010
641      * </pre>
642      * @see "Springer Handbook of Global Navigation Satellite Systems, Peter Teunissen & Oliver Montenbruck"
643      *
644      * @param convention IERS conventions to apply
645      * @param simpleEOP if true, tidal effects are ignored when interpolating EOP
646      * @return the selected reference frame singleton.
647      */
648     @DefaultDataContext
649     public static FactoryManagedFrame getPZ9011(final IERSConventions convention,
650                                                 final boolean simpleEOP) {
651         return getFrames().getPZ9011(convention, simpleEOP);
652     }
653 
654     /** Get the transform between two frames, suppressing all interpolation.
655      * <p>
656      * This method is similar to {@link Frame#getTransformTo(Frame, AbsoluteDate)}
657      * except it removes the performance enhancing interpolation features that are
658      * added by the {@link FramesFactory factory} to some frames, in order to focus
659      * on accuracy. The interpolation features are intended to save processing time
660      * by avoiding doing some lengthy computation like nutation evaluation at each
661      * time step and caching some results. This method can be used to avoid this,
662      * when very high accuracy is desired, or for testing purposes. It should be
663      * used with care, as doing the full computation is <em>really</em> costly for
664      * some frames.
665      * </p>
666      * @param from frame from which transformation starts
667      * @param to frame to which transformation ends
668      * @param date date of the transform
669      * @return transform between the two frames, avoiding interpolation
670      */
671     public static Transform getNonInterpolatingTransform(final Frame from, final Frame to,
672                                                          final AbsoluteDate date) {
673 
674         // common ancestor to both frames in the frames tree
675         Frame currentF = from.getDepth() > to.getDepth() ? from.getAncestor(from.getDepth() - to.getDepth()) : from;
676         Frame currentT = from.getDepth() > to.getDepth() ? to : to.getAncestor(to.getDepth() - from.getDepth());
677         while (currentF != currentT) {
678             currentF = currentF.getParent();
679             currentT = currentT.getParent();
680         }
681         final Frame common = currentF;
682 
683         // transform from common to origin
684         Transform commonToOrigin = Transform.IDENTITY;
685         for (Frame frame = from; frame != common; frame = frame.getParent()) {
686             commonToOrigin = new Transform(date,
687                                              peel(frame.getTransformProvider()).getTransform(date),
688                                              commonToOrigin);
689         }
690 
691         // transform from destination up to common
692         Transform commonToDestination = Transform.IDENTITY;
693         for (Frame frame = to; frame != common; frame = frame.getParent()) {
694             commonToDestination = new Transform(date,
695                                                 peel(frame.getTransformProvider()).getTransform(date),
696                                                 commonToDestination);
697         }
698 
699         // transform from origin to destination via common
700         return new Transform(date, commonToOrigin.getInverse(), commonToDestination);
701 
702     }
703 
704     /* The methods below are static helper methods for Frame and TransformProvider. */
705 
706     /** Get the transform between two frames, suppressing all interpolation.
707      * <p>
708      * This method is similar to {@link Frame#getTransformTo(Frame, AbsoluteDate)}
709      * except it removes the performance enhancing interpolation features that are
710      * added by the {@link FramesFactory factory} to some frames, in order to focus
711      * on accuracy. The interpolation features are intended to save processing time
712      * by avoiding doing some lengthy computation like nutation evaluation at each
713      * time step and caching some results. This method can be used to avoid this,
714      * when very high accuracy is desired, or for testing purposes. It should be
715      * used with care, as doing the full computation is <em>really</em> costly for
716      * some frames.
717      * </p>
718      * @param from frame from which transformation starts
719      * @param to frame to which transformation ends
720      * @param date date of the transform
721      * @param <T> type of the field elements
722      * @return transform between the two frames, avoiding interpolation
723      * @since 9.0
724      */
725     public static <T extends CalculusFieldElement<T>> FieldTransform<T> getNonInterpolatingTransform(final Frame from, final Frame to,
726                                                                                                  final FieldAbsoluteDate<T> date) {
727 
728         // common ancestor to both frames in the frames tree
729         Frame currentF = from.getDepth() > to.getDepth() ? from.getAncestor(from.getDepth() - to.getDepth()) : from;
730         Frame currentT = from.getDepth() > to.getDepth() ? to : to.getAncestor(to.getDepth() - from.getDepth());
731         while (currentF != currentT) {
732             currentF = currentF.getParent();
733             currentT = currentT.getParent();
734         }
735         final Frame common = currentF;
736 
737         // transform from common to origin
738         FieldTransform<T> commonToOrigin = FieldTransform.getIdentity(date.getField());
739         for (Frame frame = from; frame != common; frame = frame.getParent()) {
740             commonToOrigin = new FieldTransform<>(date,
741                                                    peel(frame.getTransformProvider()).getTransform(date),
742                                                    commonToOrigin);
743         }
744 
745         // transform from destination up to common
746         FieldTransform<T> commonToDestination = FieldTransform.getIdentity(date.getField());
747         for (Frame frame = to; frame != common; frame = frame.getParent()) {
748             commonToDestination = new FieldTransform<>(date,
749                                                        peel(frame.getTransformProvider()).getTransform(date),
750                                                        commonToDestination);
751         }
752 
753         // transform from origin to destination via common
754         return new FieldTransform<>(date, commonToOrigin.getInverse(), commonToDestination);
755 
756     }
757 
758     /** Retrieve EOP from a frame hierarchy.
759      * <p>
760      * The frame hierarchy tree is walked from specified frame up to root
761      * traversing parent frames, and the providers are checked to see if they
762      * reference EOP history. The first EOP history found is returned.
763      * </p>
764      * @param start frame from which to start search, will typically be some
765      * Earth related frame, like a topocentric frame or an ITRF frame
766      * @return EOP history found while walking the frames tree, or null if
767      * no EOP history is found
768      * @since 9.1
769      */
770     public static EOPHistory findEOP(final Frame start) {
771 
772         for (Frame frame = start; frame != null; frame = frame.getParent()) {
773 
774             TransformProvider peeled = frame.getTransformProvider();
775 
776             boolean peeling = true;
777             while (peeling) {
778                 if (peeled instanceof InterpolatingTransformProvider) {
779                     peeled = ((InterpolatingTransformProvider) peeled).getRawProvider();
780                 } else if (peeled instanceof ShiftingTransformProvider) {
781                     peeled = ((ShiftingTransformProvider) peeled).getRawProvider();
782                 } else if (peeled instanceof EOPBasedTransformProvider &&
783                            ((EOPBasedTransformProvider) peeled).getEOPHistory() != null) {
784                     return ((EOPBasedTransformProvider) peeled).getEOPHistory();
785                 } else {
786                     peeling = false;
787                 }
788             }
789 
790         }
791 
792         // no history found
793         return null;
794 
795     }
796 
797     /** Peel interpolation and shifting from a transform provider.
798      * @param provider transform provider to peel
799      * @return peeled transform provider
800      */
801     private static TransformProvider peel(final TransformProvider provider) {
802 
803         TransformProvider peeled = provider;
804 
805         boolean peeling = true;
806         while (peeling) {
807             if (peeled instanceof InterpolatingTransformProvider) {
808                 peeled = ((InterpolatingTransformProvider) peeled).getRawProvider();
809             } else if (peeled instanceof ShiftingTransformProvider) {
810                 peeled = ((ShiftingTransformProvider) peeled).getRawProvider();
811             } else if (peeled instanceof EOPBasedTransformProvider &&
812                        ((EOPBasedTransformProvider) peeled).getEOPHistory() != null &&
813                        ((EOPBasedTransformProvider) peeled).getEOPHistory().cachesTidalCorrection()) {
814                 peeled = ((EOPBasedTransformProvider) peeled).getNonInterpolatingProvider();
815             } else {
816                 peeling = false;
817             }
818         }
819 
820         return peeled;
821 
822     }
823 
824 }