66# Rob Andrews (@Calama-Consulting), Calama Consulting, 2014
77# Will Holmgren (@wholmgren), University of Arizona, 2014
88# Tony Lorenzo (@alorenzo175), University of Arizona, 2015
9+ # Cliff hansen (@cwhanse), Sandia National Laboratories, 2018
910
1011from __future__ import division
1112import os
@@ -438,7 +439,26 @@ def get_sun_rise_set_transit(time, latitude, longitude, how='numpy',
438439 return result
439440
440441
441- def _ephem_setup (latitude , longitude , altitude , pressure , temperature ):
442+ def _ephem_convert_to_seconds_and_microseconds (date ):
443+ # utility from unreleased PyEphem 3.6.7.1
444+ """Converts a PyEphem date into seconds"""
445+ microseconds = int (round (24 * 60 * 60 * 1000000 * date ))
446+ seconds , microseconds = divmod (microseconds , 1000000 )
447+ seconds -= 2209032000 # difference between epoch 1900 and epoch 1970
448+ return seconds , microseconds
449+
450+
451+ def _ephem_to_timezone (date , tzinfo ):
452+ # utility from unreleased PyEphem 3.6.7.1
453+ """"Convert a PyEphem Date into a timezone aware python datetime"""
454+ seconds , microseconds = _ephem_convert_to_seconds_and_microseconds (date )
455+ date = dt .datetime .fromtimestamp (seconds , tzinfo )
456+ date = date .replace (microsecond = microseconds )
457+ return date
458+
459+
460+ def _ephem_setup (latitude , longitude , altitude , pressure , temperature ,
461+ horizon ):
442462 import ephem
443463 # initialize a PyEphem observer
444464 obs = ephem .Observer ()
@@ -447,14 +467,97 @@ def _ephem_setup(latitude, longitude, altitude, pressure, temperature):
447467 obs .elevation = altitude
448468 obs .pressure = pressure / 100. # convert to mBar
449469 obs .temp = temperature
470+ obs .horizon = horizon
450471
451472 # the PyEphem sun
452473 sun = ephem .Sun ()
453474 return obs , sun
454475
455476
477+ def rise_set_transit_ephem (time , latitude , longitude ,
478+ next_or_previous = 'next' ,
479+ altitude = 0 ,
480+ pressure = 101325 , temperature = 12 , horizon = '0:00' ):
481+ """
482+ Calculate the next sunrise and sunset times using the PyEphem package.
483+
484+ Parameters
485+ ----------
486+ time : pandas.DatetimeIndex
487+ Must be localized
488+ latitude : float
489+ positive is north of 0
490+ longitude : float
491+ positive is east of 0
492+ next_or_previous : str
493+ 'next' or 'previous' sunrise and sunset relative to time
494+ altitude : float, default 0
495+ distance above sea level in meters.
496+ pressure : int or float, optional, default 101325
497+ air pressure in Pascals.
498+ temperature : int or float, optional, default 12
499+ air temperature in degrees C.
500+ horizon : string, format +/-X:YY
501+ arc degrees:arc minutes from geometrical horizon for sunrise and
502+ sunset, e.g., horizon='+0:00' to use sun center crossing the
503+ geometrical horizon to define sunrise and sunset,
504+ horizon='-0:34' for when the sun's upper edge crosses the
505+ geometrical horizon
506+
507+ Returns
508+ -------
509+ pandas.DataFrame
510+ index is the same as input `time` argument
511+ columns are 'sunrise', 'sunset', and 'transit'
512+
513+ See also
514+ --------
515+ pyephem
516+ """
517+
518+ try :
519+ import ephem
520+ except ImportError :
521+ raise ImportError ('PyEphem must be installed' )
522+
523+ # times must be localized
524+ if not time .tz :
525+ raise ValueError ('rise_set_ephem: times must be localized' )
526+
527+ obs , sun = _ephem_setup (latitude , longitude , altitude ,
528+ pressure , temperature , horizon )
529+ # create lists of sunrise and sunset time localized to time.tz
530+ if next_or_previous .lower () == 'next' :
531+ rising = obs .next_rising
532+ setting = obs .next_setting
533+ transit = obs .next_transit
534+ elif next_or_previous .lower () == 'previous' :
535+ rising = obs .previous_rising
536+ setting = obs .previous_setting
537+ transit = obs .previous_transit
538+ else :
539+ raise ValueError ("next_or_previous must be either 'next' or" +
540+ " 'previous'" )
541+
542+ sunrise = []
543+ sunset = []
544+ trans = []
545+ for thetime in time :
546+ thetime = thetime .to_pydatetime ()
547+ # pyephem drops timezone when converting to its internal datetime
548+ # format, so handle timezone explicitly here
549+ obs .date = ephem .Date (thetime - thetime .utcoffset ())
550+ sunrise .append (_ephem_to_timezone (rising (sun ), time .tz ))
551+ sunset .append (_ephem_to_timezone (setting (sun ), time .tz ))
552+ trans .append (_ephem_to_timezone (transit (sun ), time .tz ))
553+
554+ return pd .DataFrame (index = time , data = {'sunrise' : sunrise ,
555+ 'sunset' : sunset ,
556+ 'transit' : trans })
557+
558+
456559def pyephem (time , latitude , longitude , altitude = 0 , pressure = 101325 ,
457- temperature = 12 ):
560+ temperature = 12 , horizon = '+0:00' ):
458561 """
459562 Calculate the solar position using the PyEphem package.
460563
@@ -463,17 +566,26 @@ def pyephem(time, latitude, longitude, altitude=0, pressure=101325,
463566 time : pandas.DatetimeIndex
464567 Localized or UTC.
465568 latitude : float
569+ positive is north of 0
466570 longitude : float
571+ positive is east of 0
467572 altitude : float, default 0
468- distance above sea level.
573+ distance above sea level in meters .
469574 pressure : int or float, optional, default 101325
470575 air pressure in Pascals.
471576 temperature : int or float, optional, default 12
472577 air temperature in degrees C.
578+ horizon : string, optional, default '+0:00'
579+ arc degrees:arc minutes from geometrical horizon for sunrise and
580+ sunset, e.g., horizon='+0:00' to use sun center crossing the
581+ geometrical horizon to define sunrise and sunset,
582+ horizon='-0:34' for when the sun's upper edge crosses the
583+ geometrical horizon
473584
474585 Returns
475586 -------
476- DataFrame
587+ pandas.DataFrame
588+ index is the same as input `time` argument
477589 The DataFrame will have the following columns:
478590 apparent_elevation, elevation,
479591 apparent_azimuth, azimuth,
@@ -499,7 +611,7 @@ def pyephem(time, latitude, longitude, altitude=0, pressure=101325,
499611 sun_coords = pd .DataFrame (index = time )
500612
501613 obs , sun = _ephem_setup (latitude , longitude , altitude ,
502- pressure , temperature )
614+ pressure , temperature , horizon )
503615
504616 # make and fill lists of the sun's altitude and azimuth
505617 # this is the pressure and temperature corrected apparent alt/az.
@@ -711,7 +823,8 @@ def ephemeris(time, latitude, longitude, pressure=101325, temperature=12):
711823
712824
713825def calc_time (lower_bound , upper_bound , latitude , longitude , attribute , value ,
714- altitude = 0 , pressure = 101325 , temperature = 12 , xtol = 1.0e-12 ):
826+ altitude = 0 , pressure = 101325 , temperature = 12 , horizon = '+0:00' ,
827+ xtol = 1.0e-12 ):
715828 """
716829 Calculate the time between lower_bound and upper_bound
717830 where the attribute is equal to value. Uses PyEphem for
@@ -736,6 +849,12 @@ def calc_time(lower_bound, upper_bound, latitude, longitude, attribute, value,
736849 atmospheric correction.
737850 temperature : int or float, optional, default 12
738851 Air temperature in degrees C.
852+ horizon : string, optional, default '+0:00'
853+ arc degrees:arc minutes from geometrical horizon for sunrise and
854+ sunset, e.g., horizon='+0:00' to use sun center crossing the
855+ geometrical horizon to define sunrise and sunset,
856+ horizon='-0:34' for when the sun's upper edge crosses the
857+ geometrical horizon
739858 xtol : float, optional, default 1.0e-12
740859 The allowed error in the result from value
741860
@@ -758,7 +877,7 @@ def calc_time(lower_bound, upper_bound, latitude, longitude, attribute, value,
758877 raise ImportError ('The calc_time function requires scipy' )
759878
760879 obs , sun = _ephem_setup (latitude , longitude , altitude ,
761- pressure , temperature )
880+ pressure , temperature , horizon )
762881
763882 def compute_attr (thetime , target , attr ):
764883 obs .date = thetime
0 commit comments