R实现地图绘制


图片

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")



文章作者: 徐老师
版权声明: 本博客所有文章除特別声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 徐老师 !