acgc.solar API documentation

# acgc.solar

Module for calculating solar position (zenith angle, elevation, azimuth)

The functions here are are vectorized and generally broadcast over xarray dimensions, making this program faster than PySolar. Calculations assume spherical Earth (not ellipsoidal). Results should be accurate to < 0.2 degree, which is less than radius of the sun. Other modules (e.g. pvlib) should be used for high-precision calculations.

View Source

      1#!/usr/local/bin/env python3
      2'''Module for calculating solar position (zenith angle, elevation, azimuth)
      3
      4The functions here are are vectorized and generally broadcast over xarray dimensions,
      5making this program faster than PySolar. Calculations assume spherical Earth (not ellipsoidal).
      6Results should be accurate to < 0.2 degree, which is less than radius of the sun.
      7Other modules (e.g. pvlib) should be used for high-precision calculations.
      8
      9'''
     10
     11from collections import namedtuple
     12import warnings
     13import numpy as np
     14import pandas as pd
     15
     16pi180 = np.pi / 180
     17
     18def insolation_toa( lat, lon, datetime, **kwargs ):
     19    '''Insolation at top of the atmosphere, accounting for solar zenith angle
     20
     21    Parameters
     22    ----------
     23    **kwargs passed to `solar_zenith_angle`
     24
     25    Returns
     26    -------
     27    Insolation : float
     28        radiation flux density accounting for solar zenith angle, W/m2
     29    '''
     30
     31    solar_pos = solar_position( datetime )
     32
     33    S = solar_constant(datetime, solar_pos=solar_pos)
     34    sza = solar_zenith_angle(lat, lon, datetime, **kwargs, solar_pos=solar_pos )
     35
     36    return S * np.cos(sza)
     37
     38def solar_azimuth_angle( lat, lon, datetime ):
     39    '''Solar azimuth angle (degrees) for a latitude, longitude, date and time
     40
     41    SAA is degrees clockwise from north.
     42
     43    Parameters
     44    ----------
     45    lat : float or ndarray
     46        latitude in degrees
     47    lon : float or ndarray
     48        longitudes in degrees
     49    datetime : datetime-like or str
     50        date and time. Include time zone or UTC will be assumed
     51
     52    Returns
     53    -------
     54    saa : float or ndarray
     55        solar azimuth angle in degrees (clockwise from north)
     56    '''
     57    # Convert to pandas Timestamp, if needed
     58    datetime = _to_timestamp(datetime)
     59
     60    # Subsolar point, latitude longitude, degrees
     61    solar_lat = solar_latitude( datetime )
     62    solar_lon = solar_longitude( datetime )
     63
     64    # Vector pointing toward sun
     65    x = np.cos( solar_lat * pi180 ) * np.sin( (solar_lon - lon) * pi180 )
     66    y = np.cos( lat*pi180 ) * np.sin( solar_lat*pi180 ) \
     67        - np.sin( lat*pi180 ) * np.cos( solar_lat*pi180 ) \
     68            * np.cos( (solar_lon - lon) * pi180 )
     69
     70    # Azimuth angle from north, degrees
     71    saa = np.arctan2( x, y ) / pi180
     72
     73    # Change range [-180,180] to [0,360]
     74    return np.mod( saa+360, 360 )
     75
     76def solar_elevation_angle( lat, lon, alt, datetime,
     77                       refraction=False, temperature=10., pressure=101325. ):
     78    '''Solar elevation angle (degrees) above the horizon
     79
     80    The altitude parameter should be the vertical distance
     81    above the surrounding terrain that defines the horizon,
     82    not necessarily the altitude above sea level or the altitude above ground level.
     83    For example, on a mountain peak that is 4000 m above sea level and
     84    1500 m above the surrounding plateau, the relevant altitude is 1500 m.
     85    For an observer on the plateau, the relevant altitude is 0 m.
     86
     87    See documentation for `solar_zenith_angle` and `horizon_zenith_angle`.
     88
     89    Parameters
     90    ----------
     91    lat : float or ndarray
     92        latitude in degrees
     93    lon : float or ndarray
     94        longitudes in degrees
     95    alt : float or ndarray
     96        altitude above surrounding terrain that defines the horizon, meters
     97    datetime : datetime-like or str
     98        date and time. Include time zone or UTC will be assumed
     99    refraction : bool, optional (default=False)
    100        specifies whether to account for atmospheric refraction
    101    temperature : float or ndarray, optional (default=10)
    102        surface atmospheric temperature (Celsius), only used for refraction calculation
    103    pressure : float or ndarray, optional (default=101325)
    104        surface atmospheric pressure (Pa), only used for refraction calculation
    105
    106    Returns
    107    -------
    108    sea : float or ndarray
    109        solar elevation angle in degrees at the designated locations and times
    110        If refraction=False, this is the true solar elevation angle
    111        If refraction=True, this is the apparent solar elevation angle
    112
    113    '''
    114
    115    if refraction and np.any(alt):
    116        warnings.warn( 'Atmospheric refraction is calculated for surface conditions, '
    117                    + 'but an altitude above the surface was specified',
    118                     category=UserWarning,
    119                     stacklevel=2 )
    120
    121    sea = horizon_zenith_angle( alt ) \
    122         - solar_zenith_angle( lat, lon, datetime, refraction, temperature, pressure )
    123
    124    return sea
    125
    126def solar_zenith_angle( lat, lon, datetime,
    127                        refraction=False, temperature=10., pressure=101325.,
    128                        solar_pos=None ):
    129    '''Solar zenith angle (degrees) for a given latitude, longitude, date and time.
    130
    131    Accounts for equation of time and (optionally) for atmospheric refraction.
    132    Altitude of the observer is not accounted for, which can be important when the sun
    133    is near the horizon.
    134
    135    Results are accurate to tenths of a degree, except where altitude is important
    136    (< 20 degrees solar elevation)
    137
    138    Parameters
    139    ----------
    140    lat : float or ndarray
    141        latitude in degrees
    142    lon : float or ndarray
    143        longitudes in degrees
    144    datetime : datetime-like or str
    145        date and time. Include time zone or UTC will be assumed
    146    refraction : bool, optional (default=False)
    147        specifies whether to account for atmospheric refraction
    148    temperature : float or ndarray, optional (default=10)
    149        surface atmospheric temperature (Celsius), only used for refraction calculation
    150    pressure : float or ndarray, optional (default=101325)
    151        surface atmospheric pressure (Pa), only used for refraction calculation
    152
    153    Returns
    154    -------
    155    sza : float or ndarray
    156        solar zenith angle in degrees at the designated locations and times
    157        If refraction=False, this is the true solar zenith angle
    158        If refraction=True, this is the apparent solar zenith angle
    159    '''
    160    # Convert to pandas Timestamp, if needed
    161    datetime = _to_timestamp(datetime)
    162
    163    # Solar declination, degrees
    164    if solar_pos is None:
    165        dec = solar_declination( datetime )
    166    else:
    167        dec = solar_pos.declination
    168
    169    # Hour angle, degrees
    170    Ha = solar_hour_angle( lon, datetime, solar_pos )
    171
    172    # True solar zenith angle, radians
    173    sza = np.arccos( np.sin(lat*pi180) * np.sin(dec*pi180) + \
    174          np.cos(lat*pi180) * np.cos(dec*pi180) * np.cos(Ha*pi180) )
    175
    176    # Convert radians -> degrees
    177    sza /= pi180
    178
    179    if refraction:
    180        # Subtract refraction angle (degrees) from zenith angle.
    181        # SZA is always smaller due to refraction.
    182        sza -= refraction_angle( 90-sza, pressure, temperature )
    183
    184    return sza
    185
    186def sunrise_time( *args, **kwargs ):
    187    '''Compute sunrise time
    188
    189    See `sun_times` for Parameters.'''
    190    result = sun_times( *args, **kwargs )
    191    return result[0]
    192
    193def sunset_time( *args, **kwargs ):
    194    '''Compute sunset time
    195
    196    See `sun_times` for Parameters.'''
    197    result = sun_times( *args, **kwargs )
    198    return result[1]
    199
    200def day_length( *args, **kwargs ):
    201    '''Compute length of daylight
    202
    203    See `sun_times` for Parameters.'''
    204    result = sun_times( *args, **kwargs )
    205    return result[2]
    206
    207def solar_noon( *args, **kwargs ):
    208    '''Compute time of solar noon (meridian transit)
    209
    210    See `sun_times` for Parameters.'''
    211    result = sun_times( *args, **kwargs )
    212    return result[3]
    213
    214def solar_constant( datetime, solar_pos=None ):
    215    '''Compute solar constant for specific date or dates
    216
    217    Parameters
    218    ----------
    219    datetime : datetime-like
    220    solar_pos : tuple (default=None)
    221        solar position parameters from a prior call to `solar_position`
    222
    223    Returns
    224    -------
    225    S : float
    226        Solar direct beam radiation flux density, W/m2
    227    '''
    228    if solar_pos is None:
    229        solar_pos = solar_position( datetime )
    230    S = 1361/solar_pos.distance**2
    231
    232    return S
    233
    234def solar_declination( datetime, fast=False ):
    235    '''Calculate solar declination (degrees) for specified date
    236
    237    Implements Eq. 9.68-9.72 from M.Z. Jacobson, Fundamentals of Atmospheric Modeling
    238
    239    Parameters
    240    ----------
    241    datetime : datetime-like or str
    242        date and time. Include time zone or UTC will be assumed
    243    fast : bool (default=False)
    244        Specifies using a faster but less accurate calculation
    245
    246    Returns
    247    -------
    248    dec : float
    249        solar declination in degrees at the specified date
    250    '''
    251    # Convert to pandas Timestamp, if needed
    252    datetime = _to_timestamp(datetime)
    253
    254    # Select the accurate or fast calculation
    255    accurate = not fast
    256
    257    if accurate:
    258
    259        # Solar declination, degrees
    260        dec, junk, junk, junk = solar_position( datetime )
    261
    262    else:
    263        # Number of days since beginning of 2000
    264        NJD = datetime - np.datetime64('2000-01-01')
    265        try:
    266            NJD = NJD.dt.days
    267        except AttributeError:
    268            NJD = NJD.days
    269
    270        # Obliquity, degrees
    271        ob = 23.439 - 4e-7 * NJD
    272
    273        # Parameters for ecliptic, degrees
    274        gm = 357.528 + 0.9856003 * NJD
    275        lm = 280.460 + 0.9856474 * NJD
    276
    277        # Ecliptic longitude of sun, degrees
    278        ec = lm + 1.915 * np.sin( gm * pi180 ) + 0.020 * np.sin( 2 * gm * pi180 )
    279
    280        #Solar declination, degrees
    281        dec = np.arcsin( np.sin( ob * pi180 ) * np.sin( ec * pi180 ) ) / pi180
    282
    283    return dec
    284
    285def solar_latitude( datetime ):
    286    '''Latitude of the subsolar point
    287
    288    Parameters
    289    ----------
    290    datetimeUTC : datetime-like or str
    291        date and time. Include time zone or UTC will be assumed
    292
    293    Returns
    294    -------
    295    latitude : float
    296        degrees of latitude
    297    '''
    298    return solar_declination( datetime )
    299
    300def solar_longitude( datetime, solar_pos=None ):
    301    '''Longitude of the subsolar point, degrees
    302
    303    Parameters
    304    ----------
    305    datetimeUTC : datetime-like or str
    306        date and time. Include time zone or UTC will be assumed
    307
    308    Returns
    309    -------
    310    longitude : float
    311        degrees of longitude
    312    '''
    313    # Convert to pandas Timestamp, if needed
    314    datetimeUTC = _to_timestamp_utc(datetime)
    315
    316    # Longitude of subsolar point, degrees
    317    # Equation of time will be added below
    318    try:
    319        # Treat as xarray.DataArray or pandas.Series
    320        solar_lon = - 15 * ( datetimeUTC.dt.hour +
    321                          datetimeUTC.dt.minute / 60 +
    322                          datetimeUTC.dt.second / 3600 - 12 )
    323    except AttributeError:
    324        solar_lon = - 15 * ( datetimeUTC.hour +
    325                          datetimeUTC.minute / 60 +
    326                          datetimeUTC.second / 3600 - 12 )
    327
    328    # Add equation of time to the solar longitude, degrees
    329    if solar_pos is None:
    330        eot = equation_of_time( datetimeUTC, degrees=True )
    331    else:
    332        eot = solar_pos.equation_of_time
    333    solar_lon -= eot
    334
    335    return solar_lon
    336
    337def solar_hour_angle( lon, datetime, solar_pos=None ):
    338    '''Solar hour angle (degrees) for specified longitude, date and time
    339
    340    Hour angle is the angular displacement of the sun from the local meridian.
    341    It is zero at local noon, negative in the morning, and positive is afternoon.
    342
    343    Parameters
    344    ----------
    345    lon : float
    346        longitude in degrees east
    347    datetimeUTC : datetime-like or str
    348        date and time. Include time zone or UTC will be assumed
    349
    350    Returns
    351    -------
    352    ha : float
    353        hour angle in degrees at the specified location and time
    354    '''
    355
    356    # Subsolar longitude, degrees
    357    solar_lon = solar_longitude(datetime, solar_pos)
    358
    359    # Hour angle, degrees
    360    Ha = lon - solar_lon
    361
    362    return Ha
    363
    364def equation_of_time( datetime, degrees=False, fast=False ):
    365    '''Equation of time for specified date
    366
    367    Accounts for the solar day being slightly different from 24 hours
    368
    369    Parameters
    370    ----------
    371    datetime : datetime-like or str
    372        date and time. Include time zone or UTC will be assumed
    373    degrees : bool (default=False)
    374        If True, then return value in compass degrees
    375        If False, then return value in minutes of an hour
    376    fast : bool (default=False)
    377        specifies whether to use a faster, but less accurate calculation
    378
    379    Returns
    380    -------
    381    eot : float
    382        equation of time on the specified date, degrees or minutes
    383    '''
    384    # Convert to pandas Timestamp, if needed
    385    datetime = _to_timestamp(datetime)
    386
    387    # Determine whether to use the fast or accurate calculation
    388    accurate = not fast
    389
    390    if accurate:
    391
    392        # Equation of time, minutes
    393        junk, junk, eot, junk = solar_position( datetime )
    394
    395    else:
    396        # Implements the "alternative equation" from Wikipedia, derived from
    397        # https://web.archive.org/web/20120323231813/http://www.green-life-innovators.org/tiki-index.php?page=The%2BLatitude%2Band%2BLongitude%2Bof%2Bthe%2BSun%2Bby%2BDavid%2BWilliams
    398        # Results checked against NOAA solar calculator and agree within 10 seconds.
    399        # Note: Leap years are not accounted for.
    400
    401        # Equation of time, accounts for the solar day differing slightly from 24 hr
    402        try:
    403            doy = datetime.dt.dayofyear
    404        except AttributeError:
    405            doy = datetime.dayofyear
    406        W = 360 / 365.24
    407        A = W * (doy+10)
    408        B = A + 1.914 * np.sin( W * (doy-2) * pi180 )
    409        C = ( A - np.arctan2( np.tan(B*pi180), np.cos(23.44*pi180) ) / pi180 ) / 180
    410
    411        # Equation of time in minutes of an hour (1440 minutes per day)
    412        eot = 720 * ( C - np.round(C) )
    413
    414    # Equation of time, minutes -> degrees (360 degrees per day)
    415    if degrees:
    416        eot = eot / 60 * 360 / 24
    417
    418    return eot
    419
    420def solar_position( datetime ):
    421    '''Compute position of sun (declination, right ascension, equation of time, distance)) on specified date
    422
    423    Calculations follow the NOAA solar calculator spreadsheet
    424    Applicable to years 1900-2100.
    425
    426    Parameters
    427    ----------
    428    date : datetime-like or str
    429        date and time. Include time zone or UTC will be assumed
    430
    431    Returns
    432    -------
    433    declination : float
    434        position of the sun relative to Earth's equatorial plane, degrees
    435    right_ascension : float
    436        position of the sun along Earth's equatorial plane, degrees
    437        relative to the sun's position on the vernal equinox
    438    equation_of_time : float
    439        equation of time (minutes) between mean solar time and true solar time
    440        Divide by 4 minutes per degree to obtain equation of time in degrees
    441    distance : float
    442        Earth-sun distance in AU (1 AU = 1.495978707e11 m)
    443    '''
    444    # Ensure time is Timestamp in UTC
    445    datetimeUTC, tz_in = _to_timestamp_utc(datetime)
    446
    447    # Raise warning if any dates are outside date range
    448    # recommended for orbital parameters used here
    449    if np.logical_or( np.any( datetimeUTC < np.datetime64('1900-01-01') ),
    450                      np.any( datetimeUTC > np.datetime64('2100-01-01') ) ):
    451        warnings.warn('Solar position accuracy declines for dates outside 1900-2100', \
    452                      RuntimeWarning )
    453
    454    # Number of days since 1 Jan 2000
    455    NJD = datetimeUTC - np.datetime64('2000-01-01')
    456    try:
    457        NJD = NJD.dt.days
    458    except AttributeError:
    459        NJD = NJD.days
    460
    461    # Julian day (since 12:00 1 Jan 4713 BCE)
    462    NJD += 2451544.50
    463
    464    # Julian century
    465    JC = (NJD-2451545)/36525
    466
    467    # Earth orbital eccentricity, unitless
    468    ec = 0.016708634 - JC*( 0.000042037 + 0.0000001267*JC )
    469
    470    # Earth mean orbital obliquity, degree
    471    mean_ob = 23 + ( 26 + ( (21.448
    472                             - JC * (46.815
    473                                    + JC * (0.00059 - JC * 0.001813) ) ) )/60 )/60
    474
    475    # Earth true orbital obliquity, corrected for nutation, degree
    476    ob = mean_ob + 0.00256 * np.cos( (125.04 - 1934.136*JC ) * pi180 )
    477
    478    # Sun Mean ecliptic longitude, degree
    479    mean_ec_lon = np.mod( 280.46646 + JC*( 36000.76983 + JC*0.0003032 ), 360 )
    480
    481    # Sun Mean anomaly, degree
    482    mean_anom = 357.52911 + JC*( 35999.05029 - 0.0001537*JC )
    483
    484    # Sun Equation of center, degree
    485    eq_center = np.sin(mean_anom*pi180) * (1.914602 - JC*( 0.004817 + 0.000014*JC )) \
    486                    + np.sin(2*mean_anom*pi180) * (0.019993 - 0.000101*JC) \
    487                    + np.sin(3*mean_anom*pi180) * 0.000289
    488
    489    # Sun True ecliptic longitude, degrees
    490    true_ec_lon = mean_ec_lon + eq_center
    491
    492    # Sun True anomaly, degree
    493    true_anom = mean_anom + eq_center
    494
    495    # Earth-Sun distance, AU
    496    distance = (1.000001018 * (1-ec**2) ) / (1 + ec * np.cos( true_anom * pi180 ))
    497
    498    # Sun Apparent ecliptic longitude, corrected for nutation, degrees
    499    ec_lon = true_ec_lon - 0.00569 - 0.00478 * np.sin( (125.04 - 1934.136*JC ) * pi180)
    500
    501    # Sun Right ascension, deg
    502    right_ascension = np.arctan2( np.cos(ob*pi180) * np.sin(ec_lon*pi180),
    503                                  np.cos(ec_lon*pi180) ) / pi180
    504
    505    # Sun Declination, deg
    506    declination = np.arcsin( np.sin(ob*pi180) * np.sin(ec_lon*pi180) ) / pi180
    507
    508    # var y
    509    vary = np.tan( ob/2 * pi180 )**2
    510
    511    # Equation of time, minutes
    512    eot = vary * np.sin( 2 * mean_ec_lon * pi180) \
    513        - 2 * ec * np.sin( mean_anom * pi180 ) \
    514        + 4 * ec * vary * np.sin( mean_anom * pi180 ) * np.cos( 2 * mean_ec_lon * pi180) \
    515        - 0.5 * vary**2 * np.sin( 4 * mean_ec_lon * pi180) \
    516        - 1.25 * ec**2 * np.sin( 2 * mean_anom * pi180)
    517    eot = eot * 4 / pi180
    518
    519    # Define named tuple to hold result
    520    solar_pos_tuple = namedtuple('SolarPosition',
    521                        'declination right_ascension equation_of_time distance datetimeUTC')
    522    result = solar_pos_tuple(declination, right_ascension, eot, distance, datetimeUTC)
    523
    524    return result
    525
    526def sun_times( lat, lon, datetime, tz_out=None, sza_sunrise=90.833, fast=False ):
    527    '''Compute times of sunrise, sunset, solar noon, and day length
    528
    529    Common options for solar zenith angle at sunrise
    530    1. 90.833 for first edge of sun rising, typical (0.567°) refraction (default)
    531    2. 90.267 for first edge of sun rising, no refraction
    532    3. 90 degrees for center of sun rising, no refraction
    533
    534    Parameters
    535    ----------
    536    lat : float or ndarray
    537        latitude in degrees
    538    lon : float or ndarray
    539        longitudes in degrees
    540    datetime : datetime-like or str
    541        datetime, provide a time zone or UTC will be assumed
    542    tz_out : str, pytz.timezone, datetime.tzinfo or None (default=None)
    543        timezone to be used for output times.
    544        If None is provided, then result will be in same time zone as input or UTC
    545    sza_sunrise : float (default=90.833)
    546        Solar zenith angle at which sunrise and sunset are calculated, degrees
    547    fast : bool (default=False)
    548        Select a faster but less accurate calculation
    549
    550    Returns
    551    -------
    552    sunrise : pandas.DatetimeIndex
    553        sunrise time
    554    sunset : pandas.DatetimeIndex
    555        sunset time
    556    day_length : pandas.Timedelta
    557        duration of daylight
    558    solar_noon : pandas.DatetimeIndex
    559        time of meridian transit
    560    '''
    561    # Convert to pandas Timestamp in UTC, if needed
    562    datetimeUTC, tz_in = _to_timestamp_utc(datetime)
    563
    564    # If no output timezone is specified, use the input time zone
    565    if tz_out is None:
    566        tz_out = tz_in
    567
    568    # Select fast or accurate calculation
    569    accurate = not fast
    570
    571    # Solar declination (degrees) and equation of time (minutes)
    572    if accurate:
    573        dec, junk, eot, junk = solar_position( datetimeUTC )
    574    else:
    575        dec = solar_declination( datetimeUTC )
    576        eot = equation_of_time( datetimeUTC )
    577
    578    # Sunrise hour angle, degree
    579    # Degrees east of the local meridian where sun rises
    580    ha_sunrise = np.arccos( np.cos(sza_sunrise*pi180) /
    581                           (np.cos(lat*pi180) * np.cos(dec*pi180))
    582                           - np.tan(lat*pi180)*np.tan(dec*pi180) ) / pi180
    583
    584    # Solar noon, local standard time, day fraction
    585    solar_noon = (720 - 4*lon - eot ) / 1440
    586
    587    # Sunrise and sunset, local standard time, day fraction
    588    t_sunrise = solar_noon - 4 * ha_sunrise / 1440
    589    t_sunset  = solar_noon + 4 * ha_sunrise / 1440
    590
    591    # Date portion only
    592    try:
    593        # Convert to UTC
    594        dateUTC = datetimeUTC.tz_convert('UTC').normalize()
    595    except TypeError:
    596        # Assume already in UTC
    597        dateUTC = datetimeUTC.tz_localize('UTC').normalize()
    598
    599    # Convert day fraction -> date time
    600    solar_noon = dateUTC + solar_noon * pd.Timedelta( 1, 'day' )
    601    t_sunrise  = dateUTC + t_sunrise  * pd.Timedelta( 1, 'day' )
    602    t_sunset   = dateUTC + t_sunset   * pd.Timedelta( 1, 'day' )
    603
    604    # Localize to input timezone
    605    try:
    606        solar_noon = solar_noon.dt.tz_convert(tz_out)
    607        t_sunrise  = t_sunrise.dt.tz_convert(tz_out)
    608        t_sunset   = t_sunset.dt.tz_convert(tz_out)
    609    except AttributeError:
    610        solar_noon = solar_noon.tz_convert(tz_out)
    611        t_sunrise  = t_sunrise.tz_convert(tz_out)
    612        t_sunset   = t_sunset.tz_convert(tz_out)
    613
    614    # Sunlight duration, minutes
    615    day_length = 8 * ha_sunrise * pd.Timedelta(1, 'minute')
    616
    617    return t_sunrise, t_sunset, day_length, solar_noon
    618
    619def horizon_zenith_angle( alt ):
    620    '''Angle from the zenith to the horizon
    621
    622    The horizon is the locii of points where a line from the
    623    observation location to the ellipsoid is tangent to the ellipsoid surface.
    624
    625    The altitude parameter should be the vertical distance
    626    above the surrounding terrain that defines the horizon,
    627    not necessarily the altitude above sea level or the altitude above ground level.
    628    For example, on a mountain peak that is 4000 m above sea level and
    629    1500 m above the surrounding plateau, the relevant altitude is 1500 m.
    630    For an observer on the plateau, the relevant altitude is 0 m.
    631
    632    The implementation below assumes a spherical Earth.
    633    Results using the WGS84 ellipsoid (see commented code below)
    634    differ from the spherical case by << 1°. Terrain,
    635    which is neglected here, has a larger effect on the horizon
    636    location, so the simpler spherical calculation is appropriate.
    637
    638    Parameters
    639    ----------
    640    lat : float or ndarray
    641        latitude in degrees
    642    alt : float or ndarray
    643        altitude above surrounding terrain that defines the horizon, meters
    644
    645    Returns
    646    -------
    647    hza : float or ndarray
    648        horizon zenith angle in degrees
    649    '''
    650
    651    # WGS84 ellipsoid parameters
    652    # semi-major radius, m
    653    r_earth = 6378137.0
    654    # ellipsoidal flattening, unitless
    655    f = 1/298.257223563
    656
    657    # Horizon zenith angle, degrees (spherical earth)
    658    hza = 180 - np.arcsin( r_earth / ( r_earth + alt ) ) / pi180
    659
    660    ## Ellipsoidal Earth
    661    # # Eccentricity of ellipsoid
    662    # ecc = f * (2-f)
    663    # # Local (i.e. prime vertical) radius of curvature at latitude
    664    # N = r_earth / np.sqrt( 1 - ecc**2 * np.sin(lat*pi180)**2 )
    665    # # Horizon zenith angle, degrees
    666    # hza = 180 - np.arcsin( N / (N+alt) ) / pi180
    667
    668    return hza
    669
    670def refraction_angle( true_elevation_angle, pressure=101325., temperature_celsius=10. ):
    671    '''Atmospheric refraction angle for light passing through Earth's atmosphere
    672
    673    The apparent locations in the sky of objects outsides Earth's atmosphere
    674    differs from their true locations due to atmospheric refraction.
    675    (e.g. sun and moon when they rise and set)
    676    The apparent elevation of an object above the horizon is
    677    apparent elevation angle = (true elevation angle) + (refraction angle)
    678
    679    The equations here are from Saemundsson/Bennett, whose calculations use
    680    a typical vertical profile of atmospheric density (i.e. temperature and pressure).
    681    The profiles can be rescaled to a particular surface temperature and pressure
    682    to approximately account for varying atmospheric conditions.
    683    Accurate refraction calculations should use fully specified vertical profile
    684    of temperature and pressure, which cannot be done here.
    685
    686    Parameters
    687    ----------
    688    true_elevation_angle : float
    689        degrees above horizon of sun or other object
    690    pressure : float (default=101325)
    691        surface atmospheric pressure (Pa)
    692    temperature_celsius : float (default=10)
    693        surface atmospheric temperature (C)
    694
    695    Returns
    696    -------
    697    angle : float
    698        refraction angle in degrees. Value is zero when apparent elevation is below horizon
    699    '''
    700    # Refraction angle, arcminutes
    701    R = 1.02 / np.tan( ( true_elevation_angle + 10.3 / (true_elevation_angle + 5.11) ) * pi180 )
    702    # Account for temperature and pressure, arcminutes
    703    R = R * pressure / 101325 * 283 / ( 273 + temperature_celsius )
    704    # Convert arcminutes -> degrees
    705    R /= 60
    706
    707    # Result must be positive
    708    R = np.maximum(R,0)
    709
    710    # Refraction defined only when the apparent elevation angle is positive
    711    # Set refraction to zero when the apparent elevation is below horizon
    712    refraction_angle = np.where( true_elevation_angle + R <= 0, 0, R)
    713
    714    return refraction_angle
    715
    716def _to_timestamp(time_in):
    717    '''Convert input to Pandas Timestamp or Series of datetime64
    718
    719    Parameters
    720    ----------
    721    time_in : datetime-like or array
    722        time to be converted
    723
    724    Returns
    725    -------
    726    time_out : pandas.Timestamp or pandas.Series of datetime64
    727    '''
    728    if hasattr(time_in,'dt'):
    729        time_out = time_in
    730    # elif isinstance(time_in, pd.DatetimeIndex ):
    731    #     # Unnecessary; DatetimeIndex will work fine in the else cases
    732    #     time_out = pd.Series(time_in)
    733    #     tz = time_out.dt.tz
    734    else:
    735        try:
    736            # Convert list of times
    737            time_out = pd.Series(pd.DatetimeIndex(time_in))
    738        except TypeError:
    739            # Single datetime or str
    740            time_out = pd.Timestamp(time_in)
    741
    742    return time_out
    743
    744def _to_timestamp_utc( datetime_in ):
    745
    746    # Ensure input is a timestamp
    747    datetime_in = _to_timestamp( datetime_in )
    748
    749    # Convert to UTC, then strip timezone
    750    try:
    751        try:
    752            tz_in = datetime_in.dt.tz
    753            datetimeUTC = datetime_in.dt.tz_convert('UTC').tz.tz_localize(None)
    754        except AttributeError:
    755            tz_in = datetime_in.tzinfo
    756            datetimeUTC = datetime_in.tz_convert('UTC').tz_localize(None)
    757    except TypeError:
    758        # No timezone info, so assume it is already UTC
    759        datetimeUTC = datetime_in
    760        tz_in = None
    761
    762    return datetimeUTC, tz_in
    763
    764# Aliases for functions
    765sza = solar_zenith_angle
    766saa = solar_azimuth_angle
    767sea = solar_elevation_angle
    768# Additional aliases for backwards compatibility
    769equationOfTime = equation_of_time
    770solarDeclination = solar_declination

pi180 = 0.017453292519943295
def insolation_toa(lat, lon, datetime, **kwargs): View Source

    19def insolation_toa( lat, lon, datetime, **kwargs ):
    20    '''Insolation at top of the atmosphere, accounting for solar zenith angle
    21
    22    Parameters
    23    ----------
    24    **kwargs passed to `solar_zenith_angle`
    25
    26    Returns
    27    -------
    28    Insolation : float
    29        radiation flux density accounting for solar zenith angle, W/m2
    30    '''
    31
    32    solar_pos = solar_position( datetime )
    33
    34    S = solar_constant(datetime, solar_pos=solar_pos)
    35    sza = solar_zenith_angle(lat, lon, datetime, **kwargs, solar_pos=solar_pos )
    36
    37    return S * np.cos(sza)

Insolation at top of the atmosphere, accounting for solar zenith angle

###### Parameters

- **kwargs passed to `solar_zenith_angle`

###### Returns

- Insolation (float): radiation flux density accounting for solar zenith angle, W/m2

def solar_azimuth_angle(lat, lon, datetime): View Source

    39def solar_azimuth_angle( lat, lon, datetime ):
    40    '''Solar azimuth angle (degrees) for a latitude, longitude, date and time
    41
    42    SAA is degrees clockwise from north.
    43
    44    Parameters
    45    ----------
    46    lat : float or ndarray
    47        latitude in degrees
    48    lon : float or ndarray
    49        longitudes in degrees
    50    datetime : datetime-like or str
    51        date and time. Include time zone or UTC will be assumed
    52
    53    Returns
    54    -------
    55    saa : float or ndarray
    56        solar azimuth angle in degrees (clockwise from north)
    57    '''
    58    # Convert to pandas Timestamp, if needed
    59    datetime = _to_timestamp(datetime)
    60
    61    # Subsolar point, latitude longitude, degrees
    62    solar_lat = solar_latitude( datetime )
    63    solar_lon = solar_longitude( datetime )
    64
    65    # Vector pointing toward sun
    66    x = np.cos( solar_lat * pi180 ) * np.sin( (solar_lon - lon) * pi180 )
    67    y = np.cos( lat*pi180 ) * np.sin( solar_lat*pi180 ) \
    68        - np.sin( lat*pi180 ) * np.cos( solar_lat*pi180 ) \
    69            * np.cos( (solar_lon - lon) * pi180 )
    70
    71    # Azimuth angle from north, degrees
    72    saa = np.arctan2( x, y ) / pi180
    73
    74    # Change range [-180,180] to [0,360]
    75    return np.mod( saa+360, 360 )

Solar azimuth angle (degrees) for a latitude, longitude, date and time

SAA is degrees clockwise from north.

###### Parameters

- lat (float or ndarray): latitude in degrees
- lon (float or ndarray): longitudes in degrees
- datetime (datetime-like or str): date and time. Include time zone or UTC will be assumed

###### Returns

- saa (float or ndarray): solar azimuth angle in degrees (clockwise from north)

def solar_elevation_angle( lat, lon, alt, datetime, refraction=False, temperature=10.0, pressure=101325.0): View Source

     77def solar_elevation_angle( lat, lon, alt, datetime,
     78                       refraction=False, temperature=10., pressure=101325. ):
     79    '''Solar elevation angle (degrees) above the horizon
     80
     81    The altitude parameter should be the vertical distance
     82    above the surrounding terrain that defines the horizon,
     83    not necessarily the altitude above sea level or the altitude above ground level.
     84    For example, on a mountain peak that is 4000 m above sea level and
     85    1500 m above the surrounding plateau, the relevant altitude is 1500 m.
     86    For an observer on the plateau, the relevant altitude is 0 m.
     87
     88    See documentation for `solar_zenith_angle` and `horizon_zenith_angle`.
     89
     90    Parameters
     91    ----------
     92    lat : float or ndarray
     93        latitude in degrees
     94    lon : float or ndarray
     95        longitudes in degrees
     96    alt : float or ndarray
     97        altitude above surrounding terrain that defines the horizon, meters
     98    datetime : datetime-like or str
     99        date and time. Include time zone or UTC will be assumed
    100    refraction : bool, optional (default=False)
    101        specifies whether to account for atmospheric refraction
    102    temperature : float or ndarray, optional (default=10)
    103        surface atmospheric temperature (Celsius), only used for refraction calculation
    104    pressure : float or ndarray, optional (default=101325)
    105        surface atmospheric pressure (Pa), only used for refraction calculation
    106
    107    Returns
    108    -------
    109    sea : float or ndarray
    110        solar elevation angle in degrees at the designated locations and times
    111        If refraction=False, this is the true solar elevation angle
    112        If refraction=True, this is the apparent solar elevation angle
    113
    114    '''
    115
    116    if refraction and np.any(alt):
    117        warnings.warn( 'Atmospheric refraction is calculated for surface conditions, '
    118                    + 'but an altitude above the surface was specified',
    119                     category=UserWarning,
    120                     stacklevel=2 )
    121
    122    sea = horizon_zenith_angle( alt ) \
    123         - solar_zenith_angle( lat, lon, datetime, refraction, temperature, pressure )
    124
    125    return sea

Solar elevation angle (degrees) above the horizon

The altitude parameter should be the vertical distance above the surrounding terrain that defines the horizon, not necessarily the altitude above sea level or the altitude above ground level. For example, on a mountain peak that is 4000 m above sea level and 1500 m above the surrounding plateau, the relevant altitude is 1500 m. For an observer on the plateau, the relevant altitude is 0 m.

See documentation for `solar_zenith_angle` and `horizon_zenith_angle`.

###### Parameters

- lat (float or ndarray): latitude in degrees
- lon (float or ndarray): longitudes in degrees
- alt (float or ndarray): altitude above surrounding terrain that defines the horizon, meters
- datetime (datetime-like or str): date and time. Include time zone or UTC will be assumed
- refraction (bool, optional (default=False)): specifies whether to account for atmospheric refraction
- temperature (float or ndarray, optional (default=10)): surface atmospheric temperature (Celsius), only used for refraction calculation
- pressure (float or ndarray, optional (default=101325)): surface atmospheric pressure (Pa), only used for refraction calculation

###### Returns

- sea (float or ndarray): solar elevation angle in degrees at the designated locations and times If refraction=False, this is the true solar elevation angle If refraction=True, this is the apparent solar elevation angle

def solar_zenith_angle( lat, lon, datetime, refraction=False, temperature=10.0, pressure=101325.0, solar_pos=None): View Source

    127def solar_zenith_angle( lat, lon, datetime,
    128                        refraction=False, temperature=10., pressure=101325.,
    129                        solar_pos=None ):
    130    '''Solar zenith angle (degrees) for a given latitude, longitude, date and time.
    131
    132    Accounts for equation of time and (optionally) for atmospheric refraction.
    133    Altitude of the observer is not accounted for, which can be important when the sun
    134    is near the horizon.
    135
    136    Results are accurate to tenths of a degree, except where altitude is important
    137    (< 20 degrees solar elevation)
    138
    139    Parameters
    140    ----------
    141    lat : float or ndarray
    142        latitude in degrees
    143    lon : float or ndarray
    144        longitudes in degrees
    145    datetime : datetime-like or str
    146        date and time. Include time zone or UTC will be assumed
    147    refraction : bool, optional (default=False)
    148        specifies whether to account for atmospheric refraction
    149    temperature : float or ndarray, optional (default=10)
    150        surface atmospheric temperature (Celsius), only used for refraction calculation
    151    pressure : float or ndarray, optional (default=101325)
    152        surface atmospheric pressure (Pa), only used for refraction calculation
    153
    154    Returns
    155    -------
    156    sza : float or ndarray
    157        solar zenith angle in degrees at the designated locations and times
    158        If refraction=False, this is the true solar zenith angle
    159        If refraction=True, this is the apparent solar zenith angle
    160    '''
    161    # Convert to pandas Timestamp, if needed
    162    datetime = _to_timestamp(datetime)
    163
    164    # Solar declination, degrees
    165    if solar_pos is None:
    166        dec = solar_declination( datetime )
    167    else:
    168        dec = solar_pos.declination
    169
    170    # Hour angle, degrees
    171    Ha = solar_hour_angle( lon, datetime, solar_pos )
    172
    173    # True solar zenith angle, radians
    174    sza = np.arccos( np.sin(lat*pi180) * np.sin(dec*pi180) + \
    175          np.cos(lat*pi180) * np.cos(dec*pi180) * np.cos(Ha*pi180) )
    176
    177    # Convert radians -> degrees
    178    sza /= pi180
    179
    180    if refraction:
    181        # Subtract refraction angle (degrees) from zenith angle.
    182        # SZA is always smaller due to refraction.
    183        sza -= refraction_angle( 90-sza, pressure, temperature )
    184
    185    return sza

Solar zenith angle (degrees) for a given latitude, longitude, date and time.

Accounts for equation of time and (optionally) for atmospheric refraction. Altitude of the observer is not accounted for, which can be important when the sun is near the horizon.

Results are accurate to tenths of a degree, except where altitude is important (< 20 degrees solar elevation)

###### Parameters

- lat (float or ndarray): latitude in degrees
- lon (float or ndarray): longitudes in degrees
- datetime (datetime-like or str): date and time. Include time zone or UTC will be assumed
- refraction (bool, optional (default=False)): specifies whether to account for atmospheric refraction
- temperature (float or ndarray, optional (default=10)): surface atmospheric temperature (Celsius), only used for refraction calculation
- pressure (float or ndarray, optional (default=101325)): surface atmospheric pressure (Pa), only used for refraction calculation

###### Returns

- sza (float or ndarray): solar zenith angle in degrees at the designated locations and times If refraction=False, this is the true solar zenith angle If refraction=True, this is the apparent solar zenith angle

def sunrise_time(*args, **kwargs): View Source

    187def sunrise_time( *args, **kwargs ):
    188    '''Compute sunrise time
    189
    190    See `sun_times` for Parameters.'''
    191    result = sun_times( *args, **kwargs )
    192    return result[0]

Compute sunrise time

See `sun_times` for Parameters.

def sunset_time(*args, **kwargs): View Source

    194def sunset_time( *args, **kwargs ):
    195    '''Compute sunset time
    196
    197    See `sun_times` for Parameters.'''
    198    result = sun_times( *args, **kwargs )
    199    return result[1]

Compute sunset time

See `sun_times` for Parameters.

def day_length(*args, **kwargs): View Source

    201def day_length( *args, **kwargs ):
    202    '''Compute length of daylight
    203
    204    See `sun_times` for Parameters.'''
    205    result = sun_times( *args, **kwargs )
    206    return result[2]

Compute length of daylight

See `sun_times` for Parameters.

def solar_noon(*args, **kwargs): View Source

    208def solar_noon( *args, **kwargs ):
    209    '''Compute time of solar noon (meridian transit)
    210
    211    See `sun_times` for Parameters.'''
    212    result = sun_times( *args, **kwargs )
    213    return result[3]

Compute time of solar noon (meridian transit)

See `sun_times` for Parameters.

def solar_constant(datetime, solar_pos=None): View Source

    215def solar_constant( datetime, solar_pos=None ):
    216    '''Compute solar constant for specific date or dates
    217
    218    Parameters
    219    ----------
    220    datetime : datetime-like
    221    solar_pos : tuple (default=None)
    222        solar position parameters from a prior call to `solar_position`
    223
    224    Returns
    225    -------
    226    S : float
    227        Solar direct beam radiation flux density, W/m2
    228    '''
    229    if solar_pos is None:
    230        solar_pos = solar_position( datetime )
    231    S = 1361/solar_pos.distance**2
    232
    233    return S

Compute solar constant for specific date or dates

###### Parameters

- datetime (datetime-like):

- solar_pos (tuple (default=None)): solar position parameters from a prior call to `solar_position`

###### Returns

- S (float): Solar direct beam radiation flux density, W/m2

def solar_declination(datetime, fast=False): View Source

    235def solar_declination( datetime, fast=False ):
    236    '''Calculate solar declination (degrees) for specified date
    237
    238    Implements Eq. 9.68-9.72 from M.Z. Jacobson, Fundamentals of Atmospheric Modeling
    239
    240    Parameters
    241    ----------
    242    datetime : datetime-like or str
    243        date and time. Include time zone or UTC will be assumed
    244    fast : bool (default=False)
    245        Specifies using a faster but less accurate calculation
    246
    247    Returns
    248    -------
    249    dec : float
    250        solar declination in degrees at the specified date
    251    '''
    252    # Convert to pandas Timestamp, if needed
    253    datetime = _to_timestamp(datetime)
    254
    255    # Select the accurate or fast calculation
    256    accurate = not fast
    257
    258    if accurate:
    259
    260        # Solar declination, degrees
    261        dec, junk, junk, junk = solar_position( datetime )
    262
    263    else:
    264        # Number of days since beginning of 2000
    265        NJD = datetime - np.datetime64('2000-01-01')
    266        try:
    267            NJD = NJD.dt.days
    268        except AttributeError:
    269            NJD = NJD.days
    270
    271        # Obliquity, degrees
    272        ob = 23.439 - 4e-7 * NJD
    273
    274        # Parameters for ecliptic, degrees
    275        gm = 357.528 + 0.9856003 * NJD
    276        lm = 280.460 + 0.9856474 * NJD
    277
    278        # Ecliptic longitude of sun, degrees
    279        ec = lm + 1.915 * np.sin( gm * pi180 ) + 0.020 * np.sin( 2 * gm * pi180 )
    280
    281        #Solar declination, degrees
    282        dec = np.arcsin( np.sin( ob * pi180 ) * np.sin( ec * pi180 ) ) / pi180
    283
    284    return dec

Calculate solar declination (degrees) for specified date

Implements Eq. 9.68-9.72 from M.Z. Jacobson, Fundamentals of Atmospheric Modeling

###### Parameters

- datetime (datetime-like or str): date and time. Include time zone or UTC will be assumed
- fast (bool (default=False)): Specifies using a faster but less accurate calculation

###### Returns

- dec (float): solar declination in degrees at the specified date

def solar_latitude(datetime): View Source

    286def solar_latitude( datetime ):
    287    '''Latitude of the subsolar point
    288
    289    Parameters
    290    ----------
    291    datetimeUTC : datetime-like or str
    292        date and time. Include time zone or UTC will be assumed
    293
    294    Returns
    295    -------
    296    latitude : float
    297        degrees of latitude
    298    '''
    299    return solar_declination( datetime )

Latitude of the subsolar point

###### Parameters

- datetimeUTC (datetime-like or str): date and time. Include time zone or UTC will be assumed

###### Returns

- latitude (float): degrees of latitude

def solar_longitude(datetime, solar_pos=None): View Source

    301def solar_longitude( datetime, solar_pos=None ):
    302    '''Longitude of the subsolar point, degrees
    303
    304    Parameters
    305    ----------
    306    datetimeUTC : datetime-like or str
    307        date and time. Include time zone or UTC will be assumed
    308
    309    Returns
    310    -------
    311    longitude : float
    312        degrees of longitude
    313    '''
    314    # Convert to pandas Timestamp, if needed
    315    datetimeUTC = _to_timestamp_utc(datetime)
    316
    317    # Longitude of subsolar point, degrees
    318    # Equation of time will be added below
    319    try:
    320        # Treat as xarray.DataArray or pandas.Series
    321        solar_lon = - 15 * ( datetimeUTC.dt.hour +
    322                          datetimeUTC.dt.minute / 60 +
    323                          datetimeUTC.dt.second / 3600 - 12 )
    324    except AttributeError:
    325        solar_lon = - 15 * ( datetimeUTC.hour +
    326                          datetimeUTC.minute / 60 +
    327                          datetimeUTC.second / 3600 - 12 )
    328
    329    # Add equation of time to the solar longitude, degrees
    330    if solar_pos is None:
    331        eot = equation_of_time( datetimeUTC, degrees=True )
    332    else:
    333        eot = solar_pos.equation_of_time
    334    solar_lon -= eot
    335
    336    return solar_lon

Longitude of the subsolar point, degrees

###### Parameters

- datetimeUTC (datetime-like or str): date and time. Include time zone or UTC will be assumed

###### Returns

- longitude (float): degrees of longitude

def solar_hour_angle(lon, datetime, solar_pos=None): View Source

    338def solar_hour_angle( lon, datetime, solar_pos=None ):
    339    '''Solar hour angle (degrees) for specified longitude, date and time
    340
    341    Hour angle is the angular displacement of the sun from the local meridian.
    342    It is zero at local noon, negative in the morning, and positive is afternoon.
    343
    344    Parameters
    345    ----------
    346    lon : float
    347        longitude in degrees east
    348    datetimeUTC : datetime-like or str
    349        date and time. Include time zone or UTC will be assumed
    350
    351    Returns
    352    -------
    353    ha : float
    354        hour angle in degrees at the specified location and time
    355    '''
    356
    357    # Subsolar longitude, degrees
    358    solar_lon = solar_longitude(datetime, solar_pos)
    359
    360    # Hour angle, degrees
    361    Ha = lon - solar_lon
    362
    363    return Ha

Solar hour angle (degrees) for specified longitude, date and time

Hour angle is the angular displacement of the sun from the local meridian. It is zero at local noon, negative in the morning, and positive is afternoon.

###### Parameters

- lon (float): longitude in degrees east
- datetimeUTC (datetime-like or str): date and time. Include time zone or UTC will be assumed

###### Returns

- ha (float): hour angle in degrees at the specified location and time

def equation_of_time(datetime, degrees=False, fast=False): View Source

    365def equation_of_time( datetime, degrees=False, fast=False ):
    366    '''Equation of time for specified date
    367
    368    Accounts for the solar day being slightly different from 24 hours
    369
    370    Parameters
    371    ----------
    372    datetime : datetime-like or str
    373        date and time. Include time zone or UTC will be assumed
    374    degrees : bool (default=False)
    375        If True, then return value in compass degrees
    376        If False, then return value in minutes of an hour
    377    fast : bool (default=False)
    378        specifies whether to use a faster, but less accurate calculation
    379
    380    Returns
    381    -------
    382    eot : float
    383        equation of time on the specified date, degrees or minutes
    384    '''
    385    # Convert to pandas Timestamp, if needed
    386    datetime = _to_timestamp(datetime)
    387
    388    # Determine whether to use the fast or accurate calculation
    389    accurate = not fast
    390
    391    if accurate:
    392
    393        # Equation of time, minutes
    394        junk, junk, eot, junk = solar_position( datetime )
    395
    396    else:
    397        # Implements the "alternative equation" from Wikipedia, derived from
    398        # https://web.archive.org/web/20120323231813/http://www.green-life-innovators.org/tiki-index.php?page=The%2BLatitude%2Band%2BLongitude%2Bof%2Bthe%2BSun%2Bby%2BDavid%2BWilliams
    399        # Results checked against NOAA solar calculator and agree within 10 seconds.
    400        # Note: Leap years are not accounted for.
    401
    402        # Equation of time, accounts for the solar day differing slightly from 24 hr
    403        try:
    404            doy = datetime.dt.dayofyear
    405        except AttributeError:
    406            doy = datetime.dayofyear
    407        W = 360 / 365.24
    408        A = W * (doy+10)
    409        B = A + 1.914 * np.sin( W * (doy-2) * pi180 )
    410        C = ( A - np.arctan2( np.tan(B*pi180), np.cos(23.44*pi180) ) / pi180 ) / 180
    411
    412        # Equation of time in minutes of an hour (1440 minutes per day)
    413        eot = 720 * ( C - np.round(C) )
    414
    415    # Equation of time, minutes -> degrees (360 degrees per day)
    416    if degrees:
    417        eot = eot / 60 * 360 / 24
    418
    419    return eot

Equation of time for specified date

Accounts for the solar day being slightly different from 24 hours

###### Parameters

- datetime (datetime-like or str): date and time. Include time zone or UTC will be assumed
- degrees (bool (default=False)): If True, then return value in compass degrees If False, then return value in minutes of an hour
- fast (bool (default=False)): specifies whether to use a faster, but less accurate calculation

###### Returns

- eot (float): equation of time on the specified date, degrees or minutes

def solar_position(datetime): View Source

    421def solar_position( datetime ):
    422    '''Compute position of sun (declination, right ascension, equation of time, distance)) on specified date
    423
    424    Calculations follow the NOAA solar calculator spreadsheet
    425    Applicable to years 1900-2100.
    426
    427    Parameters
    428    ----------
    429    date : datetime-like or str
    430        date and time. Include time zone or UTC will be assumed
    431
    432    Returns
    433    -------
    434    declination : float
    435        position of the sun relative to Earth's equatorial plane, degrees
    436    right_ascension : float
    437        position of the sun along Earth's equatorial plane, degrees
    438        relative to the sun's position on the vernal equinox
    439    equation_of_time : float
    440        equation of time (minutes) between mean solar time and true solar time
    441        Divide by 4 minutes per degree to obtain equation of time in degrees
    442    distance : float
    443        Earth-sun distance in AU (1 AU = 1.495978707e11 m)
    444    '''
    445    # Ensure time is Timestamp in UTC
    446    datetimeUTC, tz_in = _to_timestamp_utc(datetime)
    447
    448    # Raise warning if any dates are outside date range
    449    # recommended for orbital parameters used here
    450    if np.logical_or( np.any( datetimeUTC < np.datetime64('1900-01-01') ),
    451                      np.any( datetimeUTC > np.datetime64('2100-01-01') ) ):
    452        warnings.warn('Solar position accuracy declines for dates outside 1900-2100', \
    453                      RuntimeWarning )
    454
    455    # Number of days since 1 Jan 2000
    456    NJD = datetimeUTC - np.datetime64('2000-01-01')
    457    try:
    458        NJD = NJD.dt.days
    459    except AttributeError:
    460        NJD = NJD.days
    461
    462    # Julian day (since 12:00 1 Jan 4713 BCE)
    463    NJD += 2451544.50
    464
    465    # Julian century
    466    JC = (NJD-2451545)/36525
    467
    468    # Earth orbital eccentricity, unitless
    469    ec = 0.016708634 - JC*( 0.000042037 + 0.0000001267*JC )
    470
    471    # Earth mean orbital obliquity, degree
    472    mean_ob = 23 + ( 26 + ( (21.448
    473                             - JC * (46.815
    474                                    + JC * (0.00059 - JC * 0.001813) ) ) )/60 )/60
    475
    476    # Earth true orbital obliquity, corrected for nutation, degree
    477    ob = mean_ob + 0.00256 * np.cos( (125.04 - 1934.136*JC ) * pi180 )
    478
    479    # Sun Mean ecliptic longitude, degree
    480    mean_ec_lon = np.mod( 280.46646 + JC*( 36000.76983 + JC*0.0003032 ), 360 )
    481
    482    # Sun Mean anomaly, degree
    483    mean_anom = 357.52911 + JC*( 35999.05029 - 0.0001537*JC )
    484
    485    # Sun Equation of center, degree
    486    eq_center = np.sin(mean_anom*pi180) * (1.914602 - JC*( 0.004817 + 0.000014*JC )) \
    487                    + np.sin(2*mean_anom*pi180) * (0.019993 - 0.000101*JC) \
    488                    + np.sin(3*mean_anom*pi180) * 0.000289
    489
    490    # Sun True ecliptic longitude, degrees
    491    true_ec_lon = mean_ec_lon + eq_center
    492
    493    # Sun True anomaly, degree
    494    true_anom = mean_anom + eq_center
    495
    496    # Earth-Sun distance, AU
    497    distance = (1.000001018 * (1-ec**2) ) / (1 + ec * np.cos( true_anom * pi180 ))
    498
    499    # Sun Apparent ecliptic longitude, corrected for nutation, degrees
    500    ec_lon = true_ec_lon - 0.00569 - 0.00478 * np.sin( (125.04 - 1934.136*JC ) * pi180)
    501
    502    # Sun Right ascension, deg
    503    right_ascension = np.arctan2( np.cos(ob*pi180) * np.sin(ec_lon*pi180),
    504                                  np.cos(ec_lon*pi180) ) / pi180
    505
    506    # Sun Declination, deg
    507    declination = np.arcsin( np.sin(ob*pi180) * np.sin(ec_lon*pi180) ) / pi180
    508
    509    # var y
    510    vary = np.tan( ob/2 * pi180 )**2
    511
    512    # Equation of time, minutes
    513    eot = vary * np.sin( 2 * mean_ec_lon * pi180) \
    514        - 2 * ec * np.sin( mean_anom * pi180 ) \
    515        + 4 * ec * vary * np.sin( mean_anom * pi180 ) * np.cos( 2 * mean_ec_lon * pi180) \
    516        - 0.5 * vary**2 * np.sin( 4 * mean_ec_lon * pi180) \
    517        - 1.25 * ec**2 * np.sin( 2 * mean_anom * pi180)
    518    eot = eot * 4 / pi180
    519
    520    # Define named tuple to hold result
    521    solar_pos_tuple = namedtuple('SolarPosition',
    522                        'declination right_ascension equation_of_time distance datetimeUTC')
    523    result = solar_pos_tuple(declination, right_ascension, eot, distance, datetimeUTC)
    524
    525    return result

Compute position of sun (declination, right ascension, equation of time, distance)) on specified date

Calculations follow the NOAA solar calculator spreadsheet Applicable to years 1900-2100.

###### Parameters

- date (datetime-like or str): date and time. Include time zone or UTC will be assumed

###### Returns

- declination (float): position of the sun relative to Earth's equatorial plane, degrees
- right_ascension (float): position of the sun along Earth's equatorial plane, degrees relative to the sun's position on the vernal equinox
- equation_of_time (float): equation of time (minutes) between mean solar time and true solar time Divide by 4 minutes per degree to obtain equation of time in degrees
- distance (float): Earth-sun distance in AU (1 AU = 1.495978707e11 m)

def sun_times(lat, lon, datetime, tz_out=None, sza_sunrise=90.833, fast=False): View Source

    527def sun_times( lat, lon, datetime, tz_out=None, sza_sunrise=90.833, fast=False ):
    528    '''Compute times of sunrise, sunset, solar noon, and day length
    529
    530    Common options for solar zenith angle at sunrise
    531    1. 90.833 for first edge of sun rising, typical (0.567°) refraction (default)
    532    2. 90.267 for first edge of sun rising, no refraction
    533    3. 90 degrees for center of sun rising, no refraction
    534
    535    Parameters
    536    ----------
    537    lat : float or ndarray
    538        latitude in degrees
    539    lon : float or ndarray
    540        longitudes in degrees
    541    datetime : datetime-like or str
    542        datetime, provide a time zone or UTC will be assumed
    543    tz_out : str, pytz.timezone, datetime.tzinfo or None (default=None)
    544        timezone to be used for output times.
    545        If None is provided, then result will be in same time zone as input or UTC
    546    sza_sunrise : float (default=90.833)
    547        Solar zenith angle at which sunrise and sunset are calculated, degrees
    548    fast : bool (default=False)
    549        Select a faster but less accurate calculation
    550
    551    Returns
    552    -------
    553    sunrise : pandas.DatetimeIndex
    554        sunrise time
    555    sunset : pandas.DatetimeIndex
    556        sunset time
    557    day_length : pandas.Timedelta
    558        duration of daylight
    559    solar_noon : pandas.DatetimeIndex
    560        time of meridian transit
    561    '''
    562    # Convert to pandas Timestamp in UTC, if needed
    563    datetimeUTC, tz_in = _to_timestamp_utc(datetime)
    564
    565    # If no output timezone is specified, use the input time zone
    566    if tz_out is None:
    567        tz_out = tz_in
    568
    569    # Select fast or accurate calculation
    570    accurate = not fast
    571
    572    # Solar declination (degrees) and equation of time (minutes)
    573    if accurate:
    574        dec, junk, eot, junk = solar_position( datetimeUTC )
    575    else:
    576        dec = solar_declination( datetimeUTC )
    577        eot = equation_of_time( datetimeUTC )
    578
    579    # Sunrise hour angle, degree
    580    # Degrees east of the local meridian where sun rises
    581    ha_sunrise = np.arccos( np.cos(sza_sunrise*pi180) /
    582                           (np.cos(lat*pi180) * np.cos(dec*pi180))
    583                           - np.tan(lat*pi180)*np.tan(dec*pi180) ) / pi180
    584
    585    # Solar noon, local standard time, day fraction
    586    solar_noon = (720 - 4*lon - eot ) / 1440
    587
    588    # Sunrise and sunset, local standard time, day fraction
    589    t_sunrise = solar_noon - 4 * ha_sunrise / 1440
    590    t_sunset  = solar_noon + 4 * ha_sunrise / 1440
    591
    592    # Date portion only
    593    try:
    594        # Convert to UTC
    595        dateUTC = datetimeUTC.tz_convert('UTC').normalize()
    596    except TypeError:
    597        # Assume already in UTC
    598        dateUTC = datetimeUTC.tz_localize('UTC').normalize()
    599
    600    # Convert day fraction -> date time
    601    solar_noon = dateUTC + solar_noon * pd.Timedelta( 1, 'day' )
    602    t_sunrise  = dateUTC + t_sunrise  * pd.Timedelta( 1, 'day' )
    603    t_sunset   = dateUTC + t_sunset   * pd.Timedelta( 1, 'day' )
    604
    605    # Localize to input timezone
    606    try:
    607        solar_noon = solar_noon.dt.tz_convert(tz_out)
    608        t_sunrise  = t_sunrise.dt.tz_convert(tz_out)
    609        t_sunset   = t_sunset.dt.tz_convert(tz_out)
    610    except AttributeError:
    611        solar_noon = solar_noon.tz_convert(tz_out)
    612        t_sunrise  = t_sunrise.tz_convert(tz_out)
    613        t_sunset   = t_sunset.tz_convert(tz_out)
    614
    615    # Sunlight duration, minutes
    616    day_length = 8 * ha_sunrise * pd.Timedelta(1, 'minute')
    617
    618    return t_sunrise, t_sunset, day_length, solar_noon

Compute times of sunrise, sunset, solar noon, and day length

Common options for solar zenith angle at sunrise

1. 90.833 for first edge of sun rising, typical (0.567°) refraction (default)
2. 90.267 for first edge of sun rising, no refraction
3. 90 degrees for center of sun rising, no refraction

###### Parameters

- lat (float or ndarray): latitude in degrees
- lon (float or ndarray): longitudes in degrees
- datetime (datetime-like or str): datetime, provide a time zone or UTC will be assumed
- tz_out (str, pytz.timezone, datetime.tzinfo or None (default=None)): timezone to be used for output times. If None is provided, then result will be in same time zone as input or UTC
- sza_sunrise (float (default=90.833)): Solar zenith angle at which sunrise and sunset are calculated, degrees
- fast (bool (default=False)): Select a faster but less accurate calculation

###### Returns

- sunrise (pandas.DatetimeIndex): sunrise time
- sunset (pandas.DatetimeIndex): sunset time
- day_length (pandas.Timedelta): duration of daylight
- solar_noon (pandas.DatetimeIndex): time of meridian transit

def horizon_zenith_angle(alt): View Source

    620def horizon_zenith_angle( alt ):
    621    '''Angle from the zenith to the horizon
    622
    623    The horizon is the locii of points where a line from the
    624    observation location to the ellipsoid is tangent to the ellipsoid surface.
    625
    626    The altitude parameter should be the vertical distance
    627    above the surrounding terrain that defines the horizon,
    628    not necessarily the altitude above sea level or the altitude above ground level.
    629    For example, on a mountain peak that is 4000 m above sea level and
    630    1500 m above the surrounding plateau, the relevant altitude is 1500 m.
    631    For an observer on the plateau, the relevant altitude is 0 m.
    632
    633    The implementation below assumes a spherical Earth.
    634    Results using the WGS84 ellipsoid (see commented code below)
    635    differ from the spherical case by << 1°. Terrain,
    636    which is neglected here, has a larger effect on the horizon
    637    location, so the simpler spherical calculation is appropriate.
    638
    639    Parameters
    640    ----------
    641    lat : float or ndarray
    642        latitude in degrees
    643    alt : float or ndarray
    644        altitude above surrounding terrain that defines the horizon, meters
    645
    646    Returns
    647    -------
    648    hza : float or ndarray
    649        horizon zenith angle in degrees
    650    '''
    651
    652    # WGS84 ellipsoid parameters
    653    # semi-major radius, m
    654    r_earth = 6378137.0
    655    # ellipsoidal flattening, unitless
    656    f = 1/298.257223563
    657
    658    # Horizon zenith angle, degrees (spherical earth)
    659    hza = 180 - np.arcsin( r_earth / ( r_earth + alt ) ) / pi180
    660
    661    ## Ellipsoidal Earth
    662    # # Eccentricity of ellipsoid
    663    # ecc = f * (2-f)
    664    # # Local (i.e. prime vertical) radius of curvature at latitude
    665    # N = r_earth / np.sqrt( 1 - ecc**2 * np.sin(lat*pi180)**2 )
    666    # # Horizon zenith angle, degrees
    667    # hza = 180 - np.arcsin( N / (N+alt) ) / pi180
    668
    669    return hza

Angle from the zenith to the horizon

The horizon is the locii of points where a line from the observation location to the ellipsoid is tangent to the ellipsoid surface.

The altitude parameter should be the vertical distance above the surrounding terrain that defines the horizon, not necessarily the altitude above sea level or the altitude above ground level. For example, on a mountain peak that is 4000 m above sea level and 1500 m above the surrounding plateau, the relevant altitude is 1500 m. For an observer on the plateau, the relevant altitude is 0 m.

The implementation below assumes a spherical Earth. Results using the WGS84 ellipsoid (see commented code below) differ from the spherical case by << 1°. Terrain, which is neglected here, has a larger effect on the horizon location, so the simpler spherical calculation is appropriate.

###### Parameters

- lat (float or ndarray): latitude in degrees
- alt (float or ndarray): altitude above surrounding terrain that defines the horizon, meters

###### Returns

- hza (float or ndarray): horizon zenith angle in degrees

def refraction_angle(true_elevation_angle, pressure=101325.0, temperature_celsius=10.0): View Source

    671def refraction_angle( true_elevation_angle, pressure=101325., temperature_celsius=10. ):
    672    '''Atmospheric refraction angle for light passing through Earth's atmosphere
    673
    674    The apparent locations in the sky of objects outsides Earth's atmosphere
    675    differs from their true locations due to atmospheric refraction.
    676    (e.g. sun and moon when they rise and set)
    677    The apparent elevation of an object above the horizon is
    678    apparent elevation angle = (true elevation angle) + (refraction angle)
    679
    680    The equations here are from Saemundsson/Bennett, whose calculations use
    681    a typical vertical profile of atmospheric density (i.e. temperature and pressure).
    682    The profiles can be rescaled to a particular surface temperature and pressure
    683    to approximately account for varying atmospheric conditions.
    684    Accurate refraction calculations should use fully specified vertical profile
    685    of temperature and pressure, which cannot be done here.
    686
    687    Parameters
    688    ----------
    689    true_elevation_angle : float
    690        degrees above horizon of sun or other object
    691    pressure : float (default=101325)
    692        surface atmospheric pressure (Pa)
    693    temperature_celsius : float (default=10)
    694        surface atmospheric temperature (C)
    695
    696    Returns
    697    -------
    698    angle : float
    699        refraction angle in degrees. Value is zero when apparent elevation is below horizon
    700    '''
    701    # Refraction angle, arcminutes
    702    R = 1.02 / np.tan( ( true_elevation_angle + 10.3 / (true_elevation_angle + 5.11) ) * pi180 )
    703    # Account for temperature and pressure, arcminutes
    704    R = R * pressure / 101325 * 283 / ( 273 + temperature_celsius )
    705    # Convert arcminutes -> degrees
    706    R /= 60
    707
    708    # Result must be positive
    709    R = np.maximum(R,0)
    710
    711    # Refraction defined only when the apparent elevation angle is positive
    712    # Set refraction to zero when the apparent elevation is below horizon
    713    refraction_angle = np.where( true_elevation_angle + R <= 0, 0, R)
    714
    715    return refraction_angle

Atmospheric refraction angle for light passing through Earth's atmosphere

The apparent locations in the sky of objects outsides Earth's atmosphere differs from their true locations due to atmospheric refraction. (e.g. sun and moon when they rise and set) The apparent elevation of an object above the horizon is apparent elevation angle = (true elevation angle) + (refraction angle)

The equations here are from Saemundsson/Bennett, whose calculations use a typical vertical profile of atmospheric density (i.e. temperature and pressure). The profiles can be rescaled to a particular surface temperature and pressure to approximately account for varying atmospheric conditions. Accurate refraction calculations should use fully specified vertical profile of temperature and pressure, which cannot be done here.

###### Parameters

- true_elevation_angle (float): degrees above horizon of sun or other object
- pressure (float (default=101325)): surface atmospheric pressure (Pa)
- temperature_celsius (float (default=10)): surface atmospheric temperature (C)

###### Returns

- angle (float): refraction angle in degrees. Value is zero when apparent elevation is below horizon

def sza( lat, lon, datetime, refraction=False, temperature=10.0, pressure=101325.0, solar_pos=None): View Source

    127def solar_zenith_angle( lat, lon, datetime,
    128                        refraction=False, temperature=10., pressure=101325.,
    129                        solar_pos=None ):
    130    '''Solar zenith angle (degrees) for a given latitude, longitude, date and time.
    131
    132    Accounts for equation of time and (optionally) for atmospheric refraction.
    133    Altitude of the observer is not accounted for, which can be important when the sun
    134    is near the horizon.
    135
    136    Results are accurate to tenths of a degree, except where altitude is important
    137    (< 20 degrees solar elevation)
    138
    139    Parameters
    140    ----------
    141    lat : float or ndarray
    142        latitude in degrees
    143    lon : float or ndarray
    144        longitudes in degrees
    145    datetime : datetime-like or str
    146        date and time. Include time zone or UTC will be assumed
    147    refraction : bool, optional (default=False)
    148        specifies whether to account for atmospheric refraction
    149    temperature : float or ndarray, optional (default=10)
    150        surface atmospheric temperature (Celsius), only used for refraction calculation
    151    pressure : float or ndarray, optional (default=101325)
    152        surface atmospheric pressure (Pa), only used for refraction calculation
    153
    154    Returns
    155    -------
    156    sza : float or ndarray
    157        solar zenith angle in degrees at the designated locations and times
    158        If refraction=False, this is the true solar zenith angle
    159        If refraction=True, this is the apparent solar zenith angle
    160    '''
    161    # Convert to pandas Timestamp, if needed
    162    datetime = _to_timestamp(datetime)
    163
    164    # Solar declination, degrees
    165    if solar_pos is None:
    166        dec = solar_declination( datetime )
    167    else:
    168        dec = solar_pos.declination
    169
    170    # Hour angle, degrees
    171    Ha = solar_hour_angle( lon, datetime, solar_pos )
    172
    173    # True solar zenith angle, radians
    174    sza = np.arccos( np.sin(lat*pi180) * np.sin(dec*pi180) + \
    175          np.cos(lat*pi180) * np.cos(dec*pi180) * np.cos(Ha*pi180) )
    176
    177    # Convert radians -> degrees
    178    sza /= pi180
    179
    180    if refraction:
    181        # Subtract refraction angle (degrees) from zenith angle.
    182        # SZA is always smaller due to refraction.
    183        sza -= refraction_angle( 90-sza, pressure, temperature )
    184
    185    return sza

Solar zenith angle (degrees) for a given latitude, longitude, date and time.

Accounts for equation of time and (optionally) for atmospheric refraction. Altitude of the observer is not accounted for, which can be important when the sun is near the horizon.

Results are accurate to tenths of a degree, except where altitude is important (< 20 degrees solar elevation)

###### Parameters

- lat (float or ndarray): latitude in degrees
- lon (float or ndarray): longitudes in degrees
- datetime (datetime-like or str): date and time. Include time zone or UTC will be assumed
- refraction (bool, optional (default=False)): specifies whether to account for atmospheric refraction
- temperature (float or ndarray, optional (default=10)): surface atmospheric temperature (Celsius), only used for refraction calculation
- pressure (float or ndarray, optional (default=101325)): surface atmospheric pressure (Pa), only used for refraction calculation

###### Returns

- sza (float or ndarray): solar zenith angle in degrees at the designated locations and times If refraction=False, this is the true solar zenith angle If refraction=True, this is the apparent solar zenith angle

def saa(lat, lon, datetime): View Source

    39def solar_azimuth_angle( lat, lon, datetime ):
    40    '''Solar azimuth angle (degrees) for a latitude, longitude, date and time
    41
    42    SAA is degrees clockwise from north.
    43
    44    Parameters
    45    ----------
    46    lat : float or ndarray
    47        latitude in degrees
    48    lon : float or ndarray
    49        longitudes in degrees
    50    datetime : datetime-like or str
    51        date and time. Include time zone or UTC will be assumed
    52
    53    Returns
    54    -------
    55    saa : float or ndarray
    56        solar azimuth angle in degrees (clockwise from north)
    57    '''
    58    # Convert to pandas Timestamp, if needed
    59    datetime = _to_timestamp(datetime)
    60
    61    # Subsolar point, latitude longitude, degrees
    62    solar_lat = solar_latitude( datetime )
    63    solar_lon = solar_longitude( datetime )
    64
    65    # Vector pointing toward sun
    66    x = np.cos( solar_lat * pi180 ) * np.sin( (solar_lon - lon) * pi180 )
    67    y = np.cos( lat*pi180 ) * np.sin( solar_lat*pi180 ) \
    68        - np.sin( lat*pi180 ) * np.cos( solar_lat*pi180 ) \
    69            * np.cos( (solar_lon - lon) * pi180 )
    70
    71    # Azimuth angle from north, degrees
    72    saa = np.arctan2( x, y ) / pi180
    73
    74    # Change range [-180,180] to [0,360]
    75    return np.mod( saa+360, 360 )

Solar azimuth angle (degrees) for a latitude, longitude, date and time

SAA is degrees clockwise from north.

###### Parameters

- lat (float or ndarray): latitude in degrees
- lon (float or ndarray): longitudes in degrees
- datetime (datetime-like or str): date and time. Include time zone or UTC will be assumed

###### Returns

- saa (float or ndarray): solar azimuth angle in degrees (clockwise from north)

def sea( lat, lon, alt, datetime, refraction=False, temperature=10.0, pressure=101325.0): View Source

     77def solar_elevation_angle( lat, lon, alt, datetime,
     78                       refraction=False, temperature=10., pressure=101325. ):
     79    '''Solar elevation angle (degrees) above the horizon
     80
     81    The altitude parameter should be the vertical distance
     82    above the surrounding terrain that defines the horizon,
     83    not necessarily the altitude above sea level or the altitude above ground level.
     84    For example, on a mountain peak that is 4000 m above sea level and
     85    1500 m above the surrounding plateau, the relevant altitude is 1500 m.
     86    For an observer on the plateau, the relevant altitude is 0 m.
     87
     88    See documentation for `solar_zenith_angle` and `horizon_zenith_angle`.
     89
     90    Parameters
     91    ----------
     92    lat : float or ndarray
     93        latitude in degrees
     94    lon : float or ndarray
     95        longitudes in degrees
     96    alt : float or ndarray
     97        altitude above surrounding terrain that defines the horizon, meters
     98    datetime : datetime-like or str
     99        date and time. Include time zone or UTC will be assumed
    100    refraction : bool, optional (default=False)
    101        specifies whether to account for atmospheric refraction
    102    temperature : float or ndarray, optional (default=10)
    103        surface atmospheric temperature (Celsius), only used for refraction calculation
    104    pressure : float or ndarray, optional (default=101325)
    105        surface atmospheric pressure (Pa), only used for refraction calculation
    106
    107    Returns
    108    -------
    109    sea : float or ndarray
    110        solar elevation angle in degrees at the designated locations and times
    111        If refraction=False, this is the true solar elevation angle
    112        If refraction=True, this is the apparent solar elevation angle
    113
    114    '''
    115
    116    if refraction and np.any(alt):
    117        warnings.warn( 'Atmospheric refraction is calculated for surface conditions, '
    118                    + 'but an altitude above the surface was specified',
    119                     category=UserWarning,
    120                     stacklevel=2 )
    121
    122    sea = horizon_zenith_angle( alt ) \
    123         - solar_zenith_angle( lat, lon, datetime, refraction, temperature, pressure )
    124
    125    return sea

Solar elevation angle (degrees) above the horizon

The altitude parameter should be the vertical distance above the surrounding terrain that defines the horizon, not necessarily the altitude above sea level or the altitude above ground level. For example, on a mountain peak that is 4000 m above sea level and 1500 m above the surrounding plateau, the relevant altitude is 1500 m. For an observer on the plateau, the relevant altitude is 0 m.

See documentation for `solar_zenith_angle` and `horizon_zenith_angle`.

###### Parameters

- lat (float or ndarray): latitude in degrees
- lon (float or ndarray): longitudes in degrees
- alt (float or ndarray): altitude above surrounding terrain that defines the horizon, meters
- datetime (datetime-like or str): date and time. Include time zone or UTC will be assumed
- refraction (bool, optional (default=False)): specifies whether to account for atmospheric refraction
- temperature (float or ndarray, optional (default=10)): surface atmospheric temperature (Celsius), only used for refraction calculation
- pressure (float or ndarray, optional (default=101325)): surface atmospheric pressure (Pa), only used for refraction calculation

###### Returns

- sea (float or ndarray): solar elevation angle in degrees at the designated locations and times If refraction=False, this is the true solar elevation angle If refraction=True, this is the apparent solar elevation angle

def equationOfTime(datetime, degrees=False, fast=False): View Source

    365def equation_of_time( datetime, degrees=False, fast=False ):
    366    '''Equation of time for specified date
    367
    368    Accounts for the solar day being slightly different from 24 hours
    369
    370    Parameters
    371    ----------
    372    datetime : datetime-like or str
    373        date and time. Include time zone or UTC will be assumed
    374    degrees : bool (default=False)
    375        If True, then return value in compass degrees
    376        If False, then return value in minutes of an hour
    377    fast : bool (default=False)
    378        specifies whether to use a faster, but less accurate calculation
    379
    380    Returns
    381    -------
    382    eot : float
    383        equation of time on the specified date, degrees or minutes
    384    '''
    385    # Convert to pandas Timestamp, if needed
    386    datetime = _to_timestamp(datetime)
    387
    388    # Determine whether to use the fast or accurate calculation
    389    accurate = not fast
    390
    391    if accurate:
    392
    393        # Equation of time, minutes
    394        junk, junk, eot, junk = solar_position( datetime )
    395
    396    else:
    397        # Implements the "alternative equation" from Wikipedia, derived from
    398        # https://web.archive.org/web/20120323231813/http://www.green-life-innovators.org/tiki-index.php?page=The%2BLatitude%2Band%2BLongitude%2Bof%2Bthe%2BSun%2Bby%2BDavid%2BWilliams
    399        # Results checked against NOAA solar calculator and agree within 10 seconds.
    400        # Note: Leap years are not accounted for.
    401
    402        # Equation of time, accounts for the solar day differing slightly from 24 hr
    403        try:
    404            doy = datetime.dt.dayofyear
    405        except AttributeError:
    406            doy = datetime.dayofyear
    407        W = 360 / 365.24
    408        A = W * (doy+10)
    409        B = A + 1.914 * np.sin( W * (doy-2) * pi180 )
    410        C = ( A - np.arctan2( np.tan(B*pi180), np.cos(23.44*pi180) ) / pi180 ) / 180
    411
    412        # Equation of time in minutes of an hour (1440 minutes per day)
    413        eot = 720 * ( C - np.round(C) )
    414
    415    # Equation of time, minutes -> degrees (360 degrees per day)
    416    if degrees:
    417        eot = eot / 60 * 360 / 24
    418
    419    return eot

Equation of time for specified date

Accounts for the solar day being slightly different from 24 hours

###### Parameters

- datetime (datetime-like or str): date and time. Include time zone or UTC will be assumed
- degrees (bool (default=False)): If True, then return value in compass degrees If False, then return value in minutes of an hour
- fast (bool (default=False)): specifies whether to use a faster, but less accurate calculation

###### Returns

- eot (float): equation of time on the specified date, degrees or minutes

def solarDeclination(datetime, fast=False): View Source

    235def solar_declination( datetime, fast=False ):
    236    '''Calculate solar declination (degrees) for specified date
    237
    238    Implements Eq. 9.68-9.72 from M.Z. Jacobson, Fundamentals of Atmospheric Modeling
    239
    240    Parameters
    241    ----------
    242    datetime : datetime-like or str
    243        date and time. Include time zone or UTC will be assumed
    244    fast : bool (default=False)
    245        Specifies using a faster but less accurate calculation
    246
    247    Returns
    248    -------
    249    dec : float
    250        solar declination in degrees at the specified date
    251    '''
    252    # Convert to pandas Timestamp, if needed
    253    datetime = _to_timestamp(datetime)
    254
    255    # Select the accurate or fast calculation
    256    accurate = not fast
    257
    258    if accurate:
    259
    260        # Solar declination, degrees
    261        dec, junk, junk, junk = solar_position( datetime )
    262
    263    else:
    264        # Number of days since beginning of 2000
    265        NJD = datetime - np.datetime64('2000-01-01')
    266        try:
    267            NJD = NJD.dt.days
    268        except AttributeError:
    269            NJD = NJD.days
    270
    271        # Obliquity, degrees
    272        ob = 23.439 - 4e-7 * NJD
    273
    274        # Parameters for ecliptic, degrees
    275        gm = 357.528 + 0.9856003 * NJD
    276        lm = 280.460 + 0.9856474 * NJD
    277
    278        # Ecliptic longitude of sun, degrees
    279        ec = lm + 1.915 * np.sin( gm * pi180 ) + 0.020 * np.sin( 2 * gm * pi180 )
    280
    281        #Solar declination, degrees
    282        dec = np.arcsin( np.sin( ob * pi180 ) * np.sin( ec * pi180 ) ) / pi180
    283
    284    return dec

Calculate solar declination (degrees) for specified date

Implements Eq. 9.68-9.72 from M.Z. Jacobson, Fundamentals of Atmospheric Modeling

###### Parameters

- datetime (datetime-like or str): date and time. Include time zone or UTC will be assumed
- fast (bool (default=False)): Specifies using a faster but less accurate calculation

###### Returns

- dec (float): solar declination in degrees at the specified date