这个问题在三年多以前就有人问过了,有一个答案,但是我发现了解决方案中的一个小故障。
下面的代码是在R。我已经把它移植到另一种语言,但是已经测试了原始代码直接在R,以确保问题不是与我的移植。
sunPosition <- function(year, month, day, hour=12, min=0, sec=0,
lat=46.5, long=6.5) {
twopi <- 2 * pi
deg2rad <- pi / 180
# Get day of the year, e.g. Feb 1 = 32, Mar 1 = 61 on leap years
month.days <- c(0,31,28,31,30,31,30,31,31,30,31,30)
day <- day + cumsum(month.days)[month]
leapdays <- year %% 4 == 0 & (year %% 400 == 0 | year %% 100 != 0) & day >= 60
day[leapdays] <- day[leapdays] + 1
# Get Julian date - 2400000
hour <- hour + min / 60 + sec / 3600 # hour plus fraction
delta <- year - 1949
leap <- trunc(delta / 4) # former leapyears
jd <- 32916.5 + delta * 365 + leap + day + hour / 24
# The input to the Atronomer's almanach is the difference between
# the Julian date and JD 2451545.0 (noon, 1 January 2000)
time <- jd - 51545.
# Ecliptic coordinates
# Mean longitude
mnlong <- 280.460 + .9856474 * time
mnlong <- mnlong %% 360
mnlong[mnlong < 0] <- mnlong[mnlong < 0] + 360
# Mean anomaly
mnanom <- 357.528 + .9856003 * time
mnanom <- mnanom %% 360
mnanom[mnanom < 0] <- mnanom[mnanom < 0] + 360
mnanom <- mnanom * deg2rad
# Ecliptic longitude and obliquity of ecliptic
eclong <- mnlong + 1.915 * sin(mnanom) + 0.020 * sin(2 * mnanom)
eclong <- eclong %% 360
eclong[eclong < 0] <- eclong[eclong < 0] + 360
oblqec <- 23.429 - 0.0000004 * time
eclong <- eclong * deg2rad
oblqec <- oblqec * deg2rad
# Celestial coordinates
# Right ascension and declination
num <- cos(oblqec) * sin(eclong)
den <- cos(eclong)
ra <- atan(num / den)
ra[den < 0] <- ra[den < 0] + pi
ra[den >= 0 & num < 0] <- ra[den >= 0 & num < 0] + twopi
dec <- asin(sin(oblqec) * sin(eclong))
# Local coordinates
# Greenwich mean sidereal time
gmst <- 6.697375 + .0657098242 * time + hour
gmst <- gmst %% 24
gmst[gmst < 0] <- gmst[gmst < 0] + 24.
# Local mean sidereal time
lmst <- gmst + long / 15.
lmst <- lmst %% 24.
lmst[lmst < 0] <- lmst[lmst < 0] + 24.
lmst <- lmst * 15. * deg2rad
# Hour angle
ha <- lmst - ra
ha[ha < -pi] <- ha[ha < -pi] + twopi
ha[ha > pi] <- ha[ha > pi] - twopi
# Latitude to radians
lat <- lat * deg2rad
# Azimuth and elevation
el <- asin(sin(dec) * sin(lat) + cos(dec) * cos(lat) * cos(ha))
az <- asin(-cos(dec) * sin(ha) / cos(el))
elc <- asin(sin(dec) / sin(lat))
az[el >= elc] <- pi - az[el >= elc]
az[el <= elc & ha > 0] <- az[el <= elc & ha > 0] + twopi
el <- el / deg2rad
az <- az / deg2rad
lat <- lat / deg2rad
return(list(elevation=el, azimuth=az))
}
我遇到的问题是,它返回的方位角似乎是错误的,例如,如果我在夏至(南方)的12:00对位置0ºE和41ºS、3ºS、3ºN和41ºN运行该函数:
> sunPosition(2012,12,22,12,0,0,-41,0)
$elevation
[1] 72.42113
$azimuth
[1] 180.9211
> sunPosition(2012,12,22,12,0,0,-3,0)
$elevation
[1] 69.57493
$azimuth
[1] -0.79713
Warning message:
In asin(sin(dec)/sin(lat)) : NaNs produced
> sunPosition(2012,12,22,12,0,0,3,0)
$elevation
[1] 63.57538
$azimuth
[1] -0.6250971
Warning message:
In asin(sin(dec)/sin(lat)) : NaNs produced
> sunPosition(2012,12,22,12,0,0,41,0)
$elevation
[1] 25.57642
$azimuth
[1] 180.3084
这些数字看起来不太对。我很满意的仰角--前两个应该大致相同,第三个稍微低一点,第四个低得多。然而,第一个方位角应该大致在正北,而它给出的数字则完全相反。剩下的三个应该大致指向正南,但只有最后一个是这样。中间的两个指向正北。再次向外180度。
正如您所看到的,低纬度(靠近赤道)也会触发一些错误
我认为故障在这一节,错误在第三行触发(从elc
开始)。
# Azimuth and elevation
el <- asin(sin(dec) * sin(lat) + cos(dec) * cos(lat) * cos(ha))
az <- asin(-cos(dec) * sin(ha) / cos(el))
elc <- asin(sin(dec) / sin(lat))
az[el >= elc] <- pi - az[el >= elc]
az[el <= elc & ha > 0] <- az[el <= elc & ha > 0] + twopi
我在谷歌上搜索了一下,发现了一段类似的C语言代码,转换成R语言后,它用来计算方位角的那条线就像这样
az <- atan(sin(ha) / (cos(ha) * sin(lat) - tan(dec) * cos(lat)))
这里的输出看起来是朝着正确的方向前进的,但是当它被转换回度数时,我就是不能让它一直给予我正确的答案。
更正代码(怀疑它只是上面的几行),使它计算正确的方位角将是梦幻般的。
6条答案
按热度按时间z5btuh9x1#
这似乎是一个重要的主题,所以我贴了一个比典型的答案更长:如果这个算法将来被其他人使用,我认为重要的是,它必须附有对它所源自的文献的参考。
∮简短的回答∮
正如您所注意到的,您发布的代码在赤道附近或南半球的位置无法正常工作。
要修复它,只需替换原始代码中的以下行:
与这些:
它现在应该适用于地球仪任何地点。
讨论
您示例中的代码几乎是逐字改编自J. J. Michalsky(Solar Energy. 40:227-235)1988年的一篇文章。这篇文章又改进了R. Walraven(Solar Energy. 20:393-397)1978年的一篇文章中提出的算法。Walraven报告说,该方法已成功用于精确定位加利福尼亚州戴维斯(38° 33' 14”N,121° 44' 17”W)的偏振辐射计数年。
**米哈尔斯基和瓦尔拉文的代码都包含重要的/致命的错误。**特别是,虽然米哈尔斯基的算法在美国大部分地区运行良好,但在赤道附近或南半球的地区却失败了(正如你所发现的)。1989年,澳大利亚维多利亚州的斯宾塞(J.W. Spencer)注意到了同样的事情(Solar Energy. 42(4):353):
尊敬的先生:
Michalsky将计算的方位角分配到正确象限的方法源自Walraven,当应用于南(负)纬度时,该方法不能给予正确值。此外,对于纬度为零的情况,临界高程(elc)的计算将失败,因为除以零。这两种反对意见都可以通过考虑cos(方位角)的符号将方位角分配到正确象限来避免。
我对你的代码的编辑是基于Spencer在那篇发表的评论中所建议的修正。我只是对它们做了一些修改,以确保R函数
sunPosition()
保持“矢量化”(即,正确地处理点位置的矢量,而不需要一次传递一个点)。函数
sunPosition()
的精度为了测试
sunPosition()
是否正确工作,我将其结果与美国国家海洋和大气管理局的Solar Calculator计算结果进行了比较。在这两种情况下,太阳位置都是在2012年南半球夏至(12月22日)中午(12:00 PM)计算的。所有结果都在0.02度以内一致。代码中的其他错误
在发布的代码中至少还有两个(相当小的)错误。第一个错误导致闰年的2月29日和3月1日都被记为一年中的第61天。第二个错误源于原始文章中的一个打字错误,Michalsky在1989年的一份说明(Solar Energy. 43(5):323)中纠正了这个错误。
此代码块显示了有问题的行,已注解掉,后面紧跟更正的版本:
sunPosition()
的更正版本以下是上面验证的更正代码:
参考文献:
Michalsky,J. J. 1988年。《天文年历》中太阳位置近似算法(1950-2050)。《太阳能》,40(3):227-235。
Michalsky,J. J. 1989.勘误表.太阳能. 43(5):323.
Spencer,J.W. 1989。对“天文年历中太阳近似位置的算法(1950-2050)"的评论。《太阳能》。42(4):353。
1978年。计算太阳的位置。《太阳能》。20:393-397。
j7dteeu82#
使用上面链接中的“NOAA Solar Calculations”,我对函数的最后一部分做了一些修改,使用了一个稍微不同的算法,我希望这个算法在翻译时没有错误。我注解掉了现在无用的代码,并在纬度到弧度转换之后添加了新的算法:
为了验证您提到的四种情况下的方位角趋势,我们将其与一天中的时间绘制在一起:
随附图片:
wh6knrhe3#
这里是一个重写,它更符合R的习惯,更容易调试和维护。它本质上是Josh的答案,但是方位角是使用Josh和Charlie的算法计算的,以供比较。我还包括了对我的另一个答案中的日期代码的简化。基本原则是将代码分成许多更小的函数,您可以更容易地为它们编写单元测试。
14ifxucb4#
这是对Josh出色回答的建议更新。
该函数的大部分开头都是样板代码,用于计算自2000年1月1日中午以来的天数。使用R现有的日期和时间函数可以更好地处理这一问题。
我还认为,与其用六个不同的变量来指定日期和时间,不如指定一个现有的日期对象或一个日期字符串+格式字符串,这样更容易(而且与其他R函数更一致)。
下面是两个帮助函数
函数的开头现在简化为
另一个奇怪的地方是在像这样的台词里
由于
mnlong
的值已经被%%
调用,它们应该都已经是非负的了,所以这一行是多余的。oxiaedzo5#
我在一个Python项目中需要太阳的位置,我改编了Josh奥布莱恩的算法。
谢谢你乔什。
如果它可能对任何人有用,这是我的改编。
请注意,我的项目只需要即时太阳位置,所以时间不是一个参数。
t40tm48m6#
我遇到了一个数据点的小问题&上面Richie Cotton的函数(在Charlie代码的实现中)
因为在solarAzimuthRadiansCharlie函数中,在180 °角附近存在浮点激励,使得
(sin(lat) * cos(zenithAngle) - sin(dec)) / (cos(lat) * sin(zenithAngle))
是超过1的最小量,所以1.0000000000000000004440892098生成NaN,因为acos的输入不应大于1或小于-1。我怀疑Josh的计算可能存在类似的边缘情况,其中浮点舍入效应导致asin步骤的输入超出-1:1,但我在特定的数据集中没有遇到它们。
在半打左右的案例中,我发现了这个,“真实”的(白天或夜晚的中间)是问题发生的时间,因此根据经验真实值应该是1/-1。因此,通过在
solarAzimuthRadiansJosh
和solarAzimuthRadiansCharlie
中应用舍入步长,我可以轻松地解决这个问题。我不确定NOAA算法的理论精度是多少(无论如何,数字准确性不再重要的那一点)但是四舍五入到小数点后12位固定了我数据集中的数据。