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 + datetimeUTC.second / 3600 - 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
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)
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
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
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
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
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
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 + datetimeUTC.second / 3600 - 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
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
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
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)
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
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
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