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.
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é. .
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.
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).
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
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
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))
}
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.
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:
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)
)
}
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.
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
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.