View Javadoc
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 }