Положення сонця за заданим часом доби, широтою та довготою


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
Можливо, вам пощастить у математичному обміні
abcde123483

1
Код для цього є в пакеті maptools, дивіться? Solarpos
mdsumner

Дякую @ulvund - може спробувати там далі.
SpoonNZ

4
Гаразд, тоді я думаю, вам слід просто скопіювати Javascript з сайту 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]

Тепер він повинен працювати в будь-якому місці на земній кулі.

Обговорення

Код у вашому прикладі майже дослівно адаптований до статті Дж. Дж. Міхальського (Solar Energy. 40: 227-235). Ця стаття, у свою чергу, вдосконалила алгоритм, представлений у статті Р. Вальравена 1978 р. (Сонячна енергія. 20: 393-397). Уолревен повідомив, що метод успішно застосовувався протягом декількох років для точного розташування поляризуючого радіометра в Девісі, Каліфорнія (38 ° 33 '14 "пн.ш., 121 ° 44' 17" з.д.).

І код Михальського, і Вальравен містять важливі / фатальні помилки. Зокрема, хоча алгоритм Міхальського працює чудово в більшості Сполучених Штатів, він не працює (як ви вже виявили) для районів поблизу екватора або в південній півкулі. У 1989 р. Дж. В. Спенсер з Вікторії, Австралія, зазначив те саме (Сонячна енергія. 42 (4): 353):

Дорогий сер:

Метод Міхальського для присвоєння обчисленого азимуту правильному квадранту, отриманого з Вальравена, не дає правильних значень, якщо застосовувати його для південних (негативних) широт. Надалі обчислення критичної висоти (elc) не вдасться для нульової широти через ділення на нуль. Обидва ці заперечення можна уникнути, просто приписавши азимут правильному квадранту, враховуючи знак cos (азимут).

Мої редагування вашого коду базуються на виправленнях, запропонованих Спенсером в опублікованому коментарі. Я просто дещо змінив їх, щоб переконатись, що функція R sunPosition()залишається "векторизованою" (тобто працює належним чином над векторами розташування точок, а не потребує передачі по одній точці за раз).

Точність функції sunPosition()

Щоб перевірити sunPosition()правильність роботи, я порівняв її результати з результатами, розрахованими Сонячним калькулятором Національної адміністрації океанів та атмосфери . В обох випадках сонячні позиції були розраховані на південь (12:00 вечора) на південному літньому сонцестоянні (22 грудня) 2012 року. Усі результати були збіжні з точністю до 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

Інші помилки в коді

У розміщеному коді є принаймні дві інші (досить незначні) помилки. Перша причина, що 29 лютого та 1 березня високосного року обидва вважатимуться днем ​​61 року. Друга помилка випливає з друкарської помилки в оригінальній статті, яку Міхалскі виправив у примітці 1989 р. (Сонячна енергія. 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.

Міхальський, JJ 1989. Помилка. Сонячна енергія. 43 (5): 323.

Спенсер, штат Джорджія, 1989. Коментарі до "Алгоритму астрономічного альманаху для приблизної сонячної позиції (1950-2050)". Сонячна енергія. 42 (4): 353.

Walraven, R. 1978. Розрахунок положення сонця. Сонячна енергія. 20: 393-397.


Дякую за фантастичну відповідь! Я не був тут протягом вихідних, тому пропустив це, вибачте. Не отримаю можливості спробувати це хоча б сьогодні ввечері, але, схоже, це зробить трюк. Ура!
SpoonNZ

1
@SpoonNZ - із задоволенням. Якщо вам потрібні копії будь-якого із зазначених посилань у форматі PDF, повідомте мене про це на мою електронну адресу, і я зможу їх надіслати вам.
Джош О'Брайен

1
@ JoshO'Brien: Просто додав кілька пропозицій в окрему відповідь. Можливо, ви захочете поглянути і включити їх у свій власний.
Річі Коттон,

@RichieCotton - Дякуємо за розміщення ваших пропозицій. Я не збираюся додавати їх сюди, але лише тому, що вони є Rспецифічними, а OP використовував R-код, щоб спробувати налагодити його перед перенесенням на іншу мову. (Насправді, я щойно відредагував свою публікацію, щоб виправити помилку обробки дати в оригінальному коді, і саме така помилка аргументує використання коду вищого рівня, подібного запропонованому вами.) Вітаємо!
Джош О'Брайен,

Можна також поєднати юліанські дати: час = 365 * (рік - 2000) + поверх ((рік - 1949) / 4) + день + година - 13,5
Яструб

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)

Зображення додається:

введіть тут опис зображення


@ Джош О'Брайен: Ваша дуже детальна відповідь - чудове читання. Як відповідне зауваження, наші функції SunPosition дають абсолютно однакові результати.
mbask

Я вклав файл зображення, якщо ви цього хочете.
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 використовує Longitude West як + ve. Цей алгоритм використовує Longitude West як -ve.
Neon22,

Коли я запускаю "sunPosition (lat = 43, long = -89)", я отримую висоту 52 і азимут 175. Але за допомогою веб-програми NOAA esrl.noaa.gov/gmd/grad/solcalc я отримую висоту близько 5 і азимут 272. Чи щось мені не вистачає? NOAA правильний, але я не можу отримати sunPosition, щоб дати точні результати.
Тедвард

За sunPositionзамовчуванням @Tedward використовується поточний час і дата. Це те, що ти хотів?
Річі Коттон

Так. Я також тестував з різними часами. Це було пізно вдень, я збираюся спробувати ще раз сьогодні з нового початку. Я майже впевнений, що роблю щось не так, але не знаю чого. Я продовжуватиму над цим працювати.
Тедвард

Мені потрібно було перевести "коли" в UTC, щоб отримати точні результати. Див. Stackoverflow.com/questions/39393514/… . @aichao пропонує код для перетворення.
Тедвард

10

Це пропоноване оновлення чудової відповіді Джоша.

Більшу частину початку функції складає шаблонний код для обчислення кількості днів з полудня 1 січня 2000 р. Це набагато краще вирішити, використовуючи існуючу функцію дати та часу 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 (коли), і цей змінний час повинен містити кількість днів, а не об'єкт класу "difftime". Другий рядок функції astronomers_almanac_time слід змінити на щось на зразок as.numeric (difftime (x, origin, units = "days"), units = "days").
mbask

1
Дякуємо за чудові пропозиції. Було б непогано (якщо вам цікаво) включити до вашого допису відредаговану версію всієї sunPosition()функції, яка є більш R-ішною у її побудові.
Джош О'Брайен

@ JoshO'Brien: Готово. Я зробив вікі-спільноту відповідей, оскільки це поєднання всіх наших відповідей. Він дає ту саму відповідь, що і ваша, на поточний час та за замовчуванням (швейцарські?) Координати, але потрібно ще багато тестування.
Richie Cotton,

@RichieCotton - Яка приємна ідея. Я глибше подивлюсь на те, що ви зробили, як тільки отримаю можливість.
Джош О'Брайен,

4

Мені знадобилося положення сонця в проекті Python. Я адаптував алгоритм Джоша О'Брайена.

Дякую Джошу.

На випадок, якщо це може бути корисним комусь, ось моя адаптація.

Зверніть увагу, що моєму проекту потрібно було лише миттєве положення сонця, тому час не є параметром.

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
Марк Ірландія

1

Я зіткнувся з невеликою проблемою з точкою даних та функціями Річі Коттона вище (при реалізації коду Чарлі)

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, 10000000000000004440892098, що генерує NaN, оскільки вхідні дані для acos не повинні бути вище 1 або нижче -1.

Я підозрюю, що для обчислення Джоша можуть бути подібні випадки ребер, коли ефекти округлення з плаваючою комою призводять до того, що вхід для кроку asin знаходиться за межами -1: 1, але я не вражав їх у своєму конкретному наборі даних.

Приблизно в півдюжині випадків, коли я вдарився про це, "істинно" (середина дня чи ночі) - це коли проблема виникає настільки емпірично, істинним значенням має бути 1/1. З цієї причини мені було б зручно це виправити, застосувавши крок округлення всередині solarAzimuthRadiansJoshі solarAzimuthRadiansCharlie. Я не впевнений, якою є теоретична точність алгоритму NOAA (точка, в якій чисельна точність і так перестає мати значення), але округлення до 12 знаків після коми зафіксувало дані у моєму наборі даних.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.