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.bodies;
18  
19  import java.util.ArrayList;
20  import java.util.HashMap;
21  import java.util.List;
22  import java.util.Map;
23  
24  import org.orekit.data.DataProvidersManager;
25  import org.orekit.errors.OrekitException;
26  import org.orekit.errors.OrekitMessages;
27  import org.orekit.frames.Frame;
28  import org.orekit.time.TimeScales;
29  
30  /**
31   * This class lazily loads auxiliary data when it is needed by a requested body. It is
32   * designed to match the behavior of {@link CelestialBodyFactory} in Orekit 10.0.
33   *
34   * @author Luc Maisonobe
35   * @author Evan Ward
36   * @see CelestialBodyFactory
37   * @since 10.1
38   */
39  public class LazyLoadedCelestialBodies implements CelestialBodies {
40  
41      /** Supplies the auxiliary data files. */
42      private final DataProvidersManager dataProvidersManager;
43      /** Provides access to time scales when parsing bodies. */
44      private final TimeScales timeScales;
45      /** Earth centered frame aligned with ICRF. */
46      private final Frame gcrf;
47      /** Celestial body loaders map. */
48      private final Map<String, List<CelestialBodyLoader>> loadersMap = new HashMap<>();
49  
50      /** Celestial body map. */
51      private final Map<String, CelestialBody> celestialBodyMap = new HashMap<>();
52  
53      /**
54       * Create a celestial body factory with the given auxiliary data sources.
55       *
56       * @param dataProvidersManager supplies JPL ephemerides auxiliary data files.
57       * @param timeScales           set of time scales to use when loading bodies.
58       * @param gcrf                 Earth centered frame aligned with ICRF.
59       */
60      public LazyLoadedCelestialBodies(final DataProvidersManager dataProvidersManager,
61                                       final TimeScales timeScales,
62                                       final Frame gcrf) {
63          this.dataProvidersManager = dataProvidersManager;
64          this.timeScales = timeScales;
65          this.gcrf = gcrf;
66      }
67  
68      /** Add a loader for celestial bodies.
69       * @param name name of the body (may be one of the predefined names or a user-defined name)
70       * @param loader custom loader to add for the body
71       * @see #addDefaultCelestialBodyLoader(String)
72       * @see #clearCelestialBodyLoaders(String)
73       * @see #clearCelestialBodyLoaders()
74       */
75      public void addCelestialBodyLoader(final String name,
76                                         final CelestialBodyLoader loader) {
77          synchronized (loadersMap) {
78              loadersMap.computeIfAbsent(name, k -> new ArrayList<>()).add(loader);
79          }
80      }
81  
82      /** Add the default loaders for all predefined celestial bodies.
83       * @param supportedNames regular expression for supported files names
84       * (may be null if the default JPL file names are used)
85       * <p>
86       * The default loaders look for DE405 or DE406 JPL ephemerides.
87       * </p>
88       * @see <a href="ftp://ssd.jpl.nasa.gov/pub/eph/planets/Linux/de405/">DE405 JPL ephemerides</a>
89       * @see <a href="ftp://ssd.jpl.nasa.gov/pub/eph/planets/Linux/de406/">DE406 JPL ephemerides</a>
90       * @see #addCelestialBodyLoader(String, CelestialBodyLoader)
91       * @see #addDefaultCelestialBodyLoader(String)
92       * @see #clearCelestialBodyLoaders(String)
93       * @see #clearCelestialBodyLoaders()
94       */
95      public void addDefaultCelestialBodyLoader(final String supportedNames) {
96          addDefaultCelestialBodyLoader(CelestialBodyFactory.SOLAR_SYSTEM_BARYCENTER, supportedNames);
97          addDefaultCelestialBodyLoader(CelestialBodyFactory.SUN,                     supportedNames);
98          addDefaultCelestialBodyLoader(CelestialBodyFactory.MERCURY,                 supportedNames);
99          addDefaultCelestialBodyLoader(CelestialBodyFactory.VENUS,                   supportedNames);
100         addDefaultCelestialBodyLoader(CelestialBodyFactory.EARTH_MOON,              supportedNames);
101         addDefaultCelestialBodyLoader(CelestialBodyFactory.EARTH,                   supportedNames);
102         addDefaultCelestialBodyLoader(CelestialBodyFactory.MOON,                    supportedNames);
103         addDefaultCelestialBodyLoader(CelestialBodyFactory.MARS,                    supportedNames);
104         addDefaultCelestialBodyLoader(CelestialBodyFactory.JUPITER,                 supportedNames);
105         addDefaultCelestialBodyLoader(CelestialBodyFactory.SATURN,                  supportedNames);
106         addDefaultCelestialBodyLoader(CelestialBodyFactory.URANUS,                  supportedNames);
107         addDefaultCelestialBodyLoader(CelestialBodyFactory.NEPTUNE,                 supportedNames);
108         addDefaultCelestialBodyLoader(CelestialBodyFactory.PLUTO,                   supportedNames);
109     }
110 
111     /** Add the default loaders for celestial bodies.
112      * @param name name of the body (if not one of the predefined names, the method does nothing)
113      * @param supportedNames regular expression for supported files names
114      * (may be null if the default JPL file names are used)
115      * <p>
116      * The default loaders look for DE405 or DE406 JPL ephemerides.
117      * </p>
118      * @see <a href="ftp://ssd.jpl.nasa.gov/pub/eph/planets/Linux/de405/">DE405 JPL ephemerides</a>
119      * @see <a href="ftp://ssd.jpl.nasa.gov/pub/eph/planets/Linux/de406/">DE406 JPL ephemerides</a>
120      * @see #addCelestialBodyLoader(String, CelestialBodyLoader)
121      * @see #addDefaultCelestialBodyLoader(String)
122      * @see #clearCelestialBodyLoaders(String)
123      * @see #clearCelestialBodyLoaders()
124      */
125     public void addDefaultCelestialBodyLoader(final String name,
126                                               final String supportedNames) {
127 
128         CelestialBodyLoader loader = null;
129         if (name.equalsIgnoreCase(CelestialBodyFactory.SOLAR_SYSTEM_BARYCENTER)) {
130             loader =
131                     new JPLEphemeridesLoader(supportedNames, JPLEphemeridesLoader.EphemerisType.SOLAR_SYSTEM_BARYCENTER, dataProvidersManager, timeScales, gcrf);
132         } else if (name.equalsIgnoreCase(CelestialBodyFactory.SUN)) {
133             loader =
134                     new JPLEphemeridesLoader(supportedNames, JPLEphemeridesLoader.EphemerisType.SUN, dataProvidersManager, timeScales, gcrf);
135         } else if (name.equalsIgnoreCase(CelestialBodyFactory.MERCURY)) {
136             loader =
137                     new JPLEphemeridesLoader(supportedNames, JPLEphemeridesLoader.EphemerisType.MERCURY, dataProvidersManager, timeScales, gcrf);
138         } else if (name.equalsIgnoreCase(CelestialBodyFactory.VENUS)) {
139             loader =
140                     new JPLEphemeridesLoader(supportedNames, JPLEphemeridesLoader.EphemerisType.VENUS, dataProvidersManager, timeScales, gcrf);
141         } else if (name.equalsIgnoreCase(CelestialBodyFactory.EARTH_MOON)) {
142             loader =
143                     new JPLEphemeridesLoader(supportedNames, JPLEphemeridesLoader.EphemerisType.EARTH_MOON, dataProvidersManager, timeScales, gcrf);
144         } else if (name.equalsIgnoreCase(CelestialBodyFactory.EARTH)) {
145             loader =
146                     new JPLEphemeridesLoader(supportedNames, JPLEphemeridesLoader.EphemerisType.EARTH, dataProvidersManager, timeScales, gcrf);
147         } else if (name.equalsIgnoreCase(CelestialBodyFactory.MOON)) {
148             loader =
149                     new JPLEphemeridesLoader(supportedNames, JPLEphemeridesLoader.EphemerisType.MOON, dataProvidersManager, timeScales, gcrf);
150         } else if (name.equalsIgnoreCase(CelestialBodyFactory.MARS)) {
151             loader =
152                     new JPLEphemeridesLoader(supportedNames, JPLEphemeridesLoader.EphemerisType.MARS, dataProvidersManager, timeScales, gcrf);
153         } else if (name.equalsIgnoreCase(CelestialBodyFactory.JUPITER)) {
154             loader =
155                     new JPLEphemeridesLoader(supportedNames, JPLEphemeridesLoader.EphemerisType.JUPITER, dataProvidersManager, timeScales, gcrf);
156         } else if (name.equalsIgnoreCase(CelestialBodyFactory.SATURN)) {
157             loader =
158                     new JPLEphemeridesLoader(supportedNames, JPLEphemeridesLoader.EphemerisType.SATURN, dataProvidersManager, timeScales, gcrf);
159         } else if (name.equalsIgnoreCase(CelestialBodyFactory.URANUS)) {
160             loader =
161                     new JPLEphemeridesLoader(supportedNames, JPLEphemeridesLoader.EphemerisType.URANUS, dataProvidersManager, timeScales, gcrf);
162         } else if (name.equalsIgnoreCase(CelestialBodyFactory.NEPTUNE)) {
163             loader =
164                     new JPLEphemeridesLoader(supportedNames, JPLEphemeridesLoader.EphemerisType.NEPTUNE, dataProvidersManager, timeScales, gcrf);
165         } else if (name.equalsIgnoreCase(CelestialBodyFactory.PLUTO)) {
166             loader =
167                     new JPLEphemeridesLoader(supportedNames, JPLEphemeridesLoader.EphemerisType.PLUTO, dataProvidersManager, timeScales, gcrf);
168         }
169 
170         if (loader != null) {
171             addCelestialBodyLoader(name, loader);
172         }
173 
174     }
175 
176     /** Clear loaders for one celestial body.
177      * <p>
178      * Calling this method also clears the celestial body that
179      * has been loaded via this {@link CelestialBodyLoader}.
180      * </p>
181      * @param name name of the body
182      * @see #addCelestialBodyLoader(String, CelestialBodyLoader)
183      * @see #clearCelestialBodyLoaders()
184      * @see #clearCelestialBodyCache(String)
185      */
186     public void clearCelestialBodyLoaders(final String name) {
187         // use same synchronization order as in getBody to prevent deadlocks
188         synchronized (celestialBodyMap) {
189             // take advantage of reentrent synchronization as
190             // clearCelestialBodyCache uses the same lock inside
191             clearCelestialBodyCache(name);
192 
193             synchronized (loadersMap) {
194                 loadersMap.remove(name);
195             }
196         }
197     }
198 
199     /** Clear loaders for all celestial bodies.
200      * <p>
201      * Calling this method also clears all loaded celestial bodies.
202      * </p>
203      * @see #addCelestialBodyLoader(String, CelestialBodyLoader)
204      * @see #clearCelestialBodyLoaders(String)
205      * @see #clearCelestialBodyCache()
206      */
207     public void clearCelestialBodyLoaders() {
208         synchronized (celestialBodyMap) {
209             clearCelestialBodyCache();
210 
211             synchronized (loadersMap) {
212                 loadersMap.clear();
213             }
214         }
215     }
216 
217     /** Clear the specified celestial body from the internal cache.
218      * @param name name of the body
219      */
220     public void clearCelestialBodyCache(final String name) {
221         synchronized (celestialBodyMap) {
222             celestialBodyMap.remove(name);
223         }
224     }
225 
226     /** Clear all loaded celestial bodies.
227      * <p>
228      * Calling this method will remove all loaded bodies from the internal
229      * cache. Subsequent calls to {@link #getBody(String)} or similar methods
230      * will result in a reload of the requested body from the configured loader(s).
231      * </p>
232      */
233     public void clearCelestialBodyCache() {
234         synchronized (celestialBodyMap) {
235             celestialBodyMap.clear();
236         }
237     }
238 
239     @Override
240     public CelestialBody getSolarSystemBarycenter() {
241         return getBody(CelestialBodyFactory.SOLAR_SYSTEM_BARYCENTER);
242     }
243 
244     @Override
245     public CelestialBody getSun() {
246         return getBody(CelestialBodyFactory.SUN);
247     }
248 
249     @Override
250     public CelestialBody getMercury() {
251         return getBody(CelestialBodyFactory.MERCURY);
252     }
253 
254     @Override
255     public CelestialBody getVenus() {
256         return getBody(CelestialBodyFactory.VENUS);
257     }
258 
259     @Override
260     public CelestialBody getEarthMoonBarycenter() {
261         return getBody(CelestialBodyFactory.EARTH_MOON);
262     }
263 
264     @Override
265     public CelestialBody getEarth() {
266         return getBody(CelestialBodyFactory.EARTH);
267     }
268 
269     @Override
270     public CelestialBody getMoon() {
271         return getBody(CelestialBodyFactory.MOON);
272     }
273 
274     @Override
275     public CelestialBody getMars() {
276         return getBody(CelestialBodyFactory.MARS);
277     }
278 
279     @Override
280     public CelestialBody getJupiter() {
281         return getBody(CelestialBodyFactory.JUPITER);
282     }
283 
284     @Override
285     public CelestialBody getSaturn() {
286         return getBody(CelestialBodyFactory.SATURN);
287     }
288 
289     @Override
290     public CelestialBody getUranus() {
291         return getBody(CelestialBodyFactory.URANUS);
292     }
293 
294     @Override
295     public CelestialBody getNeptune() {
296         return getBody(CelestialBodyFactory.NEPTUNE);
297     }
298 
299     @Override
300     public CelestialBody getPluto() {
301         return getBody(CelestialBodyFactory.PLUTO);
302     }
303 
304     /**
305      * {@inheritDoc}
306      *
307      * <p>
308      * If no {@link CelestialBodyLoader} has been added by calling {@link
309      * #addCelestialBodyLoader(String, CelestialBodyLoader) addCelestialBodyLoader} or if
310      * {@link #clearCelestialBodyLoaders(String) clearCelestialBodyLoaders} has been
311      * called afterwards, the {@link #addDefaultCelestialBodyLoader(String, String)
312      * addDefaultCelestialBodyLoader} method will be called automatically, once with the
313      * default name for JPL DE ephemerides and once with the default name for IMCCE INPOP
314      * files.
315      * </p>
316      */
317     @Override
318     public CelestialBody getBody(final String name) {
319         synchronized (celestialBodyMap) {
320             CelestialBody body = celestialBodyMap.get(name);
321             if (body == null) {
322                 synchronized (loadersMap) {
323                     List<CelestialBodyLoader> loaders = loadersMap.get(name);
324                     if (loaders == null || loaders.isEmpty()) {
325                         addDefaultCelestialBodyLoader(name, JPLEphemeridesLoader.DEFAULT_DE_SUPPORTED_NAMES);
326                         addDefaultCelestialBodyLoader(name, JPLEphemeridesLoader.DEFAULT_INPOP_SUPPORTED_NAMES);
327                         loaders = loadersMap.get(name);
328                     }
329                     OrekitException delayedException = null;
330                     for (CelestialBodyLoader loader : loaders) {
331                         try {
332                             body = loader.loadCelestialBody(name);
333                             if (body != null) {
334                                 break;
335                             }
336                         } catch (OrekitException oe) {
337                             delayedException = oe;
338                         }
339                     }
340                     if (body == null) {
341                         throw (delayedException != null) ?
342                                 delayedException :
343                                 new OrekitException(OrekitMessages.NO_DATA_LOADED_FOR_CELESTIAL_BODY, name);
344                     }
345 
346                 }
347 
348                 // save the body
349                 celestialBodyMap.put(name, body);
350 
351             }
352 
353             return body;
354 
355         }
356     }
357 
358 }