acgc.solar

Module to calculate solar zenith angle, solar declination, and equation of time Results should be accurate to < 0.1 degree, but other modules should be used for high-precision calculations.

C.D. Holmes 9 Nov 2018

  1#!/usr/local/bin/env python3
  2''' Module to calculate solar zenith angle, solar declination, and equation of time 
  3Results should be accurate to < 0.1 degree, but other modules should be used for 
  4high-precision calculations.
  5
  6C.D. Holmes 9 Nov 2018
  7'''
  8
  9import warnings
 10import numpy as np
 11import pandas as pd
 12
 13pi180 = np.pi / 180
 14
 15def solar_azimuth_angle( lat, lon, datetimeUTC ):
 16    '''Solar azimuth angle (degrees) for a latitude, longitude, date and time
 17    
 18    SAA is degrees clockwise from north.
 19    
 20    Parameters
 21    ----------
 22    lat : float or ndarray
 23        latitude in degrees
 24    lon : float or ndarray
 25        longitudes in degrees
 26    datetimeUTC : pandas.Timestamp, datetime, or str
 27        date and time in UTC
 28
 29    Returns
 30    -------
 31    saa : float or ndarray
 32        solar azimuth angle in degrees (clockwise from north)
 33    '''
 34    # Convert to pandas Timestamp, if needed
 35    datetimeUTC = _to_timestamp(datetimeUTC)
 36
 37    # Solar declination, degrees
 38    dec = solar_declination( datetimeUTC )
 39
 40    # Hour angle, degrees
 41    Ha = solar_hour_angle( lon, datetimeUTC )
 42
 43    # Solar zenith angle, degrees
 44    # Use true sza, without refraction
 45    zen = sza( lat, lon, datetimeUTC, refraction=False )
 46
 47    # Solar azimuth angle, degrees
 48    saa = np.arcsin( -np.sin( Ha*pi180 ) * np.cos( dec*pi180 ) /
 49            np.sin( zen*pi180 ) ) / pi180
 50
 51    # Change range [-180,180] to [0,360]
 52    return np.mod( saa+360, 360 )
 53
 54def solar_elevation_angle( lat, lon, alt, datetimeUTC,
 55                       refraction=False, temperature=10., pressure=101325. ):
 56    '''Solar elevation angle (degrees) above the horizon
 57
 58    The altitude parameter should be the vertical distance 
 59    above the surrounding terrain that defines the horizon,
 60    not necessarily the altitude above sea level or the altitude above ground level.
 61    For example, on a mountain peak that is 4000 m above sea level and 
 62    1500 m above the surrounding plateau, the relevant altitude is 1500 m.
 63    For an observer on the plateau, the relevant altitude is 0 m.
 64
 65    See documentation for `solar_zenith_angle` and `horizon_zenith_angle`.
 66
 67    Parameters
 68    ----------
 69    lat : float or ndarray
 70        latitude in degrees
 71    lon : float or ndarray
 72        longitudes in degrees
 73    alt : float or ndarray
 74        altitude above surrounding terrain that defines the horizon, meters
 75    datetimeUTC : pandas.Timestamp, datetime, or str
 76        date and time in UTC
 77    refraction : bool, optional (default=False)
 78        specifies whether to account for atmospheric refraction
 79    temperature : float or ndarray, optional (default=10)
 80        surface atmospheric temperature (Celsius), only used for refraction calculation
 81    pressure : float or ndarray, optional (default=101325)
 82        surface atmospheric pressure (Pa), only used for refraction calculation
 83    
 84    Returns
 85    -------
 86    sea : float or ndarray
 87        solar elevation angle in degrees at the designated locations and times
 88        If refraction=False, this is the true solar elevation angle
 89        If refraction=True, this is the apparent solar elevation angle
 90    
 91    '''
 92
 93    if refraction and np.any(alt):
 94        warnings.warn( 'Atmospheric refraction is calculated for surface conditions, '
 95                    + 'but an altitude above the surface was specified',
 96                     category=UserWarning,
 97                     stacklevel=2 )
 98
 99    sea = horizon_zenith_angle( alt ) \
100         - solar_zenith_angle( lat, lon, datetimeUTC, refraction, temperature, pressure )
101
102    return sea
103
104def solar_zenith_angle( lat, lon, datetimeUTC, 
105                       refraction=False, temperature=10., pressure=101325. ):
106    '''Solar zenith angle (degrees) for a given latitude, longitude, date and time.
107    
108    Accounts for equation of time and (optionally) for atmospheric refraction.
109    Altitude of the observer is not accounted for, which can be important when the sun 
110    is near the horizon. 
111    
112    Results are accurate to tenths of a degree, except where altitude is important
113    (< 20 degrees solar elevation)
114
115    Parameters
116    ----------
117    lat : float or ndarray
118        latitude in degrees
119    lon : float or ndarray
120        longitudes in degrees
121    datetimeUTC : pandas.Timestamp, datetime, or str
122        date and time in UTC
123    refraction : bool, optional (default=False)
124        specifies whether to account for atmospheric refraction
125    temperature : float or ndarray, optional (default=10)
126        surface atmospheric temperature (Celsius), only used for refraction calculation
127    pressure : float or ndarray, optional (default=101325)
128        surface atmospheric pressure (Pa), only used for refraction calculation
129    
130    Returns
131    -------
132    sza : float or ndarray
133        solar zenith angle in degrees at the designated locations and times
134        If refraction=False, this is the true solar zenith angle
135        If refraction=True, this is the apparent solar zenith angle
136    '''
137    # Convert to pandas Timestamp, if needed
138    datetimeUTC = _to_timestamp(datetimeUTC)
139
140    # Solar declination, degrees
141    dec = solar_declination( datetimeUTC )
142
143    # Hour angle, degrees
144    Ha = solar_hour_angle( lon, datetimeUTC )
145
146    # True solar zenith angle, radians
147    sza = np.arccos( np.sin(lat*pi180) * np.sin(dec*pi180) + \
148          np.cos(lat*pi180) * np.cos(dec*pi180) * np.cos(Ha*pi180) )
149
150    # Convert radians -> degrees
151    sza /= pi180
152
153    if refraction:
154        # Subtract refraction angle (degrees) from zenith angle.
155        # SZA is always smaller due to refraction.
156        sza -= refraction_angle( 90-sza, pressure, temperature )
157
158    return sza
159
160def equation_of_time( date ):
161    '''Equation of time (degrees) for specified date
162    
163    Implements the "alternative equation" from Wikipedia, derived from
164    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
165    Results checked against NOAA solar calculator and agree within 10 seconds.
166    
167    Argument
168    --------
169    date : pandas.Timestamp, date, or datetime
170        date for calculation
171
172    Returns
173    -------
174    eot : float
175        equation of time in degrees on the specified date
176    '''
177    # Convert to pandas Timestamp, if needed
178    date = _to_timestamp(date)
179
180    # Equation of time, accounts for the solar day differing slightly from 24 hr
181    doy = date.dayofyear
182    W = 360 / 365.24
183    A = W * (doy+10)
184    B = A + 1.914 * np.sin( W * (doy-2) * pi180 )
185    C = ( A - np.arctan2( np.tan(B*pi180), np.cos(23.44*pi180) ) / pi180 ) / 180
186
187    # Equation of time in minutes of an hour
188    eotmin = 720 * ( C - np.round(C) )
189
190    # Equation of time, minutes -> degrees
191    eot = eotmin / 60 * 360 / 24
192
193    return eot
194
195def horizon_zenith_angle( alt ):
196    '''Angle from the zenith to the horizon
197    
198    The horizon is the locii of points where a line from the 
199    observation location to the ellipsoid is tangent to the ellipsoid surface.
200    
201    The altitude parameter should be the vertical distance 
202    above the surrounding terrain that defines the horizon,
203    not necessarily the altitude above sea level or the altitude above ground level.
204    For example, on a mountain peak that is 4000 m above sea level and 
205    1500 m above the surrounding plateau, the relevant altitude is 1500 m.
206    For an observer on the plateau, the relevant altitude is 0 m.
207
208    The implementation below assumes a spherical Earth.
209    Results using the WGS84 ellipsoid (see commented code below)
210    differ from the spherical case by << 1°. Terrain,
211    which is neglected here, has a larger effect on the horizon
212    location, so the simpler spherical calculation is appropriate. 
213
214    Parameters
215    ----------
216    lat : float or ndarray
217        latitude in degrees
218    alt : float or ndarray
219        altitude above surrounding terrain that defines the horizon, meters
220        
221    Returns
222    -------
223    hza : float or ndarray
224        horizon zenith angle in degrees
225    '''
226
227    # WGS84 ellipsoid parameters
228    # semi-major radius, m
229    r_earth = 6378137.0
230    # ellipsoidal flattening, unitless
231    f = 1/298.257223563
232
233    # Horizon zenith angle, degrees (spherical earth)
234    hza = 180 - np.arcsin( r_earth / ( r_earth + alt ) ) / pi180
235
236    ## Ellipsoidal Earth
237    # # Eccentricity of ellipsoid
238    # ecc = f * (2-f)
239    # # Local (i.e. prime vertical) radius of curvature at latitude
240    # N = r_earth / np.sqrt( 1 - ecc**2 * np.sin(lat*pi180)**2 )
241    # # Horizon zenith angle, degrees
242    # hza = 180 - np.arcsin( N / (N+alt) ) / pi180
243
244    return hza
245
246def solar_declination( date ):
247    '''Calculate solar declination (degrees) for specified date
248    
249    Implements Eq. 9.68-9.72 from M.Z. Jacobson, Fundamentals of Atmospheric Modeling
250    
251    Argument
252    --------
253    date : pandas.Timestamp, date, datetime, or str
254        date for calculation
255
256    Returns
257    -------
258    dec : float
259        solar declination in degrees at the specified date
260    '''
261
262    # Convert to pandas Timestamp, if needed
263    date = _to_timestamp(date)
264
265     # Number of days since beginning of 2000
266    NJD = np.floor( date.to_julian_date() - pd.Timestamp(2000,1,1).to_julian_date() )
267
268    # Obliquity, degrees
269    ob = 23.439 - 4e-7 * NJD
270
271    # Parameters for ecliptic, degrees
272    gm = 357.528 + 0.9856003 * NJD
273    lm = 280.460 + 0.9856474 * NJD
274
275    # Ecliptic longitude of sun, degrees
276    ec = lm + 1.915 * np.sin( gm * pi180 ) + 0.020 * np.sin( 2 * gm * pi180 )
277
278    #Solar declination, degrees
279    dec = np.arcsin( np.sin( ob * pi180 ) * np.sin( ec * pi180 ) ) / pi180
280
281    return dec
282
283def solar_hour_angle( lon, datetimeUTC ):
284    '''Solar hour angle (degrees) for specified longitude, date and time
285
286    Hour angle is the angular displacement of the sun from the local meridian.
287    It is zero at local noon, negative in the morning, and positive is afternoon.
288    
289    Parameters
290    ----------
291    lon : float
292        longitude in degrees east
293    datetimeUTC : pandas.Timestamp or datetime
294        date and time for calculation, must be UTC
295    
296    Returns
297    -------
298    ha : float
299        hour angle in degrees at the specified location and time
300    '''
301
302    # Convert to pandas Timestamp, if needed
303    datetimeUTC = _to_timestamp(datetimeUTC)
304
305    # Hour angle for mean solar time.
306    # Actual solar position has a small offset given by the equation of time (below)
307    Ha = ( datetimeUTC.hour + datetimeUTC.minute / 60 - 12 ) * 15 + lon
308
309    # Add equation of time to the hour angle, degrees
310    Ha += equation_of_time( datetimeUTC )
311
312    return Ha
313
314def refraction_angle( true_elevation_angle, pressure=101325., temperature_celsius=10. ):
315    '''Atmospheric refraction angle for light passing through Earth's atmosphere
316
317    The apparent locations in the sky of objects outsides Earth's atmosphere 
318    differs from their true locations due to atmospheric refraction. 
319    (e.g. sun and moon when they rise and set)
320    The apparent elevation of an object above the horizon is
321    apparent elevation angle = (true elevation angle) + (refraction angle)
322    
323    The equations here are from Saemundsson/Bennett, whose calculations use
324    a typical vertical profile of atmospheric density (i.e. temperature and pressure).
325    The profiles can be rescaled to a particular surface temperature and pressure
326    to approximately account for varying atmospheric conditions.
327    Accurate refraction calculations should use fully specified vertical profile
328    of temperature and pressure, which cannot be done here.
329
330    Parameters
331    ----------
332    true_elevation_angle : float
333        degrees above horizon of sun or other object
334    pressure : float (default=101325)
335        surface atmospheric pressure (Pa)
336    temperature_celsius : float (default=10)
337        surface atmospheric temperature (C)
338
339    Returns
340    -------
341    angle : float
342        refraction angle in degrees. Value is zero when apparent elevation is below horizon
343    '''
344    # Refraction angle, arcminutes
345    R = 1.02 / np.tan( ( true_elevation_angle + 10.3 / (true_elevation_angle + 5.11) ) * pi180 )
346    # Account for temperature and pressure, arcminutes
347    R = R * pressure / 101325 * 283 / ( 273 + temperature_celsius )
348    # Convert arcminutes -> degrees
349    R /= 60
350
351    # Result must be positive
352    R = np.maximum(R,0)
353
354    # Refraction defined only when the apparent elevation angle is positive
355    # Set refraction to zero when the apparent elevation is below horizon
356    refraction_angle = np.where( true_elevation_angle + R <= 0, 0, R)
357
358    return refraction_angle
359
360def _to_timestamp(time_in):
361    '''Convert input to Pandas Timestamp or DatetimeIndex
362    
363    Arguments
364    ---------
365    time_in : datetime-like or array
366        time to be converted
367
368    Returns
369    -------
370    time_out : pandas.DatetimeIndex or pandas.Timestamp
371        result will be a DatetimeIndex, if possible, and Timestamp otherwise
372    '''
373    if not isinstance(time_in, (pd.Timestamp,pd.DatetimeIndex) ):
374        try:
375            time_out = pd.DatetimeIndex(time_in)
376        except TypeError:
377            time_in = pd.Timestamp(time_in)
378    else:
379        time_out = time_in
380    return time_out
381
382# Aliases for functions
383sza = solar_zenith_angle
384saa = solar_azimuth_angle
385sea = solar_elevation_angle
386# Additional aliases for backwards compatibility
387equationOfTime = equation_of_time
388solarDeclination = solar_declination
pi180 = 0.017453292519943295
def solar_azimuth_angle(lat, lon, datetimeUTC):
16def solar_azimuth_angle( lat, lon, datetimeUTC ):
17    '''Solar azimuth angle (degrees) for a latitude, longitude, date and time
18    
19    SAA is degrees clockwise from north.
20    
21    Parameters
22    ----------
23    lat : float or ndarray
24        latitude in degrees
25    lon : float or ndarray
26        longitudes in degrees
27    datetimeUTC : pandas.Timestamp, datetime, or str
28        date and time in UTC
29
30    Returns
31    -------
32    saa : float or ndarray
33        solar azimuth angle in degrees (clockwise from north)
34    '''
35    # Convert to pandas Timestamp, if needed
36    datetimeUTC = _to_timestamp(datetimeUTC)
37
38    # Solar declination, degrees
39    dec = solar_declination( datetimeUTC )
40
41    # Hour angle, degrees
42    Ha = solar_hour_angle( lon, datetimeUTC )
43
44    # Solar zenith angle, degrees
45    # Use true sza, without refraction
46    zen = sza( lat, lon, datetimeUTC, refraction=False )
47
48    # Solar azimuth angle, degrees
49    saa = np.arcsin( -np.sin( Ha*pi180 ) * np.cos( dec*pi180 ) /
50            np.sin( zen*pi180 ) ) / pi180
51
52    # Change range [-180,180] to [0,360]
53    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
  • datetimeUTC (pandas.Timestamp, datetime, or str): date and time in UTC
Returns
  • saa (float or ndarray): solar azimuth angle in degrees (clockwise from north)
def solar_elevation_angle( lat, lon, alt, datetimeUTC, refraction=False, temperature=10.0, pressure=101325.0):
 55def solar_elevation_angle( lat, lon, alt, datetimeUTC,
 56                       refraction=False, temperature=10., pressure=101325. ):
 57    '''Solar elevation angle (degrees) above the horizon
 58
 59    The altitude parameter should be the vertical distance 
 60    above the surrounding terrain that defines the horizon,
 61    not necessarily the altitude above sea level or the altitude above ground level.
 62    For example, on a mountain peak that is 4000 m above sea level and 
 63    1500 m above the surrounding plateau, the relevant altitude is 1500 m.
 64    For an observer on the plateau, the relevant altitude is 0 m.
 65
 66    See documentation for `solar_zenith_angle` and `horizon_zenith_angle`.
 67
 68    Parameters
 69    ----------
 70    lat : float or ndarray
 71        latitude in degrees
 72    lon : float or ndarray
 73        longitudes in degrees
 74    alt : float or ndarray
 75        altitude above surrounding terrain that defines the horizon, meters
 76    datetimeUTC : pandas.Timestamp, datetime, or str
 77        date and time in UTC
 78    refraction : bool, optional (default=False)
 79        specifies whether to account for atmospheric refraction
 80    temperature : float or ndarray, optional (default=10)
 81        surface atmospheric temperature (Celsius), only used for refraction calculation
 82    pressure : float or ndarray, optional (default=101325)
 83        surface atmospheric pressure (Pa), only used for refraction calculation
 84    
 85    Returns
 86    -------
 87    sea : float or ndarray
 88        solar elevation angle in degrees at the designated locations and times
 89        If refraction=False, this is the true solar elevation angle
 90        If refraction=True, this is the apparent solar elevation angle
 91    
 92    '''
 93
 94    if refraction and np.any(alt):
 95        warnings.warn( 'Atmospheric refraction is calculated for surface conditions, '
 96                    + 'but an altitude above the surface was specified',
 97                     category=UserWarning,
 98                     stacklevel=2 )
 99
100    sea = horizon_zenith_angle( alt ) \
101         - solar_zenith_angle( lat, lon, datetimeUTC, refraction, temperature, pressure )
102
103    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
  • datetimeUTC (pandas.Timestamp, datetime, or str): date and time in UTC
  • 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, datetimeUTC, refraction=False, temperature=10.0, pressure=101325.0):
105def solar_zenith_angle( lat, lon, datetimeUTC, 
106                       refraction=False, temperature=10., pressure=101325. ):
107    '''Solar zenith angle (degrees) for a given latitude, longitude, date and time.
108    
109    Accounts for equation of time and (optionally) for atmospheric refraction.
110    Altitude of the observer is not accounted for, which can be important when the sun 
111    is near the horizon. 
112    
113    Results are accurate to tenths of a degree, except where altitude is important
114    (< 20 degrees solar elevation)
115
116    Parameters
117    ----------
118    lat : float or ndarray
119        latitude in degrees
120    lon : float or ndarray
121        longitudes in degrees
122    datetimeUTC : pandas.Timestamp, datetime, or str
123        date and time in UTC
124    refraction : bool, optional (default=False)
125        specifies whether to account for atmospheric refraction
126    temperature : float or ndarray, optional (default=10)
127        surface atmospheric temperature (Celsius), only used for refraction calculation
128    pressure : float or ndarray, optional (default=101325)
129        surface atmospheric pressure (Pa), only used for refraction calculation
130    
131    Returns
132    -------
133    sza : float or ndarray
134        solar zenith angle in degrees at the designated locations and times
135        If refraction=False, this is the true solar zenith angle
136        If refraction=True, this is the apparent solar zenith angle
137    '''
138    # Convert to pandas Timestamp, if needed
139    datetimeUTC = _to_timestamp(datetimeUTC)
140
141    # Solar declination, degrees
142    dec = solar_declination( datetimeUTC )
143
144    # Hour angle, degrees
145    Ha = solar_hour_angle( lon, datetimeUTC )
146
147    # True solar zenith angle, radians
148    sza = np.arccos( np.sin(lat*pi180) * np.sin(dec*pi180) + \
149          np.cos(lat*pi180) * np.cos(dec*pi180) * np.cos(Ha*pi180) )
150
151    # Convert radians -> degrees
152    sza /= pi180
153
154    if refraction:
155        # Subtract refraction angle (degrees) from zenith angle.
156        # SZA is always smaller due to refraction.
157        sza -= refraction_angle( 90-sza, pressure, temperature )
158
159    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
  • datetimeUTC (pandas.Timestamp, datetime, or str): date and time in UTC
  • 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 equation_of_time(date):
161def equation_of_time( date ):
162    '''Equation of time (degrees) for specified date
163    
164    Implements the "alternative equation" from Wikipedia, derived from
165    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
166    Results checked against NOAA solar calculator and agree within 10 seconds.
167    
168    Argument
169    --------
170    date : pandas.Timestamp, date, or datetime
171        date for calculation
172
173    Returns
174    -------
175    eot : float
176        equation of time in degrees on the specified date
177    '''
178    # Convert to pandas Timestamp, if needed
179    date = _to_timestamp(date)
180
181    # Equation of time, accounts for the solar day differing slightly from 24 hr
182    doy = date.dayofyear
183    W = 360 / 365.24
184    A = W * (doy+10)
185    B = A + 1.914 * np.sin( W * (doy-2) * pi180 )
186    C = ( A - np.arctan2( np.tan(B*pi180), np.cos(23.44*pi180) ) / pi180 ) / 180
187
188    # Equation of time in minutes of an hour
189    eotmin = 720 * ( C - np.round(C) )
190
191    # Equation of time, minutes -> degrees
192    eot = eotmin / 60 * 360 / 24
193
194    return eot

Equation of time (degrees) for specified date

Implements the "alternative equation" from Wikipedia, derived from 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 Results checked against NOAA solar calculator and agree within 10 seconds.

Argument

date : pandas.Timestamp, date, or datetime date for calculation

Returns
  • eot (float): equation of time in degrees on the specified date
def horizon_zenith_angle(alt):
196def horizon_zenith_angle( alt ):
197    '''Angle from the zenith to the horizon
198    
199    The horizon is the locii of points where a line from the 
200    observation location to the ellipsoid is tangent to the ellipsoid surface.
201    
202    The altitude parameter should be the vertical distance 
203    above the surrounding terrain that defines the horizon,
204    not necessarily the altitude above sea level or the altitude above ground level.
205    For example, on a mountain peak that is 4000 m above sea level and 
206    1500 m above the surrounding plateau, the relevant altitude is 1500 m.
207    For an observer on the plateau, the relevant altitude is 0 m.
208
209    The implementation below assumes a spherical Earth.
210    Results using the WGS84 ellipsoid (see commented code below)
211    differ from the spherical case by << 1°. Terrain,
212    which is neglected here, has a larger effect on the horizon
213    location, so the simpler spherical calculation is appropriate. 
214
215    Parameters
216    ----------
217    lat : float or ndarray
218        latitude in degrees
219    alt : float or ndarray
220        altitude above surrounding terrain that defines the horizon, meters
221        
222    Returns
223    -------
224    hza : float or ndarray
225        horizon zenith angle in degrees
226    '''
227
228    # WGS84 ellipsoid parameters
229    # semi-major radius, m
230    r_earth = 6378137.0
231    # ellipsoidal flattening, unitless
232    f = 1/298.257223563
233
234    # Horizon zenith angle, degrees (spherical earth)
235    hza = 180 - np.arcsin( r_earth / ( r_earth + alt ) ) / pi180
236
237    ## Ellipsoidal Earth
238    # # Eccentricity of ellipsoid
239    # ecc = f * (2-f)
240    # # Local (i.e. prime vertical) radius of curvature at latitude
241    # N = r_earth / np.sqrt( 1 - ecc**2 * np.sin(lat*pi180)**2 )
242    # # Horizon zenith angle, degrees
243    # hza = 180 - np.arcsin( N / (N+alt) ) / pi180
244
245    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 solar_declination(date):
247def solar_declination( date ):
248    '''Calculate solar declination (degrees) for specified date
249    
250    Implements Eq. 9.68-9.72 from M.Z. Jacobson, Fundamentals of Atmospheric Modeling
251    
252    Argument
253    --------
254    date : pandas.Timestamp, date, datetime, or str
255        date for calculation
256
257    Returns
258    -------
259    dec : float
260        solar declination in degrees at the specified date
261    '''
262
263    # Convert to pandas Timestamp, if needed
264    date = _to_timestamp(date)
265
266     # Number of days since beginning of 2000
267    NJD = np.floor( date.to_julian_date() - pd.Timestamp(2000,1,1).to_julian_date() )
268
269    # Obliquity, degrees
270    ob = 23.439 - 4e-7 * NJD
271
272    # Parameters for ecliptic, degrees
273    gm = 357.528 + 0.9856003 * NJD
274    lm = 280.460 + 0.9856474 * NJD
275
276    # Ecliptic longitude of sun, degrees
277    ec = lm + 1.915 * np.sin( gm * pi180 ) + 0.020 * np.sin( 2 * gm * pi180 )
278
279    #Solar declination, degrees
280    dec = np.arcsin( np.sin( ob * pi180 ) * np.sin( ec * pi180 ) ) / pi180
281
282    return dec

Calculate solar declination (degrees) for specified date

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

Argument

date : pandas.Timestamp, date, datetime, or str date for calculation

Returns
  • dec (float): solar declination in degrees at the specified date
def solar_hour_angle(lon, datetimeUTC):
284def solar_hour_angle( lon, datetimeUTC ):
285    '''Solar hour angle (degrees) for specified longitude, date and time
286
287    Hour angle is the angular displacement of the sun from the local meridian.
288    It is zero at local noon, negative in the morning, and positive is afternoon.
289    
290    Parameters
291    ----------
292    lon : float
293        longitude in degrees east
294    datetimeUTC : pandas.Timestamp or datetime
295        date and time for calculation, must be UTC
296    
297    Returns
298    -------
299    ha : float
300        hour angle in degrees at the specified location and time
301    '''
302
303    # Convert to pandas Timestamp, if needed
304    datetimeUTC = _to_timestamp(datetimeUTC)
305
306    # Hour angle for mean solar time.
307    # Actual solar position has a small offset given by the equation of time (below)
308    Ha = ( datetimeUTC.hour + datetimeUTC.minute / 60 - 12 ) * 15 + lon
309
310    # Add equation of time to the hour angle, degrees
311    Ha += equation_of_time( datetimeUTC )
312
313    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 (pandas.Timestamp or datetime): date and time for calculation, must be UTC
Returns
  • ha (float): hour angle in degrees at the specified location and time
def refraction_angle(true_elevation_angle, pressure=101325.0, temperature_celsius=10.0):
315def refraction_angle( true_elevation_angle, pressure=101325., temperature_celsius=10. ):
316    '''Atmospheric refraction angle for light passing through Earth's atmosphere
317
318    The apparent locations in the sky of objects outsides Earth's atmosphere 
319    differs from their true locations due to atmospheric refraction. 
320    (e.g. sun and moon when they rise and set)
321    The apparent elevation of an object above the horizon is
322    apparent elevation angle = (true elevation angle) + (refraction angle)
323    
324    The equations here are from Saemundsson/Bennett, whose calculations use
325    a typical vertical profile of atmospheric density (i.e. temperature and pressure).
326    The profiles can be rescaled to a particular surface temperature and pressure
327    to approximately account for varying atmospheric conditions.
328    Accurate refraction calculations should use fully specified vertical profile
329    of temperature and pressure, which cannot be done here.
330
331    Parameters
332    ----------
333    true_elevation_angle : float
334        degrees above horizon of sun or other object
335    pressure : float (default=101325)
336        surface atmospheric pressure (Pa)
337    temperature_celsius : float (default=10)
338        surface atmospheric temperature (C)
339
340    Returns
341    -------
342    angle : float
343        refraction angle in degrees. Value is zero when apparent elevation is below horizon
344    '''
345    # Refraction angle, arcminutes
346    R = 1.02 / np.tan( ( true_elevation_angle + 10.3 / (true_elevation_angle + 5.11) ) * pi180 )
347    # Account for temperature and pressure, arcminutes
348    R = R * pressure / 101325 * 283 / ( 273 + temperature_celsius )
349    # Convert arcminutes -> degrees
350    R /= 60
351
352    # Result must be positive
353    R = np.maximum(R,0)
354
355    # Refraction defined only when the apparent elevation angle is positive
356    # Set refraction to zero when the apparent elevation is below horizon
357    refraction_angle = np.where( true_elevation_angle + R <= 0, 0, R)
358
359    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, datetimeUTC, refraction=False, temperature=10.0, pressure=101325.0):
105def solar_zenith_angle( lat, lon, datetimeUTC, 
106                       refraction=False, temperature=10., pressure=101325. ):
107    '''Solar zenith angle (degrees) for a given latitude, longitude, date and time.
108    
109    Accounts for equation of time and (optionally) for atmospheric refraction.
110    Altitude of the observer is not accounted for, which can be important when the sun 
111    is near the horizon. 
112    
113    Results are accurate to tenths of a degree, except where altitude is important
114    (< 20 degrees solar elevation)
115
116    Parameters
117    ----------
118    lat : float or ndarray
119        latitude in degrees
120    lon : float or ndarray
121        longitudes in degrees
122    datetimeUTC : pandas.Timestamp, datetime, or str
123        date and time in UTC
124    refraction : bool, optional (default=False)
125        specifies whether to account for atmospheric refraction
126    temperature : float or ndarray, optional (default=10)
127        surface atmospheric temperature (Celsius), only used for refraction calculation
128    pressure : float or ndarray, optional (default=101325)
129        surface atmospheric pressure (Pa), only used for refraction calculation
130    
131    Returns
132    -------
133    sza : float or ndarray
134        solar zenith angle in degrees at the designated locations and times
135        If refraction=False, this is the true solar zenith angle
136        If refraction=True, this is the apparent solar zenith angle
137    '''
138    # Convert to pandas Timestamp, if needed
139    datetimeUTC = _to_timestamp(datetimeUTC)
140
141    # Solar declination, degrees
142    dec = solar_declination( datetimeUTC )
143
144    # Hour angle, degrees
145    Ha = solar_hour_angle( lon, datetimeUTC )
146
147    # True solar zenith angle, radians
148    sza = np.arccos( np.sin(lat*pi180) * np.sin(dec*pi180) + \
149          np.cos(lat*pi180) * np.cos(dec*pi180) * np.cos(Ha*pi180) )
150
151    # Convert radians -> degrees
152    sza /= pi180
153
154    if refraction:
155        # Subtract refraction angle (degrees) from zenith angle.
156        # SZA is always smaller due to refraction.
157        sza -= refraction_angle( 90-sza, pressure, temperature )
158
159    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
  • datetimeUTC (pandas.Timestamp, datetime, or str): date and time in UTC
  • 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, datetimeUTC):
16def solar_azimuth_angle( lat, lon, datetimeUTC ):
17    '''Solar azimuth angle (degrees) for a latitude, longitude, date and time
18    
19    SAA is degrees clockwise from north.
20    
21    Parameters
22    ----------
23    lat : float or ndarray
24        latitude in degrees
25    lon : float or ndarray
26        longitudes in degrees
27    datetimeUTC : pandas.Timestamp, datetime, or str
28        date and time in UTC
29
30    Returns
31    -------
32    saa : float or ndarray
33        solar azimuth angle in degrees (clockwise from north)
34    '''
35    # Convert to pandas Timestamp, if needed
36    datetimeUTC = _to_timestamp(datetimeUTC)
37
38    # Solar declination, degrees
39    dec = solar_declination( datetimeUTC )
40
41    # Hour angle, degrees
42    Ha = solar_hour_angle( lon, datetimeUTC )
43
44    # Solar zenith angle, degrees
45    # Use true sza, without refraction
46    zen = sza( lat, lon, datetimeUTC, refraction=False )
47
48    # Solar azimuth angle, degrees
49    saa = np.arcsin( -np.sin( Ha*pi180 ) * np.cos( dec*pi180 ) /
50            np.sin( zen*pi180 ) ) / pi180
51
52    # Change range [-180,180] to [0,360]
53    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
  • datetimeUTC (pandas.Timestamp, datetime, or str): date and time in UTC
Returns
  • saa (float or ndarray): solar azimuth angle in degrees (clockwise from north)
def sea( lat, lon, alt, datetimeUTC, refraction=False, temperature=10.0, pressure=101325.0):
 55def solar_elevation_angle( lat, lon, alt, datetimeUTC,
 56                       refraction=False, temperature=10., pressure=101325. ):
 57    '''Solar elevation angle (degrees) above the horizon
 58
 59    The altitude parameter should be the vertical distance 
 60    above the surrounding terrain that defines the horizon,
 61    not necessarily the altitude above sea level or the altitude above ground level.
 62    For example, on a mountain peak that is 4000 m above sea level and 
 63    1500 m above the surrounding plateau, the relevant altitude is 1500 m.
 64    For an observer on the plateau, the relevant altitude is 0 m.
 65
 66    See documentation for `solar_zenith_angle` and `horizon_zenith_angle`.
 67
 68    Parameters
 69    ----------
 70    lat : float or ndarray
 71        latitude in degrees
 72    lon : float or ndarray
 73        longitudes in degrees
 74    alt : float or ndarray
 75        altitude above surrounding terrain that defines the horizon, meters
 76    datetimeUTC : pandas.Timestamp, datetime, or str
 77        date and time in UTC
 78    refraction : bool, optional (default=False)
 79        specifies whether to account for atmospheric refraction
 80    temperature : float or ndarray, optional (default=10)
 81        surface atmospheric temperature (Celsius), only used for refraction calculation
 82    pressure : float or ndarray, optional (default=101325)
 83        surface atmospheric pressure (Pa), only used for refraction calculation
 84    
 85    Returns
 86    -------
 87    sea : float or ndarray
 88        solar elevation angle in degrees at the designated locations and times
 89        If refraction=False, this is the true solar elevation angle
 90        If refraction=True, this is the apparent solar elevation angle
 91    
 92    '''
 93
 94    if refraction and np.any(alt):
 95        warnings.warn( 'Atmospheric refraction is calculated for surface conditions, '
 96                    + 'but an altitude above the surface was specified',
 97                     category=UserWarning,
 98                     stacklevel=2 )
 99
100    sea = horizon_zenith_angle( alt ) \
101         - solar_zenith_angle( lat, lon, datetimeUTC, refraction, temperature, pressure )
102
103    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
  • datetimeUTC (pandas.Timestamp, datetime, or str): date and time in UTC
  • 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(date):
161def equation_of_time( date ):
162    '''Equation of time (degrees) for specified date
163    
164    Implements the "alternative equation" from Wikipedia, derived from
165    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
166    Results checked against NOAA solar calculator and agree within 10 seconds.
167    
168    Argument
169    --------
170    date : pandas.Timestamp, date, or datetime
171        date for calculation
172
173    Returns
174    -------
175    eot : float
176        equation of time in degrees on the specified date
177    '''
178    # Convert to pandas Timestamp, if needed
179    date = _to_timestamp(date)
180
181    # Equation of time, accounts for the solar day differing slightly from 24 hr
182    doy = date.dayofyear
183    W = 360 / 365.24
184    A = W * (doy+10)
185    B = A + 1.914 * np.sin( W * (doy-2) * pi180 )
186    C = ( A - np.arctan2( np.tan(B*pi180), np.cos(23.44*pi180) ) / pi180 ) / 180
187
188    # Equation of time in minutes of an hour
189    eotmin = 720 * ( C - np.round(C) )
190
191    # Equation of time, minutes -> degrees
192    eot = eotmin / 60 * 360 / 24
193
194    return eot

Equation of time (degrees) for specified date

Implements the "alternative equation" from Wikipedia, derived from 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 Results checked against NOAA solar calculator and agree within 10 seconds.

Argument

date : pandas.Timestamp, date, or datetime date for calculation

Returns
  • eot (float): equation of time in degrees on the specified date
def solarDeclination(date):
247def solar_declination( date ):
248    '''Calculate solar declination (degrees) for specified date
249    
250    Implements Eq. 9.68-9.72 from M.Z. Jacobson, Fundamentals of Atmospheric Modeling
251    
252    Argument
253    --------
254    date : pandas.Timestamp, date, datetime, or str
255        date for calculation
256
257    Returns
258    -------
259    dec : float
260        solar declination in degrees at the specified date
261    '''
262
263    # Convert to pandas Timestamp, if needed
264    date = _to_timestamp(date)
265
266     # Number of days since beginning of 2000
267    NJD = np.floor( date.to_julian_date() - pd.Timestamp(2000,1,1).to_julian_date() )
268
269    # Obliquity, degrees
270    ob = 23.439 - 4e-7 * NJD
271
272    # Parameters for ecliptic, degrees
273    gm = 357.528 + 0.9856003 * NJD
274    lm = 280.460 + 0.9856474 * NJD
275
276    # Ecliptic longitude of sun, degrees
277    ec = lm + 1.915 * np.sin( gm * pi180 ) + 0.020 * np.sin( 2 * gm * pi180 )
278
279    #Solar declination, degrees
280    dec = np.arcsin( np.sin( ob * pi180 ) * np.sin( ec * pi180 ) ) / pi180
281
282    return dec

Calculate solar declination (degrees) for specified date

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

Argument

date : pandas.Timestamp, date, datetime, or str date for calculation

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