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

Calculate 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 hour_angle(lon, datetimeUTC):
 90def hour_angle( lon, datetimeUTC ):
 91    '''Compute hour angle (degrees) for specified longitude, date and time
 92
 93    Hour angle is the angular displacement of the sun from the local meridian.
 94    It is zero at local noon, negative in the morning, and positive is afternoon.
 95    
 96    Parameters
 97    ----------
 98    lon : float
 99        longitude in degrees east
100    datetimeUTC : pandas.Timestamp or datetime
101        date and time for calculation, must be UTC
102    
103    Returns
104    -------
105    ha : float
106        hour angle in degrees at the specified location and time
107    '''
108
109    # Convert to pandas Timestamp, if needed
110    if not isinstance(datetimeUTC, pd.Timestamp):
111        datetimeUTC = pd.Timestamp(datetimeUTC)
112
113    # Hour angle for mean solar time.
114    # Actual solar position has a small offset given by the equation of time (below)
115    Ha = ( datetimeUTC.hour + datetimeUTC.minute / 60 - 12 ) * 15 + lon
116
117    # Add equation of time to the hour angle, degrees
118    Ha += equation_of_time( datetimeUTC )
119
120    return Ha

Compute 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):
122def refraction_angle( true_elevation_angle, pressure=101325., temperature_celsius=10. ):
123    '''Atmospheric refraction angle
124
125    apparent elevation angle = (true elevation angle) + (refraction angle)
126    Equation from Saemundsson/Bennett
127
128    Parameters
129    ----------
130    true_elevation_angle : float
131        degrees above horizon of sun or other object
132    pressure : float
133        surface atmospheric pressure (Pa)
134    temperature_celsius : float
135        surface atmospheric temperature (C)
136
137    Returns
138    -------
139    angle : float
140        refraction angle in degrees. Value is zero when apparent elevation is below horizon
141    '''
142    # Refraction angle, arcminutes
143    R = 1.02 / np.tan( ( true_elevation_angle + 10.3 / (true_elevation_angle + 5.11) ) * pi180 )
144    # Account for temperature and pressure, arcminutes
145    R = R * pressure / 101325 * 283 / ( 273 + temperature_celsius )
146    # Convert arcminutes -> degrees
147    R /= 60
148
149    # Result must be positive
150    R = np.maximum(R,0)
151
152    # Refraction defined only when the apparent elevation angle is positive
153    # Set refraction to zero when the apparent elevation is below horizon
154    refraction_angle = np.where( true_elevation_angle + R <= 0, 0, R)
155
156    return refraction_angle

Atmospheric refraction angle

apparent elevation angle = (true elevation angle) + (refraction angle) Equation from Saemundsson/Bennett

Parameters
  • true_elevation_angle (float): degrees above horizon of sun or other object
  • pressure (float): surface atmospheric pressure (Pa)
  • temperature_celsius (float): surface atmospheric temperature (C)
Returns
  • angle (float): refraction angle in degrees. Value is zero when apparent elevation is below horizon
def solar_zenith_angle( lat, lon, datetimeUTC, refraction=False, temperature=10.0, pressure=101325.0):
158def solar_zenith_angle( lat, lon, datetimeUTC, 
159                       refraction=False, temperature=10., pressure=101325. ):
160    '''Calculate solar zenith angle for a given latitude, longitude, date and time.
161    
162    Accounts for equation of time and (optionally) for atmospheric refraction.
163    Altitude of the observer is not accounted for, which can be important when the sun 
164    is near the horizon. 
165    
166    Results are accurate to tenths of a degree, except where altitude is important
167    (< 20 degrees solar elevation)
168
169    Parameters
170    ----------
171    lat : float or ndarray
172        latitude in degrees
173    lon : float or ndarray
174        longitudes in degrees
175    datetimeUTC : pandas.Timestamp, datetime, or str
176        date and time in UTC
177    refraction : bool, optional (default=False)
178        specifies whether to account for atmospheric refraction
179    temperature : float or ndarray, optional (default=10)
180        surface atmospheric temperature (Celsius), only used for refraction calculation
181    pressure : float or ndarray, optional (default=101325)
182        surface atmospheric pressure (Pa), only used for refraction calculation
183    
184    Returns
185    -------
186    sza : float or ndarray
187        solar zenith angle in degrees at the designated locations and times
188        If refraction=False, this is the true solar zenith angle
189        If refraction=True, this is the apparent solar zenith angle
190    '''
191    # Convert to pandas Timestamp, if needed
192    if not isinstance(datetimeUTC, pd.Timestamp):
193        datetimeUTC = pd.Timestamp(datetimeUTC)
194
195    # Solar declination, degrees
196    dec = solar_declination( datetimeUTC )
197
198    # Hour angle, degrees
199    Ha = hour_angle( lon, datetimeUTC )
200
201    # True solar zenith angle, radians
202    sza = np.arccos( np.sin(lat*pi180) * np.sin(dec*pi180) + \
203          np.cos(lat*pi180) * np.cos(dec*pi180) * np.cos(Ha*pi180) )
204
205    # Convert radians -> degrees
206    sza /= pi180
207
208    if refraction:
209        # Subtract refraction angle (degrees) from zenith angle.
210        # SZA is always smaller due to refraction.
211        sza -= refraction_angle( 90-sza, pressure, temperature )
212
213    return sza

Calculate solar zenith angle 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 solar_azimuth_angle(lat, lon, datetimeUTC):
215def solar_azimuth_angle( lat, lon, datetimeUTC ):
216    '''Solar azimuth angle (degrees) for a latitude, longitude, date and time
217    
218    SAA is degrees clockwise from north.
219    
220    Parameters
221    ----------
222    lat : float or ndarray
223        latitude in degrees
224    lon : float or ndarray
225        longitudes in degrees
226    datetimeUTC : pandas.Timestamp, datetime, or str
227        date and time in UTC
228
229    Returns
230    -------
231    saa : float or ndarray
232        solar azimuth angle in degrees (clockwise from north)
233    '''
234    # Convert to pandas Timestamp, if needed
235    if not isinstance(datetimeUTC, pd.Timestamp):
236        datetimeUTC = pd.Timestamp(datetimeUTC)
237
238    # Solar declination, degrees
239    dec = solar_declination( datetimeUTC )
240
241    # Hour angle, degrees
242    Ha = hour_angle( lon, datetimeUTC )
243
244    # Solar zenith angle, degrees
245    # Use true sza, without refraction
246    zen = sza( lat, lon, datetimeUTC, refraction=False )
247
248    # Solar azimuth angle, degrees
249    saa = np.arcsin( -np.sin( Ha*pi180 ) * np.cos( dec*pi180 ) /
250            np.sin( zen*pi180 ) ) / pi180
251
252    # Change range [-180,180] to [0,360]
253    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 horizon_zenith_angle(lat, alt):
255def horizon_zenith_angle( lat, alt ):
256    '''Angle from the zenith to the horizon
257    
258    The horizon is the locii of points where a line from the 
259    observation location to the ellipsoid is tangent to the ellipsoid surface.
260    
261    The altitude parameter should be the vertical distance 
262    above the surrounding terrain that defines the horizon,
263    not necessarily the altitude above sea level or the altitude above ground level.
264    For example, on a mountain peak that is 4000 m above sea level and 
265    1500 m above the surrounding plateau, the relevant altitude is 1500 m.
266    For an observer on the plateau, the relevant altitude is 0 m.
267
268    The implementation below assumes a spherical Earth.
269    Results using the WGS84 ellipsoid (see commented code below)
270    differ from the spherical case by << 1°. Terrain,
271    which is neglected here, has a larger effect on the horizon
272    location, so the simpler spherical calculation is appropriate. 
273
274    Parameters
275    ----------
276    lat : float or ndarray
277        latitude in degrees
278    alt : float or ndarray
279        altitude above surrounding terrain that defines the horizon, meters
280        
281    Returns
282    -------
283    hza : float or ndarray
284        horizon zenith angle in degrees
285    '''
286
287    # WGS84 ellipsoid parameters
288    # semi-major radius, m
289    r_earth = 6378137.0
290    # ellipsoidal flattening, unitless
291    f = 1/298.257223563
292
293    # Horizon zenith angle, degrees (spherical earth)
294    hza = 180 - np.arcsin( r_earth / ( r_earth + alt ) ) / pi180
295
296    ## Ellipsoidal Earth
297    # # Eccentricity of ellipsoid
298    # ecc = f * (2-f)
299    # # Local (i.e. prime vertical) radius of curvature at latitude
300    # N = r_earth / np.sqrt( 1 - ecc**2 * np.sin(lat*pi180)**2 )
301    # # Horizon zenith angle, degrees
302    # hza = 180 - np.arcsin( N / (N+alt) ) / pi180
303
304    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_elevation_angle( lat, lon, alt, datetimeUTC, refraction=False, temperature=10.0, pressure=101325.0):
306def solar_elevation_angle( lat, lon, alt, datetimeUTC,
307                       refraction=False, temperature=10., pressure=101325. ):
308    '''Solar elevation angle above the horizon
309
310    The altitude parameter should be the vertical distance 
311    above the surrounding terrain that defines the horizon,
312    not necessarily the altitude above sea level or the altitude above ground level.
313    For example, on a mountain peak that is 4000 m above sea level and 
314    1500 m above the surrounding plateau, the relevant altitude is 1500 m.
315    For an observer on the plateau, the relevant altitude is 0 m.
316
317    See documentation for `solar_zenith_angle` and `horizon_zenith_angle`.
318
319    Parameters
320    ----------
321    lat : float or ndarray
322        latitude in degrees
323    lon : float or ndarray
324        longitudes in degrees
325    alt : float or ndarray
326        altitude above surrounding terrain that defines the horizon, meters
327    datetimeUTC : pandas.Timestamp, datetime, or str
328        date and time in UTC
329    refraction : bool, optional (default=False)
330        specifies whether to account for atmospheric refraction
331    temperature : float or ndarray, optional (default=10)
332        surface atmospheric temperature (Celsius), only used for refraction calculation
333    pressure : float or ndarray, optional (default=101325)
334        surface atmospheric pressure (Pa), only used for refraction calculation
335    
336    Returns
337    -------
338    sea : float or ndarray
339        solar elevation angle in degrees at the designated locations and times
340        If refraction=False, this is the true solar elevation angle
341        If refraction=True, this is the apparent solar elevation angle
342    
343    '''
344
345    if refraction and np.any(alt):
346        warnings.warn( 'Atmospheric refraction is calculated for surface conditions, '
347                    + 'but an altitude above the surface was specified',
348                     category=UserWarning,
349                     stacklevel=2 )
350
351    sea = horizon_zenith_angle( lat, alt ) \
352         - solar_zenith_angle( lat, lon, datetimeUTC, refraction, temperature, pressure )
353
354    return sea

Solar elevation angle 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 sza( lat, lon, datetimeUTC, refraction=False, temperature=10.0, pressure=101325.0):
158def solar_zenith_angle( lat, lon, datetimeUTC, 
159                       refraction=False, temperature=10., pressure=101325. ):
160    '''Calculate solar zenith angle for a given latitude, longitude, date and time.
161    
162    Accounts for equation of time and (optionally) for atmospheric refraction.
163    Altitude of the observer is not accounted for, which can be important when the sun 
164    is near the horizon. 
165    
166    Results are accurate to tenths of a degree, except where altitude is important
167    (< 20 degrees solar elevation)
168
169    Parameters
170    ----------
171    lat : float or ndarray
172        latitude in degrees
173    lon : float or ndarray
174        longitudes in degrees
175    datetimeUTC : pandas.Timestamp, datetime, or str
176        date and time in UTC
177    refraction : bool, optional (default=False)
178        specifies whether to account for atmospheric refraction
179    temperature : float or ndarray, optional (default=10)
180        surface atmospheric temperature (Celsius), only used for refraction calculation
181    pressure : float or ndarray, optional (default=101325)
182        surface atmospheric pressure (Pa), only used for refraction calculation
183    
184    Returns
185    -------
186    sza : float or ndarray
187        solar zenith angle in degrees at the designated locations and times
188        If refraction=False, this is the true solar zenith angle
189        If refraction=True, this is the apparent solar zenith angle
190    '''
191    # Convert to pandas Timestamp, if needed
192    if not isinstance(datetimeUTC, pd.Timestamp):
193        datetimeUTC = pd.Timestamp(datetimeUTC)
194
195    # Solar declination, degrees
196    dec = solar_declination( datetimeUTC )
197
198    # Hour angle, degrees
199    Ha = hour_angle( lon, datetimeUTC )
200
201    # True solar zenith angle, radians
202    sza = np.arccos( np.sin(lat*pi180) * np.sin(dec*pi180) + \
203          np.cos(lat*pi180) * np.cos(dec*pi180) * np.cos(Ha*pi180) )
204
205    # Convert radians -> degrees
206    sza /= pi180
207
208    if refraction:
209        # Subtract refraction angle (degrees) from zenith angle.
210        # SZA is always smaller due to refraction.
211        sza -= refraction_angle( 90-sza, pressure, temperature )
212
213    return sza

Calculate solar zenith angle 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):
215def solar_azimuth_angle( lat, lon, datetimeUTC ):
216    '''Solar azimuth angle (degrees) for a latitude, longitude, date and time
217    
218    SAA is degrees clockwise from north.
219    
220    Parameters
221    ----------
222    lat : float or ndarray
223        latitude in degrees
224    lon : float or ndarray
225        longitudes in degrees
226    datetimeUTC : pandas.Timestamp, datetime, or str
227        date and time in UTC
228
229    Returns
230    -------
231    saa : float or ndarray
232        solar azimuth angle in degrees (clockwise from north)
233    '''
234    # Convert to pandas Timestamp, if needed
235    if not isinstance(datetimeUTC, pd.Timestamp):
236        datetimeUTC = pd.Timestamp(datetimeUTC)
237
238    # Solar declination, degrees
239    dec = solar_declination( datetimeUTC )
240
241    # Hour angle, degrees
242    Ha = hour_angle( lon, datetimeUTC )
243
244    # Solar zenith angle, degrees
245    # Use true sza, without refraction
246    zen = sza( lat, lon, datetimeUTC, refraction=False )
247
248    # Solar azimuth angle, degrees
249    saa = np.arcsin( -np.sin( Ha*pi180 ) * np.cos( dec*pi180 ) /
250            np.sin( zen*pi180 ) ) / pi180
251
252    # Change range [-180,180] to [0,360]
253    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):
306def solar_elevation_angle( lat, lon, alt, datetimeUTC,
307                       refraction=False, temperature=10., pressure=101325. ):
308    '''Solar elevation angle above the horizon
309
310    The altitude parameter should be the vertical distance 
311    above the surrounding terrain that defines the horizon,
312    not necessarily the altitude above sea level or the altitude above ground level.
313    For example, on a mountain peak that is 4000 m above sea level and 
314    1500 m above the surrounding plateau, the relevant altitude is 1500 m.
315    For an observer on the plateau, the relevant altitude is 0 m.
316
317    See documentation for `solar_zenith_angle` and `horizon_zenith_angle`.
318
319    Parameters
320    ----------
321    lat : float or ndarray
322        latitude in degrees
323    lon : float or ndarray
324        longitudes in degrees
325    alt : float or ndarray
326        altitude above surrounding terrain that defines the horizon, meters
327    datetimeUTC : pandas.Timestamp, datetime, or str
328        date and time in UTC
329    refraction : bool, optional (default=False)
330        specifies whether to account for atmospheric refraction
331    temperature : float or ndarray, optional (default=10)
332        surface atmospheric temperature (Celsius), only used for refraction calculation
333    pressure : float or ndarray, optional (default=101325)
334        surface atmospheric pressure (Pa), only used for refraction calculation
335    
336    Returns
337    -------
338    sea : float or ndarray
339        solar elevation angle in degrees at the designated locations and times
340        If refraction=False, this is the true solar elevation angle
341        If refraction=True, this is the apparent solar elevation angle
342    
343    '''
344
345    if refraction and np.any(alt):
346        warnings.warn( 'Atmospheric refraction is calculated for surface conditions, '
347                    + 'but an altitude above the surface was specified',
348                     category=UserWarning,
349                     stacklevel=2 )
350
351    sea = horizon_zenith_angle( lat, alt ) \
352         - solar_zenith_angle( lat, lon, datetimeUTC, refraction, temperature, pressure )
353
354    return sea

Solar elevation angle 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):
54def equation_of_time( date ):
55    '''Calculate equation of time (degrees) for specified date
56    
57    Implements the "alternative equation" from Wikipedia, derived from
58    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
59    Results checked against NOAA solar calculator and agree within 10 seconds.
60    
61    Argument
62    --------
63    date : pandas.Timestamp, date, or datetime
64        date for calculation
65
66    Returns
67    -------
68    eot : float
69        equation of time in degrees on the specified date
70    '''
71    # Convert to pandas Timestamp, if needed
72    if not isinstance(date, pd.Timestamp):
73        date = pd.Timestamp(date)
74
75    # Equation of time, accounts for the solar day differing slightly from 24 hr
76    doy = date.dayofyear
77    W = 360 / 365.24
78    A = W * (doy+10)
79    B = A + 1.914 * np.sin( W * (doy-2) * pi180 )
80    C = ( A - np.arctan2( np.tan(B*pi180), np.cos(23.44*pi180) ) / pi180 ) / 180
81
82    # Equation of time in minutes of an hour
83    eotmin = 720 * ( C - np.round(C) )
84
85    # Equation of time, minutes -> degrees
86    eot = eotmin / 60 * 360 / 24
87
88    return eot

Calculate 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):
16def solar_declination( date ):
17    '''Calculate solar declination (degrees) for specified date
18    
19    Implements Eq. 9.68-9.72 from M.Z. Jacobson, Fundamentals of Atmospheric Modeling
20    
21    Argument
22    --------
23    date : pandas.Timestamp, date, datetime, or str
24        date for calculation
25
26    Returns
27    -------
28    dec : float
29        solar declination in degrees at the specified date
30    '''
31
32    # Convert to pandas Timestamp, if needed
33    if not isinstance(date, pd.Timestamp):
34        date = pd.Timestamp(date)
35
36     # Number of days since beginning of 2000
37    NJD = np.floor( date.to_julian_date() - pd.Timestamp(2000,1,1).to_julian_date() )
38
39    # Obliquity, degrees
40    ob = 23.439 - 4e-7 * NJD
41
42    # Parameters for ecliptic, degrees
43    gm = 357.528 + 0.9856003 * NJD
44    lm = 280.460 + 0.9856474 * NJD
45
46    # Ecliptic longitude of sun, degrees
47    ec = lm + 1.915 * np.sin( gm * pi180 ) + 0.020 * np.sin( 2 * gm * pi180 )
48
49    #Solar declination, degrees
50    dec = np.arcsin( np.sin( ob * pi180 ) * np.sin( ec * pi180 ) ) / pi180
51
52    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