77# Will Holmgren (@wholmgren), University of Arizona, 2014
88
99from __future__ import division
10-
1110import logging
1211pvl_logger = logging .getLogger ('pvlib' )
12+ import datetime as dt
1313
14- import datetime
1514
1615import numpy as np
1716import pandas as pd
1817import pytz
1918
20- try :
21- from .spa_c_files .spa_py import spa_calc
22- except ImportError as e :
23- pvl_logger .exception ('Could not import built-in SPA calculator. You may need to recompile the SPA code.' )
24-
25- try :
26- import ephem
27- except ImportError as e :
28- pvl_logger .warning ('PyEphem not found.' )
2919
20+ from .pvl_tools import localize_to_utc , datetime_to_djd , djd_to_datetime
3021
3122
32- def get_solarposition (time , location , method = 'spa ' , pressure = 101325 ,
23+ def get_solarposition (time , location , method = 'pyephem ' , pressure = 101325 ,
3324 temperature = 12 ):
3425 """
3526 A convenience wrapper for the solar position calculators.
@@ -48,6 +39,8 @@ def get_solarposition(time, location, method='spa', pressure=101325,
4839 """
4940
5041 method = method .lower ()
42+ if isinstance (time , dt .datetime ):
43+ time = pd .DatetimeIndex ([time ,])
5144
5245 if method == 'spa' :
5346 ephem_df = spa (time , location )
@@ -59,7 +52,6 @@ def get_solarposition(time, location, method='spa', pressure=101325,
5952 return ephem_df
6053
6154
62-
6355def spa (time , location , raw_spa_output = False ):
6456 '''
6557 Calculate the solar position using the C implementation of the NREL
@@ -90,10 +82,16 @@ def spa(time, location, raw_spa_output=False):
9082
9183 # Added by Rob Andrews (@Calama-Consulting), Calama Consulting, 2014
9284 # Edited by Will Holmgren (@wholmgren), University of Arizona, 2014
85+
86+ try :
87+ from .spa_c_files .spa_py import spa_calc
88+ except ImportError as e :
89+ raise ImportError ('Could not import built-in SPA calculator. ' +
90+ 'You may need to recompile the SPA code.' )
9391
9492 pvl_logger .debug ('using built-in spa code to calculate solar position' )
9593
96- time_utc = _localize_to_utc (time , location )
94+ time_utc = localize_to_utc (time , location )
9795
9896 spa_out = []
9997
@@ -120,6 +118,20 @@ def spa(time, location, raw_spa_output=False):
120118 return dfout
121119
122120
121+ def _ephem_setup (location , pressure , temperature ):
122+ import ephem
123+ # initialize a PyEphem observer
124+ obs = ephem .Observer ()
125+ obs .lat = str (location .latitude )
126+ obs .lon = str (location .longitude )
127+ obs .elevation = location .altitude
128+ obs .pressure = pressure / 100. # convert to mBar
129+ obs .temp = temperature
130+
131+ # the PyEphem sun
132+ sun = ephem .Sun ()
133+ return obs , sun
134+
123135
124136def pyephem (time , location , pressure = 101325 , temperature = 12 ):
125137 """
@@ -144,24 +156,17 @@ def pyephem(time, location, pressure=101325, temperature=12):
144156 """
145157
146158 # Written by Will Holmgren (@wholmgren), University of Arizona, 2014
147-
159+
160+ import ephem
161+
148162 pvl_logger .debug ('using PyEphem to calculate solar position' )
149163
150- time_utc = _localize_to_utc (time , location )
164+ time_utc = localize_to_utc (time , location )
151165
152166 sun_coords = pd .DataFrame (index = time_utc )
153167
154- # initialize a PyEphem observer
155- obs = ephem .Observer ()
156- obs .lat = str (location .latitude )
157- obs .lon = str (location .longitude )
158- obs .elevation = location .altitude
159- obs .pressure = pressure / 100. # convert to mBar
160- obs .temp = temperature
161-
162- # the PyEphem sun
163- sun = ephem .Sun ()
164-
168+ obs , sun = _ephem_setup (location , pressure , temperature )
169+
165170 # make and fill lists of the sun's altitude and azimuth
166171 # this is the pressure and temperature corrected apparent alt/az.
167172 alts = []
@@ -197,8 +202,7 @@ def pyephem(time, location, pressure=101325, temperature=12):
197202 return sun_coords .tz_convert (location .tz )
198203 except TypeError :
199204 return sun_coords .tz_localize (location .tz )
200-
201-
205+
202206
203207def ephemeris (time , location , pressure = 101325 , temperature = 12 ):
204208 '''
@@ -217,7 +221,7 @@ def ephemeris(time, location, pressure=101325, temperature=12):
217221 pressure : float or DataFrame
218222 Ambient pressure (Pascals)
219223
220- tempreature : float or DataFrame
224+ temperature : float or DataFrame
221225 Ambient temperature (C)
222226
223227 Returns
@@ -374,30 +378,63 @@ def ephemeris(time, location, pressure=101325, temperature=12):
374378 DFOut ['solar_time' ] = SolarTime
375379
376380 return DFOut
377-
378-
379-
380- def _localize_to_utc ( time , location ):
381+
382+
383+ def calc_time ( lower_bound , upper_bound , location , attribute , value ,
384+ pressure = 101325 , temperature = 12 , xtol = 1.0e-12 ):
381385 """
382- Converts or localizes a time series to UTC.
386+ Calculate the time between lower_bound and upper_bound
387+ where the attribute is equal to value. Uses PyEphem for
388+ solar position calculations.
383389
384390 Parameters
385391 ----------
386- time : pandas.DatetimeIndex or pandas.Series/DataFrame with a DatetimeIndex.
392+ lower_bound : datetime.datetime
393+ upper_bound : datetime.datetime
387394 location : pvlib.Location object
395+ attribute : str
396+ The attribute of a pyephem.Sun object that
397+ you want to solve for. Likely options are 'alt'
398+ and 'az' (which must be given in radians).
399+ value : int or float
400+ The value of the attribute to solve for
401+ pressure : int or float, optional
402+ Air pressure in Pascals. Set to 0 for no
403+ atmospheric correction.
404+ temperature : int or float, optional
405+ Air temperature in degrees C.
406+ xtol : float, optional
407+ The allowed error in the result from value
388408
389409 Returns
390410 -------
391- pandas object localized to UTC.
411+ datetime.datetime
412+
413+ Raises
414+ ------
415+ ValueError
416+ If the value is not contained between the bounds.
417+ AttributeError
418+ If the given attribute is not an attribute of a
419+ PyEphem.Sun object.
392420 """
393-
421+
394422 try :
395- time_utc = time .tz_convert ('UTC' )
396- pvl_logger .debug ('tz_convert to UTC' )
397- except TypeError :
398- time_utc = time .tz_localize (location .tz ).tz_convert ('UTC' )
399- pvl_logger .debug ('tz_localize to {} and then tz_convert to UTC'
400- .format (location .tz ))
423+ import scipy .optimize as so
424+ except ImportError as e :
425+ raise ImportError ('The calc_time function requires scipy' )
426+
427+ obs , sun = _ephem_setup (location , pressure , temperature )
428+
429+ def compute_attr (thetime , target , attr ):
430+ obs .date = thetime
431+ sun .compute (obs )
432+ return getattr (sun , attr ) - target
401433
402- return time_utc
403-
434+ lb = datetime_to_djd (lower_bound )
435+ ub = datetime_to_djd (upper_bound )
436+
437+ djd_root = so .brentq (compute_attr , lb , ub ,
438+ (value , attribute ), xtol = xtol )
439+
440+ return djd_to_datetime (djd_root , location .tz )
0 commit comments