Nothing
      #' @title worker function for K-nearest points on network
#'
#' @description The worker the K-nearest points for a set of points on a network.
#'
#' @param points A feature collection of points, for each point, its k nearest
#' neighbours will be found on the network.
#' @param lines A feature collection of lines representing the network
#' @param k An integer indicating the number of neighbours to find..
#' @param direction Indicates a field providing information about authorized
#' travelling direction on lines. if NULL, then all lines can be used in both
#' directions. Must be the name of a column otherwise. The values of the
#' column must be "FT" (From - To), "TF" (To - From) or "Both".
#' @param use_dest A boolean indicating if the origins and separations are
#' separated (TRUE), FALSE if only origins are used.
#' @param verbose A Boolean indicating if the function should print its
#' progress
#' @param digits The number of digits to retain in the spatial coordinates (
#' simplification used to reduce risk of topological error)
#' @param tol A float indicating the spatial tolerance when points are
#' added as vertices to lines.
#' @return A list with two matrices, one with the index of the neighbours and
#' one with the distances.
#' @importFrom sf st_drop_geometry
#' @keywords internal
#' @examples
#' #no example provided, this is an internal function
network_knn_worker <- function(points, lines, k, direction = NULL, use_dest = FALSE, verbose = verbose, digits = digits, tol=tol){
  ## step1 : adding the points to the lines
  lines$worker_id <- 1:nrow(lines)
  points$nearest_line_id <- as.numeric(as.character(points$nearest_line_id))
  joined <- data.table(st_drop_geometry(points))
  B <- data.table(st_drop_geometry(lines))
  joined[B,  on = c("nearest_line_id" = "tmpid"),
         names(B) := mget(paste0("i.", names(B)))]
  ## step2 : adding the points to the lines
  if(verbose){
    print("adding the points as vertices to nearest lines")
  }
  graph_lines <- split_lines_at_vertex(lines, points, joined$worker_id, 1)
  graph_lines$lx_length <- as.numeric(st_length(graph_lines))
  ## step4 : building the graph
  if(verbose){
    print("build the local graph")
  }
  graph_lines$lx_weight <- (graph_lines$lx_length / graph_lines$line_length) * graph_lines$line_weight
  if (is.null(direction)){
    result_graph <- build_graph(graph_lines, digits = digits,
                                attrs = TRUE, line_weight = "lx_weight")
  }else{
    #dir <- ifelse(graph_lines[[direction]]=="Both",0,1)
    #graph_lines$direction <- graph_lines[[direction]]
    result_graph <- build_graph_directed(graph_lines, digits = digits,
                                         attrs = TRUE, line_weight='lx_weight',
                                         direction = direction)
  }
  points$vertex <- closest_points(points,result_graph$spvertices)
  if(use_dest) {
    endV <- unique(subset(points, points$type == "destination")$vertex)
    startV <- unique(subset(points, points$pttype == "start" & points$type == "origin")$vertex)
  }else{
    endV <- unique(points$vertex)
    startV <- unique(subset(points, points$pttype == "start")$vertex)
  }
  ## step4 : calculating the distances between each vertex
  if(verbose){
    print("calculating the distances on the graph")
  }
  distmat <- igraph::distances(result_graph$graph,
                               mode = "out", v = startV, to = endV)
  rownames(distmat) <- startV
  colnames(distmat) <- endV
  points$ch_vertex <- as.character(points$vertex)
  ## step5 find for each observation its n nearest neighbours
  ok_points <- subset(points, points$type == "origin" & points$pttype == "start")
  raiseWarning <- FALSE
  ok_pt_data <- st_drop_geometry(ok_points)
  values <- lapply(1:nrow(ok_points), function(i){
    row <- ok_pt_data[i,]
    vert <- row$ch_vertex
    dists <- distmat[vert,]
    bests <- sort(dists)[1:(k+1)]
    sub <- subset(points, points$ch_vertex %in% names(bests) & points$oids != row$oids)
    distsf <- bests[sub$ch_vertex]
    fids <- sub$oids
    fids <- fids[order(distsf)]
    distsf <- distsf[order(distsf)]
    n <- length(fids)
    if(n == 0){
      fids <- rep(NA,(k))
      distsf <- rep(NA,(k))
    }else{
      if(n < k){
        fids <- c(fids, rep(NA,(k-length(fids))))
        distsf <- c(distsf, rep(NA,(k-length(distsf))))
      }
      if(n>k){
        raiseWarning <<- TRUE
        fids <- fids[1:k]
        distsf <- distsf[1:k]
      }
    }
    return(list(fids, distsf))
  })
  if(raiseWarning){
    warning("Several points share the exact same location, see details of the function network_knn for more information")
  }
  ## creating matrices
  matdists <- do.call(rbind,lapply(values, function(l){
    return(l[[2]])
  }))
  matoids <- do.call(rbind,lapply(values, function(l){
    return(l[[1]])
  }))
  rownames(matdists) <- ok_points$oids
  rownames(matoids) <- ok_points$oids
  return (list(matdists, matoids))
}
#' @title K-nearest points on network
#'
#' @description Calculate the K-nearest points for a set of points on a network.
#'
#' @template knn-args
#'
#' @details The k nearest neighbours of each point are found by using the network distance.
#' The results could not be exact if some points share the exact same location. As an example,
#' consider the following case. If A and B are two points at the exact same location, and C is
#' a third point close to A and B. If the 1 nearest neighbour is requested for C, the function
#' could return either A or B but not both. When such situation happens, a warning is raised by
#' the function.
#'
#' @return A list with two matrices, one with the index of the neighbours and
#' one with the distances.
#' @importFrom sf st_coordinates st_length st_buffer st_intersects
#' @importFrom utils txtProgressBar setTxtProgressBar
#' @export
#' @examples
#' \donttest{
#'     data(main_network_mtl)
#'     data(mtl_libraries)
#'     results <- network_knn(mtl_libraries, main_network_mtl,
#'         k = 3, maxdistance = 1000, line_weight = "length",
#'         grid_shape=c(1,1), verbose = FALSE)
#' }
network_knn <- function(origins, lines, k, destinations = NULL, maxdistance = 0, snap_dist=Inf, line_weight = "length", direction=NULL, grid_shape=c(1,1), verbose = FALSE, digits = 3, tol=0.1){
  ## quick sanity check before starting
  sanity_check_knn(origins = origins, destinations = destinations,
                   lines = lines, k = k, maxdistance = maxdistance,
                   snap_dist = snap_dist, line_weight = line_weight,
                   direction = direction, grid_shape = grid_shape,
                   verbose = verbose, digits = digits, tol = tol)
  ## step0 adjusting the weights of the lines
  lines$tmpid <- 1:nrow(lines)
  lines$line_length <- as.numeric(st_length(lines))
  if(line_weight=="length"){
    lines$line_weight <- as.numeric(st_length(lines))
  }else {
    lines$line_weight <- lines[[line_weight]]
  }
  if(min(lines$line_weight)<=0){
    stop("the weights of the lines must be superior to 0")
  }
  ## step1 adjusting the directions of the lines
  if(is.null(direction) == FALSE){
    lines <- lines_direction(lines,direction)
  }
  ## step2 snap points on the lines
  if(is.null(destinations)){
    use_dest <- FALSE
    origins$type <- "origin"
    origins$base_oid <- 1:nrow(origins)
    comb_pts <- origins[c("base_oid","type")]
  }else{
    use_dest <- TRUE
    destinations$base_oid <- 1:nrow(destinations)
    destinations$type <- "destination"
    origins$base_oid <- 1:nrow(origins)
    origins$type <- "origin"
    comb_pts <- rbind(origins[c("base_oid","type")], destinations[c("base_oid","type")])
  }
  if(verbose){
    print("snapping the points to the lines (only once)")
  }
  snapped_points <- snapPointsToLines2(comb_pts,lines, idField="tmpid")
  ## step 3 building grid
  grid <- build_grid(grid_shape,list(comb_pts,lines))
  if(verbose){
    print("preparing the data in the grid")
  }
  ids <- 1:nrow(grid)
  list_elements <- prepare_elements_netlistw(ids,grid,snapped_points,lines,maxdistance)
  ## step7 iterating over the grid
  listvalues <- lapply(1:nrow(grid),function(i){
    quadra <- grid[i,]
    if(verbose){
      print(paste("working on quadra : ",i,"/",nrow(grid),sep=""))
    }
    elements <- list_elements[[i]]
    if(length(elements)==0){
      return()
    }else {
      all_pts <- elements[[1]]
      selected_lines <- elements[[2]]
      #calculating the elements
      values <- network_knn_worker(points = all_pts, lines = selected_lines, k = k, direction=direction,
                                    use_dest = use_dest,
                                    verbose = verbose, digits = digits, tol=tol)
      return(values)
    }
  })
  ## step8 combining the results in two global matrices
  okvalues <- listvalues[lengths(listvalues) != 0]
  matdists <- do.call(rbind, lapply(okvalues, function(l){
    return(l[[1]])
  }))
  matoids <- do.call(rbind, lapply(okvalues, function(l){
    return(l[[2]])
  }))
  matoids <- matoids[order(as.numeric(rownames(matoids))),]
  matdists <- matdists[order(as.numeric(rownames(matdists))),]
  ## dealing with special cases
  if(k == 1){
    matoids <- matrix(matoids, ncol = 1)
    matdists <- matrix(matdists, ncol = 1)
  }
  if (use_dest){
    matoids <- matoids - (nrow(origins))
  }
  return(list("distances" = matdists,
              "ids" = matoids))
}
#' @title K-nearest points on network (multicore version)
#'
#' @description Calculate the K-nearest points for a set of points on a network with multicore support.
#'
#'@template knn-args
#'
#' @return A list with two matrices, one with the index of the neighbours and
#' one with the distances.
#' @importFrom utils txtProgressBar setTxtProgressBar
#' @export
#' @examples
#' \donttest{
#' data(main_network_mtl)
#' data(mtl_libraries)
#' future::plan(future::multisession(workers=1))
#' results <- network_knn.mc(mtl_libraries, main_network_mtl,
#'     k = 3, maxdistance = 1000, line_weight = "length",
#'     grid_shape=c(1,1), verbose = FALSE)
#' ## make sure any open connections are closed afterward
#' if (!inherits(future::plan(), "sequential")) future::plan(future::sequential)
#' }
network_knn.mc <- function(origins, lines, k, destinations = NULL, maxdistance = 0, snap_dist = Inf, line_weight = "length", direction=NULL, grid_shape=c(1,1), verbose = FALSE, digits = 3, tol=0.1){
  ## quick sanity check before starting
  sanity_check_knn(origins, destinations,
                   lines, k, maxdistance, snap_dist,
                   line_weight, direction, grid_shape,
                   verbose, digits, tol)
  ## step0 adjusting the weights of the lines
  lines$tmpid <- 1:nrow(lines)
  lines$line_length <- as.numeric(st_length(lines))
  if(line_weight=="length"){
    lines$line_weight <- as.numeric(st_length(lines))
  }else {
    lines$line_weight <- lines[[line_weight]]
  }
  if(min(lines$line_weight)<=0){
    stop("the weights of the lines must be superior to 0")
  }
  ## step1 adjusting the directions of the lines
  if(is.null(direction) == FALSE){
    lines <- lines_direction(lines,direction)
  }
  ## step2 snap points on the lines
  if(is.null(destinations)){
    use_dest <- FALSE
    origins$type <- "origin"
    origins$base_oid <- 1:nrow(origins)
    comb_pts <- origins[c("base_oid","type")]
  }else{
    use_dest <- TRUE
    destinations$base_oid <- 1:nrow(destinations)
    destinations$type <- "destination"
    origins$base_oid <- 1:nrow(origins)
    origins$type <- "origin"
    comb_pts <- rbind(origins[c("base_oid","type")], destinations[c("base_oid","type")])
  }
  if(verbose){
    print("snapping the points to the lines (only once)")
  }
  snapped_points <- snapPointsToLines2(comb_pts,lines, idField="tmpid")
  ## step 3 building grid
  grid <- build_grid(grid_shape,list(comb_pts,lines))
  if(verbose){
    print("preparing the data in the grid")
  }
  all_is <- 1:nrow(grid)
  iseq <- list()
  cnt <- 0
  for(i in 1:grid_shape[[1]]){
    start <- cnt*grid_shape[[2]]+1
    iseq[[length(iseq)+1]] <- list(cnt+1,all_is[start:(start+grid_shape[[2]]-1)])
    cnt<-cnt+1
  }
  listelements <- future.apply::future_lapply(iseq,function(ii){
    elements <- prepare_elements_netlistw(ii[[2]],grid,snapped_points,lines,maxdistance)
    return(elements)
  })
  listelements <- unlist(listelements,recursive = FALSE)
  ## step7 iterating over the grid
  listvalues <- future.apply::future_lapply(listelements,function(elements){
    ##step1 : preparing elements
    if(is.null(elements)){
      return()
    }else {
      all_pts <- elements[[1]]
      selected_lines <- elements[[2]]
      #calculating the elements
      values <- network_knn_worker(all_pts, selected_lines, k, direction = direction,
                                   use_dest = use_dest,
                                   verbose = verbose, digits = digits, tol=tol)
      return(values)
    }
  })
  ## step8 combining the results in two global matrices
  okvalues <- listvalues[lengths(listvalues) != 0]
  matdists <- do.call(rbind, lapply(okvalues, function(l){
    return(l[[1]])
  }))
  matoids <- do.call(rbind, lapply(okvalues, function(l){
    return(l[[2]])
  }))
  matoids <- matoids[order(as.numeric(rownames(matoids))),]
  matdists <- matdists[order(as.numeric(rownames(matdists))),]
  ## dealing with special cases
  if(k == 1){
    matoids <- matrix(matoids, ncol = 1)
    matdists <- matrix(matdists, ncol = 1)
  }
  if (use_dest){
    matoids <- matoids - (nrow(origins))
  }
  return(list("distances" = matdists,
              "ids" = matoids))
}
#' @title Sanity check for the knn functions
#'
#' @description Check if all the parameters are valid for the knn functions
#'
#' @param origins A a feature collection of points, for each point, its k nearest
#' neighbours will be found on the network.
#' @param destinations A a feature collection of points, might be used if the neighbours
#' must be found in a separate dataset. NULL if the neighbours must be found in
#' origins.
#' @param lines A a feature collection of linestrings representing the network
#' @param k An integer indicating the number of neighbours to find..
#' @param maxdistance The maximum distance between two observations to
#' consider them as neighbours. It is useful only if a grid is used, a
#' lower value will reduce calculating time, but one must be sure that the
#' k nearest neighbours are within this radius. Otherwise NAs will be present
#' in the final matrices.
#' @param snap_dist The maximum distance to snap the start and end points on
#' the network.
#' @param line_weight The weighting to use for lines. Default is "length"
#' (the geographical length), but can be the name of a column. The value is
#' considered proportional to the geographical length of the lines.
#' @param direction Indicates a field providing information about authorized
#' travelling direction on lines. if NULL, then all lines can be used in both
#' directions. Must be the name of a column otherwise. The values of the
#' column must be "FT" (From - To), "TF" (To - From) or "Both".
#' @param grid_shape A vector of length 2 indicating the shape of the grid to
#' use for splitting the dataset. Default is c(1,1), so all the calculation is
#' done in one go. It might be necessary to split it if the dataset is large.
#' @param verbose A Boolean indicating if the function should print its
#' progress
#' @param digits The number of digits to retain in the spatial coordinates (
#' simplification used to reduce risk of topological error)
#' @param tol A float indicating the spatial tolerance when points are
#' added as vertices to lines.
#' @return A list with two matrices, one with the index of the neighbours and
#' one with the distances.
#' @importFrom utils txtProgressBar setTxtProgressBar
#' @keywords internal
#' @examples
#' #no example provided, this is an internal function
sanity_check_knn <- function(origins, destinations, lines, k, maxdistance, snap_dist, line_weight, direction, grid_shape, verbose , digits, tol){ # nocov start
  ## check des types destinations, lines et origins
  if(class(origins)[[1]] != "sf"){
    stop("The origins must be a feature collection of points")
  }else{
    if(unique(st_geometry_type(origins)) != "POINT"){
      stop("The origins must be a feature collection of points")
    }
  }
  if(is.null(destinations) == FALSE){
    if(class(destinations)[[1]] != "sf"){
      stop("The destinations must be NULL or a feature collection of points")
    }else{
      if(unique(st_geometry_type(destinations)) != "POINT"){
        stop("The destinations must be NULL or a feature collection of points")
      }
    }
  }
  if(class(lines)[[1]] != "sf"){
    stop("The lines must be a feature collection of linestrings")
  }else{
    if(unique(st_geometry_type(lines)) != "LINESTRING"){
      stop("The lines must be a feature collection of linestrings")
    }
  }
  ## check des types numerics
  if((all.equal(k, as.integer(k))) == FALSE | k < 1){
    stop("The k parameter must be an integer >=1 ")
  }
  if(is.numeric(maxdistance) == FALSE | maxdistance < 0){
    stop("The maxdistance parameter must be a postive numeric")
  }
  if(is.numeric(snap_dist) == FALSE | snap_dist < 0){
    stop("The snap_dist parameter must be a postive numeric")
  }
  if(line_weight != "length" & line_weight %in% names(lines) == FALSE){
    stop("line_weight must be 'length' or a column in lines")
  }
  if(line_weight != "length"){
    if(is.numeric(lines[[line_weight]]) == FALSE){
      stop("line_weight must a numeric column in lines")
    }
  }
  ## check de directions
  if(is.null(direction) == FALSE){
    if(is.null(lines[[direction]])){
      stop("direction must be the name of a column in lines")
    }else{
      vals <-lines[[direction]]
      diffs <- union(unique(vals), c("TF","FT","Both"))
      if(length(diffs) != 3){
        stop("the values in the column direction must be in c('TF', 'FT', 'Both')")
      }
    }
  }
  ## check grid shape
  if(all(grid_shape == floor(grid_shape)) == FALSE){
    stop("the values in grid shape must all be integers")
  }
  if(length(grid_shape)!=2){
    stop("grid_shape must be a vector with a length of 2")
  }
  if(sum(grid_shape) > 2 & maxdistance == 0){
    stop("When a grid is used (grid_shape != c(1,1)), a maxdistance > 0 must be provided. It is used to ensure that neighbours under that distance are not missed even if the study area is split")
  }
  ## check the final parameters
  if(verbose %in% c(TRUE,FALSE) == FALSE){
    stop("verbose must be a boolean")
  }
  if((all.equal(digits, as.integer(digits))) == FALSE){
    stop("digits must be an integer")
  }
  if(is.numeric(tol) == FALSE){
    stop("tol must be a numeric")
  }
} # nocov end
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.