Vị trí của mặt trời vào thời gian nhất định trong ngày, vĩ độ và kinh độ


83

Câu hỏi này đã được hỏi trước đây hơn ba năm. Đã có một câu trả lời được đưa ra, tuy nhiên tôi đã tìm thấy giải pháp có trục trặc.

Mã bên dưới bằng R. Tôi đã chuyển nó sang ngôn ngữ khác, tuy nhiên, tôi đã kiểm tra mã gốc trực tiếp trong R để đảm bảo vấn đề không xảy ra với quá trình chuyển của tôi.

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))
}

Vấn đề tôi đang gặp phải là phương vị nó trả về có vẻ sai. Ví dụ: nếu tôi chạy hàm vào ngày hạ chí (miền nam) lúc 12:00 cho các vị trí 0ºE và 41ºS, 3ºS, 3ºN và 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

Những con số này có vẻ không đúng. Độ cao mà tôi hài lòng - hai cái đầu tiên phải gần bằng nhau, cái thứ ba thấp hơn một chút và cái thứ tư thấp hơn nhiều. Tuy nhiên, phương vị đầu tiên phải là hướng Bắc, trong khi con số mà nó đưa ra thì hoàn toàn ngược lại. Ba điểm còn lại sẽ gần đúng hướng Nam, tuy nhiên chỉ có cái cuối cùng làm được. Hai điểm ở giữa chỉ cách Bắc, một lần nữa ra ngoài 180º.

Như bạn có thể thấy, cũng có một số lỗi được kích hoạt với các vĩ độ thấp (gần đường xích đạo)

Tôi tin rằng lỗi nằm trong phần này, với lỗi được kích hoạt ở dòng thứ ba (bắt đầu bằng 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

Tôi tìm kiếm xung quanh và tìm thấy một đoạn mã tương tự trong C, được chuyển đổi thành R dòng nó sử dụng để tính toán góc phương vị sẽ giống như

az <- atan(sin(ha) / (cos(ha) * sin(lat) - tan(dec) * cos(lat)))

Kết quả đầu ra ở đây dường như đang đi đúng hướng, nhưng tôi không thể đưa nó cho tôi câu trả lời đúng mọi lúc khi nó được chuyển đổi trở lại độ.

Việc sửa mã (nghi ngờ nó chỉ là vài dòng ở trên) để làm cho nó tính toán đúng phương vị sẽ thật tuyệt vời.


2
Bạn có thể có may mắn hơn trong stackexchange toán
abcde123483

1
Có mã để làm điều này trong gói maptools, xem solarpos?
mdsumner

Cảm ơn @ulvund - có thể thử ở đó tiếp theo.
SpoonNZ

4
Được rồi, tôi nghĩ bạn chỉ nên sao chép Javascript từ trang NOAA, đó là nguồn của rất nhiều phiên bản ngoài kia. Đoạn mã chúng tôi viết đã thu gọn tất cả những thứ này thành chỉ những gì chúng tôi cần trong hai hàm nhỏ, nhưng đó chỉ dành cho nâng cao và điều chỉnh cho một ứng dụng cụ thể. Chỉ cần xem nguồn của srrb.noaa.gov/highlights/sunrise/azel.html
mdsumner

1
bạn đã thử câu trả lời của tôi từ câu hỏi trước chưa? ephemthậm chí có thể tính đến sự khúc xạ của khí quyển (bị ảnh hưởng bởi nhiệt độ, áp suất) và độ cao của người quan sát.
jfs

Câu trả lời:


110

Đây có vẻ như là một chủ đề quan trọng, vì vậy tôi đã đăng một câu trả lời dài hơn so với thông thường: nếu thuật toán này được sử dụng bởi những người khác trong tương lai, tôi nghĩ điều quan trọng là nó phải kèm theo các tham chiếu đến tài liệu mà nó đã được tạo ra .

Câu trả lời ngắn gọn

Như bạn đã lưu ý, mã đã đăng của bạn không hoạt động chính xác cho các vị trí gần đường xích đạo hoặc ở bán cầu nam.

Để khắc phục, chỉ cần thay thế các dòng này trong mã gốc của bạn:

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

với những điều này:

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

Bây giờ nó sẽ hoạt động cho bất kỳ vị trí nào trên thế giới.

Thảo luận

Đoạn mã trong ví dụ của bạn được điều chỉnh gần như nguyên văn từ một bài báo năm 1988 của JJ Michalsky (Solar Energy. 40: 227-235). Bài báo đó lần lượt tinh chỉnh một thuật toán được trình bày trong một bài báo năm 1978 của R. Walraven (Năng lượng mặt trời. 20: 393-397). Walraven báo cáo rằng phương pháp này đã được sử dụng thành công trong vài năm để định vị chính xác một máy đo bức xạ phân cực ở Davis, CA (38 ° 33 '14 "N, 121 ° 44' 17" W).

Cả mã của Michalsky và Walraven đều chứa các lỗi quan trọng / nghiêm trọng. Đặc biệt, trong khi thuật toán của Michalsky hoạt động tốt ở hầu hết Hoa Kỳ, nó không thành công (như bạn đã tìm thấy) đối với các khu vực gần xích đạo hoặc ở bán cầu nam. Vào năm 1989, JW Spencer ở Victoria, Úc, đã ghi nhận điều tương tự (Năng lượng Mặt trời. 42 (4): 353):

Xin chào ngài:

Phương pháp của Michalsky để chỉ định góc phương vị được tính toán cho đúng góc phần tư, bắt nguồn từ Walraven, không đưa ra giá trị chính xác khi áp dụng cho các vĩ độ Nam (âm). Hơn nữa, việc tính toán độ cao tới hạn (elc) sẽ không thành công đối với vĩ độ bằng không vì phép chia cho số không. Có thể tránh được cả hai phản đối này chỉ đơn giản bằng cách gán góc phương vị cho đúng góc phần tư bằng cách xem xét dấu hiệu của cos (phương vị).

Các chỉnh sửa của tôi đối với mã của bạn dựa trên các chỉnh sửa do Spencer đề xuất trong Nhận xét đã xuất bản đó. Tôi chỉ đơn giản là đã thay đổi chúng một chút để đảm bảo rằng chức năng RsunPosition() vẫn được 'vectơ hóa' (tức là hoạt động bình thường trên vectơ của các vị trí điểm, thay vì cần phải được chuyển qua từng điểm một).

Độ chính xác của chức năng sunPosition()

Để kiểm tra kết sunPosition()quả hoạt động chính xác, tôi đã so sánh kết quả của nó với kết quả được tính toán bởi Máy tính Mặt trời của Cục Quản lý Khí quyển và Đại dương Quốc gia . Trong cả hai trường hợp, vị trí mặt trời được tính toán cho giữa trưa (12:00 PM) vào ngày hạ chí phía nam (ngày 22 tháng 12) năm 2012. Tất cả các kết quả đều thống nhất trong khoảng 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

Các lỗi khác trong mã

Có ít nhất hai lỗi khác (khá nhỏ) trong mã đã đăng. Nguyên nhân đầu tiên khiến ngày 29 tháng 2 và ngày 1 tháng 3 của các năm nhuận đều được coi là ngày 61 trong năm. Lỗi thứ hai bắt nguồn từ một lỗi chính tả trong bài báo gốc, đã được Michalsky sửa lại trong một ghi chú năm 1989 (Năng lượng Mặt trời. 43 (5): 323).

Khối mã này hiển thị các dòng vi phạm, được nhận xét và theo sau là các phiên bản sửa chữa:

# 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

Phiên bản đã sửa của sunPosition()

Đây là mã sửa chữa đã được xác minh ở trên:

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))
}

Người giới thiệu:

Michalsky, JJ 1988. Thuật toán Thiên văn học cho vị trí gần đúng của mặt trời (1950-2050). Năng lượng mặt trời. 40 (3): 227-235.

Michalsky, JJ 1989. Errata. Năng lượng mặt trời. 43 (5): 323.

Spencer, JW 1989. Bình luận về "Thuật toán thiên văn học cho vị trí mặt trời gần đúng (1950-2050)". Năng lượng mặt trời. 42 (4): 353.

Walraven, R. 1978. Tính vị trí của mặt trời. Năng lượng mặt trời. 20: 393-397.


Cảm ơn vì câu trả lời tuyệt vời! Tôi đã không ở đây vào cuối tuần nên bỏ lỡ nó, xin lỗi. Sẽ không có cơ hội để thử điều này cho đến ít nhất là tối nay, nhưng có vẻ như nó sẽ thành công. Chúc mừng!
SpoonNZ

1
@SpoonNZ - Niềm vui của tôi. Nếu bạn cần bản sao pdf của bất kỳ tài liệu tham khảo được trích dẫn nào, hãy cho tôi biết theo địa chỉ email của tôi, và tôi có thể gửi chúng cho bạn.
Josh O'Brien

1
@ JoshO'Brien: Chỉ cần thêm một số gợi ý trong một câu trả lời riêng. Bạn có thể muốn xem và kết hợp chúng theo ý mình.
Richie Cotton

@RichieCotton - Cảm ơn bạn đã đăng đề xuất của mình. Tôi sẽ không thêm chúng ở đây, mà chỉ vì chúng đặc Rbiệt và OP đang sử dụng mã R để cố gắng gỡ lỗi trước khi chuyển nó sang ngôn ngữ khác. (Trên thực tế, tôi vừa mới chỉnh sửa bài đăng của mình để sửa lỗi xử lý ngày tháng trong mã gốc và đó chính xác là loại lỗi lập luận cho việc sử dụng mã cấp cao hơn như bạn đã đề xuất.) Chúc mừng!
Josh O'Brien

Người ta cũng có thể kết hợp ngày julian thành: time = 365 * (năm - 2000) + tầng ((năm - 1949) / 4) + ngày + giờ - 13,5
Hawk

19

Bằng cách sử dụng "NOAA Solar Calculations" từ một trong các liên kết ở trên, tôi đã thay đổi một chút phần cuối cùng của hàm bằng cách sử dụng một thuật toán hoàn toàn khác mà tôi hy vọng đã dịch mà không có lỗi. Tôi đã nhận xét về đoạn mã bây giờ vô dụng và thêm thuật toán mới ngay sau khi chuyển đổi vĩ độ sang radian:

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

Để xác minh xu hướng phương vị trong bốn trường hợp bạn đã đề cập, hãy vẽ biểu đồ của nó theo thời gian trong ngày:

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)

Hình ảnh đính kèm:

nhập mô tả hình ảnh ở đây


@Josh O'Brien: Câu trả lời rất chi tiết của bạn là một bài đọc tuyệt vời. Như một lưu ý liên quan, các hàm SunPosition của chúng tôi mang lại kết quả chính xác như nhau.
mbask

Tôi đã đính kèm tệp hình ảnh, nếu bạn muốn.
mdsumner

1
@Charlie - Câu trả lời tuyệt vời và các âm mưu là một bổ sung đặc biệt tốt đẹp. Trước khi nhìn thấy chúng, tôi đã không đánh giá cao tọa độ phương vị ban đêm của mặt trời sẽ khác nhau như thế nào tại các vị trí "xích đạo" so với các vị trí "ôn đới" hơn. Quả thật rất tuyệt.
Josh O'Brien

12

Đây là cách viết lại bằng R dễ thành ngữ hơn, đồng thời dễ gỡ lỗi và bảo trì hơn. Về cơ bản đây là câu trả lời của Josh, nhưng với góc phương vị được tính toán bằng cách sử dụng cả thuật toán của Josh và Charlie để so sánh. Tôi cũng đã bao gồm các đơn giản hóa cho mã ngày từ câu trả lời khác của tôi. Nguyên tắc cơ bản là chia mã thành nhiều chức năng nhỏ hơn mà bạn có thể dễ dàng viết các bài kiểm tra đơn vị hơn.

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)
  )
}

Lưu ý khi thử nghiệm trên trang web của NOAA tại đây: esrl.noaa.gov/gmd/grad/solcalc/azel.html NOAA đó sử dụng Longitude West làm + ve. Thuật toán này sử dụng Kinh độ Tây là -ve.
Neon22

Khi tôi chạy "sunPosition (lat = 43, long = -89)", tôi nhận được độ cao là 52 và góc phương vị là 175. Nhưng bằng cách sử dụng ứng dụng web của NOAA, esrl.noaa.gov/gmd/grad/solcalc , tôi nhận được độ cao là khoảng 5 và phương vị là 272. Tôi có thiếu thứ gì không? NOAA đúng, nhưng tôi không thể lấy sunPosition để đưa ra kết quả chính xác.
Tedward

@Tedward sunPositionmặc định sử dụng ngày và giờ hiện tại. Là những gì bạn muốn?
Richie Cotton

Đúng. Tôi cũng đã thử nghiệm với một số thời điểm khác nhau. Hôm nay đã muộn, hôm nay tôi sẽ thử lại với một khởi đầu mới. Tôi khá chắc mình đang làm gì đó sai, nhưng không biết là gì. Tôi sẽ tiếp tục làm việc với nó.
Tedward

Tôi cần chuyển đổi "khi nào" sang UTC để nhận được kết quả chính xác. Xem stackoverflow.com/questions/39393514/… . @aichao đề xuất mã để chuyển đổi.
Tedward

10

Đây là bản cập nhật gợi ý cho câu trả lời xuất sắc của Josh.

Phần lớn thời gian bắt đầu của hàm là mã soạn sẵn để tính số ngày kể từ trưa ngày 1 tháng 1 năm 2000. Điều này được xử lý tốt hơn nhiều khi sử dụng hàm ngày và giờ hiện có của R.

Tôi cũng nghĩ rằng thay vì có sáu biến khác nhau để chỉ định ngày và giờ, sẽ dễ dàng hơn (và nhất quán hơn với các hàm R khác) để chỉ định một đối tượng ngày hiện có hoặc một chuỗi ngày + chuỗi định dạng.

Đây là hai hàm trợ giúp

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)
}

Và phần bắt đầu của hàm bây giờ đơn giản hóa thành

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

Sự kỳ lạ khác là ở các dòng như

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

mnlongđã được %%gọi trên các giá trị của nó, tất cả chúng phải là không âm, vì vậy dòng này là thừa.


Cảm ơn rất nhiều! Như đã đề cập, tôi đã chuyển mã này sang PHP (và có lẽ sẽ chuyển sang Javascript - chỉ cần quyết định nơi tôi muốn những chức năng nào được xử lý) để mã đó không giúp ích nhiều cho tôi, nhưng sẽ có thể được chuyển (mặc dù với một chút liên quan đến nhiều tư duy hơn so với mã gốc!). Tôi cần chỉnh sửa mã xử lý múi giờ một chút để có thể tích hợp thay đổi này cùng một lúc.
SpoonNZ

2
Nifty thay đổi @Richie Cotton. Lưu ý rằng giờ gán <- giờ_ ngày_ngày thực sự phải là giờ <- giờ_giờ_ngày (khi nào) và thời gian biến đổi đó phải chứa số ngày, không phải là một đối tượng của lớp "difftime". Dòng thứ hai của hàm astronomers_almanac_time nên được thay đổi thành một cái gì đó như as.numeric (difftime (x, origin, units = "days"), units = "days").
mbask

1
Cảm ơn vì những gợi ý tuyệt vời. Có thể là tốt (nếu bạn quan tâm) khi đưa vào bài đăng của mình một phiên bản đã chỉnh sửa của toàn bộ sunPosition()hàm có cấu trúc R-ish hơn.
Josh O'Brien

@ JoshO'Brien: Xong. Tôi đã tạo wiki cộng đồng câu trả lời, vì nó là sự kết hợp của tất cả các câu trả lời của chúng tôi. Nó đưa ra câu trả lời giống như của bạn cho thời gian hiện tại và tọa độ mặc định (Thụy Sĩ?) Nhưng cần phải thử nghiệm nhiều hơn nữa.
Richie Cotton

@RichieCotton - Thật là một ý tưởng hay. Tôi sẽ xem xét sâu hơn những gì bạn đã làm, ngay khi tôi có cơ hội.
Josh O'Brien

4

Tôi cần vị trí mặt trời trong một dự án Python. Tôi đã điều chỉnh thuật toán của Josh O'Brien.

Cảm ơn Josh.

Trong trường hợp nó có thể hữu ích cho bất kỳ ai, đây là sự thích nghi của tôi.

Lưu ý rằng dự án của tôi chỉ cần vị trí mặt trời tức thì nên thời gian không phải là một tham số.

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

Điều này thực sự hữu ích đối với tôi. Cảm ơn. Một điều tôi đã làm là thêm điều chỉnh cho Tiết kiệm ánh sáng ban ngày. Trong trường hợp nó được sử dụng, nó chỉ đơn giản là: if (time.localtime (). Tm_isdst == 1): giờ + = 1
Mark Ireland

1

Tôi gặp sự cố nhỏ với điểm dữ liệu và các chức năng của Richie Cotton ở trên (trong quá trình triển khai mã của 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

bởi vì trong hàm solarAzimuthRadiansCharlie đã có dấu chấm động kích thích xung quanh một góc 180, đó (sin(lat) * cos(zenithAngle) - sin(dec)) / (cos(lat) * sin(zenithAngle))là lượng nhỏ nhất trên 1, 1.0000000000000004440892098, tạo ra NaN làm đầu vào cho acos không được trên 1 hoặc dưới -1.

Tôi nghi ngờ có thể có các trường hợp cạnh tương tự đối với tính toán của Josh, trong đó hiệu ứng làm tròn dấu phẩy động khiến đầu vào cho bước asin nằm ngoài -1: 1 nhưng tôi đã không nhấn chúng trong tập dữ liệu cụ thể của mình.

Trong khoảng nửa tá trường hợp tôi đã gặp phải trường hợp này, "true" (giữa ngày hoặc đêm) là khi sự cố xảy ra nên theo kinh nghiệm, giá trị true phải là 1 / -1. Vì lý do đó, tôi có thể thoải mái sửa lỗi đó bằng cách áp dụng một bước làm tròn trong solarAzimuthRadiansJoshsolarAzimuthRadiansCharlie. Tôi không chắc độ chính xác lý thuyết của thuật toán NOAA là bao nhiêu (điểm mà tại đó độ chính xác số ngừng quan trọng) nhưng việc làm tròn đến 12 chữ số thập phân đã sửa dữ liệu trong tập dữ liệu của tôi.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.