Skip to content

Commit

Permalink
preliminary michalsky implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
kandersolar committed Aug 6, 2024
1 parent 72185cb commit 3bc2566
Showing 1 changed file with 94 additions and 0 deletions.
94 changes: 94 additions & 0 deletions pvlib/solarposition.py
Original file line number Diff line number Diff line change
Expand Up @@ -869,6 +869,100 @@ def ephemeris(time, latitude, longitude, pressure=101325, temperature=12):
return DFOut


def michalsky(time, lat, lon):
"""
Calculate solar position using Michalsky's algorithm.
Michalsky's algorithm [1]_ has a stated accuracy of 0.01 degrees
through the year 2050.
Parameters
----------
time : pandas.DatetimeIndex
Must be localized or UTC will be assumed.
latitude : float
Latitude in decimal degrees. Positive north of equator, negative
to south.
longitude : float
Longitude in decimal degrees. Positive east of prime meridian,
negative to west.
Returns
-------
DataFrame with the following columns (all values in degrees):
* apparent_elevation : sun elevation, accounting for
atmospheric refraction.
* elevation : actual sun elevation (not accounting for refraction).
* azimuth : sun azimuth, east of north.
* apparent_zenith : sun zenith, accounting for atmospheric
refraction.
* zenith : actual sun zenith (not accounting for refraction).
References
----------
.. [1] Michalsky, J., 1988. The Astronomical Almanac's algorithm for
approximate solar position (1950–2050). Solar Energy vol. 40.
:doi:`10.1016/j.solener.2016.03.017`
"""
lat = np.radians(lat)
lon = np.radians(lon)
unixtime = _datetime_to_unixtime(time)
# unix time is the number of seconds since 1970-01-01, but Michalsky needs
# number of days since 2000-01-01 12:00. The difference is 10957.5 days.
n = unixtime / 86400 - 10957.5
hour = ((unixtime / 86400) % 1)*24

# ecliptic coordinates
L = 280.460 + 0.9856474 * n
g = np.radians(357.528 + 0.9856003 * n)
# Note: there is a typo in the reference math vs appendix (0.02 vs 0.2).
# 0.02 gives much better agreement with SPA, so use that one.
l = np.radians(L + 1.915 * np.sin(g) + 0.020 * np.sin(2*g))
ep = np.radians(23.439 - 0.0000004 * n)

# celestial coordinates
ra = np.arctan2(np.cos(ep) * np.sin(l), np.cos(l))
dec = np.arcsin(np.sin(ep) * np.sin(l))

# local coordinates
gmst = np.radians(6.697375 + 0.0657098242 * n + hour)
lmst = gmst + lon / 15
ha = 15*lmst - ra # Eq 3
el = np.arcsin( # Eq 4
np.sin(dec) * np.sin(lat) + np.cos(dec) * np.cos(lat) * np.cos(ha)
)
az = np.arcsin( # Eq 5
-np.cos(dec) * np.sin(ha) / np.cos(el)
)
elc = np.arcsin(np.sin(dec) / np.sin(lat))

el = np.degrees(el)
az = np.degrees(az)
elc = np.degrees(elc)

az = np.where(el >= elc, 180 - az, az)
az = np.where((el <= elc) & (ha > 0), 360 + az, az) % 360

# refraction correction
r = (
3.51561
* (0.1594 + 0.0196 * el + 0.00002 * el**2)
/ (1 + 0.505 * el + 0.0845 * el**2)
)
apparent_elevation = el + r

result = pd.DataFrame({
'elevation': el,
'azimuth': az,
'zenith': 90 - el,
'apparent_elevation': apparent_elevation,
'apparent_zenith': 90 - apparent_elevation,
# TODO equation of time?
}, index=time)
return result


def calc_time(lower_bound, upper_bound, latitude, longitude, attribute, value,
altitude=0, pressure=101325, temperature=12, horizon='+0:00',
xtol=1.0e-12):
Expand Down

0 comments on commit 3bc2566

Please sign in to comment.