85 votes

Position du soleil vu l’heure, latitude et longitude

Cette question a été posée avant un peu plus de trois ans. Il y avait une réponse, cependant j'ai trouvé une erreur dans la solution.

Le Code ci-dessous dans R. je l'ai porté à une autre langue, mais ont testé le code d'origine directement dans la R de sorte que la question n'était pas avec mon portage.

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

Le problème, je vais frapper, c'est que l'azimut il retourne semble erroné. Par exemple, si je lance la fonction sur l' (sud) solstice d'été, à 12:00 pour les emplacements 0ºE et 41ºS, 3ºS, 3ºN et 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

Ces chiffres ne semblent pas le droit. L'élévation que je suis heureux avec les deux premiers devraient être à peu près la même, le troisième une touche de moins, et le quatrième, beaucoup plus bas. Cependant, le premier azimut doit être à peu près vers le Nord, tandis que le nombre qu'il donne, c'est tout le contraire. Les trois autres doivent point à peu près au Sud, cependant seul le dernier n'. Les deux dans le milieu juste à côté du Nord, à nouveau à 180°.

Comme vous pouvez le voir, il ya aussi un couple d'erreurs déclenchées avec les basses latitudes (près de l'équateur)

Je crois que la faute est dans cette section que l'erreur ne soit déclenchée à la troisième ligne (en commençant par 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

J'ai googlé autour et a trouvé un similaire morceau de code en C, converti à la R la ligne qu'il utilise pour calculer l'azimut serait quelque chose comme

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

La sortie semble se diriger dans la bonne direction, mais je ne peux pas le faire pour me donner le droit de répondre à toutes les fois, quand il est de nouveau converti en degrés.

Une correction du code (suspect c'est juste les quelques lignes ci-dessus) pour faire calculer le bon azimut serait fantastique.

113voto

Josh O'Brien Points 68397

Cela semble être un sujet important, de sorte que j'ai posté plus que la réponse typique: si cet algorithme est utilisé par d'autres dans l'avenir, je pense qu'il est important qu'elle soit accompagnée par des références à la littérature à partir de laquelle elle a été dérivée.

La réponse courte

Comme vous l'avez remarqué, votre posté code ne fonctionne pas correctement pour les emplacements près de l'équateur, ou dans l'hémisphère sud.

Pour le fixer, il suffit de remplacer ces lignes dans votre code d'origine:

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

avec ces:

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

Il devrait maintenant fonctionner pour n'importe quel endroit sur le globe.

Discussion

Le code dans votre exemple est une adaptation presque mot pour mot à partir de 1988, article de J. J. Michalsky (Énergie Solaire. 40:227-235). Que l'article à son tour raffiné un algorithme présenté en 1978 par l'article R. Walraven (Énergie Solaire. 20:393-397). Walraven signalé que la méthode a été utilisée avec succès depuis plusieurs années pour positionner précisément un polarisant radiomètre à Davis, en CALIFORNIE (38° 33' 14" N 121° 44' 17" W).

Les deux Michalsky et Walraven code contient d'importantes/les erreurs fatales. En particulier, alors que Michalsky de l'algorithme fonctionne bien dans la plupart des États-unis, il ne parvient pas (comme vous l'avez constaté) pour les régions proches de l'équateur, ou dans l'hémisphère sud. En 1989, J. W. Spencer de Victoria, en Australie, a noté la même chose (l'Énergie Solaire. 42(4):353):

Cher Monsieur:

Michalsky de la méthode de l'attribution de l'azimut calculé pour le bon quadrant, dérivé de Walraven, ne donne pas les valeurs correctes lorsqu'il est appliqué pour le Sud (négatif) latitudes. En outre le calcul de la critique d'élévation (elc) échoue pour une latitude de zéro à cause de la division par zéro. Ces deux objections peuvent être évités simplement en attribuant à l'azimut pour le bon quadrant en considérant le signe de cos(azimut).

Mes modifications de votre code sont basées sur les corrections proposées par Spencer dans qui a publié Commentaire. J'ai simplement modifié un peu pour s'assurer que la fonction R sunPosition() reste "vectorisé" (c'fonctionne correctement sur les vecteurs de localisation des points de, plutôt que de devoir être transmise d'un point à un moment).

La précision de la fonction sunPosition()

Pour tester qu' sunPosition() fonctionne correctement, j'ai comparé ses résultats avec ceux calculés par la National Oceanic and Atmospheric Administration Calculatrice Solaire. Dans les deux cas, les positions du soleil ont été calculés pour midi (12:00) sur le sud du solstice d'été (22 décembre), 2012. Tous les résultats sont en accord à l'intérieur de 0,02 degrés.

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

D'autres erreurs dans le code

Il y a au moins deux autres (assez petites) erreurs dans les codes affichée. La première provoque le 29 février et le 1er Mars de l'année bissextile à la fois d'être comptabilisés comme des 61 jours de l'année. La deuxième erreur vient d'une faute de frappe dans l'article original, qui a été corrigée par Michalsky en 1989 note (Énergie Solaire. 43(5):323).

Ce bloc de code montre la délinquance lignes, commentée et suivie immédiatement par des versions corrigées:

# 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

Version corrigée de l' sunPosition()

Voici le code corrigé qui a été vérifié ci-dessus:

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

Références:

Michalsky, J. J. 1988. L'Almanach Astronomique de l'algorithme approximatif de la position solaire (1950-2050). L'Énergie Solaire. 40(3):227-235.

Michalsky, J. J. 1989. Errata. L'Énergie Solaire. 43(5):323.

Spencer, J. W., 1989. Commentaires sur "L'Almanach Astronomique de l'Algorithme Approximatif de la Position Solaire (1950-2050)". L'Énergie Solaire. 42(4):353.

Walraven, R. 1978. Calcul de la position du soleil. L'Énergie Solaire. 20:393-397.

19voto

mbask Points 950

À l'aide de "la NOAA Solaire Calculs" de l'un des liens ci-dessus, j'ai changé un peu la dernière partie de la fonction en utilisant un légèrement différentes algorithme qui, je l'espère, ont traduit sans erreurs. J'ai commenté le code inutile et a ajouté l'algorithme de nouveau juste après la latitude de conversion de radians:

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

Pour vérifier l'azimut de la tendance dans les quatre cas, vous l'avez mentionné, nous allons tracer en fonction du temps de la journée:

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)

L'Image ci-jointe:

enter image description here

12voto

Richie Cotton Points 35365

Voici une réécriture qui sont plus idiomatique de R et plus facile à déboguer et à maintenir. C’est essentiellement de Josh réponse, mais avec azimut calculé en utilisant les algorithmes de Josh et de drôles de comparaison. J’ai également inclus les simplifications du code date de mon autre réponse. Le principe de base était de diviser le code en lots de fonctions plus petites que vous pouvez plus facilement écrire des tests unitaires pour.

10voto

Richie Cotton Points 35365

C'est une suggestion de mise à jour de Josh excellente réponse.

Beaucoup de début de la fonction de code réutilisable pour calculer le nombre de jours depuis midi le 1er janvier 2000. C'est beaucoup mieux traitée avec l'utilisation de R existante de la date et de l'heure fonction.

Je pense aussi que plutôt que d'avoir six différentes variables pour spécifier la date et l'heure, c'est plus facile (et plus cohérent avec les autres fonctions R) pour spécifier un objet date ou à une date chaînes + les chaînes de format.

Voici deux fonctions d'aide

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

Et le début de la fonction simplifie à

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

L'autre bizarrerie est dans les lignes comme

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

Depuis mnlong a %% a appelé ses valeurs, ils doivent tous être non négatif déjà, de sorte que cette ligne est superflu.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X