一天中给定时间、纬度和经度的太阳位置

2024-02-09

这个问题已被问到before https://stackoverflow.com/questions/257717/position-of-the-sun-given-time-of-day-and-lat-long/258106#comment10668948_258106三年多前。给出了答案,但是我发现解决方案中存在问题。

下面的代码是用 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)))

这里的输出似乎正朝着正确的方向前进,但当它转换回度数时,我无法让它始终给出正确的答案。

修正代码(怀疑只是上面的几行)以使其计算正确的方位角将是非常棒的。


这似乎是一个重要的主题,所以我发布了一个比典型答案更长的答案:如果这个算法将来要被其他人使用,我认为重要的是附上对其衍生文献的引用。

简短的回答

正如您所指出的,您发布的代码对于赤道附近或南半球的位置无法正常工作。

要修复它,只需替换原始代码中的以下行:

elc <- asin(sin(dec) / sin(lat))
az[el >= elc] <- pi - az[el >= elc]
az[el <= elc & ha > 0] <- az[el <= elc & ha > 0] + twopi

用这些:

cosAzPos <- (0 <= sin(dec) - sin(el) * sin(lat))
sinAzNeg <- (sin(az) < 0)
az[cosAzPos & sinAzNeg] <- az[cosAzPos & sinAzNeg] + twopi
az[!cosAzPos] <- pi - az[!cosAzPos]

它现在应该适用于全球任何地点。

讨论

您示例中的代码几乎是逐字改编自 J.J. 1988 年的文章。 Michalsky(太阳能。40:227-235)。该文章进而改进了 R. Walraven 1978 年文章中提出的算法(太阳能。20:393-397)。 Walraven 报告称,该方法已成功使用多年,在加利福尼亚州戴维斯(北纬 38° 33' 14",西经 121° 44' 17")精确定位偏振辐射计。

Michalsky 和 ​​Walraven 的代码都包含重要/致命的错误。特别是,虽然米哈斯基的算法在美国大部分地区运行良好,但对于赤道附近或南半球的地区却失败了(正如您所发现的)。 1989 年,J.W.澳大利亚维多利亚州的 Spencer 也指出了同样的事情(Solar Energy. 42(4):353):

尊敬的先生:

Michalsky 将计算的方位角分配给正确象限的方法(源自 Walraven)在应用于南纬(负)纬度时不会给出正确的值。此外,由于除以零,对于零纬度,临界高程 (elc) 的计算将失败。只需考虑 cos(方位角) 的符号,将方位角分配给正确的象限,即可避免这两种反对意见。

我对您的代码的编辑是基于斯宾塞在该发表的评论中建议的更正。我只是对它们进行了一些修改以确保 R 函数sunPosition()保持“矢量化”(即在点位置的矢量上正常工作,而不需要一次传递一个点)。

功能的准确性sunPosition()

为了测试这一点sunPosition()工作正常,我将其结果与国家海洋和大气管理局的计算结果进行了比较太阳能计算器 http://www.esrl.noaa.gov/gmd/grad/solcalc/。在这两种情况下,太阳位置都是在 2012 年南方夏至(12 月 22 日)中午(下午 12:00)计算的。所有结果都一致在 0.02 度以内。

testPts <- data.frame(lat = c(-41,-3,3, 41), 
                      long = c(0, 0, 0, 0))

# Sun's position as returned by the NOAA Solar Calculator,
NOAA <- data.frame(elevNOAA = c(72.44, 69.57, 63.57, 25.6),
                   azNOAA = c(359.09, 180.79, 180.62, 180.3))

# Sun's position as returned by sunPosition()
sunPos <- sunPosition(year = 2012,
                      month = 12,
                      day = 22,
                      hour = 12,
                      min = 0,
                      sec = 0,
                      lat = testPts$lat,
                      long = testPts$long)

cbind(testPts, NOAA, sunPos)
#   lat long elevNOAA azNOAA elevation  azimuth
# 1 -41    0    72.44 359.09  72.43112 359.0787
# 2  -3    0    69.57 180.79  69.56493 180.7965
# 3   3    0    63.57 180.62  63.56539 180.6247
# 4  41    0    25.60 180.30  25.56642 180.3083

代码中的其他错误

发布的代码中至少还有两个其他(相当小的)错误。第一个导致闰年的 2 月 29 日和 3 月 1 日都被计为一年中的第 61 天。第二个错误源于原始文章中的拼写错误,Michalsky 在 1989 年的注释中更正了该错误(Solar Energy. 43(5):323)。

此代码块显示了有问题的行,已注释掉,并紧随其后的是更正的版本:

# leapdays <- year %% 4 == 0 & (year %% 400 == 0 | year %% 100 != 0) & day >= 60
  leapdays <- year %% 4 == 0 & (year %% 400 == 0 | year %% 100 != 0) & 
              day >= 60 & !(month==2 & day==60)

# oblqec <- 23.429 - 0.0000004 * time
  oblqec <- 23.439 - 0.0000004 * time

更正后的版本sunPosition()

这是上面验证的更正代码:

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 & !(month==2 & 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.439 - 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))

    # For logic and names, see Spencer, J.W. 1989. Solar Energy. 42(4):353
    cosAzPos <- (0 <= sin(dec) - sin(el) * sin(lat))
    sinAzNeg <- (sin(az) < 0)
    az[cosAzPos & sinAzNeg] <- az[cosAzPos & sinAzNeg] + twopi
    az[!cosAzPos] <- pi - az[!cosAzPos]

    # if (0 < sin(dec) - sin(el) * sin(lat)) {
    #     if(sin(az) < 0) az <- az + twopi
    # } else {
    #     az <- pi - az
    # }


    el <- el / deg2rad
    az <- az / deg2rad
    lat <- lat / deg2rad

    return(list(elevation=el, azimuth=az))
}

参考:

米哈尔斯基,J.J. 1988。天文年历的太阳近似位置算法(1950-2050)。太阳能。 40(3):227-235。

米哈尔斯基,J.J. 1989。勘误表。太阳能。 43(5):323。

斯宾塞,J.W. 1989年。对《天文年鉴近似太阳位置的算法(1950-2050)》的评论。太阳能。 42(4):353。

Walraven, R. 1978。计算太阳的位置。太阳能。 20:393-397。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

一天中给定时间、纬度和经度的太阳位置 的相关文章

  • stat_function 从函数生成平线

    我有以下代码 library ggplot2 f lt function x if x gt 2 1 x 0 3 else 0 graph lt ggplot data frame x c 0 10 aes x graph lt graph
  • 从 data.frame 中提取时用 NA 填充缺失的列

    我有一个函数 它将具有某些列的数据框作为输入 columns a b z 现在我有一个数据框DF只有很少的这些列DF columns f u z 如果列不在其中 如何创建一个包含所有值为 NA 的列的数据框DF这与DF在柱子上 f u z
  • 如何有效地将多个光栅 (.tif) 文件导入 R

    我是 R 新手 尤其是在空间数据方面 我正在尝试找到一种方法来有效地将多个 600 单波段栅格 tif 文件导入到 R 中 所有文件都存储在同一文件夹中 不确定这是否重要 但请注意 在我的 Mac 和 Windows 并行 VM 上的文件夹
  • R data.table 连接不等式条件

    我想使用 data table 包根据多个不等式条件对数据进行子集化 data table 手册中的示例展示了如何使用字符变量执行此操作 但不显示数字不等式 我还了解了如何使用子集函数来执行此操作 但我真的很想利用 data table 二
  • 通过三点的贝塞尔曲线

    我已经阅读了类似的主题以找到解决方案 但没有成功 我想做的是使该工具与 CorelDraw 中的工具相同 名为 钢笔工具 我通过连接贝塞尔三次曲线来做到这一点 但仍然缺少一个功能 即拖动曲线 而不是控制点 以编辑其形状 我可以成功确定曲线上
  • 根据 R 数据框中的名称对列进行平均

    我想知道是否有一种有效的方法来获取每组的平均值类似命名的列谁的名字结尾为 1S and 2S ex ex1S ex2S at time 1并取每组的平均值类似命名的列谁的名字结尾为 1C or 2C ex ex1C ex2C at time
  • 计算两点之间的最短路线

    过去几周我一直在开发一款多人 HTML5 游戏 使用nodejs and websockets 我已经被这个问题困扰了一段时间 想象一下 我用数组实现了这个平铺地图 如下所示 1 or 棕色瓷砖 路上有障碍物 玩家无法通过 0 or 绿色瓷
  • 如何在R中匹配具有相同主键的两个表中的数据

    我有两个表 其中包含有关人员的数据 df1 lt data frame id c 113 202 377 288 359 name c Alex Silvia Peter Jack Jonny 这为我提供了 id name 1 113 Al
  • 如何读取 R 中的每个 .csv 文件并将其导出到单个大文件中

    你好 我有以下格式的数据 101 20130826T155649 3 1 round 0 10552 180 yellow 12002 1 round 1 19502 150 yellow 22452 1 round 2 28957 130
  • 根据 row_number() 过滤 data.frame

    更新 自从提出这个问题以来 dplyr 已经更新 现在按照 OP 的要求执行 我正在尝试获取第二行到第七行data frame using dplyr 我正在这样做 require dplyr df lt data frame id 1 1
  • 在 Lavaan 生长曲线模型中提取个体轨迹

    我已经使用 R 的 Lavaan 包中的 Growth 函数成功地对一项研究的纵向数据进行了建模 我找不到任何关于如何提取每个参与者的预测轨迹的记录 我只能找到整个组的预测轨迹 在摘要输出的 拦截 部分下给出 使用 lavPredict m
  • R 中使用 `UseMethod()` 与 `inherits()` 来确定对象的类

    如果我需要根据 R 对象的类以不同的方式处理它们 我可以使用if and else在单个函数内 foo lt function x if inherits x list Foo the list else if inherits x num
  • R data.table fwrite 到 fread 空间分隔符并清空

    我在使用 fread 以 作为分隔符和散布的空白值时遇到问题 例如 这个 dt lt data table 1 5 1 5 1 5 make a simple table dt 3 V2 NA add a blank in the midd
  • 如果条目出现次数少于 x 则删除数据框中的行

    我有以下数据框 称之为 df 它是由三个向量组成的数据框 姓名 年龄 和 邮政编码 df Name Age ZipCode 1 Joe 16 60559 2 Jim 20 60637 3 Bob 64 94127 4 Joe 23 9412
  • 更改 pander 中的默认对齐方式 (pandoc.table)

    我目前正在切换到pander对于我的大部分时间knitr markdown格式化 因为它提供了如此出色的pandoc支持 我不太满意的一件事是默认的居中对齐 营销人员可能会喜欢它 但对于技术报告来说这是一个可怕的事情 使用的最佳选择Hmis
  • 按不规则时间间隔对数据进行分组求和(R语言)

    我正在看这里的 stackoverflow 帖子 R 计算一组内的观察次数 https stackoverflow com questions 65366412 r count number of observations within a
  • 在 igraph 中为社区分配颜色

    我在 igraph 中使用 fastgreedy community 检测算法在 R 中生成社区 代码返回 12 个社区 但是在绘图时很难识别它们 因为它返回的图的颜色数量有限 我怎样才能用十二种不同的颜色绘制这个图表 l2 lt layo
  • RStudio 如何确定控制台宽度,为什么它似乎总是出错?

    我刚刚发现wid lt options width在 RStudio 中 它似乎是我日常控制台使用中令人烦恼的根源 或者更确切地说 更接近根源 我应该先说一下 我目前使用的是 R 3 2 2 RStudio 0 99 491 Linux M
  • 计算互相关函数?

    In R 我在用ccf or acf计算成对互相关函数 以便我可以找出哪个移位给我带来最大值 从它的外观来看 R给我一个标准化的值序列 Python 的 scipy 中是否有类似的东西 或者我应该使用fft模块 目前 我正在这样做 xcor
  • 无法部署 ShinyApp:readTableHeader 在“raw”上发现不完整的最后一行(使用默认值:en_US)

    我已经拼命尝试部署我的闪亮应用程序大约一周了 但不幸的是我无法停止收到以下消息 Warning message Error detecting locale Error in read table file file header head

随机推荐