|
1
|
|
|
# -*- coding: utf-8 -*- |
|
2
|
|
|
# vim:fileencoding=utf-8 |
|
3
|
|
|
# |
|
4
|
|
|
# Copyright (c) 2017-2018 Stefan Bender |
|
5
|
|
|
# |
|
6
|
|
|
# This file is part of sciapy. |
|
7
|
|
|
# sciapy is free software: you can redistribute it or modify it |
|
8
|
|
|
# under the terms of the GNU General Public License as published by |
|
9
|
|
|
# the Free Software Foundation, version 2. |
|
10
|
|
|
# See accompanying LICENSE file or http://www.gnu.org/licenses/gpl-2.0.html. |
|
11
|
1 |
|
"""IGRF geomagnetic coordinates |
|
12
|
|
|
|
|
13
|
|
|
This is a python (mix) version of GMPOLE and GMCOORD from |
|
14
|
|
|
http://www.ngdc.noaa.gov/geomag/geom_util/utilities_home.shtml |
|
15
|
|
|
to transform geodetic to geomagnetic coordinates. |
|
16
|
|
|
It uses the IGRF 2012 model and coefficients [#]_. |
|
17
|
|
|
|
|
18
|
|
|
.. [#] Thébault et al. 2015, |
|
19
|
|
|
International Geomagnetic Reference Field: the 12th generation. |
|
20
|
|
|
Earth, Planets and Space, 67 (79) |
|
21
|
|
|
http://nora.nerc.ac.uk/id/eprint/511258 |
|
22
|
|
|
https://doi.org/10.1186/s40623-015-0228-9 |
|
23
|
|
|
""" |
|
24
|
1 |
|
from __future__ import absolute_import, division, print_function |
|
25
|
|
|
|
|
26
|
1 |
|
import logging |
|
27
|
1 |
|
from collections import namedtuple |
|
28
|
1 |
|
from pkg_resources import resource_filename |
|
29
|
|
|
|
|
30
|
1 |
|
import numpy as np |
|
31
|
1 |
|
from scipy.interpolate import interp1d |
|
32
|
1 |
|
from scipy.special import lpmn |
|
33
|
|
|
|
|
34
|
1 |
|
__all__ = ['gmpole', 'gmag_igrf'] |
|
35
|
|
|
|
|
36
|
|
|
# The WGS84 reference ellipsoid |
|
37
|
1 |
|
Earth_ellipsoid = { |
|
38
|
|
|
"a": 6378.137, # semi-major axis of the ellipsoid in km |
|
39
|
|
|
"b": 6356.7523142, # semi-minor axis of the ellipsoid in km |
|
40
|
|
|
"fla": 1. / 298.257223563, # flattening |
|
41
|
|
|
"re": 6371.2 # Earth's radius in km |
|
42
|
|
|
} |
|
43
|
|
|
|
|
44
|
1 |
|
def _ellipsoid(ellipsoid_data=Earth_ellipsoid): |
|
45
|
|
|
# extends the dictionary with the eccentricities |
|
46
|
1 |
|
ell = namedtuple('ellip', ["a", "b", "fla", "eps", "epssq", "re"]) |
|
47
|
1 |
|
ell.a = ellipsoid_data["a"] |
|
48
|
1 |
|
ell.b = ellipsoid_data["b"] |
|
49
|
1 |
|
ell.fla = ellipsoid_data["fla"] |
|
50
|
1 |
|
ell.re = ellipsoid_data["re"] |
|
51
|
|
|
# first eccentricity squared |
|
52
|
1 |
|
ell.epssq = 1. - ell.b**2 / ell.a**2 |
|
53
|
|
|
# first eccentricity |
|
54
|
1 |
|
ell.eps = np.sqrt(ell.epssq) |
|
55
|
1 |
|
return ell |
|
56
|
|
|
|
|
57
|
1 |
|
def _date_to_frac_year(year, month, day): |
|
58
|
|
|
# fractional year by dividing the day of year by the overall |
|
59
|
|
|
# number of days in that year |
|
60
|
1 |
|
extraday = 0 |
|
61
|
1 |
|
if ((year % 4 == 0) and (year % 100 != 0)) or (year % 400 == 0): |
|
62
|
1 |
|
extraday = 1 |
|
63
|
1 |
|
month_days = [0, 31, 28 + extraday, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] |
|
64
|
1 |
|
doy = np.sum(month_days[:month]) + day |
|
65
|
1 |
|
return year + (doy - 1) / (365.0 + extraday) |
|
66
|
|
|
|
|
67
|
1 |
|
def _load_igrf_file(filename="IGRF.tab"): |
|
68
|
|
|
"""Load IGRF coefficients |
|
69
|
|
|
|
|
70
|
|
|
Parameters |
|
71
|
|
|
---------- |
|
72
|
|
|
filename: str, optional |
|
73
|
|
|
The file with the IGRF coefficients. |
|
74
|
|
|
|
|
75
|
|
|
Returns |
|
76
|
|
|
------- |
|
77
|
|
|
interpol: `scipy.interpolate.interp1d` instance |
|
78
|
|
|
Interpolator instance, called with the fractional |
|
79
|
|
|
year to obtain the IGRF coefficients for the epoch. |
|
80
|
|
|
""" |
|
81
|
1 |
|
igrf_tab = np.genfromtxt(filename, skip_header=3, dtype=None) |
|
82
|
|
|
|
|
83
|
1 |
|
sv = igrf_tab[igrf_tab.dtype.names[-1]][1:].astype(np.float64) |
|
84
|
|
|
|
|
85
|
1 |
|
years = np.asarray(igrf_tab[0].tolist()[3:-1]) |
|
86
|
1 |
|
years = np.append(years, [years[-1] + 5]) |
|
87
|
|
|
|
|
88
|
1 |
|
coeffs = [] |
|
89
|
1 |
|
for i in range(1, len(igrf_tab)): |
|
90
|
1 |
|
coeff = np.asarray(igrf_tab[i].tolist()[3:-1]) |
|
91
|
1 |
|
coeff = np.append(coeff, np.array([5]) * sv[0] + coeff[-1]) |
|
92
|
1 |
|
coeffs.append(coeff) |
|
93
|
|
|
|
|
94
|
1 |
|
return interp1d(years, coeffs) |
|
95
|
|
|
|
|
96
|
1 |
|
def _geod_to_spher(phi, lon, Ellip, HeightAboveEllipsoid=0.): |
|
97
|
|
|
"""Convert geodetic to spherical coordinates |
|
98
|
|
|
|
|
99
|
|
|
Converts geodetic coordinates on the WGS-84 reference ellipsoid |
|
100
|
|
|
to Earth-centered Earth-fixed Cartesian coordinates, |
|
101
|
|
|
and then to spherical coordinates. |
|
102
|
|
|
""" |
|
103
|
1 |
|
CosLat = np.cos(np.radians(phi)) |
|
104
|
1 |
|
SinLat = np.sin(np.radians(phi)) |
|
105
|
|
|
|
|
106
|
|
|
# compute the local radius of curvature on the WGS-84 reference ellipsoid |
|
107
|
1 |
|
rc = Ellip.a / np.sqrt(1.0 - Ellip.epssq * SinLat**2) |
|
108
|
|
|
|
|
109
|
|
|
# compute ECEF Cartesian coordinates of specified point (for longitude=0) |
|
110
|
1 |
|
xp = (rc + HeightAboveEllipsoid) * CosLat |
|
111
|
1 |
|
zp = (rc * (1.0 - Ellip.epssq) + HeightAboveEllipsoid) * SinLat |
|
112
|
|
|
|
|
113
|
|
|
# compute spherical radius and angle phi of specified point |
|
114
|
1 |
|
rg = np.sqrt(xp**2 + zp**2) |
|
115
|
|
|
# geocentric latitude |
|
116
|
1 |
|
phig = np.degrees(np.arcsin(zp / rg)) |
|
117
|
1 |
|
return phig, lon, rg |
|
118
|
|
|
|
|
119
|
1 |
|
def _igrf_model(coeffs, Lmax, r, theta, phi, R_E=Earth_ellipsoid["re"]): |
|
120
|
|
|
"""Evaluates the IGRF model function at the given location |
|
121
|
|
|
""" |
|
122
|
|
|
rho = R_E / r |
|
123
|
|
|
sin_theta = np.sin(theta) |
|
124
|
|
|
cos_theta = np.cos(theta) |
|
125
|
|
|
Plm, dPlm = lpmn(Lmax, Lmax, cos_theta) |
|
126
|
|
|
logging.debug("R_E: %s, r: %s, rho: %s", R_E, r, rho) |
|
127
|
|
|
logging.debug("rho: %s, theta: %s, sin_theta: %s, cos_theta: %s", |
|
128
|
|
|
rho, theta, sin_theta, cos_theta) |
|
129
|
|
|
Bx, By, Bz = 0., 0., 0. # Btheta, Bphi, Br |
|
130
|
|
|
idx = 0 |
|
131
|
|
|
rho_l = rho |
|
132
|
|
|
K_l1 = 1. |
|
133
|
|
|
for l in range(1, Lmax + 1): |
|
134
|
|
|
rho_l *= rho # rho^(l+1) |
|
135
|
|
|
# m = 0 values, h_l^m = 0 |
|
136
|
|
|
Bxl = rho_l * coeffs[idx] * dPlm[0, l] * (-sin_theta) |
|
137
|
|
|
Byl = 0. |
|
138
|
|
|
Bzl = -(l + 1) * rho_l * coeffs[idx] * Plm[0, l] |
|
139
|
|
|
if l > 1: |
|
140
|
|
|
K_l1 *= np.sqrt((l - 1) / (l + 1)) |
|
141
|
|
|
idx += 1 |
|
142
|
|
|
K_lm = -K_l1 |
|
143
|
|
|
for m in range(1, l + 1): |
|
144
|
|
|
cfi = K_lm * rho_l * (coeffs[idx] * np.cos(m * phi) |
|
145
|
|
|
+ coeffs[idx + 1] * np.sin(m * phi)) |
|
146
|
|
|
Bxl += cfi * dPlm[m, l] * (-sin_theta) |
|
147
|
|
|
Bzl += -(l + 1) * cfi * Plm[m, l] |
|
148
|
|
|
if sin_theta != 0: |
|
149
|
|
|
Byl += K_lm * rho_l * m * Plm[m, l] * ( |
|
150
|
|
|
- coeffs[idx] * np.sin(m * phi) + |
|
151
|
|
|
coeffs[idx + 1] * np.cos(m * phi)) |
|
152
|
|
|
else: |
|
153
|
|
|
Byl += 0. |
|
154
|
|
|
if m < l: |
|
155
|
|
|
# K_lm for the next m |
|
156
|
|
|
K_lm /= -np.sqrt((l + m + 1) * (l - m)) |
|
157
|
|
|
idx += 2 |
|
158
|
|
|
Bx += Bxl |
|
159
|
|
|
By += Byl |
|
160
|
|
|
Bz += Bzl |
|
161
|
|
|
Bx = rho * Bx |
|
162
|
|
|
By = -rho * By / sin_theta |
|
163
|
|
|
Bz = rho * Bz |
|
164
|
|
|
return Bx, By, Bz |
|
165
|
|
|
|
|
166
|
1 |
|
def igrf_mag(date, lat, lon, alt, filename="IGRF.tab"): |
|
167
|
|
|
"""Evaluate the local magnetic field using the IGRF model |
|
168
|
|
|
|
|
169
|
|
|
Evaluates the IGRF coefficients to calculate the Earth's |
|
170
|
|
|
magnetic field at the given location. |
|
171
|
|
|
The results agree to within a few decimal places with |
|
172
|
|
|
https://ngdc.noaa.gov/geomag-web/ |
|
173
|
|
|
|
|
174
|
|
|
Parameters |
|
175
|
|
|
---------- |
|
176
|
|
|
date: `datetime.date` or `datetime.datetime` instance |
|
177
|
|
|
The date for the evaluation. |
|
178
|
|
|
lat: float |
|
179
|
|
|
Geographic latitude in degrees north |
|
180
|
|
|
lon: float |
|
181
|
|
|
Geographic longitude in degrees east |
|
182
|
|
|
alt: float |
|
183
|
|
|
Altitude above ground in km. |
|
184
|
|
|
filename: str, optional |
|
185
|
|
|
File containing the IGRF coefficients. |
|
186
|
|
|
|
|
187
|
|
|
Returns |
|
188
|
|
|
------- |
|
189
|
|
|
Bx: float |
|
190
|
|
|
Northward component of the magnetic field, B_N. |
|
191
|
|
|
By: float |
|
192
|
|
|
Eastward component of the magnetic field, B_E. |
|
193
|
|
|
Bz: float |
|
194
|
|
|
Downward component of the magnetic field, B_D. |
|
195
|
|
|
""" |
|
196
|
|
|
ellip = _ellipsoid() |
|
197
|
|
|
# date should be datetime.datetime or datetime.date instance, |
|
198
|
|
|
# or something else that provides .year, .month, and .day attributes |
|
199
|
|
|
frac_year = _date_to_frac_year(date.year, date.month, date.day) |
|
200
|
|
|
glat, glon, grad = _geod_to_spher(lat, lon, ellip, alt) |
|
201
|
|
|
sin_theta = np.sin(np.radians(90. - glat)) |
|
202
|
|
|
cos_theta = np.cos(np.radians(90. - glat)) |
|
203
|
|
|
|
|
204
|
|
|
rho = np.sqrt((ellip.a * sin_theta)**2 + (ellip.b * cos_theta)**2) |
|
205
|
|
|
r = np.sqrt(alt**2 + 2 * alt * rho + |
|
206
|
|
|
(ellip.a**4 * sin_theta**2 + ellip.b**4 * cos_theta**2) / rho**2) |
|
207
|
|
|
cd = (alt + rho) / r |
|
208
|
|
|
sd = (ellip.a**2 - ellip.b**2) / rho * cos_theta * sin_theta / r |
|
209
|
|
|
logging.debug("rho: %s, r: %s, (alt + rho) / r: %s, R_E / (R_E + h): %s, R_E / r: %s", |
|
210
|
|
|
rho, r, cd, ellip.re / (ellip.re + alt), ellip.re / r) |
|
211
|
|
|
|
|
212
|
|
|
cos_theta, sin_theta = cos_theta*cd - sin_theta*sd, sin_theta*cd + cos_theta*sd |
|
213
|
|
|
logging.debug("r: %s, spherical coordinates (radius, rho, theta, lat): %s, %s, %s, %s", |
|
214
|
|
|
r, grad, rho, np.degrees(np.arccos(cos_theta)), 90. - glat) |
|
215
|
|
|
|
|
216
|
|
|
# evaluate the IGRF model in spherical coordinates |
|
217
|
|
|
igrf_file = resource_filename(__name__, filename) |
|
218
|
|
|
igrf_coeffs = _load_igrf_file(igrf_file)(frac_year) |
|
219
|
|
|
Bx, By, Bz = _igrf_model(igrf_coeffs, 13, r, np.radians(90. - glat), np.radians(glon)) |
|
220
|
|
|
logging.debug("spherical geomagnetic field (Bx, By, Bz): %s, %s, %s", Bx, By, Bz) |
|
221
|
|
|
logging.debug("spherical dip coordinates: lat %s, lon %s", |
|
222
|
|
|
np.degrees(np.arctan2(0.5 * Bz, np.sqrt(Bx**2 + By**2))), |
|
223
|
|
|
np.degrees(np.arctan2(-By, Bz))) |
|
224
|
|
|
# back to geodetic coordinates |
|
225
|
|
|
Bx, Bz = cd * Bx + sd * Bz, cd * Bz - sd * Bx |
|
226
|
|
|
logging.debug("geodetic geomagnetic field (Bx, By, Bz): %s, %s, %s", Bx, By, Bz) |
|
227
|
|
|
logging.debug("geodetic dip coordinates: lat %s, lon %s", |
|
228
|
|
|
np.degrees(np.arctan2(0.5 * Bz, np.sqrt(Bx**2 + By**2))), |
|
229
|
|
|
np.degrees(np.arctan2(-By, Bz))) |
|
230
|
|
|
return Bx, By, Bz |
|
231
|
|
|
|
|
232
|
1 |
|
def gmpole(date, r_e=Earth_ellipsoid["re"], filename="IGRF.tab"): |
|
233
|
|
|
"""Centered dipole geomagnetic pole coordinates |
|
234
|
|
|
|
|
235
|
|
|
Parameters |
|
236
|
|
|
---------- |
|
237
|
|
|
date: datetime.datetime or datetime.date |
|
238
|
|
|
r_e: float, optional |
|
239
|
|
|
Earth radius to evaluate the dipole's off-centre shift. |
|
240
|
|
|
filename: str, optional |
|
241
|
|
|
File containing the IGRF parameters. |
|
242
|
|
|
|
|
243
|
|
|
Returns |
|
244
|
|
|
------- |
|
245
|
|
|
(lat_n, phi_n): tuple of floats |
|
246
|
|
|
Geographic latitude and longitude of the centered dipole |
|
247
|
|
|
magnetic north pole. |
|
248
|
|
|
(lat_s, phi_s): tuple of floats |
|
249
|
|
|
Geographic latitude and longitude of the centered dipole |
|
250
|
|
|
magnetic south pole. |
|
251
|
|
|
(dx, dy, dz): tuple of floats |
|
252
|
|
|
Magnetic variations in Earth-centered Cartesian coordinates |
|
253
|
|
|
for shifting the dipole off-center. |
|
254
|
|
|
(dX, dY, dZ): tuple of floats |
|
255
|
|
|
Magnetic variations in centered-dipole Cartesian coordinates |
|
256
|
|
|
for shifting the dipole off-center. |
|
257
|
|
|
B_0: float |
|
258
|
|
|
The magnitude of the magnetic field. |
|
259
|
|
|
""" |
|
260
|
1 |
|
igrf_file = resource_filename(__name__, filename) |
|
261
|
1 |
|
gh_func = _load_igrf_file(igrf_file) |
|
262
|
|
|
|
|
263
|
1 |
|
frac_year = _date_to_frac_year(date.year, date.month, date.day) |
|
264
|
1 |
|
logging.debug("fractional year: %s", frac_year) |
|
265
|
1 |
|
g10, g11, h11, g20, g21, h21, g22, h22 = gh_func(frac_year)[:8] |
|
266
|
|
|
|
|
267
|
|
|
# This function finds the location of the north magnetic pole in spherical coordinates. |
|
268
|
|
|
# The equations are from Wallace H. Campbell's "Introduction to Geomagnetic Fields" |
|
269
|
|
|
# and Fraser-Smith 1987, Eq. (5). |
|
270
|
|
|
# For the minus signs see also |
|
271
|
|
|
# Laundal and Richmond, Space Sci. Rev. (2017) 206:27--59, |
|
272
|
|
|
# doi:10.1007/s11214-016-0275-y: (p. 31) |
|
273
|
|
|
# "The Earth’s field is such that the dipole axis points roughly southward, |
|
274
|
|
|
# so that the dipole North Pole is really in the Southern Hemisphere (SH). |
|
275
|
|
|
# However convention dictates that the axis of the geomagnetic dipole is |
|
276
|
|
|
# positive northward, hence the negative sign in the definition of mˆ." |
|
277
|
|
|
# Note that hence Phi_N in their Eq. (14) is actually Phi_S. |
|
278
|
1 |
|
B_0_sq = g10**2 + g11**2 + h11**2 |
|
279
|
1 |
|
theta_n = np.arccos(-g10 / np.sqrt(B_0_sq)) |
|
280
|
1 |
|
phi_n = np.arctan2(-h11, -g11) |
|
281
|
1 |
|
lat_n = 0.5 * np.pi - theta_n |
|
282
|
1 |
|
logging.debug("centered dipole north pole coordinates " |
|
283
|
|
|
"(lat, theta, phi): %s, %s, %s", |
|
284
|
|
|
np.degrees(lat_n), np.degrees(theta_n), np.degrees(phi_n)) |
|
285
|
|
|
|
|
286
|
|
|
# dipole offset according to Fraser-Smith, 1987 |
|
287
|
1 |
|
L_0 = 2 * g10 * g20 + np.sqrt(3) * (g11 * g21 + h11 * h21) |
|
288
|
1 |
|
L_1 = -g11 * g20 + np.sqrt(3) * (g10 * g21 + g11 * g22 + h11 * h22) |
|
289
|
1 |
|
L_2 = -h11 * g20 + np.sqrt(3) * (g10 * h21 - h11 * g22 + g11 * h22) |
|
290
|
1 |
|
E = (L_0 * g10 + L_1 * g11 + L_2 * h11) / (4 * B_0_sq) |
|
291
|
|
|
|
|
292
|
|
|
# dipole offset in geodetic Cartesian coordinates |
|
293
|
1 |
|
xi = (L_0 - g10 * E) / (3 * B_0_sq) |
|
294
|
1 |
|
eta = (L_1 - g11 * E) / (3 * B_0_sq) |
|
295
|
1 |
|
zeta = (L_2 - h11 * E) / (3 * B_0_sq) |
|
296
|
1 |
|
dx = eta * r_e |
|
297
|
1 |
|
dy = zeta * r_e |
|
298
|
1 |
|
dz = xi * r_e |
|
299
|
1 |
|
logging.debug("dx, dy, dz: %s, %s, %s", dx, dy, dz) |
|
300
|
|
|
|
|
301
|
|
|
# dipole offset in geodetic spherical coordinates |
|
302
|
|
|
# Fraser-Smith 1987, Eq. (24) |
|
303
|
1 |
|
delta = np.sqrt(dx**2 + dy**2 + dz**2) |
|
304
|
1 |
|
theta_d = np.arccos(dz / delta) |
|
305
|
1 |
|
lambda_d = 0.5 * np.pi - theta_d |
|
306
|
1 |
|
phi_d = np.arctan2(dy, dx) |
|
307
|
1 |
|
logging.debug( |
|
308
|
|
|
"delta: %s, theta_d: %s, phi_d: %s", |
|
309
|
|
|
delta, np.degrees(theta_d), np.degrees(phi_d), |
|
310
|
|
|
) |
|
311
|
|
|
|
|
312
|
|
|
# dipole offset in centred-dipole spherical coordindates |
|
313
|
1 |
|
sin_lat_ed = (np.sin(lambda_d) * np.sin(lat_n) |
|
314
|
|
|
+ np.cos(lambda_d) * np.cos(lat_n) * np.cos(phi_d - phi_n)) |
|
315
|
1 |
|
lat_ed = np.arcsin(sin_lat_ed) |
|
316
|
1 |
|
theta_ed = 0.5 * np.pi - lat_ed |
|
317
|
1 |
|
sin_lon_ed = np.sin(theta_d) * np.sin(phi_d - phi_n) / np.sin(theta_ed) |
|
318
|
1 |
|
lon_ed = np.pi - np.arcsin(sin_lon_ed) |
|
319
|
1 |
|
logging.debug("eccentric dipole offset in CD coordinates " |
|
320
|
|
|
"(lat, theta, lon): %s, %s, %s", |
|
321
|
|
|
np.degrees(theta_ed), np.degrees(lat_ed), np.degrees(lon_ed)) |
|
322
|
|
|
|
|
323
|
|
|
# dipole offset in centred-dipole Cartesian coordindates |
|
324
|
1 |
|
dX = delta * np.sin(theta_ed) * np.cos(lon_ed) |
|
325
|
1 |
|
dY = delta * np.sin(theta_ed) * np.sin(lon_ed) |
|
326
|
1 |
|
dZ = delta * np.cos(theta_ed) |
|
327
|
1 |
|
logging.debug("magnetic variations (dX, dY, dZ): %s, %s, %s", dX, dY, dZ) |
|
328
|
|
|
|
|
329
|
|
|
# North pole, south pole coordinates |
|
330
|
1 |
|
return ((np.degrees(lat_n), np.degrees(phi_n)), |
|
331
|
|
|
(-np.degrees(lat_n), np.degrees(phi_n + np.pi)), |
|
332
|
|
|
(dx, dy, dz), |
|
333
|
|
|
(dX, dY, dZ), |
|
334
|
|
|
np.sqrt(B_0_sq)) |
|
335
|
|
|
|
|
336
|
1 |
|
def gmag_igrf(date, lat, lon, alt=0., |
|
337
|
|
|
centered_dipole=False, |
|
338
|
|
|
igrf_name="IGRF.tab"): |
|
339
|
|
|
"""Centered or eccentric dipole geomagnetic coordinates |
|
340
|
|
|
|
|
341
|
|
|
Parameters |
|
342
|
|
|
---------- |
|
343
|
|
|
date: datetime.datetime |
|
344
|
|
|
lat: float |
|
345
|
|
|
Geographic latitude in degrees north |
|
346
|
|
|
lon: float |
|
347
|
|
|
Geographic longitude in degrees east |
|
348
|
|
|
alt: float, optional |
|
349
|
|
|
Altitude in km. Default: 0. |
|
350
|
|
|
centered_dipole: bool, optional |
|
351
|
|
|
Returns the centered dipole geomagnetic coordinates |
|
352
|
|
|
if set to True, returns the eccentric dipole |
|
353
|
|
|
geomagnetic coordinates if set to False. |
|
354
|
|
|
Default: False |
|
355
|
|
|
igrf_name: str, optional |
|
356
|
|
|
Default: "IGRF.tab" |
|
357
|
|
|
|
|
358
|
|
|
Returns |
|
359
|
|
|
------- |
|
360
|
|
|
geomag_latitude: numpy.ndarray or float |
|
361
|
|
|
Geomagnetic latitude in eccentric dipole coordinates, |
|
362
|
|
|
centered dipole coordinates if `centered_dipole` is True. |
|
363
|
|
|
geomag_longitude: numpy.ndarray or float |
|
364
|
|
|
Geomagnetic longitude in eccentric dipole coordinates, |
|
365
|
|
|
centered dipole coordinates if `centered_dipole` is True. |
|
366
|
|
|
""" |
|
367
|
1 |
|
ellip = _ellipsoid() |
|
368
|
1 |
|
glat, glon, grad = _geod_to_spher(lat, lon, ellip, alt) |
|
369
|
1 |
|
(lat_GMP, lon_GMP), _, _, (dX, dY, dZ), B_0 = gmpole(date, ellip.re, igrf_name) |
|
370
|
1 |
|
latr, lonr = np.radians(glat), np.radians(glon) |
|
371
|
1 |
|
lat_GMPr, lon_GMPr = np.radians(lat_GMP), np.radians(lon_GMP) |
|
372
|
1 |
|
sin_lat_gmag = (np.sin(latr) * np.sin(lat_GMPr) |
|
373
|
|
|
+ np.cos(latr) * np.cos(lat_GMPr) * np.cos(lonr - lon_GMPr)) |
|
374
|
1 |
|
lon_gmag_y = np.cos(latr) * np.sin(lonr - lon_GMPr) |
|
375
|
1 |
|
lon_gmag_x = (np.cos(latr) * np.sin(lat_GMPr) * np.cos(lonr - lon_GMPr) |
|
376
|
|
|
- np.sin(latr) * np.cos(lat_GMPr)) |
|
377
|
1 |
|
lat_gmag = np.arcsin(sin_lat_gmag) |
|
378
|
1 |
|
lat_gmag_geod = np.arctan2(np.tan(lat_gmag), (1. - ellip.epssq)) |
|
379
|
|
|
|
|
380
|
1 |
|
B_r = -2. * B_0 * sin_lat_gmag / (grad / ellip.re)**3 |
|
381
|
1 |
|
B_th = -B_0 * np.cos(lat_gmag) / (grad / ellip.re)**3 |
|
382
|
1 |
|
logging.debug("B_r: %s, B_th: %s, dip lat: %s", |
|
383
|
|
|
-B_r, B_th, np.degrees(np.arctan2(0.5 * B_r, B_th))) |
|
384
|
|
|
|
|
385
|
1 |
|
lon_gmag = np.arctan2(lon_gmag_y, lon_gmag_x) |
|
386
|
1 |
|
logging.debug("centered dipole coordinates: " |
|
387
|
|
|
"lat_gmag: %s, lon_gmag: %s", |
|
388
|
|
|
np.degrees(lat_gmag), np.degrees(lon_gmag)) |
|
389
|
1 |
|
logging.debug("lat_gmag_geod: %s, lat_GMPr: %s", |
|
390
|
|
|
np.degrees(lat_gmag_geod), np.degrees(lat_GMPr)) |
|
391
|
1 |
|
if centered_dipole: |
|
392
|
|
|
return (np.degrees(lat_gmag), np.degrees(lon_gmag)) |
|
393
|
|
|
|
|
394
|
|
|
# eccentric dipole coordinates (shifted dipole) |
|
395
|
1 |
|
phi_ed = np.arctan2( |
|
396
|
|
|
(grad * np.cos(lat_gmag) * np.sin(lon_gmag) - dY), |
|
397
|
|
|
(grad * np.cos(lat_gmag) * np.cos(lon_gmag) - dX) |
|
398
|
|
|
) |
|
399
|
1 |
|
theta_ed = np.arctan2( |
|
400
|
|
|
(grad * np.cos(lat_gmag) * np.cos(lon_gmag) - dX), |
|
401
|
|
|
(grad * np.sin(lat_gmag) - dZ) * np.cos(phi_ed) |
|
402
|
|
|
) |
|
403
|
1 |
|
lat_ed = 0.5 * np.pi - theta_ed |
|
404
|
1 |
|
lat_ed_geod = np.arctan2(np.tan(lat_ed), (1. - ellip.epssq)) |
|
405
|
1 |
|
logging.debug("lats ed: %s", np.degrees(lat_ed)) |
|
406
|
1 |
|
logging.debug("lats ed geod: %s", np.degrees(lat_ed_geod)) |
|
407
|
1 |
|
logging.debug("phis ed: %s", np.degrees(phi_ed)) |
|
408
|
|
|
return (np.degrees(lat_ed_geod), np.degrees(phi_ed)) |
|
409
|
|
|
|