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      /** Returns a {@link Collection} of the {@link GeoMagneticField} models that
79       * have been successfully loaded. The {@link Collection} is in
80       * insertion-order, thus it may not be sorted in order of the model epoch.
81       * @return a {@link Collection} of {@link GeoMagneticField} models
82       */
83      public Collection<GeoMagneticField> getModels() {
84          return models;
85      }
86  
87      /** {@inheritDoc} */
88      public boolean stillAcceptsData() {
89          return models == null || models.isEmpty();
90      }
91  
92      /** {@inheritDoc} */
93      public void loadData(final InputStream input, final String name)
94          throws IOException, ParseException {
95  
96          // open data file and parse values
97          final StreamTokenizer str = new StreamTokenizer(new InputStreamReader(input, StandardCharsets.UTF_8));
98  
99          while (true) {
100             final GeoMagneticField model = readModel(str);
101             if (model != null) {
102                 models.add(model);
103             } else {
104                 break;
105             }
106         }
107     }
108 
109     /** Read the model from the given {@link StreamTokenizer}.
110      * @param stream the stream to read the model from
111      * @return the parsed geomagnetic field model
112      * @throws IOException if an I/O error occurs
113      */
114     private GeoMagneticField readModel(final StreamTokenizer stream) throws IOException {
115 
116         // check whether there is another model available in the stream
117         final int ttype = stream.nextToken();
118         if (ttype == StreamTokenizer.TT_EOF) {
119             return null;
120         }
121 
122         if (ttype == StreamTokenizer.TT_WORD) {
123             return readCombinedFormat(stream);
124         } else {
125             return readOriginalWMMFormat(stream);
126         }
127     }
128 
129     /** Read a magnetic field from combined format.
130      * @param stream the stream to read the model from
131      * @return magnetic field
132      * @throws IOException if some read error occurs
133      */
134     private GeoMagneticField readCombinedFormat(final StreamTokenizer stream)
135         throws IOException {
136         final String modelName = stream.sval;
137         stream.nextToken();
138         final double epoch = stream.nval;
139         stream.nextToken();
140         final int nMax = (int) stream.nval;
141         stream.nextToken();
142         final int nMaxSecVar = (int) stream.nval;
143 
144         // ignored
145         stream.nextToken();
146 
147         stream.nextToken();
148         final double startYear = stream.nval;
149 
150         stream.nextToken();
151         final double endYear = stream.nval;
152 
153         final GeoMagneticField model = new GeoMagneticField(modelName, epoch, nMax, nMaxSecVar,
154                                                             startYear, endYear);
155 
156         // the rest is ignored
157         stream.nextToken();
158         @SuppressWarnings("unused")
159         final double altmin = stream.nval;
160 
161         stream.nextToken();
162         @SuppressWarnings("unused")
163         final double altmax = stream.nval;
164 
165         stream.nextToken();
166         stream.nextToken();
167 
168         // loop to get model data from file
169         boolean done = false;
170         int n;
171         int m;
172 
173         do {
174             stream.nextToken();
175             n = (int) stream.nval;
176             stream.nextToken();
177             m = (int) stream.nval;
178 
179             stream.nextToken();
180             final double gnm = stream.nval;
181             stream.nextToken();
182             final double hnm = stream.nval;
183             stream.nextToken();
184             final double dgnm = stream.nval;
185             stream.nextToken();
186             final double dhnm = stream.nval;
187 
188             model.setMainFieldCoefficients(n, m, gnm, hnm);
189             if (n <= nMaxSecVar && m <= nMaxSecVar) {
190                 model.setSecularVariationCoefficients(n, m, dgnm, dhnm);
191             }
192 
193             stream.nextToken();
194             stream.nextToken();
195 
196             done = n == nMax && m == nMax;
197         } while (!done);
198 
199         return model;
200     }
201 
202     /** Read a magnetic field from original WMM files.
203      * @param stream the stream to read the model from
204      * @return magnetic field
205      * @throws IOException if some read error occurs
206      */
207     private GeoMagneticField readOriginalWMMFormat(final StreamTokenizer stream)
208         throws IOException {
209 
210         // hard-coded values in original WMM format
211         final int nMax = 12;
212         final int nMaxSecVar = 12;
213 
214         // the validity start is encoded in format MM/dd/yyyy
215         // use the slash as whitespace character to get separate tokens
216         stream.whitespaceChars('/', '/');
217 
218         final double epoch = stream.nval;
219         stream.nextToken();
220         final String modelName = stream.sval;
221         stream.nextToken();
222         final double month = stream.nval;
223         stream.nextToken();
224         final double day = stream.nval;
225         stream.nextToken();
226         final double year = stream.nval;
227 
228         final double startYear = GeoMagneticField.getDecimalYear((int) day, (int) month, (int) year);
229 
230         final GeoMagneticField model = new GeoMagneticField(modelName, epoch, nMax, nMaxSecVar,
231                                                             startYear, epoch + 5.0);
232 
233         // loop to get model data from file
234         boolean done = false;
235         int n;
236         int m;
237 
238         do {
239             stream.nextToken();
240             n = (int) stream.nval;
241             stream.nextToken();
242             m = (int) stream.nval;
243 
244             stream.nextToken();
245             final double gnm = stream.nval;
246             stream.nextToken();
247             final double hnm = stream.nval;
248             stream.nextToken();
249             final double dgnm = stream.nval;
250             stream.nextToken();
251             final double dhnm = stream.nval;
252 
253             model.setMainFieldCoefficients(n, m, gnm, hnm);
254             if (n <= nMaxSecVar && m <= nMaxSecVar) {
255                 model.setSecularVariationCoefficients(n, m, dgnm, dhnm);
256             }
257 
258             done = n == nMax && m == nMax;
259         } while (!done);
260 
261         // the original format closes with two delimiting lines of '9's
262         stream.nextToken();
263         stream.nextToken();
264 
265         return model;
266     }
267 
268 }