R/meeus.r

Defines functions azimuth_angle atm_refraction_approx elevation_angle zenith_angle hour_angle solar_tod solar_datetime sunlight_duration sunset sunrise solar_noon ha_sunrise eq_of_time var_y sun_decline sun_rt_ascen obliq_corr mean_obliq_eclip sun_app_lon sun_rad_vector sun_eq_of_ctr eccent_earth_orbit geom_mean_anom_sun geom_mean_lon_sun julian_century julian_day_fast julian_day

Documented in atm_refraction_approx azimuth_angle eccent_earth_orbit elevation_angle eq_of_time geom_mean_anom_sun geom_mean_lon_sun ha_sunrise hour_angle julian_century julian_day julian_day_fast mean_obliq_eclip obliq_corr solar_datetime solar_noon solar_tod sun_app_lon sun_decline sun_eq_of_ctr sunlight_duration sun_rad_vector sunrise sun_rt_ascen sunset var_y zenith_angle

#' @rdname julian_day
#'
calendar_change <- lubridate::ymd_hms("1582-10-14 22:20:11 UTC") # Julian -> Gregorian

# All functions defined in this file are "internal" and not exported
# They are organized as very small functions to allow reuse of the results of
# partial calculations. All constants are contained in the code itself.
# They are based in "NOAA Sunrise/Sunset and Solar Position Calculators"
# available at http://www.esrl.noaa.gov/gmd/grad/solcalc/calcdetails.html
#' Solar astronomy using Meeus' algorithm
#'
#' The exact julian day computation is adapted from ode::julianDay() and tested
#' againts test cases in Redas and Andreas (2008, table A4.1) for validity up
#' to year 4712 BC.
#'
#' Low level functions based on NOAA's Excel worksheet
#'
#' @param time dateTime
#' @param x numeric Julian century
#' @param anom numeric Solar anomaly in degrees
#' @param eccent numeric Eccentricity of Earth orbit
#' @param eclip numeric Ecliptic
#' @param app.lon,obliq.corr,mean.lon,nag,decline numeric Angles in degrees
#' @param eq.of.time,ha.sunrise,noon numeric
#' @param zenith.angle,elevation.angle,hour.angle numeric Angles in degrees
#' @param lat,lon numeric Geographic coordinates in degrees
#'
#' @keywords internal
#'
julian_day <- function(time) {
  time <- lubridate::with_tz(time, tzone = "UTC")
  y <- as.double(lubridate::year(time))
  m <- as.double(lubridate::month(time))
  d <- as.double(lubridate::day(time))
  selector <- m <= 2
  m[selector] <- m[selector] + 12
  y[selector] <- y[selector] - 1
  d <- d + as_tod(time, unit.out = "hours", tz = "UTC") / 24
  a <- floor(y / 100)
  b <- 2 - a + floor(a / 4)
  jd <- floor(365.25 * y) + floor(30.6001 * (m + 1)) + d + 1720994.5
  ifelse(time > calendar_change, jd + b, jd)
}

#' @rdname julian_day
#'
julian_day_fast <- function(time) {
  2440587.79166667 + as.numeric(julian(time))
}

#' @rdname julian_day
#'
julian_century <- function(time) {
  (julian_day(time) - 2451545) / 36525
}

#' @rdname julian_day
#'
geom_mean_lon_sun <- function(x) {
  (280.46646 + x * (36000.76983 + x * 0.0003032)) %% 360
}

#' @rdname julian_day
#'
geom_mean_anom_sun <- function(x) {
  357.52911 + x * (35999.05029 - 0.0001537 * x)
}

#' @rdname julian_day
#'
eccent_earth_orbit <- function(x) {
  0.016708634 - x * (0.000042037 + 0.0000001267 * x)
}

#' @rdname julian_day
#'
sun_eq_of_ctr <- function(x, anom) {
  anom.rad <- anom / 180 * pi
  sin(anom.rad) * (1.914602 - x * (0.004817 + 0.000014 * x )) +
    sin(2 * anom.rad) * (0.019993 - 0.000101 * x) +
    sin(3 * anom.rad) * 0.000289
}

#' @rdname julian_day
#'
sun_rad_vector <- function(eccent, anom) {
  anom.rad <- anom / 180 * pi
  (1.000001018*  (1 - eccent^2))/(1 + eccent * cos(anom.rad))
}

#' @rdname julian_day
#'
sun_app_lon <- function(x, lon) {
  lon - 0.00569 - 0.00478 * sin((125.04 - 1934.136 * x) / 180 * pi)
}

#' @rdname julian_day
#'
mean_obliq_eclip <- function(x) {
  23 + (26 + ((21.448 -
                 x * (46.815 + x * (0.00059 - x * 0.001813)))) / 60) / 60
}

#' @rdname julian_day
#'
obliq_corr <- function(x, eclip) {
  eclip + 0.00256 * cos((125.04 - 1934.136 * x) / 180 * pi)
}

#' @rdname julian_day
#'
sun_rt_ascen <- function(app.lon, obliq.corr) {
  app.lon.rad <- app.lon / 180 * pi
  obliq.corr.rad <- obliq.corr / 180 * pi
  atan2(cos(obliq.corr.rad) * sin(app.lon.rad),
        cos(app.lon.rad )) / pi * 180
}

#' @rdname julian_day
#'
sun_decline <- function(app.lon, obliq.corr) {
  app.lon.rad <- app.lon / 180 * pi
  obliq.corr.rad <- obliq.corr / 180 * pi
  asin(sin(obliq.corr.rad) * sin(app.lon.rad)) /
    pi * 180
}

#' @rdname julian_day
#'
var_y <- function(obliq.corr) {
  x <- obliq.corr / 360 * pi
  tan(x)^2
}

#' @rdname julian_day
#'
eq_of_time <- function(mean.lon,
                       eccent.earth,
                       anom.mean,
                       var.y) {
  mean.lon.rad <- mean.lon / 180 * pi
  anom.mean.rad <- anom.mean / 180 * pi
  sin.anom.mean.rad <- sin(anom.mean.rad) # avoid computing it twice
  4 * ((var.y * sin(2 * mean.lon.rad) -
         2 * eccent.earth * sin.anom.mean.rad +
         4 * eccent.earth * var.y * sin.anom.mean.rad * cos(2 * mean.lon.rad) -
         0.5 * var.y^2 * sin(4 * mean.lon.rad) -
         1.25 * eccent.earth^2 * sin(2 * anom.mean.rad)) / pi * 180)
}

#' @rdname julian_day
#'
ha_sunrise <- function(lat, decline, nag = 0) {
  lat.rad <- lat / 180 * pi
  declin.rad <- decline / 180 * pi
  suppressWarnings( # NaNs can be produced for polar regions
    z <-
      acos(cos((90 + nag) / 180 * pi) /            # 90.833 in NOAA's code
             (cos(lat.rad) * cos(declin.rad)) -
             tan(lat.rad) * tan(declin.rad))  / pi * 180)
  z
}

#' @rdname julian_day
#'
solar_noon <- function(lon, eq.of.time) {
  (720 - 4 * lon - eq.of.time) / 1440
}

#' @rdname julian_day
#'
sunrise <- function(noon, ha.sunrise) {
  (noon * 1440 -
     ha.sunrise * 4) / 1440
}

#' @rdname julian_day
#'
sunset <- function(noon, ha.sunrise) {
  (noon * 1440 +
     ha.sunrise * 4) / 1440
}

#' @rdname julian_day
#'
sunlight_duration <- function(ha.sunrise, unit.out = "hours") {
  8 * ha.sunrise
}

#' @rdname julian_day
#'
#' @return datetime
solar_datetime <- function(time, lat, lon, eq.of.time) {
  time + lubridate::seconds((eq.of.time + 4 * lon) * 60)
}

#' @rdname julian_day
#'
#' @return numeric
solar_tod <- function(time, lat, lon, eq.of.time) {
  stopifnot(lubridate::is.instant(time))
  tod <- as_tod(time, unit.out = "minutes", tz = "UTC")
  (tod + eq.of.time + 4 * lon) %% 1440
}

#' @rdname julian_day
#'
hour_angle <- function(solar.time) {
  ifelse(solar.time < 0, solar.time / 4 + 180, solar.time / 4 - 180)
}

#' @rdname julian_day
#'
zenith_angle <- function(lat, hour.angle, decline) {
  lat.rad <- lat / 180 * pi
  hour.angle.rad <- hour.angle / 180 * pi
  declin.rad <- decline / 180 * pi
  (acos(sin(lat.rad) * sin(declin.rad) +
         cos(lat.rad) * cos(declin.rad) * cos(hour.angle.rad))) / pi * 180
}

#' @rdname julian_day
#'
elevation_angle <- function(lat, hour.angle, decline) {
  90 - zenith_angle(lat, hour.angle, decline)
}

#' @rdname julian_day
#'
atm_refraction_approx <- function(elevation.angle) {
# The effects of variation in atmospheric pressure and temperature are ignored
# in this implementation.
#
  elev.rad <- elevation.angle / 180 * pi
  ifelse(elevation.angle > 85,
         0,
         ifelse(elevation.angle > 5,
                58.1 / tan(elev.rad) - 0.07 / tan(elev.rad)^3 + 0.000086 / tan(elev.rad)^5,
                ifelse(elevation.angle > -0.575,
                       1735 + elevation.angle *
                         (-518.2 + elevation.angle * (103.4 + elevation.angle * (-12.79 + elevation.angle * 0.711))),
                       -20.772 / tan(elev.rad)))) / 3600
}

#' @rdname julian_day
#'
azimuth_angle <- function(lat, hour.angle, zenith.angle, decline) {
  lat.rad <- lat / 180 * pi
  zenith.angle.rad <- zenith.angle / 180 * pi
  declin.rad <- decline / 180 * pi
#  hour.angle.rad <- hour.angle / 180 * pi
  ifelse(hour.angle > 0,
         (acos(((sin(lat.rad) * cos(zenith.angle.rad)) -
                  sin(declin.rad)) / (cos(lat.rad) * sin(zenith.angle.rad))) /
            pi * 180 + 180) %% 360,
         (540 - (acos(((sin(lat.rad) * cos(zenith.angle.rad)) -
                        sin(declin.rad)) / (cos(lat.rad) * sin(zenith.angle.rad)))) /
                  pi * 180) %% 360)
}

# IF(AC2>0,MOD(DEGREES(ACOS(((SIN(RADIANS($B$3))*COS(RADIANS(AD2)))-
#                 SIN(RADIANS(T2)))/(COS(RADIANS($B$3))*SIN(RADIANS(AD2)))))+180,360),
#    MOD(540-DEGREES(ACOS(((SIN(RADIANS($B$3))*COS(RADIANS(AD2)))-
#                            SIN(RADIANS(T2)))/(COS(RADIANS($B$3))*SIN(RADIANS(AD2))))), 360))
aphalo/photobiology documentation built on April 1, 2024, 6:48 p.m.