This seems to be an important topic, so I published a longer than typical answer: if this algorithm will be used by others in the future, I consider it important that it be accompanied by references to the literature from which it was derived.
Short answer
As you already noted, your published code does not work properly for locations near the equator or in the southern hemisphere.
To fix this, simply replace these lines in the source code:
elc <- asin(sin(dec) / sin(lat)) az[el >= elc] <- pi - az[el >= elc] az[el <= elc & ha > 0] <- az[el <= elc & ha > 0] + twopi
with these:
cosAzPos <- (0 <= sin(dec) - sin(el) * sin(lat)) sinAzNeg <- (sin(az) < 0) az[cosAzPos & sinAzNeg] <- az[cosAzPos & sinAzNeg] + twopi az[!cosAzPos] <- pi - az[!cosAzPos]
Now he must work anywhere on the globe.
Discussion
The code in your example is adapted almost verbatim from a 1988 article by JJ Michalsky (Solar Energy, 40: 227-235). This article, in turn, refined the algorithm presented in R. Walraven (Solar Energy, 20: 393-397). Valraven reported that this method has been used successfully for several years to accurately position a polarizing radiometer in Davis, California (38 ° 33'14 "N, 121 ° 44'17" W).
Both Michalsky and Walraven codes contain important / fatal errors. In particular, although the Michalsky algorithm works very well in most of the United States, it fails (as you already found) for areas close to the equator or in the southern hemisphere. In 1989, JW Spencer from Victoria, Australia, noted the same (Solar Energy 42 (4): 353):
Dear Sir:
The Mikhalsky method for assigning the calculated azimuth to the correct quadrant obtained from Valraven does not give the correct values when applied to the southern (negative) latitudes. Further, the calculation of the critical mark (elc) will fail for latitude zero due to division by zero. Both of these objections can be avoided simply by assigning the azimuth to the correct quadrant by considering the cos sign (azimuth).
My changes to your code are based on the corrections suggested by Spencer in the posted comment. I just modified them a bit to ensure that the R sunPosition() function remains "vectorized" (i.e., it works correctly on point location vectors, and should not be passed one point at a time).
The accuracy of the sunPosition() function
To verify that sunPosition() working correctly, I compared its results with those calculated by the National Oceanic and Atmospheric Administration Solar Calculator . In both cases, the solar positions were calculated at noon (12:00 PM) for the southern summer solstice (December 22), 2012. All results were consistent with an accuracy of 0.02 degrees.
testPts <- data.frame(lat = c(-41,-3,3, 41), long = c(0, 0, 0, 0))
Other code errors
There are at least two other (rather minor) errors in the published code. The first reasons that the February 29 and March 1 leap years coincide coincide with the 61st day. The second error results from a typo in the original article, which was corrected by Mikhalsky in a 1989 note (Solar Energy, 43 (5): 323).
This code block shows the lines of violation, commented out and immediately executed the corrected versions:
# 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
Corrected version of sunPosition()
Here is the corrected code that was checked above:
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, eg 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 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, JW 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)) }
Literature:
Michalsky, JJ 1988. Astronomical Almanac Algorithm for the Approximate Position of the Sun (1950–2050). Solar energy. 40 (3) :. 227-235
Michalsky, JJ 1989. Correction. Solar energy. 43 (5) :. 323
Spencer, JW 1989. Comments on "The Astronomical Almanac Algorithm for Approximate Solar Position (1950-2050)." Solar energy. 42 (4) :. 353
Walraven, R. 1978. Calculation of the position of the sun. Solar energy. twenty:. 393-397