web-dev-qa-db-fra.com

Position du soleil en fonction de l'heure, de la latitude et de la longitude

Cette question a été posée avant il y a un peu plus de trois ans. Une réponse a été donnée, mais j'ai trouvé un problème dans la solution.

Le code ci-dessous est en R. Je l'ai porté dans une autre langue, mais j'ai testé le code original directement en R pour m'assurer que le problème ne venait pas de 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 que je rencontre est que l'azimut qu'il renvoie semble incorrect. Par exemple, si j'exécute la fonction au solstice d'été (sud) à 12h00 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 tout simplement pas corrects. L'élévation dont je suis satisfait - les deux premiers devraient être à peu près les mêmes, le troisième un peu plus bas et le quatrième beaucoup plus bas. Cependant, le premier azimut devrait être à peu près dû au nord, tandis que le nombre qu'il donne est tout le contraire. Les trois autres devraient pointer à peu près vers le sud, mais seul le dernier le fait. Les deux au milieu juste à côté du nord, à nouveau à 180º.

Comme vous pouvez le voir, il y a aussi quelques erreurs déclenchées par les basses latitudes (fermez l'équateur)

Je crois que la faute est dans cette section, l'erreur étant 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 parcouru Google et trouvé un morceau de code similaire en C, converti en 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 ici semble aller dans la bonne direction, mais je n'arrive tout simplement pas à me donner la bonne réponse tout le temps lorsqu'elle est reconvertie en degrés.

Une correction du code (suspect que ce ne sont que les quelques lignes ci-dessus) pour lui faire calculer l'azimut correct serait fantastique.

82
SpoonNZ

Cela semble être un sujet important, j'ai donc posté une réponse plus longue que la normale: si cet algorithme doit être utilisé par d'autres à l'avenir, je pense qu'il est important qu'il soit accompagné de références à la littérature dont il est dérivé. .

La réponse courte

Comme vous l'avez noté, votre code affiché ne fonctionne pas correctement pour les emplacements proches de l'équateur ou dans l'hémisphère sud.

Pour le corriger, remplacez simplement 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 du globe.

Discussion

Le code de votre exemple est adapté presque mot pour mot d'un article de 1988 de J.J. Michalsky (Énergie solaire. 40: 227-235). Cet article a ensuite affiné un algorithme présenté dans un article de 1978 de R. Walraven (Solar Energy. 20: 393-397). Walraven a indiqué que la méthode avait été utilisée avec succès pendant plusieurs années pour positionner avec précision un radiomètre polarisant à Davis, Californie (38 ° 33 '14 "N, 121 ° 44' 17" W).

Le code de Michalsky et de Walraven contient des erreurs importantes/fatales. En particulier, alors que l'algorithme de Michalsky fonctionne très bien dans la plupart des États-Unis, il échoue (comme vous 'ai trouvé) pour les zones proches de l'équateur, ou dans l'hémisphère sud. En 1989, J.W. Spencer de Victoria, Australie, a noté la même chose (Solar Energy. 42 (4): 353):

Cher Monsieur:

La méthode de Michalsky pour attribuer l'azimut calculé au quadrant correct, dérivée de Walraven, ne donne pas de valeurs correctes lorsqu'elle est appliquée pour les latitudes méridionales (négatives). De plus, le calcul de l'élévation critique (elc) échouera pour une latitude de zéro en raison de la division par zéro. Ces deux objections peuvent être évitées simplement en affectant l'azimut au quadrant correct en considérant le signe de cos (azimut).

Mes modifications de votre code sont basées sur les corrections suggérées par Spencer dans ce commentaire publié. Je les ai simplement quelque peu modifiés pour m'assurer que la fonction R sunPosition() reste 'vectorisée' (c'est-à-dire qu'elle fonctionne correctement sur les vecteurs de points, au lieu de devoir passer un point à la fois).

Précision de la fonction sunPosition()

Pour tester que sunPosition() fonctionne correctement, j'ai comparé ses résultats avec ceux calculés par la National Oceanic and Atmospheric Administration Solar Calculator . Dans les deux cas, les positions du soleil ont été calculées pour midi (12h00) au solstice d'été sud (22 décembre 2012). Tous les résultats étaient en accord à 0,02 degré prè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

Autres erreurs dans le code

Il y a au moins deux autres erreurs (assez mineures) dans le code affiché. Le premier fait que le 29 février et le 1er mars des années bissextiles sont tous deux comptabilisés comme le 61e jour de l'année. La deuxième erreur provient d'une faute de frappe dans l'article original, qui a été corrigée par Michalsky dans une note de 1989 (Solar Energy. 43 (5): 323).

Ce bloc de code affiche les lignes incriminées, commentées et suivies immédiatement par les 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 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))
}

Les références:

Michalsky, J.J. 1988. Algorithme de l'Almanach astronomique pour la position solaire approximative (1950-2050). Énergie solaire. 40 (3): 227-235.

Michalsky, J.J. 1989. Errata. Énergie solaire. 43 (5): 323.

Spencer, J.W. 1989. Commentaires sur "L'algorithme de l'almanach astronomique pour la position solaire approximative (1950-2050)". Énergie solaire. 42 (4): 353.

Walraven, R. 1978. Calcul de la position du Soleil. Énergie solaire. 20: 393-397.

106
Josh O'Brien

En utilisant "NOAA Solar Calculations" à partir de l'un des liens ci-dessus, j'ai légèrement modifié la dernière partie de la fonction en utilisant un algorithme légèrement différent qui, je l'espère, s'est traduit sans erreur. J'ai commenté le code désormais inutile et ajouté le nouvel algorithme juste après la conversion de latitude en 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 la tendance de l'azimut dans les quatre cas que vous avez mentionnés, traçons-la en fonction de l'heure:

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)

Image jointe:

enter image description here

19
mbask

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

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)
  )
}
12
Richie Cotton

Il s'agit d'une mise à jour suggérée de l'excellente réponse de Josh.

Une grande partie du début de la fonction est un code standard pour calculer le nombre de jours depuis midi le 1er janvier 2000. Ceci est beaucoup mieux traité en utilisant la fonction de date et d'heure existante de R.

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

Voici deux fonctions d'assistance

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 maintenant

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 que mnlong a eu %% a appelé ses valeurs, elles devraient déjà toutes être non négatives, donc cette ligne est superflue.

10
Richie Cotton

J'avais besoin de la position de Sun dans un projet Python. J'ai adapté l'algorithme de Josh O'Brien.

Merci Josh.

Au cas où cela pourrait être utile à n'importe qui, voici mon adaptation.

Notez que mon projet n'a besoin que de la position instantanée du soleil, donc le temps n'est pas un paramètre.

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
4
Jérôme

J'ai rencontré un léger problème avec un point de données et les fonctions de Richie Cotton ci-dessus (dans la mise en œuvre du code de 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

car dans la fonction solarAzimuthRadiansCharlie, il y a eu une excitation en virgule flottante autour d'un angle de 180 telle que (sin(lat) * cos(zenithAngle) - sin(dec)) / (cos(lat) * sin(zenithAngle)) est le plus petit montant supérieur à 1, 1.0000000000000004440892098, qui génère un NaN car l'entrée dans acos ne doit pas être supérieure à 1 ni inférieure à -1.

Je soupçonne qu'il pourrait y avoir des cas Edge similaires pour le calcul de Josh, où les effets d'arrondi en virgule flottante font que l'entrée pour l'étape asin est en dehors de -1: 1 mais je ne les ai pas touchés dans mon ensemble de données particulier.

Dans la demi-douzaine de cas que j'ai rencontrés, le "vrai" (milieu de la journée ou de la nuit) est lorsque le problème se produit de façon empirique, la vraie valeur devrait être 1/-1. Pour cette raison, je serais à l'aise de corriger cela en appliquant une étape d'arrondi dans solarAzimuthRadiansJosh et solarAzimuthRadiansCharlie. Je ne sais pas quelle est la précision théorique de l'algorithme NOAA (le point auquel la précision numérique cesse de toute façon de compter) mais l'arrondi à 12 décimales a fixé les données dans mon ensemble de données.

1
David Hood