From df53f800403dcf0c8d97fe67bfeae2a1ae2dbc42 Mon Sep 17 00:00:00 2001 From: William Gearty Date: Wed, 20 Dec 2023 07:45:42 -0500 Subject: [PATCH] Resolve taxonomic names from external APIs to PhyloPic (#97) * Start resolve_phylopic * Implement taxonomic hierarchy for some APIs * Implement taxonomic hierarchy for some other APIs * Get names from APIs; add tests and more docs * Add function to pkgdown index * Make error message more useful; add test * Lint fixes * Update docs; shorten example * Apply suggestions from code review Co-authored-by: Lewis A. Jones <41071747+LewisAJones@users.noreply.github.com> * Fix some docs; allow shortened api names * hack for the pbdb * Fix api abbreviation support * Fix EOL subspecies issue * Pull out some shared code * Pull out some more shared code * Fix docs * Test api abbreviations --------- Co-authored-by: Lewis A. Jones <41071747+LewisAJones@users.noreply.github.com> --- NAMESPACE | 4 + NEWS.md | 2 + R/get_uuid.R | 2 +- R/pick_phylopic.R | 3 +- R/resolve_phylopic.R | 227 +++++++++++++++++++++++++ R/zzz.r | 16 +- _pkgdown.yml | 1 + man/pick_phylopic.Rd | 3 +- man/resolve_phylopic.Rd | 100 +++++++++++ tests/testthat/test-resolve_phylopic.R | 135 +++++++++++++++ 10 files changed, 484 insertions(+), 9 deletions(-) create mode 100644 R/resolve_phylopic.R create mode 100644 man/resolve_phylopic.Rd create mode 100644 tests/testthat/test-resolve_phylopic.R diff --git a/NAMESPACE b/NAMESPACE index 422b9533..0c4e5e0b 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -21,6 +21,7 @@ export(get_uuid) export(phylopic_key_glyph) export(pick_phylopic) export(recolor_phylopic) +export(resolve_phylopic) export(rotate_phylopic) export(save_phylopic) importFrom(curl,nslookup) @@ -65,6 +66,7 @@ importFrom(grid,nullGrob) importFrom(grid,rasterGrob) importFrom(grid,unit) importFrom(httr,GET) +importFrom(httr,POST) importFrom(httr,content) importFrom(jsonlite,fromJSON) importFrom(knitr,combine_words) @@ -75,6 +77,8 @@ importFrom(png,readPNG) importFrom(rsvg,rsvg_png) importFrom(rsvg,rsvg_svg) importFrom(stats,setNames) +importFrom(utils,URLdecode) +importFrom(utils,URLencode) importFrom(utils,browseURL) importFrom(utils,menu) importFrom(utils,packageDescription) diff --git a/NEWS.md b/NEWS.md index 7b3f4675..11e239af 100644 --- a/NEWS.md +++ b/NEWS.md @@ -3,6 +3,8 @@ * updated citation * added warning when specified size is more than 1000 times smaller than the y-axis range (mostly useful for when making maps with coord_sf) (#86) * changed the defaults and behavior of the color and fill argument/aesthetics to better maintain backwards compatibility but also prevent unnecessary outlines (#87) +* added resolve_phylopic (#66) +* pick_phylopic now accepts a list of uuids via the uuid argument (#95) # rphylopic 1.2.2 diff --git a/R/get_uuid.R b/R/get_uuid.R index 67b3349d..9149d9e6 100644 --- a/R/get_uuid.R +++ b/R/get_uuid.R @@ -67,7 +67,7 @@ get_uuid <- function(name = NULL, img = NULL, n = 1, filter = NULL, if (!is.logical(url)) { stop("`url` should be of class logical.") } - # Normalise name ------------------------------------------------------- + # Normalize name ------------------------------------------------------- name <- tolower(name) name <- gsub("_", " ", name) # API call ------------------------------------------------------------- diff --git a/R/pick_phylopic.R b/R/pick_phylopic.R index 90a12843..889c7119 100644 --- a/R/pick_phylopic.R +++ b/R/pick_phylopic.R @@ -16,7 +16,8 @@ utils::globalVariables(c("x", "y", "uuid", "label")) #' of available images, all available uuids will be returned. Defaults to 5. #' Only relevant if `name` supplied. #' @param uuid \code{character}. A vector (or list) of valid PhyloPic -#' silhouette uuids, such as that returned by [get_uuid()]. +#' silhouette uuids, such as that returned by [get_uuid()] or +#' [resolve_phylopic()]. #' @param view \code{numeric}. Number of silhouettes that should be plotted at #' the same time. Defaults to 1. #' @param filter \code{character}. Filter uuid(s) by usage license. Use "by" diff --git a/R/resolve_phylopic.R b/R/resolve_phylopic.R new file mode 100644 index 00000000..27948823 --- /dev/null +++ b/R/resolve_phylopic.R @@ -0,0 +1,227 @@ +#' Use a taxonomic name from another database to get a PhyloPic image UUID +#' +#' This function takes a supplied taxonomic name, queries it via the specified +#' external API, resolves the API's returned taxonomic ID to the PhyloPic +#' taxonomic node database, then retrieves PhyloPic image uuids (or urls) for +#' that node. +#' +#' @param name \code{character}. A full or partial taxonomic name to be queried +#' via the specified `api`. +#' @param api \code{character}. The API in which to query `name`. See Details +#' for the available options. +#' @param hierarchy \code{logical}. Whether the taxonomic hierarchy of `name` +#' should be retrieved from the API and used to get several sets of PhyloPic +#' image uuids (or urls). +#' @param max_ranks \code{numeric}. The maximum number of taxonomic ranks that +#' should be included if `hierarchy` is `TRUE`. +#' @inheritParams get_uuid +#' +#' @return A `list` where each value is `n` (or fewer) PhyloPic image uuids (or +#' urls if `url = TRUE`) and each name is the taxonomic name as matched and +#' reported by the specified `api`. If `hierarchy` is `FALSE`, the list has +#' length 1. If `hierarchy` is `TRUE`, the list has maximum length +#' `max_ranks`. +#' +#' @details If `hierarchy` is `FALSE`, the specified `name` is queried via the +#' specified `api`. The matched id is then resolved to the matching taxonomic +#' node in the PhyloPic database. If `hierarchy` is `TRUE`, the full taxonomic +#' hierarchy for `name` is retrieved from the specified `api`, those taxonomic +#' names are subset to `max_ranks` ranks (starting from the specified `name` +#' and ascending the hierarchy). Then each of those names is resolved to the +#' matching taxonomic node in the PhyloPic database (where possible). In +#' either case, [get_uuid()] is then used to retrieve `n` image UUID(s) for +#' each taxonomic name. +#' +#' Note that while the names of the returned list are the taxonomic names as +#' reported by the specified `api`, the PhyloPic images that are returned are +#' associated with whatever taxonomic node that taxonomic name resolves to in +#' the PhyloPic database. This means that, if `hierarchy` is `TRUE`, the same +#' images may be returned for multiple taxonomic names. Also, if a particular +#' taxonomic name does not resolve to any node in the PhyloPic database, no +#' images will be returned for that name. +#' +#' The following APIs are available for querying (`api`): +#' \itemize{ +#' \item{"eol.org": the \href{https://eol.org/}{Encyclopedia of Life}} +#' (note: `hierarchy = TRUE` is not currently available for this API) ("eol" +#' is also allowed) +#' \item{"gbif.org": the \href{https://www.gbif.org/}{Global Biodiversity +#' Information Facility}} ("gbif" is also allowed) +#' \item{"marinespecies.org": the \href{https://marinespecies.org/}{World +#' Registor of Marine Species}} ("worms" is also allowed) +#' \item{"opentreeoflife.org": the \href{https://tree.opentreeoflife.org/}{ +#' Open Tree of Life}} ("otol" is also allowed) +#' \item{"paleobiodb.org": the \href{https://paleobiodb.org/#/}{Paleobiology +#' Database}} ("pbdb" is also allowed) +#' } +#' +#' @importFrom httr POST +#' @importFrom utils URLencode URLdecode +#' @importFrom stats setNames +#' @export +#' @examples \dontrun{ +#' # get a uuid for a single name +#' resolve_phylopic(name = "Canis lupus") +#' # get uuids for the taxonomic hierarchy +#' resolve_phylopic(name = "Velociraptor mongoliensis", api = "paleobiodb.org", +#' hierarchy = TRUE, max_ranks = 3) +#' } +resolve_phylopic <- function(name, api = "gbif.org", hierarchy = FALSE, + max_ranks = 5, n = 1, filter = NULL, url = FALSE) { + url_arg <- url + # replace api abbreviations + abbrvs <- setNames(c("eol.org", "gbif.org", "marinespecies.org", + "opentreeoflife.org", "paleobiodb.org"), + c("eol", "gbif", "worms", "otol", "pbdb")) + if (api %in% names(abbrvs)) api <- abbrvs[[api]] + # Check arguments ------------------------------------------------------ + api <- match.arg(api, unname(abbrvs)) + if (!is.character(name)) { + stop("`name` should be of class character.") + } + if (!is.character(api)) { + stop("`api` should be of class character.") + } + if (!is.logical(hierarchy)) { + stop("`hierarchy` should be of class logical.") + } + if (!is.numeric(max_ranks)) { + stop("`max_ranks` should be of class numeric.") + } + # Normalize name ------------------------------------------------------- + name <- tolower(name) + name <- gsub("_", " ", name) + name_encode <- URLencode(name) + # Query specified API for the name ------------------------------------- + if (api == "eol.org") { + check_url("https://eol.org/api/search/1.0.json") + namespace <- "pages" + url <- paste0("https://eol.org/api/search/1.0.json?page=1&q=", name_encode) + jsn <- json_GET(url) + # EOL appears to return lots of subspecies, so check if any match `name` + # first, otherwise, return the first result + if (jsn$totalResults == 0) stop("No results returned from the API.") + matches <- which(tolower(jsn$results$title) == name) + ind <- ifelse(any(matches), matches[1], 1) + ids <- jsn$results$id[ind] + name_vec <- jsn$results$title[ind] + if (hierarchy) { + warning("`hierarchy = TRUE` is not currently available for eol.org.") + hierarchy <- FALSE + } + } else if (api == "gbif.org") { + check_url("https://api.gbif.org/v1/") + namespace <- "species" + url <- paste0("https://api.gbif.org/v1/species/suggest?", + "limit=1&q=", name_encode) + jsn <- json_GET(url) + if (length(jsn) == 0) stop("No results returned from the API.") + ids <- jsn$key + name_vec <- jsn$canonicalName + if (hierarchy) { + url <- paste0("https://api.gbif.org/v1/species/match?verbose=true&", + "name=", URLencode(jsn$canonicalName[1])) + jsn <- json_GET(url) + ids <- c(jsn$speciesKey[1], jsn$genusKey[1], jsn$familyKey[1], + jsn$orderKey[1], jsn$classKey[1], jsn$phylumKey[1], + jsn$kingdomKey[1]) + name_vec <- c(jsn$species[1], jsn$genus[1], jsn$family[1], + jsn$order[1], jsn$class[1], jsn$phylum[1], + jsn$kingdom[1]) + } + } else if (api == "marinespecies.org") { + check_url("https://www.marinespecies.org/rest/") + namespace <- "taxname" + url <- paste0("https://www.marinespecies.org/rest/", + "AphiaRecordsByMatchNames?marine_only=false&", + "scientificnames%5B%5D=", name_encode) + jsn <- json_GET(url) + ids <- jsn[[1]]$AphiaID[1] + name_vec <- jsn[[1]]$scientificname[1] + if (hierarchy) { + url <- paste0("https://www.marinespecies.org/rest/", + "AphiaClassificationByAphiaID/", ids) + jsn <- json_GET(url) + lst_sub <- jsn + ids <- character() + name_vec <- character() + while ("child" %in% names(lst_sub)) { + ids <- c(ids, lst_sub$AphiaID) + name_vec <- c(name_vec, lst_sub$scientificname) + lst_sub <- lst_sub$child + } + ids <- rev(ids) + name_vec <- rev(name_vec) + } + } else if (api == "paleobiodb.org") { + check_url("https://paleobiodb.org/data1.2/") + namespace <- "txn" + url <- paste0("https://paleobiodb.org/data1.2/taxa/auto.json?", + "limit=10&name=", name_encode) + jsn <- json_GET(url) + if ("errors" %in% jsn || length(jsn$records) == 0) + stop("No results returned from the API.") + # sometimes returns higher taxonomic ranks first even when there is a + # perfect match, so check if any match `name`, otherwise, return the first + # result + matches <- which(tolower(jsn$records$nam) == name) + ind <- ifelse(any(matches), matches[1], 1) + ids <- jsn$records$oid[ind] + name_vec <- jsn$records$nam[ind] + if (hierarchy) { + url <- paste0("https://paleobiodb.org/data1.2/taxa/list.json?", + "rel=all_parents&", "id=txn:", ids) + jsn <- json_GET(url) + ids <- rev(gsub("txn:", "", jsn$records$oid)) + name_vec <- rev(jsn$records$nam) + } + } else if (api == "opentreeoflife.org") { + check_url("https://api.opentreeoflife.org/") + namespace <- "taxonomy" + url <- "https://api.opentreeoflife.org/v3/tnrs/autocomplete_name" + res <- POST(url = url, encode = "json", body = list("name" = name)) + jsn <- response_to_JSON(res) + if (length(jsn) == 0) stop("No results returned from the API.") + ids <- jsn$ott_id[1] + name_vec <- jsn$unique_name[1] + if (hierarchy) { + url <- "https://api.opentreeoflife.org/v3/taxonomy/taxon_info" + res <- POST(url = url, encode = "json", + body = list("include_lineage" = TRUE, "ott_id" = ids)) + jsn <- response_to_JSON(res) + ids <- c(ids, jsn$lineage$ott_id) + name_vec <- c(name_vec, jsn$lineage$unique_name) + } + } + # subset ids if more than max_ranks + ids <- ids[seq_len(min(length(ids), max_ranks))] + # Resolve to PhyloPic and get images ----------------------------------- + lst <- list() + for (i in seq_along(ids)) { + api_return <- phy_GET(path = paste0("resolve/", api, "/", namespace, "/", + ids[i])) + # catch any errors here + if ("errors" %in% names(api_return)) { + lst[[name_vec[i]]] <- character() + } else { + tax <- api_return$names[[1]]$text[1] + lst[[name_vec[i]]] <- get_uuid(tax, n = n, filter = filter, url = url_arg) + } + } + return(lst) +} + +# check that a particular URL (e.g., for an API) is online +check_url <- function(url) { + headers <- curlGetHeaders(url) + if (attr(headers, "status") != 200) { + stop("API is not available or you have no internet connection.") + } +} + +#' @importFrom httr GET +json_GET <- function(url) { + res <- GET(url = url) + if (length(res$content) == 0) stop("No results returned from the API.") + response_to_JSON(res) +} diff --git a/R/zzz.r b/R/zzz.r index ba9bd584..0f3fa957 100644 --- a/R/zzz.r +++ b/R/zzz.r @@ -13,8 +13,7 @@ as_null <- function(x) if (length(x) == 0) NULL else x pbase <- function() "https://api.phylopic.org" -#' @importFrom jsonlite fromJSON -#' @importFrom httr GET content +#' @importFrom httr GET #' @importFrom curl nslookup phy_GET <- function(path, query = list(), ...) { # Check PhyloPic (or user) is online @@ -26,13 +25,18 @@ phy_GET <- function(path, query = list(), ...) { }) query <- as_null(pc(query)) tt <- GET(url = pbase(), path = path, query = query) - tmp <- content(tt, as = "text", encoding = "UTF-8") - jsn <- fromJSON(tmp) + jsn <- response_to_JSON(tt) if (tt$status == 400) { # need to supply the build argument query[["build"]] <- jsn$build tt <- GET(url = pbase(), path = path, query = query) - tmp <- content(tt, as = "text", encoding = "UTF-8") - jsn <- fromJSON(tmp) + jsn <- response_to_JSON(tt) } jsn } + +#' @importFrom httr content +#' @importFrom jsonlite fromJSON +response_to_JSON <- function(response) { + tmp <- content(response, as = "text", encoding = "UTF-8") + return(fromJSON(tmp)) +} diff --git a/_pkgdown.yml b/_pkgdown.yml index 5e889875..48374c69 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -24,6 +24,7 @@ reference: Functions for identifying and retrieving PhyloPic silhouettes. contents: - get_uuid + - resolve_phylopic - pick_phylopic - get_phylopic - plot_phylopic diff --git a/man/pick_phylopic.Rd b/man/pick_phylopic.Rd index 798f87f7..c99c6774 100644 --- a/man/pick_phylopic.Rd +++ b/man/pick_phylopic.Rd @@ -23,7 +23,8 @@ of available images, all available uuids will be returned. Defaults to 5. Only relevant if \code{name} supplied.} \item{uuid}{\code{character}. A vector (or list) of valid PhyloPic -silhouette uuids, such as that returned by \code{\link[=get_uuid]{get_uuid()}}.} +silhouette uuids, such as that returned by \code{\link[=get_uuid]{get_uuid()}} or +\code{\link[=resolve_phylopic]{resolve_phylopic()}}.} \item{view}{\code{numeric}. Number of silhouettes that should be plotted at the same time. Defaults to 1.} diff --git a/man/resolve_phylopic.Rd b/man/resolve_phylopic.Rd new file mode 100644 index 00000000..6fa52068 --- /dev/null +++ b/man/resolve_phylopic.Rd @@ -0,0 +1,100 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/resolve_phylopic.R +\name{resolve_phylopic} +\alias{resolve_phylopic} +\title{Use a taxonomic name from another database to get a PhyloPic image UUID} +\usage{ +resolve_phylopic( + name, + api = "gbif.org", + hierarchy = FALSE, + max_ranks = 5, + n = 1, + filter = NULL, + url = FALSE +) +} +\arguments{ +\item{name}{\code{character}. A full or partial taxonomic name to be queried +via the specified \code{api}.} + +\item{api}{\code{character}. The API in which to query \code{name}. See Details +for the available options.} + +\item{hierarchy}{\code{logical}. Whether the taxonomic hierarchy of \code{name} +should be retrieved from the API and used to get several sets of PhyloPic +image uuids (or urls).} + +\item{max_ranks}{\code{numeric}. The maximum number of taxonomic ranks that +should be included if \code{hierarchy} is \code{TRUE}.} + +\item{n}{\code{numeric}. How many uuids should be returned? Depending on +the requested \code{name}, multiple silhouettes might exist. If \code{n} exceeds +the number of available images, all available uuids will be returned. +This argument defaults to 1.} + +\item{filter}{\code{character}. Filter uuid(s) by usage license. Use "by" to +limit results to image uuids which do not require attribution, "nc" for +image uuids which allow commercial usage, and "sa" for image uuids without +a ShareAlike clause. The user can also combine these filters as a vector.} + +\item{url}{\code{logical}. If \code{FALSE} (default), only the uuid is +returned. If \code{TRUE}, a valid PhyloPic image url of the uuid is +returned.} +} +\value{ +A \code{list} where each value is \code{n} (or fewer) PhyloPic image uuids (or +urls if \code{url = TRUE}) and each name is the taxonomic name as matched and +reported by the specified \code{api}. If \code{hierarchy} is \code{FALSE}, the list has +length 1. If \code{hierarchy} is \code{TRUE}, the list has maximum length +\code{max_ranks}. +} +\description{ +This function takes a supplied taxonomic name, queries it via the specified +external API, resolves the API's returned taxonomic ID to the PhyloPic +taxonomic node database, then retrieves PhyloPic image uuids (or urls) for +that node. +} +\details{ +If \code{hierarchy} is \code{FALSE}, the specified \code{name} is queried via the +specified \code{api}. The matched id is then resolved to the matching taxonomic +node in the PhyloPic database. If \code{hierarchy} is \code{TRUE}, the full taxonomic +hierarchy for \code{name} is retrieved from the specified \code{api}, those taxonomic +names are subset to \code{max_ranks} ranks (starting from the specified \code{name} +and ascending the hierarchy). Then each of those names is resolved to the +matching taxonomic node in the PhyloPic database (where possible). In +either case, \code{\link[=get_uuid]{get_uuid()}} is then used to retrieve \code{n} image UUID(s) for +each taxonomic name. + +Note that while the names of the returned list are the taxonomic names as +reported by the specified \code{api}, the PhyloPic images that are returned are +associated with whatever taxonomic node that taxonomic name resolves to in +the PhyloPic database. This means that, if \code{hierarchy} is \code{TRUE}, the same +images may be returned for multiple taxonomic names. Also, if a particular +taxonomic name does not resolve to any node in the PhyloPic database, no +images will be returned for that name. + +The following APIs are available for querying (\code{api}): +\itemize{ +\item{"eol.org": the \href{https://eol.org/}{Encyclopedia of Life}} +(note: \code{hierarchy = TRUE} is not currently available for this API) ("eol" +is also allowed) +\item{"gbif.org": the \href{https://www.gbif.org/}{Global Biodiversity +Information Facility}} ("gbif" is also allowed) +\item{"marinespecies.org": the \href{https://marinespecies.org/}{World +Registor of Marine Species}} ("worms" is also allowed) +\item{"opentreeoflife.org": the \href{https://tree.opentreeoflife.org/}{ +Open Tree of Life}} ("otol" is also allowed) +\item{"paleobiodb.org": the \href{https://paleobiodb.org/#/}{Paleobiology +Database}} ("pbdb" is also allowed) +} +} +\examples{ +\dontrun{ +# get a uuid for a single name +resolve_phylopic(name = "Canis lupus") +# get uuids for the taxonomic hierarchy +resolve_phylopic(name = "Velociraptor mongoliensis", api = "paleobiodb.org", + hierarchy = TRUE, max_ranks = 3) +} +} diff --git a/tests/testthat/test-resolve_phylopic.R b/tests/testthat/test-resolve_phylopic.R new file mode 100644 index 00000000..197d98ad --- /dev/null +++ b/tests/testthat/test-resolve_phylopic.R @@ -0,0 +1,135 @@ +test_that("resolve_phylopic works with GBIF", { + skip_if_offline(host = "api.gbif.org") + res <- resolve_phylopic(name = "Acropora cervicornis", api = "gbif.org") + expect_equal(length(res), 1) + expect_true(is.character(names(res))) + expect_true(all(sapply(res, is.character))) + res2 <- resolve_phylopic(name = "Acropora cervicornis", api = "gbif") + expect_equal(res, res2) + res3 <- resolve_phylopic(name = "Acropora cervicornis", api = "g") + expect_equal(res, res3) + + suppressWarnings( + res <- resolve_phylopic(name = "Acropora cervicornis", api = "gbif.org", + n = 5) + ) + expect_equal(length(res), 1) + expect_true(is.character(names(res))) + expect_true(all(sapply(res, is.character))) + + res <- resolve_phylopic(name = "Acropora cervicornis", api = "gbif.org", + hierarchy = TRUE, max_ranks = 5) + expect_equal(length(res), 5) + expect_true(is.character(names(res))) + expect_true(all(sapply(res, is.character))) + + expect_error(resolve_phylopic(name = "fdsafdsafdas;fdsaf", + api = "gbif.org")) + expect_error(resolve_phylopic(name = "Acropora cervicornis", + api = "gbif.org", n = "hi")) + expect_error(resolve_phylopic(name = "Acropora cervicornis", + api = "gbif.org", hierarchy = "hi")) + expect_error(resolve_phylopic(name = "Acropora cervicornis", + api = "gbif.org", max_ranks = "hi")) + expect_error(resolve_phylopic(name = 15, api = "gbif.org")) + expect_error(resolve_phylopic(name = "Acropora cervicornis", api = "hi")) +}) + +test_that("resolve_phylopic works with EOL", { + skip_if_offline(host = "eol.org") + res <- resolve_phylopic(name = "Enhydra lutris", api = "eol.org") + expect_equal(length(res), 1) + expect_true(is.character(names(res))) + expect_true(all(sapply(res, is.character))) + + suppressWarnings( + res <- resolve_phylopic(name = "Enhydra lutris", api = "eol.org", + n = 5) + ) + expect_equal(length(res), 1) + expect_true(is.character(names(res))) + expect_true(all(sapply(res, is.character))) + + expect_warning({ + res <- resolve_phylopic(name = "Enhydra lutris", api = "eol.org", + hierarchy = TRUE, max_ranks = 5) + }) + expect_equal(length(res), 1) + expect_true(is.character(names(res))) + expect_true(all(sapply(res, is.character))) + + expect_error(resolve_phylopic(name = "fdsafdsafdas;fdsaf", + api = "eol.org")) +}) + +test_that("resolve_phylopic works with WoRMS", { + skip_if_offline(host = "marinespecies.org") + res <- resolve_phylopic(name = "Enhydra lutris", api = "marinespecies.org") + expect_equal(length(res), 1) + expect_true(is.character(names(res))) + expect_true(all(sapply(res, is.character))) + + suppressWarnings( + res <- resolve_phylopic(name = "Enhydra lutris", api = "marinespecies.org", + n = 5) + ) + expect_equal(length(res), 1) + expect_true(is.character(names(res))) + expect_true(all(sapply(res, is.character))) + + res <- resolve_phylopic(name = "Enhydra lutris", api = "marinespecies.org", + hierarchy = TRUE, max_ranks = 5) + expect_equal(length(res), 5) + expect_true(is.character(names(res))) + expect_true(all(sapply(res, is.character))) + + expect_error(resolve_phylopic(name = "fdsafdsafdas;fdsaf", + api = "marinespecies.org")) +}) + +test_that("resolve_phylopic works with PBDB", { + skip_if_offline(host = "paleobiodb.org") + res <- resolve_phylopic(name = "Velociraptor mongoliensis", + api = "paleobiodb.org") + expect_equal(length(res), 1) + expect_true(is.character(names(res))) + expect_true(all(sapply(res, is.character))) + + res <- resolve_phylopic(name = "Velociraptor mongoliensis", + api = "paleobiodb.org", n = 5) + expect_equal(length(res), 1) + expect_true(is.character(names(res))) + expect_true(all(sapply(res, is.character))) + + res <- resolve_phylopic(name = "Velociraptor mongoliensis", + api = "paleobiodb.org", hierarchy = TRUE) + expect_equal(length(res), 5) + expect_true(is.character(names(res))) + expect_true(all(sapply(res, is.character))) + + expect_error(resolve_phylopic(name = "fdsafdsafdas;fdsaf", + api = "paleobiodb.org")) +}) + +test_that("resolve_phylopic works with OTOL", { + skip_if_offline(host = "opentreeoflife.org") + res <- resolve_phylopic(name = "Canis lupus", api = "opentreeoflife.org") + expect_equal(length(res), 1) + expect_true(is.character(names(res))) + expect_true(all(sapply(res, is.character))) + + res <- resolve_phylopic(name = "Canis lupus", api = "opentreeoflife.org", + n = 5) + expect_equal(length(res), 1) + expect_true(is.character(names(res))) + expect_true(all(sapply(res, is.character))) + + res <- resolve_phylopic(name = "Canis lupus", api = "opentreeoflife.org", + hierarchy = TRUE, max_ranks = 5) + expect_equal(length(res), 5) + expect_true(is.character(names(res))) + expect_true(all(sapply(res, is.character))) + + expect_error(resolve_phylopic(name = "fdsafdsafdas;fdsaf", + api = "opentreeoflife.org")) +})