1#![allow(non_snake_case)]
2#![allow(non_upper_case_globals)]
3use std::{fs, process};
4use std::io::{BufRead,BufReader};
5
6const WMM_VALIDITY_RANGE: f64 = 5.0;
8const A : f64 = 6378137.0; const f : f64 = 1.0 / 298.257223563; const e_2 : f64 = f * (2.0 - f); const a : f64 = 6371200.0; fn factorial(n: usize) -> f64 {
18 (1..=n).fold(1.0, |acc, x| acc * x as f64)
19}
20
21fn legendre_polynomial(t: f64, n: usize, m: usize) -> f64 {
37 let mut P = 0.0;
38 let mut k = 0;
39 while k <= (n - m) / 2 {
40 let num = (-1.0 as f64).powi(k.try_into().unwrap()) * factorial(2 * n - 2 * k) * t.powi(n as i32 - m as i32 - 2 * k as i32);
41 let den = factorial(k) * factorial(n - k) * factorial(n - m - 2 * k);
42 P += num / den;
43 k += 1;
44 }
45 P *= (2.0 as f64).powi(-(n as i32)) * (1.0 - t*t).powf(m as f64 / 2.0);
46 P
47}
48
49fn schmidt_semi_normalised_associated_legendre(mu: f64) -> [[f64; 14]; 14] {
60 let mut Psn = [[0.0; 14]; 14];
61 for n in 0..=13 {
62 for m in 0..=n {
63 if m == 0 {
64 Psn[n][m] = legendre_polynomial(mu, n, m);
65 } else {
66 Psn[n][m] = f64::sqrt(2.0 * factorial(n - m) / factorial(n + m)) * legendre_polynomial(mu, n, m);
67 }
68 }
69 }
70 Psn
71}
72
73pub enum Error {
74 DateOutsideOfValidityRange
75}
76
77impl std::fmt::Display for Error {
78 fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
79 match self {
80 Error::DateOutsideOfValidityRange => write!(formatter, "Date outside of validity range!")
81 }
82 }
83}
84
85pub struct MagneticModel {
86 from_year: f64,
87 until_year: f64,
88 g: [[f64; 13]; 13],
89 h: [[f64; 13]; 13],
90 g_sv: [[f64; 13]; 13],
91 h_sv: [[f64; 13]; 13]
92}
93
94impl MagneticModel {
95 pub fn calculate(&self, h: f64, phi: f64, lambda: f64, year: f64) -> Result<(f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64, f64), Error> {
126 let lambda = lambda.to_radians();
128 let phi = phi.to_radians();
129 let _R_c = A / f64::sqrt(1.0 - e_2 * f64::sin(phi)*f64::sin(phi)); let _p = (_R_c + h) * f64::cos(phi); let _z = (_R_c * (1.0 - e_2) + h) * f64::sin(phi); let r = f64::sqrt(_p*_p + _z*_z); let phi_prime = f64::asin(_z / r); let Psn_sinphi = schmidt_semi_normalised_associated_legendre(f64::sin(phi_prime));
136
137 let mut g_t = [[0.0; 13]; 13];
138 let mut h_t = [[0.0; 13]; 13];
139 let mut dPsn_sinphi_dphi = [[0.0; 13]; 13];
140 let time_delta = year - self.from_year;
141
142 if time_delta < 0.0 || time_delta > WMM_VALIDITY_RANGE {
143 return Result::Err(Error::DateOutsideOfValidityRange);
144 }
145
146 for n in 0..=12 {
148 for m in 0..=n {
149 g_t[n][m] = self.g[n][m] + time_delta * self.g_sv[n][m]; h_t[n][m] = self.h[n][m] + time_delta * self.h_sv[n][m]; dPsn_sinphi_dphi[n][m] = (n as f64 + 1.0) * f64::tan(phi_prime) * Psn_sinphi[n][m] - f64::sqrt((n as f64 + 1.0).powi(2) - (m as f64).powi(2)) / f64::cos(phi_prime) * Psn_sinphi[n+1][m];
154 }
155 }
156
157 let mut X_prime = 0.0; let mut Y_prime = 0.0; let mut Z_prime = 0.0; let mut X_dot_prime = 0.0; let mut Y_dot_prime = 0.0; let mut Z_dot_prime = 0.0; for n in 1..=12 {
166 let mut X_prime_tmp = 0.0;
167 let mut Y_prime_tmp = 0.0;
168 let mut Z_prime_tmp = 0.0;
169 let mut X_dot_prime_tmp = 0.0;
170 let mut Y_dot_prime_tmp = 0.0;
171 let mut Z_dot_prime_tmp = 0.0;
172
173 for m in 0..=n {
174 X_prime_tmp += (g_t[n][m] * f64::cos(m as f64 * lambda) + h_t[n][m] * f64::sin(m as f64 * lambda)) * dPsn_sinphi_dphi[n][m];
175 Y_prime_tmp += m as f64 * (g_t[n][m] * f64::sin(m as f64 * lambda) - h_t[n][m] * f64::cos(m as f64 * lambda)) * Psn_sinphi[n][m];
176 Z_prime_tmp += (g_t[n][m] * f64::cos(m as f64 * lambda) + h_t[n][m] * f64::sin(m as f64 * lambda)) * Psn_sinphi[n][m];
177 X_dot_prime_tmp += (self.g_sv[n][m] * f64::cos(m as f64 * lambda) + self.h_sv[n][m] * f64::sin(m as f64 * lambda)) * dPsn_sinphi_dphi[n][m];
178 Y_dot_prime_tmp += m as f64 * (self.g_sv[n][m] * f64::sin(m as f64 * lambda) - self.h_sv[n][m] * f64::cos(m as f64 * lambda)) * Psn_sinphi[n][m];
179 Z_dot_prime_tmp += (self.g_sv[n][m] * f64::cos(m as f64 * lambda) + self.h_sv[n][m] * f64::sin(m as f64 * lambda)) * Psn_sinphi[n][m];
180 }
181 let k_temp = (a / r).powi(n as i32 + 2);
182 X_prime += k_temp * X_prime_tmp;
183 Y_prime += k_temp * Y_prime_tmp;
184 Z_prime += (n as f64 + 1.0) * k_temp * Z_prime_tmp;
185 X_dot_prime += k_temp * X_dot_prime_tmp;
186 Y_dot_prime += k_temp * Y_dot_prime_tmp;
187 Z_dot_prime += (n as f64 + 1.0) * k_temp * Z_dot_prime_tmp;
188 }
189 X_prime *= -1.0;
190 Y_prime *= 1.0 / f64::cos(phi_prime);
191 Z_prime *= -1.0;
192 X_dot_prime *= -1.0;
193 Y_dot_prime *= 1.0 / f64::cos(phi_prime);
194 Z_dot_prime *= -1.0;
195
196 let X = X_prime * f64::cos(phi_prime - phi) - Z_prime * f64::sin(phi_prime - phi);
198 let Y = Y_prime;
199 let Z = X_prime * f64::sin(phi_prime - phi) + Z_prime * f64::cos(phi_prime - phi);
200 let X_dot = X_dot_prime * f64::cos(phi_prime - phi) - Z_dot_prime * f64::sin(phi_prime - phi);
201 let Y_dot = Y_dot_prime;
202 let Z_dot = X_dot_prime * f64::sin(phi_prime - phi) + Z_dot_prime * f64::cos(phi_prime - phi);
203
204 let H = f64::sqrt(X*X + Y*Y);
206 let F = f64::sqrt(H*H + Z*Z);
207 let I = f64::atan2(Z, H);
208 let D = f64::atan2(Y, X);
209 let H_dot = (X*X_dot + Y*Y_dot) / H;
210 let F_dot = (X*X_dot + Y*Y_dot + Z*Z_dot) / F;
211 let I_dot = (Z_dot*H - Z*H_dot) / (F*F);
212 let D_dot = (Y_dot*X - Y*X_dot) / (H*H);
213
214 return Result::Ok (
215 (X, Y, Z, H, F, I.to_degrees(), D.to_degrees(),
216 X_dot, Y_dot, Z_dot, H_dot, F_dot, I_dot.to_degrees(), D_dot.to_degrees())
217 );
218 }
219}
220
221pub fn initialise_magnetic_model(path: &str) -> MagneticModel {
227 let model_file = fs::File::open(path).expect("Model file not found!");
229 let mut model_file = BufReader::new(model_file).lines();
230
231 let from_year = model_file
233 .next().expect("Model file is empty!")
234 .expect("Error reading model file!")
235 .split_whitespace()
236 .nth(0).expect("Error parsing model file!")
237 .parse::<f64>().expect("Error parsing model file!");
238
239 let until_year = from_year + WMM_VALIDITY_RANGE;
241
242 let mut g = [[0.0; 13]; 13];
244 let mut h = [[0.0; 13]; 13];
245 let mut g_sv = [[0.0; 13]; 13];
246 let mut h_sv = [[0.0; 13]; 13];
247 for line in model_file {
248 match line {
249 Ok(line) => {
250 if line.starts_with("9999") {
251 break;
252 }
253
254 let mut line = line.split_whitespace();
255
256 let n_line: i32 = line.next().expect("Error parsing model file!").parse().expect("Error parsing model file!");
257 let m_line: i32 = line.next().expect("Error parsing model file!").parse().expect("Error parsing model file!");
258 let g_line: f64 = line.next().expect("Error parsing model file!").parse().expect("Error parsing model file!");
259 let h_line: f64 = line.next().expect("Error parsing model file!").parse().expect("Error parsing model file!");
260 let g_sv_line: f64 = line.next().expect("Error parsing model file!").parse().expect("Error parsing model file!");
261 let h_sv_line: f64 = line.next().expect("Error parsing model file!").parse().expect("Error parsing model file!");
262
263 if n_line > 12 {
264 break;
265 }
266
267 if m_line > n_line || m_line < 0 {
268 eprintln!("Corrupt record in model file!");
269 process::exit(1);
270 }
271
272 g[n_line as usize][m_line as usize] = g_line;
273 g_sv[n_line as usize][m_line as usize] = g_sv_line;
274
275 if m_line != 0 {
276 h[n_line as usize][m_line as usize] = h_line;
277 h_sv[n_line as usize][m_line as usize] = h_sv_line;
278 }
279 },
280 Err(e) => {
281 eprintln!("Error reading model file: {:?}", e);
282 }
283 }
284 }
285
286 println!("Model is valid from {:04}-01-01 to {:04}-01-01", from_year, until_year);
287
288 MagneticModel {
289 from_year,
290 until_year,g,
292 h,
293 g_sv,
294 h_sv
295 }
296}
297
298
299
300#[cfg(test)]
301mod tests {
302 use super::*;
303
304 fn round_to_decimal_places(value: f64, decimal_places: i32) -> f64 {
305 let multiplier = 10.0_f64.powi(decimal_places);
306 (value * multiplier).round() / multiplier
307 }
308
309 fn test(year: f64, h: f64, phi: f64, l: f64,
337 expected_X: f64, expected_Y: f64, expected_Z: f64, expected_H: f64, expected_F: f64, expected_I: f64, expected_D: f64, expected_GV: f64,
338 expected_X_dot: f64, expected_Y_dot: f64, expected_Z_dot: f64, expected_H_dot: f64, expected_F_dot: f64, expected_I_dot: f64, expected_D_dot: f64) {
339 let magnetic_model = initialise_magnetic_model("WMM.2020.COF");
340 let h = h * 1000.0;
341 let result = magnetic_model.calculate(h, phi, l, year);
342
343 match result {
344 Ok(results) => {
345 assert_eq!(round_to_decimal_places(results.0, 1), expected_X);
346 assert_eq!(round_to_decimal_places(results.1, 1), expected_Y);
347 assert_eq!(round_to_decimal_places(results.2, 1), expected_Z);
348 assert_eq!(round_to_decimal_places(results.3, 1), expected_H);
349 assert_eq!(round_to_decimal_places(results.4, 1), expected_F);
350 assert_eq!(round_to_decimal_places(results.5, 2), expected_I);
351 assert_eq!(round_to_decimal_places(results.6, 2), expected_D);
352 assert_eq!(round_to_decimal_places(results.7, 1), expected_X_dot);
353 assert_eq!(round_to_decimal_places(results.8, 1), expected_Y_dot);
354 assert_eq!(round_to_decimal_places(results.9, 1), expected_Z_dot);
355 assert_eq!(round_to_decimal_places(results.10, 1), expected_H_dot);
356 assert_eq!(round_to_decimal_places(results.11, 1), expected_F_dot);
357 assert!(f64::abs(results.12 - expected_I_dot) <= 0.01);
358 assert!(f64::abs(results.13 - expected_D_dot) <= 0.01);
359 },
360 Err(e) => {
361 eprintln!("Error: {}", e);
362 process::exit(1);
363 }
364 }
365 }
366
367 #[test]
368 fn wmm2020_case1() {
369 test(2020.0, 0.0, 80.0, 0.0,
370 6570.4, -146.3, 54606.0, 6572.0, 55000.1, 83.14, -1.28, -1.28,
371 -16.2, 59.0, 42.9, -17.5, 40.5, 0.02, 0.51
372 );
373 }
374
375 #[test]
376 fn wmm2020_case2() {
377 test(2020.0, 0.0, 0.0, 120.0, 39624.3, 109.9, -10932.5, 39624.4, 41104.9, -15.42, 0.16, 020.0, 24.2, -60.8, 49.2, 24.0, 10.1, 0.08, -0.09);
378 }
379
380 #[test]
381 fn wmm2020_case3() {
382 test(2020.0, 0.0, -80.0, 240.0, 5940.6, 15772.1, -52480.8, 16853.8, 55120.6, -72.20, 69.36, -50.64, 30.4, 1.8, 91.7, 12.4, -83.5, 0.04, -0.10);
383 }
384
385 #[test]
386 fn wmm2020_case4() {
387 test(2020.0, 100.0, 80.0, 0.0, 6261.8, -185.5, 52429.1, 6264.5, 52802.0, 83.19, -1.70, -1.70, -15.1, 56.4, 39.2, -16.8, 36.9, 0.02, 0.51);
388 }
389
390 #[test]
391 fn wmm2020_case5() {
392 test(2020.0, 100.0, 0.0, 120.0, 37636.7, 104.9, -10474.8, 37636.9, 39067.3, -15.55, 0.16, 0.0, 22.9, -56.1, 45.1, 22.8, 9.8, 0.07, -0.09);
393 }
394
395 #[test]
396 fn wmm2020_case6() {
397 test(2020.0, 100.0, -80.0, 240.0, 5744.9, 14799.5, -49969.4, 15875.4, 52430.6, -72.37, 68.78, -51.22, 28.0, 1.4, 85.6, 11.4, -78.1, 0.04, -0.09);
398 }
399
400 #[test]
401 fn wmm2020_case7() {
402 test(2022.5, 0.0, 80.0, 0.0, 6529.9, 1.1, 54713.4, 6529.9, 55101.7, 83.19, 0.01, 0.01, -16.2, 59.0, 42.9, -16.2, 40.7, 0.02, 0.52);
403 }
404
405 #[test]
406 fn wmm2020_case8() {
407 test(2022.5, 0.0, 0.0, 120.0, 39684.7, -42.2, -10809.5, 39684.7, 41130.5, -15.24, -0.06, 0.0, 24.2, -60.8, 49.2, 24.2, 10.5, 0.08, -0.09);
408 }
409
410 #[test]
411 fn wmm2020_case9() {
412 test(2022.5, 0.0, -80.0, 240.0, 6016.5, 15776.7, -52251.6, 16885.0, 54912.1, -72.09, 69.13, -50.87, 30.4, 1.8, 91.7, 12.6, -83.4, 0.04, -0.09);
413 }
414
415 #[test]
416 fn wmm2020_case10() {
417 test(2022.5, 100.0, 80.0, 0.0, 6224.0, -44.5, 52527.0, 6224.2, 52894.5, 83.24, -0.41, -0.41, -15.1, 56.4, 39.2, -15.5, 37.1, 0.02, 0.52);
418 }
419
420 #[test]
421 fn wmm2020_case11() {
422 test(2022.5, 100.0, 0.0, 120.0, 37694.0, -35.3, -10362.0, 37694.1, 39092.4, -15.37, -0.05, 0.0, 22.9, -56.1, 45.1, 23.0, 10.2, 0.07, -0.09);
423 }
424
425 #[test]
426 fn wmm2020_case12() {
427 test(2022.5, 100.0, -80.0, 240.0, 5815.0, 14803.0, -49755.3, 15904.1, 52235.4, -72.27, 68.55, -51.47, 28.0, 1.4, 85.6, 11.6, -78.0, 0.04, -0.09);
428 }
429}