#' @title Correct L1C products using Sen2Cor
#' @description The function uses Sen2Cor to manually correct L1C products.
#' Standalone version of
#' [sen2cor](https://step.esa.int/main/snap-supported-plugins/sen2cor/)
#' (version 2.8.0 or 2.5.5) is used.
#' @param l1c_prodlist List of L1C product names to be corrected. They can be both
#' product names with full/relative path or only names of SAFE products (in this case, also
#' l1c_dir argument must be provided). SAFE products must be unzipped.
#' Note that, at this stage, all products must be in the same directory (this will be fixed).
#' @param l1c_dir Full or relative path of input L1C products.
#' If NULL (default), `l1c_prodlist` must already be a vector of full paths.
#' @param outdir Directory where output L2A products will be placed.
#' If NULL (default), each product is left in the parent directory of `l1c_prodlist`.
#' @param proc_dir (optional) Directory where processing is applied.
#' If NA (default), processing is done in `l1c_dir` and output L2A product is
#' then moved to `outdir`, unless `l1c_dir` is a subdirectory of a SAMBA mountpoint under Linux:
#' in this case, L1C input products are copied in a temporary directory
#' (specified with argument `tmpdir`),
#' processing is done there and then L2A is moved to `outdir`.
#' This is required under Linux systems when `l1c_dir` is a subdirectory of
#' a unit mounted with SAMBA, otherwise Sen2Cor would produce empty L2A products.
#' @param tmpdir (optional) Path where processing is performed if a temporary
#' working directory is required (see argument `proc_dir`). Be sure `tmpdir`
#' not to be a SAMBA mountpoint under Linux.
#' Default is a temporary directory.
#' If `tmpdir` is a non-empty folder, a random subdirectory will be used.
#' @param rmtmp (optional) Logical: should temporary files be removed?
#' (Default: TRUE)
#' @param gipp (optional) Ground Image Processing Parameters (GIPP)
#' to be passed to Sen2Cor.
#' It is possible to specify both the path of an existing XML file
#' or a list of parameters in the form `parameter_name = "value"`, where
#' `parameter_name` is the name of the parameter as specified in the
#' XML file (case insensitive), and `"value"` is the character value
#' which the user wants to set
#' (notice that, in the case the user wants to specify the value `NONE`,
#' both `"NONE"` and `NA` can be used, but not `NULL`, which has the effect
#' to maintain the value specified in the XML file).
#'
#' For details about the GIPP parameters, refer to the Sen2Cor documentation
#' (v. [2.5.5](https://step.esa.int/main/snap-supported-plugins/sen2cor/sen2cor_v2-5-5/)
#' or [2.8.0](https://step.esa.int/main/snap-supported-plugins/sen2cor/sen2cor_v2-8/):
#' see the "Schemas of the GIPP file" at the end of each page).
#' _Note_: this argument takes effect only in the current execution of
#' `sen2cor()` function.
#' @param use_dem (optional) Logical:
#' if TRUE, Sen2Cor is set to use a Digital Elevation Model for topographic
#' correction (reflecting what is done for Level-2A SAFE images provided by ESA Hub);
#' if FALSE, it is set not to perform topographic correction (reflecting the
#' current default Sen2Cor behaviour);
#' if NA (default), the option set in the XML GIPP configuration file
#' used by sen2r (stored in the default sen2r settings directory) is respected;
#' in case the user never edited it,
#' the current default setting is not to perform topographic correction.
#'
#' _Notes_:
#' 1. if TRUE, the path used to read or store DEM files
#' and the online source used to download missing DEM tiles
#' are respectively the `DEM_Directory` and `DEM_Reference` parameters
#' set in the default sen2r GIPP XML file (the user can read them
#' with the function `read_gipp(c("DEM_Directory", "DEM_Reference"))`).
#' In case one or both these parameters were set to `"NONE"`,
#' a subdirectory `"srtm90"` of the default sen2r directory is used as
#' DEM directory, and/or the
#' [CGIAR SRTM 90m](https://csidotinfo.wordpress.com/category/data/)
#' is set as online source.
#' To set another directory or reference, use argument `gipp` in the form
#' `gipp = list(DEM_Directory = tempdir(), DEM_Reference ="another_reference", ...)`
#' (replacing `tempdir()` with the desired path and specifying the online resource).
#' 2. Currently the default value is NA in order to grant backward
#' compatibility. In a future release of sen2r, the default value will be
#' set to TRUE, so to grant homogeneity between Level-2A products downloaded
#' from ESA Hub and generated using Sen2Cor.
#' @param tiles Vector of Sentinel-2 Tile strings (5-length character) to be
#' processed (default: process all the tiles found in the input L1C products).
#' @param parallel (optional) Logical: if TRUE, Sen2Cor instances are launched
#' in parallel using multiple cores; if FALSE (default), they are launched in
#' series on a single core.
#' The number of cores is automatically determined; specifying it is also
#' possible (e.g. `parallel = 4`).
#' @param timeout Integer value: number of minutes after which killing Sen2Cor
#' if it is still running (default, 0, means that this is never done).
#' This can be useful in case Sen2Cor produced an error without exiting
#' from Python (leaving a standing process running).
#' @param overwrite Logical value: should existing output L2A products be overwritten?
#' (default: FALSE)
#' @param kill_errored Logical: experimental feature allowing killing dead Sen2Cor
#' processes, so leaving `sen2cor()` continuing processing on the remaining
#' products. Set to TRUE to activate it (default is FALSE).
#' This experimental feature is available only on Unix systems,
#' and requires package "tools" to be installed.
#' This option is not compatible with `timeout` (in case both are set,
#' `kill_errored` will be ignored).
#' @param overwrite Logical value: should existing output L2A products be overwritten?
#' (default: FALSE)
#' @param .log_message (optional) Internal parameter
#' (it is used when the function is called by `sen2r()`).
#' @param .log_output (optional) Internal parameter
#' (it is used when the function is called by `sen2r()`).
#' @return Vector character with the list ot the output products
#' (being corrected or already existing).
#'
#' @author Luigi Ranghetti, phD (2019)
#' @references L. Ranghetti, M. Boschetti, F. Nutini, L. Busetto (2020).
#' "sen2r": An R toolbox for automatically downloading and preprocessing
#' Sentinel-2 satellite data. _Computers & Geosciences_, 139, 104473.
#' \doi{10.1016/j.cageo.2020.104473}, URL: \url{https://sen2r.ranghetti.info/}.
#' @note License: GPL 3.0
#' @importFrom jsonlite fromJSON
#' @importFrom doParallel registerDoParallel
#' @importFrom foreach foreach "%do%" "%dopar%"
#' @importFrom parallel detectCores makeCluster stopCluster
#' @export
#'
#' @examples
#' \dontrun{
#' # Download an L1C SAFE product
#' example_s2_list <- s2_list(
#' spatial_extent = st_sfc(st_point(c(12.0, 44.8)), crs=st_crs(4326)),
#' tile = "32TQQ",
#' time_interval = as.Date(c("2017-05-01","2017-07-30"))
#' )
#' s2_download(example_s2_list, outdir = tempdir())
#'
#' # Correct it applying a topographic correction
#' sen2cor(
#' names(example_s2_list)[1],
#' l1c_dir = tempdir(),
#' outdir = tempdir(),
#' use_dem = TRUE
#' )
#' }
sen2cor <- function(
l1c_prodlist = NULL,
l1c_dir = NULL,
outdir = NULL,
proc_dir = NA,
tmpdir = NA,
rmtmp = TRUE,
gipp = NULL,
use_dem = NA,
tiles = NULL,
parallel = FALSE,
timeout = 0,
kill_errored = FALSE,
overwrite = FALSE,
.log_message = NA,
.log_output = NA
) {
# to avoid NOTE on check
i <- creation_datetime <- id_baseline <- path <- name <- NULL
# load Sen2Cor executable path
binpaths <- tryCatch(
load_binpaths("sen2cor"),
warning = stop
)
# Use a copy of the default GIPP XML, setting parameters properly
gipp_curr_path <- tempfile(pattern = "L2A_GIPP_", fileext = ".xml")
set_gipp(gipp = gipp, gipp_path = gipp_curr_path, use_dem = use_dem)
# get version
sen2cor_version_raw0 <- system(paste(binpaths$sen2cor, "-h"), intern = TRUE)
sen2cor_version_raw1 <- sen2cor_version_raw0[grep(
"^Sentinel\\-2 Level 2A Processor \\(Sen2Cor\\)\\. Version:",
sen2cor_version_raw0
)]
sen2cor_version <- package_version(gsub(
"^Sentinel\\-2 Level 2A Processor \\(Sen2Cor\\)\\. Version: ([2-9]+\\.[0-9]+\\.[0-9]+),.*$",
"\\1",
sen2cor_version_raw1
))
# tiles NA -> NULL
if (!is.null(tiles)) {
if (all(is.na(tiles))) {tiles <- character(0)}
}
# if l1c_dir is defined, append to product names
if (!is.null(l1c_dir)) {
l1c_prodlist <- file.path(l1c_dir,l1c_prodlist)
}
# # check that all products are in the same directory (FIXME allow differents <- need to change call_sen2cor)
# if (length(unique(dirname(l1c_prodlist)))>1) {
# print_message(
# type="error",
# "All input products must be in the same folder (this will be fixed to allow different ones).")
# }
# check that proc_dir is not on a mountpoint
if (!is.na(proc_dir) & Sys.info()["sysname"] != "Windows") {
if (any(attr(mountpoint(proc_dir), "protocol") %in% c("cifs", "nsfs"))) {
print_message(
type = "warning",
proc_dir, "is on a SAMBA mounted unit, so it will not be used."
)
proc_dir <- NA
}
}
# accept only input names which are L1C
l1c_prodlist_level <- safe_getMetadata(
l1c_prodlist, "level",
format = "vector", abort = FALSE, simplify = TRUE
)
l1c_prodlist <- l1c_prodlist[which(l1c_prodlist_level == "1C")]
# if no products were found, exit
if (length(l1c_prodlist) == 0) {
print_message(
type = "warning",
"No valid L1C products were found,"
)
return(invisible(NULL))
}
# Check kill_errored
if (all(kill_errored == TRUE, Sys.info()["sysname"] == "Windows")) {
print_message(
type = "warning",
"'kill_errored' is available only for Unix systems; skipping it."
)
kill_errored <- FALSE
}
if (all(kill_errored == TRUE, !requireNamespace("tools", quietly = TRUE))) {
print_message(
type = "warning",
"'kill_errored' can be used only if package \"tools\" is installed; ",
"skipping it."
)
kill_errored <- FALSE
}
if (all(kill_errored == TRUE, timeout > 0)) {
print_message(
type = "warning",
"'timeout' and 'kill_errored' conflict; 'kill_errored' will be ignored."
)
kill_errored <- FALSE
}
## Cycle on each file
# if parallel==TRUE, use doParallel
n_cores <- if (is.numeric(parallel)) {
min(as.integer(parallel), length(l1c_prodlist))
} else if (parallel==FALSE) {
1
} else {
min(parallel::detectCores()-1, length(l1c_prodlist), 8) # use at most 8 cores
}
# n_cores <- 1
# if (parallel==FALSE | Sys.info()["sysname"] == "Windows" | n_cores<=1) {
if (parallel==FALSE | n_cores<=1) {
`%DO%` <- `%do%`
parallel <- FALSE
n_cores <- 1
} else {
`%DO%` <- `%dopar%`
cl <- makeCluster(n_cores)
registerDoParallel(cl)
}
# cycle on eacjh product
l2a_prodlist <- foreach(
i=seq_along(l1c_prodlist),
.combine=c,
.export = "mountpoint",
.packages='sen2r'
) %DO% {
# l2a_prodlist <- c()
# for (i in seq_along(l1c_prodlist)) {
# redirect to log files
if (n_cores > 1) { # nocov start
if (!is.na(.log_output)) {
sink(.log_output, split = TRUE, type = "output", append = TRUE)
}
if (!is.na(.log_message)) {
logfile_message = file(.log_message, open = "a")
sink(logfile_message, type="message")
}
} # nocov end
# set paths
sel_l1c <- file.path(
normalize_path(dirname(l1c_prodlist[i])),
basename(normalizePath(l1c_prodlist[i]))
)
sel_l2a <- file.path(
if (is.null(outdir)) {dirname(sel_l1c)} else {normalize_path(outdir)},
gsub(
"_MSIL1C\\_", "_MSIL2A_",
gsub("_OPER_", "_USER_", basename(sel_l1c))
)
) # path of the L2A product where it should be placed definitively
# Check if a "comparable" file already excists
# (in case using Sen2cor 2.8.0, this is necessary in order not to
# re-correct L1C products every time)
sel_l2a_regex <- gsub(
"^S(2[AB])\\_MSIL([12][AC])\\_([0-9]{8}T[0-9]{6})\\_N([0-9]{4})\\_R([0-9]{3})\\_T([A-Z0-9]{5})\\_([0-9]{8}T[0-9]{6})\\.SAFE$",
"^S\\1\\\\_MSIL\\2\\\\_\\3\\\\_N[0-9]{4}\\\\_R\\5\\\\_T\\6\\\\_[0-9]{8}T[0-9]{6}\\\\.SAFE$",
basename(sel_l2a)
)
sel_l2a_exi <- list.files(dirname(sel_l2a), sel_l2a_regex, full.names = TRUE)
# exclude invalid SAFEs
sel_l2a_exi <- sel_l2a_exi[safe_isvalid(sel_l2a_exi)]
sel_l2a_meta <- safe_getMetadata(
sel_l2a_exi,
info = c("creation_datetime", "id_baseline", "tiles")
)
if (nrow(sel_l2a_meta) > 0) {
# order by ingestion date (recent first) and baseline (locally corrected and newer first)
sel_l2a_meta <- sel_l2a_meta[
order(-creation_datetime, -id_baseline),
path := file.path(dirname(sel_l2a), name)
]
# replace the defined sel_l2a with the actual safe to be used
sel_l2a <- sel_l2a_meta[1,path]
}
## Set the tiles vectors (existing, required, ...)
# existing L1C tiles within input product
sel_l1c_tiles_existing <- safe_getMetadata(
list.files(file.path(sel_l1c,"GRANULE")),
allow_oldnames = TRUE,
"id_tile", format = "vector", simplify = TRUE
)
# L2A tiles already existing
sel_l2a_tiles_existing <- safe_getMetadata(
list.files(file.path(sel_l2a,"GRANULE")),
allow_oldnames = TRUE,
"id_tile", format = "vector", simplify = TRUE
)
# L2A tiles required as output (already existing or not)
sel_l2a_tiles_required <- if (length(tiles)==0) {
sel_l1c_tiles_existing
} else {
sel_l1c_tiles_existing[sel_l1c_tiles_existing %in% tiles]
}
# L2A tiles to be corrected (= required and not yet existing)
sel_l2a_tiles_tocorrect <- sel_l2a_tiles_required[
!sel_l2a_tiles_required %in% sel_l2a_tiles_existing
]
# L1C tiles not required to be corrected
sel_l1c_tiles_unnecessary <- sel_l1c_tiles_existing[
!sel_l1c_tiles_existing %in% sel_l2a_tiles_tocorrect
]
# L1C tiles to be manually excluded before applying sen2cor in order
# not to process them (= tiles present within input L1C product
# and not already esisting as L2A, because in this case sen2cor would
# automatically skip them)
sel_l1c_tiles_toavoid <- sel_l1c_tiles_unnecessary[
!sel_l1c_tiles_unnecessary %in% sel_l2a_tiles_existing
]
## Continue only if not all the existing tiles have already been corrected
## (or if the user chosed to overwrite)
if (overwrite==TRUE | length(sel_l2a_tiles_tocorrect)>0) {
# if output exists (and overwrite==TRUE), delete it
if (overwrite==TRUE) {
unlink(sel_l2a, recursive = TRUE)
}
# set a temporary proc_dir in the following three cases:
# 1) if some L1C tiles need to be manually excluded;
# 2) if dirname(sel_l1c)!=dirname(sel_l2a) & input L1C was already partially corrected
# (to avoid the risk to process more tiles than required);
# 3) if sel_l1c is on a SAMBA mountpoint (because sen2cor does not process
# correctly in this case)
sel_proc_dir <- proc_dir
if (is.na(proc_dir)) {
if (
length(sel_l1c_tiles_toavoid)>0 | # 1
dirname(sel_l1c)!=dirname(sel_l2a) & length(sel_l2a_tiles_existing)>0 | # 2
Sys.info()["sysname"] != "Windows" & any(attr(suppressWarnings(mountpoint(sel_l1c)), "protocol") %in% c("cifs", "nsfs")) # 3
) {
if (is.na(tmpdir)) {
tmpdir <- tempfile(pattern="sen2cor_")
} else if (dir.exists(tmpdir)) {
tmpdir <- file.path(tmpdir, basename(tempfile(pattern="sen2cor_")))
}
dir.create(tmpdir, recursive=FALSE, showWarnings=FALSE)
# check that tmpdir is not on a mountpoint
if (Sys.info()["sysname"] != "Windows") {
if (any(attr(mountpoint(tmpdir), "protocol") %in% c("cifs", "nsfs"))) {
print_message(
type = "error",
tmpdir, "is on a SAMBA mounted unit, so it can not be used."
)
}
}
sel_proc_dir <- tmpdir
}
}
# if proc_dir is [manually or automatically] set, copy sel_l1c
if (!is.na(sel_proc_dir)) {
use_tmpdir <- TRUE
dir.create(sel_proc_dir, recursive=FALSE, showWarnings=FALSE)
if (length(sel_l1c_tiles_unnecessary)>0) {
# if some tiles is unnecessary, copy only necessary files
dir.create(file.path(sel_proc_dir,basename(sel_l1c)))
sel_l1c_files <- list.files(sel_l1c, recursive=FALSE, full.names=FALSE)
file.copy(
file.path(sel_l1c,sel_l1c_files[!grepl("^GRANULE$",sel_l1c_files)]),
file.path(sel_proc_dir,basename(sel_l1c)),
recursive = TRUE
)
dir.create(file.path(sel_proc_dir,basename(sel_l1c),"GRANULE"))
file.copy(
file.path(sel_l1c,"GRANULE",names(sel_l2a_tiles_tocorrect)),
file.path(sel_proc_dir,basename(sel_l1c),"GRANULE"),
recursive = TRUE
)
} else {
# else, copy the whole product
file.copy(sel_l1c, sel_proc_dir, recursive=TRUE)
}
sel_l1c <- file.path(sel_proc_dir, basename(sel_l1c))
} else {
use_tmpdir <- FALSE
}
# path of the L2A product where it is placed by sen2cor
sen2cor_out_l2a <- file.path(
dirname(sel_l1c),
gsub(
"_MSIL1C\\_", "_MSIL2A_",
gsub("_OPER_", "_USER_", basename(sel_l1c))
)
)
# command arguments
sen2cor_args <- c(
"--GIP_L2A", gipp_curr_path,
if (sen2cor_version>=package_version("2.8.0")) {c("--output_dir", sen2cor_out_l2a)},
if (sen2cor_version<package_version("2.8.0")) {"--refresh"},
sel_l1c
)
# apply sen2cor
trace_paths <- if (use_tmpdir) {
c(sel_l1c, sen2cor_out_l2a)
} else if (overwrite==TRUE | !file.exists(sen2cor_out_l2a)) {
sen2cor_out_l2a
} else {
file.path(sen2cor_out_l2a,"GRANULE",names(sel_l2a_tiles_tocorrect))
}
sel_trace <- start_trace(trace_paths, "sen2cor")
sel_sen2cor_timestart <- Sys.time()
print_message(
type = "message", date = TRUE,
"Launching Sen2Cor on ",basename(sel_l1c)," ",
"(",i,"/",length(l1c_prodlist),")..."
)
# Launch with exec_background() or system(),
# depending on the presence of package "sys"
if (requireNamespace("sys", quietly = TRUE)) {
sel_sen2cor_log_output <- if (!is.na(.log_output)) {
tempfile(pattern = "sen2cor_log_output_", fileext = ".txt")
} else {NA}
sel_sen2cor_log_message <- if (!is.na(.log_message)) {
tempfile(pattern = "sen2cor_log_message_", fileext = ".txt")
} else {NA}
if (kill_errored == TRUE) {
sel_sen2cor_pid_1 <- sys::exec_background(
binpaths$sen2cor,
args = sen2cor_args,
std_out = sel_sen2cor_log_output,
std_err = sel_sen2cor_log_message
)
} else {
sys::exec_wait(
binpaths$sen2cor,
args = sen2cor_args,
timeout = timeout,
std_out = sel_sen2cor_log_output,
std_err = sel_sen2cor_log_message
)
}
} else {
if (kill_errored == TRUE) {
system(
paste(binpaths$sen2cor, paste(sen2cor_args, collapse = " ")),
intern = FALSE,
wait = FALSE
)
} else {
system(
paste(binpaths$sen2cor, paste(sen2cor_args, collapse = " ")),
intern = Sys.info()["sysname"] == "Windows",
timeout = timeout
)
}
}
if (kill_errored == TRUE) {
Sys.sleep(10) # wait until all processes started
# Retrieve the correct PID
# (sel_sen2cor_pid_1 is the PID of L2A_Process bash script,
# while we need the PID of the second launched python utility)
psaux_raw0 <- system(paste0("ps aux"), intern = TRUE)
psaux_raw1 <- psaux_raw0[grepl(sel_l1c, psaux_raw0)]
psaux_raw2 <- strsplit(psaux_raw1, " +")
sel_sen2cor_pids <- sapply(psaux_raw2, function(x){x[[2]]})
sel_sen2cor_pid_2 <- if (length(sel_sen2cor_pids) > 0) {
max(sel_sen2cor_pids)
# psaux_raw2[[which(selmax(sel_sen2cor_pids)_sen2cor_pids==max(sel_sen2cor_pids))]][2]
} else {
1E9 # dummy nonexistent PID
}
# check every 10 seconds
while (tools::pskill(sel_sen2cor_pid_2, 0)) {
# check sel_sen2cor_pid_2 to be running (CPU > 0)
sel_sen2cor_cpu <- 0
n_check_cpu <- 0
while (sel_sen2cor_cpu == 0) {
if (n_check_cpu <= 5) { # maximum 5 consecutive checks
Sys.sleep(10)
n_check_cpu <- n_check_cpu + 1
psaux_raw3 <- system(paste0("ps aux"), intern = TRUE)
psaux_raw4 <- psaux_raw3[grepl(sel_l1c, psaux_raw3)]
psaux_raw5 <- strsplit(psaux_raw4, " +")
sel_sen2cor_cpu <- if (
length(psaux_raw5) > 0 &&
sum(sapply(psaux_raw5, function(x){x[2]==sel_sen2cor_pid_2})) > 0
) {
as.integer(psaux_raw5[[
which(sapply(psaux_raw5, function(x){x[2]==sel_sen2cor_pid_2}))
]][3])
} else {
0
}
} else {
print_message(
type = "message", date = TRUE,
"Sen2Cor on ", basename(sel_l1c)," was killed after ",
format(Sys.time() - sel_sen2cor_timestart, digits = 3)," ",
"because inactive."
)
tools::pskill(sel_sen2cor_pids)
sel_sen2cor_cpu <- 1E6 # to exit from WHILE cycle
}
}
}
tools::pskill(sel_sen2cor_pids)
}
print_message(
type = "message", date = TRUE,
"Running Sen2Cor on ", basename(sel_l1c)," finished after ",
format(Sys.time() - sel_sen2cor_timestart, digits = 3),"."
)
if (all(exists("sel_sen2cor_log_output"), !is.na(.log_output))) {
print(print_message(
type = "string", date = TRUE,
"Sen2Cor log on ", basename(sel_l1c),":"
))
for (x in readLines(sel_sen2cor_log_output)) {print(x)}
}
if (all(exists("sel_sen2cor_log_message"), !is.na(.log_message))) {
print_message(
type = "message", date = TRUE,
"Some errors occurred running Sen2Cor on ", basename(sel_l1c),":"
)
for (x in readLines(sel_sen2cor_log_message)) {message(x)}
}
if (TRUE) { # TODO define a way to check if sen2cor ran correctly
end_trace(sel_trace)
} else {
clean_trace(sel_trace)
}
# correct sen2cor_out_l2a
# (starting from v. 2.8.0, L2A baseline and creation date are different from L1C ones)
if (sen2cor_version>=package_version("2.8.0")) {
sen2cor_out_l2a_basename <- list.files(sen2cor_out_l2a, "\\.SAFE$")
file.rename(
file.path(sen2cor_out_l2a,sen2cor_out_l2a_basename),
file.path(dirname(sen2cor_out_l2a),sen2cor_out_l2a_basename)
)
unlink(sen2cor_out_l2a, recursive = TRUE)
sen2cor_out_l2a <- file.path(dirname(sen2cor_out_l2a),sen2cor_out_l2a_basename)
sel_l2a <- file.path(dirname(sel_l2a), sen2cor_out_l2a_basename)
}
# move output to the required output directory
if (use_tmpdir | dirname(sen2cor_out_l2a)!=dirname(sel_l2a)) {
file.copy(sen2cor_out_l2a, dirname(sel_l2a), recursive=TRUE, overwrite=TRUE)
if (rmtmp == TRUE) {
unlink(sen2cor_out_l2a, recursive=TRUE)
}
}
if (use_tmpdir & rmtmp == TRUE) {
unlink(sel_l1c, recursive=TRUE)
}
} # end IF cycle on overwrite
# stop sinking
if (n_cores > 1) { # nocov start
if (!is.na(.log_output)) {
sink(type = "output")
}
if (!is.na(.log_message)) {
sink(type = "message"); close(logfile_message)
}
} # nocov end
sel_l2a
# l2a_prodlist <- c(l2a_prodlist, sel_l2a)
} # end FOREACH cycle on each product
if (n_cores > 1) { # nocov start
stopCluster(cl)
} # nocov end
# Remove temporary directory
if (rmtmp == TRUE & !is.na(tmpdir)) {
unlink(tmpdir, recursive=TRUE)
}
return(l2a_prodlist)
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.