diff --git a/R/max_coverage.R b/R/max_coverage.R index 36e22ac..1349f6a 100644 --- a/R/max_coverage.R +++ b/R/max_coverage.R @@ -18,6 +18,12 @@ #' you are interested in. If a number is less than distance_cutoff, it will be #' 1, if it is greater than it, it will be 0. #' @param n_added the maximum number of facilities to add. +#' @param d_existing_user Optional distance matrix between existing facilities +#' and users. Default distances are direct (geospherical ellipsoidal) distances; +#' this allows alternative measures such as street-network distances to be +#' submitted (see Examples). +#' @param d_proposed_user Option distance matrix between proposed facilities and +#' users (see Examples). #' @param solver character "glpk" (default) or "lpSolve". "gurobi" is currently #' in development, see #' @@ -52,12 +58,36 @@ #' # get the summaries #' mc_result$summary #' +#' # Example of street-network distance calculations +#' \dontrun{ +#' library(dodgr) +#' net <- dodgr_streetnet_sf ("york england") %>% +#' weight_streetnet (wt_profile = "foot") +#' +#' from <- match_points_to_graph (v, york_selected [, c ("long", "lat")]) +#' to <- match_points_to_graph (v, york_crime [, c ("long", "lat")]) +#' d_existing_user <- dodgr_dists (net, from = from, to = to) +#' +#' from <- match_points_to_graph (v, york_unselected [, c ("long", "lat")]) +#' d_proposed_user <- dodgr_dists (net, from = from, to = to) +#' +#' mc_result <- max_coverage(existing_facility = york_selected, +#' proposed_facility = york_unselected, +#' user = york_crime, +#' distance_cutoff = 100, +#' n_added = 20, +#' d_existing_user = d_existing_user, +#' d_proposed_user = d_proposed_user) +#' +#' } #' @export max_coverage <- function(existing_facility, proposed_facility, user, distance_cutoff, n_added, + d_existing_user = NULL, + d_proposed_user = NULL, solver = "glpk"){ # give user an ID @@ -65,11 +95,13 @@ max_coverage <- function(existing_facility, user_not_covered <- find_users_not_covered(existing_facility, user, - distance_cutoff) + distance_cutoff, + d_existing_user = d_existing_user) A <- binary_distance_matrix(facility = proposed_facility, user = user_not_covered, - distance_cutoff = distance_cutoff) + distance_cutoff = distance_cutoff, + d_proposed_user = d_proposed_user) colnames(A) <- 1:nrow(proposed_facility) user_id_list <- 1:nrow(user_not_covered) diff --git a/R/utils.R b/R/utils.R index 7659d8f..6b9c5df 100644 --- a/R/utils.R +++ b/R/utils.R @@ -202,19 +202,28 @@ nearest_facility_distances <- function(existing_facility, #' 0 otherwise. binary_distance_matrix <- function(facility, user, - distance_cutoff){ - - facility_cpp <- facility %>% - dplyr::select(lat, long) %>% - as.matrix() - - user_cpp <- user %>% - dplyr::select(lat, long) %>% - as.matrix() - - A <- maxcovr::binary_matrix_cpp(facility = facility_cpp, - user = user_cpp, - distance_cutoff = distance_cutoff) + distance_cutoff, + d_proposed_user = NULL){ + + if (is.null (d_proposed_user)){ + facility_cpp <- facility %>% + dplyr::select(lat, long) %>% + as.matrix() + + user_cpp <- user %>% + dplyr::select(lat, long) %>% + as.matrix() + + A <- maxcovr::binary_matrix_cpp(facility = facility_cpp, + user = user_cpp, + distance_cutoff = distance_cutoff) + } else { + # reduce d_proposed_user down to submitted `user_not_covered`: + d_proposed_user <- d_proposed_user [, user_not_covered$user_id] + d_proposed_user [is.na (d_proposed_user)] <- + max (d_proposed_user, na.rm = TRUE) + A <- t (d_proposed_user < distance_cutoff) + } return(A) @@ -225,20 +234,39 @@ binary_distance_matrix <- function(facility, #' @param existing_facility data.frame of existing facilities #' @param user data.frame of existing users #' @param distance_cutoff integer of distance cutoff +#' @param d_existing_user Optional distance matrix between existing facilities +#' and users. #' #' @return data.frame of those users not covered by current facilities find_users_not_covered <- function(existing_facility, user, - distance_cutoff){ - - - # make nearest dist into dataframe - dat_nearest_no_cov <- nearest_facility_distances( - existing_facility = existing_facility, - user = user) %>% - # leave only those not covered - dplyr::filter(distance > distance_cutoff) - + distance_cutoff, + d_existing_user = NULL){ + + + if (is.null (d_existing_user)){ + # make nearest dist into dataframe + dat_nearest_no_cov <- nearest_facility_distances( + existing_facility = existing_facility, + user = user) %>% + # leave only those not covered + dplyr::filter(distance > distance_cutoff) + + } else { + if (nrow (d_existing) != nrow (existing_facility) | + ncol (d_existing) != nrow (user)) + stop ("'d_existing_user' must have same number of rows as 'user',", + " and same number of columns as 'existing_facility'") + + d_existing_user [is.na(d_existing_user)] <- + max(d_existing_user, na.rm = TRUE) + index <- which(apply(d_existing_user, 2, min) > distance_cutoff) + nearest_facility <- t(apply(d_existing_user [, index], 2, + function(i) c(which.min(i), min(i)))) + dat_nearest_no_cov <- tibble::tibble (user_id = index, + facility_id = nearest_facility[, 1], + distance = nearest_facility[, 2]) + } user_not_covered <- dplyr::left_join(dat_nearest_no_cov, user, by = "user_id") diff --git a/man/binary_distance_matrix.Rd b/man/binary_distance_matrix.Rd index d10dcee..ed61e3d 100644 --- a/man/binary_distance_matrix.Rd +++ b/man/binary_distance_matrix.Rd @@ -4,7 +4,8 @@ \alias{binary_distance_matrix} \title{(Internal) Create a binary distance matrix} \usage{ -binary_distance_matrix(facility, user, distance_cutoff) +binary_distance_matrix(facility, user, distance_cutoff, + d_proposed_user = NULL) } \arguments{ \item{facility}{data.frame of facilities} diff --git a/man/find_users_not_covered.Rd b/man/find_users_not_covered.Rd index a317dc2..7968927 100644 --- a/man/find_users_not_covered.Rd +++ b/man/find_users_not_covered.Rd @@ -4,7 +4,8 @@ \alias{find_users_not_covered} \title{(Internal) Create a dataframe of the users not covered} \usage{ -find_users_not_covered(existing_facility, user, distance_cutoff) +find_users_not_covered(existing_facility, user, distance_cutoff, + d_existing_user = NULL) } \arguments{ \item{existing_facility}{data.frame of existing facilities} @@ -12,6 +13,9 @@ find_users_not_covered(existing_facility, user, distance_cutoff) \item{user}{data.frame of existing users} \item{distance_cutoff}{integer of distance cutoff} + +\item{d_existing_user}{Optional distance matrix between existing facilities +and users.} } \value{ data.frame of those users not covered by current facilities diff --git a/man/max_coverage.Rd b/man/max_coverage.Rd index 541ce93..9402a53 100644 --- a/man/max_coverage.Rd +++ b/man/max_coverage.Rd @@ -5,7 +5,8 @@ \title{Solve the Maximal Covering Location Problem} \usage{ max_coverage(existing_facility, proposed_facility, user, distance_cutoff, - n_added, solver = "glpk") + n_added, d_existing_user = NULL, d_proposed_user = NULL, + solver = "glpk") } \arguments{ \item{existing_facility}{data.frame containing the facilities that are @@ -23,6 +24,14 @@ you are interested in. If a number is less than distance_cutoff, it will be \item{n_added}{the maximum number of facilities to add.} +\item{d_existing_user}{Optional distance matrix between existing facilities +and users. Default distances are direct (geospherical ellipsoidal) distances; +this allows alternative measures such as street-network distances to be +submitted (see Examples).} + +\item{d_proposed_user}{Option distance matrix between proposed facilities and +users (see Examples).} + \item{solver}{character "glpk" (default) or "lpSolve". "gurobi" is currently in development, see \url{https://github.com/njtierney/maxcovr/issues/25}} } @@ -67,4 +76,26 @@ mc_result$user_affected # get the summaries mc_result$summary +# Example of street-network distance calculations +\dontrun{ +library(dodgr) +net <- dodgr_streetnet_sf ("york england") \%>\% + weight_streetnet (wt_profile = "foot") + +from <- match_points_to_graph (v, york_selected [, c ("long", "lat")]) +to <- match_points_to_graph (v, york_crime [, c ("long", "lat")]) +d_existing_user <- dodgr_dists (net, from = from, to = to) + +from <- match_points_to_graph (v, york_unselected [, c ("long", "lat")]) +d_proposed_user <- dodgr_dists (net, from = from, to = to) + +mc_result <- max_coverage(existing_facility = york_selected, + proposed_facility = york_unselected, + user = york_crime, + distance_cutoff = 100, + n_added = 20, + d_existing_user = d_existing_user, + d_proposed_user = d_proposed_user) + +} }