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.models.earth;
18  
19  import java.util.Collection;
20  import java.util.NavigableMap;
21  import java.util.SortedMap;
22  import java.util.TreeMap;
23  
24  import org.hipparchus.util.FastMath;
25  import org.orekit.data.DataProvidersManager;
26  import org.orekit.errors.OrekitException;
27  import org.orekit.errors.OrekitMessages;
28  import org.orekit.models.earth.GeoMagneticFieldFactory.FieldModel;
29  
30  /**
31   * Loads magnetic fields on request and can be configured after creation. Designed to
32   * match the behavior of {@link GeoMagneticFieldFactory} in Orekit 10.0
33   *
34   * @author Evan Ward
35   * @author Thomas Neidhart
36   * @since 10.1
37   */
38  public class LazyLoadedGeoMagneticFields implements GeoMagneticFields {
39  
40      /** Loaded IGRF models. */
41      private NavigableMap<Integer, GeoMagneticField> igrfModels = null;
42  
43      /** Loaded WMM models. */
44      private NavigableMap<Integer, GeoMagneticField> wmmModels = null;
45  
46      /** Provides access to auxiliary data files. */
47      private final DataProvidersManager dataProvidersManager;
48  
49      /**
50       * Create a factory for magnetic fields that uses the given data manager to load
51       * magnetic field files.
52       *
53       * @param dataProvidersManager provides access to auxiliary data files.
54       */
55      public LazyLoadedGeoMagneticFields(final DataProvidersManager dataProvidersManager) {
56          this.dataProvidersManager = dataProvidersManager;
57      }
58  
59      @Override
60      public GeoMagneticField getField(final FieldModel type, final double year) {
61          switch (type) {
62              case WMM:
63                  return getWMM(year);
64              case IGRF:
65                  return getIGRF(year);
66              default:
67                  throw new OrekitException(OrekitMessages.NON_EXISTENT_GEOMAGNETIC_MODEL, type.name(), year);
68          }
69      }
70  
71      @Override
72      public GeoMagneticField getIGRF(final double year) {
73          synchronized (this) {
74              if (igrfModels == null) {
75                  igrfModels = loadModels("^IGRF\\.COF$");
76              }
77              return getModel(FieldModel.IGRF, igrfModels, year);
78          }
79      }
80  
81      @Override
82      public GeoMagneticField getWMM(final double year) {
83          synchronized (this) {
84              if (wmmModels == null) {
85                  wmmModels = loadModels("^WMM\\.COF$");
86              }
87              return getModel(FieldModel.WMM, wmmModels, year);
88          }
89      }
90  
91      /** Loads the geomagnetic model files from the given filename. The loaded
92       * models are inserted in a {@link NavigableMap} with their epoch as key in order
93       * to retrieve them in a sorted manner.
94       * @param supportedNames a regular expression for valid filenames
95       * @return a {@link NavigableMap} of all loaded models
96       */
97      private NavigableMap<Integer, GeoMagneticField> loadModels(final String supportedNames) {
98  
99          NavigableMap<Integer, GeoMagneticField> loadedModels = null;
100         final GeoMagneticModelLoader loader = new GeoMagneticModelLoader();
101         dataProvidersManager.feed(supportedNames, loader);
102 
103         if (!loader.stillAcceptsData()) {
104             final Collection<GeoMagneticField> models = loader.getModels();
105             if (models != null) {
106                 loadedModels = new TreeMap<>();
107                 for (GeoMagneticField model : models) {
108                     // round to a precision of two digits after the comma
109                     final int epoch = (int) FastMath.round(model.getEpoch() * 100d);
110                     loadedModels.put(epoch, model);
111                 }
112             }
113         }
114 
115         // if no models could be loaded -> throw exception
116         if (loadedModels == null || loadedModels.size() == 0) {
117             throw new OrekitException(OrekitMessages.UNABLE_TO_FIND_RESOURCE, supportedNames);
118         }
119 
120         return loadedModels;
121     }
122 
123     /** Gets a geomagnetic field model for the given year. In case the specified
124      * year does not match an existing model epoch, the resulting field is
125      * generated by either time-transforming an existing model using its secular
126      * variation coefficients, or by linear interpolating two existing models.
127      * @param type the type of the field (e.g. WMM or IGRF)
128      * @param models all loaded field models, sorted by their epoch
129      * @param year the epoch of the resulting field model
130      * @return a {@link GeoMagneticField} model for the given year
131      */
132     private static GeoMagneticField getModel(final FieldModel type,
133                                              final NavigableMap<Integer, GeoMagneticField> models,
134                                              final double year) {
135 
136         final int epochKey = (int) (year * 100d);
137         final SortedMap<Integer, GeoMagneticField> head = models.headMap(epochKey, true);
138 
139         if (head.isEmpty()) {
140             throw new OrekitException(OrekitMessages.NON_EXISTENT_GEOMAGNETIC_MODEL, type.name(), year);
141         }
142 
143         GeoMagneticField model = models.get(head.lastKey());
144         if (model.getEpoch() < year) {
145             if (model.supportsTimeTransform()) {
146                 model = model.transformModel(year);
147             } else {
148                 final SortedMap<Integer, GeoMagneticField> tail = models.tailMap(epochKey, false);
149                 if (tail.isEmpty()) {
150                     throw new OrekitException(OrekitMessages.NON_EXISTENT_GEOMAGNETIC_MODEL, type.name(), year);
151                 }
152                 final GeoMagneticField secondModel = models.get(tail.firstKey());
153                 if (secondModel != model) {
154                     model = model.transformModel(secondModel, year);
155                 }
156             }
157         }
158         return model;
159     }
160 
161 }