diff --git a/.Rbuildignore b/.Rbuildignore index 156c933..30858d4 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -8,3 +8,9 @@ zip gpkg ^Doc$ +^\.devcontainer$ +^code$ +^network_eval\.qmd$ +^network_eval_file$ +^plot$ +^requirements\.txt$ diff --git a/DESCRIPTION b/DESCRIPTION index 16fc06f..8158e7d 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -27,11 +27,18 @@ Imports: purrr, units, rlang, + anime, + stats, + grDevices Suggests: tibble, tmaptools, zonebuilder, - rsgeo + rsgeo, + fmsb +Remotes: + josiahparry/anime/r, + josiahparry/rsgeo Encoding: UTF-8 LazyData: true Roxygen: list(markdown = TRUE) diff --git a/R/anime_join.R b/R/anime_join.R index 0819651..dfe752a 100644 --- a/R/anime_join.R +++ b/R/anime_join.R @@ -108,7 +108,8 @@ anime_join = function( net_target_aggregated = net_source_matches |> dplyr::group_by(row_number = target_id) |> dplyr::summarise( - !!rlang::sym(new_name) := agg_fun(!!mult_expr, na.rm = TRUE) + !!rlang::sym(new_name) := agg_fun(!!mult_expr, na.rm = TRUE), + .groups = "drop" ) # 6. If aggregator is max, round the result diff --git a/R/corenet.R b/R/corenet.R index a30d01f..f947f8b 100644 --- a/R/corenet.R +++ b/R/corenet.R @@ -2,7 +2,12 @@ utils::globalVariables(c("edge_paths", "influence_network", "all_fastest_bicycle_go_dutch", "weight", "to_linegraph", "edges", "group", "mean_potential", "LAD23NM", "road_function", "grid_id", "density", - "max_value", "min_value", "arterialness", "road_score", "value", "key_attribute", "n_group",".data", "n_removeDangles", "path_type", "maxDistPts", "minDistPts","penalty_value","penalty", "use_stplanr", "group_column")) + "max_value", "min_value", "arterialness", "road_score", "value", "key_attribute", "n_group",".data", "n_removeDangles", "path_type", "maxDistPts", "minDistPts","penalty_value","penalty", "use_stplanr", "group_column", + # Variables from net_eval.R functions + "geometry", "InterZone", "TotPop2011", "StdAreaKm2", "ResPop2011", "W_i", + "has_intersection", "covered_area", "covered_area_km2", "pop_density", + "covered_population", "distance_to_network", "all", "length_m", "km", + "target_id", "id")) #' Prepare a cohesive cycling network using NPT data #' @@ -146,6 +151,7 @@ cohesive_network_prep = function(base_network, influence_network, target_zone, c #' @param n_removeDangles Number of iterations to remove dangles from the network, default is 6. #' @param penalty_value The penalty value for roads with low values, default is 1. #' @param group_column The column name to group the network by edge betweenness, default is "name_1". +#' @param max_path_weight Maximum weight allowed for paths in network calculations, default is 10. #' @return A spatial object representing the largest cohesive component of the network, free of dangles. #' @export #' @examples @@ -193,9 +199,9 @@ corenet = function(influence_network, cohesive_base_network, target_zone, key_at # Perform DBSCAN clustering coordinates = sf::st_coordinates(centroids) - coordinates_clean = coordinates[complete.cases(coordinates), ] + coordinates_clean = coordinates[stats::complete.cases(coordinates), ] clusters = dbscan::dbscan(coordinates_clean, eps = 18, minPts = 1) - centroids_clean = centroids[complete.cases(coordinates), ] + centroids_clean = centroids[stats::complete.cases(coordinates), ] centroids_clean$cluster = clusters$cluster unique_centroids = centroids_clean[!duplicated(centroids_clean$cluster), ] @@ -479,8 +485,10 @@ prepare_network = function(network, key_attribute = "all_fastest_bicycle_go_dutc #' @param minDistPts The minimum distance (in meters) to consider for path calculations. #' @param centroids An sf object containing centroids to which paths are calculated. #' @param path_type A character string indicating the type of path calculation: 'shortest', 'all_shortest', or 'all_simple'. -#' @param max_path_weight +#' @param max_path_weight Maximum weight allowed for paths in network calculations. +#' @param crs_transform The coordinate reference system to transform to, default is 27700 (British National Grid). #' +#' @return A list containing the computed paths and their associated weights. #' - 'shortest': Calculates the shortest path considering weights. #' - 'all_shortest': Calculates all paths that tie for the shortest distance, considering weights. #' - 'all_simple': Calculates all simple paths, ignoring weights. @@ -506,16 +514,7 @@ calculate_paths_from_point_dist = function( network = sf::st_transform(network, crs = crs_transform) } - # 2) Generate a unique key for caching based on the point's coordinates - # You can modify how this key is generated if your 'point' is already sf-compatible - point_key = paste(sort(as.character(point)), collapse = "_") - - # 3) Check if result is already cached - if (exists(point_key, envir = path_cache_env)) { - return(get(point_key, envir = path_cache_env)) - } - - # 4) Convert the point and centroids to sfc if not already + # 2) Convert the point and centroids to sfc if not already point_geom = sf::st_as_sfc(point) centroids_geom = sf::st_as_sfc(centroids) @@ -529,9 +528,8 @@ calculate_paths_from_point_dist = function( distances_m <= units::set_units(maxDistPts, "m") ) - # If no centroids qualify, cache NULL and return + # If no centroids qualify, return NULL if (!length(valid_idx)) { - assign(point_key, NULL, envir = path_cache_env) return(NULL) } @@ -568,7 +566,6 @@ calculate_paths_from_point_dist = function( # 9) Filter out paths whose total weight exceeds threshold valid_paths = paths_from_point[total_weights <= max_path_weight, ] if (!nrow(valid_paths)) { - assign(point_key, NULL, envir = path_cache_env) return(NULL) } @@ -580,8 +577,7 @@ calculate_paths_from_point_dist = function( dplyr::slice(edges_in_paths) |> sf::st_as_sf() - # 12) Store in cache and return - assign(point_key, result, envir = path_cache_env) + # 12) Return the result return(result) } diff --git a/R/net_eval.R b/R/net_eval.R index 9894f16..4be1991 100644 --- a/R/net_eval.R +++ b/R/net_eval.R @@ -1,18 +1,18 @@ ### Function: Compute Spatial Coverage compute_spatial_coverage = function(rnet_core, lads, city_name = "City of Edinburgh", buffer_distance = 500) { - city_boundary = lads |> filter(LAD23NM == city_name) + city_boundary = lads |> dplyr::filter(LAD23NM == city_name) rnet_core_zone = sf::st_intersection(rnet_core, city_boundary) # Create a buffer around the network - network_buffer = st_buffer(rnet_core_zone, dist = buffer_distance) + network_buffer = sf::st_buffer(rnet_core_zone, dist = buffer_distance) # Union all buffer polygons - buffer_union = st_union(network_buffer) + buffer_union = sf::st_union(network_buffer) # Intersect with city boundary buffer_intersection = sf::st_intersection(buffer_union, city_boundary) # Compute areas - area_buffered = st_area(buffer_intersection) - area_city = st_area(city_boundary) + area_buffered = sf::st_area(buffer_intersection) + area_city = sf::st_area(city_boundary) # Spatial coverage ratio spatial_coverage = as.numeric(area_buffered / area_city) @@ -25,24 +25,24 @@ compute_spatial_coverage = function(rnet_core, lads, city_name = "City of Edinbu ### Function: Compute Zone Connectivity compute_zone_connectivity = function(intermediate_zone, lads, city_name = "City of Edinburgh", buffer_intersection, density_quantile = 0.3) { # Filter zones by density threshold - city_boundary = lads |> filter(LAD23NM == city_name) + city_boundary = lads |> dplyr::filter(LAD23NM == city_name) intermediate_zone = sf::st_intersection(intermediate_zone, city_boundary) intermediate_zone$density = intermediate_zone$ResPop2011 / intermediate_zone$StdAreaKm2 - density_threshold = quantile(intermediate_zone$density, density_quantile, na.rm = TRUE) - intermediate_zone = intermediate_zone |> filter(density > density_threshold) + density_threshold = stats::quantile(intermediate_zone$density, density_quantile, na.rm = TRUE) + intermediate_zone = intermediate_zone |> dplyr::filter(density > density_threshold) zones = intermediate_zone |> - select(InterZone, geometry) |> - st_make_valid() + dplyr::select(InterZone, geometry) |> + sf::st_make_valid() # W = B_union ∩ A_city (already computed outside) W = buffer_intersection # Compute intersections W_i zones = zones |> - rowwise() |> - mutate(W_i = list(st_intersection(geometry, W))) |> - ungroup() + dplyr::rowwise() |> + dplyr::mutate(W_i = list(sf::st_intersection(geometry, W))) |> + dplyr::ungroup() # Check intersections zones = zones |> mutate(has_intersection = lengths(W_i) > 0) @@ -61,8 +61,8 @@ compute_zone_connectivity = function(intermediate_zone, lads, city_name = "City geom_i = zones$W_i[[i]] geom_j = zones$W_i[[j]] - intersects = st_intersects(geom_i, geom_j, sparse = FALSE) - touches = st_touches(geom_i, geom_j, sparse = FALSE) + intersects = sf::st_intersects(geom_i, geom_j, sparse = FALSE) + touches = sf::st_touches(geom_i, geom_j, sparse = FALSE) if (any(intersects) | any(touches)) { adj_matrix[i, j] = 1 @@ -75,10 +75,10 @@ compute_zone_connectivity = function(intermediate_zone, lads, city_name = "City all_connected = all(adj_matrix == 1) cat("Are all zones inter-connected within W? ", all_connected, "\n") - g = graph_from_adjacency_matrix(adj_matrix, mode = "undirected") - comp = components(g) + g = igraph::graph_from_adjacency_matrix(adj_matrix, mode = "undirected") + comp = igraph::components(g) largest_comp_size = max(comp$csize) - total_zones = length(V(g)) + total_zones = length(igraph::V(g)) fraction_connected = largest_comp_size / total_zones return(list( graph = g, @@ -92,13 +92,13 @@ compute_zone_connectivity = function(intermediate_zone, lads, city_name = "City compute_cycling_potential_coverage = function(rnet_npt, lads, city_name = "City of Edinburgh", rnet_core, crs_target, buffer_distance = 20) { # Filter city network to within the city boundary - city_boundary = lads |> filter(LAD23NM == city_name) + city_boundary = lads |> dplyr::filter(LAD23NM == city_name) rnet_core_zone = sf::st_intersection(rnet_core, city_boundary) rnet_city = sf::st_intersection(rnet_npt, city_boundary) # Compute length of each segment rnet_city = rnet_city |> - mutate(length_m = as.numeric(st_length(geometry))) + dplyr::mutate(length_m = as.numeric(sf::st_length(geometry))) # Total city potential (sum of all_fastest_bicycle_go_dutch) P_total = sum(rnet_city$all_fastest_bicycle_go_dutch, na.rm = TRUE) @@ -110,10 +110,10 @@ compute_cycling_potential_coverage = function(rnet_npt, lads, city_name = "City D_city = P_total / L_city # Create a buffer around the core network - rnet_core_buffer = st_buffer(rnet_core_zone, buffer_distance) + rnet_core_buffer = sf::st_buffer(rnet_core_zone, buffer_distance) # Extract segments within the buffer - rnet_city_buffer = rnet_city[st_union(rnet_core_buffer), , op = st_within] + rnet_city_buffer = rnet_city[sf::st_union(rnet_core_buffer), , op = sf::st_within] # Buffered potential sum P_U_city = sum(rnet_city_buffer$all_fastest_bicycle_go_dutch, na.rm = TRUE) @@ -139,24 +139,24 @@ compute_cycling_potential_coverage = function(rnet_npt, lads, city_name = "City ### Function: Compute Population Coverage compute_population_coverage = function(intermediate_zone, lads, city_name = "City of Edinburgh", rnet_core, dist_threshold = 500) { - city_boundary = lads |> filter(LAD23NM == city_name) + city_boundary = lads |> dplyr::filter(LAD23NM == city_name) intermediate_zone = sf::st_intersection(intermediate_zone, city_boundary) rnet_core_zone = sf::st_intersection(rnet_core, city_boundary) zones = intermediate_zone |> - select(InterZone, TotPop2011, StdAreaKm2, geometry) |> - st_make_valid() |> - mutate(pop_density = TotPop2011 / StdAreaKm2) + dplyr::select(InterZone, TotPop2011, StdAreaKm2, geometry) |> + sf::st_make_valid() |> + dplyr::mutate(pop_density = TotPop2011 / StdAreaKm2) - rnet_core_buffer = st_buffer(rnet_core_zone, dist_threshold) - W = st_intersection(st_union(rnet_core_buffer), city_boundary) + rnet_core_buffer = sf::st_buffer(rnet_core_zone, dist_threshold) + W = sf::st_intersection(sf::st_union(rnet_core_buffer), city_boundary) - zones_coverage = st_intersection(zones, W) - zones_coverage$covered_area = st_area(zones_coverage) + zones_coverage = sf::st_intersection(zones, W) + zones_coverage$covered_area = sf::st_area(zones_coverage) zones_coverage$covered_area_km2 = units::set_units(zones_coverage$covered_area, km^2) zones_coverage = zones_coverage |> - mutate(covered_population = pop_density * as.numeric(covered_area_km2)) + dplyr::mutate(covered_population = pop_density * as.numeric(covered_area_km2)) P_covered = sum(zones_coverage$covered_population, na.rm = TRUE) P_total = sum(zones$TotPop2011, na.rm = TRUE) @@ -272,7 +272,9 @@ generate_radar_chart = function(city_name, dist_threshold = 500, buffer_distance = 500, save_path = NULL) { - library(fmsb) + if (!requireNamespace("fmsb", quietly = TRUE)) { + stop("Package 'fmsb' is required for this function. Please install it with install.packages('fmsb')") + } # 1. Spatial Coverage sp_cov_result = compute_spatial_coverage(rnet_core, lads, city_name = city_name, buffer_distance = buffer_distance) @@ -364,15 +366,15 @@ generate_radar_chart = function(city_name, # Generate radar chart if (!is.null(save_path)) { # Open a PNG device to save the plot - png(filename = save_path, width = 800, height = 800) + grDevices::png(filename = save_path, width = 800, height = 800) } - radarchart( + fmsb::radarchart( df_radar, axistype = 1, seg = 5, - pcol = rgb(0.2, 0.5, 0.5, 0.9), - pfcol = rgb(0.2, 0.5, 0.5, 0.5), + pcol = grDevices::rgb(0.2, 0.5, 0.5, 0.9), + pfcol = grDevices::rgb(0.2, 0.5, 0.5, 0.5), plwd = 2, cglcol = "grey", cglty = 1, @@ -387,7 +389,7 @@ generate_radar_chart = function(city_name, ) if (!is.null(save_path)) { - dev.off() # Close the PNG device + grDevices::dev.off() # Close the PNG device } } diff --git a/man/calculate_paths_from_point_dist.Rd b/man/calculate_paths_from_point_dist.Rd index 34d228f..dc5a986 100644 --- a/man/calculate_paths_from_point_dist.Rd +++ b/man/calculate_paths_from_point_dist.Rd @@ -11,7 +11,8 @@ calculate_paths_from_point_dist( maxDistPts = 1500, centroids, path_type = "shortest", - max_path_weight = 10 + max_path_weight = 10, + crs_transform = 27700 ) } \arguments{ @@ -27,13 +28,16 @@ calculate_paths_from_point_dist( \item{path_type}{A character string indicating the type of path calculation: 'shortest', 'all_shortest', or 'all_simple'.} -\item{max_path_weight}{\itemize{ -\item 'shortest': Calculates the shortest path considering weights. -- 'all_shortest': Calculates all paths that tie for the shortest distance, considering weights. -- 'all_simple': Calculates all simple paths, ignoring weights. -}} +\item{max_path_weight}{Maximum weight allowed for paths in network calculations.} + +\item{crs_transform}{The coordinate reference system to transform to, default is 27700 (British National Grid).} } \value{ +A list containing the computed paths and their associated weights. +- 'shortest': Calculates the shortest path considering weights. +- 'all_shortest': Calculates all paths that tie for the shortest distance, considering weights. +- 'all_simple': Calculates all simple paths, ignoring weights. + An sf object containing the paths that meet the criteria or NULL if no paths meet the criteria. } \description{ diff --git a/man/corenet.Rd b/man/corenet.Rd index 207f360..87a09c7 100644 --- a/man/corenet.Rd +++ b/man/corenet.Rd @@ -44,6 +44,8 @@ corenet( \item{penalty_value}{The penalty value for roads with low values, default is 1.} \item{group_column}{The column name to group the network by edge betweenness, default is "name_1".} + +\item{max_path_weight}{Maximum weight allowed for paths in network calculations, default is 10.} } \value{ A spatial object representing the largest cohesive component of the network, free of dangles. diff --git a/plot/Aberdeen City_radar_chart.jpg b/plot/Aberdeen_City_radar_chart.jpg similarity index 100% rename from plot/Aberdeen City_radar_chart.jpg rename to plot/Aberdeen_City_radar_chart.jpg diff --git a/plot/City of Edinburgh_radar_chart.jpg b/plot/City_of_Edinburgh_radar_chart.jpg similarity index 100% rename from plot/City of Edinburgh_radar_chart.jpg rename to plot/City_of_Edinburgh_radar_chart.jpg diff --git a/plot/Dundee City_radar_chart.jpg b/plot/Dundee_City_radar_chart.jpg similarity index 100% rename from plot/Dundee City_radar_chart.jpg rename to plot/Dundee_City_radar_chart.jpg diff --git a/plot/Glasgow City_radar_chart.jpg b/plot/Glasgow_City_radar_chart.jpg similarity index 100% rename from plot/Glasgow City_radar_chart.jpg rename to plot/Glasgow_City_radar_chart.jpg