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
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
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
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
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
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
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)
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
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
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
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)
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
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
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