1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305
| # ---- Step 1: Install and Load Necessary Packages ---- # Ensure these packages are installed # install.packages(c("sf", "ggplot2", "dplyr", "readr", "purrr", "ggspatial", "grid", "officer", "rvg"))
library(sf) library(ggplot2) library(dplyr) library(readr) # For read_csv library(purrr) # For map functional programming library(ggspatial) library(grid) # library(showtext) # Removed showtext dependency library(officer) # Optional library(rvg) # Optional # library(readxl) # Optional
# ---- Step 2: Set Font ---- # Specify the desired font family directly in ggplot elements # Ensure "Times New Roman" is available on your system base_font_family <- "Times New Roman"
# ---- Step 3: Read geojson Files ---- cities_data <- st_read("China Map/中国_市.geojson") districts_data <- st_read("China Map/中国_县.geojson")
# ---- Step 4: Define Target Cities and Districts ---- target_areas_cities_cn <- c("成都市", "绵阳市", "宜宾市", "德阳市", "南充市", "达州市", "泸州市", "乐山市", "自贡市", "眉山市", "遂宁市", "内江市", "广安市", "雅安市", "资阳市") target_areas_districts_cn <- c("渝北区", "渝中区", "九龙坡区", "沙坪坝区", "南岸区", "江津区", "北碚区", "巴南区", "涪陵区", "璧山区", "万州区", "永川区", "长寿区", "合川区", "大渡口区", "铜梁区", "大足区", "綦江区", "荣창区", "开州区", "潼南区", "梁平区", "南川区", "云阳县", "忠县", "垫江县", "黔江区", "丰都县", "石柱土家族自治县", "彭水苗族土家族自治县", "武隆区", "城口县", "秀山土家族苗族自治县", "酉阳土家族苗族自治县", "江北区") # Ensure Jiangbei is here target_areas_cities_en <- c("Chengdu", "Mianyang", "Yibin", "Deyang", "Nanchong", "Dazhou", "Luzhou", "Leshan", "Zigong", "Meishan", "Suining", "Neijiang", "Guang'an", "Ya'an", "Ziyang") target_areas_districts_en <- c("Yubei", "Yuzhong", "Jiulongpo", "Shapingba", "Nan'an", "Jiangjin", "Beibei", "Banan", "Fuling", "Bishan", "Wanzhou", "Yongchuan", "Changshou", "Hechuan", "Dadukou", "Tongliang", "Dazu", "Qijiang", "Rongchang", "Kaizhou", "Tongnan", "Liangping", "Nanchuan", "Yunyang", "Zhongxian", "Dianjiang", "Qianjiang", "Fengdu", "Shizhu", "Pengshui", "Wulong", "Chengkou", "Xiushan", "Youyang", "Jiangbei") # Add Jiangbei EN gb_code_for_jiangbei <- '156500105'
# ---- Step 5: Filter Geographic Data ---- sichuan_cities <- cities_data %>% filter(name %in% target_areas_cities_cn) chongqing_districts <- districts_data %>% filter((name %in% target_areas_districts_cn & name != "江北区") | (name == "江北区" & gb == gb_code_for_jiangbei)) sichuan_chongqing <- rbind(sichuan_cities, chongqing_districts) %>% distinct(name, .keep_all = TRUE) %>% mutate(name = trimws(name)) # Trim names early
# ---- Step 6: Calculate Average Node Strength (2020-2024) ---- avg_node_strength_data <- NULL # Initialize tryCatch({ years <- 2020:2024 base_path <- "Python/Output Data" # Base path for data folders source_col_name <- "Source" target_col_name <- "Target" prob_col_name <- "Weight" # Function to read and process data for a single year read_edge_data <- function(year, base_path, src_col, trg_col, prob_col) { file_path <- file.path(base_path, as.character(year), "边数据.csv") if (!file.exists(file_path)) { warning("File not found for year ", year, ": ", file_path) return(NULL) # Return NULL if file doesn't exist } tryCatch({ df <- read_csv(file_path, show_col_types = FALSE) # Check for required columns if (!all(c(src_col, trg_col, prob_col) %in% names(df))) { warning("Missing required columns in file for year ", year, ": ", file_path) return(NULL) } df %>% rename(source = !!sym(src_col), target = !!sym(trg_col), prob = !!sym(prob_col)) %>% mutate(source = trimws(source), target = trimws(target)) %>% # Ensure probability is numeric, coerce non-numeric to NA mutate(prob = as.numeric(prob)) %>% filter(!is.na(prob)) %>% # Remove rows where probability couldn't be read as numeric select(source, target, prob) %>% mutate(year = year) %>% # Add year column # Create a unique pair ID, sorting source/target to handle directionality mutate(pair_id = map2_chr(source, target, ~ paste(sort(c(.x, .y)), collapse = "-"))) }, error = function(e) { warning("Error reading or processing file for year ", year, ": ", file_path, " - ", e$message) return(NULL) }) } # Read all yearly data into a single data frame all_years_edges <- map_dfr(years, ~read_edge_data(.x, base_path, source_col_name, target_col_name, prob_col_name)) if (nrow(all_years_edges) == 0) { stop("No valid edge data could be read from any year.") } message("Read data for ", length(unique(all_years_edges$year)), " years.") # Calculate the average probability for each unique edge pair across all years # Ensure source and target are consistent for each pair_id before averaging consistent_pairs <- all_years_edges %>% select(pair_id, source, target) %>% distinct(pair_id, .keep_all = TRUE) # Get one consistent source/target for each pair_id average_edges <- all_years_edges %>% group_by(pair_id) %>% # Calculate the mean probability for each pair, handling potential NA values if a pair doesn't exist in a year summarise(average_prob = mean(prob, na.rm = TRUE), .groups = 'drop') %>% # Replace NaN with 0 (can happen if a pair exists but only has NA probabilities, though filter step should prevent this) mutate(average_prob = ifelse(is.nan(average_prob), 0, average_prob)) %>% filter(average_prob > 0) %>% # Keep only edges with positive average strength # Join back the consistent source/target names left_join(consistent_pairs, by = "pair_id") %>% select(source, target, average_prob) # Keep relevant columns if (nrow(average_edges) == 0) { stop("No edges with positive average probability found.") } message("Calculated average probability for ", nrow(average_edges), " unique edges.") # Calculate Average Node Strength # Sum the average strength for each node (appearing as source or target) strength_source <- average_edges %>% select(node = source, strength = average_prob) strength_target <- average_edges %>% select(node = target, strength = average_prob) avg_node_strength_data <- bind_rows(strength_source, strength_target) %>% group_by(node) %>% summarise(avg_node_strength = sum(strength), .groups = 'drop') %>% rename(name = node) # Rename column to 'name' for joining message("Successfully calculated average node strength (2020-2024) for ", nrow(avg_node_strength_data), " nodes.") }, error = function(e) { warning("Error calculating average node strength (2020-2024): ", e$message, "\nMap fill will be missing.") avg_node_strength_data <- NULL # Ensure it's NULL on error })
# ---- Step 7: Merge Average Node Strength to Geographical Data ---- if (!is.null(avg_node_strength_data)) { sichuan_chongqing <- sichuan_chongqing %>% left_join(avg_node_strength_data, by = "name") } else { # Add a dummy column if calculation failed, to avoid errors later sichuan_chongqing$avg_node_strength <- NA_real_ # Use numeric NA warning("Using NA for node strength due to calculation error.") }
# ---- Step 8: Calculate Centroids and Prepare Labels ---- # Use st_point_on_surface for potentially better placement inside polygons sichuan_chongqing_centroids <- suppressWarnings(st_point_on_surface(sichuan_chongqing))
# Manual Adjustments (keep if needed) suppressWarnings({ # Find indices safely idx_luzhou <- which(sichuan_chongqing_centroids$name == "泸州市") idx_yubei <- which(sichuan_chongqing_centroids$name == "渝北区") idx_meishan <- which(sichuan_chongqing_centroids$name == "眉山市") if(length(idx_luzhou) > 0) sichuan_chongqing_centroids[idx_luzhou, ]$geometry <- st_geometry(sichuan_chongqing_centroids[idx_luzhou, ]) + c(-0.23, 0.23) if(length(idx_yubei) > 0) sichuan_chongqing_centroids[idx_yubei, ]$geometry <- st_geometry(sichuan_chongqing_centroids[idx_yubei, ]) + c(0.03, -0.03) if(length(idx_meishan) > 0) sichuan_chongqing_centroids[idx_meishan, ]$geometry <- st_geometry(sichuan_chongqing_centroids[idx_meishan, ]) + c(0, 0.1) })
# ---- Step 9: Define and Process Labels (English with Numbered Core) ---- # Combine CN and EN names for mapping (ensure Jiangbei is included) name_map_cn <- c(target_areas_cities_cn, target_areas_districts_cn, "江北区") name_map_en <- c(target_areas_cities_en, target_areas_districts_en, "Jiangbei") # Make sure Jiangbei is here name_mapping <- setNames(name_map_en, name_map_cn) %>% .[!duplicated(names(.))] # Create named vector, remove duplicate CN names if any
# Define the core Chongqing districts for numbering chongqing_core_cn <- c("江北区", "沙坪坝区", "九龙坡区", "渝中区", "大渡口区", "南岸区") chongqing_core_labels <- c("1", "2", "3", "4", "5", "6") core_mapping <- setNames(chongqing_core_labels, chongqing_core_cn)
# Apply mapping: use number if core, else use English name from mapping, else original name sichuan_chongqing_centroids <- sichuan_chongqing_centroids %>% mutate(label = case_when( name %in% chongqing_core_cn ~ recode(name, !!!core_mapping), name %in% names(name_mapping) ~ recode(name, !!!name_mapping), TRUE ~ name # Fallback to original name if no mapping found ))
# ---- Step 10: Calculate Shared Boundary ---- shared_boundary <- NULL tryCatch({ # Ensure input layers are valid before proceeding if(!all(st_is_valid(sichuan_cities))) sichuan_cities <- st_make_valid(sichuan_cities) if(!all(st_is_valid(chongqing_districts))) chongqing_districts <- st_make_valid(chongqing_districts) sichuan_cities_proj <- st_transform(sichuan_cities, crs = 3857) # Use a projected CRS for geometric operations chongqing_districts_proj <- st_transform(chongqing_districts, crs = 3857) sichuan_boundary_proj <- suppressMessages(suppressWarnings(st_union(sichuan_cities_proj) %>% st_boundary())) chongqing_boundary_proj <- suppressMessages(suppressWarnings(st_union(chongqing_districts_proj) %>% st_boundary())) # Perform intersection only if boundaries are valid and not empty if(!st_is_empty(sichuan_boundary_proj) && !st_is_empty(chongqing_boundary_proj) && st_is_valid(sichuan_boundary_proj) && st_is_valid(chongqing_boundary_proj)){ shared_boundary_proj <- suppressMessages(suppressWarnings(st_intersection(sichuan_boundary_proj, chongqing_boundary_proj))) if (length(shared_boundary_proj) > 0 && !st_is_empty(shared_boundary_proj)) { shared_boundary <- st_transform(shared_boundary_proj, crs = 4326) # Transform back to WGS84 message("Shared boundary calculated.") } else { message("No shared boundary found or intersection empty.") } } else { message("Could not calculate shared boundary due to invalid or empty input boundaries.") } }, error = function(e){ warning("Could not calculate shared boundary: ", e$message) })
# ---- Step 11: Ensure Correct CRS for Plotting ---- sichuan_chongqing <- st_transform(sichuan_chongqing, crs = 4326) sichuan_chongqing_centroids <- st_transform(sichuan_chongqing_centroids, crs = 4326) # shared_boundary is already transformed if calculated successfully
# ---- Step 12: Plot the Map ---- map_plot <- ggplot() + # Base Map: Fill by Average Node Strength geom_sf(data = sichuan_chongqing, aes(fill = avg_node_strength), # Use calculated average node strength color = "gray50", # Boundary color for regions linewidth = 0.2) + # Shared Boundary (if calculated) { if (!is.null(shared_boundary) && inherits(shared_boundary, "sf") && nrow(shared_boundary) > 0 && !st_is_empty(shared_boundary)) geom_sf(data = shared_boundary, color = "black", linewidth = 0.8) } + # Text Labels geom_sf_text(data = sichuan_chongqing_centroids, aes(label = label), size = 2.5, # Adjust size as needed color = "black", family = base_font_family, # Use Times New Roman check_overlap = FALSE) + # Or use ggrepel::geom_text_repel # North Arrow annotation_north_arrow(location = "tl", which_north = "true", pad_x = unit(0.3, "cm"), pad_y = unit(0.9, "cm"), height = unit(1.2, "cm"), width = unit(1.2, "cm"), style = north_arrow_fancy_orienteering) + # Scale Bar annotation_scale(location = "bl", width_hint = 0.3, # Bottom-left pad_x = unit(0.6, "cm"), pad_y = unit(0.6, "cm"), text_family = base_font_family) + # Use Times New Roman # Color Scale for Node Strength scale_fill_gradient(name = "Average Node Strength\n(2020-2024)", # Legend Title low = "#FDE7D7", high = "#F08B5D", # Orange gradient na.value = "grey90", # Color for NA values n.breaks = 5) + # Adjust number of breaks in legend # Labels and Theme labs(title = "Sichuan-Chongqing Area: Average Node Strength (2020-2024)") + theme_minimal(base_family = base_font_family) + # Use Times New Roman theme( plot.title = element_text(hjust = 0.5, size=14, face="bold"), panel.grid.major = element_line(color = "grey85", linetype = "dashed", linewidth = 0.3), panel.grid.minor = element_blank(), axis.title = element_blank(), axis.text = element_text(color = "black", size = 9, family=base_font_family), # Font for axis text axis.ticks = element_line(color = "grey70"), legend.title = element_text(size = 9, face = "bold", family=base_font_family), # Font for legend title legend.text = element_text(size = 8, family=base_font_family), # Font for legend text legend.position = "right", plot.margin = unit(c(0.5, 2.5, 0.5, 0.5), "cm") # Adjust right margin for annotation )
# ---- Step 13: Add Custom Annotation for Numbered Labels ---- # Define annotation text (ensure names match numbered labels 1-6) annotation_text <- "Numbered Districts:\n1: Jiangbei\n2: Shapingba\n3: Jiulongpo\n4: Yuzhong\n5: Dadukou\n6: Nan'an"
# Create grob with Times New Roman font annotation_grob <- textGrob( label = annotation_text, hjust = 0, vjust = 0, gp = gpar(fontsize = 9, fontfamily = base_font_family, lineheight = 1.3) # Use Times New Roman )
# Add annotation outside plot area # Using annotation_custom with Inf/-Inf and clip="off" final_plot <- map_plot + annotation_custom( grob = annotation_grob, xmin = Inf, xmax = Inf, ymin = -Inf, ymax = -Inf # Place outside data range ) + coord_sf(crs = 4326, clip = "off", expand = TRUE) # Allow drawing outside, expand=TRUE might help positioning annotation
# ---- Step 14: Print and Save ---- # Print to RStudio plotting window print(final_plot)
# Save to PPT (Optional) # Make sure 'final_plot' object is used # try({ # ppt_path <- "images/Sichuan_Chongqing_Avg_Node_Strength_Map_2020_2024.pptx" # ppt <- read_pptx() # ppt <- add_slide(ppt, layout = "Blank", master = "Office Theme") # # Ensure rvg is loaded if using dml # plot_dml <- rvg::dml(ggobj = final_plot) # ppt <- ph_with(ppt, value = plot_dml, location = ph_location_fullsize()) # print(ppt, target = ppt_path) # message("Plot saved to: ", ppt_path) # }, error = function(e){ # warning("Could not save to PPT: ", e$message) # })
# Save as PNG (Optional) # ggsave("images/Sichuan_Chongqing_Avg_Node_Strength_Map_2020_2024.png", plot = final_plot, width = 11, height = 8, dpi = 300, bg = "white")
|