1 /*
2 * Copyright (C) 2020 Alberto Irurueta Carro (alberto@irurueta.com)
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package com.irurueta.navigation.inertial.wmm;
17
18 import java.io.File;
19 import java.io.FileInputStream;
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.io.InputStreamReader;
23 import java.io.StreamTokenizer;
24 import java.net.HttpURLConnection;
25 import java.net.URL;
26
27 /**
28 * Loads a WWM from a file of coefficients.
29 * The file of coefficients is updated every 5 years and can be obtained
30 * at: <a href="https://www.ngdc.noaa.gov/geomag/WMM/">https://www.ngdc.noaa.gov/geomag/WMM/</a>
31 *
32 * @see WorldMagneticModel
33 */
34 public class WMMLoader {
35
36 /**
37 * Maximum allowed value within file of coefficients.
38 */
39 private static final double MAX_VAL = 9999.0;
40
41 /**
42 * Number of coefficients.
43 */
44 private static final int N = WorldMagneticModel.N;
45
46 /**
47 * Constructor.
48 * Prevents instantiation of helper class.
49 */
50 private WMMLoader() {
51 }
52
53 /**
54 * Loads World Magnetic Model from provided resource name.
55 * Resource will be resolved an loaded using current class loader.
56 *
57 * @param resource a resource name.
58 * @return a World Magnetic Model containing all required coefficients.
59 * @throws IOException if an I/O error occurs.
60 */
61 public static WorldMagneticModel loadFromResource(final String resource) throws IOException {
62 try (final var stream = WMMLoader.class.getResourceAsStream(resource)) {
63 return load(stream);
64 }
65 }
66
67 /**
68 * Loads World Magnetic Model from provided URL.
69 * Data will be requested with a "GET" method without additional headers.
70 *
71 * @param url URL to request data from.
72 * @return a World Magnetic Model containing all required coefficients.
73 * @throws IOException if an I/O error occurs.
74 */
75 public static WorldMagneticModel loadFromUrl(final String url) throws IOException {
76 return load(new URL(url));
77 }
78
79 /**
80 * Loads World Magnetic Model from provided file path.
81 *
82 * @param filePath a file path.
83 * @return a World Magnetic Model containing all required coefficients.
84 * @throws IOException if an I/O error occurs.
85 */
86 public static WorldMagneticModel loadFromFile(final String filePath) throws IOException {
87 try (final var stream = new FileInputStream(filePath)) {
88 return load(stream);
89 }
90 }
91
92 /**
93 * Loads World Magnetic Model from provided URL.
94 * Data will be requested with a "GET" method without additional headers.
95 *
96 * @param url URL to request data from.
97 * @return a World Magnetic Model containing all required coefficients.
98 * @throws IOException if an I/O error occurs.
99 */
100 public static WorldMagneticModel load(final URL url) throws IOException {
101 final var connection = (HttpURLConnection) url.openConnection();
102 connection.setRequestMethod("GET");
103 try (final var stream = connection.getInputStream()) {
104 return load(stream);
105 }
106 }
107
108 /**
109 * Loads World Magnetic Model from provided file.
110 *
111 * @param file a file.
112 * @return a World Magnetic Model containing all required coefficients.
113 * @throws IOException if an I/O error occurs.
114 */
115 public static WorldMagneticModel load(final File file) throws IOException {
116 try (final var stream = new FileInputStream(file)) {
117 return load(stream);
118 }
119 }
120
121 /**
122 * Loads World Magnetic Model from provided stream of data.
123 *
124 * @param stream a stream of data.
125 * @return a World Magnetic Model containing all required coefficients.
126 * @throws IOException if an I/O error occurs.
127 */
128 public static WorldMagneticModel load(final InputStream stream) throws IOException {
129 try (final var reader = new InputStreamReader(stream)) {
130 final var result = new WorldMagneticModel();
131 final var tokenizer = new StreamTokenizer(reader);
132
133 // Read World Magnetic Model spherical harmonic coefficients
134 result.snorm[0] = 1.0;
135 result.c[0][0] = 0.0;
136 result.cd[0][0] = 0.0;
137
138 tokenizer.nextToken();
139 result.epoch = tokenizer.nval;
140 tokenizer.nextToken();
141 tokenizer.nextToken();
142
143 // loop to get data from file
144 while (true) {
145 tokenizer.nextToken();
146 if (tokenizer.nval >= MAX_VAL) {
147 // end of file
148 break;
149 }
150
151 final var n = (int) tokenizer.nval;
152 tokenizer.nextToken();
153 final var m = (int) tokenizer.nval;
154 tokenizer.nextToken();
155 final var gnm = tokenizer.nval;
156 tokenizer.nextToken();
157 final var hnm = tokenizer.nval;
158 tokenizer.nextToken();
159 final var dgnm = tokenizer.nval;
160 tokenizer.nextToken();
161 final var dhnm = tokenizer.nval;
162
163 if (m <= n) {
164 result.c[m][n] = gnm;
165 result.cd[m][n] = dgnm;
166
167 if (m != 0) {
168 result.c[n][m - 1] = hnm;
169 result.cd[n][m - 1] = dhnm;
170 }
171 }
172 }
173
174 // convert Schmidt normalized Gauss coefficients to un-normalized
175 result.snorm[0] = 1.0;
176 for (var n = 1; n <= WorldMagneticModel.MAX_ORDER; n++) {
177
178 result.snorm[n] = result.snorm[n - 1] * (2 * n - 1) / n;
179 var j = 2;
180
181 for (int m = 0, D1 = 1, D2 = (n - m + D1) / D1; D2 > 0; D2--, m += D1) {
182 result.k[m][n] = (double) (((n - 1) * (n - 1)) - (m * m)) / (double) ((2 * n - 1) * (2 * n - 3));
183 if (m > 0) {
184 final var flnmj = ((n - m + 1) * j) / (double) (n + m);
185 result.snorm[n + m * N] = result.snorm[n + (m - 1) * N] * Math.sqrt(flnmj);
186 j = 1;
187 result.c[n][m - 1] = result.snorm[n + m * N] * result.c[n][m - 1];
188 result.cd[n][m - 1] = result.snorm[n + m * N] * result.cd[n][m - 1];
189 }
190 result.c[m][n] = result.snorm[n + m * N] * result.c[m][n];
191 result.cd[m][n] = result.snorm[n + m * N] * result.cd[m][n];
192 }
193
194 result.fn[n] = (n + 1);
195 result.fm[n] = n;
196 }
197
198 result.k[1][1] = 0.0;
199
200 return result;
201 }
202 }
203 }