R/datatools.R

Defines functions runauto microclimaforNMR coastalNCEP .get_sst .invls.auto .eleveffects .pointradwind .shortwave.ts dailyprecipNCEP hourlyNCEP .tme.sort siflat get_NCEP get_dem

Documented in coastalNCEP dailyprecipNCEP get_dem get_NCEP .get_sst hourlyNCEP .invls.auto microclimaforNMR .pointradwind runauto .shortwave.ts siflat

#' Gets raster of elevations
#'
#' @description Several web services provide access to raster elevations. This function
#' provides access to the Registry of Open Data on AWS to retrieve a raster object of
#' elevations.
#'
#' @param r optional raster object covering the location for which elevations are
#' required
#' @param lat the latitude (in decimal degrees) of the location for which elevations
#' are required. Not used if `r` is a raster.
#' @param long the longitude (in decimal degrees) of the location for which elevations
#' are required. Not used if `r` is a raster
#' @param resolution the resolution (in metres) of the required dem (see details).
#' @param zmin assumed sea-level height. Values below this are set to zmin.
#' @param xdims optional numeric value specifying the number of longitudinal grid cells
#' @param ydims optional numeric value specifying the number of latitudinal grid cells
#'
#' @return a raster object of elevations in metres.
#' @import raster elevatr sf
#' @export
#'
#' @details Currently, `get_dem` utilises the Amazon Web Services (AWS)
#' (https://aws.amazon.com/public-datasets/terrain/) to retrieve dems.  The spatial
#' resolution of the data available vary by location
#' (see https://mapzen.com/documentation/terrain-tiles/data-sources/ for details
#' and https://github.com/tilezen/joerd/blob/master/docs/images/footprints-preview.png
#' for a map showing the resolution of data available for different parts of the globe).
#' Global coverage is ~30m resolution and if the resolution of data requested is
#' higher than that available, elevations are derived by interpolation. If `r` is
#' unspecified, then `lat` and `long` are used to retrieve a xdims x ydims cell raster
#' centred on `lat` and `long`. Bathymetry is also returned automatically form AWS,
#' so `zmin` allows the user to specify a sea-level height. Elevations below `zmin`
#' are set at `zmin`. For use with `microclima` the returned raster has a Mercator
#' Coordinate Reference System if the latitude specified by `lat` is between 80
#' degrees S and 84 degrees N. In polar regions WGS 84 / NSIDC Sea Ice Polar
#' Stereographic projections are used. X, y and Z units are all in metres. If `r` is
#' specified, the Coordinate Reference System of `r` is used.
#'
#' @examples
#' library(raster)
#' dem50m <- get_dem(dtm100m, resolution = 50)
#' plot(dem50m) # 50m raster, OSGB projection system
#'
#' dem5m <- get_dem(lat = 49.97, long = -5.22, resolution = 5)
#' plot(dem5m) # 5m raster, Mercator projection system
get_dem <- function(r = NA, lat, long, resolution = 30, zmin = 0, xdims = 200, ydims = 200) {
  if (resolution < 30) {
    warning("Higher resolution data only available for some locations. DEM
            may be derived by interpolation")
  }
  if (class(r)[1] != "RasterLayer") {
    xy <- data.frame(x = long, y = lat)
    xy <- sf::st_as_sf(xy, coords = c('x', 'y'), crs = 4326)

    if (lat >= -80 & lat <= 84)
      xy <- sf::st_transform(xy, 3395)
    if (lat > 84)
      xy <- sf::st_transform(xy, 3413)
    if (lat < -80)
      xy <- sf::st_transform(xy, 3976)

      e <- extent(c(sf::st_coordinates(xy)[1] - floor(xdims/2) * resolution,
                    sf::st_coordinates(xy)[1] + ceiling(xdims/2) * resolution,
                    sf::st_coordinates(xy)[2] - floor(ydims/2) * resolution,
                    sf::st_coordinates(xy)[2] + ceiling(ydims/2) * resolution))

    r <- raster(e)
    res(r) <- resolution
	crs(r) <- CRS(SRS_string = st_crs(xy)$wkt)

  } else {
    lat <- latlongfromraster(r)$lat
    long <- latlongfromraster(r)$long
    res(r) <- resolution
  }
  z <- ceiling(log((cos(lat * pi/180) * 2 * pi * 6378137) / (256 * resolution), 2))
  z <- ifelse(z > 14, 14, z)
  p <- as(extent(r), 'SpatialPoints')
  p <- as.data.frame(p)
  xx <- sp::proj4string(r)
  r2 <- elevatr::get_elev_raster(p, z = z, src = "aws", prj = xx)
  r2 <- resample(r2, r)
  m2 <- getValues(r2, format = "matrix")
  m2[m2 < zmin] <- zmin
  m2[is.na(m2)] <- zmin
  r2 <- if_raster(m2, r2)
  return(r2)
}
#' Obtains NCEP data required to run microclima
#'
#' @param tme a POSIXlt object covering the duration for which data are required.
#' Hourly data are returned irrespective of the time interval of `tme`.
#' @param lat the latitude of the location for which data are required
#' @param long the longitude of the location for which data are required
#' @param reanalysis2 Logical. Should data be obtained from the Reanalysis II dataset (default) or
#' from Reanalysis I (data prior to 1979).
#'
#' @return a dataframe with the following variables: (i) obs_time:times in YYY-MM-DD HH:MM format (UTC),
#' (ii) Tk: mean temperatures at 2m (K), (iii) Tkmin:  minimum temperatures at 2m (K) (iv) Tkmax:
#' maximum temperatures at 2m (K), (v) sh: specific humidity at 2m (Kg / Kg),
#' (vi) pr: surface pressure (Pa), (vii) wu u wind vector at 10m (m/s) (viii) wv: v wind vector
#' at 10m (m/s) (ix) dlw: downward longwave radiation flux (\ifelse{html}{\out{W m<sup>-2</sup>}}{\eqn{W m^-2}}),
#' (x) ulw: upward longwave radiation flux (\ifelse{html}{\out{W m<sup>-2</sup>}}{\eqn{W m^-2}}), (xi)
#' dsw: downward shortwave radiation flux (\ifelse{html}{\out{W m<sup>-2</sup>}}{\eqn{W m^-2}}),
#' (xii) tcdc: Total cloud cover for the entire atmosphere.

#' @import RNCEP
#' @export
#'
#' @seealso [hourlyNCEP()]
#'
#' @details The NCEP-NCAR and NCEP–DOE Atmospheric Model Intercomparison Project (Kanamitso et al 2002)
#' provides estimates of a range of climate and radiation data for the period 1948 to present.
#' Data at six-hourly intervals are available at a spatial resolution of ~ 1.8 to 2.5º. The function downloads
#' the following datasets: mean, min and max air temperature at 2m,  specific humidity and 2m, surface pressure,
#' the u and v wind vectors at 10, the downward and upward longwave radiation fluxes at the surface
#' and the downward shortwave flux at the surface. Data for days either side of `tme` are downloaded to aid with
#' interpolation by [hourlyNCEP()]. Requires internet connection.
#'
#' @references Kanamitsu M, Ebisuzaki W, Woollen J, Yang SK, Hnilo JJ, Fiorino M, Potter GL (2002)
#'Ncep–doe amip-ii reanalysis (r-2). Bulletin of the American Meteorological Society 83: 1631-1644.
#'
#' @examples
#' tme <- as.POSIXlt(c(1:15) * 24 * 3600, origin = "2015-01-15", tz = 'UTC')
#' head(get_NCEP(50, -5, tme))
get_NCEP <- function(lat, long, tme, reanalysis2 = FALSE) {
  ncepget1 <- function(climvar, tme2, ll) {
    yrs <- unique(tme2$year + 1900)
    vv <- list()
    for (yr in 1:length(yrs)) {
      sel <- which(tme2$year + 1900 == yrs[yr])
      tme3 <- tme2[sel]
      mths <- unique(tme3$mon + 1)
      v <- NCEP.gather(climvar, level = 'gaussian',
                       years.minmax = c(yrs[yr],yrs[yr]),
                       months.minmax = c(min(mths):max(mths)),
                       lat.southnorth = c(ll$y,ll$y), lon.westeast = c(ll$x,ll$x),
                       reanalysis2 = reanalysis2, return.units = FALSE, status.bar = FALSE)
      if (is.null(dim(v)) == F) {
        latdif <- abs(ll$y - as.numeric(rownames(v)))
        londif <- abs(ll$x%%360 - as.numeric(colnames(v)))
        vv[[yr]] <- as.numeric(v[which.min(latdif), which.min(londif),])
      } else vv[[yr]] <- as.numeric(v)
    }
    vv <- as.vector(unlist(vv))
  }
  tme <- as.POSIXlt(tme + 0, tz = "UTC")
  ll <- data.frame(x = long, y = lat)
  tme2 <- as.POSIXct(tme)
  tme2 <- c((tme2 - 24 * 3600), tme2, (tme2 + 24 * 3600), (tme2 + 42 * 3600))
  tme2 <- as.POSIXlt(tme2)
  # These variables are forecasts valid 6 hours after the reference time.
  Tk <- ncepget1('air.2m', tme2, ll)
  Tkmin <- ncepget1('tmin.2m', tme2, ll)
  Tkmax <- ncepget1('tmax.2m', tme2, ll)
  sh <- ncepget1('shum.2m', tme2, ll)
  pr <- ncepget1('pres.sfc', tme2, ll)
  wu <- ncepget1('uwnd.10m', tme2, ll)
  wv <- ncepget1('vwnd.10m', tme2, ll)
  # These variables are 6 hour averages starting at the reference time.
  dlw <- ncepget1('dlwrf.sfc', tme2, ll)
  ulw <- ncepget1('ulwrf.sfc', tme2, ll)
  dsw <- ncepget1('dswrf.sfc', tme2, ll)
  tcdc <- ncepget1('tcdc.eatm', tme2, ll)
  # get correct data
  ogn <- paste0(tme2$year[1] + 1900, "-", tme2$mon[1] + 1, "-01 00:00")
  xx <- (c(1:length(Tk)) - 1) * 3600 * 6
  tma <- as.POSIXlt(xx, origin = ogn, tz = "UTC")
  sel <- which(tma >= tme2[1] & tma <= tme2[length(tme2)])
  dfout <- data.frame(obs_time = tma[sel], timezone = "UTC", Tk = Tk[sel],
                      Tkmin = Tkmin[sel], Tkmax = Tkmax[sel], sh = sh[sel],
                      pr = pr[sel], wu = wu[sel], wv = wv[sel], dlw = dlw[sel],
                      ulw = ulw[sel], dsw = dsw[sel], tcdc = tcdc[sel])
  rownames(dfout) <- NULL
  return(dfout)
}


#' Calculates the solar index for a flat surface
#'
#' @param localtime local time (decimal hour, 24 hour clock).
#' @param lat latitude of the location for which the solar altitude is required (decimal degrees, -ve south of the equator).
#' @param long longitude of the location for which the solar altitude is required (decimal degrees, -ve west of Greenwich meridian).
#' @param julian Julian day expressed as an integer as returned by [julday()].
#' @param merid an optional numeric value representing the longitude (decimal degrees) of the local time zone meridian (0 for GMT). Default is `round(long / 15, 0) * 15`
#' @param dst an optional numeric value representing the time difference from the timezone meridian (hours, e.g. +1 for BST if `merid` = 0).
#'
#' @return a numeric value representing the solar altitude (º).
#' @export
#'
#' @examples
#' # solar index at noon GMT on 21 June 2010, Porthleven, Cornwall
#' jd <- julday (2010, 6, 21) # Julian day
#' siflat(12, 50.08, -5.31, jd)
#'
siflat <- function(localtime, lat, long, julian, merid = round(long / 15, 0) * 15, dst = 0){
  saltitude <- solalt(localtime, lat, long, julian, merid, dst)
  alt <- saltitude * (pi/180)
  index <- cos(pi/2 - alt)
  index[index < 0] <- 0
  index
}
#' Calculates the diffuse fraction from incoming shortwave radiation (time series)
#' @export
.difprop2 <- function (rad, julian, localtime, lat, long, hourly = FALSE,
                       watts = TRUE, merid = 0, dst = 0)
{
  if (watts)
    rad <- rad * 0.0036
  lt <- c(localtime:(localtime + 5)) + 0.5
  jd <- rep(julian, 6)
  sel <- which(lt > 24)
  lt[sel] <- lt[sel] -24
  jd[sel] <- jd[sel] + 1
  sa <- solalt(lt, lat, long, jd, merid, dst)
  alt <- sa * (pi/180)
  k1 <- 0.83 - 0.56 * exp(-0.06 * sa)
  si <- cos(pi/2 - alt)
  k <- rad/(4.87 * si)
  k[is.na(k)] <- 0
  k <- ifelse(k > k1, k1, k)
  k[k < 0] <- 0
  rho <- k/k1
  sigma3a <- 0.021 + 0.397 * rho - 0.231 * rho^2 - 0.13 *
    exp(-1 * (((rho - 0.931)/0.134)^2)^0.834)
  sigma3b <- 0.12 + 0.65 * (rho - 1.04)
  sigma3 <- ifelse(rho <= 1.04, sigma3a, sigma3b)

  k2 <- 0.95 * k1
  d1 <- ifelse(sa > 1.4, 0.07 + 0.046 * (90 - sa)/(sa + 3),
               1)
  d1 <- mean(d1)
  K <- 0.5 * (1 + sin(pi * (k - 0.22)/(k1 - 0.22) - pi/2))
  d2 <- 1 - ((1 - d1) * (0.11 * sqrt(K) + 0.15 * K + 0.74 *
                           K^2))
  d3 <- (d2 * k2) * (1 - k)/(k * (1 - k2))
  alpha <- (1/sin(alt))^0.6
  kbmax <- 0.81^alpha
  kmax <- (kbmax + d2 * k2/(1 - k2))/(1 + d2 * k2/(1 - k2))
  dmax <- (d2 * k2) * (1 - kmax)/(kmax * (1 - k2))
  d4 <- 1 - kmax * (1 - dmax)/k
  d <- ifelse(k <= kmax, d3, d4)
  d <- ifelse(k <= k2, d2, d)
  d <- ifelse(k <= 0.22, 1, d)
  kX <- 0.56 - 0.32 * exp(-0.06 * sa)
  kL <- (k - 0.14)/(kX - 0.14)
  kR <- (k - kX)/0.71
  delta <- ifelse(k >= 0.14 & k < kX, -3 * kL^2 * (1 - kL) *
                    sigma3^1.3, 0)
  delta <- ifelse(k >= kX & k < (kX + 0.71), 3 * kR * (1 -
                                                         kR)^2 * sigma3^0.6, delta)
  d[sigma3 > 0.01] <- d[sigma3 > 0.01] + delta[sigma3 > 0.01]
  d[si <= 0] <- NA
  d[d > 1] <- 1
  mean(d, na.rm = T)
}
# time sort out
#' @export
.tme.sort <- function(tme, UTC = TRUE) {
  zero <- function(x) ifelse(x < 10, paste0("0", x), paste0("", x))
  pasted <- function(tme, i) {
    paste0(tme$year[i] + 1900, "-", zero(tme$mon[i] + 1), "-", zero(tme$mday[i]))
  }
  tmeseq <- function(dstart, dfinish, tz) {
    seq(as.POSIXlt(dstart, format = "%Y-%m-%d", origin = "1900-01-01", tz = tz),
        as.POSIXlt(dfinish, format = "%Y-%m-%d", origin = "1900-01-01", tz = tz),
        by = 'days')
  }
  if (UTC) {
    tme <- as.POSIXlt(tme + 0, tz = "UTC")
    tme <- tmeseq(pasted(tme, 1), pasted(tme, length(tme)), "UTC")
  } else tme <- tmeseq(pasted(tme, 1), pasted(tme, length(tme)), "")
  tme
}
#' Interpolate NCEP data for running microclima to hourly
#'
#' @description `hourlyNCEP` optionally downloads the required NCEP climate and radiation forcing data
#' required for running microclima and interpolates 4x daily data to hourly.
#'
#' @param ncepdata an optional  data frame of climate variables as returned by [get_NCEP()].
#' @param tme a POSIXlt object covering the duration for which data are required. Ignored if `ncepdata``
#' provided.
#' Hourly data are returned irrespective of the time interval of `tme`.Ignored if `ncepdata` provided.
#' @param lat the latitude of the location for which data are required. Ignored if `ncepdata` provided.
#' @param long the longitude of the location for which data are required. Ignored if `ncepdata` provided.
#' @param reanalysis2 Logical. Should data be obtained from the Reanalysis II dataset (default) or
#' from Reanalysis I (data prior to 1979). Ignored if `ncepdata` provided.
#'
#' @return a dataframe with the following variables:
#' \describe{
#'   \item{obs_time}{POSIXlt object of times in UTC}
#'   \item{temperature}{emperatures at 2m (ºC)}
#'   \item{humidity}{specific humidity at 2m (Kg / Kg)}
#'   \item{pressure}{surface pressure (Pa)}
#'   \item{windspeed}{wind speed at 2m (metres per second}
#'   \item{winddir}{wind direction (degrees from N)}
#'   \item{emissivity}{emissivity of the atmosphere (0 - 1, downlong / uplong)}
#'   \item{netlong}{Net longwave radiation (\ifelse{html}{\out{MJ m<sup>-2</sup> hr<sup>-1</sup>}}{\eqn{MJ m^-2 hr^{-1}}})}
#'   \item{uplong}{Upward longwave radiation (\ifelse{html}{\out{MJ m<sup>-2</sup> hr<sup>-1</sup>}}{\eqn{MJ m^-2 hr^{-1}}})}
#'   \item{downlong}{Downward longwave radiation (\ifelse{html}{\out{MJ m<sup>-2</sup> hr<sup>-1</sup>}}{\eqn{MJ m^-2 hr^{-1}}})}
#'   \item{rad_dni}{Direct radiation normal to the solar beam (\ifelse{html}{\out{MJ m<sup>-2</sup> hr<sup>-1</sup>}}{\eqn{MJ m^-2 hr^{-1}}})}
#'   \item{rad_dif}{Diffuse radiation (\ifelse{html}{\out{MJ m<sup>-2</sup> hr<sup>-1</sup>}}{\eqn{MJ m^-2 hr^{-1}}})}
#'   \item{szenith}{the zenith angle (degrees)}
#'   \item{cloudcover}{cloud cover (Percentage)}
#' }
#' @export
#' @import zoo
#'
#' @seealso [get_NCEP()]
#' @details If `ncepdata` is not provided, then [get_NCEP()] is called and data are downloaded from NCEP Atmospheric
#' Model Intercomparison Project (Kanamitso et al 2002). Six-hourly data are interpolated as follows.
#' Pressure, humidity and the u and v wind vectors are converted to hourly using spline interpolation. Wind speeed and direction and then
#' calculated and adjusted to give values at 1 m using [windheight()]. The diffuse radiation
#' proportion is calculated using [difprop()], and hourly obtained by adusting for the direction
#' of the solar beam and airmass thickness using functions [siflat()] and [airmasscoef()].
#' Hourly temperature are derived using [hourlytemp()] and longwave radiation by splining emissivity
#' values.
#' @examples
#' tme <- as.POSIXlt(c(0:30) * 24 * 3600, origin ="2015-01-15 00:00", tz = "UTC")
#' # NB takes a while to download data
#' hdata<- hourlyNCEP(NA, 50, -5, tme)
#' head(hdata)
#' plot(temperature ~ as.POSIXct(obs_time), data = hdata, type = "l", xlab = "Month")
#' plot(rad_dif ~ as.POSIXct(obs_time), data = hdata, type = "l", xlab = "Month")
hourlyNCEP <- function(ncepdata = NA, lat, long, tme, reanalysis2 = FALSE) {
  bound <- function(x, mn = 0, mx = 1) {
    x[x > mx] <- mx
    x[x < mn] <- mn
    x
  }
  tz <- format(tme, format="%Z")
  if (tz[1] != "UTC" & tz[1]!= "GMT") {
    warning(paste("NCEP data use UTC/GMT. Timezone converted from", tz[1], "to UTC/GMT"))
  }
  tme <- .tme.sort(tme)
  tmeout <- tme
  tme <- c(tme, tme[length(tme)] + 24 * 3600)
  if (class(ncepdata)[1] != "logical") {
    tme <- as.POSIXlt(ncepdata$obs_time)
    tme <- tme[5:(length(tme) - 4)]
  }
  long <- ifelse(long > 180, long - 360, long)
  int <- as.numeric(tme[2]) - as.numeric(tme[1])
  lgth <- (length(tme) * int) / (24 * 3600)
  tme2 <- as.POSIXlt(c(0:(lgth - 1)) * 3600 * 24, origin = min(tme), tz = 'UTC')
  if (class(ncepdata)[1] == "logical") {
    ncepdata <- get_NCEP(lat, long, tme2, reanalysis2)
  }
  tme6 <- as.POSIXlt(ncepdata$obs_time)
  tme6 <- as.POSIXlt(tme6 + 0, tz = "UTC")
  n <- (length(tme6) - 1) * 6 + 1
  h_pr <- spline(tme6, ncepdata$pr, n = n)$y
  h_sh <- spline(tme6, ncepdata$sh, n = n)$y
  h_cc <- bound(spline(tme6, ncepdata$tcdc, n = n)$y, mx = 100)
  tmo  <- spline(tme6, ncepdata$sh, n = n)$x
  tmo <- as.POSIXlt(tmo, origin = "1970-01-01 00:00",
                    tz = "UTC")
  tmo2 <- as.POSIXlt(seq(0,(length(tme6) / 4), by = 1/24) * 3600 * 24, origin = min(tme6), tz = 'UTC')
  csr <- clearskyrad(tmo2, lat, long, merid = 0, dst = 0)
  csr[is.na(csr)] <- 0
  csr_m <- 0
  for (i in 1:(length(tme6))) {
    st <- (i - 1) * 6 + 1
    ed <- st + 6
    csr_m[i] <- mean(csr[st:ed], na.rm = T)
  }
  od <- bound(ncepdata$dsw / csr_m)
  if (is.na(od)[1]) od[1] <- mean(od, na.rm = T)
  if (is.na(od)[length(od)]) od[length(od)] <- mean(od, na.rm = T)
  h_od <- bound(spline(tme6, od, n = n)$y)
  tmorad <- as.POSIXlt(tmo + 3600 * 3)
  csrh <- clearskyrad(tmorad, lat, long, merid = 0, dst = 0)
  rad_h <- h_od * csrh
  rad_h[is.na(rad_h)] <- 0
  jd <- julday(tmorad$year + 1900, tmorad$mon + 1, tmorad$mday)
  dp <- 0
  for (i in 1:length(jd)) {
    dp[i] <- .difprop2(rad_h[i], jd[i], tmorad$hour[i], lat, long)
  }
  dp[is.na(dp)] <- 1
  si <- siflat(tmorad$hour, lat, long, jd, merid = 0)
  h_dif <- dp *  rad_h
  h_dni <- ((1 - dp) * rad_h) / si
  h_dni[is.na(h_dni)] <- 0
  h_dni[h_dni > 1353] <- 1353
  ###
  em <- ncepdata$dlw / ncepdata$ulw
  em_h <- spline(tme6, em, n = n)$y
  t6sel <- c(4:(length(tme6)-5))
  thsel <-c(22:(length(tmorad)-22))
  thsel2 <-c(19:(length(tmo) - 25))
  tcmn <- ncepdata$Tkmin[t6sel] - 273.15
  tcmx <- ncepdata$Tkmax[t6sel] - 273.15
  tcmn <-t(matrix(tcmn, nrow = 4))
  tcmx <-t(matrix(tcmx, nrow = 4))
  tmin <- apply(tcmn, 1, min)
  tmax <- apply(tcmx, 1, max)
  jd <- julday(tme2$year + 1900, tme2$mon + 1, tme2$mday)
  h_dni <- h_dni * 0.0036
  h_dif <- h_dif * 0.0036
  h_tc<-suppressWarnings(hourlytemp(julian = jd, em = em_h[thsel], dni = h_dni[thsel],
                                    dif = h_dif[thsel], mintemp = tmin, maxtemp = tmax,
                                    lat = lat, long = long, merid = 0))
  hlwu <- 2.043e-10 * (h_tc + 273.15)^4
  hlwd <- em_h[thsel] *  hlwu
  h_nlw <- hlwu - hlwd
  h_uw <- spline(tme6, ncepdata$wu, n = n)$y
  h_vw <- spline(tme6, ncepdata$wv, n = n)$y
  h_ws <- sqrt(h_uw^2 + h_vw^2)
  h_ws <- windheight(h_ws, 10, 2)
  h_wd <- atan2(h_uw, h_vw) * 180/pi + 180
  h_wd <- h_wd%%360
  jd <- julday(tmorad$year + 1900, tmorad$mon + 1, tmorad$mday)
  szenith <- 90 - solalt(tmorad$hour, lat, long, jd, merid = 0)
  hourlyout <- data.frame(obs_time = tmorad[thsel], temperature = h_tc,
                          humidity = h_sh[thsel2], pressure = h_pr[thsel2],
                          windspeed = h_ws[thsel2], winddir = h_wd[thsel2],
                          emissivity = em_h[thsel2], cloudcover = h_cc[thsel],
                          netlong = h_nlw, uplong = hlwu, downlong = hlwd,
                          rad_dni = h_dni[thsel],
                          rad_dif = h_dif[thsel],
                          szenith = szenith[thsel], timezone = "UTC")
  tmetest <- as.POSIXlt(hourlyout$obs_time)
  tmeout <- as.POSIXlt(tmeout + 0, tz = "UTC")
  sel <- which(tmetest >= min(tmeout) & tmetest <= max(tmeout + 23 * 3600))
  return(hourlyout[sel,])
}
#' Obtain daily precipitation from NCEP
#'
#' @param tme a POSIXlt object covering the duration for which data are required.
#' Intervals should be daily.
#' Hourly data are returned irrespective of the time interval of `tme`.
#' @param lat the latitude of the location for which data are required
#' @param long the longitude of the location for which data are required
#' @param reanalysis2 Logical. Should data be obtained from the Reanalysis II dataset (default) or
#' from Reanalysis I (data prior to 1979).
#'
#' @return a vector of daily precipitations (mm / day) for the period covered by tme
#' @export
#' @import RNCEP
#'
#' @examples
#' tme <- as.POSIXlt(c(1:15) * 24 * 3600, origin = "2015-01-15", tz = 'UTC')
#' dailyprecipNCEP(50, -5, tme)
dailyprecipNCEP <- function(lat, long, tme, reanalysis2 = FALSE) {
  tmeout <- tme
  tme <- .tme.sort(tme)
  long <- ifelse(long > 180, long - 360, long)
  if (length(tme) > 1) {
    int <- as.numeric(tme[2]) - as.numeric(tme[1])
    lgth <- (length(tme) * int) / (24 * 3600)
    tme <- as.POSIXlt(c(0:(lgth - 1)) * 3600 * 24, origin = min(tme), tz = 'UTC')
  } else tme <- as.POSIXlt(0, origin = min(tme), tz = 'UTC')
  yrs <- unique(tme$year + 1900)
  apre <- list()
  for (y in 1:length(yrs)) {
    sely <- which(tme$year + 1900 == yrs[y])
    mths <- unique(tme$mon[sely] + 1)
    ll <- data.frame(x = long, y = lat)
    pre <- NCEP.gather('prate.sfc', level = 'gaussian',
                       years.minmax = c(min(yrs[y]),max(yrs[y])),
                       months.minmax = c(min(mths):max(mths)),
                       lat.southnorth = c(ll$y,ll$y), lon.westeast = c(ll$x,ll$x),
                       return.units = FALSE, status.bar = FALSE, reanalysis2 = reanalysis2)
    if (is.null(dim(pre)) == F) {
      latdif <- abs(ll$y - as.numeric(rownames(pre)))
      londif <- abs(ll$x%%360 - as.numeric(colnames(pre)))
      pre <- pre[which.min(latdif), which.min(londif),]
    }
    apre[[y]] <-as.numeric(pre)
  }
  pre <- as.vector(unlist(apre))
  pre <- pre * 6 * 3600
  tma <- 0
  dm <- c(31,28,31,30,31,30,31,31,30,31,30,31) * 4 - 1
  for (yr in min(yrs):max(yrs)) {
    dm[2] <- ifelse(yr%%4 == 0, 115, 111)
    sely <- which(tme$year + 1900 == yr)
    mths <- unique(tme$mon[sely] + 1)
    for (mth in min(mths):max(mths)) {
      tmym <- as.POSIXct(c(0:dm[mth]) * 3600 * 6,
                         origin = paste0(yr,"-",mth,"-01 00:00"),
                         tz = 'UTC')
      tma <- c(tma, tmym)
    }
  }
  tma <- as.POSIXlt(tma[-1], origin = '1970-01-01', tz = 'UTC')
  tmeout <- as.POSIXlt(tmeout + 0, tz = "UTC")
  sel <- which(tma >= min(tmeout) & tma < max(tmeout))
  pre <- pre[sel]
  dpre <- t(matrix(pre, nrow = 4))
  dpre <- apply(dpre, 1, sum)
  return(dpre)
}
#' get shortwave radiation (time series)
#' @export
.shortwave.ts <- function(dni, dif, jd, localtime, lat, long, slope, aspect,
                          ha = 0, svv = 1, x = 1, l = 0, albr = 0,
                          merid = round(long / 15, 0) * 15, dst = 0, difani = TRUE) {
  sa <- solalt(localtime, lat, long, jd, merid)
  alt <- sa * (pi / 180)
  zen <- pi / 2 - alt
  saz <- solazi(localtime, lat, long, jd, merid)
  azi <- saz * (pi / 180)
  sl <- slope * (pi / 180)
  aspe <- aspect * (pi / 180)
  si <- cos(zen) * cos(sl) + sin(zen) * sin(sl) * cos(azi - aspe)
  si[sa < ha] <- 0
  si[si < 0] <- 0
  dirr <- si * dni
  if (difani) {
    k <- dni / 4.87
  } else k <- 0
  isor <- 0.5 * dif * (1 + cos(sl)) * (1 - k)
  cisr <- k * dif * si
  sdi <- (slope + ha) * (pi / 180)
  refr <- 0.5 * albr * (1 - cos(sdi)) * dif
  fd <- dirr + cisr  # direct
  fdf <- isor + refr  # diffuse
  kk <- ((x ^ 2 + 1 / (tan(sa * (pi / 180)) ^ 2)) ^ 0.5) /
    (x + 1.774 * (x + 1.182) ^ (-0.733))
  trd <- exp(-kk * l)
  fr <- as.vector(canopy(array(l, dim = c(1,1))))
  trf <- (1 - fr)
  fgd <- fd * trd
  fged <- fdf * trf * svv
  fgc <- fgd + fged
  cfc <- ((1 - trd) * fd + fr * fdf) / (fd + fdf)
  cfc[is.na(cfc)] <- ((1 - trd[is.na(cfc)]) * 0.5 + fr * 0.5) / (0.5 + 0.5)
  return(xxx=list(swrad = fgc, canopyfact = cfc))
}
#' get radiation and wind for use with NicheMapR
#' @import sf
#' @export
.pointradwind <- function(hourlydata, dem, lat, long, l, x, albr = 0.15, zmin = 0,
                          slope = NA, aspect = NA, horizon = NA, svf = NA, difani = TRUE) {
  m <- is_raster(dem)
  m[is.na(m)] <- zmin
  m[m < zmin] <- zmin
  dem <- if_raster(m, dem)
  xy <- data.frame(x = long, y = lat)
  xy <- sf::st_as_sf(xy, coords = c("x","y"), crs = 4326)
  xy <- st_transform(xy, st_crs(dem)$wkt)
  reso <- res(dem)[1]
  wsc36 <- 0
  wsc36atground <- 0
  for (i in 0:35) {
    wscr <- windcoef(dem, i*10, hgt = 2, reso = reso)
    wscr2 <- windcoef(dem, i*10, hgt = 0, reso = reso)
    wsc36[i + 1] <- raster::extract(wscr, xy)
    wsc36atground[i + 1] <- raster::extract(wscr2, xy)
  }
  wshelt <- 0
  wsheltatground <- 0
  hourlydata$winddir <- hourlydata$winddir%%360
  for (i in 1:length(hourlydata$winddir)) {
    daz <- round(hourlydata$winddir[i] / 10, 0) + 1
    daz[daz > 36] <- daz[daz > 36] - 36
    wshelt[i] <- wsc36[daz]
    wsheltatground[i] <- wsc36atground[daz]
  }
  if (class(slope)[1] == "logical") {
    slope <- terrain(dem, unit = 'degrees')
    slope <- raster::extract(slope, xy)
  }
  if (class(aspect)[1] == "logical") {
    aspect <- terrain(dem, opt = 'aspect', unit = 'degrees')
    aspect <- raster::extract(aspect, xy)
  }
  fr <- canopy(array(l, dim = dim(dem)[1:2]))
  if (class(svf)[1] == "logical") {
    svf <- skyviewveg(dem, array(l, dim = dim(dem)[1:2]),
                      array(x, dim = dim(dem)[1:2]), reso = reso)
    svf <- raster::extract(svf, xy)
  }
  if (class(horizon)[1] == "logical") {
    ha36 <- 0
    for (i in 0:35) {
      har <- horizonangle(dem, i*10, reso)
      ha36[i + 1] <- atan(raster::extract(har, xy)) * (180/pi)
    }
  } else ha36 <- rep(horizon, 36)
  tme <- as.POSIXlt(hourlydata$obs_time)
  tme <- as.POSIXlt(tme + 0, tz = 'UTC')
  ha <- 0
  jd <- julday(tme$year + 1900, tme$mon + 1, tme$mday)
  for (i in 1:length(tme)) {
    saz <- solazi(tme$hour[i], lat, long, jd[i], merid = 0)
    saz <- round(saz / 10, 0) + 1
    saz <- ifelse(saz > 36, 1, saz)
    ha[i] <- ha36[saz]
  }
  sw <-.shortwave.ts(hourlydata$rad_dni, hourlydata$rad_dif, jd, tme$hour,
                     lat, long, slope, aspect, ha, svf, x, l, albr, merid = 0,
                     difani = difani)
  windsp <- hourlydata$windspeed
  hourlyrad <- data.frame(swrad = sw$swrad, skyviewfact = svf, canopyfact = sw$canopyfact,
                          whselt = wsheltatground, windspeed = wshelt *  windsp,
                          slope = slope, aspect = aspect)
  return(hourlyrad)
}
#' Cold air drainage direct from emissivity
#' @export
.cadconditions2 <- function (em, wind, startjul, lat, long, starttime = 0, hourint = 1,
                             windthresh = 4.5, emthresh = 0.725, con = TRUE)
{
  jd <- floor(c(1:length(em)) * hourint/24 - hourint/24 + startjul +
                starttime/24)

  st <- suntimes(jd, lat, long, merid = 0)
  hrs <- (c(1:length(em)) * hourint - hourint + starttime)%%24
  dn <- ifelse(hrs > (st$sunrise + 3) & hrs < st$sunset, 1, 0)  # sunrise before sunset
  dn2 <- ifelse(hrs > (st$sunrise + 3) | hrs < st$sunset, 1, 0)  # sunrise after sunset
  sel <- which(st$sunrise > st$sunset)
  dn[sel] <- dn2[sel]
  cad <- ifelse(dn < 1 & wind < windthresh & em < emthresh,
                1, 0)
  if (hourint == 1 & con) {
    cad[2] <- ifelse(cad[1] & cad[2] == 1, 1, 0)
    cad <- c(cad[1:2], cad[3:length(cad)] * cad[2:(length(cad) -
                                                     1)] * cad[1:(length(cad) - 2)])
  }
  cad
}
# Calculates elevation effects
#' @import sf
#' @export
.eleveffects <- function(hourlydata, dem, lat, long, windthresh = 4.5,
                         emthresh = 0.78,  weather.elev, cad.effects) {
  xy <- data.frame(x = long, y = lat)
  if (weather.elev == 'ncep') {
    elevncep <- raster::extract(demworld, xy)
  } else if (weather.elev == 'era5') {
    lar <- round(lat*4,0)/4
    lor <- round(long*4,0)/4
    e <- extent(lor-0.875,lor+0.875,lar-0.875,lar+0.875)
    e5d <- get_dem(r = NA, lat, long, resolution = 10000, xdims = 10, ydims = 10)
    e5d <- projectRaster(e5d, crs = sf::st_crs(4326)$wkt)
    rte <- raster(e)
    res(rte) <- 0.25
    e5d <- resample(e5d,rte)
    elevncep <- raster:: extract(e5d, xy)
  } else elevncep <- as.numeric(weather.elev)
  if (is.na(elevncep)) {
    warnings("elevation of input weather data NA. Setting to zero")
    elevncep <- 0
  }
  xy <- sf::st_as_sf(xy, coords = c("x", "y"), crs = 4326)
  xy <- sf::st_transform(xy, sf::st_crs(dem)$wkt)
  elev <- raster::extract(dem, xy)
  if (is.na(elev)) elev <- 0
  lr <- lapserate(hourlydata$temperature, hourlydata$humidity, hourlydata$pressure)
  elevt <- lr * (elev - elevncep) + hourlydata$temperature
  tme <- as.POSIXlt(hourlydata$obs_time)
  ## cad effects
  if (cad.effects) {
    jds <- julday(tme$year[1] + 1900, tme$mday[1] + 1, tme$mday[1])
    cad <- .cadconditions2(hourlydata$emissivity, hourlydata$windspeed,
                           jds, lat, long, starttime = tme$hour[1], hourint = 1,
                           windthresh = windthresh, emthresh = emthresh)
    pxls <- dim(dem)[1] * dim(dem)[2]
    if (pxls > 300 * 300) {
      basins <- basindelin_big(dem)
    } else {
      basins <- basindelin(dem)
    }
    basins <- basinmerge(dem, basins, 2)
    basins <- basinsort(dem, basins)
    fa <- flowacc(dem)
    pfa <- is_raster(fa) * 0
    td <- is_raster(fa) * 0
    bm <- is_raster(basins)
    dm <- is_raster(dem)
    for (b in 1:max(bm, na.rm = TRUE)) {
      sel <- which(bm == b)
      fao <- log(is_raster(fa)[sel])
      pfa[sel] <- fao/max(fao, na.rm = TRUE)
      ed <- max(dm[sel], na.rm = TRUE) - dm[sel]
      td[sel] <- ed
    }
    cdif <- pfa * td
    cdif <- if_raster(cdif, dem)
    cdif <- extract(cdif, xy)
    if (is.na(cdif)) cdif <- 0
    cadt <- cdif * lr * cad
  } else {
    basins <- dem*0+1
    fa <- basins
    cadt <- rep(0,length(tme))
  }
  tout <- data.frame(tref = hourlydata$temperature,
                     elev = elev, elevncep = elevncep,
                     telev = elevt, tcad = cadt, lapserate = lr)
  return(list(tout = tout, basins = basins, flowacc = fa))
}
#' Calculates coastal exposure automatically
#' @import sf
#' @export
.invls.auto <- function(r, steps = 8, use.raster = T, zmin = 0, plot.progress = TRUE, tidyr = FALSE) {
  tidydems <- function(rfine, rc) {
    rfine[is.na(rfine)] <- zmin
    rc <- trim(rc)
    aggf <- floor(mean(res(rc)[1:2]) / mean(res(rfine)))
    if (aggf > 1) {
      rfine2 <- suppressWarnings(aggregate(rfine, aggf, max))
    } else rfine2 <- rfine
    rfine2 <- suppressWarnings(resample(rfine2, rc, method = 'ngb'))
    rfine2 <- crop(rfine2, extent(rfine))
    if (dim(rfine2)[1] * dim(rfine2)[2] == 1) {
      xx <- mean(is_raster(rfine), na.rm = T)
      rfine2 <- if_raster(as.matrix(xx), rfine2)
    }
    a <- array(-999, dim = dim(rfine2)[1:2])
    rc2 <- raster(a)
    extent(rc2) <- extent(rfine2)
    rc2 <- mosaic(rc, rc2, fun = min)
    rc2 <- mosaic(rc2, rfine2, fun = max)
    rc2[rc2 == zmin] <- NA
    rc2
  }
  adjust.lsr <- function(lsr, rs) {
    m <- is_raster(lsr)
    m[m < 0] <- 0
    s <- c(0, (8:10000) / 8) ^ 2 * 30
    sl <- which(s <= rs)
    mval <- length(sl) / 1453
    m2 <- m * (1 - mval) + mval
    m2
  }
  ll <- latlongfromraster(r)
  xs <- seq(0, by = 1.875, length.out = 192)
  ys <- seq(-88.542, by = 1.904129, length.out = 94)
  xc <- xs[which.min(abs(xs - ll$long%%360))]
  yc <- ys[which.min(abs(ys - ll$lat))]
  rll <- raster(matrix(0, nrow = 3, ncol = 3))
  extent(rll) <- extent(xc -  2.8125, xc +  2.8125,
                        yc - 2.856193, yc + 2.856193)
  crs(rll) <- sf::st_crs(4326)$wkt
  ress <- c(30, 90, 500, 1000, 10000)
  ress <- ress[ress > mean(res(r))]
  ress <- c(mean(res(r)), ress)
  ress <- rev(ress)
  # Create a list of dems
  cat("Downloading land sea data \n")
  dem.list <- list()
  rmet <- projectRaster(rll, crs = sf::st_crs(r)$wkt)
  dem <- get_dem(rmet, resolution = ress[1], zmin = zmin)
  dem.list[[1]] <- projectRaster(dem, crs = sf::st_crs(r)$wkt)
  dc <- ceiling(max(dim(r)) / 2)
  rres <- mean(res(r))
  for (i in 2:(length(ress)-1)) {
    d <- 50
    tst <- rres / ress[i] * dc * 2
    if (ress[i] <= mean(res(r)[1:2]))
      d <- dc
    if (tst > 50) d <- tst
    e <- extent(r)
    xy <- c((e@xmin + e@xmax) / 2, (e@ymin + e@ymax) / 2)
    e2 <- extent(c(xy[1] - d * ress[i], xy[1] + d * ress[i],
                   xy[2] - d * ress[i], xy[2] + d * ress[i]))
    rr <- raster()
    extent(rr) <- e2
    crs(rr) <- sf::st_crs(r)$wkt
    dem <- suppressWarnings(get_dem(rr, resolution = ress[i], zmin = zmin))
    dem.list[[i]] <- projectRaster(dem, crs = st_crs(r)$wkt)
  }
  if (tidyr & mean(res(r)) <= 30)
    warning("raster tidying ignored as resolution <= 30")
  if (tidyr & mean(res(r)) > 30) {
    rx <- raster(extent(r))
    res(rx) <- 30
    crs(rx) <- sf::st_crs(r)$wkt
    rfine <- get_dem(rx, resolution = 30, zmin = zmin)
    r <- tidydems(rfine, r)
  }
  dem.list[[i + 1]] <- r
  for (j in 1:i) dem.list[[j]] <- tidydems(r, dem.list[[j]])
  cat("Computing coastal exposure \n")
  lsa.array <- array(NA, dim = c(dim(r)[1:2], steps))
  for (dct in 0:(steps - 1)) {
    direction <- dct * (360 / steps)
    cat(paste("Direction:", direction, "degrees"), "\n")
    lsa.list <- list()
    for (i in 1:length(ress)) {
      dem <- dem.list[[i]]
      m <- is_raster(dem)
      m[m == zmin] <- NA
      dem <- if_raster(m, dem)
      lsa <- invls(dem, extent(r), direction)
      lsa[is.na(lsa)] <- 0
      lsm <- is_raster(lsa)
      if (max(lsm) > min(lsm)) {
        if (min(dim(lsm)) < 2) {
          r2 <- aggregate(r, 100)
          xx<- resample(lsa, r2, method = "ngb")
          xx <- resample(xx, r)
          xx <- mask(xx, r)
        } else {
          xx <- resample(lsa, r)
          x <- mask(xx, r)
        }
        mx <- mean(is_raster(xx), na.rm = T)
        if  (is.na(mx)) xx <- xx * 0 + mean(lsm, na.rm = T)
        lsa.list[[i]] <- xx
      } else  {
        lsa.list[[i]] <- if_raster(array(mean(lsm, na.rm = T),
                                         dim = dim(r)[1:2]), r)
      }
    }
    # find min vals
    for (i in 1:length(ress))
      lsa.list[[i]] <- adjust.lsr(lsa.list[[i]], ress[i])
    lsa <- is_raster(lsa.list[[1]])
    for (i in 2:length(ress))
      lsa <- pmin(lsa, is_raster(lsa.list[[i]]))
    lsa <- if_raster(lsa, r)
    if (use.raster) lsa <- mask(lsa, r)
    if (plot.progress) plot(lsa, main = paste("Direction:",direction))
    lsa.array[,,dct + 1] <- is_raster(lsa)
  }
  # Compute land-sea ratio of ncep grid cell
  cat("Computing mean coastal exposure of ncep grid cell \n")
  cncep <-0
  eone <- extent(xc - 1.875 / 2, xc + 1.875 / 2,
                 yc - 1.904129 / 2, yc + 1.904129 / 2)
  rllo <- crop(rll, eone)
  rone <- projectRaster(rllo, crs = sf::st_crs(r)$wkt)
  rone[is.na(rone)] <- zmin
  dem <- dem.list[[1]]
  m <- is_raster(dem)
  m[m == zmin] <- NA
  dem <- if_raster(m, dem)
  e <- extent(r)
  xy <- data.frame(x = (e@xmin + e@xmax) / 2,
                   y = (e@ymin + e@ymax) / 2)
  xy <- sf::st_as_sf(xy, coords = c("x","y"), crs = st_crs(r)$wkt)
  for (dct in 0:(steps - 1)) {
    direction <- dct * (360 / steps)
    lsa <- invls(dem, extent(rone), direction)
    f1 <- extract(lsa, xy)
    if (is.na(f1)) f1 <- mean(is_raster(lsa), na.rm = T)
    mncep <- mean(is_raster(lsa), na.rm = T) / f1
    cncep[dct + 1] <- mncep
  }
  cat("Adjusting coastal exposure by ncep means \n")
  for (i in 1:steps) lsa.array[,,i] <- lsa.array[,,i] * cncep[i]
  return(lsa.array)
}
#' Downloads sea-surface temperature data
#' @export
#' @import rnoaa ncdf4
.get_sst <- function(lat, long, tme) {
  sel1 <- which(tme$year == min(tme$year))
  sel2 <- which(tme$year == max(tme$year))
  dms <- c(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
  yr <-  unique(tme$year) + 1900
  if (length(yr) > 1) stop("SST intepolation only works on data from one year. Select dates in yearly blocks")
  if(yr%%4 == 0) dms[2] <- 29
  amonth <- min(tme$mon[sel1])
  amonth <- ifelse(amonth == 0, 12, amonth)
  pmonth <- max(tme$mon[sel1]) + 2
  pmonth <- ifelse(pmonth > 12, pmonth -12, pmonth)
  dm <- dms[max(tme$mon[sel2]) + 1]
  omn <- paste0(min(tme$year) + 1900, "-", min(tme$mon[sel1]) + 1, "-01 00:00:00")
  omx <- paste0(max(tme$year) + 1900, "-", max(tme$mon[sel2]) + 1, "-", dm, " 23:59:59")
  tmn <- as.POSIXlt(0, origin = omn, tz = "GMT") - dms[amonth] / 2 * 24 * 3600
  tmx <- as.POSIXlt(1, origin = omx, tz = "GMT") + dms[pmonth] / 2 * 24 * 3600
  tme2 <-  as.POSIXlt(seq(tmn, tmx, by = 'hour'))
  yrs <- unique(tme2$year) + 1900
  tmes <- as.POSIXlt(0, origin = "1900-01-01 00:00", tz = "GMT")
  sstdf <- data.frame(obs_time = tmes, sst = -99)
  for (yr in 1:length(yrs)) {
    dms[2] <- 28
    if (yrs[yr]%%4 == 0) dms[2] <- 29
    if (yrs[yr]%%100 == 0 & yrs[yr]%%400 != 0) dms[2] <- 28
    sel <- which(tme2$year + 1900 == yrs[yr])
    mths <- unique(tme2$mon[sel]) + 1
    for (mt in 1:length(mths)) {
      nc <- ersst(yrs[yr], mths[mt])
      sst <- ncvar_get(nc, 'sst')
      sst <- t(sst)
      sst <- apply(sst, 2, rev)
      sst <- na.approx(sst, na.rm = F)
      sstr <- raster(sst)
      extent(sstr) <- extent(-1, 359, -89, 89)
      long2 <- long%%360
      long2 <- ifelse(long2 > 359, long2 - 360, long2)
      s <- extract(sstr, data.frame(long2, lat))
      orn <- paste0(yrs[yr], "-", mths[mt], "-01 00:00")
      tmem <- as.POSIXlt(0 + 3600 * 24 * dms[mths[mt]] / 2, origin = orn, tz = "GMT")
      sstdo <- data.frame(obs_time = tmem, sst = s)
      sstdf <- rbind(sstdf, sstdo)
    }
  }
  sstdf <- sstdf[-1,]
  ssth <- spline(sstdf$sst~as.POSIXct(sstdf$obs_time), n = length(tme2))$y
  tmeh <- spline(sstdf$sst~as.POSIXct(sstdf$obs_time), n = length(tme2))$x
  tmeh <- as.POSIXlt(tmeh, origin = "1970-01-01 00:00", tz = "GMT")
  tme <- as.POSIXlt(tme + 0, tz = "GMT")
  sel <- which(tmeh >=min(tme) & tmeh <= max(tme))
  return(ssth[sel])
}
#' Calculates coastal effects from NCEP data
#'
#' @param landsea a raster object with NAs (representing sea) or any non-NA value and a projection system defined.
#' @param ncephourly a dataframe of hourly climate data as returned by [hourlyNCEP()].
#' @param steps an optional integer. Coastal effects are calculated in specified directions upwind. Steps defines the total number of directions used. If the default 8 is specified, coastal effects are calculated at 45º intervals.
#' @param use.raster an optional logical value indicating whether to mask the output values by `landsea`.
#' @param zmin optional assumed sea-level height. Values below this are set to zmin
#' @param plot.progress logical value indicating whether to produce plots to track progress.
#' @param tidyr logical value indicating whether to download 30m resolution digital elevation data
#' and use these data to improve the assignment of land and sea pixels in `landsea`.
#' @details coastalNCEP downloads digital elevation and varying resolutions to calculate
#' coastal effects by applying a variant of [invls()] over a greater extent then that specified by landsea, but the resulting outputs are
#' cropped to the same extent as landsea. Land temperatyres as a function of coastal exposure
#' and sea-surface temperature data are then calculated. Sea surface temperature data are
#' automatically downloaded from the NOAA.
#'
#' @return a three-dimension array of hourly temperature values for each pixel of `landsea`
#' @export
#' @import raster sp zoo rnoaa ncdf4 sf
#' @examples
#' library(raster)
#' # Download NCEP data
#' ll <- latlongfromraster(dtm100m)
#' tme <-as.POSIXlt(c(0:31) * 3600 * 24, origin = "2015-03-15", tz = "GMT")
#' ncephourly<-hourlyNCEP(NA, ll$lat, ll$long, tme)
#' aout <- coastalNCEP(dtm100m, ncephourly)
#' # Calculate mean temperature and convert to raster
#' mtemp <- if_raster(apply(aout, c(1, 2), mean), dtm100m)
#' plot(mtemp, main = "Mean temperature")
coastalNCEP <- function(landsea, ncephourly, steps = 8, use.raster = T, zmin = 0, plot.progress = T, tidyr = F) {
  bound <- function(x, mn = 0.1, mx = 1) {
    x[x < mn] <- mn
    x[x > mx] <- mx
    x
  }
  lsr1 <- .invls.auto(landsea, steps, use.raster, zmin, plot.progress, tidyr)
  lsr2 <- lsr1
  for (i in 1:steps) {
    mn <- i - 1
    mn <- ifelse(mn == 0, 8, mn)
    mx <- i + 1
    mx <- ifelse(mx >8, mx - 8, mx)
    lsr2[,,i] <- 0.25 * lsr1[,,mn]  + 0.5 * lsr1[,,i] + 0.25 * lsr1[,,mx]
  }
  lsrm <- apply(lsr1, c(1,2), mean)
  ll <- latlongfromraster(landsea)
  tme <- as.POSIXlt(ncephourly$obs_time)
  cat("Downloading sea-surface temperature data \n")
  sst <- .get_sst(ll$lat, ll$long, tme)
  wd <- round(ncephourly$winddir%%360 / (360 / 8), 0) + 1
  wd <- ifelse(wd > 8, wd - 8, wd)
  lws <- log(ncephourly$windspeed)
  b1 <- 11.003 * lws - 9.357
  b1[b1 < 0] <- 0
  b2 <- 0.6253 * lws - 3.5185
  dT <- sst - ncephourly$temperature
  p1 <- 1.12399 - 0.39985 * dT - 0.73361 * lws
  p2 <- -0.011142 + 0.01048 * dT + 0.043311 * lws
  d2 <- bound((1 - lsrm + 2) / 3)
  aout <- array(NA, dim = c(dim(landsea)[1:2], length(tme)))
  cat("Applying coastal effects \n")
  for (i in 1:length(tme)) {
    d1 <- bound((1 - lsr2[,,wd[i]] + 2) / 3)
    xx <- p1[i] * d1^b1[i] + p2[i] * d2^b2[i]
    xx[xx > 6] <- 6
    xx[xx < -6] <- -6
    pdT <- dT[i] + xx
    aout[,,i] <- (pdT - sst[i]) * (-1)
    if (plot.progress == T & i%%500 == 0) {
      plot(if_raster(aout[,,i], landsea), main = tme[i])
    }
  }
  return(aout)
}

#' Extract NCEP data and run microclima for use with NicheMapR
#'
#' @param lat the latitude (in decimal degrees) of the location for point data are required.
#' @param long the longitude (in decimal degrees) of the location for point data are required.
#' @param dstart start date as character of time period required in format DD/MM/YYYY (UTC timezones assumed)
#' @param dfinish end date as character of time period required in format DD/MM/YYYY (UTC timezones assumed)
#' @param l  a single numeric value of the leaf area index for location (see details).
#' @param x  a single numeric value representing the ratio of vertical to horizontal
#' projections of leaf foliage for the location (see details).
#' @param coastal an optional logical value indicating whether to calculate coastal effects using [coastalNCEP()].
#' @param hourlydata an optional data frame of ourly weather and radiation data as returned
#' by function [hourlyNCEP()]. Function called if data not provided.
#' @param dailyprecip an optional data frame of ourly weather and radiation data as returned
#' by function [dailyprecipNCEP()]. Function called if data not provided.
#' @param dem an ptional raster object of elevations covering the location for which point
#' data are required. If not provided, one is downloaded from the registry of Open
#' Data on AWS. Used to calculate coastal, wind and radiation effects
#' @param demmeso an optional raster object of elevations covering the location for which point
#' data are required. Used to calculate elevation and cold air drainage effects.
#' @param albr albedo of ground surface from which radiation is reflected (see details)
#' @param resolution the resolution (in metres) of the dem used for downscaling (see details).
#' @param zmin assumed sea-level height. Values below this are set to zmin (see details).
#' @param slope an optional slope value for the location in decimal degrees. If not specified, then
#' the slope is calculated from the retrieved dem.
#' @param aspect an optional aspect value for the location in decimal degrees. If not specified, then
#' the slope is calculated from the retrieved dem.
#' @param windthresh an optional threshold wind value (m /s) above which cold air drainage is
#' assumed not to occur
#' @param emthresh an optional threshold emissivity value above which cold air drainage is
#' assumed not to occur
#' @param reanalysis2 Logical. Should data be obtained from the Reanalysis II dataset (default) or
#' from Reanalysis I (data prior to 1979).
#' @param steps  an optional integer. Coastal effects are calculated in specified directions upwind. Steps defines the total number of directions used. If the default 8 is specified, coastal effects are calculated at 45º intervals.
#' @param use.raster an optional logical value indicating whether to mask the output values by `landsea`.
#' @param plot.progress logical value indicating whether to produce plots to track progress.
#' @param tidyr logical value indicating whether to download 30m resolution digital elevation data.
#' @param weather.elev optional value indicating the elevation of values in `hourlydata`. Either a numeric value, corresponding to
#' the elevation in (m) of the location from which `hourlydata` were obtained, or one of `ncep` (default, data derive
#' from NOAA-NCEP reanalysis) project or `era5` (derived from Copernicus ERA5 climate reanalysis).
#' @param cad.effects optional logical indicating whether to calaculate cold air drainage effects
#' (TRUE = Yes, slower. FALSE =  No, quicker)
#'
#' @return a list with the following objects:
#'
#' (1) hourlydata: a data frame of hourly climate data from NCEP,
#' as returned by [hourlyNCEP()].
#'
#' (2) hourlyradwind: A data frame with seven columns:
#' (i) `swrad`: total downward shortwave radiation as a funcion of slope, aspect and
#' canopy shading (\ifelse{html}{\out{MJ m<sup>-2</sup> hr<sup>-1</sup>}}{\eqn{MJ m^-2 hr^{-1}}})
#' as returned by [shortwaveveg()].(ii) `skyviewfact`: a topographic sky view correction factor
#' as returned by [skyviewveg()]. (iii) `canopyfact` a canopy shading factor weighted by the ratio of direct to diffuse radiation.
#' (iv) `whselt`: a ground level wind shelter coefficient as returned by
#' [windcoef()]. (v) `windspeed`: wind speed (m /s) at 2 m, corrected for topographic
#' sheltering. (vi) `slope`: the slope (decimal degrees) of the location specified
#' by `lat` and `long`. Either the same as that input, or determined from `dem`.
#' (vii) `aspect`: the aspect (decimal degrees) of the location specified by `lat` and
#' `long`. Either the same as that input, or determined from `dem`.
#'
#' (3) `tref`: a dataframe with five columns (i) `tref` the reference temperature, derived from
#' NCEP data. (ii) `elev` the elevation of the location. (iii) `elevncep`: the mean elevation
#' of the 2.5º grid cell corresponding to that from which NCEP data were derived. (iv)
#' `telev` the expected difference in temperature due to elevation, calculated by
#' applying [lapserate()]. (v) `tcad` the expected difference in temperature due to cold
#' air drainage effects, determined using [pcad()] and [cadconditions()].
#'
#' (4) `dailyprecip` a vector of daily rainfalls derived from NCEP.
#'
#' (5) `acoast` an array of temperatures that account for coastal effects for each
#' pixel of `dem` and each hour of hourlydata.
#'
#' (6) `basins` a raster object of basins used for calculating cold air drainage.
#'
#' (7) `flowacc` a raster object of accumulated flow.
#'
#' @details The package `NicheMapR` (Kearney & Porter 2016), includes a suite of
#' programs for mechanistic modelling of heat and mass exchange and provides a
#' time-series of hourly estimates of above- and below‐ground conditions. It has the
#' significant advantage over `microclima` in that it works entirely from first
#' principles does not require measurements of temperature for calibration, but does
#' not explicitly model elevation and cold-air drainage effects. The slope, aspect
#' and canopy effects must be specified by the user, and their effect on radiation
#' are more simplistically modelled than by `microclima`.
#'
#' @references Kearney MR,  Porter WP (2016) NicheMapR – an R package for biophysical
#' modelling: the microclimate model. Ecography 40: 664-674.
#'
#' @seealso Function [get_dem()] for retrieving digital elevation data and
#' [hourlyNCEP()] for retrieving and processing hourly data from NCEP and
#' [coastalNCEP()] for retrieving and processing coastal effects.
#'
#' @export
#' @import raster
#' @import sp sf
#' @import zoo
#'
#' @examples
#' mnr <- microclimaforNMR(50, -5.2, '15/01/2015', '15/02/2015', 1, 1, coastal = FALSE)
#' head(mnr$hourlydata)
#' head(mnr$hourlyradwind)
#' head(mnr$tref)
#' head(mnr$dailyprecip)
microclimaforNMR <- function(lat, long, dstart, dfinish, l, x, coastal = TRUE, hourlydata = NA,
                             dailyprecip = NA, dem = NA, demmeso = dem, albr =0.15,
                             resolution = 100, zmin = 0, slope = NA, aspect = NA, horizon = NA,
                             svf = NA, difani = TRUE, windthresh = 4.5, emthresh = 0.78, reanalysis2 = FALSE,
                             steps = 8, use.raster = TRUE, plot.progress = TRUE, tidyr = FALSE,
                             weather.elev = 'ncep', cad.effects = TRUE) {
  tme <- seq(as.POSIXlt(dstart, format = "%d/%m/%Y", origin = "01/01/1900", tz = "UTC"),
             as.POSIXlt(dfinish, format = "%d/%m/%Y", origin = "01/01/1900", tz = "UTC")
             + 3600 * 24, by = 'hours')
  tme2 <- seq(as.POSIXlt(dstart, format = "%d/%m/%Y", origin = "01/01/1900"),
              as.POSIXlt(dfinish, format = "%d/%m/%Y", origin = "01/01/1900")
              + 3600 * 24, by = 'hours')
  tz1 <- format(as.POSIXlt(tme), format="%Z")
  tz2 <- format(as.POSIXlt(tme2), format="%Z")
  xx <- as.numeric(tme) == as.numeric(tme2)
  sel <- which(xx == FALSE)
  if (length(sel) > 1) {
    warning(paste("Data sequence in UTC/GMT. Some or all dates using system timezone are in", tz2[sel[1]]))
  }
  tme <- tme[-length(tme)]
  tme <- as.POSIXlt(tme)
  if (class(dem)[1] == "logical") {
    cat("Downloading digital elevation data \n")
    dem <- get_dem(r = NA, lat = lat, long = long, resolution = resolution, zmin = zmin)
  }
  if (class(demmeso)[1] == "logical") demmeso <- dem
  if (class(hourlydata)[1] == "logical") {
    cat("Extracting climate data from NCEP \n")
    hourlydata <- hourlyNCEP(ncepdata = NA, lat, long, tme, reanalysis2)
  }
  if (class(dailyprecip) == "logical") {
    cat("Extracting rainfall data from NCEP \n")
    dailyprecip <- dailyprecipNCEP(lat, long, tme, reanalysis2)
  }
  cat("Downscaling radiation and wind speed \n")
  radwind <- .pointradwind(hourlydata, dem, lat, long, l, x, albr, zmin, slope, aspect,
                           horizon, svf, difani)
  cat("Calculating meso-scale terrain effects \n")
  info <- .eleveffects(hourlydata, demmeso, lat, long, windthresh, emthresh, weather.elev, cad.effects)
  elev <- info$tout
  if (coastal) {
    m <- is_raster(dem)
    m[m == zmin] <- NA
    dem <- if_raster(m, dem)
    acoast <- coastalNCEP(dem, hourlydata, steps, use.raster, zmin, plot.progress, tidyr = FALSE)
    xi <- floor(dim(dem)[1] / 2)
    yi <- floor(dim(dem)[2] / 2)
    ce <- acoast[xi, yi, ]
    if (is.na(ce[1])) ce <- apply(acoast, 3, mean, na.rm = T)
    elev$tref <- ce - elev$tref + elev$telev
  }  else acoast <- NA
  return(list(hourlydata = hourlydata, hourlyradwind = radwind, tref = elev,
              dailyprecip = dailyprecip, acoast = acoast, basins = info$basins,
              flowacc = info$flowacc))
}
#' Function for automatically generating microclimate surfaces for anywhere in the word
#'
#' @description This function generating microclimate temperature surfaces for anywhere
#' in the word. If hourly weather data are not provided, it first downloads coarse-resolution climate and radiation data from the
#' NCEP-NCAR or NCEP–DOE Atmospheric Model Intercomparison Project (Kanamitso et al
#' 2002) and interpolates these data to hourly. It calculates mesoclimatic effects and derives parameters for fitting the
#' microclimate model using the `NicheMapR` package (Kearny & Porter 2016). Using digital
#' elevation data either downloaded or provided by the user, and canopy
#' characteristics either specified by the user, or derived from habitat,
#' it runs the microclimate model in hourly timesteps to generate an array of temperatures.
#'
#' @param r a raster object defining the extent and resolution for which microclimate
#' temperature data are required. Supplied raster must have a projection such that the units of
#' x, y and z are identical, and grid cells are square. NA values are assumed to be sea, which
#' is important in the calculation of coastal and cold air drainage effects.
#' @param dstart start date as character of time period required in format DD/MM/YYYY
#' @param dfinish end date as character of time period required in format DD/MM/YYYY
#' @param hgt the height (in m) above or below ground for which temperature estimates
#' are required. If negative, below-ground temperatures are derived (range -2 to 2).
#' @param l  an optional single numeric value, vector, raster, matrix or array of leaf area
#' index values. If a single numeric value, the same leaf area is assumed at all locations
#' and times. If a vector, the same leaf area is assumed at all locations, but leaf area is
#' assumed ot vary through time. If a matrix or raster, leaf area is assumed to vary by
#' location, but is assumed static in time. If an array, variable leaf areas in time and
#' space are assumed.
#' @param x  a single numeric value, matrix or raster representing the ratio of vertical to horizontal projections of foliage as,
#' for example, returned by [leaf_geometry()]. if a single numeric value, the same value of `x`
#' is assumed at all locations. Varying `x` through time is not supported.
#' @param habitat a character string, numeric value or matrix or raster of numeric
#' values of habitat type(s). See [habitats()]. Ignored if l or x are provided. If a single
#' numeric value, the same habitat is assumed at all locations. Varying habitats in time are not
#' supported.
#' @param hourlydata an optional dataframe of hourly climate forcing variables. If not supplied
#' downloaded using [hourlyNCEP()]
#' \describe{
#'   \item{obs_time}{POSIXlt object of times in UTC}
#'   \item{temperature}{Temperatures at 2 m (ºC)}
#'   \item{humidity}{Specific humidity at 2m (Kg / Kg)}
#'   \item{pressure}{Surface pressure (Pa)}
#'   \item{windspeed}{Wind speed at 2 m (metres per second)}
#'   \item{winddir}{Wind direction (degrees from N)}
#'   \item{emissivity}{Emissivity of the atmosphere (0 - 1, downlong / uplong)}
#'   \item{netlong}{Net longwave radiation (\ifelse{html}{\out{MJ m<sup>-2</sup> hr<sup>-1</sup>}}{\eqn{MJ m^-2 hr^{-1}}})}
#'   \item{uplong}{Upward longwave radiation (\ifelse{html}{\out{MJ m<sup>-2</sup> hr<sup>-1</sup>}}{\eqn{MJ m^-2 hr^{-1}}})}
#'   \item{downlong}{Downward longwave radiation (\ifelse{html}{\out{MJ m<sup>-2</sup> hr<sup>-1</sup>}}{\eqn{MJ m^-2 hr^{-1}}})}
#'   \item{rad_dni}{Direct radiation normal to the solar beam (\ifelse{html}{\out{MJ m<sup>-2</sup> hr<sup>-1</sup>}}{\eqn{MJ m^-2 hr^{-1}}})}
#'   \item{rad_dif}{Diffuse radiation (\ifelse{html}{\out{MJ m<sup>-2</sup> hr<sup>-1</sup>}}{\eqn{MJ m^-2 hr^{-1}}})}
#'   \item{szenith}{The zenith angle (degrees)}
#'   \item{cloudcover}{Cloud cover (Percentage)}
#' }
#' @param dailyprecip a vector of daily rainfall (mm / day) for the period specified.
#' If not supplied downloaded using [dailyprecipNCEP()].
#' @param coastal optional logical value indicating whether or not to calculate
#' coastal effects.
#' @param use.raster optional logical value indicating whether to use `r` in the
#' calculation of coastal effects. If used, NAs or values corresponding to `zmin`
#' must represent sea.
#' @param r.is.dem optional logical value indicating whether 'r' is a digital
#' elevation dataset used for calculating microclimatic effects. If FALSE, then a
#' dem is downloaded.
#' @param save optional integer: don't save forcing data (0), save the forcing data (1)
#' or read previously saved data (2).
#' @param dailyprecip Optional data.frame of daily precipitation data as returned by [dailyprecipNCEP()]
#' @param albg an optional single value, raster object, two-dimensional array or
#' matrix of values representing the albedo(s) of the ground as returned by [albedo2()].
#' @param albr albr an optional single value, raster object, two-dimensional array
#' or matrix of values representing the albedo(s) of adjacent surfaces as returned
#' by [albedo_reflected()].
#' @param albc an optional single value, raster object, two-dimensional array or
#' matrix of values representing the albedo(s) of the vegetated canopy as returned
#' by [albedo2()].
#' @param mesoresolution optional numeric value indicating resolution of the dem used for
#' modelling mesoclimate.
#' @param zmin assumed sea-level height. Values below this are set to zmin (see details).
#' @param slope an optional slope value for the location in decimal degrees. If not specified, then
#' the slope is calculated from the retrieved dem.
#' @param aspect an optional aspect value for the location in decimal degrees. If not specified, then
#' the slope is calculated from the retrieved dem.
#' @param windthresh an optional threshold wind value (m /s) above which cold air drainage is
#' assumed not to occur.
#' @param emthresh an optional threshold emissivity value above which cold air drainage is
#' assumed not to occur.
#' @param reanalysis2 Optional logical. Should data be obtained from the Reanalysis II dataset (default) or
#' from Reanalysis I (data prior to 1979).
#' @param steps  an optional integer. Coastal effects are calculated in specified
#' directions upwind. Steps defines the total number of directions used. If the
#' default 8 is specified, coastal effects are calculated at 45º intervals.
#' @param plot.progress an optional logical indicating whether to produce plots to track progress.
#' @param continuous an optional logical value indicating whether to treat wind speed as a continuous variable
#' @param summarydata an optional logical indicating whether to calculate summary data
#' (frost hours and maximum, minimum and mean temperature) for each pixel and return these to the output.
#' @param save.memory An optional logical indicatign whether to save
#' @param weather.elev optional value indicating the elevation of values in `hourlydata`. Either a numeric value, corresponding to
#' the elevation in (m) of the location from which `hourlydata` were obtained, or one of `ncep` (default, data derive
#' from NOAA-NCEP reanalysis) project or `era5` (derived from Copernicus ERA5 climate reanalysis).
#' @param cad.effects optional logical indicating whether to calaculate cold air drainage effects
#' (TRUE = Yes, slower. FALSE =  No, quicker)
#' memory by storing temperature x 1000 as an integer values.
#'
#' @references Kearney MR,  Porter WP (2016) NicheMapR – an R package for biophysical
#' modelling: the microclimate model. Ecography 40: 664-674.
#'
#' Kanamitsu M, Ebisuzaki W, Woollen J, Yang SK, Hnilo JJ, Fiorino M, Potter GL (2002)
#'Ncep–doe amip-ii reanalysis (r-2). Bulletin of the American Meteorological Society 83: 1631-1644.
#'
#' @return a list with the following objects:
#' (1) temps: an array of temperatures for each pixel of r and hour of the time sequence.
#' (2) e: an extent object given the extent of `temps`. Generally the same as `raster::extent(r)`
#' though note that edge cells are NA as slopes cannot be calculated for these cells.
#' (3) units: the units of `temps`. Either deg C or dec C * 1000 if `save.memory` is TRUE.
#' (4) tmax: If `summarydata` is TRUE, a matrix of maximum temperatures
#' (5) tmin: If `summarydata` is TRUE, a matrix of minimum temperatures
#' (6) tmean: If `summarydata` is TRUE, a matrix of mean temperatures
#' (7) frosthours: If `summarydata` is TRUE, a matrix of hours below 0 deg C.
#'
#' @import raster
#' @export
#'
#' @examples
#' library(raster)
#' require(NicheMapR)
#' # Get DEM for Pico, Azores
#' r <- get_dem(lat = 38.467429, long = -28.398995, resolution = 30)
#' plot(r)
#' # Takes ~ c. 5 minutes to run
#' temps <- runauto(r, "10/06/2010", "15/06/2010", hgt = 0.1, l = NA, x = NA,
#'                      habitat = "Barren or sparsely vegetated")
#' mypal <- colorRampPalette(c("darkblue", "blue", "green", "yellow", "orange",
#'                           "red"))(255)
#' meantemp <- temps$tmean
#' extent(meantemp) <- temps$e
#' par(mfrow = c(1, 1))
#' plot(meantemp, main = "Mean temperature", col = mypal)
#' # Interactive 3D plot
#' require(plotly)
#' zrange<-list(range = c(0, 3000))
#' plot_ly(z = ~is_raster(r)) %>%
#' add_surface(surfacecolor = ~is_raster(meantemp)) %>%
#' layout(scene = list(zaxis = zrange))

runauto <- function(r, dstart, dfinish, hgt = 0.05, l, x, habitat = NA,
                    hourlydata = NA, dailyprecip = NA, use.raster = FALSE,
                    coastal = TRUE, r.is.dem = TRUE, save = 0, albg = 0.15, albr =0.15,
                    albc = 0.23, mesoresolution = 100, zmin = 0, slope = NA,
                    aspect = NA, windthresh = 4.5, emthresh = 0.78, reanalysis2 = FALSE,
                    steps = 8, plot.progress = TRUE, continuous = TRUE,
                    summarydata = TRUE, save.memory = FALSE, weather.elev = 'ncep',
                    cad.effects = TRUE) {
  longwaveveg2 <- function(le0, lwsky, x, fr, svv, albc) {
    lw1 <- (1 - fr) * lwsky
    lr <- (2 / 3) * log(x + 1)
    r <- 1 / (1 + exp(-1 * lr))
    lw2 <- albc * r * fr * lwsky
    lw3 <- r * (1 - albc) * le0 * fr
    lwd <- lw1 + lw2 + lw3
    lwrad <- (le0 - lwd) * svv
    lwrad
  }
  habfun <- function(habitat, lat, long, sel = NA, hoy, year, la, hgt) {
    if (hgt < 0) hgt <- 0
    xx <- laifromhabitat(habitat[1], lat, long, year = year)
    lv <- xx$lai[hoy]
    hh <- xx$height
    mm <- (1 - (hgt / hh))
    if (hgt > hh) mm <- 0
    if (class(sel) == "logical") {
      la <- round(lv * mm, 3)
    } else la[sel] <- round(lv, 3)
    la
  }
  laitime <- function(l, habitat, lat, long, tme, i, hgt) {
    yr <- tme$year[i] + 1900
    hoy <- (as.numeric(strftime(tme[i], format = "%j")) - 1) * 24 +
      as.numeric(tme$hour[i]) + 1
    if (class(habitat)[1] == "matrix") {
      u <- unique(as.vector(habitat))
      u <- u[is.na(u) == F]
      la <- array(NA, dim = c(dim(r)[1:2]))
      for (ii in 1:length(u)) {
        sel <- which(habitat == u[ii], arr.ind = T)
        la <-  habfun(habitat[sel], lat, long, sel, hoy, yr, la, hgt)
      }
    }
    if  (class(habitat) == "numeric" | class(habitat) == "character") {
      la <- 0
      la <-  habfun(habitat, lat, long, sel = NA, hoy, yr, la, hgt)
      la <- array(la, dim = dim(r)[1:2])
    }
    if (class(l)[1] == "numeric" | class(l)[1] == "integer") {
      if (length(l) == 1) {
        la <- array(l, dim = dim(r)[1:2])
      } else la <- array(l[i], dim = dim(r)[1:2])
    }
    if (class(l)[1] == "matrix") la <- l
    if (class(l)[1] == "array") la <- l[,,i]
    la
  }
  checkfun <- function(l, x, habitat, r, tme) {
    if (class(l)[1] == "logical" & class(habitat)[1] == "logical") {
      stop ("No habitat or leaf area provided")
    }
    if (class(x)[1] == "logical" & class(habitat)[1] == "logical") {
      stop ("No habitat or leaf angle distribution provided")
    }
    if (class(l)[1] == "matrix" | class(l)[1] == "array") {
      if (dim(l)[1] != dim(r)[1] & dim(l)[2] != dim(r)[2]) {
        stop ("l must have same xy dimensions as r")
      }
    }
    if (class(l)[1] == "array") {
      if (dim(l)[3] != length(tme)) {
        stop ("Length of l must equal 1 or number of hours in time sequence")
      }
    }
    if (class(l)[1] == "numeric" | class(l)[1] == "integer") {
      if (length(l) != 1 & length(l)  != length(tme)) {
        stop ("Length of l must equal 1 or number of hours in time sequence")
      }
    }
    if (class(habitat)[1] == "matrix") {
      if (dim(habitat)[1] != dim(r)[1] & dim(habitat)[2] != dim(r)[2]) {
        stop("habitat must have same dimensions as r")
      }
    }
    if (class(x)[1] == "matrix") {
      if (dim(x)[1] != dim(r)[1] & dim(x)[2] != dim(r)[2]) {
        stop("x must have same dimensions as r")
      }
    }
    if (class(x)[1] == "numeric" | class(x)[1] == "integer") {
      if (length(x) > 1) stop("varying x through time not supported")
    }
    if (class(x)[1] == "array") stop("varying x through time not supported")
  }
  frost <- function(x) {
    sel <- which(x <= 0)
    y <- length(sel)
    if (is.na(mean(x, na.rm = TRUE))) y <- NA
    y
  }
  if (!requireNamespace("NicheMapR", quietly = TRUE)) {
    #  stop("package 'NicheMapR' is needed. Please install it from Github: 'mrke/NicheMapR'",
    #       call. = FALSE)
  }
  # Lat long and time
  lat <- latlongfromraster(r)$lat
  long <- latlongfromraster(r)$long
  tme <- seq(as.POSIXlt(dstart, format = "%d/%m/%Y", origin = "01/01/1900", tz = "UTC"),
             as.POSIXlt(dfinish, format = "%d/%m/%Y", origin = "01/01/1900", tz = "UTC")
             + 3600 * 24, by = 'hours')
  tme2 <- seq(as.POSIXlt(dstart, format = "%d/%m/%Y", origin = "01/01/1900"),
              as.POSIXlt(dfinish, format = "%d/%m/%Y", origin = "01/01/1900")
              + 3600 * 24, by = 'hours')
  tz1 <- format(as.POSIXlt(tme), format="%Z")
  tz2 <- format(as.POSIXlt(tme2), format="%Z")
  xx <- as.numeric(tme) == as.numeric(tme2)
  sel <- which(xx == FALSE)
  if (length(sel) > 1) {
    cat(paste("Note: Model run in UTC/GMT. Some or all dates using system timezone are in", tz2[sel[1]]), "\n")
  }
  tme <- tme[-length(tme)]
  tme <- as.POSIXlt(tme)
  # l and x
  l <- is_raster(l)
  x <- is_raster(x)
  habitat <- is_raster(habitat)
  checkfun(l, x, habitat, r, tme)
  if (class(x)[1] == "numeric" | class(x)[1] == "integer") x <- array(x, dim = dim(r)[1:2])
  if (class(x)[1] == "logical") {
    if (class(habitat)[1]  == "numeric" | class(habitat)[1] == "integer" |
        class(habitat)[1] == "character") {
      x <- laifromhabitat(habitat[1], lat, long, year = 2015)$x
      x <- array(x, dim = dim(r)[1:2])
    } else {
      u <- unique(as.vector(habitat))
      u <- u[is.na(u) == F]
      x <- array(NA, dim = c(dim(r)[1:2]))
      for (ii in 1:length(u)) {
        sel <- which(habitat == u[ii])
        x[sel] <-  laifromhabitat(u[ii], lat, long, year = 2015)$x
      }
    }
  }
  # Run NicheMapR
  loc <- as.numeric(c(long, lat))
  reso <- mean(res(r))
  if (reso < 100) {
    cat("Downloading DEM for mesoscale modelling \n")
    dem <- get_dem(r = NA, lat = lat, long = long, resolution = mesoresolution, zmin = zmin)
    if (mesoresolution > 30) {
      dm30 <- get_dem(r = dem, lat = lat, long = long, resolution = 30, zmin = zmin)
      dem <- resample(dm30, dem)
    }
  } else {
    if (r.is.dem) {
      dem <- r
    } else dem <- suppressWarnings(get_dem(r = r, lat = lat, long = long, resolution = reso, zmin = zmin))
  }
  if (r.is.dem == F) {
    r <- suppressWarnings(get_dem(r = r, lat = lat, long = long, resolution = reso, zmin = zmin))
  }
  if (hgt < -2) {
    warning("At depths > 2 m temperature assumed to to be stable. Depth set to 2 m")
    hgt <- -2
  }
  if (hgt > 2) {
    warning("User height greater than reerence height. Height set to 2 m")
    hgt <- 2
  }
  dep <- c(2.5, 5, 10, 15, 20, 30, 50, 100, 200)
  if (hgt < 0) {
    shgt <- hgt * -100
    dif <- abs(dep - shgt)
    selsoil <- which(dif == min(dif))
    dep[selsoil] <- shgt
    hgt2 <- 0.05
  } else if (hgt < 0.005) {
    hgt2 <- 0.005
  } else hgt2 <- hgt
  dep <- c(0, dep)
  loc <- c(long, lat)
  dem[dem == zmin] <- NA
  r[r == zmin] <- NA
  micronmr <- micro_ncep(dstart = dstart, dfinish = dfinish, dem = r, dem2 = dem, LAI = 0,
                         loc = loc, Usrhyt = hgt2, Refhyt = 2, coastal = coastal, reanalysis = reanalysis2,
                         DEP = dep, save = save, hourlydata = hourlydata, dailyprecip = dailyprecip,
                         weather.elev = weather.elev, cad.effects = cad.effects)
  ma <- micronmr$microclima.out
  hourlydata <- ma$hourlydata
  #####################
  tme <- as.POSIXlt(tme + 0, tz = "UTC")
  nmrout <- as.data.frame(micronmr$metout)
  rwind <- ma$hourlyradwind
  netrad = (1 - 0.15) * rwind$swrad - 0.98 * hourlydata$netlong * rwind$skyviewfact * (1 - rwind$canopyfact)
  cat("Paramaterising model using NicheMapR \n")
  if (hgt > 0) {
    mft <- data.frame(temperature = nmrout$TALOC,
                      reftemp = nmrout$TAREF,
                      wind = rwind$windspeed,
                      netrad = netrad)
    params <- fitmicro(mft, alldata = TRUE, continuous = continuous)
  }
  if (hgt <= 0) {
    soiltemps <- as.data.frame(micronmr$soil)
    mft <- data.frame(temperature = soiltemps$D0cm,
                      reftemp = nmrout$TAREF,
                      wind = rwind$windspeed,
                      netrad = netrad)
    params <- fitmicro(mft, alldata = TRUE, continuous = continuous)
  }
  if (hgt < 0) {
    pred0 <- mft$reftemp + params$Estimate[1] + params$Estimate[3] * netrad +
      params$Estimate[2] * log(rwind$windspeed + 1) +
      params$Estimate[4] * netrad * log(rwind$windspeed + 1)
    soiltemp <- soiltemps[,selsoil+3]
    dfsoil <- .fitsoil(pred0, mean(soiltemps$D200cm), soiltemp)
  }
  if (coastal) {
    tref <- ma$acoast
  } else {
    tref <- rep(hourlydata$temperature, each = dim(r)[1] * dim(r)[2])
    tref <- array(tref, dim = c(dim(r)[1:2], length(tme)))
  }
  tout <- array(NA, dim = dim(tref))
  if (hgt < 0) {
    tout <- abind(tout, tout[,,1])
    tout[,,1] <- pred0[1]
  }
  jd <- julday(tme$year + 1900, tme$mon + 1, tme$mday)
  m <- is_raster(dem)
  m[is.na(m)] <- zmin
  dem2 <- if_raster(m, dem)
  m <- is_raster(r)
  m[is.na(m)] <- zmin
  r2 <- if_raster(m, r)
  cat("Calculating wind shelter coefficients \n")
  wca <- array(NA, dim = c(dim(r)[1:2], 16))
  rff <- raster(r)
  res(rff) <- res(r)[1] / 4
  r3 <- resample(r2, rff)
  hgtw <- ifelse(hgt < 0, 0, hgt)
  for (i in 0:15) {
    wcr <- windcoef(r3, i * 22.5, hgt = hgtw, reso = res(r3))
    wcr <- aggregate(wcr, 8)
    wca[,,i + 1] <- is_raster(resample(wcr, r))
  }
  wca2 <- wca
  for (i in 1:16) {
    mn <- i - 1
    mn <- ifelse(mn == 0, 8, mn)
    mx <- i + 1
    mx <- ifelse(mx >8, mx - 8, mx)
    wca2[,,i] <- 0.25 * wca[,,mn]  + 0.5 * wca[,,i] + 0.25 * wca[,,mx]
  }
  wca <- wca2
  wca2 <- NULL
  cad <- ma$tref
  cad <- cad$tcad
  basin <- mask(ma$basins, dem)
  fa <- mask(ma$flowacc, dem)
  la <-  laitime(l, habitat, lat, long, tme, 1, hgt)
  svv <- skyviewveg(is_raster(r2), la, x, steps = 36, reso = reso)
  ha <- mean_slope(is_raster(r2), reso = reso)
  cat("Running model in hourly time-steps \n")
  if (hgt < 0) tout[,,1] <- soiltemp[1]
  if  (plot.progress) {
    mypal <- colorRampPalette(c("darkblue", "blue", "green", "yellow", "orange", "red"))(255)
    mypal2 <- rev(colorRampPalette(c("white", "blue", "green"))(255))
  }
  mnwind <- as.numeric(quantile(rwind$windspeed, 0.05))
  mxwind <- as.numeric(quantile(rwind$windspeed, 0.95))
  for (i in 1:length(jd)) {
    # elevation effects
    tr <- mean(tref[,,i], na.rm = T)
    lr <- lapserate(tr, hourlydata$humidity[i], hourlydata$pressure[i])
    tr <- tr + is_raster(r2) * lr
    # cold air drainage effects
    if (cad[i] > 0) {
      cadp <- pcad(dem, basin, fa, hourlydata$temperature[i],
                   hourlydata$humidity[i], hourlydata$pressure[i])
      cadp <- projectRaster(cadp, crs = crs(r))
      cadp <- resample(cadp, r2)
      cadp <- mask(cadp, r)
      tr <- tr + cadp
    }
    if (i%%100 == 0) {
      la <-  laitime(l, habitat, lat, long, tme, i, hgt)
      svv <- skyviewveg(is_raster(r2), la, x, steps = 36, reso = reso)
      ha <- mean_slope(is_raster(r2), reso = reso)
    }
    fr <- canopy(la, 0.23)
    alb <- fr * albc + (1 - fr) * albg
    radsw <- shortwaveveg(hourlydata$rad_dni[i], hourlydata$rad_dif[i], jd[i],
                          tme$hour[i], dtm = r2, svv = svv, alb = alb,
                          fr = fr, albr = albr, ha = ha, reso = reso, merid = 0,
                          x = x, l = la)
    radlw <- longwaveveg2(hourlydata$uplong[i], hourlydata$downlong[i], x, fr,
                          svv, is_raster(albc))
    nr <- is_raster(radsw) - 0.98 * radlw
    nr <- if_raster(nr, r2)
    wi <- round(hourlydata$winddir[i] / 22.5, 0) + 1
    wi <- ifelse(wi > 16, wi - 16, wi)
    wi <-ifelse(wi < 1, wi + 16, wi)
    wspd <- windheight(hourlydata$windspeed[i], 2, 1)
    ws <- wca[,,wi] * wspd
    ws <- ifelse(ws < mnwind, mnwind, ws)
    ws <- ifelse(ws > mxwind, mxwind, ws)
    ws <- if_raster(ws, r2)
    tea <- runmicro(params, nr, ws, continuous = continuous)
    tea <- mask(tea, r)
    if (hgt < 0) {
      soil0 <- is_raster(tea) + tr
      heatdown <-  soil0 - tout[,,i]
      heatup <-  mean(soiltemps$D200cm) - tout[,,i]
      xx <- tout[,,i] + dfsoil$x1 * heatdown + dfsoil$x2 * heatup
      if (save.memory) xx <- round(xx * 1000, 0)
      tout[,,i + 1] <- xx
    } else  {
      xx <- is_raster(tr) + is_raster(tea)
      if (save.memory) xx <- round(xx * 1000, 0)
      tout[,,i] <- xx
    }
    if (plot.progress & i%%100 == 0) {
      tempr <- if_raster(tout[,,i], r)
      if (save.memory) tempr <- tempr / 1000
      plot(tempr, main = tme[i], col = mypal)
    }
  }
  if (hgt < 0) {
    dtr <- dim(tout)[3]
    tout <- tout[,,-dtr]
  }
  tmax <- NA
  tmin <- NA
  tmean <- NA
  hfrost <- NA
  if (summarydata) {
    cat("Calculating summary data \n")
    tmax <- if_raster(apply(tout, c(1, 2), max), r)
    tmin <- if_raster(apply(tout, c(1, 2), min), r)
    tmean <- if_raster(apply(tout, c(1, 2), mean), r)
    tfrost <- if_raster(apply(tout, c(1, 2), frost), r)
  }
  if (plot.progress) {
    if (save.memory) {
      tmax <- tmax / 1000
      tmin <- tmin / 1000
      tmean <- tmean / 1000
    }
    par(mfrow=c(2,2))
    plot(tmax, main = "Maximum temperature", col = mypal)
    plot(tmin, main = "Minimum temperature", col = mypal)
    plot(tmean, main = "Mean temperature", col = mypal)
    plot(tfrost, main = "Frost hours", col = mypal2)
  }
  if (save.memory) {
    return(list(temps = tout, e = extent(r), tme = tme, units = "deg C * 1000",
                tmax = tmax, tmin = tmin, tmean = tmean, frosthours = hfrost))
  } else {
    return(list(temps = tout, e = extent(r), tme = tme, units = "deg C",
                tmax = tmax, tmin = tmin, tmean = tmean, frosthours = hfrost))
  }
}
ilyamaclean/microclima documentation built on Oct. 31, 2023, 11:41 p.m.