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