# Copyright (C) 2019 NRL
# Author: Angeline Burrell
# Disclaimer: This code is under the MIT license, whose details can be found at
# the root in the LICENSE file
#
# -*- coding: utf-8 -*-
"""Utilities that support the AACGM-V2 C functions.
References
----------
Laundal, K. M. and A. D. Richmond (2016), Magnetic Coordinate Systems, Space
Sci. Rev., doi:10.1007/s11214-016-0275-y.
"""
import datetime as dt
import numpy as np
import aacgmv2
[docs]def gc2gd_lat(gc_lat):
"""Convert geocentric latitude to geodetic latitude using WGS84.
Parameters
-----------
gc_lat : array-like or float
Geocentric latitude in degrees N
Returns
---------
gd_lat : array-like or float
Geodetic latitude in degrees N, same type as input `gc_lat`
"""
wgs84_e2 = 0.006694379990141317 - 1.0
gd_lat = np.rad2deg(-np.arctan(np.tan(np.deg2rad(gc_lat)) / wgs84_e2))
return gd_lat
[docs]def subsol(year, doy, utime):
"""Find subsolar geocentric longitude and latitude.
Parameters
----------
year : int
Calendar year between 1601 and 2100
doy : int
Day of year between 1-365/366
utime : float
Seconds since midnight on the specified day
Returns
-------
sbsllon : float
Subsolar longitude in degrees E for the given date/time
sbsllat : float
Subsolar latitude in degrees N for the given date/time
Raises
------
ValueError
If year is out of range
Notes
-----
Based on formulas in Astronomical Almanac for the year 1996, p. C24.
(U.S. Government Printing Office, 1994). Usable for years 1601-2100,
inclusive. According to the Almanac, results are good to at least 0.01
degree latitude and 0.025 degrees longitude between years 1950 and 2050.
Accuracy for other years has not been tested. Every day is assumed to have
exactly 86400 seconds; thus leap seconds that sometimes occur on December
31 are ignored (their effect is below the accuracy threshold of the
algorithm).
References
----------
After Fortran code by A. D. Richmond, NCAR. Translated from IDL
by K. Laundal.
"""
# Convert from 4 digit year to 2 digit year
yr2 = year - 2000
if year >= 2101 or year <= 1600:
raise ValueError('subsol valid between 1601-2100. Input year is:', year)
# Determine if this year is a leap year
nleap = np.floor((year - 1601) / 4)
nleap = nleap - 99
if year <= 1900:
ncent = np.floor((year - 1601) / 100)
ncent = 3 - ncent
nleap = nleap + ncent
# Calculate some of the coefficients needed to deterimine the mean longitude
# of the sun and the mean anomaly
l_0 = -79.549 + (-0.238699 * (yr2 - 4 * nleap) + 3.08514e-2 * nleap)
g_0 = -2.472 + (-0.2558905 * (yr2 - 4 * nleap) - 3.79617e-2 * nleap)
# Days (including fraction) since 12 UT on January 1 of IYR2:
dfrac = (utime / 86400 - 1.5) + doy
# Mean longitude of Sun:
l_sun = l_0 + 0.9856474 * dfrac
# Mean anomaly:
grad = np.radians(g_0 + 0.9856003 * dfrac)
# Ecliptic longitude:
lmrad = np.radians(l_sun + 1.915 * np.sin(grad) + 0.020 * np.sin(2 * grad))
sinlm = np.sin(lmrad)
# Days (including fraction) since 12 UT on January 1 of 2000:
epoch_day = dfrac + 365.0 * yr2 + nleap
# Obliquity of ecliptic:
epsrad = np.radians(23.439 - 4.0e-7 * epoch_day)
# Right ascension:
alpha = np.degrees(np.arctan2(np.cos(epsrad) * sinlm, np.cos(lmrad)))
# Declination, which is the subsolar latitude:
sbsllat = np.degrees(np.arcsin(np.sin(epsrad) * sinlm))
# Equation of time (degrees):
etdeg = l_sun - alpha
etdeg = etdeg - 360.0 * np.round(etdeg / 360.0)
# Apparent time (degrees):
aptime = utime / 240.0 + etdeg # Earth rotates one degree every 240 s.
# Subsolar longitude:
sbsllon = 180.0 - aptime
sbsllon = sbsllon - 360.0 * np.round(sbsllon / 360.0)
return sbsllon, sbsllat
[docs]def igrf_dipole_axis(date):
"""Get Cartesian unit vector pointing at dipole pole in the north (IGRF).
Parameters
----------
date : dt.datetime
Date and time
Returns
-------
m_0 : np.ndarray
Cartesian 3 element unit vector pointing at dipole pole in the north
(geocentric coords)
Notes
-----
IGRF coefficients are read from the igrf12coeffs.txt file. It should also
work after IGRF updates. The dipole coefficients are interpolated to the
date, or extrapolated if date > latest IGRF model
"""
# Get time in years, as float
year = date.year
doy = date.timetuple().tm_yday
year_days = dt.date(date.year, 12, 31).timetuple().tm_yday
year = year + doy / year_days
# Read the IGRF coefficients
with open(aacgmv2.IGRF_COEFFS) as f_igrf:
lines = f_igrf.readlines()
years = lines[3].split()[3:][:-1]
years = np.array(years, dtype=float) # time array
g10 = lines[4].split()[3:]
g11 = lines[5].split()[3:]
h11 = lines[6].split()[3:]
# Secular variation coefficients (for extrapolation)
g10sv = np.float32(g10[-1])
g11sv = np.float32(g11[-1])
h11sv = np.float32(h11[-1])
# Model coefficients:
g10 = np.array(g10[:-1], dtype=float)
g11 = np.array(g11[:-1], dtype=float)
h11 = np.array(h11[:-1], dtype=float)
# Get the gauss coefficient at given time:
if year <= years[-1] and year >= years[0]:
# Regular interpolation
g10 = np.interp(year, years, g10)
g11 = np.interp(year, years, g11)
h11 = np.interp(year, years, h11)
else:
# Extrapolation
dyear = year - years[-1]
g10 = g10[-1] + g10sv * dyear
g11 = g11[-1] + g11sv * dyear
h11 = h11[-1] + h11sv * dyear
# Calculate pole position
B_0 = np.sqrt(g10**2 + g11**2 + h11**2)
# Calculate output
m_0 = -np.array([g11, h11, g10]) / B_0
return m_0