5
5
# Contributors:
6
6
# Rob Andrews (@Calama-Consulting), Calama Consulting, 2014
7
7
# Will Holmgren (@wholmgren), University of Arizona, 2014
8
+ # Tony Lorenzo (@alorenzo175), University of Arizona, 2015
8
9
9
10
from __future__ import division
11
+ import os
10
12
import logging
11
13
pvl_logger = logging .getLogger ('pvlib' )
12
14
import datetime as dt
15
+ try :
16
+ from importlib import reload
17
+ except ImportError :
18
+ try :
19
+ from imp import reload
20
+ except ImportError :
21
+ pass
13
22
14
23
15
24
import numpy as np
@@ -32,7 +41,11 @@ def get_solarposition(time, location, method='pyephem', pressure=101325,
32
41
method : string
33
42
'pyephem' uses the PyEphem package (default): :func:`pyephem`
34
43
35
- 'spa' uses the spa code: :func:`spa`
44
+ 'spa' or 'spa_c' uses the spa code: :func:`spa`
45
+
46
+ 'spa_numpy' uses SPA code translated to python: :func: `spa_python`
47
+
48
+ 'spa_numba' uses SPA code translated to python: :func: `spa_python`
36
49
37
50
'ephemeris' uses the pvlib ephemeris code: :func:`ephemeris`
38
51
pressure : float
@@ -45,10 +58,16 @@ def get_solarposition(time, location, method='pyephem', pressure=101325,
45
58
if isinstance (time , dt .datetime ):
46
59
time = pd .DatetimeIndex ([time , ])
47
60
48
- if method == 'spa' :
49
- ephem_df = spa (time , location )
61
+ if method == 'spa' or method == 'spa_c' :
62
+ ephem_df = spa (time , location , pressure , temperature )
50
63
elif method == 'pyephem' :
51
64
ephem_df = pyephem (time , location , pressure , temperature )
65
+ elif method == 'spa_numpy' :
66
+ ephem_df = spa_python (time , location , pressure , temperature ,
67
+ how = 'numpy' )
68
+ elif method == 'spa_numba' :
69
+ ephem_df = spa_python (time , location , pressure , temperature ,
70
+ how = 'numba' )
52
71
elif method == 'ephemeris' :
53
72
ephem_df = ephemeris (time , location , pressure , temperature )
54
73
else :
@@ -57,7 +76,8 @@ def get_solarposition(time, location, method='pyephem', pressure=101325,
57
76
return ephem_df
58
77
59
78
60
- def spa (time , location , raw_spa_output = False ):
79
+ def spa (time , location , pressure = 101325 , temperature = 12 , delta_t = 67.0 ,
80
+ raw_spa_output = False ):
61
81
'''
62
82
Calculate the solar position using the C implementation of the NREL
63
83
SPA code
@@ -69,6 +89,13 @@ def spa(time, location, raw_spa_output=False):
69
89
----------
70
90
time : pandas.DatetimeIndex
71
91
location : pvlib.Location object
92
+ pressure : float
93
+ Pressure in Pascals
94
+ temperature : float
95
+ Temperature in C
96
+ delta_t : float
97
+ Difference between terrestrial time and UT1.
98
+ USNO has previous values and predictions.
72
99
raw_spa_output : bool
73
100
If true, returns the raw SPA output.
74
101
@@ -78,15 +105,19 @@ def spa(time, location, raw_spa_output=False):
78
105
The DataFrame will have the following columns:
79
106
elevation,
80
107
azimuth,
81
- zenith.
108
+ zenith,
109
+ apparent_elevation,
110
+ apparent_zenith.
82
111
83
112
References
84
113
----------
85
114
NREL SPA code: http://rredc.nrel.gov/solar/codesandalgorithms/spa/
115
+ USNO delta T: http://www.usno.navy.mil/USNO/earth-orientation/eo-products/long-term
86
116
'''
87
117
88
118
# Added by Rob Andrews (@Calama-Consulting), Calama Consulting, 2014
89
119
# Edited by Will Holmgren (@wholmgren), University of Arizona, 2014
120
+ # Edited by Tony Lorenzo (@alorenzo175), University of Arizona, 2015
90
121
91
122
try :
92
123
from pvlib .spa_c_files .spa_py import spa_calc
@@ -102,27 +133,129 @@ def spa(time, location, raw_spa_output=False):
102
133
103
134
for date in time_utc :
104
135
spa_out .append (spa_calc (year = date .year ,
105
- month = date .month ,
106
- day = date .day ,
107
- hour = date .hour ,
108
- minute = date .minute ,
109
- second = date .second ,
110
- timezone = 0 , # timezone corrections handled above
111
- latitude = location .latitude ,
112
- longitude = location .longitude ,
113
- elevation = location .altitude ))
136
+ month = date .month ,
137
+ day = date .day ,
138
+ hour = date .hour ,
139
+ minute = date .minute ,
140
+ second = date .second ,
141
+ timezone = 0 , # timezone corrections handled above
142
+ latitude = location .latitude ,
143
+ longitude = location .longitude ,
144
+ elevation = location .altitude ,
145
+ pressure = pressure / 100 ,
146
+ temperature = temperature ,
147
+ delta_t = delta_t
148
+ ))
114
149
115
150
spa_df = pd .DataFrame (spa_out , index = time_utc ).tz_convert (location .tz )
116
151
117
152
if raw_spa_output :
118
153
return spa_df
119
154
else :
120
- dfout = spa_df [['zenith' , 'azimuth' ]]
121
- dfout ['elevation' ] = 90 - dfout .zenith
122
-
155
+ dfout = pd .DataFrame ({'azimuth' :spa_df ['azimuth' ],
156
+ 'apparent_zenith' :spa_df ['zenith' ],
157
+ 'apparent_elevation' :spa_df ['e' ],
158
+ 'elevation' :spa_df ['e0' ],
159
+ 'zenith' : 90 - spa_df ['e0' ]})
160
+
123
161
return dfout
124
162
125
163
164
+ def spa_python (time , location , pressure = 101325 , temperature = 12 , delta_t = None ,
165
+ atmos_refract = None , how = 'numpy' , numthreads = 4 ):
166
+ """
167
+ Calculate the solar position using a python translation of the
168
+ NREL SPA code.
169
+
170
+ If numba is installed, the functions can be compiled to
171
+ machine code and the function can be multithreaded.
172
+ Without numba, the function evaluates via numpy with
173
+ a slight performance hit.
174
+
175
+ Parameters
176
+ ----------
177
+ time : pandas.DatetimeIndex
178
+ location : pvlib.Location object
179
+ pressure : int or float, optional
180
+ avg. yearly air pressure in Pascals.
181
+ temperature : int or float, optional
182
+ avg. yearly air temperature in degrees C.
183
+ delta_t : float, optional
184
+ Difference between terrestrial time and UT1.
185
+ By default, use USNO historical data and predictions
186
+ 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.
191
+ numthreads : int, optional
192
+ Number of threads to use if how == 'numba'.
193
+
194
+ Returns
195
+ -------
196
+ DataFrame
197
+ The DataFrame will have the following columns:
198
+ apparent_zenith, zenith,
199
+ apparent_elevation, elevation,
200
+ azimuth.
201
+
202
+
203
+ References
204
+ ----------
205
+ [1] I. Reda and A. Andreas, Solar position algorithm for solar radiation applications. Solar Energy, vol. 76, no. 5, pp. 577-589, 2004.
206
+ [2] I. Reda and A. Andreas, Corrigendum to Solar position algorithm for solar radiation applications. Solar Energy, vol. 81, no. 6, p. 838, 2007.
207
+ """
208
+
209
+ # Added by Tony Lorenzo (@alorenzo175), University of Arizona, 2015
210
+
211
+ from pvlib import spa
212
+ pvl_logger .debug ('Calculating solar position with spa_python code' )
213
+
214
+ lat = location .latitude
215
+ lon = location .longitude
216
+ elev = location .altitude
217
+ pressure = pressure / 100 # pressure must be in millibars for calculation
218
+ delta_t = delta_t or 67.0
219
+ atmos_refract = atmos_refract or 0.5667
220
+
221
+ if not isinstance (time , pd .DatetimeIndex ):
222
+ try :
223
+ time = pd .DatetimeIndex (time )
224
+ except (TypeError , ValueError ):
225
+ time = pd .DatetimeIndex ([time ,])
226
+
227
+ unixtime = localize_to_utc (time , location ).astype (int )/ 10 ** 9
228
+
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 (
244
+ unixtime , lat , lon , elev , pressure , temperature , delta_t , atmos_refract ,
245
+ numthreads )
246
+ result = pd .DataFrame ({'apparent_zenith' :app_zenith , 'zenith' :zenith ,
247
+ 'apparent_elevation' :app_elevation ,
248
+ 'elevation' :elevation , 'azimuth' :azimuth },
249
+ index = time )
250
+
251
+ try :
252
+ result = result .tz_convert (location .tz )
253
+ except TypeError :
254
+ result = result .tz_localize (location .tz )
255
+
256
+ return result
257
+
258
+
126
259
def _ephem_setup (location , pressure , temperature ):
127
260
import ephem
128
261
# initialize a PyEphem observer
0 commit comments