1   /* Copyright 2011-2012 Space Applications Services
2    * Licensed to CS Communication & Systèmes (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.io.IOException;
20  import java.io.InputStream;
21  import java.io.InputStreamReader;
22  import java.io.StreamTokenizer;
23  import java.nio.charset.StandardCharsets;
24  import java.text.ParseException;
25  import java.util.Collection;
26  import java.util.LinkedList;
27  import java.util.List;
28  
29  import org.orekit.data.DataLoader;
30  
31  /** Loads geomagnetic field models from a given input stream. A stream may contain multiple
32   * models, the loader reads all available models in consecutive order.
33   * <p>
34   * The format of the expected model file is either:
35   * <ul>
36   *   <li>combined format as used by the geomag software, available from the
37   *       <a href="http://www.ngdc.noaa.gov/IAGA/vmod/igrf.html">IGRF model site</a>;
38   *       supports multiple epochs per file</li>
39   *   <li>original format as used by the
40   *       <a href="http://www.ngdc.noaa.gov/geomag/WMM/DoDWMM.shtml">WMM model site</a>.
41   * </ul>
42   * <p>
43   * <b>Combined Format</b>
44   * <pre>
45   *     {model name} {epoch} {nMax} {nMaxSec} {nMax3} {validity start} {validity end} {minAlt} {maxAlt} {model name} {line number}
46   * {n} {m} {gnm} {hnm} {dgnm} {dhnm} {model name} {line number}
47   * </pre>
48   * <p>
49   * Example:
50   * </p>
51   * <pre>
52   *    WMM2010  2010.00 12 12  0 2010.00 2015.00   -1.0  600.0          WMM2010   0
53   * 1  0  -29496.6       0.0      11.6       0.0                        WMM2010   1
54   * 1  1   -1586.3    4944.4      16.5     -25.9                        WMM2010   2
55   * </pre>
56   * <p>
57   * <b>Original WMM Format</b>
58   * <pre>
59   *    {epoch} {model name} {validity start}
60   * {n} {m} {gnm} {hnm} {dgnm} {dhnm}
61   * </pre>
62   * <p>
63   * Example:
64   * </p>
65   * <pre>
66   *    2015.0            WMM-2015        12/15/2014
67   *  1  0  -29438.5       0.0       10.7        0.0
68   *  1  1   -1501.1    4796.2       17.9      -26.8
69   * </pre>
70   *
71   * @author Thomas Neidhart
72   */
73  public class GeoMagneticModelLoader implements DataLoader {
74  
75      /** The loaded models. */
76      private List<GeoMagneticField> models = new LinkedList<GeoMagneticField>();
77  
78      /** Empty constructor.
79       * <p>
80       * This constructor is not strictly necessary, but it prevents spurious
81       * javadoc warnings with JDK 18 and later.
82       * </p>
83       * @since 12.0
84       */
85      public GeoMagneticModelLoader() {
86          // nothing to do
87      }
88  
89      /** Returns a {@link Collection} of the {@link GeoMagneticField} models that
90       * have been successfully loaded. The {@link Collection} is in
91       * insertion-order, thus it may not be sorted in order of the model epoch.
92       * @return a {@link Collection} of {@link GeoMagneticField} models
93       */
94      public Collection<GeoMagneticField> getModels() {
95          return models;
96      }
97  
98      /** {@inheritDoc} */
99      public boolean stillAcceptsData() {
100         return models == null || models.isEmpty();
101     }
102 
103     /** {@inheritDoc} */
104     public void loadData(final InputStream input, final String name)
105         throws IOException, ParseException {
106 
107         // open data file and parse values
108         final StreamTokenizer str = new StreamTokenizer(new InputStreamReader(input, StandardCharsets.UTF_8));
109 
110         while (true) {
111             final GeoMagneticField model = readModel(str);
112             if (model != null) {
113                 models.add(model);
114             } else {
115                 break;
116             }
117         }
118     }
119 
120     /** Read the model from the given {@link StreamTokenizer}.
121      * @param stream the stream to read the model from
122      * @return the parsed geomagnetic field model
123      * @throws IOException if an I/O error occurs
124      */
125     private GeoMagneticField readModel(final StreamTokenizer stream) throws IOException {
126 
127         // check whether there is another model available in the stream
128         final int ttype = stream.nextToken();
129         if (ttype == StreamTokenizer.TT_EOF) {
130             return null;
131         }
132 
133         if (ttype == StreamTokenizer.TT_WORD) {
134             return readCombinedFormat(stream);
135         } else {
136             return readOriginalWMMFormat(stream);
137         }
138     }
139 
140     /** Read a magnetic field from combined format.
141      * @param stream the stream to read the model from
142      * @return magnetic field
143      * @throws IOException if some read error occurs
144      */
145     private GeoMagneticField readCombinedFormat(final StreamTokenizer stream)
146         throws IOException {
147         final String modelName = stream.sval;
148         stream.nextToken();
149         final double epoch = stream.nval;
150         stream.nextToken();
151         final int nMax = (int) stream.nval;
152         stream.nextToken();
153         final int nMaxSecVar = (int) stream.nval;
154 
155         // ignored
156         stream.nextToken();
157 
158         stream.nextToken();
159         final double startYear = stream.nval;
160 
161         stream.nextToken();
162         final double endYear = stream.nval;
163 
164         final GeoMagneticField model = new GeoMagneticField(modelName, epoch, nMax, nMaxSecVar,
165                                                             startYear, endYear);
166 
167         // the rest is ignored
168         stream.nextToken();
169         @SuppressWarnings("unused")
170         final double altmin = stream.nval;
171 
172         stream.nextToken();
173         @SuppressWarnings("unused")
174         final double altmax = stream.nval;
175 
176         stream.nextToken();
177         stream.nextToken();
178 
179         // loop to get model data from file
180         boolean done = false;
181         int n;
182         int m;
183 
184         do {
185             stream.nextToken();
186             n = (int) stream.nval;
187             stream.nextToken();
188             m = (int) stream.nval;
189 
190             stream.nextToken();
191             final double gnm = stream.nval;
192             stream.nextToken();
193             final double hnm = stream.nval;
194             stream.nextToken();
195             final double dgnm = stream.nval;
196             stream.nextToken();
197             final double dhnm = stream.nval;
198 
199             model.setMainFieldCoefficients(n, m, gnm, hnm);
200             if (n <= nMaxSecVar && m <= nMaxSecVar) {
201                 model.setSecularVariationCoefficients(n, m, dgnm, dhnm);
202             }
203 
204             stream.nextToken();
205             stream.nextToken();
206 
207             done = n == nMax && m == nMax;
208         } while (!done);
209 
210         return model;
211     }
212 
213     /** Read a magnetic field from original WMM files.
214      * @param stream the stream to read the model from
215      * @return magnetic field
216      * @throws IOException if some read error occurs
217      */
218     private GeoMagneticField readOriginalWMMFormat(final StreamTokenizer stream)
219         throws IOException {
220 
221         // hard-coded values in original WMM format
222         final int nMax = 12;
223         final int nMaxSecVar = 12;
224 
225         // the validity start is encoded in format MM/dd/yyyy
226         // use the slash as whitespace character to get separate tokens
227         stream.whitespaceChars('/', '/');
228 
229         final double epoch = stream.nval;
230         stream.nextToken();
231         final String modelName = stream.sval;
232         stream.nextToken();
233         final double month = stream.nval;
234         stream.nextToken();
235         final double day = stream.nval;
236         stream.nextToken();
237         final double year = stream.nval;
238 
239         final double startYear = GeoMagneticField.getDecimalYear((int) day, (int) month, (int) year);
240 
241         final GeoMagneticField model = new GeoMagneticField(modelName, epoch, nMax, nMaxSecVar,
242                                                             startYear, epoch + 5.0);
243 
244         // loop to get model data from file
245         boolean done = false;
246         int n;
247         int m;
248 
249         do {
250             stream.nextToken();
251             n = (int) stream.nval;
252             stream.nextToken();
253             m = (int) stream.nval;
254 
255             stream.nextToken();
256             final double gnm = stream.nval;
257             stream.nextToken();
258             final double hnm = stream.nval;
259             stream.nextToken();
260             final double dgnm = stream.nval;
261             stream.nextToken();
262             final double dhnm = stream.nval;
263 
264             model.setMainFieldCoefficients(n, m, gnm, hnm);
265             if (n <= nMaxSecVar && m <= nMaxSecVar) {
266                 model.setSecularVariationCoefficients(n, m, dgnm, dhnm);
267             }
268 
269             done = n == nMax && m == nMax;
270         } while (!done);
271 
272         // the original format closes with two delimiting lines of '9's
273         stream.nextToken();
274         stream.nextToken();
275 
276         return model;
277     }
278 
279 }