Skip to content

Commit 64cc8d5

Browse files
committed
added sunrise/set/transit to python spa, removed pyephem dependency
1 parent 562232d commit 64cc8d5

File tree

5 files changed

+509
-79
lines changed

5 files changed

+509
-79
lines changed

pvlib/solarposition.py

Lines changed: 182 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
from __future__ import division
1111
import os
12+
import warnings
1213
import logging
1314
pvl_logger = logging.getLogger('pvlib')
1415
import datetime as dt
@@ -29,8 +30,8 @@
2930
from pvlib.tools import localize_to_utc, datetime_to_djd, djd_to_datetime
3031

3132

32-
def get_solarposition(time, location, method='pyephem', pressure=101325,
33-
temperature=12):
33+
def get_solarposition(time, location, method='nrel_numpy', pressure=101325,
34+
temperature=12, **kwargs):
3435
"""
3536
A convenience wrapper for the solar position calculators.
3637
@@ -39,51 +40,63 @@ def get_solarposition(time, location, method='pyephem', pressure=101325,
3940
time : pandas.DatetimeIndex
4041
location : pvlib.Location object
4142
method : string
42-
'pyephem' uses the PyEphem package (default): :func:`pyephem`
43+
'pyephem' uses the PyEphem package: :func:`pyephem`
4344
44-
'spa' or 'spa_c' uses the spa code: :func:`spa`
45+
'nrel_c' uses the NREL SPA C code [3]: :func:`spa_c`
4546
46-
'spa_numpy' uses SPA code translated to python: :func: `spa_python`
47+
'nrel_numpy' uses an implementation of the NREL SPA algorithm
48+
described in [1] (default): :func:`spa_python`
4749
48-
'spa_numba' uses SPA code translated to python: :func: `spa_python`
50+
'nrel_numba' uses an implementation of the NREL SPA algorithm
51+
described in [1], but also compiles the code first: :func:`spa_python`
4952
5053
'ephemeris' uses the pvlib ephemeris code: :func:`ephemeris`
5154
pressure : float
5255
Pascals.
5356
temperature : float
5457
Degrees C.
58+
59+
Other keywords are passed to the underlying solar position function.
60+
61+
References
62+
----------
63+
[1] I. Reda and A. Andreas, Solar position algorithm for solar radiation applications. Solar Energy, vol. 76, no. 5, pp. 577-589, 2004.
64+
[2] I. Reda and A. Andreas, Corrigendum to Solar position algorithm for solar radiation applications. Solar Energy, vol. 81, no. 6, p. 838, 2007.
65+
[3] NREL SPA code: http://rredc.nrel.gov/solar/codesandalgorithms/spa/
5566
"""
5667

5768
method = method.lower()
5869
if isinstance(time, dt.datetime):
5970
time = pd.DatetimeIndex([time, ])
6071

61-
if method == 'spa' or method == 'spa_c':
62-
ephem_df = spa(time, location, pressure, temperature)
63-
elif method == 'pyephem':
64-
ephem_df = pyephem(time, location, pressure, temperature)
65-
elif method == 'spa_numpy':
72+
if method == 'nrel_c':
73+
ephem_df = spa_c(time, location, pressure, temperature, **kwargs)
74+
elif method == 'nrel_numba':
6675
ephem_df = spa_python(time, location, pressure, temperature,
67-
how='numpy')
68-
elif method == 'spa_numba':
76+
how='numba', **kwargs)
77+
elif method == 'nrel_numpy':
6978
ephem_df = spa_python(time, location, pressure, temperature,
70-
how='numba')
79+
how='numpy', **kwargs)
80+
elif method == 'pyephem':
81+
ephem_df = pyephem(time, location, pressure, temperature, **kwargs)
7182
elif method == 'ephemeris':
72-
ephem_df = ephemeris(time, location, pressure, temperature)
83+
ephem_df = ephemeris(time, location, pressure, temperature, **kwargs)
7384
else:
7485
raise ValueError('Invalid solar position method')
7586

7687
return ephem_df
7788

7889

79-
def spa(time, location, pressure=101325, temperature=12, delta_t=67.0,
90+
def spa_c(time, location, pressure=101325, temperature=12, delta_t=67.0,
8091
raw_spa_output=False):
8192
'''
8293
Calculate the solar position using the C implementation of the NREL
8394
SPA code
8495
8596
The source files for this code are located in './spa_c_files/', along with
86-
a README file which describes how the C code is wrapped in Python.
97+
a README file which describes how the C code is wrapped in Python.
98+
Due to license restrictions, the C code must be downloaded seperately
99+
and used in accordance with it's license.
87100
88101
Parameters
89102
----------
@@ -113,6 +126,10 @@ def spa(time, location, pressure=101325, temperature=12, delta_t=67.0,
113126
----------
114127
NREL SPA code: http://rredc.nrel.gov/solar/codesandalgorithms/spa/
115128
USNO delta T: http://www.usno.navy.mil/USNO/earth-orientation/eo-products/long-term
129+
130+
See also
131+
--------
132+
pyephem, spa_python, ephemeris
116133
'''
117134

118135
# Added by Rob Andrews (@Calama-Consulting), Calama Consulting, 2014
@@ -161,11 +178,42 @@ def spa(time, location, pressure=101325, temperature=12, delta_t=67.0,
161178
return dfout
162179

163180

181+
def _spa_python_import(how):
182+
"""Compile spa.py appropriately"""
183+
184+
from pvlib import spa
185+
186+
# check to see if the spa module was compiled with numba
187+
using_numba = spa.USE_NUMBA
188+
189+
if how == 'numpy' and using_numba:
190+
# the spa module was compiled to numba code, so we need to
191+
# reload the module without compiling
192+
# the PVLIB_USE_NUMBA env variable is used to tell the module
193+
# to not compile with numba
194+
os.environ['PVLIB_USE_NUMBA'] = '0'
195+
pvl_logger.debug('Reloading spa module without compiling')
196+
spa = reload(spa)
197+
del os.environ['PVLIB_USE_NUMBA']
198+
elif how == 'numba' and not using_numba:
199+
# The spa module was not compiled to numba code, so set
200+
# PVLIB_USE_NUMBA so it does compile to numba on reload.
201+
os.environ['PVLIB_USE_NUMBA'] = '1'
202+
pvl_logger.debug('Reloading spa module, compiling with numba')
203+
spa = reload(spa)
204+
del os.environ['PVLIB_USE_NUMBA']
205+
elif how != 'numba' and how != 'numpy':
206+
raise ValueError("how must be either 'numba' or 'numpy'")
207+
208+
return spa
209+
210+
211+
164212
def spa_python(time, location, pressure=101325, temperature=12, delta_t=None,
165213
atmos_refract=None, how='numpy', numthreads=4):
166214
"""
167-
Calculate the solar position using a python translation of the
168-
NREL SPA code.
215+
Calculate the solar position using a python implementation of the
216+
NREL SPA algorithm described in [1].
169217
170218
If numba is installed, the functions can be compiled to
171219
machine code and the function can be multithreaded.
@@ -182,33 +230,42 @@ def spa_python(time, location, pressure=101325, temperature=12, delta_t=None,
182230
avg. yearly air temperature in degrees C.
183231
delta_t : float, optional
184232
Difference between terrestrial time and UT1.
185-
By default, use USNO historical data and predictions
233+
The USNO has historical and forecasted delta_t [3].
234+
atmos_refrac : float, optional
235+
The approximate atmospheric refraction (in degrees)
236+
at sunrise and sunset.
186237
how : str, optional
187-
If numba >= 0.17.0 is installed, how='numba' will
188-
compile the spa functions to machine code and
189-
run them multithreaded. Otherwise, numpy calculations
190-
are used.
238+
Options are 'numpy' or 'numba'. If numba >= 0.17.0
239+
is installed, how='numba' will compile the spa functions
240+
to machine code and run them multithreaded.
191241
numthreads : int, optional
192242
Number of threads to use if how == 'numba'.
193243
194244
Returns
195245
-------
196246
DataFrame
197247
The DataFrame will have the following columns:
198-
apparent_zenith, zenith,
199-
apparent_elevation, elevation,
200-
azimuth.
248+
apparent_zenith (degrees),
249+
zenith (degrees),
250+
apparent_elevation (degrees),
251+
elevation (degrees),
252+
azimuth (degrees),
253+
equation_of_time (minutes).
201254
202255
203256
References
204257
----------
205258
[1] I. Reda and A. Andreas, Solar position algorithm for solar radiation applications. Solar Energy, vol. 76, no. 5, pp. 577-589, 2004.
206259
[2] I. Reda and A. Andreas, Corrigendum to Solar position algorithm for solar radiation applications. Solar Energy, vol. 81, no. 6, p. 838, 2007.
260+
[3] USNO delta T: http://www.usno.navy.mil/USNO/earth-orientation/eo-products/long-term
261+
262+
See also
263+
--------
264+
pyephem, spa_c, ephemeris
207265
"""
208266

209267
# Added by Tony Lorenzo (@alorenzo175), University of Arizona, 2015
210268

211-
from pvlib import spa
212269
pvl_logger.debug('Calculating solar position with spa_python code')
213270

214271
lat = location.latitude
@@ -226,26 +283,16 @@ def spa_python(time, location, pressure=101325, temperature=12, delta_t=None,
226283

227284
unixtime = localize_to_utc(time, location).astype(int)/10**9
228285

229-
using_numba = spa.USE_NUMBA
230-
if how == 'numpy' and using_numba:
231-
os.environ['PVLIB_USE_NUMBA'] = '0'
232-
pvl_logger.debug('Reloading spa module without compiling')
233-
spa = reload(spa)
234-
del os.environ['PVLIB_USE_NUMBA']
235-
elif how == 'numba' and not using_numba:
236-
os.environ['PVLIB_USE_NUMBA'] = '1'
237-
pvl_logger.debug('Reloading spa module, compiling with numba')
238-
spa = reload(spa)
239-
del os.environ['PVLIB_USE_NUMBA']
240-
elif how != 'numba' and how != 'numpy':
241-
raise ValueError("how must be either 'numba' or 'numpy'")
242-
243-
app_zenith, zenith, app_elevation, elevation, azimuth = spa.solar_position(
286+
spa = _spa_python_import(how)
287+
288+
app_zenith, zenith, app_elevation, elevation, azimuth, eot = spa.solar_position(
244289
unixtime, lat, lon, elev, pressure, temperature, delta_t, atmos_refract,
245290
numthreads)
291+
246292
result = pd.DataFrame({'apparent_zenith':app_zenith, 'zenith':zenith,
247293
'apparent_elevation':app_elevation,
248-
'elevation':elevation, 'azimuth':azimuth},
294+
'elevation':elevation, 'azimuth':azimuth,
295+
'equation_of_time':eot},
249296
index = time)
250297

251298
try:
@@ -254,6 +301,85 @@ def spa_python(time, location, pressure=101325, temperature=12, delta_t=None,
254301
result = result.tz_localize(location.tz)
255302

256303
return result
304+
305+
306+
def get_sun_rise_set_transit(time, location, how='numpy', delta_t=None,
307+
numthreads=4):
308+
"""
309+
Calculate the sunrise, sunset, and sun transit times using the
310+
NREL SPA algorithm described in [1].
311+
312+
If numba is installed, the functions can be compiled to
313+
machine code and the function can be multithreaded.
314+
Without numba, the function evaluates via numpy with
315+
a slight performance hit.
316+
317+
Parameters
318+
----------
319+
time : pandas.DatetimeIndex
320+
Only the date part is used
321+
location : pvlib.Location object
322+
delta_t : float, optional
323+
Difference between terrestrial time and UT1.
324+
By default, use USNO historical data and predictions
325+
how : str, optional
326+
Options are 'numpy' or 'numba'. If numba >= 0.17.0
327+
is installed, how='numba' will compile the spa functions
328+
to machine code and run them multithreaded.
329+
numthreads : int, optional
330+
Number of threads to use if how == 'numba'.
331+
332+
Returns
333+
-------
334+
DataFrame
335+
The DataFrame will have the following columns:
336+
sunrise, sunset, transit
337+
338+
References
339+
----------
340+
[1] Reda, I., Andreas, A., 2003. Solar position algorithm for solar radiation applications. Technical report: NREL/TP-560- 34302. Golden, USA, http://www.nrel.gov.
341+
"""
342+
# Added by Tony Lorenzo (@alorenzo175), University of Arizona, 2015
343+
344+
pvl_logger.debug('Calculating sunrise, set, transit with spa_python code')
345+
346+
lat = location.latitude
347+
lon = location.longitude
348+
delta_t = delta_t or 67.0
349+
350+
if not isinstance(time, pd.DatetimeIndex):
351+
try:
352+
time = pd.DatetimeIndex(time)
353+
except (TypeError, ValueError):
354+
time = pd.DatetimeIndex([time,])
355+
356+
# must convert to midnight UTC on day of interest
357+
utcday = pd.DatetimeIndex(time.date).tz_localize('UTC')
358+
unixtime = utcday.astype(int)/10**9
359+
360+
spa = _spa_python_import(how)
361+
362+
transit, sunrise, sunset = spa.transit_sunrise_sunset(
363+
unixtime, lat, lon, delta_t, numthreads)
364+
365+
# arrays are in seconds since epoch format, need to conver to timestamps
366+
transit = pd.to_datetime(transit, unit='s', utc=True).tz_convert(
367+
location.tz).tolist()
368+
sunrise = pd.to_datetime(sunrise, unit='s', utc=True).tz_convert(
369+
location.tz).tolist()
370+
sunset = pd.to_datetime(sunset, unit='s', utc=True).tz_convert(
371+
location.tz).tolist()
372+
373+
result = pd.DataFrame({'transit':transit,
374+
'sunrise':sunrise,
375+
'sunset':sunset}, index=time)
376+
377+
try:
378+
result = result.tz_convert(location.tz)
379+
except TypeError:
380+
result = result.tz_localize(location.tz)
381+
382+
return result
257383

258384

259385
def _ephem_setup(location, pressure, temperature):
@@ -291,11 +417,18 @@ def pyephem(time, location, pressure=101325, temperature=12):
291417
apparent_elevation, elevation,
292418
apparent_azimuth, azimuth,
293419
apparent_zenith, zenith.
420+
421+
See also
422+
--------
423+
spa_python, spa_c, ephemeris
424+
294425
"""
295426

296427
# Written by Will Holmgren (@wholmgren), University of Arizona, 2014
297-
298-
import ephem
428+
try:
429+
import ephem
430+
except ImportError:
431+
raise ImportError('PyEphem must be installed')
299432

300433
pvl_logger.debug('using PyEphem to calculate solar position')
301434

@@ -394,7 +527,7 @@ def ephemeris(time, location, pressure=101325, temperature=12):
394527
395528
See also
396529
--------
397-
pyephem, spa
530+
pyephem, spa_c, spa_python
398531
399532
'''
400533

@@ -610,3 +743,5 @@ def pyephem_earthsun_distance(time):
610743
earthsun.append(sun.earth_distance)
611744

612745
return pd.Series(earthsun, index=time)
746+
747+

0 commit comments

Comments
 (0)