1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.orekit.models.earth;
18
19 import org.hipparchus.geometry.euclidean.threed.Vector3D;
20 import org.hipparchus.util.FastMath;
21 import org.hipparchus.util.SinCos;
22 import org.orekit.bodies.GeodeticPoint;
23 import org.orekit.errors.OrekitException;
24 import org.orekit.errors.OrekitMessages;
25 import org.orekit.utils.Constants;
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40 public class GeoMagneticField {
41
42
43 private static double a = Constants.WGS84_EARTH_EQUATORIAL_RADIUS;
44
45
46 private static double epssq = 0.0066943799901413169961;
47
48
49 private static double ellipsoidRadius = 6371200.0;
50
51
52 private String modelName;
53
54
55 private double epoch;
56
57
58 private double[] g;
59
60
61 private double[] h;
62
63
64 private double[] dg;
65
66
67 private double[] dh;
68
69
70 private int maxN;
71
72
73 private int maxNSec;
74
75
76 private double validityStart;
77
78 private double validityEnd;
79
80
81
82
83 private double[] schmidtQuasiNorm;
84
85
86
87
88
89
90
91
92
93
94
95 protected GeoMagneticField(final String modelName, final double epoch,
96 final int maxN, final int maxNSec,
97 final double validityStart, final double validityEnd) {
98
99 this.modelName = modelName;
100 this.epoch = epoch;
101 this.maxN = maxN;
102 this.maxNSec = maxNSec;
103
104 this.validityStart = validityStart;
105 this.validityEnd = validityEnd;
106
107
108 final int maxMainFieldTerms = (maxN + 1) * (maxN + 2) / 2;
109 g = new double[maxMainFieldTerms];
110 h = new double[maxMainFieldTerms];
111
112 final int maxSecularFieldTerms = (maxNSec + 1) * (maxNSec + 2) / 2;
113 dg = new double[maxSecularFieldTerms];
114 dh = new double[maxSecularFieldTerms];
115
116
117
118
119 schmidtQuasiNorm = new double[maxMainFieldTerms + 1];
120 schmidtQuasiNorm[0] = 1.0;
121
122 int index;
123 int index1;
124 for (int n = 1; n <= maxN; n++) {
125 index = n * (n + 1) / 2;
126 index1 = (n - 1) * n / 2;
127
128
129 schmidtQuasiNorm[index] =
130 schmidtQuasiNorm[index1] * (double) (2 * n - 1) / (double) n;
131
132 for (int m = 1; m <= n; m++) {
133 index = n * (n + 1) / 2 + m;
134 index1 = n * (n + 1) / 2 + m - 1;
135 schmidtQuasiNorm[index] =
136 schmidtQuasiNorm[index1] *
137 FastMath.sqrt((double) ((n - m + 1) * (m == 1 ? 2 : 1)) / (double) (n + m));
138 }
139 }
140 }
141
142
143
144
145 public double getEpoch() {
146 return epoch;
147 }
148
149
150
151
152 public String getModelName() {
153 return modelName;
154 }
155
156
157
158
159 public double validFrom() {
160 return validityStart;
161 }
162
163
164
165
166 public double validTo() {
167 return validityEnd;
168 }
169
170
171
172
173
174 public boolean supportsTimeTransform() {
175 return maxNSec > 0;
176 }
177
178
179
180
181
182
183
184 protected void setMainFieldCoefficients(final int n, final int m,
185 final double gnm, final double hnm) {
186 final int index = n * (n + 1) / 2 + m;
187 g[index] = gnm;
188 h[index] = hnm;
189 }
190
191
192
193
194
195
196
197 protected void setSecularVariationCoefficients(final int n, final int m,
198 final double dgnm, final double dhnm) {
199 final int index = n * (n + 1) / 2 + m;
200 dg[index] = dgnm;
201 dh[index] = dhnm;
202 }
203
204
205
206
207
208
209
210
211 public GeoMagneticElements calculateField(final double latitude,
212 final double longitude,
213 final double height) {
214
215 final GeodeticPoint gp = new GeodeticPoint(latitude, longitude, height);
216
217 final SphericalCoordinates sph = transformToSpherical(gp);
218 final SphericalHarmonicVars vars = new SphericalHarmonicVars(sph);
219 final LegendreFunction legendre = new LegendreFunction(FastMath.sin(sph.phi));
220
221
222 final Vector3D magFieldSph = summation(sph, vars, legendre);
223
224 final Vector3D magFieldGeo = rotateMagneticVector(sph, gp, magFieldSph);
225
226 return new GeoMagneticElements(magFieldGeo);
227 }
228
229
230
231
232
233
234 public GeoMagneticField transformModel(final double year) {
235
236 if (!supportsTimeTransform()) {
237 throw new OrekitException(OrekitMessages.UNSUPPORTED_TIME_TRANSFORM, modelName, String.valueOf(epoch));
238 }
239
240
241 if (year < validityStart || year > validityEnd) {
242 throw new OrekitException(OrekitMessages.OUT_OF_RANGE_TIME_TRANSFORM,
243 modelName, String.valueOf(epoch), year, validityStart, validityEnd);
244 }
245
246 final double dt = year - epoch;
247 final int maxSecIndex = maxNSec * (maxNSec + 1) / 2 + maxNSec;
248
249 final GeoMagneticField transformed = new GeoMagneticField(modelName, year, maxN, maxNSec,
250 validityStart, validityEnd);
251
252 for (int n = 1; n <= maxN; n++) {
253 for (int m = 0; m <= n; m++) {
254 final int index = n * (n + 1) / 2 + m;
255 if (index <= maxSecIndex) {
256 transformed.h[index] = h[index] + dt * dh[index];
257 transformed.g[index] = g[index] + dt * dg[index];
258
259 transformed.dh[index] = dh[index];
260 transformed.dg[index] = dg[index];
261 } else {
262
263 transformed.h[index] = h[index];
264 transformed.g[index] = g[index];
265 }
266 }
267 }
268
269 return transformed;
270 }
271
272
273
274
275
276
277
278
279 public GeoMagneticField transformModel(final GeoMagneticField otherModel, final double year) {
280
281
282 if (year < validityStart || year > validityEnd) {
283 throw new OrekitException(OrekitMessages.OUT_OF_RANGE_TIME_TRANSFORM,
284 modelName, String.valueOf(epoch), year, validityStart, validityEnd);
285 }
286
287 final double factor = (year - epoch) / (otherModel.epoch - epoch);
288 final int maxNCommon = FastMath.min(maxN, otherModel.maxN);
289 final int maxNCommonIndex = maxNCommon * (maxNCommon + 1) / 2 + maxNCommon;
290
291 final int newMaxN = FastMath.max(maxN, otherModel.maxN);
292
293 final GeoMagneticField transformed = new GeoMagneticField(modelName, year, newMaxN, 0,
294 validityStart, validityEnd);
295
296 for (int n = 1; n <= newMaxN; n++) {
297 for (int m = 0; m <= n; m++) {
298 final int index = n * (n + 1) / 2 + m;
299 if (index <= maxNCommonIndex) {
300 transformed.h[index] = h[index] + factor * (otherModel.h[index] - h[index]);
301 transformed.g[index] = g[index] + factor * (otherModel.g[index] - g[index]);
302 } else {
303 if (maxN < otherModel.maxN) {
304 transformed.h[index] = factor * otherModel.h[index];
305 transformed.g[index] = factor * otherModel.g[index];
306 } else {
307 transformed.h[index] = h[index] + factor * -h[index];
308 transformed.g[index] = g[index] + factor * -g[index];
309 }
310 }
311 }
312 }
313
314 return transformed;
315 }
316
317
318
319
320
321
322
323 public static double getDecimalYear(final int day, final int month, final int year) {
324 final int[] days = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
325 final int leapYear = ((year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0)) ? 1 : 0;
326
327 final double dayInYear = days[month - 1] + (day - 1) + (month > 2 ? leapYear : 0);
328 return (double) year + (dayInYear / (365.0d + leapYear));
329 }
330
331
332
333
334
335 private SphericalCoordinates transformToSpherical(final GeodeticPoint gp) {
336
337
338
339
340 final double lat = gp.getLatitude();
341 final SinCos sc = FastMath.sinCos(lat);
342 final double heightAboveEllipsoid = gp.getAltitude();
343
344
345 final double rc = a / FastMath.sqrt(1.0d - epssq * sc.sin() * sc.sin());
346
347
348 final double xp = (rc + heightAboveEllipsoid) * sc.cos();
349 final double zp = (rc * (1.0d - epssq) + heightAboveEllipsoid) * sc.sin();
350
351
352 final double r = FastMath.hypot(xp, zp);
353 return new SphericalCoordinates(r, gp.getLongitude(), FastMath.asin(zp / r));
354 }
355
356
357
358
359
360
361
362 private Vector3D rotateMagneticVector(final SphericalCoordinates sph,
363 final GeodeticPoint gp,
364 final Vector3D field) {
365
366
367 final double psi = sph.phi - gp.getLatitude();
368 final SinCos sc = FastMath.sinCos(psi);
369
370
371 final double Bz = field.getX() * sc.sin() + field.getZ() * sc.cos();
372 final double Bx = field.getX() * sc.cos() - field.getZ() * sc.sin();
373 final double By = field.getY();
374
375 return new Vector3D(Bx, By, Bz);
376 }
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392 private Vector3D summation(final SphericalCoordinates sph, final SphericalHarmonicVars vars,
393 final LegendreFunction legendre) {
394
395 int index;
396 double Bx = 0.0;
397 double By = 0.0;
398 double Bz = 0.0;
399
400 for (int n = 1; n <= maxN; n++) {
401 for (int m = 0; m <= n; m++) {
402 index = n * (n + 1) / 2 + m;
403
404
405
406
407
408
409
410
411
412 Bz -= vars.relativeRadiusPower[n] *
413 (g[index] * vars.cmLambda[m] + h[index] * vars.smLambda[m]) * (1d + n) * legendre.mP[index];
414
415
416
417
418
419
420
421
422
423 By += vars.relativeRadiusPower[n] *
424 (g[index] * vars.smLambda[m] - h[index] * vars.cmLambda[m]) * (double) m * legendre.mP[index];
425
426
427
428
429
430
431
432
433 Bx -= vars.relativeRadiusPower[n] *
434 (g[index] * vars.cmLambda[m] + h[index] * vars.smLambda[m]) * legendre.mPDeriv[index];
435 }
436 }
437
438 final double cosPhi = FastMath.cos(sph.phi);
439 if (FastMath.abs(cosPhi) > 1.0e-10) {
440 By = By / cosPhi;
441 } else {
442
443
444
445 By = summationSpecial(sph, vars);
446 }
447
448 return new Vector3D(Bx, By, Bz);
449 }
450
451
452
453
454
455
456 private double summationSpecial(final SphericalCoordinates sph, final SphericalHarmonicVars vars) {
457
458 double k;
459 final double sinPhi = FastMath.sin(sph.phi);
460 final double[] mPcupS = new double[maxN + 1];
461 mPcupS[0] = 1;
462 double By = 0.0;
463
464 for (int n = 1; n <= maxN; n++) {
465 final int index = n * (n + 1) / 2 + 1;
466 if (n == 1) {
467 mPcupS[n] = mPcupS[n - 1];
468 } else {
469 k = (double) (((n - 1) * (n - 1)) - 1) / (double) ((2 * n - 1) * (2 * n - 3));
470 mPcupS[n] = sinPhi * mPcupS[n - 1] - k * mPcupS[n - 2];
471 }
472
473
474
475
476
477
478
479
480
481 By += vars.relativeRadiusPower[n] *
482 (g[index] * vars.smLambda[1] - h[index] * vars.cmLambda[1]) * mPcupS[n] * schmidtQuasiNorm[index];
483 }
484
485 return By;
486 }
487
488
489 private static class SphericalCoordinates {
490
491
492 private double r;
493
494
495 private double lambda;
496
497
498 private double phi;
499
500
501
502
503
504
505 private SphericalCoordinates(final double r, final double lambda, final double phi) {
506 this.r = r;
507 this.lambda = lambda;
508 this.phi = phi;
509 }
510 }
511
512
513 private class SphericalHarmonicVars {
514
515
516 private double[] relativeRadiusPower;
517
518
519 private double[] cmLambda;
520
521
522 private double[] smLambda;
523
524
525
526
527 private SphericalHarmonicVars(final SphericalCoordinates sph) {
528
529 relativeRadiusPower = new double[maxN + 1];
530
531
532
533
534 final double p = ellipsoidRadius / sph.r;
535 relativeRadiusPower[0] = p * p;
536 for (int n = 1; n <= maxN; n++) {
537 relativeRadiusPower[n] = relativeRadiusPower[n - 1] * (ellipsoidRadius / sph.r);
538 }
539
540
541
542
543 cmLambda = new double[maxN + 1];
544 smLambda = new double[maxN + 1];
545
546 cmLambda[0] = 1.0d;
547 smLambda[0] = 0.0d;
548
549 final SinCos scLambda = FastMath.sinCos(sph.lambda);
550 cmLambda[1] = scLambda.cos();
551 smLambda[1] = scLambda.sin();
552
553 for (int m = 2; m <= maxN; m++) {
554 cmLambda[m] = cmLambda[m - 1] * scLambda.cos() - smLambda[m - 1] * scLambda.sin();
555 smLambda[m] = cmLambda[m - 1] * scLambda.sin() + smLambda[m - 1] * scLambda.cos();
556 }
557 }
558 }
559
560
561 private class LegendreFunction {
562
563
564 private double[] mP;
565
566
567 private double[] mPDeriv;
568
569
570
571
572
573
574
575
576
577
578 private LegendreFunction(final double x) {
579
580 final int numTerms = (maxN + 1) * (maxN + 2) / 2;
581
582 mP = new double[numTerms + 1];
583 mPDeriv = new double[numTerms + 1];
584
585 mP[0] = 1.0;
586 mPDeriv[0] = 0.0;
587
588
589 final double z = FastMath.sqrt((1.0d - x) * (1.0d + x));
590
591 int index;
592 int index1;
593 int index2;
594
595
596 for (int n = 1; n <= maxN; n++) {
597 for (int m = 0; m <= n; m++) {
598 index = n * (n + 1) / 2 + m;
599 if (n == m) {
600 index1 = (n - 1) * n / 2 + m - 1;
601 mP[index] = z * mP[index1];
602 mPDeriv[index] = z * mPDeriv[index1] + x * mP[index1];
603 } else if (n == 1 && m == 0) {
604 index1 = (n - 1) * n / 2 + m;
605 mP[index] = x * mP[index1];
606 mPDeriv[index] = x * mPDeriv[index1] - z * mP[index1];
607 } else if (n > 1) {
608 index1 = (n - 2) * (n - 1) / 2 + m;
609 index2 = (n - 1) * n / 2 + m;
610 if (m > n - 2) {
611 mP[index] = x * mP[index2];
612 mPDeriv[index] = x * mPDeriv[index2] - z * mP[index2];
613 } else {
614 final double k = (double) ((n - 1) * (n - 1) - (m * m)) /
615 (double) ((2 * n - 1) * (2 * n - 3));
616
617 mP[index] = x * mP[index2] - k * mP[index1];
618 mPDeriv[index] = x * mPDeriv[index2] - z * mP[index2] - k * mPDeriv[index1];
619 }
620 }
621
622 }
623 }
624
625
626
627
628 for (int n = 1; n <= maxN; n++) {
629 for (int m = 0; m <= n; m++) {
630 index = n * (n + 1) / 2 + m;
631
632 mP[index] = mP[index] * schmidtQuasiNorm[index];
633
634
635 mPDeriv[index] = -mPDeriv[index] * schmidtQuasiNorm[index];
636 }
637 }
638 }
639 }
640 }