주어진 시간, 위도 및 경도가 주어진 태양의 위치


83

이 질문은 요청 된 이상 3 년 전에 조금. 주어진 대답이 있었지만 솔루션에서 결함을 발견했습니다.

아래 코드는 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))
}

내가 치는 문제는 그것이 반환하는 방위각이 잘못 보인다는 것입니다. 예를 들어, 0ºE 및 41ºS, 3ºS, 3ºN 및 41ºN 위치에 대해 12:00에 (남부)하지에서 함수를 실행하는 경우 :

> 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

이 숫자는 옳지 않은 것 같습니다. 내가 만족하는 고도-처음 두 개는 거의 같고 세 번째는 터치가 더 낮아야하며 네 번째는 훨씬 더 낮아야합니다. 그러나 첫 번째 방위각은 대략 정북이어야하며, 제공하는 숫자는 완전히 반대입니다. 나머지 3 개는 대략 남쪽을 가리켜 야하지만 마지막 하나만 그렇습니다. 중간에있는 두 개는 북쪽에서 약간 떨어져 다시 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에서 더 나은 운이있을 수 있습니다
abcde123483

1
maptools 패키지에이 작업을 수행하는 코드가 있습니다.? solarpos
mdsumner

감사합니다 @ulvund-다음에 시도 할 수 있습니다.
SpoonNZ

4
좋아, 나는 당신이 NOAA 사이트에서 자바 스크립트를 복사해야한다고 생각합니다. 그것은 많은 버전의 소스입니다. 우리가 작성한 코드는이 모든 것을 우리가 필요로하는 두 개의 작은 함수로 축소했지만, 그것은 고도만을위한 것이었고 특정 앱에 맞게 조정되었습니다. 단지의 소스보기 srrb.noaa.gov/highlights/sunrise/azel.html
mdsumner

1
이전 질문에서 내 대답 을 시도해 보셨습니까 ? ephem대기의 굴절 (온도, 압력의 영향)과 관찰자의 고도를 고려할 수도 있습니다.
jfs

답변:


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]

이제 지구상의 모든 위치에서 작동합니다.

토론

이 예제의 코드는 JJ Michalsky (Solar Energy. 40 : 227-235)의 1988 년 기사에서 거의 그대로 적용되었습니다. 이 기사는 R. Walraven (Solar Energy. 20 : 393-397)의 1978 년 기사에 제시된 알고리즘을 차례로 개선했습니다. Walraven은이 방법이 캘리포니아 데이비스 (38 ° 33 '14 "N, 121 ° 44'17"W)에 편광 복사계를 정확하게 배치하는 데 몇 년 동안 성공적으로 사용되었다고보고했습니다.

Michalsky와 Walraven의 코드에는 모두 중요 / 치명적인 오류가 포함되어 있습니다. 특히, Michalsky의 알고리즘은 대부분의 미국에서 잘 작동하지만 적도 근처 또는 남반구 지역에서는 실패합니다. 1989 년에 호주 빅토리아에있는 JW Spencer는 똑같은 사실을 언급했습니다 (Solar Energy. 42 (4) : 353) :

근계:

계산 된 방위각을 Walraven에서 파생 된 올바른 사분면에 할당하는 Michalsky의 방법은 남부 (음수) 위도에 적용될 때 올바른 값을 제공하지 않습니다. 또한 0으로 나누기 때문에 위도가 0 인 경우 임계 고도 (elc) 계산이 실패합니다. cos (azimuth)의 부호를 고려하여 방위각을 올바른 사분면에 할당함으로써이 두 가지 반대를 피할 수 있습니다.

코드에 대한 편집 내용은 게시 된 주석에서 Spencer가 제안한 수정 사항을 기반으로합니다. R 함수 sunPosition()가 '벡터화'상태를 유지 하도록 약간 변경했습니다 (즉, 한 번에 한 지점을 전달하는 대신 지점 위치의 벡터에서 제대로 작동 함).

기능의 정확성 sunPosition()

sunPosition()제대로 작동하는지 테스트하기 위해 그 결과를 국립 해양 대기 청의 태양열 계산기 에서 계산 한 결과와 비교했습니다 . 두 경우 모두 2012 년 12 월 22 일 남하의 정오 (오후 12시)에 태양 위치를 계산했습니다. 모든 결과는 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))
}

참조 :

Michalsky, JJ 1988. 대략적인 태양 위치에 대한 천문 연감의 알고리즘 (1950-2050). 태양 에너지. 40 (3) : 227-235.

Michalsky, JJ 1989. 정오표. 태양 에너지. 43 (5) : 323.

Spencer, JW 1989. "근사적인 태양 위치에 대한 천문 연감의 알고리즘 (1950-2050)"에 대한 주석. 태양 에너지. 42 (4) : 353.

Walraven, R. 1978 년. 태양의 위치 계산. 태양 에너지. 20 : 393-397.


환상적인 답변에 감사드립니다! 나는 주말 동안 여기에 없었기 때문에 미안해 놓쳤다. 적어도 오늘 밤까지 이것을 시도 할 기회를 얻지 못할 것입니다. 그러나 그것이 트릭을 할 것 같습니다. 건배!
SpoonNZ

1
@SpoonNZ-내 기쁨. 인용 된 문헌의 PDF 사본이 필요한 경우 제 이메일 주소로 알려 주시면 보내 드리겠습니다.
Josh O'Brien

1
@ JoshO'Brien : 방금 별도의 답변에 몇 가지 제안을 추가했습니다. 직접 살펴보고 통합 할 수 있습니다.
Richie Cotton

@RichieCotton-제안을 게시 해 주셔서 감사합니다. 나는 그러나, 여기에 추가 않을거야 단지 그들이 있기 때문에 R특이하고, 영업 이익은 다른 언어로 포팅하기 전에 디버깅하려고하는 R-코드를 사용했다. (사실, 난 지금 원래 코드의 날짜-처리 오류를 수정하기 위해 내 게시물을 편집했습니다, 그리고 그건 바로 당신이 제안한 같은 높은 수준의 코드를 사용하기위한 주장 오류의 종류.) 건배를!
Josh O'Brien

줄리안 날짜를 다음과 같이 결합 할 수도 있습니다. time = 365 * (year-2000) + floor ((year-1949) / 4) + day + hour-13.5
Hawk

19

위의 링크 중 하나에서 "NOAA Solar Calculations"를 사용하여 오류없이 번역 된 약간 다른 알고리즘을 사용하여 함수의 마지막 부분을 약간 변경했습니다. 지금 쓸모없는 코드를 주석 처리하고 위도를 라디안으로 변환 한 직후에 새 알고리즘을 추가했습니다.

# -----------------------------------------------
# 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

원하는 경우 이미지 파일을 첨부했습니다.
mdsumner

1
@Charlie-훌륭한 대답이며 플롯은 특히 좋은 추가입니다. 그것들을보기 전에 나는 태양의 야간 방위각 좌표가 '적도'와 더 '온대'위치에 얼마나 다른지 이해하지 못했습니다. 정말 멋지다.
Josh O'Brien

12

다음은 R에 더 관용적이고 디버그 및 유지 관리가 더 쉬운 재 작성입니다. 본질적으로 Josh의 대답이지만 비교를 위해 Josh와 Charlie의 알고리즘을 모두 사용하여 계산 된 방위각이 있습니다. 또한 다른 답변의 날짜 코드에 대한 단순화를 포함했습니다. 기본 원칙은 코드를 단위 테스트를 더 쉽게 작성할 수있는 여러 개의 작은 함수로 분할하는 것입니다.

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는 Longitude West를 + ve로 사용합니다. 이 알고리즘은 Longitude West를 -ve로 사용합니다.
Neon22 2014-06-28

"sunPosition (lat = 43, long = -89)"를 실행하면 고도는 52, 방위각은 175입니다. 그러나 NOAA의 웹 앱 esrl.noaa.gov/gmd/grad/solcalc를 사용 하면 고도가 약 5와 272의 방위각이 있습니다. 내가 뭔가를 놓치고 있습니까? NOAA는 정확하지만 정확한 결과를 제공하기 위해 sunPosition을 얻을 수 없습니다.
Tedward 2016-09-07

@Tedward는 sunPosition기본적으로 현재 시간과 날짜를 사용합니다. 그게 당신이 원했던 건가요?
리치면

예. 나는 또한 몇 가지 다른 시간으로 테스트했습니다. 오늘은 늦었어요. 오늘도 새롭게 시작해 보겠습니다. 내가 뭔가 잘못하고 있다고 확신하지만 무엇을하는지 모르겠다. 계속 노력하겠습니다.
Tedward

정확한 결과를 얻으려면 "언제"를 UTC로 변환해야했습니다. stackoverflow.com/questions/39393514/…를 참조하십시오 . @aichao는 변환을위한 코드를 제안합니다.
Tedward 2016-09-08

10

이것은 Josh의 탁월한 답변에 대한 제안 업데이트입니다.

함수 시작의 대부분은 2000 년 1 월 1 일 정오 이후의 일수를 계산하기위한 상용구 코드입니다. 이것은 R의 기존 날짜 및 시간 함수를 사용하는 경우 훨씬 더 잘 처리됩니다.

또한 날짜와 시간을 지정하기 위해 6 개의 다른 변수를 사용하는 것보다 기존 날짜 개체 또는 날짜 문자열 + 형식 문자열을 지정하는 것이 더 쉽고 (다른 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로 포팅했습니다 (그리고 아마도 자바 스크립트로 갈 것입니다-어떤 함수를 처리 할 것인지 결정해야합니다). 그래서 코드는 나에게별로 도움이되지 않지만 포팅 될 수 있어야합니다. 원래 코드보다 더 많은 생각이 필요합니다!). 시간대를 처리하는 코드를 약간 조정해야하므로이 변경 사항을 동시에 통합 할 수 있습니다.
SpoonNZ 2012 년

2
멋진 변화 @Richie Cotton. 할당 시간 <-hour_of_day는 실제로 hour <-hour_of_day (when)이어야하며 해당 변수 시간은 "difftime"클래스의 객체가 아닌 일 수를 포함해야합니다. astronomers_almanac_time 함수의 두 번째 줄은 as.numeric (difftime (x, origin, units = "days"), units = "days")와 같이 변경해야합니다.
mbask

1
훌륭한 제안에 감사드립니다. (관심이 있다면) 게시물에 전체 sunPosition()기능 의 편집 버전을 포함하는 것이 좋을 수 있습니다 .
Josh O'Brien

@ JoshO'Brien : 완료. 우리의 모든 답변의 조합이기 때문에 답변 커뮤니티 위키를 만들었습니다. 현재 시간과 기본 (Swiss?) 좌표에 대해 귀하와 동일한 답변을 제공하지만 더 많은 테스트가 필요합니다.
Richie Cotton

@RichieCotton-좋은 생각입니다. 기회가 생기는대로 당신이 한 일을 좀 더 자세히 살펴 볼게요.
Josh O'Brien

4

파이썬 프로젝트에서 태양 위치가 필요했습니다. 저는 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) : 시간 + = 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보다 작은 양이어서 acos에 대한 입력이 1보다 크거나 -1보다 작아서는 안되므로 NaN을 생성합니다.

Josh의 계산과 유사한 경우가있을 수 있습니다. 부동 소수점 반올림 효과로 인해 asin 단계에 대한 입력이 -1 : 1을 벗어나게되지만 특정 데이터 세트에서 해당 항목에 도달하지 않았습니다.

6 개 정도의 경우에이 문제가 발생했을 때 "참"(낮이나 밤의 중간)이 발생하므로 경험적으로 실제 값은 1 / -1이어야합니다. 따라서 solarAzimuthRadiansJosh및 내에서 반올림 단계를 적용하여 문제를 해결할 수 solarAzimuthRadiansCharlie있습니다. NOAA 알고리즘의 이론적 정확성이 무엇인지 (수치 적 정확성이 문제가되지 않는 지점) 확실하지 않지만 소수점 이하 12 자리로 반올림하면 데이터 세트의 데이터가 수정되었습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.