https://maxcovr.njtierney.com/index.html https://prioritizr.net/articles/publication_record.html https://dirkschumacher.github.io/ompr/articles/problem-warehouse-location.html https://github.com/njtierney/maxcovr https://pysal.org/spopt/notebooks/p-median.html # location-allocation brute force library(accessibility) library(data.table) library(furrr) data_dir <- system.file("extdata", package = "accessibility") travel_matrix <- readRDS(file.path(data_dir, "travel_matrix.rds")) land_use_data <- readRDS(file.path(data_dir, "land_use_data.rds")) df <- accessibility:::merge_by_reference(data = travel_matrix, land_use_data = land_use_data, left_df_idcol = 'from_id', opportunity = 'population') df <- accessibility:::merge_by_reference(data = df, land_use_data = land_use_data, left_df_idcol = 'to_id' , opportunity = 'schools' ) head(df) #' references #' #' Open-source approaches for location cover models: capabilities and efficiency #' https://link.springer.com/article/10.1007/s10109-021-00350-w #' #' Mark green #' https://github.com/markagreen/mapping_test_accessibility #' #' Pysal spopt #' https://github.com/pysal/spopt optimal_location <- function(df, candidates='all'){ # candidates <- c('89a88cd90b7ffff', '89a88cdb607ffff') # ## drop origin areas with no population # df <- df[population>0,] ## get baseline time to access closest facility # find time closest # dfclosest <- df[,.(population=population[1L], # travel_to_closest=min(travel_time[which(schools>0)]),na.rm=TRUE), # by=from_id] # internal function to find travel time to closest facility get_closest <-function(ttmfull){ temp_closest <- ttmfull[schools>0,.(population=population[1L], travel_to_closest=min(travel_time,na.rm=TRUE)), by=from_id] return(temp_closest) } # find travel time to closest facility dfclosest <- get_closest(ttmfull=df) # internal function to find average time to closest avg_time2closest <- function(df_closest){ df_avg_time <- df_closest[population>0,.(wmean_time= weighted.mean(travel_to_closest, w=population, na.rm=TRUE))] return(df_avg_time) } # get baseline average time to closest baseline <- avg_time2closest(df_closest=dfclosest)$wmean_time # find all location candidates which do not have any facility # in a future version, the user can pass a vector of location candidates if(candidates=='all'){ all_cadidates <- unique(df$to_id[which(df$schools==0)]) } if (all(candidates!='all')) { all_cadidates = candidates } # function to simulate impact of allocation at an specific destination sim_one_location <- function(candn, df = parent.frame()$df){ # candn <- '89a88cd968fffff' # add 1 school to candn df[to_id == candn,] df[to_id == candn, schools := schools + 1] # find travel time to closest facility step1_closest <- get_closest(ttmfull = df) # get baseline average time to closest step1_avg_time <- avg_time2closest(df_closest = step1_closest) # add simulation id step1_avg_time[, candn := candn] return(step1_avg_time) } ## apply function to all candidate locations # df2 <- pbapply::pblapply(X=all_cadidates, FUN=sim_one_location, df=df) |> # data.table::rbindlist() # parallel future::plan(strategy = 'multisession') df2 <- furrr::future_map(.x = all_cadidates, .f = sim_one_location, .progress = TRUE, df=df) |> data.table::rbindlist() # location which minimizes average distance step2 <- df2[ wmean_time== min(wmean_time)] # organize output step2[, baseline_time := baseline] data.table::setnames(step2, old = 'candn', new = 'optimal_locations') data.table::setcolorder(step2, neworder = c('optimal_locations', 'wmean_time', 'baseline_time')) return(step2) } t <- optimal_location(df = df, candidates = 'all') #' P-Median Problem #' location which minimizes total distance #' https://pysal.org/spopt/notebooks/p-median.html df[, .(d = to_id[which.min(sum(travel_time * population))] )]