R/dive_cycle_summary.R

Defines functions dive_cycle_summary

Documented in dive_cycle_summary

#' Dive-cycle-level acoustic and tag data, for one or more tags
#'
#' Summarize SMRT (and/or Lander2) tag data from .nc files for each foraging dive cycle. There are data at two, nested time-scales: the dive cycle (a foraging dive and all the dives that follow it) and, within that, time-periods of set length in which other metrics are averaged. This function does coarse scale only. The data are intended for use in a state-switching model.
#'
#' @param tag_id Character string or vector with tag IDs (without "-cal.nc"). Default: all SMRT ziphius tags.
#' @param nc_path Directory (quoted string) where .nc files are stored. Can be one string, or a list the same length as tag_ids. Note: to download latest versions from Google drive, try function: \code{\link[FBtagtools]{download_drive_nc}}. Default: current working directory. Note: use "/" and not "\" to avoid headaches.
#' @param ae_path Directory (quoted string) where text files with info about acoustic events are stored. If needed, you can use \code{\link[FBtagtools]{download_drive_acoustic_events}} to get these. Default is the current working directory.
#' @param bathy_path A directory path to the folder containing all bathymetry data. Use \code{\link[FBtagtools]{download_bathy}} if you don't have this data already. If not provided, the bathymetry data will not be included in the output dataset.
#' @param ETOP01_bathy whether to try to fill-in bathymetry info for locations outside NEPAC dataset bounds using NOAA online ETOP01 database. Default is FALSE. This is slow and no animals have gone out-of-bounds yet as of July 2021.
#' @param rl_file name (with path, if needed) of locally-stored .csv file with raw RL data. If not provided, the default is to use \code{\link[FBtagtools]{download_drive_rls}} to obtain it from the FB Google Drive.
#' @param ping_log_file name (with path, if needed) of locally-stored .csv file with raw RL data. If not provided, the default is to use \code{\link[FBtagtools]{download_drive_rls}} to obtain it from the FB Google Drive.
#' @param rl_model_dir directory where files with results of RL modeling (for non-acoustic tags) are stored locally
#' @param dive_class_file name (with path, if needed) of locally-stored .csv file with information on machine-learning classification of dives as foraging or not. This should match perfectly with presence/absence of clicking from acoustic recordings, but is mainly for tags/dives without acoutics.
#' @param acoustic_summary_file optional - if you have already run \code{\link[FBtagtools]{dive_acoustic_summary}} and have saved results as a csv, you can use that file to get results faster. Otherwise, this function will call \code{\link[FBtagtools]{dive_acoustic_summary}} and re-do the dive-level processing.
#' @param email Email address (required for FB Google Drive authentication; optional if `rl_file` is provided). You may also be asked to sign in or verify your Google identity as this function runs.
#' @param save_csv Logical; whether or not to save a csv file with the results. Default: TRUE (and will overwrite any existing file)
#' @param save_tracks Logical; whether or not to save csv file with geo-referenced pseudotrack (in meters northing/easting relative to initial location)
#' @param track_dir Directory to store tracks in (if save_tracks is TRUE). Defaults to working directory.
#' @param csv_name File name (with path, if desired) in which to save results in csv format. Default is dive_acoustic_summary.csv.
#' @return A data.frame() with one row per dive, per whale
#' @export

dive_cycle_summary <- function(tag_id = zc_smrt_tag_list,
                               nc_path = getwd(),
                               ae_path = getwd(),
                               bathy_path,
                               ETOP01_bathy = FALSE,
                               ping_log_file,
                               rl_file,
                               rl_model_dir,
                               dive_class_file,
                               acoustic_summary_file,
                               email,
                               save_csv = TRUE,
                               save_tracks = FALSE,
                               track_dir,
                               csv_name = 'dive_cycle_summary.csv'){
  # notes:

  # - include fluke rate - in line with MJ method. this is @ fine scale.
  # - incorporate RL/signal type info from Navy modeling efforts at both time scales
  # - also incorporate actual measured RLs at both time scales
  # probably want to do coarse and fine scales separately (diff functions?) and then combine @ start of HMM analysis
  # create finescale file for each whale in matlab (for speed) and add RL data in R (for ease of joins)

  # coarse scale variables:
  # dive start, end, max depth, click duration, number of clicks,
  # number of buzzes, buzz duration total, mean, ...
  # number of shallow dives, number of breaths, surface time,
  # nf click info, path/step/turn/tortuosity (GPS and ptrack??)
  # season, lunar cycle, time of day, ?known exposure history

  # short scale variables:
  # median fluke rate, MSA: mean, 90th percentile EVD;
  # rms stroke amplitude, peak stroke amplitude, strokes per second
  # count of buzzes, focal and non?
  # dive wiggliness, median vertical velocity, median horiz velocity,
  # pitch, roll, head variance,
  #
  # Notes: get_br function (and others?) only in matlab, and some datasets are big, so maybe do the coarse part in matlab??
  #


  if (!missing(acoustic_summary_file) && !is.null(acoustic_summary_file)){
    # if possible, read in dive level data from previous run of dive_acoustic_summary()
    das <- readr::read_csv(acoustic_summary_file,
                           guess_max = 2700,
                           show_col_types = FALSE)
  }else{
    # if not provided, compute dive summary data
    das <- dive_acoustic_summary(tag_id = tag_id,
                                 nc_path = nc_path,
                                 ae_path = ae_path,
                                 bathy_path = bathy_path,
                                 ETOP01_bathy = ETOP01_bathy,
                                 rl_file = rl_file,
                                 ping_log_file = ping_log_file,
                                 email = email,
                                 save_csv = FALSE)
  }


  # read in data on foraging dives/not and add to das
  # note: a few fewer dives that in das :(
  # dive_class <- readr::read_csv(dive_class_file,
  #                               guess_max = 2000,
  #                               show_col_types = FALSE) |>
  #   dplyr::mutate(rounded_start = round(Start/10) * 10) |>
  #   dplyr::select(TagID, rounded_start, preds_prob.bottom.tree.Yes, preds_class.bottom.tree) |>
  #   dplyr::rename(tag_id = TagID,
  #                 prob_foraging = preds_prob.bottom.tree.Yes,
  #                 dive_class = preds_class.bottom.tree) |>
  #   dplyr::mutate(dive_class = ifelse(dive_class == 'Yes', 'foraging', 'non-foraging'),
  #                 tag_id = ifelse(tag_id == "Zc-20180331-173187", "Zica-20180331-173187", tag_id))

  dive_class <- readr::read_csv(dive_class_file,
                                guess_max = 2000,
                                show_col_types = FALSE) |>
    dplyr::mutate(rounded_start = round(Start/10) * 10) |>
    dplyr::select(TagID, rounded_start, ForagingModPrediction) |>
    dplyr::rename(tag_id = TagID) |>
    dplyr::mutate(dive_class = ifelse(ForagingModPrediction == 'Yes', 'foraging', 'non-foraging'),
                  tag_id = ifelse(tag_id == "Zc-20180331-173187", "Zica-20180331-173187", tag_id))

  das <- das |>
    dplyr::mutate(rounded_start = round(dive_start_sec/10) * 10)

  das <- dplyr::left_join(das,
                          dive_class,
                          by = c('tag_id', 'rounded_start'))

  # fill in "foraging"/non-foraging for missing IF acoustic data present
  # leave missing if no acoustic data and not classed by ML tree algorithm (about 9 dives)
  das <- das |>
    dplyr::mutate(dive_class = ifelse(is.na(dive_class) & click_dur_sec > 0, 'foraging', dive_class)) |>
    dplyr::mutate(dive_class = ifelse(is.na(dive_class) & click_dur_sec == 0, 'non-foraging', dive_class))

  # aggregate to dive CYCLES instead of just dives
  # give a dive-number to each foraging dive by whale and then fill down to create groups?
  FD <- das |>
    dplyr::arrange(tag_id, dive_start_sec) |>
    dplyr::group_by(tag_id) |>
    dplyr::filter(dive_class == 'foraging') |>
    dplyr::mutate(foraging_dive_id = as.numeric(c(1:dplyr::n()))) |>
    dplyr::select(tag_id, dive_start_sec, foraging_dive_id)

  das <- dplyr::left_join(das, FD, by = c('tag_id', 'dive_start_sec')) |>
    dplyr::group_by(tag_id) |>
    tidyr::fill(foraging_dive_id, .direction = 'down') |>
    dplyr::ungroup()

  # group by foraging dive cycle and compute stuff
  dcs <- das |>
    # get rid of dives that are not part of a foraging dive cycle
    dplyr::filter(!is.na(foraging_dive_id)) |>
    # coarse scale variables:
    # path/step/turn/tortuosity (GPS and ptrack??)

    dplyr::group_by(tag_id, foraging_dive_id) |>
    dplyr::summarise(dive_cycle_start_sec = dplyr::first(dive_start_sec),
                     dive_cycle_end_sec = dplyr::last(next_start),
                     dive_cycle_dur_sec = dive_cycle_end_sec - dive_cycle_start_sec,
                     fd_end_sec = dplyr::first(dive_end_sec),
                     fd_dur_sec = dplyr::first(dive_dur_sec),
                     fd_start_UTC = dplyr::first(start_UTC),
                     fd_max_depth = dplyr::first(max_depth),
                     fd_breath_count_post_dive = dplyr::first(breath_count_post_dive),
                     n_shallow_dives = dplyr::n() - 1,
                     fd_surface_interval = dive_cycle_dur_sec - fd_dur_sec,
                     dive_cycle_surface_sec = sum(next_start - dive_end_sec, na.rm = TRUE),
                     dive_cycle_breath_count = ifelse(all(is.na(breath_count_post_dive)),
                                                      NA,
                                                      sum(breath_count_post_dive, na.rm = TRUE)),
                     fd_bathy = dplyr::first(bathy),
                     fd_bathy_slope = dplyr::first(bathy_slope),
                     fd_bathy_aspect = dplyr::first(bathy_aspect),
                     bathy_source = dplyr::first(bathy_source),
                     bathy_radius_km = dplyr::first(bathy_radius_km),
                     lat_initial = dplyr::first(lat_initial),
                     lon_initial = dplyr::first(lon_initial),
                     lat_final = dplyr::last(lat_final),
                     lon_final = dplyr::last(lon_final),
                     gps_fd_dist_km = dplyr::first(distance_traveled_km),
                     gps_dive_cycle_path_km = sum(distance_traveled_km, na.rm = TRUE),
                     horiz_speed_mean_km_h = gps_dive_cycle_path_km / ((dplyr::last(final_loc_time) - dplyr::first(initial_loc_time)) / 3600),
                     solar_phase_initial = dplyr::first(solar_phase),
                     solar_phase_final = dplyr::last(solar_phase),
                     lunar_phase = oce::moonAngle(t = dplyr::first(start_UTC),
                                                  longitude = dplyr::first(na.omit(lon_initial)),
                                                  latitude = dplyr::first(na.omit(lat_initial)))$illuminatedFraction,
                     quarter_of_year = lubridate::quarter(dplyr::first(start_UTC), fiscal_start = 12),
                     # click variables
                     n_clicks = sum(n_clicks, na.rm = TRUE),
                     across(starts_with("click_"), ~ dplyr::first(.x)),
                     dive_to_click1_sec = dplyr::first(dive_to_click1_sec),
                     across(starts_with("nonfocal_click_"), ~ mean(.x, na.rm = TRUE)),
                     n_nonfocal_clicks = sum(n_nonfocal_clicks, na.rm = TRUE),
                     # buzz variables
                     n_buzzes = sum(n_buzzes, na.rm = TRUE),
                     across(starts_with("buzz_"), ~ dplyr::first(.x))
                     # # RL variables
                     # across(contains('_n_pings'), ~ sum(.x, na.rm = TRUE)),
                     # across(contains('_ping_dur_max'), ~ max(.x, na.rm = TRUE)),
                     # across(contains('_ping_dur_min'), ~ min(.x, na.rm = TRUE)),
                     # across(contains('_bb_rms_min'), ~ min(.x, na.rm = TRUE)),
                     # across(contains('_bb_rms_max'), ~ max(.x, na.rm = TRUE)),
                     # NOTE: should add these after using dive cycle intervals.
    ) |>
    dplyr::ungroup()

  step_turn_data <- moveHMM::prepData(dcs |> dplyr::select(tag_id, dive_cycle_start_sec, lat_initial, lon_initial) |>
                                        data.frame() |>
                                        dplyr::mutate(ID = tag_id),
                                      type = "LL",
                                      coordNames = c("lon_initial", "lat_initial"),
                                      LLangle = TRUE) |>
    dplyr::select(tag_id, dive_cycle_start_sec, step, angle) |>
    data.frame() |>
    dplyr::rename(gps_dive_cycle_net_km = step,
                  turn_angle_rad = angle)

  # join back in
  dcs <- dplyr::left_join(dcs, step_turn_data,
                          by = c('tag_id', 'dive_cycle_start_sec'))

  if ('data.frame' %in% class(tag_id)){
    tag_id <- tag_id[,'tag_id']
  }

  # read in all RL data for all signals, for all these tags
  all_rls <- extract_rls(rl_file = rl_file,
                         ping_log_file = ping_log_file,
                         signal = c('MFAS', 'Echosounder', 'Explosive'),
                         save_output = FALSE)

  # read in metadata about modeled RLs
  amf1 <- rl_model_dir[grepl('2021', rl_model_dir)]
  amf2 <- rl_model_dir[grepl('2022', rl_model_dir)]

  acous_model_meta1 <- readxl::read_xlsx(file.path(amf1, 'FB_ModelingDelivery_20210709_Unclass.xlsx')) |>
    dplyr::mutate(ID = paste0(ID, 'A'))

  acous_model_meta2 <- readxl::read_xlsx(file.path(amf2, '2021_2022_Tracking_2022.07.20Delivery.xlsx')) |>
    dplyr::mutate(ID = paste0(ID, 'B'),
                  ready = VBP # indicator of whether modeling has a data file or a failure in which case there is a jpg instead - in older file this var was called ready and here called vbp
                  )

  acous_model_meta <- dplyr::bind_rows(acous_model_meta1,
                                acous_model_meta2)

  # paste together file path(s) and tag file name(s)
  tags <- file.path(nc_path, tag_id)
  data_out <- list()

  # loop over tags
  for (t in c(1:length(tags))){
    cat(paste('tag: ', t, ', ', tags[t], '\n'))
    if (exists('this_data')){
      rm(this_data)
    }
    if (exists('these_dive_cycles')){
      rm(these_dive_cycles)
    }
    if (exists('trk')){
      rm(trk)
    }
    if (exists('spd')){
      rm(spd)
    }
    if (exists('ptk')){
      rm(ptk)
    }
    if (exists('step_turn_data')){
      rm(step_turn_data)
    }
    if (exists('T', mode = 'numeric')){
      rm(T)
    }

    # garbage collection/free memory
    gc()


    # check if the tags filename contains ".nc" and add it if not
    if (stringr::str_detect(tags[t],
                            pattern = '.nc',
                            negate = TRUE)){
      if (stringr::str_detect(tags[t],
                              pattern = stringr::fixed('.'))){
        warning('tag_id inputs to dive_acoustic_summary must be .nc files; no other file types can be used.')
      }
      tags[t] <- paste0(tags[t], '-cal.nc')
    }

    # load in the data for this tag
    # if there's a file with lf A and M already in then use it
    # these were precreated in matlab
    this_data <- tryCatch(tagtools::load_nc(stringr::str_replace(tags[t],
                                                                 pattern = '.nc',
                                                                 replacement = '-lo.nc')),
                          error = function(e){
                            return(tagtools::load_nc(tags[t]))
                          })

    if (this_data$info$depid == "Zc-20180331-173187"){
      this_data$info$depid <- "Zica-20180331-173187"
    }



    # Add GPS info
    # note -- make function to turn GPS stuff into data frame?
    these_locs <- data.frame(this_data$GPS_position$data)

    if (ncol(these_locs) < 3){
      #   these_locs <- data.frame(this_data$GPS_position$sampling_rate,
      #                            these_locs[1,1],
      #                            these_locs[2,1])
      these_locs <- data.frame(t(this_data$GPS_position$data))
    }

    these_sats <- data.frame(this_data$GPS_satellites$data)
    if (ncol(these_sats) < 2){
      # these_sats <- data.frame(this_data$GPS_satellites$sampling_rate,
      # these_sats)
      these_sats <- data.frame(t(this_data$GPS_satellites$data))
    }

    these_resids <- data.frame(this_data$GPS_residual$data)
    if (ncol(these_resids) < 2){
      # these_resids <- data.frame(this_data$GPS_residual$sampling_rate,
      #                          these_resids)
      these_resids <- data.frame(t(this_data$GPS_residual$data))
    }
    these_timeerr <- data.frame(this_data$GPS_time_error$data)
    if (ncol(these_timeerr) < 2){
      # these_timeerr <- data.frame(this_data$GPS_time_error$sampling_rate,
      #                            these_timeerr)
      these_timeerr <- data.frame(t(this_data$GPS_time_error$data))
    }

    names(these_locs) <- c('sec_since_tagon', 'latitude', 'longitude')
    names(these_sats) <- c('sec_since_tagon', 'satellites')
    names(these_resids) <- c('sec_since_tagon', 'residual')
    names(these_timeerr) <- c('sec_since_tagon', 'time_error')

    these_locs <- dplyr::left_join(these_locs, these_sats, by = 'sec_since_tagon')
    these_locs <- dplyr::left_join(these_locs, these_resids, by = 'sec_since_tagon')
    these_locs <- dplyr::left_join(these_locs, these_timeerr, by = 'sec_since_tagon')

    # Filter GPS data for low time error, residual, and enough sats
    these_locs <- these_locs |>
      dplyr::filter(time_error < 3 &
                      time_error > -3 &
                      residual < 35 &
                      satellites >= 4)

    these_dive_cycles <- dcs |>
      dplyr::filter(tag_id == this_data$info$depid)

    these_locs <- these_locs |>
      dplyr::select(sec_since_tagon,
                    latitude,
                    longitude) |>
      dplyr::filter(sec_since_tagon < max(dplyr::pull(these_dive_cycles, dive_cycle_end_sec) + 615, na.rm = TRUE)) |>
      tidyr::drop_na(latitude, longitude)

    # add northing and easting
    these_locs <- dplyr::bind_cols(these_locs, tagtools::lalo2llf(as.matrix(these_locs[,2:3])) )

    # some GPS points are impossible. Remove by hand (will thus need to review all tagouts, removing ind points more than 2km off track)
    if (this_data$info$depid == "Zica-20191012-144029"){
      these_locs <- these_locs |>
        dplyr::filter(northing < 5000 & easting > -1000 & easting < 20000)
    }
    # if (this_data$info$depid == "Zica-20190113-151361"){ # think this one OK
    #   these_locs <- these_locs |>
    #     dplyr::filter()
    # }
    # if (this_data$info$depid == "Zica-20191012-145101"){ # this one OK
    #   these_locs <- these_locs |>
    #     dplyr::filter()
    # }
    # if (this_data$info$depid == "Zica-20191111-94810"){ # this one OK
    #   these_locs <- these_locs |>
    #     dplyr::filter()
    # }
    # if (this_data$info$depid == "Zica-20191117-195993"){ # this one OK
    #   these_locs <- these_locs |>
    #     dplyr::filter()
    # }

    # add tagon time to dataset
    this_tagont <- lubridate::dmy_hms(this_data$info$dephist_device_datetime_start)
    these_dive_cycles$tag_recording_start_UTC <- this_tagont

    # compute tortuosity for each dive cycle
    these_dive_cycles$tortuosity = NA

    if ('Mw' %in% names(this_data) & 'Aw' %in% names(this_data)){ #only for tags with magnetometer and accel
      # compute anchored ptrack
      if ('Alo' %in% names(this_data)){
        this_data$Alo$data <- tidyr::replace_na(this_data$Alo$data, 0)
        this_data$Mlo$data <- tidyr::replace_na(this_data$Mlo$data, 0)
        this_data$Aw <- NULL
        this_data$Mw <- NULL
        gc()
      }else{
        this_data$Aw$data <- tidyr::replace_na(this_data$Aw$data, 0)
        this_data$Mw$data <- tidyr::replace_na(this_data$Mw$data, 0)
        this_data$Alo <- tagtools::decdc(this_data$Aw, df = this_data$Aw$sampling_rate)
        this_data$Aw <- NULL
        gc()
        this_data$Mlo <- tagtools::decdc(this_data$Mw, df = this_data$Mw$sampling_rate)
        this_data$Mw <- NULL
        gc()
      }

      this_data$depth$data <- zoo::na.approx(this_data$depth$data, x = c(1:nrow(this_data$depth$data)), na.rm = FALSE)
      this_data$depth$data <- tidyr::fill(data.frame(dpth = this_data$depth$data), dpth, .direction = "downup") |>
        dplyr::pull(dpth)
      this_data$Alo$data <- zoo::na.approx(this_data$Alo$data,
                                           x = c(1:nrow(this_data$Alo$data)),
                                           na.rm = FALSE)
      spd <- abs(tagtools::speed_from_depth(tagtools::interp2length(this_data$depth,
                                                                    this_data$Alo),
                                            this_data$Alo))
      spd <- zoo::na.approx(spd,
                            x = c(1:length(spd)),
                            na.rm = FALSE)
      spd <- zoo::na.approx(spd, x = c(1:length(spd)),
                            na.rm = FALSE)
      spd <- tidyr::fill(data.frame(spd = spd),
                         spd, .direction = "downup") |>
        dplyr::pull(spd) # to get initial NAs
      ptk <- tagtools::ptrack(A = this_data$Alo,
                              M = this_data$Mlo,
                              s = spd)
      # here pause and graph and verify that all GPS points are OK
      # gf_point(northing ~ easting, data = ptk) |>
      # gf_point(northing ~ easting, data = these_locs, color = 'red')
      if (nrow(these_locs) == 1){
        # if there are not any GPS points other than the first then don't bother
        trk <- ptk
      }else{
        trk <- tagtools::fit_tracks(P = these_locs[,c('northing', 'easting')],
                                    T = these_locs |> dplyr::pull(sec_since_tagon),
                                    D = ptk[,c('northing', 'easting')],
                                    sampling_rate = this_data$Alo$sampling_rate)
      }

      trk <- trk |>
        dplyr::mutate(sec_since_tagon = (-1 + c(1:nrow(trk))) / this_data$Alo$sampling_rate,
                      tagon_lat = these_locs |> dplyr::pull(latitude) |> dplyr::first(),
                      tagon_lon = these_locs |> dplyr::pull(longitude) |> dplyr::first())


      if (save_tracks){
        if (missing(track_dir) || is.null(track_dir)){
          track_dir <- getwd()
        }
        track_fname <- file.path(track_dir,
                                 paste0(this_data$info$depid, '-georef-ptrack.csv'))
        readr::write_csv(trk, file = track_fname)
      }

      for (cy in c(1:nrow(these_dive_cycles))){
        if (nrow(trk) > 1){
          cysi <- round(dplyr::pull(these_dive_cycles[cy, "dive_cycle_start_sec"]) * this_data$Alo$sampling_rate)
          cyei <- round(dplyr::pull(these_dive_cycles[cy, "dive_cycle_end_sec"]) * this_data$Alo$sampling_rate)
          T <- trk[seq(from = cysi, to = cyei), c('northing', 'easting')]
          these_dive_cycles[cy, 'tortuosity'] <- tagtools::tortuosity(T,
                                                                      sampling_rate = this_data$Alo$sampling_rate,
                                                                      intvl = (cyei - cysi + 1)/ this_data$Alo$sampling_rate)[1]
        }else{# end of "if trk has more than 1 row"
          these_dive_cycles[cy, 'tortuosity'] <- NA
        }
      } # end of loop over dive cycles
    } # end of "if there is Aw and Mw"

    # add in RL data
    # measured RL data for this whale
    these_rls <- all_rls |>
      dplyr::filter(TagID == tag_id[t])

    # add in measured RLs
    if (nrow(these_rls) > 0){
      # add in measured RLs
      these_dive_cycles <- add_interval_rls(these_dive_cycles,
                                            ping_data = these_rls,
                                            start_x = dive_cycle_start_sec,
                                            end_x = dive_cycle_end_sec,
                                            start_ping = sec_since_tagon)
    }

    # add in MODELED RLs
    #model_fnames <- list.files(rl_model_dir, pattern = paste0(tag_id[t], '.*', '.csv'))
    these_model_meta <- acous_model_meta |>
      dplyr::filter(TagID == tag_id[t]) |>
      dplyr::rename(tag_sonar_km = `Tag-SonarKm`)

    # add model results file names for each dive cycle plus modeled RLs?
    these_dive_cycles <- these_dive_cycles |>
      dplyr::mutate(model_fnames = NA,
                    model_ids = NA,
                    model_data_source = NA,
                    model_sonar_type = NA,
                    model_sonar_loc_source = NA,
                    model_source_whale_distance_km = NA,
                    model_source_whale_distance_min_km = NA,
                    model_source_whale_distance_max_km = NA,
                    model_source_whale_distance_median_km = NA,
                    model_rl_max_depth = NA,
                    model_rl_min_depth = NA,
                    model_rl_min = NA,
                    model_rl_max = NA,
                    model_rl_median = NA)

    for (cy in c(1:nrow(these_dive_cycles))){
      dcst <- these_dive_cycles |>
        dplyr::pull(fd_start_UTC) |>
        dplyr::nth(cy)
      dcet <- these_dive_cycles$fd_start_UTC[cy] +
        lubridate::seconds(these_dive_cycles |>
                             dplyr::pull(dive_cycle_dur_sec) |>
                             dplyr::nth(cy))
      this_cycle_meta <- these_model_meta |>
        dplyr::filter((StartTime >= dcst & StartTime < dcet) | # starts during this cycle
                        (StartTime < dcst & EndTime > dcst) | # or spans whole cycle, starting before and ending after
                        (EndTime >= dcst & EndTime < dcet)) # or ends during this cycle

      if (nrow(this_cycle_meta) > 0){
        these_dive_cycles$model_fnames[cy] <- list(paste(this_cycle_meta$TagID, '_',
                                                         this_cycle_meta$SonarID, '_',
                                                         this_cycle_meta$Type, '_',
                                                         stringr::str_pad(as.character(this_cycle_meta$ID),
                                                                          width = 5,
                                                                          side = 'left',
                                                                          pad = '0'), '.csv',
                                                         sep = ''))
        these_dive_cycles$model_ids[cy] <- this_cycle_meta |> dplyr::pull(ID) |> list()
        these_dive_cycles$model_sonar_loc_source[cy] = this_cycle_meta |> dplyr::pull(SonarLocSource) |> unique() |> list()
        these_dive_cycles$model_source_whale_distance_km[cy] = this_cycle_meta |> dplyr::pull(tag_sonar_km) |> list()
        these_dive_cycles$model_source_whale_distance_min_km[cy] = this_cycle_meta |> dplyr::pull(tag_sonar_km) |> min(na.rm = TRUE)
        these_dive_cycles$model_source_whale_distance_max_km[cy] = this_cycle_meta |> dplyr::pull(tag_sonar_km) |> max(na.rm = TRUE)
        these_dive_cycles$model_source_whale_distance_median_km[cy] = this_cycle_meta |> dplyr::pull(tag_sonar_km) |> median(na.rm = TRUE)
      }

      this_model <- list()
      # make sure to get the acoustic model data file location that matches this tagout (there are 2 as of 2022)
      if (this_data$info$depid %in% dplyr::pull(acous_model_meta1, TagID)){
        # first group -- data folder is amf1
        this_rl_model_dir <- amf1
      }else{
        if (this_data$info$depid %in% dplyr::pull(acous_model_meta2, TagID)){
          this_rl_model_dir <- amf2
        }else{
          warning(paste('Tag ID not found in acoustic model output metadata for tag', this_data$info$depid))
        }
      }
      if (nrow(this_cycle_meta) > 0){
        for (mf in c(1:nrow(this_cycle_meta))){
          this_file <- file.path(this_rl_model_dir, these_dive_cycles |>
                                   dplyr::pull(model_fnames) |>
                                   dplyr::nth(cy))
          this_file <- this_file[mf]

          ss <- dplyr::case_when(stringr::str_detect(string = this_file,
                                                     pattern = 'SPORTS') ~
                                   'SPORTS',
                                 stringr::str_detect(string = this_file,
                                                     pattern = 'ARCHIVE') ~
                                   'Archive',
                                 TRUE ~ 'Other')
          sty <- stringr::str_split(this_file, '_', simplify = TRUE)
          sty <- sty |> dplyr::nth(length(sty) - 1)
            if (!file.exists(this_file)){
              # need to check if this_cycle_meta[mf] has ready = jpg or error = low snr
              this_model[[mf]] <- tibble::tibble(
                hyd_lat = NA,
                hyd_lon = NA,
                rl_depth_m = NA,
                rl = 0,
                model_data_source = ss,
                model_sonar_type = sty
              )
              if (dplyr::pull(this_cycle_meta, ready) |> dplyr::nth(mf) == 'jpg'){
                this_model[[mf]] <- this_model[[mf]] |>
                  dplyr::mutate(model_status = 'too quiet')
              }
            }else{
              this_model[[mf]] <- readr::read_csv(this_file,
                                                  show_col_types = FALSE) |>
                dplyr::rename(hyd_lat = `Hyd Lat (deg)`,
                              hyd_lon = `Hyd Lon (deg)`,
                              rl_depth_m = `Received Depth (m)`,
                              rl = `Received Level (dB // 1uPa)`) |>
                dplyr::mutate(model_data_source = ss,
                              model_sonar_type = sty,
                              model_status = 'OK')
            } # if file exists
          } # loop over mf
        this_model <- dplyr::bind_rows(this_model)
      }# end of if nrow(this_cycle_meta > 0)


          if ('rl_depth_m' %in% names(this_model)) {
            this_model2 <- this_model |>
              dplyr::filter(rl_depth_m <= (dplyr::pull(these_dive_cycles, fd_max_depth) |>
                                             dplyr::nth(cy)))

            these_dive_cycles$model_rl_max_depth[cy] <- this_model |>
              dplyr::filter(rl_depth_m == plyr::round_any((these_dive_cycles |>
                                                             dplyr::pull(fd_max_depth) |>
                                                             dplyr::nth(cy)),
                                                          5,
                                                          f = floor) ) |>
              dplyr::pull(rl) |>
              max(na.rm = TRUE)

            # this will be the RL at the min modeled depth (since during a dive cycle the whale is @ zero depth sometimes)
            these_dive_cycles$model_rl_min_depth[cy] <- this_model |>
              dplyr::filter(rl_depth_m == min(dplyr::pull(this_model, rl_depth_m), na.rm = TRUE)) |>
              dplyr::pull(rl) |>
              max(na.rm = TRUE)

            these_dive_cycles$model_rl_min[cy] <- this_model2 |>
              dplyr::pull(rl) |>
              min(na.rm = TRUE)

            these_dive_cycles$model_rl_max[cy] <- this_model2 |>
              dplyr::pull(rl) |>
              max(na.rm = TRUE)
            these_dive_cycles$model_rl_median[cy] <- this_model2 |>
              dplyr::pull(rl) |>
              median(na.rm = TRUE)
            these_dive_cycles$model_data_source[cy] <- this_model |>
              dplyr::pull(model_data_source) |>
              unique() |>
              sort() |>
              stringr::str_c(collapse = ', ')
            these_dive_cycles$model_sonar_type[cy] <- this_model |>
              dplyr::pull(model_sonar_type) |>
              unique() |>
              sort() |>
              stringr::str_c(collapse = ', ')

          }# end of if nrow(this_data) > 0)
    } # end loop over dive cycles cy

    these_dive_cycles <- these_dive_cycles |>
      dplyr::mutate(dplyr::across(starts_with('model_rl'),
                                  ~ifelse(.x < 0, 0, .x)))
    these_dive_cycles <- these_dive_cycles |>
      # unlistify list columns (don't translate to CSV at all...)
      dplyr::group_by(dplyr::across(tidyselect::everything())) |>
      dplyr::summarise(model_fnames = paste0(model_fnames, collapse = ' '),
                       model_ids = paste0(model_ids, collapse = ' '),
                       model_sonar_loc_source = paste0(model_sonar_loc_source, collapse = ' '),
                       model_source_whale_distance_km = paste0(model_source_whale_distance_km, collapse = ' ')
                       ) |>
      dplyr::ungroup()

    # tibble-concatenating and saving file
    # do INSIDE loop so you don't have to start over in case of an error
    data_out[[t]] <- these_dive_cycles
    data_out_all <- dplyr::bind_rows(data_out)
    if (save_csv){
      readr::write_csv(data_out_all, file = csv_name)
    }

  } # end of loop over TAGS

  return(data_out_all)
  }

## WHEW! This is the dive-cycle-scale data.


# Also need to make fine-scale (evenly sampled in time windows) data
# and add it in as additional rows (ie join the dive-cycle and fine scale @ end...)
stacyderuiter/FBtagtools documentation built on June 1, 2025, 6:26 p.m.