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 }