给定时间,纬度和经度的太阳位置


83

三年多以前这个问题。给出了一个答案,但是我在解决方案中发现了一个小故障。

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

这里的输出似乎朝着正确的方向发展,但是当它转换回度数时,我一直无法获得一直给我正确答案的输出。

对代码进行更正(可能只是上面几行),使其能够计算出正确的方位角,这将是非常棒的。


2
您可能在数学
stackexchange中

1
在maptools软件包中有执行此操作的代码,请参见?solarpos
mdsumner'1

感谢@ulvund-接下来可以尝试。
SpoonNZ

4
好的,那么我认为您应该只从NOAA站点复制Javascript,这就是那里很多版本的来源。我们编写的代码将这一切分解为我们需要的两个小函数,但这仅用于提升并调整到特定的应用程序。只需查看srrb.noaa.gov/highlights/sunrise/azel.html
mdsumner 2012年

1
您是否尝试过上一个问题的回答ephem甚至可能考虑到大气的折射(受温度,压力影响)和观察者的高度。
jfs 2012年

Answers:


110

这似乎是一个重要的话题,所以我发布了比典型答案更长的答案:如果将来该算法要供其他人使用,我认为将其衍生自的文献作为参考很重要。 。

简短的答案

如前所述,您发布的代码在赤道附近或南半球的位置无法正常工作。

要解决此问题,只需在原始代码中替换以下几行:

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]

现在,它应该可以在全球任何位置使用。

讨论区

您的示例中的代码几乎完全照搬了1988年JJ Michalsky的文章(Solar Energy。40:227-235)。该文章进而完善了R. Walraven在1978年的一篇文章(Solar Energy。20:393-397)中提出的算法。Walraven报告说,该方法已经成功使用了数年,以在加利福尼亚州戴维斯市精确定位偏光辐射计(北纬38°33'14“,西经121°44'17”)。

Michalsky和Walraven的代码均包含重要/致命错误。特别是,虽然Michalsky的算法在美国大部分地区都可以正常工作,但对于赤道附近或南半球,它却失败了(正如您所发现的)。1989年,澳大利亚维多利亚州的JW Spencer指出了同一件事(Solar Energy。42(4):353):

亲爱的先生:

当应用南部(负)纬度时,米哈尔斯基的将计算出的方位角分配给从瓦尔拉文得出的正确象限的方法不能给出正确的值。此外,由于被零除,因此对于零纬度,临界高程(elc)的计算将失败。通过考虑cos(方位角)的符号将方位角分配给正确的象限,就可以避免这两个反对意见。

我对您的代码所做的修改是基于Spencer在该已发表评论中建议的更正。我只是简单地对其进行了一些更改,以确保R函数sunPosition()保持“向量化”(即,在点位置的向量上正常工作,而不是一次传递一个点)。

功能精度 sunPosition()

为了测试该方法sunPosition()是否正常工作,我将其结果与美国国家海洋和大气管理局的“太阳计算器”计算出的结果进行了比较。在这两种情况下,均计算了2012年南部夏至(12月22日)的正午(12:00 PM)太阳位置。所有结果均在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

代码中的其他错误

发布的代码中至少还有其他两个(非常小的)错误。第一个导致of年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))
}

参考文献:

Michalsky,JJ,1988年。《天文学年历》算法,用于近似太阳位置(1950-2050)。太阳能。40(3):227-235。

Michalsky,JJ,1989年。勘误。太阳能。43(5):323。

Spencer,JW1989。评论“近似太阳位置的天文年历算法(1950-2050年)”。太阳能。42(4):353。

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


感谢您的精彩回答!周末我没来过这里,所以对不起。至少要到今晚才能尝试这种方法,但是看起来可以解决问题。干杯!
SpoonNZ 2012年

1
@SpoonNZ-我很高兴 如果您需要这些引用参考文献的pdf副本,请在我的电子邮件地址中告知我,然后我可以将其发送给您。
乔什·奥布莱恩

1
@ JoshO'Brien:刚刚在一个单独的答案中添加了一些建议。您可能需要看看并将它们合并到您自己的文件中。
Richie Cotton 2012年

@RichieCotton-感谢您发布建议。我不会在这里添加它们,只是因为它们是R特定于-并且OP在将其移植到另一种语言之前使用R代码尝试对其进行调试。(实际上,我刚刚编辑了我的文章,以更正原始代码中的日期处理错误,而这正是使用您提议的更高级别代码所引起的那种错误。)干杯!
乔什·奥布莱恩

也可以将儒略日日期组合为:时间= 365 *(年-2000)+落地((年-1949)/ 4)+天+小时-13.5

19

使用上面链接之一中的“ NOAA太阳计算”,我使用了一种完全不同的算法,希望该函数的翻译没有错误,从而对函数的最后部分做了一些更改。我已经注释掉了现在没用的代码,并在纬度到弧度转换之后添加了新算法:

# -----------------------------------------------
# New code
# Solar zenith angle
zenithAngle <- acos(sin(lat) * sin(dec) + cos(lat) * cos(dec) * cos(ha))
# Solar azimuth
az <- acos(((sin(lat) * cos(zenithAngle)) - sin(dec)) / (cos(lat) * sin(zenithAngle)))
rm(zenithAngle)
# -----------------------------------------------

# 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

# -----------------------------------------------
# New code
if (ha > 0) az <- az + 180 else az <- 540 - az
az <- az %% 360
# -----------------------------------------------

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

为了验证您提到的四种情况下的方位角趋势,让我们针对一天中的时间进行绘制:

hour <- seq(from = 0, to = 23, by = 0.5)
azimuth <- data.frame(hour = hour)
az41S <- apply(azimuth, 1, function(x) sunPosition(2012,12,22,x,0,0,-41,0)$azimuth)
az03S <- apply(azimuth, 1, function(x) sunPosition(2012,12,22,x,0,0,-03,0)$azimuth)
az03N <- apply(azimuth, 1, function(x) sunPosition(2012,12,22,x,0,0,03,0)$azimuth)
az41N <- apply(azimuth, 1, function(x) sunPosition(2012,12,22,x,0,0,41,0)$azimuth)
azimuth <- cbind(azimuth, az41S, az03S, az41N, az03N)
rm(az41S, az03S, az41N, az03N)
library(ggplot2)
azimuth.plot <- melt(data = azimuth, id.vars = "hour")
ggplot(aes(x = hour, y = value, color = variable), data = azimuth.plot) + 
    geom_line(size = 2) + 
    geom_vline(xintercept = 12) + 
    facet_wrap(~ variable)

图片附:

在此处输入图片说明


@Josh O'Brien:您的详细答案是一本好书。作为相关说明,我们的SunPosition函数产生的结果完全相同。
mbask 2012年

如果需要,我会附加图像文件。
mdsumner

1
@Charlie-很好的答案,情节特别好。在看到它们之前,我没有欣赏过太阳在“赤道”位置与更多“温带”位置的夜间方位角坐标有何不同。真的很酷。
乔什·奥布莱恩

12

这是一个重写,它对R更惯用,并且更易于调试和维护。从本质上讲,这是乔什的答案,但使用乔什和查理算法进行比较计算出的方位角。我还包括了其他答案中日期代码的简化。基本原则是将代码分成许多较小的函数,您可以更轻松地为其编写单元测试。

astronomersAlmanacTime <- function(x)
{
  # Astronomer's almanach time is the number of 
  # days since (noon, 1 January 2000)
  origin <- as.POSIXct("2000-01-01 12:00:00")
  as.numeric(difftime(x, origin, units = "days"))
}

hourOfDay <- function(x)
{
  x <- as.POSIXlt(x)
  with(x, hour + min / 60 + sec / 3600)
}

degreesToRadians <- function(degrees)
{
  degrees * pi / 180
}

radiansToDegrees <- function(radians)
{
  radians * 180 / pi
}

meanLongitudeDegrees <- function(time)
{
  (280.460 + 0.9856474 * time) %% 360
}

meanAnomalyRadians <- function(time)
{
  degreesToRadians((357.528 + 0.9856003 * time) %% 360)
}

eclipticLongitudeRadians <- function(mnlong, mnanom)
{
  degreesToRadians(
      (mnlong + 1.915 * sin(mnanom) + 0.020 * sin(2 * mnanom)) %% 360
  )
}

eclipticObliquityRadians <- function(time)
{
  degreesToRadians(23.439 - 0.0000004 * time)
}

rightAscensionRadians <- function(oblqec, eclong)
{
  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] + 2 * pi 
  ra
}

rightDeclinationRadians <- function(oblqec, eclong)
{
  asin(sin(oblqec) * sin(eclong))
}

greenwichMeanSiderealTimeHours <- function(time, hour)
{
  (6.697375 + 0.0657098242 * time + hour) %% 24
}

localMeanSiderealTimeRadians <- function(gmst, long)
{
  degreesToRadians(15 * ((gmst + long / 15) %% 24))
}

hourAngleRadians <- function(lmst, ra)
{
  ((lmst - ra + pi) %% (2 * pi)) - pi
}

elevationRadians <- function(lat, dec, ha)
{
  asin(sin(dec) * sin(lat) + cos(dec) * cos(lat) * cos(ha))
}

solarAzimuthRadiansJosh <- function(lat, dec, ha, el)
{
  az <- asin(-cos(dec) * sin(ha) / cos(el))
  cosAzPos <- (0 <= sin(dec) - sin(el) * sin(lat))
  sinAzNeg <- (sin(az) < 0)
  az[cosAzPos & sinAzNeg] <- az[cosAzPos & sinAzNeg] + 2 * pi
  az[!cosAzPos] <- pi - az[!cosAzPos]
  az
}

solarAzimuthRadiansCharlie <- function(lat, dec, ha)
{
  zenithAngle <- acos(sin(lat) * sin(dec) + cos(lat) * cos(dec) * cos(ha))
  az <- acos((sin(lat) * cos(zenithAngle) - sin(dec)) / (cos(lat) * sin(zenithAngle)))
  ifelse(ha > 0, az + pi, 3 * pi - az) %% (2 * pi)
}

sunPosition <- function(when = Sys.time(), format, lat = 46.5, long = 6.5) 
{    
  if(is.character(when)) when <- strptime(when, format)
  when <- lubridate::with_tz(when, "UTC")
  time <- astronomersAlmanacTime(when)
  hour <- hourOfDay(when)

  # Ecliptic coordinates  
  mnlong <- meanLongitudeDegrees(time)   
  mnanom <- meanAnomalyRadians(time)  
  eclong <- eclipticLongitudeRadians(mnlong, mnanom)     
  oblqec <- eclipticObliquityRadians(time)

  # Celestial coordinates
  ra <- rightAscensionRadians(oblqec, eclong)
  dec <- rightDeclinationRadians(oblqec, eclong)

  # Local coordinates
  gmst <- greenwichMeanSiderealTimeHours(time, hour)  
  lmst <- localMeanSiderealTimeRadians(gmst, long)

  # Hour angle
  ha <- hourAngleRadians(lmst, ra)

  # Latitude to radians
  lat <- degreesToRadians(lat)

  # Azimuth and elevation
  el <- elevationRadians(lat, dec, ha)
  azJ <- solarAzimuthRadiansJosh(lat, dec, ha, el)
  azC <- solarAzimuthRadiansCharlie(lat, dec, ha)

  data.frame(
      elevation = radiansToDegrees(el), 
      azimuthJ  = radiansToDegrees(azJ),
      azimuthC  = radiansToDegrees(azC)
  )
}

在针对NOAA网站进行测试时,请注意以下内容:esrl.noaa.gov/gmd/grad/solcalc/azel.html NOAA将经度西用作+ ve。此算法将“西经”用作-ve。
Neon22年

当我运行“ sunPosition(lat = 43,long = -89)”时,我得到的高度为52,方位角为175。但是使用NOAA的网络应用程序esrl.noaa.gov/gmd/grad/solcalc,我得到的高度大约5,方位角为272。我是否缺少某些东西?NOAA是正确的,但是我无法让sunPosition给出准确的结果。
Tedward

@TedwardsunPosition默认使用当前时间和日期。那是你想要的吗?
Richie Cotton

是。我还测试了一些不同的时间。今天已经很晚了,今天我要重新尝试。我可以肯定我做错了什么,但不知道怎么做。我会继续努力。
Tedward

我需要将“ when”转换为UTC才能获得准确的结果。参见stackoverflow.com/questions/39393514/…。@aichao建议转换代码。
Tedward

10

这是对Josh出色答案的建议更新。

该函数的大部分开始都是样板代码,用于计算自2000年1月1日中午以来的天数。使用R的现有日期和时间函数可以更好地解决这一问题。

我还认为,与其使用六个不同的变量来指定日期和时间,不如(并且与其他R函数更加一致)指定一个现有的日期对象或日期字符串+格式字符串。

这是两个助手功能

astronomers_almanac_time <- function(x)
{
  origin <- as.POSIXct("2000-01-01 12:00:00")
  as.numeric(difftime(x, origin, units = "days"))
}

hour_of_day <- function(x)
{
  x <- as.POSIXlt(x)
  with(x, hour + min / 60 + sec / 3600)
}

现在,该功能的开始简化为

sunPosition <- function(when = Sys.time(), format, lat=46.5, long=6.5) {

  twopi <- 2 * pi
  deg2rad <- pi / 180

  if(is.character(when)) when <- strptime(when, format)
  time <- astronomers_almanac_time(when)
  hour <- hour_of_day(when)
  #...

另一个奇怪的地方是

mnlong[mnlong < 0] <- mnlong[mnlong < 0] + 360

既然mnlong已经%%调用了它的值,那么它们都应该已经是非负的,因此这行是多余的。


十分感谢!如前所述,我已经将其移植到PHP(可能会移植到Javascript-只需决定我要在哪里处理什么函数),这样代码对我没有多大帮助,但应该可以移植(尽管稍作改动)比原始代码需要更多的思考!)。我需要对处理时区的代码进行一些调整,以便可以同时集成此更改。
SpoonNZ

2
漂亮的变化@Richie Cotton。请注意,分配小时<-hour_of_day实际上应该是小时<-hour_of_day(when),并且可变时间应保留天数,而不是“ difftime”类的对象。函数astronomers_almanac_time的第二行应更改为as.numeric(difftime(x,origin,units =“ days”),units =“ days”)。
mbask 2012年

1
感谢您的宝贵建议。(如果您有兴趣)在您的帖子中添加整个sunPosition()功能的编辑版本,使其结构更像R-ish可能会很好。
乔什·奥布莱恩

@ JoshO'Brien:完成。我已经制作了答案社区Wiki,因为它是我们所有答案的组合。它给出与您当前时间和默认(Swiss?)坐标相同的答案,但还需要进行更多测试。
Richie Cotton

@RichieCotton-好主意。只要有机会,我就会更深入地研究您所做的事情。
乔什·奥布莱恩

4

我需要在Python项目中晒太阳。我改编了乔希·奥布莱恩(Josh O'Brien)的算法。

谢谢乔希。

万一它对任何人都有用,这是我的适应。

请注意,我的项目仅需要即时的太阳位置,因此时间不是参数。

def sunPosition(lat=46.5, long=6.5):

    # Latitude [rad]
    lat_rad = math.radians(lat)

    # Get Julian date - 2400000
    day = time.gmtime().tm_yday
    hour = time.gmtime().tm_hour + \
           time.gmtime().tm_min/60.0 + \
           time.gmtime().tm_sec/3600.0
    delta = time.gmtime().tm_year - 1949
    leap = delta / 4
    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)
    t = jd - 51545

    # Ecliptic coordinates

    # Mean longitude
    mnlong_deg = (280.460 + .9856474 * t) % 360

    # Mean anomaly
    mnanom_rad = math.radians((357.528 + .9856003 * t) % 360)

    # Ecliptic longitude and obliquity of ecliptic
    eclong = math.radians((mnlong_deg + 
                           1.915 * math.sin(mnanom_rad) + 
                           0.020 * math.sin(2 * mnanom_rad)
                          ) % 360)
    oblqec_rad = math.radians(23.439 - 0.0000004 * t)

    # Celestial coordinates
    # Right ascension and declination
    num = math.cos(oblqec_rad) * math.sin(eclong)
    den = math.cos(eclong)
    ra_rad = math.atan(num / den)
    if den < 0:
        ra_rad = ra_rad + math.pi
    elif num < 0:
        ra_rad = ra_rad + 2 * math.pi
    dec_rad = math.asin(math.sin(oblqec_rad) * math.sin(eclong))

    # Local coordinates
    # Greenwich mean sidereal time
    gmst = (6.697375 + .0657098242 * t + hour) % 24
    # Local mean sidereal time
    lmst = (gmst + long / 15) % 24
    lmst_rad = math.radians(15 * lmst)

    # Hour angle (rad)
    ha_rad = (lmst_rad - ra_rad) % (2 * math.pi)

    # Elevation
    el_rad = math.asin(
        math.sin(dec_rad) * math.sin(lat_rad) + \
        math.cos(dec_rad) * math.cos(lat_rad) * math.cos(ha_rad))

    # Azimuth
    az_rad = math.asin(
        - math.cos(dec_rad) * math.sin(ha_rad) / math.cos(el_rad))

    if (math.sin(dec_rad) - math.sin(el_rad) * math.sin(lat_rad) < 0):
        az_rad = math.pi - az_rad
    elif (math.sin(az_rad) < 0):
        az_rad += 2 * math.pi

    return el_rad, az_rad

这对我真的很有用。谢谢。我所做的一件事是增加了夏令时的调整。如果使用它,那就很简单:if(time.localtime()。tm_isdst == 1):hour + = 1
Mark Ireland

1

我在上面的数据点和Richie Cotton的函数中遇到了一个小问题(在Charlie代码的实现中)

longitude= 176.0433687000000020361767383292317390441894531250
latitude= -39.173830619999996827118593500927090644836425781250
event_time = as.POSIXct("2013-10-24 12:00:00", format="%Y-%m-%d %H:%M:%S", tz = "UTC")
sunPosition(when=event_time, lat = latitude, long = longitude)
elevation azimuthJ azimuthC
1 -38.92275      180      NaN
Warning message:
In acos((sin(lat) * cos(zenithAngle) - sin(dec))/(cos(lat) * sin(zenithAngle))) : NaNs produced

因为在solarAzimuthRadiansCharlie函数中,围绕180度角存在浮点兴奋,因此 (sin(lat) * cos(zenithAngle) - sin(dec)) / (cos(lat) * sin(zenithAngle))因此最小的数量超过1,1.0000000000000004440892098,该数量生成NaN,因为对acos的输入不应大于1或小于-1。

我怀疑Josh的计算可能会出现类似的边缘情况,其中浮点舍入效果导致asin步骤的输入在-1:1之外,但是我没有在我的特定数据集中找到它们。

在我遇到的大约六种情况下,“真”(白天或夜晚)是问题发生的时间,因此从经验上讲,真值应为1 / -1。因此,我很乐意通过在solarAzimuthRadiansJosh和中应用舍入步骤来解决此问题solarAzimuthRadiansCharlie。我不确定NOAA算法的理论精度是多少(无论如何数值精度都不再重要),但是四舍五入到小数点后12位固定了我数据集中的数据。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.