kopia lustrzana https://github.com/sq9atk/sr0wx
533 wiersze
18 KiB
Python
Executable File
533 wiersze
18 KiB
Python
Executable File
#!/usr/bin/env python
|
|
# -*- coding: iso-8859-1 -*-
|
|
"""
|
|
SUNRISET.C - computes Sun rise/set times, start/end of twilight, and
|
|
the length of the day at any date and latitude
|
|
|
|
Written as DAYLEN.C, 1989-08-16
|
|
|
|
Modified to SUNRISET.C, 1992-12-01
|
|
|
|
(c) Paul Schlyter, 1989, 1992
|
|
|
|
Released to the public domain by Paul Schlyter, December 1992
|
|
|
|
Direct conversion to Java
|
|
Sean Russell <ser@germane-software.com>
|
|
|
|
Conversion to Python Class, 2002-03-21
|
|
Henrik Härkönen <radix@kortis.to>
|
|
|
|
Solar Altitude added by Miguel Tremblay 2005-01-16
|
|
Solar flux, equation of time and import of python library
|
|
added by Miguel Tremblay 2007-11-22
|
|
|
|
|
|
2007-12-12 - v1.5 by Miguel Tremblay: bug fix to solar flux calculation
|
|
|
|
|
|
"""
|
|
|
|
SUN_PY_VERSION = 1.5
|
|
|
|
import math
|
|
from math import pi
|
|
|
|
import calendar
|
|
|
|
class Sun:
|
|
|
|
def __init__(self):
|
|
""""""
|
|
|
|
# Some conversion factors between radians and degrees
|
|
self.RADEG = 180.0 / pi
|
|
self.DEGRAD = pi / 180.0
|
|
self.INV360 = 1.0 / 360.0
|
|
|
|
|
|
def daysSince2000Jan0(self, y, m, d):
|
|
"""A macro to compute the number of days elapsed since 2000 Jan 0.0
|
|
(which is equal to 1999 Dec 31, 0h UT)"""
|
|
return (367*(y)-((7*((y)+(((m)+9)/12)))/4)+((275*(m))/9)+(d)-730530)
|
|
|
|
|
|
# The trigonometric functions in degrees
|
|
def sind(self, x):
|
|
"""Returns the sin in degrees"""
|
|
return math.sin(x * self.DEGRAD)
|
|
|
|
def cosd(self, x):
|
|
"""Returns the cos in degrees"""
|
|
return math.cos(x * self.DEGRAD)
|
|
|
|
def tand(self, x):
|
|
"""Returns the tan in degrees"""
|
|
return math.tan(x * self.DEGRAD)
|
|
|
|
def atand(self, x):
|
|
"""Returns the arc tan in degrees"""
|
|
return math.atan(x) * self.RADEG
|
|
|
|
def asind(self, x):
|
|
"""Returns the arc sin in degrees"""
|
|
return math.asin(x) * self.RADEG
|
|
|
|
def acosd(self, x):
|
|
"""Returns the arc cos in degrees"""
|
|
return math.acos(x) * self.RADEG
|
|
|
|
def atan2d(self, y, x):
|
|
"""Returns the atan2 in degrees"""
|
|
return math.atan2(y, x) * self.RADEG
|
|
|
|
# Following are some macros around the "workhorse" function __daylen__
|
|
# They mainly fill in the desired values for the reference altitude
|
|
# below the horizon, and also selects whether this altitude should
|
|
# refer to the Sun's center or its upper limb.
|
|
|
|
def dayLength(self, year, month, day, lon, lat):
|
|
"""
|
|
This macro computes the length of the day, from sunrise to sunset.
|
|
Sunrise/set is considered to occur when the Sun's upper limb is
|
|
35 arc minutes below the horizon (this accounts for the refraction
|
|
of the Earth's atmosphere).
|
|
"""
|
|
return self.__daylen__(year, month, day, lon, lat, -35.0/60.0, 1)
|
|
|
|
def dayCivilTwilightLength(self, year, month, day, lon, lat):
|
|
"""
|
|
This macro computes the length of the day, including civil twilight.
|
|
Civil twilight starts/ends when the Sun's center is 6 degrees below
|
|
the horizon.
|
|
"""
|
|
return self.__daylen__(year, month, day, lon, lat, -6.0, 0)
|
|
|
|
def dayNauticalTwilightLength(self, year, month, day, lon, lat):
|
|
"""
|
|
This macro computes the length of the day, incl. nautical twilight.
|
|
Nautical twilight starts/ends when the Sun's center is 12 degrees
|
|
below the horizon.
|
|
"""
|
|
return self.__daylen__(year, month, day, lon, lat, -12.0, 0)
|
|
|
|
def dayAstronomicalTwilightLength(self, year, month, day, lon, lat):
|
|
"""
|
|
This macro computes the length of the day, incl. astronomical twilight.
|
|
Astronomical twilight starts/ends when the Sun's center is 18 degrees
|
|
below the horizon.
|
|
"""
|
|
return self.__daylen__(year, month, day, lon, lat, -18.0, 0)
|
|
|
|
def sunRiseSet(self, year, month, day, lon, lat):
|
|
"""
|
|
This macro computes times for sunrise/sunset.
|
|
Sunrise/set is considered to occur when the Sun's upper limb is
|
|
35 arc minutes below the horizon (this accounts for the refraction
|
|
of the Earth's atmosphere).
|
|
"""
|
|
return self.__sunriset__(year, month, day, lon, lat, -35.0/60.0, 1)
|
|
|
|
|
|
def civilTwilight(self, year, month, day, lon, lat):
|
|
"""
|
|
This macro computes the start and end times of civil twilight.
|
|
Civil twilight starts/ends when the Sun's center is 6 degrees below
|
|
the horizon.
|
|
"""
|
|
return self.__sunriset__(year, month, day, lon, lat, -6.0, 0)
|
|
|
|
def nauticalTwilight(self, year, month, day, lon, lat):
|
|
"""
|
|
This macro computes the start and end times of nautical twilight.
|
|
Nautical twilight starts/ends when the Sun's center is 12 degrees
|
|
below the horizon.
|
|
"""
|
|
return self.__sunriset__(year, month, day, lon, lat, -12.0, 0)
|
|
|
|
def astronomicalTwilight(self, year, month, day, lon, lat):
|
|
"""
|
|
This macro computes the start and end times of astronomical twilight.
|
|
Astronomical twilight starts/ends when the Sun's center is 18 degrees
|
|
below the horizon.
|
|
"""
|
|
return self.__sunriset__(year, month, day, lon, lat, -18.0, 0)
|
|
|
|
# The "workhorse" function for sun rise/set times
|
|
def __sunriset__(self, year, month, day, lon, lat, altit, upper_limb):
|
|
"""
|
|
Note: year,month,date = calendar date, 1801-2099 only.
|
|
Eastern longitude positive, Western longitude negative
|
|
Northern latitude positive, Southern latitude negative
|
|
The longitude value IS critical in this function!
|
|
altit = the altitude which the Sun should cross
|
|
Set to -35/60 degrees for rise/set, -6 degrees
|
|
for civil, -12 degrees for nautical and -18
|
|
degrees for astronomical twilight.
|
|
upper_limb: non-zero -> upper limb, zero -> center
|
|
Set to non-zero (e.g. 1) when computing rise/set
|
|
times, and to zero when computing start/end of
|
|
twilight.
|
|
*rise = where to store the rise time
|
|
*set = where to store the set time
|
|
Both times are relative to the specified altitude,
|
|
and thus this function can be used to compute
|
|
various twilight times, as well as rise/set times
|
|
Return value: 0 = sun rises/sets this day, times stored at
|
|
*trise and *tset.
|
|
+1 = sun above the specified 'horizon' 24 hours.
|
|
*trise set to time when the sun is at south,
|
|
minus 12 hours while *tset is set to the south
|
|
time plus 12 hours. 'Day' length = 24 hours
|
|
-1 = sun is below the specified 'horizon' 24 hours
|
|
'Day' length = 0 hours, *trise and *tset are
|
|
both set to the time when the sun is at south.
|
|
"""
|
|
# Compute d of 12h local mean solar time
|
|
d = self.daysSince2000Jan0(year,month,day) + 0.5 - (lon/360.0)
|
|
|
|
# Compute local sidereal time of this moment
|
|
sidtime = self.revolution(self.GMST0(d) + 180.0 + lon)
|
|
|
|
# Compute Sun's RA + Decl at this moment
|
|
res = self.sunRADec(d)
|
|
sRA = res[0]
|
|
sdec = res[1]
|
|
sr = res[2]
|
|
|
|
# Compute time when Sun is at south - in hours UT
|
|
tsouth = 12.0 - self.rev180(sidtime - sRA)/15.0;
|
|
|
|
# Compute the Sun's apparent radius, degrees
|
|
sradius = 0.2666 / sr;
|
|
|
|
# Do correction to upper limb, if necessary
|
|
if upper_limb:
|
|
altit = altit - sradius
|
|
|
|
# Compute the diurnal arc that the Sun traverses to reach
|
|
# the specified altitude altit:
|
|
|
|
cost = (self.sind(altit) - self.sind(lat) * self.sind(sdec))/\
|
|
(self.cosd(lat) * self.cosd(sdec))
|
|
|
|
if cost >= 1.0:
|
|
rc = -1
|
|
t = 0.0 # Sun always below altit
|
|
|
|
elif cost <= -1.0:
|
|
rc = +1
|
|
t = 12.0; # Sun always above altit
|
|
|
|
else:
|
|
t = self.acosd(cost)/15.0 # The diurnal arc, hours
|
|
|
|
|
|
# Store rise and set times - in hours UT
|
|
return (tsouth-t, tsouth+t)
|
|
|
|
|
|
def __daylen__(self, year, month, day, lon, lat, altit, upper_limb):
|
|
"""
|
|
Note: year,month,date = calendar date, 1801-2099 only.
|
|
Eastern longitude positive, Western longitude negative
|
|
Northern latitude positive, Southern latitude negative
|
|
The longitude value is not critical. Set it to the correct
|
|
longitude if you're picky, otherwise set to, say, 0.0
|
|
The latitude however IS critical - be sure to get it correct
|
|
altit = the altitude which the Sun should cross
|
|
Set to -35/60 degrees for rise/set, -6 degrees
|
|
for civil, -12 degrees for nautical and -18
|
|
degrees for astronomical twilight.
|
|
upper_limb: non-zero -> upper limb, zero -> center
|
|
Set to non-zero (e.g. 1) when computing day length
|
|
and to zero when computing day+twilight length.
|
|
|
|
"""
|
|
|
|
# Compute d of 12h local mean solar time
|
|
d = self.daysSince2000Jan0(year,month,day) + 0.5 - (lon/360.0)
|
|
|
|
# Compute obliquity of ecliptic (inclination of Earth's axis)
|
|
obl_ecl = 23.4393 - 3.563E-7 * d
|
|
|
|
# Compute Sun's position
|
|
res = self.sunpos(d)
|
|
slon = res[0]
|
|
sr = res[1]
|
|
|
|
# Compute sine and cosine of Sun's declination
|
|
sin_sdecl = self.sind(obl_ecl) * self.sind(slon)
|
|
cos_sdecl = math.sqrt(1.0 - sin_sdecl * sin_sdecl)
|
|
|
|
# Compute the Sun's apparent radius, degrees
|
|
sradius = 0.2666 / sr
|
|
|
|
# Do correction to upper limb, if necessary
|
|
if upper_limb:
|
|
altit = altit - sradius
|
|
|
|
|
|
cost = (self.sind(altit) - self.sind(lat) * sin_sdecl) / \
|
|
(self.cosd(lat) * cos_sdecl)
|
|
if cost >= 1.0:
|
|
t = 0.0 # Sun always below altit
|
|
|
|
elif cost <= -1.0:
|
|
t = 24.0 # Sun always above altit
|
|
|
|
else:
|
|
t = (2.0/15.0) * self.acosd(cost); # The diurnal arc, hours
|
|
|
|
return t
|
|
|
|
|
|
def sunpos(self, d):
|
|
"""
|
|
Computes the Sun's ecliptic longitude and distance
|
|
at an instant given in d, number of days since
|
|
2000 Jan 0.0. The Sun's ecliptic latitude is not
|
|
computed, since it's always very near 0.
|
|
"""
|
|
|
|
# Compute mean elements
|
|
M = self.revolution(356.0470 + 0.9856002585 * d)
|
|
w = 282.9404 + 4.70935E-5 * d
|
|
e = 0.016709 - 1.151E-9 * d
|
|
|
|
# Compute true longitude and radius vector
|
|
E = M + e * self.RADEG * self.sind(M) * (1.0 + e * self.cosd(M))
|
|
x = self.cosd(E) - e
|
|
y = math.sqrt(1.0 - e*e) * self.sind(E)
|
|
r = math.sqrt(x*x + y*y) #Solar distance
|
|
v = self.atan2d(y, x) # True anomaly
|
|
lon = v + w # True solar longitude
|
|
if lon >= 360.0:
|
|
lon = lon - 360.0 # Make it 0..360 degrees
|
|
|
|
return (lon,r)
|
|
|
|
|
|
def sunRADec(self, d):
|
|
"""
|
|
Returns the angle of the Sun (RA)
|
|
the declination (dec) and the distance of the Sun (r)
|
|
for a given day d.
|
|
"""
|
|
|
|
# Compute Sun's ecliptical coordinates
|
|
res = self.sunpos(d)
|
|
lon = res[0] # True solar longitude
|
|
r = res[1] # Solar distance
|
|
|
|
# Compute ecliptic rectangular coordinates (z=0)
|
|
x = r * self.cosd(lon)
|
|
y = r * self.sind(lon)
|
|
|
|
# Compute obliquity of ecliptic (inclination of Earth's axis)
|
|
obl_ecl = 23.4393 - 3.563E-7 * d
|
|
|
|
# Convert to equatorial rectangular coordinates - x is unchanged
|
|
z = y * self.sind(obl_ecl)
|
|
y = y * self.cosd(obl_ecl)
|
|
|
|
# Convert to spherical coordinates
|
|
RA = self.atan2d(y, x)
|
|
dec = self.atan2d(z, math.sqrt(x*x + y*y))
|
|
|
|
return (RA, dec, r)
|
|
|
|
|
|
def revolution(self, x):
|
|
"""
|
|
This function reduces any angle to within the first revolution
|
|
by subtracting or adding even multiples of 360.0 until the
|
|
result is >= 0.0 and < 360.0
|
|
|
|
Reduce angle to within 0..360 degrees
|
|
"""
|
|
return (x - 360.0 * math.floor(x * self.INV360))
|
|
|
|
def rev180(self, x):
|
|
"""
|
|
Reduce angle to within +180..+180 degrees
|
|
"""
|
|
return (x - 360.0 * math.floor(x * self.INV360 + 0.5))
|
|
|
|
def GMST0(self, d):
|
|
"""
|
|
This function computes GMST0, the Greenwich Mean Sidereal Time
|
|
at 0h UT (i.e. the sidereal time at the Greenwhich meridian at
|
|
0h UT). GMST is then the sidereal time at Greenwich at any
|
|
time of the day. I've generalized GMST0 as well, and define it
|
|
as: GMST0 = GMST - UT -- this allows GMST0 to be computed at
|
|
other times than 0h UT as well. While this sounds somewhat
|
|
contradictory, it is very practical: instead of computing
|
|
GMST like:
|
|
|
|
GMST = (GMST0) + UT * (366.2422/365.2422)
|
|
|
|
where (GMST0) is the GMST last time UT was 0 hours, one simply
|
|
computes:
|
|
|
|
GMST = GMST0 + UT
|
|
|
|
where GMST0 is the GMST "at 0h UT" but at the current moment!
|
|
Defined in this way, GMST0 will increase with about 4 min a
|
|
day. It also happens that GMST0 (in degrees, 1 hr = 15 degr)
|
|
is equal to the Sun's mean longitude plus/minus 180 degrees!
|
|
(if we neglect aberration, which amounts to 20 seconds of arc
|
|
or 1.33 seconds of time)
|
|
"""
|
|
# Sidtime at 0h UT = L (Sun's mean longitude) + 180.0 degr
|
|
# L = M + w, as defined in sunpos(). Since I'm too lazy to
|
|
# add these numbers, I'll let the C compiler do it for me.
|
|
# Any decent C compiler will add the constants at compile
|
|
# time, imposing no runtime or code overhead.
|
|
|
|
sidtim0 = self.revolution((180.0 + 356.0470 + 282.9404) +
|
|
(0.9856002585 + 4.70935E-5) * d)
|
|
return sidtim0;
|
|
|
|
def solar_altitude(self, latitude, year, month, day):
|
|
"""
|
|
Compute the altitude of the sun. No atmospherical refraction taken
|
|
in account.
|
|
Altitude of the southern hemisphere are given relative to
|
|
true north.
|
|
Altitude of the northern hemisphere are given relative to
|
|
true south.
|
|
Declination is between 23.5° North and 23.5° South depending
|
|
on the period of the year.
|
|
Source of formula for altitude is PhysicalGeography.net
|
|
http://www.physicalgeography.net/fundamentals/6h.html
|
|
"""
|
|
# Compute declination
|
|
N = self.daysSince2000Jan0(year, month, day)
|
|
res = self.sunRADec(N)
|
|
declination = res[1]
|
|
sr = res[2]
|
|
|
|
# Compute the altitude
|
|
altitude = 90.0 - latitude + declination
|
|
|
|
# In the tropical and in extreme latitude, values over 90 may occurs.
|
|
if altitude > 90:
|
|
altitude = 90 - (altitude-90)
|
|
|
|
if altitude < 0:
|
|
altitude = 0
|
|
|
|
return altitude
|
|
|
|
def get_max_solar_flux(self, latitude, year, month, day):
|
|
"""
|
|
Compute the maximal solar flux to reach the ground for this date and
|
|
latitude.
|
|
Originaly comes from Environment Canada weather forecast model.
|
|
Information was of the public domain before release by Environment Canada
|
|
Output is in W/M^2.
|
|
"""
|
|
|
|
(fEot, fR0r, tDeclsc) = self.equation_of_time(year, month, day, latitude)
|
|
fSF = (tDeclsc[0]+tDeclsc[1])*fR0r
|
|
|
|
# In the case of a negative declinaison, solar flux is null
|
|
if fSF < 0:
|
|
fCoeff = 0
|
|
else:
|
|
fCoeff = -1.56e-12*fSF**4 + 5.972e-9*fSF**3 -\
|
|
8.364e-6*fSF**2 + 5.183e-3*fSF - 0.435
|
|
|
|
fSFT = fSF * fCoeff
|
|
|
|
if fSFT < 0:
|
|
fSFT=0
|
|
|
|
return fSFT
|
|
|
|
def equation_of_time(self, year, month, day, latitude):
|
|
"""
|
|
Description: Subroutine computing the part of the equation of time
|
|
needed in the computing of the theoritical solar flux
|
|
Correction originating of the CMC GEM model.
|
|
|
|
Parameters: int nTime : cTime for the correction of the time.
|
|
|
|
Returns: tuple (double fEot, double fR0r, tuple tDeclsc)
|
|
dEot: Correction for the equation of time
|
|
dR0r: Corrected solar constant for the equation of time
|
|
tDeclsc: Declinaison
|
|
"""
|
|
# Julian date
|
|
nJulianDate = self.Julian(year, month, day)
|
|
# Check if it is a leap year
|
|
if(calendar.isleap(year)):
|
|
fDivide = 366.0
|
|
else:
|
|
fDivide = 365.0
|
|
# Correction for "equation of time"
|
|
fA = nJulianDate/fDivide*2*pi
|
|
fR0r = self.__Solcons(fA)*0.1367e4
|
|
fRdecl = 0.412*math.cos((nJulianDate+10.0)*2.0*pi/fDivide-pi)
|
|
fDeclsc1 = self.sind(latitude)*math.sin(fRdecl)
|
|
fDeclsc2 = self.cosd(latitude)*math.cos(fRdecl)
|
|
tDeclsc = (fDeclsc1, fDeclsc2)
|
|
# in minutes
|
|
fEot = 0.002733 -7.343*math.sin(fA)+ .5519*math.cos(fA) \
|
|
- 9.47*math.sin(2.0*fA) - 3.02*math.cos(2.0*fA) \
|
|
- 0.3289*math.sin(3.*fA) -0.07581*math.cos(3.0*fA) \
|
|
-0.1935*math.sin(4.0*fA) -0.1245*math.cos(4.0*fA)
|
|
# Express in fraction of hour
|
|
fEot = fEot/60.0
|
|
# Express in radians
|
|
fEot = fEot*15*pi/180.0
|
|
|
|
return (fEot, fR0r, tDeclsc)
|
|
|
|
def __Solcons(self, dAlf):
|
|
"""
|
|
Name: __Solcons
|
|
|
|
Parameters: [I] double dAlf : Solar constant to correct the excentricity
|
|
|
|
Returns: double dVar : Variation of the solar constant
|
|
|
|
Functions Called: cos, sin
|
|
|
|
Description: Statement function that calculates the variation of the
|
|
solar constant as a function of the julian day. (dAlf, in radians)
|
|
|
|
Notes: Comes from the
|
|
|
|
Revision History:
|
|
Author Date Reason
|
|
Miguel Tremblay June 30th 2004
|
|
"""
|
|
|
|
dVar = 1.0/(1.0-9.464e-4*math.sin(dAlf)-0.01671*math.cos(dAlf)- \
|
|
+ 1.489e-4*math.cos(2.0*dAlf)-2.917e-5*math.sin(3.0*dAlf)- \
|
|
+ 3.438e-4*math.cos(4.0*dAlf))**2
|
|
return dVar
|
|
|
|
|
|
def Julian(self, year, month, day):
|
|
"""
|
|
Return julian day.
|
|
"""
|
|
if calendar.isleap(year): # Bissextil year, 366 days
|
|
lMonth = [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366]
|
|
else: # Normal year, 365 days
|
|
lMonth = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365]
|
|
|
|
nJulian = lMonth[month-1] + day
|
|
return nJulian
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
k = Sun()
|
|
print k.get_max_solar_flux(46.2, 2004, 01, 30)
|
|
# print k.sunRiseSet(2002, 3, 22, 25.42, 62.15)
|