extract_layer <- function(layers) {
if (list %in% is(layers)) {
layers <- lapply(layers, function (slayer) {
layer = strsplit(slayer, "-")[[1]]
if (length(layer) != 4) {
stop(paste("Layer", slayer, "must be in the format '<PRODUCT CODE>-<VARIABLE>-<SPATIAL BUFFER>-<TEMPORAL BUFFER>'"))
}
list(
product = layer[1], variable = layer[2], spatial = as.numeric(layer[3]), temporal = as.numeric(layer[4])
)
})
}
layers
}
# modis_lst_day_1000_1 -> modis-lst_day-1000-1
# landsat8_evi_30_16 -> landsat8-evi-30-16
internal_to_code <- function (code) {
b = strsplit(code, '_')
sapply(b, function(a){
n <- length(a)
if (n == 4) {
return(paste0(a, collapse = '-'))
} else {
return(paste0(c(a[1], # product
paste(a[2:3], collapse = '_'), # variable
a[4], a[5]), collapse = '-'))
}
})
}
download_annotation_err <- function (e) {
if(!curl::has_internet()) {
message('The rstoat package requires an internet connection, please connect to the internet.')
return(NULL)
} else {
message('File not found. Please check that your annotation has completed.')
return(NULL)
}
}
#' @title Start batch annotation
#'
#' @description Submit a dataset previously uploaded on mol.org for annotation.
#' To upload a dataset please visit https://mol.org/upload/
#' Requires login, please run mol_login(<email_address>)
#'
#' @param dataset_id The id of the dataset. List your datasets with the my_datasets() function.
#' @param title The title of the annotation job.
#' @param layers A list of parameters or vector of codes, of the layers, see the examples below.
#'
#' @return No return value, check my_jobs() to confirm successful job submission.
#' @export
#'
#' @examples
#' \dontrun{
#' start_annotation_batch('<dataset_id>', 'My annotation task', layers = list(
#' list(product = "chelsa", variable = "precip", spatial = 1000, temporal = 30)
#' ))
#' # alternatively supplying the code is fine.
#' start_annotation_batch('<datset_id>',
#' 'My 2nd annotation task', layers = c("modis-ndvi-1000-1", "modis-lst_day-1000-1"))
#' }
start_annotation_batch <- function (dataset_id, title, layers) {
body <- list(
dataset_id = dataset_id,
title = title,
layers = extract_layer(layers)
)
resp <- post_json('user','annotate','dataset', body = body)
message(paste(resp$status, resp$message))
}
#' @title Start simple annotation
#'
#' @description Submit a dataframe for on-the-fly annotation.
#' Does not require login - for use for small numbers of records and pilot jobs.
#'
#' You can annotate using layers that are on earthengine!
#' Layer parameters:
#' Used by all:
#' \itemize{
#' \item spatial: The spatial buffer in meters.
#' \item temporal: The temporal buffer in days.
#' }
#'
#' For STOAT layers:
#' \itemize{
#' \item product: The product e.g "srtm", or "landsat8".
#' \item variable; The vairable, e.g "elevation", or "evi".
#' }
#'
#' For non STOAT layers, ie those in Google Earth Engine:
#' \itemize{
#' \item id: The id of the image in Google Earth Engine, mapped to "product" in the output.
#' \item static: Whether to load the imagery as an ImageCollection or as an Image.
#' \item bands: A list, wiht one element, which is used to specify which band of the imagery to use for the annotation, mapped to "variable" in the output.
#' \item reducers: A list containing one or more of the following: \itemize{
#' \item mean
#' \item lcv_count
#' \item mode
#' \item median
#' \item stdev
#' \item min
#' \item max
#' \item stdev
#' } If only one is provided the output will be assigned to "value", else the output will be named the same as the reducer.
#'
#' }
#'
#'
#' @param events A data.frame for on the fly annotation
#' @param layers A list of parameters or vector of codes, of the layers, see the examples below.
#' @param coords A vector of length 2 containing column names for record longitudes, and latitudes.
#' @param date Column name for record dates, dates must take the format YYYY-MM-DD
#'
#'
#' @return Input data.frame with values from the annotation appended, in addition to unique identifier field event_id.
#' \itemize{
#' \item event_id: A unique identifier for each occurrence
#' \item product: Product used for annotation
#' \item variable: Variable used for annotation
#' \item s_buff: Spatial buffer in meters applied to occurrence
#' \item t_buff: Temporal buffer in days applied to occurrence
#' \item value: Annotated value of occurrence from requested layer (mean within buffer),
#' if there is only one reducer (default), then this value will be here.
#' \item stdev: Standard deviation of values within buffer
#' \item valid_pixel_count: Number of pixels within buffered area'
#' }
#' @export
#'
#' @examples
#' \dontrun{
#' events <- data.frame(
#' event_id = as.character(1:2),
#' lng = c(-4, 24),
#' lat = c(10, 10),
#' date = '2015-01-01'
#' )
#'
#' # simple layer string format: PRODUCT-VARIABLE-S_BUFF-T_BUFF
#' layers <- 'landsat8-evi-100-16'
#' start_annotation_simple(events, layers)
#'
#' start_annotation_simple(events, layers)
#'
#' # For lcv_count (Count of landcover value), 'value' returned is a string of
#' # landcover counts within the AOI.
#' # The output format is:
#' # <LANDCOVER_CLASS>:<COUNT_OF_PIXELS_WITH_THAT_CLASS>
#' # classes are seperated by commas.
#'
#' start_annotation_simple(events, list(
#' list(
#' id="COPERNICUS/Landcover/100m/Proba-V-C3/Global",
#' s_buff=1000,
#' reducers=list("lcv_count", "mode"),
#' static=FALSE,
#' t_buff=365,
#' bands=list("discrete_classification")
#' )
#' ))
#'
#' # Annotating with two worldclim layers:
#' # bio01 is annual mean temperature
#' # bio12 is annual precipitation
#'
#' start_annotation_simple(events, list(
#' list(
#' "id"= "WORLDCLIM/V1/BIO",
#' "s_buff"=1000,
#' "reducers"=list("mean"),
#' "static"= TRUE,
#' "t_buff"= 1,
#' "bands"=list("bio01")
#' ),
#' list(
#' "id"= "WORLDCLIM/V1/BIO",
#' "s_buff"=1000,
#' "reducers"=list("mean"),
#' "static"= TRUE,
#' "t_buff"= 1,
#' "bands"=list("bio12")
#' )
#' ))
#' }
start_annotation_simple <- function (events, layers, coords=c('lng','lat'), date='date') {
if (any(!(c(coords[1], coords[2], date) %in% names(events)))) {
stop("Dataframe must contain either columns 'lng', 'lat', 'date', or user must provide arguments for alternate column names")}
events_subset <- data.frame(event_id = events$event_id,
lng = events[[coords[1]]],
lat = events[[coords[2]]],
date = events[[date]],
stringsAsFactors = F)
body <- list(
events = events_subset,
params = extract_layer(layers)
)
resp <- post_json('annotate/ondemand', body = body, otf=T, authenticate=FALSE)
resp <- merge(events, resp, by = 'event_id')
resp
}
#' @title Download annotation results
#'
#' @description Download results of a successfully completed batch annotation.
#' Requires login, please run mol_login(<email_address>)
#' Uses the output from my_jobs() for the annotation id.
#'
#' @param annotation_id The id of the annotation
#' @param dir The directory where to write the annotation.
#' @return The path of the downloaded annotation.
#' @export
#'
#' @examples
#' \dontrun{
#' download_annotation(<annotation_id>, <dir>)
#' }
download_annotation <- function (annotation_id, dir = 'annotation_results') {
url <- build_url('user/annotations', annotation_id, 'download')
annotation_url <- get_resp({
httr::GET(url, get_auth_header(), ua())
})
if (is.null(annotation_url)) return(NULL)
annotation_url <- annotation_url$url
tmp_path <- tempfile()
download_path <- paste0(dir, '/', annotation_id)
if (!dir.exists(dir)) {
dir.create(dir)
message(paste0('Created directory: ', dir))
}
if (dir.exists(download_path)) {
message('Annotation already downloaded.')
return(NULL)
}
status <- NULL
# catch file not found error
tryCatch ({
status <- utils::download.file(annotation_url, tmp_path, mode='wb')
}, error = download_annotation_err, warning = download_annotation_err
)
if (!is.null(status)) {
if (status == 0){
message('Unzipping')
utils::unzip(tmp_path, exdir=download_path)
message(paste0('Annotation available at: ', download_path))
return(download_path)
} else {
message('There was an error downloading your annotation')
return(NULL)
}
}
else {
message('There was an error downloading your annotation')
}
}
#' @title Read annotation output into R
#' @description Convenience function which reads and joins annotation results spread across multiple files for space efficiency.
#' To run this function, please first download annotated data first using download_annotation()
#'
#' @param directory The path of the data.
#' @param drop_event_id Whether to drop the event_id column or not.
#'
#' @return A data.frame of annotated data, one row per variable per event
#' @export
#'
#' @examples
#' \dontrun{
#' read_output("path/to/your/downloaded/data/directory")
#' }
read_output <- function (directory, drop_event_id = TRUE) {
files <- list.files(directory, full.names = T)
species_file <- files[grepl('species.csv$', files)]
event_file <- files[grepl('events.csv$', files)]
events <- utils::read.csv(event_file)
events$event_id <- as.character(events$event_id)
species <- utils::read.csv(species_file)
species$event_id <- as.character(species$event_id)
if (!requireNamespace("dplyr", quietly = TRUE)) {
print("Merging using base R. Install package \"dplyr\" for faster merges")
out <- merge(species, events, by="event_id")
}
else {
out <- dplyr::left_join(species, events, by="event_id")
}
output_files <- files[grepl('results.csv$', files)]
for (output_file in output_files) {
current_file <- utils::read.csv(output_file)
current_file$event_id <- as.character(current_file$event_id)
current_name <- basename(output_file)
current_name <- sub("_results.csv", "", current_name)
# create a new column in 'out' named after each output file
current_file[current_name] <- current_file$value
current_file <- current_file[, c("event_id", current_name)]
if (!requireNamespace("dplyr", quietly = TRUE)) {
out <- merge(out, current_file, by="event_id")
}
else {
out <- dplyr::left_join(out, current_file, by="event_id")
}
}
if (drop_event_id) out$event_id <- NULL
out
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.