Skip to content
Snippets Groups Projects
NHD_navigate.R 49.9 KiB
Newer Older
  • Learn to ignore specific revisions
  • #' Network navigation for upstream/downstream from a COMID of interest
    #' @param inCOM  (list) list of input COMIDs 
    #' @param nhdDF (sf data.frame) (data frame) valid data frame of NHD flowlines
    #' @param withTrib (logical) flag for if the upstream navigation should include tributaries
    #              or stick to mainstem level path
    #
    #' @return (list) list of COMIDs upstream of point
    
    NetworkNav <- function(inCom, nhdDF, withTrib){
    
        seg <- nhdplusTools::get_UM(nhdDF, inCom, include = TRUE)
    
        seg <- nhdplusTools::get_UT(nhdDF, inCom)
    
      return(seg)
    
    #' ### DEPRECATED ###
    #' #' Identifies and connects dangles in network generated by Network Nav function
    #' #' @param inCOM  (list) list of input COMIDs 
    #' #' @param nhdDF (sf data.frame) (data frame) valid data frame of NHD flowlines
    #' #' @param withTrib (logical) flag for if the upstream navigation should include tributaries
    #' #              or stick to mainstem level path
    #' #
    #' #' @return (list) list of COMIDs connecting dangle to existing network
    #' NetworkConnection <- function(incom, nhd){
    #' 
    #'   upnet_DF <- filter(nhd, COMID %in% incom) %>%
    #'     filter(!DnHydroseq %in% Hydroseq)
    #'   
    #'   # while the number of dangles is greater than 0
    #'   while (length(upnet_DF$COMID) > 0){
    #'     # create item for number of dangles
    #'     count <- dim(upnet_DF)[1]
    #'     print (dim(upnet_DF))
    #'     # find out which level paths are downstream of dangling huc12 POIs
    #'     DSLP <- upnet_DF %>% pull(DnLevelPat)#[upnet_DF$COMID %in% incom]
    #'     # Get the COMID of the hydroseq with level path value
    #'     # the lowest downstream flowline within the levelpath
    #'     inCom2 <- nhd$COMID[nhd$Hydroseq %in% DSLP]
    #'     # Run the upstream navigation code
    #'     upNet <- unique(unlist(lapply(inCom2, NetworkNav, nhd)))
    #'     # Append result to existing segment list
    #'     incom <- append(incom, upNet)
    #'     # Get the same variable as above
    #'     upnet_DF <- filter(nhd, COMID %in% incom, !DnHydroseq %in% Hydroseq)
    #'     # Get the count
    #'     count2 <- dim(upnet_DF)[1]
    #'     # if the count has remained the same we are done and return the flowline list
    #'     if (count == count2){
    #'       return (incom)
    #'     }
    #'   }
    #'   # Not sure this other return is needed
    #'   return(incom)
    #' }
    
    Blodgett, David L.'s avatar
    Blodgett, David L. committed
    ## Deprecated -- see hyfabric package
    #' #' Switches valid POIs from minor to major path divergences
    #' #' @param inSegs  (list) list of input COMIDs representing POIs
    #' #' @param nhdDF (sf data.frame) (data frame) valid data frame of NHD flowlines
    #' #' 
    #' #' @return (sf data.frame) Corresponding major path COMID for POI
    #' switchDiv <- function(insegs, nhdDF){
    
    Blodgett, David L.'s avatar
    Blodgett, David L. committed
    #'   div_segs <- filter(nhdDF, COMID %in% insegs$COMID)
    #'   if (2 %in% div_segs$Divergence){
    #'     print ("Switching divergence to other fork")
    #'     
    #'     # Look Upstream
    #'     upstream <- st_drop_geometry(nhdDF) %>% 
    #'       inner_join(st_drop_geometry(div_segs) %>% 
    #'                    filter(Divergence == 2) %>% 
    #'                    select(COMID, Hydroseq), 
    #'                  by = c("DnMinorHyd" = "Hydroseq"))
    #'     
    #'     # From Upstream Segment switch POI to the downstream major/main path
    #'     insegs_maj <- st_drop_geometry(nhdDF) %>% 
    #'       inner_join(upstream %>% select(COMID.y, DnHydroseq), 
    #'                  by = c("Hydroseq" = "DnHydroseq")) %>% 
    #'       select(COMID, COMID.y)
    #'     
    #'     insegs <- insegs %>% 
    #'       left_join(insegs_maj, by = c("COMID" = "COMID.y")) %>%
    #'       mutate(COMID = ifelse(!is.na(COMID.y), COMID.y, COMID)) %>% select(-COMID.y)
    
    Blodgett, David L.'s avatar
    Blodgett, David L. committed
    #'   } else {
    #'     print ("no divergences present in POI set")
    #'   }
    #'   return(insegs)
    #' }
    
    Blodgett, David L.'s avatar
    Blodgett, David L. committed
    ## Deprecated -- see hyfabric package
    #' #' Creates POIs for a given data theme
    #' #' @param srcData  (data.frame) (data frame) DF with two columns: 
    #' #             1 - COMID
    #' #             2 - Unique ID value for POI (Streamgage ID, etc.)
    #' #' @param nhdDF (sf data.frame) valid data frame of NHD flowlines
    #' #' @param IDfield character) Name of 'Type' field to be modified in POI 
    #' #' 
    #' #' @return (sf data.frame) OIs for the specific data theme
    #' POI_creation<-function(srcData, nhdDF, IDfield){
    #' 
    #'   # Give generic ID to Identity field
    #'   colnames(srcData) <- c("COMID", "ID")
    #'   
    #'   sub_segs <- filter(nhdDF, COMID %in% srcData$COMID) 
    #' 
    #'   POIs <- sub_segs %>% 
    #'     get_node(., position = "end") %>% 
    #'     mutate(COMID = sub_segs$COMID) %>%
    #'     mutate(Type_HUC12 = NA, Type_WBIn = NA, Type_WBOut = NA, Type_Gages = NA, Type_TE = NA, Type_NID = NA, Type_Conf = NA) %>%
    #'     inner_join(srcData %>% select(COMID, ID), by = "COMID") %>% 
    #'     mutate(!!(paste0("Type_", IDfield)) := ID)
    #'   
    #'   if(!(paste0("Type_", IDfield)) %in% colnames(POIs)){
    #'     POIs <- POIs %>% select(COMID, Type_HUC12, Type_Gages, Type_TE, Type_NID, Type_WBIn, Type_WBOut, Type_Conf)
    #'   } else {
    #'     POIs <- POIs %>% select(COMID, Type_HUC12, Type_Gages, Type_TE, 
    #'                             Type_NID, Type_WBIn, Type_WBOut, Type_Conf, !!(paste0("Type_", IDfield)))
    #'   }
    #' 
    #'   return(POIs)
    #' }
    
    Blodgett, David L.'s avatar
    Blodgett, David L. committed
    # Deprecated -- see hyfabric package.
    #' #' Adds the type attribute for co-located POIs of multiple themes
    #' #' @param new_POIs (sf data.frame) new POIs to be tested against existing
    #' #' @param POIs  (sf data.frame) Existing POIs
    #' #' @param IDfield (character) Name of 'Type' field to be modified in existing POI 
    #' #' @param bind (logical) whether to bind non co-located POIs to data frame or just
    #' #'             attribute existing POIs 
    #' #' 
    #' #' @return (sf data.frame) xisting POIs with Type field modified for
    #' #            duplicate POIs for given data theme
    #' addType<-function(new_POIs, POIs, IDfield, bind){
    
    Blodgett, David L.'s avatar
    Blodgett, David L. committed
    #'   new_POIs <- st_compatibalize(new_POIs, POIs) 
    #'   
    #'   if(paste0("Type_", IDfield) %in% colnames(POIs)){
    #'     POIs_exist <- POIs %>% 
    #'       rename(ID = !!(paste0("Type_", IDfield)))
    #'   } else {
    #'     POIs_exist <- POIs %>%
    #'       mutate(ID = NA)
    #'   }
    #' 
    #'   # Add columns missing between master POI and new set
    #'   missing_cols <- colnames(POIs)[!colnames(POIs) %in% colnames(new_POIs)]
    #'   for(col in missing_cols){
    #'     new_POIs <-  new_POIs %>%
    #'       mutate(!!col := NA)
    #'   }
    #'   
    #'   POIs_newAtt <- filter(new_POIs, COMID %in% POIs$COMID) %>%
    #'     rename(ID2 = !!(paste0("Type_", IDfield)))
    #' 
    #'   POIs_fin <- POIs_exist %>% 
    #'     left_join(st_drop_geometry(POIs_newAtt) %>% 
    #'                                  select(COMID, ID2), by = "COMID", all.x = TRUE) %>%
    #'     mutate(ID = ifelse(!is.na(ID2), ID2, ID)) %>% 
    #'     rename(!!(paste0("Type_", IDfield)) := ID) %>% 
    #'     select(-ID2) 
    #'  
    #'   # Bind unless indicated not to
    #'   if(missing(bind)){
    #'     POIs_fin <- rbind(POIs_fin, filter(new_POIs, !COMID %in% POIs_fin$COMID))
    #'   }
    #'   
    #'   return(POIs_fin)
    #' }
    
    #' Creates raw and dissolved segment layers with necessaary
    #         upstream/downstream routing attribution
    #'  @param nhdDF (sf data.frame) valid data frame of NHD flowlines
    #'  @param POIs  (sf data.frame) Existing POIs
    #' 
    #' @return (sf data.frame) data.frame of segments connecting POIs attributed
    #'         with POI_ID for each upstream flowpath
    segment_increment <- function(nhdDF, POIs){
    
    
      ptm<-proc.time()
      
      seg_POIs <- arrange(POIs, desc(LevelPathI), desc(Hydroseq)) %>%
        select(COMID, Hydroseq, LevelPathI) %>%
        group_by(LevelPathI) %>%
        # These next two levels arrange POIs hydrologically along the 
        #       level path in order to meet the requirements of the code below
        mutate(id = row_number(), 
               num = if(n() > 1) id[1L] + row_number()-1 else id) %>%
        ungroup()
      
      # Add an empty field for POI_Id population
    
      nhdDF <- mutate(nhdDF, POI_ID = 0)
    
      POI_ID_assign <- function(i, seg_POIs, nhd){
        ##########################################
        # Populates POI_ID per segment
        # 
        # Arguments:
        #   i : (integer) iterator
        #   seg_POIs : (data frame) POI data frame
        #   nhd : (dataframe) flowlines data frame (no geometry)
        #  
        # Returns:
        #   nhd_sub : (data frame) raw segments with POI_ID populated
        #             
        ##########################################
        library(dplyr)
    
        # If POI is most upstream POI on levelpath
        if (seg_POIs$num[i] == 1){
    
          nhd_sub <- filter(nhd, Hydroseq >= seg_POIs$Hydroseq[i] & 
                              LevelPathI == seg_POIs$LevelPathI[i]) %>% 
            mutate(POI_ID =  seg_POIs$COMID[i])
          # or as you travel downstream on set of POIs below level path
        } else {
          # Assign POI_ID
          nhd_sub <- filter(nhd, LevelPathI == seg_POIs$LevelPathI[i] & 
                              Hydroseq >= seg_POIs$Hydroseq[i] & Hydroseq < seg_POIs$Hydroseq[i-1]) %>% 
            mutate(POI_ID = seg_POIs$COMID[i])
    
        # return assigned flowlines
        return(select(nhd_sub, LevelPathI, Hydroseq, COMID, POI_ID) %>%
                 filter(POI_ID != 0))
    
      library(parallel)
      library(dplyr)
      clust <- makeCluster(4)
    
      POI_list <- parLapply(clust, c(1:nrow(seg_POIs)), POI_ID_assign, seg_POIs, st_drop_geometry(nhdDF))
      #POI_list <- lapply(c(1:nrow(seg_POIs)), POI_ID_assign, seg_POIs, st_drop_geometry(nhdDF))
    
      stopCluster(clust)
      
      inc_segs <- data.table::rbindlist(POI_list)
      
      print(proc.time()-ptm)
    
    #' Creates finalized segments and routing
    #'  @param nhdDF (sf data.frame) valid data frame of NHD flowlines
    #'  @param routing_fix  (sf data.frame) any additional routing fixes
    #' 
    #' @return (sf data.frame) data.frame of segments
    segment_creation <- function(nhdDF, routing_fix){
      
      if(!"StartFlag" %in% names(nhdDF)) {
    
    Bock, Andy's avatar
    Bock, Andy committed
        nhdDF$StartFlag <- ifelse(nhdDF$Hydroseq %in% nhdDF$DnHydroseq, 0, 1)
    
      in_segs <- filter(nhdDF, !is.na(POI_ID))
    
      
      # If there are routing fixes to account for if a POI with a DA of 0 is moved upsream or downstream
      if (missing(routing_fix) == FALSE){
    
        routing_fix <- routing_fix %>%
          rename(COMID = oldPOI, new_COMID = COMID)
        
    
        # Above we generated the network using the initial set of POIs; here we crosswalk over the old COMIDs to the new
    
        nhd_fix <- nhdDF %>%
    
          left_join(routing_fix %>%
                       select(COMID, new_COMID), by = c("POI_ID" = "COMID")) %>%
          mutate(POI_ID = ifelse(is.na(new_COMID), POI_ID, new_COMID)) %>%
          filter(!POI_ID %in% routing_fix$COMID) %>%
          select(-new_COMID)
        
        in_segs <- filter(nhd_fix, !is.na(POI_ID))
      }
      
      # Dissolve flowlines to aggregated segments
      nsegments <- filter(in_segs, !is.na(POI_ID)) %>%
        group_by(POI_ID) %>%
        #arrange(desc(LevelPathI), desc(Hydroseq)) %>%
    
    Bock, Andy's avatar
    Bock, Andy committed
        summarize(TotalLength = sum(LENGTHKM),TotalDA = max(TotDASqKM), HW = max(StartFlag),
    
                  do_union = FALSE) %>%
        #st_cast("MULTILINESTRING")  %>%
        inner_join(st_drop_geometry(filter(in_segs, minNet == 1)) %>%
                     select(COMID, Hydroseq, DnHydroseq), by = c("POI_ID" = "COMID"))
      
      # produce a short data frame for populating TO_POI for downstream segment
      to_from <- filter(st_drop_geometry(in_segs)) %>%
    
        left_join(filter(st_drop_geometry(nhdDF), !is.na(POI_ID)) %>% 
    
                    select(COMID, Hydroseq, POI_ID), by = c("DnHydroseq" = "Hydroseq")) %>%
        select(COMID.x, Hydroseq, DnHydroseq, POI_ID.y) %>%
        rename(To_POI_ID = POI_ID.y) 
      
      # Add To_POI_ID to dissolved segments
      nsegments_fin <- nsegments %>% 
        left_join(select(to_from, COMID.x, To_POI_ID), by = c("POI_ID" = "COMID.x")) %>%
    
    Bock, Andy's avatar
    Bock, Andy committed
        select(POI_ID, TotalLength, TotalDA, HW, To_POI_ID) 
    
      
      return(list(diss_segs = nsegments_fin, raw_segs = in_segs))
    }
    
    
    #' Moves POI Upstream or downstream if it falls on COMID
    #       of flowline with no corresponding catchment
    #'  @param POIs_wgeom (sf data.frame) POIs
    #'  @param nhdDF  (sf data.frame) valid data frame of NHD flowlines
    #' 
    #' @return (sf data.frame) data.frame of POIs with new COMID associated
    
    DS_poiFix <- function(POIs_wgeom, nhd){
    
      POIs <- st_drop_geometry(POIs_wgeom) %>%
        arrange(COMID)
    
      # DF of downstream segment
      tocomDF <- select(nhd, COMID, DnHydroseq) %>%
        inner_join(select(st_drop_geometry(nhd), COMID, Hydroseq), by = c("DnHydroseq" = "Hydroseq"))
    
    
      # Find segments with POIs where there is no corresponding catchment that are not terminal
      unCon_POIs <- filter(nhd, COMID %in% POIs$COMID, AreaSqKM == 0 & Hydroseq != TerminalPa)
    
    
      poi_fix <- as.data.frame(do.call("rbind", lapply(unCon_POIs$COMID, movePOI_NA_DA, st_drop_geometry(nhd)))) %>%
        inner_join(POIs, by = c("oldPOI" = "COMID")) %>%
        inner_join(select(st_drop_geometry(nhd), COMID), by = c("oldPOI" = "COMID"))
      
      # Fold in new POIs with existing POIs so all the "Type" attribution will carry over
      # using the minimum will ensure correct downstream hydrosequence gets carried over
      poi_fix_unique <- filter(POIs, COMID %in% poi_fix$COMID) %>% 
        bind_rows(poi_fix) %>% 
        group_by(COMID) %>% 
        filter(n() > 1) %>%
    
    Bock, Andy's avatar
    Bock, Andy committed
        # Spurs warnings where there are NAs for a column 
        #       for a given grouped COMID, but output is what I expect
        summarise_all(funs(min(unique(.[!is.na(.)]), na.rm = T))) %>%
        # Replace -Inf with NA where applicaable
    
        mutate_all(~na_if(., -Inf))
    
      # Join new POI COMIDs and geometry with the old Type fields
      new_POIs <- bind_rows(filter(poi_fix, !COMID %in% poi_fix_unique$COMID), poi_fix_unique) %>%
        arrange(COMID) %>%
        bind_cols(get_node(filter(nhd, COMID %in% .$COMID) %>% arrange(COMID), position = "end")) %>%
        mutate(to_com = NA) %>%
        st_sf() %>%
        st_compatibalize(., POIs_wgeom)
    
      # If the DS replacement flowlines with Inc Area > 0 equals the number of replacement comIDs 
      replacement_tocoms <- st_drop_geometry(new_POIs) %>%
        inner_join(select(st_drop_geometry(nhd), COMID, Hydroseq), by = c("DnHydroseq" = "Hydroseq")) %>%
        mutate(to_com = COMID.y) %>%
        select(oldPOI, COMID.x, COMID.y, AreaSqKM)
      
      # Add downstream COMIDS to the newCOMID
      fin_POIs <- new_POIs %>% 
        left_join(replacement_tocoms, by = "oldPOI") %>% 
        mutate(to_com = ifelse(!is.na(COMID.y), COMID.y, NA)) %>% 
        select(-c(TotDASqKM, AreaSqKM.x, DnHydroseq, COMID.x, COMID.y, AreaSqKM.y)) 
      
      return (list(xWalk = select(poi_fix, COMID, oldPOI), new_POIs = fin_POIs))
    }
    
    
    
    #' Move POIs that fall on flowlines with no catchment upstream/downstream
    #     to adjacent flowline with most similar total drainage area. Called from 
    #     DS_poi_fix function above
    #'  @param poi_fix (data.frame) POI data set of COMIDs to be changed
    #'  @param nhdDF  (sf data.frame) valid data frame of NHD flowlines
    #' 
    #' @return (data frame) DF of POIs with new COMID associated
    movePOI_NA_DA <- function(poi_fix, nhdDF){
    
      up_segs <- nhdplusTools::get_UM(nhdDF, poi_fix, sort=T)
      seg2fix <- filter(nhdDF, COMID == poi_fix)
    
      
      # Sorted results and filter out all flowlines w/o catchments
    
      upstuff <- filter(nhdDF, COMID %in% unlist(up_segs)) %>% 
    
        arrange(factor(COMID, levels = up_segs)) %>%
        filter(AreaSqKM > 0)
      
    
      down_segs <- nhdplusTools::get_DM(nhdDF, poi_fix, sort=T)
      downstuff <- filter(nhdDF, COMID %in% unlist(down_segs)) %>% 
    
        arrange(factor(COMID, levels = down_segs)) %>%
        filter(AreaSqKM > 0)
      
      # combine into one dataframe, select up/downstream seg with least change in total drainage area
      near_FL <- rbind(select(upstuff, COMID, TotDASqKM, AreaSqKM) %>% 
                        slice(1), 
                        select(downstuff, COMID, TotDASqKM, AreaSqKM) %>% 
                        slice(1))
      
      # If 1 or other adjacent flowlines are coupled with a catchment
      if (sum(near_FL$AreaSqKM) > 0){
        new_POI <- near_FL[(which.min(abs(seg2fix$TotDASqKM - near_FL$TotDASqKM))),] #near_FL$COMID
        new_POI$oldPOI <- poi_fix
        new_POI$DnHydroseq <-seg2fix$DnHydroseq
      } else {
        # Remove POI if not catchment associated with flowlines upstream or downstream
        print (poi_fix)
        print ("US and DS flowlines also have no catchment, removing POI")
        new_POI <- NA
      }
      return(new_POI)
    }
    
    
    #' Collaposes/merges POIs together based on drainage area ratio and data theme
    #'  @param out_gpkg (geopackage) output geopackage to write intermediate results to
    #'  @param POIs  (sf data.frame) Original  POIs
    #'  @param Seg  (sf data.frame) dissolved segments
    #'  @param Seg2  (sf data.frame) (data frame) raw segments/flowlines
    #'  @param exp  (string) Type of thematic POI being moved round
    #'  @param DA_diff  (float) threshold of drainage area difference to
    #             consider when merging two POIs
    #'  
    #' @return (list of data.frames) 1 - New set of POIs
    #          2/3 - correpsonding segments (both raw and dissolved)
    
    POI_move_down<-function(out_gpkg, POIs, Seg, Seg2, exp, DA_diff){
    
      POIs <- POIs %>% mutate_if(is.numeric, function(x) ifelse(is.infinite(x), NA, x)) %>%
        filter(nexus != TRUE)
    
      
      # downstream POIs and their drainage area ratios and incremental drainage areas
    
      segs_down <- Seg %>% 
        inner_join(select(st_drop_geometry(.), c(POI_ID, TotalDA, TotalLength)), 
    
                   by = c("To_POI_ID" = "POI_ID")) %>%
    
        inner_join(st_drop_geometry(POIs), by = c("POI_ID" = "COMID")) %>%
        # Keep WBOut and In to preserve NHD Waterbody geometry, Keep Confluences and TE where they are
    
        filter_at(vars(Type_WBOut, Type_WBIn, Type_Conf, Type_TE), all_vars(is.na(.))) %>%
    
        # Select POIs within the correct drainage area ratio and fit the size criteria
    
        mutate(DAR = TotalDA.y/TotalDA.x, IncDA = TotalDA.y - TotalDA.x) %>% 
        # If the drainage area ratio is within acceptable range (1.05)
    
        filter(DAR < (1 + DA_diff), POI_ID != To_POI_ID) %>% 
    
        # select and re-order
    
    Bock, Andy's avatar
    Bock, Andy committed
        select(POI_ID, HW, To_POI_ID, Type_HUC12, Type_Gages, Type_TE, Type_NID, 
    
               Type_WBIn, Type_WBOut, Type_Conf, Type_Term, Type_Elev, Type_Travel, nexus, DAR, IncDA)
    
      # upstream POIs and their drainage area ratios and incremental drainage areas
    
      up_POIs <- filter(POIs, is.na(Type_Conf))
    
      segs_up <- Seg %>% inner_join(select(filter(st_drop_geometry(.), POI_ID %in% up_POIs$COMID), 
    
                                           c(POI_ID, To_POI_ID, TotalDA, TotalLength)), 
                                    by = c("POI_ID" = "To_POI_ID")) %>%
    
        inner_join(st_drop_geometry(POIs), by = c("POI_ID" = "COMID")) %>% 
        rename(From_POI_ID = POI_ID.y) %>%
    
        # Don't want to move outlets upstream, don't move confluences upstream
    
        filter_at(vars(Type_WBOut, Type_WBIn, Type_Conf, Type_TE), all_vars(is.na(.))) %>%
    
        # Select POIs within the correct drainage area ratio and fit the size criteria
    
        mutate(DAR = TotalDA.y/TotalDA.x, IncDA = TotalDA.x - TotalDA.y) %>% 
    
        # If the drainage area ratio is within acceptable range (0.95)
    
        filter(DAR >= (1 - DA_diff), POI_ID != From_POI_ID) %>% 
    
        # select and re-order
    
    Bock, Andy's avatar
    Bock, Andy committed
        select(POI_ID, HW, From_POI_ID, Type_HUC12, Type_Gages, Type_TE, Type_NID, 
    
               Type_WBIn, Type_WBOut, Type_Conf, Type_Term, Type_Elev, Type_Travel, nexus, DAR, IncDA)
    
      # Filter by POI Type, this removes the POI theme 
    
    Bock, Andy's avatar
    Bock, Andy committed
      Types <- c("Type_Gages", "Type_HUC12", "Type_TE", "Type_Conf", "Type_NID", "Type_WBIn", "Type_WBOut",
                 "Type_Term", "Type_Elev", "Type_Travel")
    
      Types <- Types[Types != exp]
      
      # Gages coupled with confluences, waterbodies, do not move these
      static_POIs <- POIs %>% 
        filter(!is.na(Type_Gages) & (!is.na(Type_Conf) | !is.na(Type_WBOut) | !is.na(Type_WBIn)))
      
    
      # compiled list of POIs to move up or down
    
      seg_choices <- filter(POIs, !COMID %in% static_POIs) %>%
    
        left_join(st_drop_geometry(segs_down) %>%
    
                    select(POI_ID, To_POI_ID, DAR, IncDA), by = c("COMID" = "POI_ID")) %>%
    
        left_join(st_drop_geometry(segs_up) %>%
                    select(POI_ID, From_POI_ID, DAR, IncDA), by = c("COMID" = "POI_ID")) %>%
        filter(!is.na(To_POI_ID) | !is.na(From_POI_ID)) %>%
        filter(IncDA.x < min_da_km | IncDA.y < min_da_km)
    
      # We only want to move POIs that are not coupled within another theme
    
      seg_sub <- seg_choices %>% 
        filter_at(Types, all_vars(is.na(.))) %>%
    
        # Don't want to move the static POIS either
    
        filter(!COMID %in% static_POIs$COMID) %>%
    
        select (COMID, To_POI_ID, DAR.x, IncDA.x, From_POI_ID, DAR.y, DAR.y, IncDA.y) %>%
        st_sf()
    
      # Commented for QAQCing 
      #st_write(seg_sub, out_gpkg, "Seg_choices", delete_layer = F, append = T)
    
      # Data frame to indicate whether POIs should be grouped upstream or downstream
      segsub_dir <- mutate(seg_sub, DirDA = ifelse(is.na(DAR.y), "Down", 
    
                              ifelse(is.na(DAR.x), "Up",
                                     ifelse(IncDA.x < IncDA.y, "Down", "Up"))),
    
               Move = ifelse(DirDA == "Down", To_POI_ID, From_POI_ID))
    
      # POIs whose moves correspond to each others locations
    
      Mult <- select(segsub_dir, -c(To_POI_ID, From_POI_ID)) %>% #select(segsub_dir, COMID, Move) %>% 
        inner_join(st_drop_geometry(.), by = c("Move" = "COMID"), suffix = c(".A", ".B")) 
    
      # Basicly just chose one of the pair in Mult.
    
      segsub_dir <- filter(segsub_dir, !COMID %in% Mult$Move)
    
      
      #1 - POIs that need to be moved downstream
    
      move_POI <- filter(POIs, COMID %in% segsub_dir$COMID)
    
      # POIs that are stationary and their infomration will be merged with upstream  POIs
    
      stationary_POI <- filter(POIs, !COMID %in% move_POI$COMID)
    
      # Final Set to be merged with that don't involve either sets above
    
      #FinalPOI <- POIs %>% filter(!COMID %in% SegSub_Dir$COMID & !COMID %in% SegSub_Dir$Move) %>%
      #  mutate(merged_COMID = NA)
    
      #2 - Join SegSub assignment to POIs to bring over POI attributes
    
      movedownPOI_withatt <- select(st_drop_geometry(segsub_dir), COMID, DirDA, Move) %>%
        left_join(st_drop_geometry(move_POI), by = "COMID")  
    
      
      # Join SegSub mod to downstream POIs, COMID.x is the final COMID
    
      merged_POIs <- stationary_POI %>%
        inner_join(movedownPOI_withatt,
    
                   by = c("COMID" = "Move"), suffix = c(".x", ".y")) %>%
    
        mutate_all(., list(~na_if(.,""))) %>%
        # Don't overwrite existing gages or HUC12s
        filter(is.na(!!as.symbol(paste0(exp, ".x")))) %>%
    
        # Bring over relevant attributes
    
        mutate(Type_HUC12 = ifelse(is.na(Type_HUC12.y), Type_HUC12.x, Type_HUC12.y)) %>%
    
        mutate(Type_Gages = ifelse(is.na(Type_Gages.x), Type_Gages.y, Type_Gages.x)) %>%
    
        mutate(Type_WBIn = ifelse(is.na(Type_WBIn.y), Type_WBIn.x, Type_WBIn.y)) %>% 
        mutate(Type_WBOut = ifelse(is.na(Type_WBOut.y), Type_WBOut.x, Type_WBOut.y)) %>%
        mutate(Type_TE = ifelse(is.na(Type_TE.y), Type_TE.x, Type_TE.y)) %>%
        mutate(Type_NID = ifelse(is.na(Type_NID.y), Type_NID.x, Type_NID.y)) %>%
        mutate(Type_Conf = ifelse(is.na(Type_Conf.y), Type_Conf.x, Type_Conf.y)) %>%
    
        mutate(Type_Term = ifelse(is.na(Type_Term.y), Type_Term.x, Type_Term.y)) %>%
    
    Bock, Andy's avatar
    Bock, Andy committed
        mutate(Type_Elev = ifelse(is.na(Type_Elev.y), Type_Elev.x, Type_Elev.y)) %>%
        mutate(Type_Travel = ifelse(is.na(Type_Travel.y), Type_Travel.x, Type_Travel.y)) %>%
    
        mutate(oldPOI = COMID.y, to_com = NA) %>% 
    
        select(COMID, Type_HUC12, Type_Gages, Type_WBIn, Type_WBOut, 
    
    Bock, Andy's avatar
    Bock, Andy committed
               Type_TE, Type_NID, Type_Conf, Type_Term, 
    
               Type_Elev, Type_Travel, DirDA, nexus = nexus.x, oldPOI = COMID.y) %>%
    
        group_by(COMID) %>%
        summarize_all(~paste(unique(na.omit(.)), collapse = ',')) %>%
    
        
      print(colnames(POIs))
      print(colnames(merged_POIs))
    
      # Bring new POI data over to new data
    
      fin_POIs <- filter(POIs, !COMID %in% c(merged_POIs$oldPOI, merged_POIs$COMID)) %>% 
        rbind(merged_POIs %>% select(-c(DirDA, oldPOI)))
        #inner_join(st_drop_geometry(merged_POIs) %>% select(COMID, Type_Gages_A), by = "COMID") %>%
        #mutate(Type_Gages = ifelse(!is.na(Type_Gages_A), Type_Gages_A, Type_Gages)) %>% select(-Type_Gages_A)
    
      changed_POIs <- POIs %>%
    
        inner_join(select(st_drop_geometry(merged_POIs), COMID, oldPOI)) %>%
    
    Bock, Andy's avatar
    Bock, Andy committed
        select(COMID, oldPOI, Type_HUC12, Type_Gages, Type_TE, Type_NID, Type_WBIn, Type_WBOut, Type_Conf, 
               Type_Term, Type_Elev, Type_Travel) %>%
    
        mutate(to_com = COMID) %>%
    
        st_compatibalize(., read_sf(out_gpkg, poi_moved_layer))
      st_write(changed_POIs, out_gpkg, poi_moved_layer, append = TRUE) # write_sf not appending?
    
      
      # Format POI moves to table
      xWalk <- select(st_drop_geometry(segsub_dir), Move, COMID) %>%
    
        filter(!is.na(Move), Move %in% merged_POIs$COMID) %>%
    
        rename(COMID = Move, oldPOI = COMID)
    
      st_write(xWalk, out_gpkg, "collapse_xWalk", append = TRUE) # write_sf not appending?
    
      newSegs <- segment_creation(mutate(Seg2, old_POI_ID = POI_ID), xWalk)
    
      newSegs$raw_segs <- distinct(newSegs$raw_segs, .keep_all = T)
      
    
      # Return POIs, segments, and raw segments
    
      return (list(allPOIs = fin_POIs, segs = newSegs$diss_segs, FL = newSegs$raw_segs))
    
    #' Identifies and attributes structural POIs
    #'  @param nhdDF (sf data.frame) valid data frame of NHD flowlines
    #'  @param final_POIs  (sf data.frame) final POIs
    #' 
    #' @return (data.frame columns) 1/0 Columns indicating structural POIs and the structural network
    structPOIsNet <- function(nhdDF, final_POIs){
    
      
      # subset to flowlines used for nsegments
    
      inc_segs <- filter(nhdDF, !is.na(POI_ID))
    
    Bock, Andy's avatar
    Bock, Andy committed
      vpu <- inc_segs$VPUID
    
      # Terminal outlets from the initial network
    
      termout <- filter(nhdDF, !Hydroseq %in% DnHydroseq & TerminalFl == 1 & !COMID %in% final_POIs$COMID)
    
      out_POIs <- filter(nhdDF, COMID %in% final_POIs$COMID & TerminalFl == 1)
    
    Bock, Andy's avatar
    Bock, Andy committed
      conf_POIs <- filter(inc_segs, COMID %in% final_POIs$COMID[!is.na(final_POIs$Type_Conf)])
    
      wb_POIs <- filter(inc_segs, COMID %in% final_POIs$COMID[!is.na(final_POIs$Type_WBOut) | !is.na(final_POIs$Type_WBIn)])
    
      struc_flines <- termout %>% 
        bind_rows(out_POIs, conf_POIs, wb_POIs) %>% 
        arrange(COMID)
    
      
      struc_flines <- struc_flines[!st_is_empty(struc_flines), , drop = F] %>%
        st_cast("LINESTRING")
      
    
      struc_POIs <- get_node(struc_flines, position = "end") %>% 
    
        mutate(COMID = struc_flines$COMID, LevelPathI = struc_flines$LevelPathI) 
    
      
      # Add back in any levelpaths missing (shouldn't be much)
    
      miss_LP <- filter(inc_segs, COMID %in% final_POIs$COMID) %>% 
    
        filter(!LevelPathI %in% struc_POIs$LevelPathI)
    
      
      # Only bind if there are rows present
      if (nrow(miss_LP) > 0){
    
        lp_POIs <- c(miss_LP$LevelPathI, struc_POIs$LevelPathI)
    
        lp_POIs <- struc_POIs$LevelPathI
    
      }
      
      # final stuctural POIs, order by COMID to match with strucPOIs
    
      final_struct <- filter(nhdDF, Hydroseq %in% lp_POIs) %>% 
    
        arrange(COMID)
      struct_POIs <- get_node(final_struct, position = "end") %>% 
        mutate(COMID = final_struct$COMID) 
    
      
      #  produce unique set of flowlines linking POIs
    
    Bock, Andy's avatar
    Bock, Andy committed
      final_net <- unique(unlist(lapply(unique(final_struct$COMID), NetworkNav, st_drop_geometry(nhdDF))))
    
      
      # Subset NHDPlusV2 flowlines to navigation results and write to shapefile
    
    Bock, Andy's avatar
    Bock, Andy committed
      structnet <- filter(nhdDF, COMID %in% final_net & grepl(paste0("^", vpu, ".*"), .data$VPUID)) 
    
      # Write out minimally-sufficient network and POIs as a list
    
      return(list(struc_POIs = struc_POIs, structnet = structnet))
    
    #'  Splits a network based elevation change and size
    #'  @param in_POI_ID (integer) POI_ID of aggregated flowline that needs to be
    #'                   split based on elevation
    #'  @param split_DF  (sf data.frame) flowlines attributed with POI_ID
    #'  @param elev_diff (numeric) max elevation change threshold within a segment
    #'  @param max_DA (numeric) minimum drainage area for resulting split
    #' 
    #' @return (sf data.frame) flowlines with new POI_IDs identified (elev_POI_ID)
    split_elev <- function(in_POI_ID, split_DF, elev_diff, max_DA){
      
      # subset to a given POI_ID
      split_elev_DF <- filter(split_DF, POI_ID == in_POI_ID) %>%
        mutate(inc_seg_area = c(TotDASqKM[1], (TotDASqKM - lag(TotDASqKM))[-1]))
      
      first_iter <- unique(split_elev_DF$iter)
      # Iterate through segs that need splitting
    
    Bock, Andy's avatar
    Bock, Andy committed
      for (i in c(first_iter:(max(split_DF$iter, na.rm = T) + 10))){
    
        #print(i)
        
        # first split
        elev_pois_init_iter <- split_elev_DF %>%
          # filter by iteration, comes into play when multiple splits 
          #        are required per single POI_ID
          filter(iter == i) %>%
          # Recalculate inc elev, length, and area based on the last split
          mutate(csum_length = cumsum(LENGTHKM),
                 inc_elev = cumsum(MAXELEVSMO - MINELEVSMO),
                 sum_area = cumsum(inc_seg_area)) %>%
          # Determine if split actually necessary on this iteration
          filter((max(MAXELEVSMO) - min(MINELEVSMO)) > (elev_diff), sum_area > max_DA) %>%
          # This is an important step, identify which row to split on based on incremental
          #      elevaton value.  Some flowlines exceed the elevation difference threshold
          # The 'split_index' values determiens which row to create a new POI on
          mutate(split_index = ifelse(nrow(filter(., inc_elev > (elev_diff))) == nrow(.), 
                                      1, nrow(filter(., inc_elev < (elev_diff))))) %>%
          # Take out if at last iteration to avoid duplicating existng POIs
          filter(!COMID == POI_ID)
        
        if(nrow(elev_pois_init_iter) == 0){
          #print("done")
          # Done iterating on possible splits
          return(split_elev_DF)}
        
        # Get the flowline POI
        elev_pois_init_pre <- elev_pois_init_iter %>%
          filter(row_number() == split_index) %>%
          mutate(new_POI_ID = COMID) %>%
          select(COMID, POI_ID, new_POI_ID, Hydroseq_cut = Hydroseq) 
        
        # Remaining rows downstream after split
        elev_pois_init_post <- elev_pois_init_iter %>%
          left_join(select(elev_pois_init_pre, -COMID), by = "POI_ID") %>%
          filter(Hydroseq < Hydroseq_cut) %>%
          ungroup() 
        
        # Test for if we need to split again
        if(nrow(elev_pois_init_post) > 0){
          #print("yope")
          # Re-cacl the iter based on results above
          split_elev_DF <- split_elev_DF %>%
            left_join(select(elev_pois_init_pre, -COMID), by = "POI_ID") %>%
            mutate(elev_POI_ID = ifelse((is.na(elev_POI_ID) & Hydroseq >= Hydroseq_cut), new_POI_ID, elev_POI_ID),
                   iter = ifelse((!is.na(new_POI_ID) & Hydroseq < Hydroseq_cut), iter + 1, orig_iter)) %>%
            select(-c(new_POI_ID, Hydroseq_cut)) %>%
            ungroup()
          
        } else {
          split_elev_DF <- split_elev_DF %>%
            left_join(select(elev_pois_init_pre, -COMID), by = "POI_ID") %>%
            mutate(elev_POI_ID = ifelse((is.na(elev_POI_ID) & Hydroseq >= Hydroseq_cut), new_POI_ID, elev_POI_ID),
                   iter = ifelse((!is.na(new_POI_ID) & Hydroseq < Hydroseq_cut), 0, orig_iter)) %>%
            select(-c(new_POI_ID, Hydroseq_cut)) %>%
            ungroup()
          
        }
      }
      return(split_elev_DF)
    }
    
    Bock, Andy's avatar
    Bock, Andy committed
    #'  Splits a network based on estimated travel time and drainage area
    #'  @param in_POI_ID (integer) POI_ID of aggregated flowline that needs to be
    #'                   split based on travel time
    #'  @param split_DF  (sf data.frame) flowlines attributed with POI_ID
    #'  @param tt_diff (numeric) max travel time change threshold within a segment
    #'  @param max_DA (numeric) minimum drainage area for resulting split
    #' 
    #' @return (sf data.frame) flowlines with new POI_IDs identified (elev_POI_ID)
    split_tt <- function(in_POI_ID, split_DF, tt_diff, max_DA){
      
      # subset to a given POI_ID
      split_tt_DF <- filter(split_DF, POI_ID == in_POI_ID) %>%
        mutate(inc_seg_area = c(TotDASqKM[1], (TotDASqKM - lag(TotDASqKM))[-1]))
      
      first_iter <- unique(split_tt_DF$iter)
      # Iterate through segs that need splitting
      for (i in c(first_iter:max(split_DF$iter, na.rm = T))){
        #print(i)
        
        # first split
        tt_pois_init_iter <- split_tt_DF %>%
          # filter by iteration, comes into play when multiple splits 
          #        are required per single POI_ID
          filter(iter == i) %>%
          # Recalculate inc elev, length, and area based on the last split
          mutate(csum_length = cumsum(LENGTHKM), 
                 cumsum_tt = cumsum(FL_tt_hrs),
                 sum_area = cumsum(inc_seg_area)) %>%
          # Determine if split actually necessary on this iteration
          filter(sum(FL_tt_hrs) > tt_diff, TotDASqKM > max_DA) %>%
          # This is an important step, identify which row to split on based on incremental
          #      elevaton value.  Some flowlines exceed the elevation difference threshold
          # The 'split_index' values determiens which row to create a new POI on
          mutate(split_index = ifelse(nrow(filter(., cumsum_tt > (tt_diff))) == nrow(.), 
                                      1, nrow(filter(., cumsum_tt < (tt_diff))))) %>%
          # Take out if at last iteration to avoid duplicating existng POIs
          filter(!COMID == POI_ID)
        
    
    Bock, Andy's avatar
    Bock, Andy committed
        if(nrow(tt_pois_init_iter) == 0){
    
    Bock, Andy's avatar
    Bock, Andy committed
          #print("done")
          # Done iterating on possible splits
    
    Blodgett, David L.'s avatar
    Blodgett, David L. committed
          return(split_tt_DF)}
    
    Bock, Andy's avatar
    Bock, Andy committed
        
        # Get the flowline POI
        tt_pois_init_pre <- tt_pois_init_iter %>%
          filter(row_number() == split_index) %>%
          mutate(new_POI_ID = COMID) %>%
          select(COMID, POI_ID, new_POI_ID, Hydroseq_cut = Hydroseq) 
        
        # Remaining rows downstream after split
        tt_pois_init_post <- tt_pois_init_iter %>%
          left_join(select(tt_pois_init_pre, -COMID), by = "POI_ID") %>%
          filter(Hydroseq < Hydroseq_cut) %>%
          ungroup() 
        
        # Test for if we need to split again
        if(nrow(tt_pois_init_post) > 0){
          #print("yope")
          # Re-cacl the iter based on results above
          split_tt_DF <- split_tt_DF %>%
            left_join(select(tt_pois_init_pre, -COMID), by = "POI_ID") %>%
    
    Bock, Andy's avatar
    Bock, Andy committed
            mutate(tt_POI_ID = ifelse((is.na(tt_POI_ID) & Hydroseq >= Hydroseq_cut), new_POI_ID, tt_POI_ID),
    
    Bock, Andy's avatar
    Bock, Andy committed
                   iter = ifelse((!is.na(new_POI_ID) & Hydroseq < Hydroseq_cut), iter + 1, orig_iter)) %>%
            select(-c(new_POI_ID, Hydroseq_cut)) %>%
            ungroup()
          
        } else {
          split_tt_DF <- split_tt_DF %>%
            left_join(select(tt_pois_init_pre, -COMID), by = "POI_ID") %>%
            mutate(tt_POI_ID = ifelse((is.na(tt_POI_ID) & Hydroseq >= Hydroseq_cut), new_POI_ID, tt_POI_ID),
                   iter = ifelse((!is.na(new_POI_ID) & Hydroseq < Hydroseq_cut), 0, orig_iter)) %>%
            select(-c(new_POI_ID, Hydroseq_cut)) %>%
            ungroup()
          
        }
      }
      return(split_tt_DF)
    }
    
    
    
    #'  Retrieves waterbodies from NHDArea layer or NHD Hi-Res for ResOpsUs 
    #'  locations if absent from NHD waterbody
    #'  @param nhd (sf data.frame) flowlines for given VPU
    #'  @param WBs  (sf data.frame) waterbodys for discretization within VPU
    #'  @param data_paths (list) data paths to data layers
    #' 
    #'  @return (list) wbs - sf data frame for NHDArea and HR waterbodies
    #'                 wb_table - table of flowlines and outlet info for each 
    #'                            feature in wb
    
    Bock, Andy's avatar
    Bock, Andy committed
    HR_Area_coms <- function(nhd, WBs, data_paths, crs){
    
      # Read in resops table
      resops_wb_df <- read.csv(data_paths$resops_NID_CW)
      
      # Pull out rows for VPU that are NHDArea
      nhd_area_resops <- resops_wb_df %>%
        filter(FlowLcomid %in% nhd$COMID, COMI_srclyr == "NHDAREA")
      
      # Pull out rows for VPU that are NHD HR
      nhd_hr_wb_resops <-  resops_wb_df %>%
        filter(FlowLcomid %in% nhd$COMID, COMI_srclyr == "HR ID AVAILABLE")
      
      # Get reachcodes for waterbody outlets, so we have an idea of which
      #     NHD HR 4-digit geodatabase we may need to retrieve
      RC <- filter(nhd, COMID %in% c(nhd_area_resops$FlowLcomid, 
                                     nhd_hr_wb_resops$FlowLcomid))$REACHCODE
      
      # If no NHDArea or HR waterbodies needed return NULL
      if (nrow(nhd_area_resops) == 0 & nrow(nhd_hr_wb_resops) == 0){
        return(NA)
      }
      
      # If NHDArea feature needed retrieve from National GDB
      if (nrow(nhd_area_resops) > 0){
        nhd_area_vpu <- read_sf(data_paths$nhdplus_gdb, "NHDArea") %>%
          filter(COMID %in% nhd_area_resops$NHDAREA) %>%
          mutate(source = "NHDv2Area")
        
    
    Bock, Andy's avatar
    Bock, Andy committed
        wb <- st_transform(nhd_area_vpu, crs)
    
      }
      
      # If NHDHR feature needed
      if (nrow(nhd_hr_wb_resops) > 0){
        # HUC04 we need to download
        huc04 <- substr(RC, 1, 4)
        
        # Download NHD HR HUC04 if we dont' have it, other wise load and
        # Bind NHDHR waterbodies into one layer
        hr_wb <- do.call("rbind", lapply(unique(huc04), function(x){
          
          if(!file.exists(file.path(data_paths$nhdplus_dir, vpu, 
                                    paste0("NHDPLUS_H_", x, "_HU4_GDB.gdb")))){
            download_nhdplushr(data_paths$nhdplus_dir, unique(huc04))
          }
          
          # Format to similar to NHDArea/Waterbody layers
          read_sf(file.path(data_paths$nhdplus_dir, substr(vpu, 1, 2), 
                            paste0("NHDPLUS_H_", x, "_HU4_GDB.gdb")), "NHDWaterbody")})) %>%
          filter(NHDPlusID %in% nhd_hr_wb_resops$HR_NHDPLUSID) %>%
          rename(COMID = NHDPlusID, GNIS_NAME = GNIS_Name, RESOLUTION = Resolution, AREASQKM = AreaSqKm, ELEVATION = Elevation,
                 FTYPE = FType, FCODE = FCode, FDATE = FDate, REACHCODE = ReachCode) %>%
          select(-c(Permanent_Identifier, VisibilityFilter, VPUID)) %>%
          st_zm(.) %>%
          st_as_sf() %>%
    
    Bock, Andy's avatar
    Bock, Andy committed
          mutate(source = "NHDHR") %>%
          st_transform(crs)
    
        
        # Bind or create new object
        if(exists("wb")){
          wb <- data.table::rbindlist(list(wb, hr_wb), fill = TRUE) %>%
            st_as_sf()
        } else {
          wb <- hr_wb
        }
      }
      
      # get the outlt rows from the table
      resops_outlet <- filter(resops_wb_df, NHDAREA %in% wb$COMID | HR_NHDPLUSID %in% wb$COMID)
      
      # Create table of all flowlines that intersect the waterbody
      nhd_wb <- st_intersects(nhd, wb) 
      comid <- nhd[lengths(nhd_wb) > 0,]$COMID
      nhd_wb_all <- nhd_wb[lengths(nhd_wb) > 0] %>%
        purrr::set_names(comid) %>%
        stack() %>%
        # Ind is the default name for the set_names
        rename(comid = ind, nhd_wb = values) %>%
        mutate(wb_comid = wb[nhd_wb,]$COMID,
               outlet = ifelse(comid %in% resops_outlet$FlowLcomid, "outlet", NA),
               comid = as.integer(as.character(comid))) %>%
        left_join(select(resops_wb_df, DAM_ID, DAM_NAME, FlowLcomid), by = c("comid" = "FlowLcomid")) %>%
        left_join(select(st_drop_geometry(wb), COMID, source), by = c("wb_comid" = "COMID"))
      
      return(list(wb_table = nhd_wb_all, wb = wb))
    }
    
    #'  Creates wb inlet and outlet events for splitting in hyRefactor
    #'          for waterbodies derived from NHDArea and NHDHR waterbodies
    #'  @param WBs  (sf data.frame) return from HR_Area_coms
    #'  @param nhd_wb (sf data.frame) flowlines that intersect waterbodies
    #'  @param type (character) whether to derive inlet or outlet points
    #' 
    #'  @return (sf data.frame) dataframe of WB inlet and outlet points to split
    #'  
    WB_event <- function(WBs, nhd_wb, type){
      # split into features and table
    
      WBs_table <- WBs$wb_table
      WBs_layer <- WBs$wb
    
      
      if (type == "outlet"){
        # get the outlet comid from the ResOps Table
        outlet_fl <- filter(nhd_wb, COMID %in% filter(WBs_table, outlet == "outlet")$comid)
        
        # Get the downstream flowline for continuity
        ds_fl <- filter(nhd_wb, DnHydroseq %in% outlet_fl$Hydroseq,
                        LevelPathI %in% outlet_fl$LevelPathI) %>%
          rbind(outlet_fl) %>%
          group_by(WBAREACOMI) %>%
          # union together 
          summarize(do_union = T)
        
        WBs_area <- filter(WBs_layer, source == "NHDv2Area")
        WBs_HR <- filter(WBs_layer, source == "NHDHR")
        
        # For NHDArea waterbodies (better congruity with th flowline st)
        if (nrow(WBs_area) > 0){
          
          # Intersect unioned FL with waterbody and get intersecting point
          outlet_pnts <- sf::st_intersection(ds_fl, WBs_area) %>%
            # Cast to point
            st_cast("POINT") %>%
            group_by(WBAREACOMI) %>%
            # Get furthest downstream point of intersection
            filter(row_number() == max(row_number(), na.rm = T)) %>%
            ungroup()
          
          # Derive event from point to use for splitting within hyRefactor
          wb_events <- get_flowline_index(nhd_wb, outlet_pnts) %>%
            inner_join(select(st_drop_geometry(nhd_wb), COMID, WBAREACOMI), by = "COMID") %>%
            mutate(event_type = type) %>%
            cbind(select(outlet_pnts, geom)) %>%
            st_as_sf()
        }
        
        # For NHD HR waterbody outlets its a bit more complicated
        if (nrow(WBs_HR) > 0){
          # Get the flowlines intersecting the HR waterbody and find one with the
          #     max DA
          outlet_wb_int <- nhd_wb[lengths(st_intersects(nhd_wb, WBs_HR)) > 0,] %>%
            group_by(WBAREACOMI) %>%
            filter(TotDASqKM == max(TotDASqKM)) %>%
            ungroup() 
          
          # get the ds flo with the same levepath (JIC)
          ds_fl <- filter(nhd_wb, DnHydroseq %in% outlet_wb_int$Hydroseq, 
                          LevelPathI %in% outlet_wb_int$LevelPathI)
          
          outlet_fl <- rbind(outlet_wb_int, ds_fl)
          
          # Cast flowlines within NHDHR waterbody to point
          WB_FL_pnts <- outlet_wb_int %>%
            st_cast("POINT") %>%
            group_by(WBAREACOMI) %>%
            mutate(pnt_id = row_number()) %>%
            ungroup()
          
          # Determine which points intersect waterbody
          WB_outlet_pnts <- WB_FL_pnts[lengths(st_intersects(WB_FL_pnts, WBs_HR)) > 0,] %>%
            st_drop_geometry() %>%
            group_by(WBAREACOMI) %>%
            mutate(within_wb_id = row_number()) %>%
            filter(within_wb_id >= max(within_wb_id)) %>%
            ungroup() %>%
            select(WBAREACOMI, orig_pnt_id = pnt_id, within_wb_id)
          
          # Deriv new linestring by concating points from most upstream point
          #       within waterbody to downstream so we can split at FL/waterbody
          #       nexus
          outlet_FL <- WB_FL_pnts %>%
            inner_join(WB_outlet_pnts, by = "WBAREACOMI") %>%
            select(WBAREACOMI, pnt_id, orig_pnt_id, within_wb_id) %>%
            filter(pnt_id >= orig_pnt_id) %>%
            group_by(WBAREACOMI) %>%
            summarize(do_union = F) %>%
            st_cast("LINESTRING")
          
          # now run the intersection with the modified flowline
          outlet_pnts <- sf::st_intersection(outlet_FL, WBs_HR) %>%
            st_cast("POINT") %>%
            group_by(WBAREACOMI) %>%
            filter(row_number() == max(row_number(), na.rm = T)) %>%
            ungroup()
          
          # Derive the events
          if(exists("wb_events")){
            hr_events <- get_flowline_index(nhd_wb, outlet_pnts) %>%
              inner_join(select(st_drop_geometry(nhd_wb), COMID, WBAREACOMI), by = "COMID") %>%
              mutate(event_type = type) %>%
              cbind(select(outlet_pnts, geom)) %>%
              st_as_sf()
            
            wb_events <- rbind(wb_events, hr_events)
          } else {
            wb_events <- get_flowline_index(nhd_wb, outlet_pnts) %>%
              inner_join(select(st_drop_geometry(nhd_wb), COMID, WBAREACOMI), by = "COMID") %>%
              mutate(event_type = type) %>%
              cbind(select(outlet_pnts, geom)) %>%
              st_as_sf()
          }
          
        }
        
      # For inlet points its alot easier for both NHDARea and NHDHR
      } else {
        start_pts <- get_node(nhd_wb, position = "start") %>%
          cbind(st_drop_geometry(nhd_wb))
        
        inlet_FL <- nhd_wb[lengths(st_intersects(start_pts, WBs_layer)) == 0,] %>%
          rbind(filter(nhd_wb, Hydroseq %in% .$DnHydroseq, 
                       LevelPathI %in% .$LevelPathI))
        
        inlet_ls <- inlet_FL %>%
          group_by(LevelPathI) %>%