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