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