Source code for utils.calcSun

# UTILS/calcSun
"""
*********************
**Module**: utils.calcSun
*********************
This subpackage contains def to calculate sunrise/sunset

This includes the following defs:
    * :func:`utils.calcSun.getJD`: 
        calculate the julian date from a python datetime object
    * :func:`utils.calcSun.calcTimeJulianCent`: 
        convert Julian Day to centuries since J2000.0.
    * :func:`utils.calcSun.calcGeomMeanLongSun`: 
        calculate the Geometric Mean Longitude of the Sun (in degrees)
    * :func:`utils.calcSun.calcGeomMeanAnomalySun`: 
        calculate the Geometric Mean Anomaly of the Sun (in degrees)
    * :func:`utils.calcSun.calcEccentricityEarthOrbit`: 
        calculate the eccentricity of earth's orbit (unitless)
    * :func:`utils.calcSun.calcSunEqOfCenter`: 
        calculate the equation of center for the sun (in degrees)
    * :func:`utils.calcSun.calcSunTrueLong`: 
        calculate the true longitude of the sun (in degrees)
    * :func:`utils.calcSun.calcSunTrueAnomaly`: 
        calculate the true anamoly of the sun (in degrees)
    * :func:`utils.calcSun.calcSunRadVector`: 
        calculate the distance to the sun in AU (in degrees)
    * :func:`utils.calcSun.calcSunApparentLong`: 
        calculate the apparent longitude of the sun (in degrees)
    * :func:`utils.calcSun.calcMeanObliquityOfEcliptic`: 
        calculate the mean obliquity of the ecliptic (in degrees)
    * :func:`utils.calcSun.calcObliquityCorrection`: 
        calculate the corrected obliquity of the ecliptic (in degrees)
    * :func:`utils.calcSun.calcSunRtAscension`: 
        calculate the right ascension of the sun (in degrees)
    * :func:`utils.calcSun.calcSunDeclination`: 
        calculate the declination of the sun (in degrees)
    * :func:`utils.calcSun.calcEquationOfTime`: 
        calculate the difference between true solar time and mean solar time (output: equation of time in minutes of time)
    * :func:`utils.calcSun.calcHourAngleSunrise`: 
        calculate the hour angle of the sun at sunrise for the latitude (in radians)
    * :func:`utils.calcSun.calcAzEl`: 
        calculate sun azimuth and zenith angle
    * :func:`utils.calcSun.calcSolNoonUTC`: 
        calculate time of solar noon the given day at the given location on earth (in minutes since 0 UTC)
    * :func:`utils.calcSun.calcSolNoon`: 
        calculate time of solar noon the given day at the given location on earth (in minutes)
    * :func:`utils.calcSun.calcSunRiseSetUTC`: 
        calculate sunrise/sunset the given day at the given location on earth (in minutes since 0 UTC)
    * :func:`utils.calcSun.calcSunRiseSet`: 
        calculate sunrise/sunset the given day at the given location on earth (in minutes)
    * :func:`utils.calcSun.calcTerminator`: 
        calculate terminator position and solar zenith angle for a given julian date-time within latitude/longitude limits
        note that for plotting only, basemap has a built-in terminator

Source: http://www.esrl.noaa.gov/gmd/grad/solcalc/
Translated to Python by Sebastien de Larquier

*******************************
"""
import math
import numpy

[docs]def calcTimeJulianCent( jd ): """ Convert Julian Day to centuries since J2000.0. """ T = (jd - 2451545.0)/36525.0 return T
[docs]def calcGeomMeanLongSun( t ): """ Calculate the Geometric Mean Longitude of the Sun (in degrees) """ L0 = 280.46646 + t * ( 36000.76983 + t*0.0003032 ) while L0 > 360.0: L0 -= 360.0 while L0 < 0.0: L0 += 360.0 return L0 # in degrees
[docs]def calcGeomMeanAnomalySun( t ): """ Calculate the Geometric Mean Anomaly of the Sun (in degrees) """ M = 357.52911 + t * ( 35999.05029 - 0.0001537 * t) return M # in degrees
[docs]def calcEccentricityEarthOrbit( t ): """ Calculate the eccentricity of earth's orbit (unitless) """ e = 0.016708634 - t * ( 0.000042037 + 0.0000001267 * t) return e # unitless
[docs]def calcSunEqOfCenter( t ): """ Calculate the equation of center for the sun (in degrees) """ mrad = numpy.radians(calcGeomMeanAnomalySun(t)) sinm = numpy.sin(mrad) sin2m = numpy.sin(mrad+mrad) sin3m = numpy.sin(mrad+mrad+mrad) C = sinm * (1.914602 - t * (0.004817 + 0.000014 * t)) + sin2m * (0.019993 - 0.000101 * t) + sin3m * 0.000289 return C # in degrees
[docs]def calcSunTrueLong( t ): """ Calculate the true longitude of the sun (in degrees) """ l0 = calcGeomMeanLongSun(t) c = calcSunEqOfCenter(t) O = l0 + c return O # in degrees
[docs]def calcSunTrueAnomaly( t ): """ Calculate the true anamoly of the sun (in degrees) """ m = calcGeomMeanAnomalySun(t) c = calcSunEqOfCenter(t) v = m + c return v # in degrees
[docs]def calcSunRadVector( t ): """ Calculate the distance to the sun in AU (in degrees) """ v = calcSunTrueAnomaly(t) e = calcEccentricityEarthOrbit(t) R = (1.000001018 * (1. - e * e)) / ( 1. + e * numpy.cos( numpy.radians(v) ) ) return R # n AUs
[docs]def calcSunApparentLong( t ): """ Calculate the apparent longitude of the sun (in degrees) """ o = calcSunTrueLong(t) omega = 125.04 - 1934.136 * t SunLong = o - 0.00569 - 0.00478 * numpy.sin(numpy.radians(omega)) return SunLong # in degrees
[docs]def calcMeanObliquityOfEcliptic( t ): """ Calculate the mean obliquity of the ecliptic (in degrees) """ seconds = 21.448 - t*(46.8150 + t*(0.00059 - t*(0.001813))) e0 = 23.0 + (26.0 + (seconds/60.0))/60.0 return e0 # in degrees
[docs]def calcObliquityCorrection( t ): """ Calculate the corrected obliquity of the ecliptic (in degrees) """ e0 = calcMeanObliquityOfEcliptic(t) omega = 125.04 - 1934.136 * t e = e0 + 0.00256 * numpy.cos(numpy.radians(omega)) return e # in degrees
[docs]def calcSunRtAscension( t ): """ Calculate the right ascension of the sun (in degrees) """ e = calcObliquityCorrection(t) SunLong = calcSunApparentLong(t) tananum = ( numpy.cos(numpy.radians(e)) * numpy.sin(numpy.radians(SunLong)) ) tanadenom = numpy.cos(numpy.radians(SunLong)) alpha = numpy.degrees(anumpy.arctan2(tananum, tanadenom)) return alpha # in degrees
[docs]def calcSunDeclination( t ): """ Calculate the declination of the sun (in degrees) """ e = calcObliquityCorrection(t) SunLong = calcSunApparentLong(t) sint = numpy.sin(numpy.radians(e)) * numpy.sin(numpy.radians(SunLong)) theta = numpy.degrees(numpy.arcsin(sint)) return theta # in degrees
[docs]def calcEquationOfTime( t ): """ Calculate the difference between true solar time and mean solar time (output: equation of time in minutes of time) """ epsilon = calcObliquityCorrection(t) l0 = calcGeomMeanLongSun(t) e = calcEccentricityEarthOrbit(t) m = calcGeomMeanAnomalySun(t) y = numpy.tan(numpy.radians(epsilon/2.0)) y *= y sin2l0 = numpy.sin(numpy.radians(2.0 * l0)) sinm = numpy.sin(numpy.radians(m)) cos2l0 = numpy.cos(numpy.radians(2.0 * l0)) sin4l0 = numpy.sin(numpy.radians(4.0 * l0)) sin2m = numpy.sin(numpy.radians(2.0 * m)) Etime = y * sin2l0 - 2.0 * e * sinm + 4.0 * e * y * sinm * cos2l0 - 0.5 * y * y * sin4l0 - 1.25 * e * e * sin2m return numpy.degrees(Etime*4.0) # in minutes of time
[docs]def calcHourAngleSunrise( lat, solarDec ): """ Calculate the hour angle of the sun at sunrise for the latitude (in radians) """ latRad = numpy.radians(lat) sdRad = numpy.radians(solarDec) HAarg = numpy.cos(numpy.radians(90.833)) / ( numpy.cos(latRad)*numpy.cos(sdRad) ) - numpy.tan(latRad) * numpy.tan(sdRad) HA = numpy.arccos(HAarg); return HA # in radians (for sunset, use -HA)
[docs]def calcAzEl( t, localtime, latitude, longitude, zone ): """ Calculate sun azimuth and zenith angle """ eqTime = calcEquationOfTime(t) theta = calcSunDeclination(t) solarTimeFix = eqTime + 4.0 * longitude - 60.0 * zone earthRadVec = calcSunRadVector(t) trueSolarTime = localtime + solarTimeFix while trueSolarTime > 1440: trueSolarTime -= 1440. hourAngle = trueSolarTime / 4.0 - 180.0 if hourAngle < -180.: hourAngle += 360.0 haRad = numpy.radians(hourAngle) csz = numpy.sin(numpy.radians(latitude)) * numpy.sin(numpy.radians(theta)) + numpy.cos(numpy.radians(latitude)) * numpy.cos(numpy.radians(theta)) * numpy.cos(haRad) if csz > 1.0: csz = 1.0 elif csz < -1.0: csz = -1.0 zenith = numpy.degrees(numpy.arccos(csz)) azDenom = numpy.cos(numpy.radians(latitude)) * numpy.sin(numpy.radians(zenith)) if abs(azDenom) > 0.001: azRad = (( numpy.sin(numpy.radians(latitude)) * numpy.cos(numpy.radians(zenith)) ) - numpy.sin(numpy.radians(theta))) / azDenom if abs(azRad) > 1.0: if azRad < 0.: azRad = -1.0 else: azRad = 1.0 azimuth = 180.0 - numpy.degrees(numpy.arccos(azRad)) if hourAngle > 0.0: azimuth = -azimuth else: if latitude > 0.0: azimuth = 180.0 else: azimuth = 0.0 if azimuth < 0.0: azimuth += 360.0 exoatmElevation = 90.0 - zenith # Atmospheric Refraction correction if exoatmElevation > 85.0: refractionCorrection = 0.0 else: te = numpy.tan(numpy.radians(exoatmElevation)) if exoatmElevation > 5.0: refractionCorrection = 58.1 / te - 0.07 / (te*te*te) + 0.000086 / (te*te*te*te*te) elif exoatmElevation > -0.575: refractionCorrection = 1735.0 + exoatmElevation * (-518.2 + exoatmElevation * (103.4 + exoatmElevation * (-12.79 + exoatmElevation * 0.711) ) ) else: refractionCorrection = -20.774 / te refractionCorrection = refractionCorrection / 3600.0 solarZen = zenith - refractionCorrection return azimuth, solarZen
[docs]def calcSolNoonUTC( jd, longitude ): """ Calculate time of solar noon the given day at the given location on earth (in minute since 0 UTC) """ tnoon = calcTimeJulianCent(jd) eqTime = calcEquationOfTime(tnoon) solNoonUTC = 720.0 - (longitude * 4.) - eqTime # in minutes return solNoonUTC
[docs]def calcSolNoon( jd, longitude, timezone, dst ): """ Calculate time of solar noon the given day at the given location on earth (in minute) """ timeUTC = calcSolNoonUTC(jd, longitude) newTimeUTC = calcSolNoonUTC(jd + timeUTC/1440.0, longitude) solNoonLocal = newTimeUTC + (timezone*60.0) # in minutes if dst: solNoonLocal += 60.0 return solNoonLocal
[docs]def calcSunRiseSetUTC( jd, latitude, longitude ): """ Calculate sunrise/sunset the given day at the given location on earth (in minute since 0 UTC) """ t = calcTimeJulianCent(jd) eqTime = calcEquationOfTime(t) solarDec = calcSunDeclination(t) hourAngle = calcHourAngleSunrise(latitude, solarDec) # Rise time delta = longitude + numpy.degrees(hourAngle) riseTimeUTC = 720. - (4.0 * delta) - eqTime # in minutes # Set time hourAngle = -hourAngle delta = longitude + numpy.degrees(hourAngle) setTimeUTC = 720. - (4.0 * delta) - eqTime # in minutes return riseTimeUTC, setTimeUTC
[docs]def calcSunRiseSet( jd, latitude, longitude, timezone, dst ): """ Calculate sunrise/sunset the given day at the given location on earth (in minutes) """ rtimeUTC, stimeUTC = calcSunRiseSetUTC(jd, latitude, longitude) # calculate local sunrise time (in minutes) rnewTimeUTC, snewTimeUTC = calcSunRiseSetUTC(jd + rtimeUTC/1440.0, latitude, longitude) rtimeLocal = rnewTimeUTC + (timezone * 60.0) rtimeLocal += 60.0 if dst else 0.0 if rtimeLocal < 0.0 or rtimeLocal >= 1440.0: jday = jd increment = 1. if rtimeLocal < 0. else -1. while rtimeLocal < 0.0 or rtimeLocal >= 1440.0: rtimeLocal += increment * 1440.0 jday -= increment # calculate local sunset time (in minutes) rnewTimeUTC, snewTimeUTC = calcSunRiseSetUTC(jd + stimeUTC/1440.0, latitude, longitude) stimeLocal = snewTimeUTC + (timezone * 60.0) stimeLocal += 60.0 if dst else 0.0 if stimeLocal < 0.0 or stimeLocal >= 1440.0: jday = jd increment = 1. if stimeLocal < 0. else -1. while stimeLocal < 0.0 or stimeLocal >= 1440.0: stimeLocal += increment * 1440.0 jday -= increment # return return rtimeLocal, stimeLocal
[docs]def calcTerminator( date, latitudes, longitudes ): """ Calculate terminator position and solar zenith angle for a given julian date-time within latitude/longitude limits Note that for plotting only, basemap has a built-in terminator """ jd = getJD(date) t = calcTimeJulianCent(jd) ut = ( jd - (int(jd - 0.5) + 0.5) )*1440. npoints = 50 zen = numpy.zeros((npoints,npoints)) lats = numpy.linspace(latitudes[0], latitudes[1], num=npoints) lons = numpy.linspace(longitudes[0], longitudes[1], num=npoints) term = [] for ilat in range(1,npoints+1): for ilon in range(npoints): az,el = calcAzEl(t, ut, lats[-ilat], lons[ilon], 0.) zen[-ilat,ilon] = el a = (90 - zen[-ilat,:]) mins = numpy.r_[False, a[1:]*a[:-1] <= 0] | \ numpy.r_[a[1:]*a[:-1] <= 0, False] zmin = mins & numpy.r_[False, a[1:] < a[:-1]] if True in zmin: ll = numpy.interp(0, a[zmin][-1::-1], lons[zmin][-1::-1]) term.append([lats[-ilat], ll]) zmin = mins & numpy.r_[a[:-1] < a[1:], False] if True in zmin: ll = numpy.interp(0, a[zmin], lons[zmin]) term.insert(0, [lats[-ilat], ll]) return lats, lons, zen, numpy.array(term)
[docs]def getJD( date ): """ Calculate julian date for given day, month and year """ from dateutil.relativedelta import relativedelta if date.month < 2: date.replace(year=date.year-1) date += relativedelta(month=12) A = numpy.floor(date.year/100.) B = 2. - A + numpy.floor(A/4.) jd = numpy.floor(365.25*(date.year + 4716.)) + numpy.floor(30.6001*(date.month+1)) + date.day + B - 1524.5 jd = jd + date.hour/24.0 + date.minute/1440.0 + date.second/86400.0 return jd