# read me --------------------------------------------------------------- # See examples at the end # libraries --------------------------------------------------------------- library(tidyverse) library(janitor) library(rebird) library(sf) library(mapsapi) # latest taxonomy --------------------------------------------------------------- taxonomy <- ebirdtaxonomy() # get up-to-date eBird taxonomy # functions --------------------------------------------------------------- # convert time in seconds to days:hours:minutes to.days.h.min.single <- function(seconds){ if(is.na(seconds)){return(NA)} days <- floor(seconds/3600/24) h <- floor((seconds-days*3600*24)/3600) min <- round((seconds-days*3600*24-3600*h)/60) h <- as.character(h) min <- as.character(min) if(days > 0 && nchar(h)==1){h <- paste0("0",h)} if(nchar(min)==1){min <- paste0("0",min)} if(days > 0){ return(paste(days,h,min,sep=":")) } else{ return(paste(h,min,sep=":")) } } # vectorised version of above to.days.h.min <- function(seconds_vector){ sapply(seconds_vector, to.days.h.min.single) } # get coordinates for a particular address using the Google Maps API address.to.lat.lng <- function(address){ coords <- mp_geocode( addresses = address, key = Sys.getenv("MAPS_API_KEY"), quiet = TRUE ) |> mp_get_points() |> st_coordinates() return(data.frame(lng=coords[,1],lat=coords[,2]))} # add travel times to a list of possible species # required for the functions below add_travel_times <- function(location, possibles, mode){ list_length <- nrow(possibles) time <- rep(NA, list_length) for(i in 0:floor(list_length/25)){ time[(1+25*i):min((25+25*i),list_length)] <- mp_matrix( origins = location, destinations = as.matrix(possibles[(1+25*i):min((25+25*i),list_length),c("lng","lat")]), mode = mode, key = Sys.getenv("MAPS_API_KEY"), quiet = TRUE ) |> mp_get_matrix(value = "duration_s") } output <- possibles output$travel_time <- time output <- select(output, date=obsDt, common_name=comName, sci_name=sciName, travel_time, location=locName, lat, lng, how_many=howMany) return(output) } # Generate list of notable species for a given location # optionally, exclude species already on your life list (specify an eBird life list to the argument 'life_list') # optionally, show only species within a given travel time of the specifed location (argument 'time') nearby_notables <- function(location, life_list = NULL, time = NULL, mode = "driving"){ location_lat_lng <- address.to.lat.lng(location) possibles <- ebirdnotable(lat = location_lat_lng$lat, lng = location_lat_lng$lng) |> filter(!grepl("hybrid|Domestic| sp\\.|\\/", comName)) possibles$comName <- str_extract(possibles$comName, "[[:alpha:][:space:]'-]+") |> trimws() if(!is.null(life_list)){ possibles <- subset(possibles, !comName %in% life_list$Common) } possibles <- add_travel_times(location, possibles, mode) if(!is.null(time)){ possibles <- subset(possibles, travel_time < time*60)} possibles$travel_time <- to.days.h.min(possibles$travel_time) return(possibles) } # convert eBird common names to species codes common2code <- function(data_frame){ inner_join(data_frame, taxonomy, by = "comName")$speciesCode.x } # generate a list of potential lifers in a given eBird region or regions # optionally, indicate travel times from a given address (argument 'location') # optionally, only show only species within a given travel time of the specifed location (argument 'time') region_lifers <- function(region_list, life_list, location = NULL, time = NULL, mode = "driving"){ potentials <- do.call(bind_rows, lapply(region_list, ebirdregion)) |> filter(!grepl("hybrid|Domestic| sp\\.|\\/", comName)) |> filter(!comName %in% life_list$Common) potentials$comName <- str_extract(potentials$comName, "[[:alpha:][:space:]'-]+") |> trimws() args <- expand.grid(region=region_list, species=common2code(potentials)) possibles <- do.call(bind_rows, mapply(ebirdregion, loc = args$region, species=args$species, SIMPLIFY = F)) if(!is.null(location)){ possibles <- add_travel_times(location, possibles, mode) if(!is.null(time)){ possibles <- subset(possibles, travel_time < time*60) } possibles$travel_time <- to.days.h.min(possibles$travel_time) } else { possibles <- select(possibles, date=obsDt, common_name=comName, sci_name=sciName, location=locName, lat, lng, how_many=howMany) } return(possibles) } # examples --------------------------------------------------------------- # Location can be entered in any format that Google Maps can make sense of my_location <- "Cape Tribulation" # List of region codes using the eBird format my_region_list <- c("AU-QLD-CAI") # Import eBird life list (optional) my_life_list <- read.csv(file = "ebird_world_life_list.csv") # Notable species near a given location notables <- nearby_notables(location = my_location, mode = "driving") # mode can be "driving", "walking", "bicycling" or "transit View(notables) # Notable species near a given location, excluding birds from your life list notables <- nearby_notables(location = my_location, life_list = my_life_list, mode = "driving") View(notables) # Notable species within a specified travel time of a given location notables <- nearby_notables(location = my_location, time = 180, # time in minutes mode = "driving") View(notables) # Potential lifers (notable or not) in a given eBird region: lifers <- region_lifers(region_list = my_region_list, life_list = my_life_list) # This function can be slow as it queries each potential lifer individually to the eBird API View(lifers) # Potential lifers in a given eBird region along with travel times to a given address: lifers <- region_lifers(region_list = my_region_list, life_list = my_life_list, location = my_location, mode = "driving") View(lifers) # Potential lifers in a given eBird region that are within a specified travel time of a given address: lifers <- region_lifers(region_list = my_region_list, life_list = my_life_list, location = my_location, time = 180, mode = "driving") View(lifers)