Skip to content

Commit d97cf22

Browse files
author
Daniel Lassahn
committed
Add a new way to calculate the solarposition
1 parent 594f47c commit d97cf22

File tree

2 files changed

+344
-82
lines changed

2 files changed

+344
-82
lines changed

pvlib/solarposition.py

+134-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
# Will Holmgren (@wholmgren), University of Arizona, 2014
88
# Tony Lorenzo (@alorenzo175), University of Arizona, 2015
99
# Cliff hansen (@cwhanse), Sandia National Laboratories, 2018
10+
# Daniel Lassahn (@meteoDaniel), meteocontrol Energy and Weather Services, 2019
1011

1112
from __future__ import division
1213
import os
@@ -18,7 +19,6 @@
1819
from imp import reload
1920
except ImportError:
2021
pass
21-
2222
import numpy as np
2323
import pandas as pd
2424
import warnings
@@ -27,8 +27,12 @@
2727
from pvlib.tools import datetime_to_djd, djd_to_datetime
2828
from pvlib._deprecation import deprecated
2929

30-
3130
NS_PER_HR = 1.e9 * 3600. # nanoseconds per hour
31+
JULIAN_2000 = 2451544.5
32+
DT_2000 = dt.datetime(2000, 1, 1)
33+
TIMESTAMP_2000 = DT_2000.timestamp()
34+
DAY_SECONDS = 60 * 60 * 24
35+
JULIAN_YEARS = 365.2425
3236

3337

3438
def get_solarposition(time, latitude, longitude,
@@ -67,6 +71,9 @@ def get_solarposition(time, latitude, longitude,
6771
6872
'nrel_c' uses the NREL SPA C code [3]: :py:func:`spa_c`
6973
74+
'spencer_mc' uses the Spencer formula [4] :py:func:`spencer_mc`
75+
76+
7077
temperature : float, default 12
7178
Degrees C.
7279
@@ -81,6 +88,9 @@ def get_solarposition(time, latitude, longitude,
8188
solar radiation applications. Solar Energy, vol. 81, no. 6, p. 838, 2007.
8289
8390
[3] NREL SPA code: http://rredc.nrel.gov/solar/codesandalgorithms/spa/
91+
92+
[4] Spencer (1972) can be found in "Solar energy fundamentals and
93+
modeling techniques" from Zekai Sen
8494
"""
8595

8696
if altitude is None and pressure is None:
@@ -114,6 +124,9 @@ def get_solarposition(time, latitude, longitude,
114124
elif method == 'ephemeris':
115125
ephem_df = ephemeris(time, latitude, longitude, pressure, temperature,
116126
**kwargs)
127+
elif method == 'spencer_mc':
128+
ephem_df = spencer_mc(time, latitude, longitude)
129+
117130
else:
118131
raise ValueError('Invalid solar position method')
119132

@@ -1440,3 +1453,122 @@ def sun_rise_set_transit_geometric(times, latitude, longitude, declination,
14401453
sunset = _local_times_from_hours_since_midnight(times, sunset_hour)
14411454
transit = _local_times_from_hours_since_midnight(times, transit_hour)
14421455
return sunrise, sunset, transit
1456+
1457+
1458+
def spencer_mc(times, latitude, longitude):
1459+
"""
1460+
Calculate the solar position using a python implementation of the
1461+
Spencer (1972) formulation
1462+
1463+
Parameters
1464+
----------
1465+
times : pandas.DatetimeIndex
1466+
Corresponding timestamps, must be localized to the timezone for the
1467+
``latitude`` and ``longitude``.
1468+
latitude : float
1469+
Latitude in degrees, positive north of equator, negative to south
1470+
longitude : float
1471+
Longitude in degrees, positive east of prime meridian, negative to west
1472+
1473+
Returns
1474+
-------
1475+
DataFrame
1476+
The DataFrame will have the following columns:
1477+
zenith (degrees),
1478+
elevation (degrees),
1479+
azimuth (degrees),
1480+
equation_of_time (seconds),
1481+
eccentricity,
1482+
declination (degrees).
1483+
1484+
References
1485+
----------
1486+
[4] Spencer (1972) can be found in
1487+
Sen, Zekai. Solar energy fundamentals and modeling techniques:
1488+
atmosphere, environment, climate change and renewable energy.
1489+
Springer Science & Business Media, 2008.
1490+
"""
1491+
1492+
julians = datetime2julian(times)
1493+
julians_2000 = np.asarray(julians, dtype=np.float) - JULIAN_2000
1494+
1495+
lat, lat_deg = np.radians(latitude), latitude
1496+
lon, lon_deg = np.radians(longitude), longitude
1497+
1498+
# Compute fractional year (gamma) in radians
1499+
gamma = 2 * np.pi * (julians_2000 % JULIAN_YEARS) / JULIAN_YEARS
1500+
cos_gamma = np.cos(gamma), np.cos(gamma * 2), np.cos(gamma * 3)
1501+
sin_gamma = np.sin(gamma), np.sin(gamma * 2), np.sin(gamma * 3)
1502+
day_time = (julians_2000 % 1) * 24
1503+
1504+
# Eccentricity: correction factor of the earth's orbit.
1505+
eccentricity = (1.00011 + 0.034221 * cos_gamma[0] + 0.001280 *
1506+
sin_gamma[0] + 0.000719 * cos_gamma[1] + 0.000077
1507+
* sin_gamma[1])
1508+
1509+
# declination.
1510+
declination = (0.006918 - 0.399912 * cos_gamma[0] +
1511+
0.070257 * sin_gamma[0] -
1512+
0.006758 * cos_gamma[1] + 0.000907 * sin_gamma[1] -
1513+
0.002697 * cos_gamma[2] + 0.001480 * sin_gamma[2])
1514+
1515+
# Equation of time (difference between standard time and solar time).
1516+
eot = (0.000075 + 0.001868 * cos_gamma[0] - 0.032077 * sin_gamma[0] -
1517+
0.014615 * cos_gamma[1] - 0.040849 * sin_gamma[1]) * 229.18
1518+
1519+
# True local time
1520+
tlt = (day_time + lon_deg / 15 + eot / 60) % 24 - 12
1521+
1522+
# Solar hour angle
1523+
ha = np.radians(tlt * 15)
1524+
1525+
# Calculate sun elevation.
1526+
sin_sun_elevation = (
1527+
np.sin(declination) * np.sin(lat) + np.cos(declination) *
1528+
np.cos(lat) * np.cos(ha)
1529+
)
1530+
1531+
# Compute the sun's elevation and zenith angle.
1532+
elevation = np.arcsin(sin_sun_elevation)
1533+
zenith = np.pi / 2 - elevation
1534+
1535+
# Compute the sun's azimuth angle.
1536+
y = -(np.sin(lat) * np.sin(elevation) - np.sin(declination)) / \
1537+
(np.cos(lat) * np.cos(elevation))
1538+
azimuth = np.arccos(y)
1539+
1540+
# Convert azimuth angle from 0-pi to 0-2pi.
1541+
tlt_filter = 0 <= tlt
1542+
azimuth[tlt_filter] = 2 * np.pi - azimuth[tlt_filter]
1543+
1544+
result = pd.DataFrame({'zenith': np.degrees(zenith),
1545+
'elevation': np.degrees(elevation),
1546+
'azimuth': np.degrees(azimuth),
1547+
'eccentricity': eccentricity,
1548+
'declination': declination,
1549+
'equation_of_time': eot},
1550+
index=times)
1551+
return result
1552+
1553+
1554+
def datetime2julian(times):
1555+
"""
1556+
Transforms pd.DateTimeIndex to Julian days
1557+
1558+
Parameters
1559+
----------
1560+
times : pandas.DatetimeIndex
1561+
Corresponding timestamps, must be localized to the timezone for the
1562+
``latitude`` and ``longitude``.
1563+
1564+
Returns
1565+
-------
1566+
Float64Index
1567+
The float index contains julian dates
1568+
"""
1569+
1570+
delta = times - DT_2000
1571+
return (
1572+
JULIAN_2000 +
1573+
delta.days + (delta.seconds + delta.microseconds / 1e6) / DAY_SECONDS
1574+
)

0 commit comments

Comments
 (0)