Welcome to R Square

绘制中国地图进阶

楚新元 / 2026-06-23


在上一篇文章《ggplot2 + sf + 高德地图数据绘制中国地图》,笔者介绍了如何利用 ggplot2 + sf + 高德地图数据绘制中国地图。本文继续讨论绘制中国地图问题,重点解决以下两个问题:其一,数据来源由第三方地图服务切换为权威官方网站,以确保数据的规范性和权威性;其二,坐标系采用 2000 国家大地坐标系(CGCS2000),投影方式选用 Albers 等面积圆锥投影(基准面:CGCS2000),其中中央经线设定为 105°E,标准纬线分别取 25°N 和 47°N,从而更好地满足专业学术论文投稿要求和正式场合展示。

数据来源

如果只是一般的学习交流使用,我建议你使用高德地图平台的数据,地图数据完整且包含各省区行政中心和质心经纬度坐标,非常容易获取,如果是学术论文投稿和正式场合,那必须从权威官方网站获取。以下两个网站是目前最权威的数据来源,推荐使用。

本文采用国家地理信息公共服务平台提供的数据。注册后依次点击数据资源 $\rightarrow$ 行政区域可视化,进入下载页面后,选择地区:中国,下载区域:省,下载格式:GeoJSON,然后点击下载数据按钮即可。

生成地图

#'
#' 科学绘制中国地图(CGCS2000 Albers)
#'
#' @author 楚新元
#' @date 2026-06-23
#'

# ------------------------------
# 加载 R 包
# ------------------------------

library(sf)
library(ggplot2)
use("dplyr", c("mutate", "select", "coalesce"))

# ------------------------------
# 获取数据地图
# ------------------------------

# 数据来源:https://cloudcenter.tianditu.gov.cn/dataSource
# 数据更新时间:2025 年 9 月
# 数据资源:行政区域可视化
# 数据粒度:省
# 数据格式: GenJSON
# Geodetic CRS:  China Geodetic Coordinate System 2000

# ------------------------------
# 地图数据和艾滋病数据并合并
# ------------------------------

# 天地图的中国行政区划 GeoJSON 是平面投影适配的矢量边界
# 禁用 s2 球面几何,使用传统 GEOS 平面笛卡尔几何
sf_use_s2(FALSE)

# 读取中国省级地图数据
cn_raw = read_sf("data/中国_省.geojson")

# 艾滋病发病率数据(1/10万)
prov_aids = tibble::tribble(
  ~name,               ~value,
  "北京市",             2.59, 
  "天津市",             2.28, 
  "河北省",             1.36, 
  "山西省",             1.92, 
  "内蒙古自治区",        1.66, 
  "辽宁省",             2.30, 
  "吉林省",             2.78, 
  "黑龙江省",           1.70, 
  "上海市",             1.60, 
  "江苏省",             1.83, 
  "浙江省",             2.64, 
  "安徽省",             1.83, 
  "福建省",             2.58, 
  "江西省",             3.85, 
  "山东省",             1.01, 
  "河南省",             3.05, 
  "湖北省",             2.57, 
  "湖南省",             4.54, 
  "广东省",             3.61, 
  "广西壮族自治区",     14.19, 
  "海南省",             2.32, 
  "重庆市",            11.99, 
  "四川省",            13.95, 
  "贵州省",            10.48, 
  "云南省",             9.12, 
  "西藏自治区",         0.82, 
  "陕西省",             2.23, 
  "甘肃省",             2.00, 
  "青海省",             3.19, 
  "宁夏回族自治区",      1.47, 
  "新疆维吾尔自治区",    5.43, 
  "台湾省",             6.09, 
  "香港特别行政区",      NA, 
  "澳门特别行政区",      NA
)

# 合并数据(地图数据 + 艾滋病数据)
cn_raw |> 
  merge(prov_aids, by = "name", all.x = TRUE) |> 
  mutate(
    interval = cut(
      value, 
      breaks = c(
        0.82, 1.02, 1.36, 1.93, 2.00, 2.79, 3.05, 4.55, 
        5.43, 9.13, 10.48, 12.00, 13.95, 14.20
      ), 
      right = FALSE, 
      labels = c(
        "0.82 - 1.01", NA, "1.36 - 1.92", NA, 
        "2.00 - 2.78", NA, "3.05 - 4.54", NA, 
        "5.43 - 9.12", NA, "10.48 - 11.99", NA, 
        "13.95 - 14.19"
      )
    )
  ) -> cn_map_cgcs2000

# CGCS2000 Albers 等面积投影
albers_crs = "
  +proj=aea \
  +lat_1=25 +lat_2=47 \
  +lat_0=0 +lon_0=105 \
  +x_0=0 +y_0=0 \
  +ellps=GRS80 \
  +towgs84=0,0,0,0,0,0,0 \
  +units=m +no_defs
"

# 执行坐标转换:CGCS2000 → CGCS2000 Albers
cn_map = st_transform(cn_map_cgcs2000, crs = albers_crs)

# ------------------------------
# 设置填充颜色和标签位置、内容、颜色
# ------------------------------

# 定义每个区间的颜色
interval_colors = c(
  "0.82 - 1.01"   = "#fbcdcd",  # 浅粉
  "1.36 - 1.92"   = "#f38b88",  # 深粉
  "2.00 - 2.78"   = "#f8413b",  # 红色
  "3.05 - 4.54"   = "#c00203",  # 深红
  "5.43 - 9.12"   = "#790102",  # 暗红
  "10.48 - 11.99" = "#340100",  # 黑红
  "13.95 - 14.19" = "#0d0101"   # 黑色
)

# 自定义各省区标签位置(基于高德地图几何质心微调)
geo_coords = tibble::tribble(
  ~short_name,  ~geo_lon,  ~geo_lat,
  "上海",     122.93874,  31.77256,
  "云南",     101.48511,  24.50864,
  "内蒙古",   110.07743,  40.83109,
  "北京",     116.41995,  41.38994,
  "台湾",     123.47149,  23.24945,
  "吉林",     126.17121,  43.10395,
  "四川",     102.69345,  30.67454,
  "天津",     118.54704,  39.28804,
  "宁夏",     106.16987,  37.29133,
  "安徽",     117.22688,  31.84925,
  "山东",     118.18776,  36.37609,
  "山西",     112.30444,  37.61818,
  "广东",     113.42992,  23.33464,
  "广西",     108.79440,  23.83338,
  "新疆",      85.29471,  41.37180,
  "江苏",     119.48651,  32.98399,
  "江西",     115.73297,  27.63611,
  "河北",     115.50246,  38.04547,
  "河南",     113.61972,  33.90265,
  "浙江",     120.10991,  29.18147,
  "海南",     107.25486,  18.88977,
  "湖北",     112.27130,  30.98753,
  "湖南",     111.71165,  27.62922,
  "澳门",     112.76699,  20.35931,
  "甘肃",     104.12356,  34.95804,
  "福建",     118.00647,  26.06993,
  "西藏",      88.38828,  31.56375,
  "贵州",     106.88045,  26.82637,
  "辽宁",     123.30499,  41.29971,
  "重庆",     107.88390,  30.06730,
  "陕西",     108.88711,  34.26366,
  "青海",      96.04353,  35.72640,
  "香港",     114.93436,  20.97737,
  "黑龙江",   128.69303,  46.54046
)

# 确定标签位置、内容和颜色
cn_map_cgcs2000 |> 
  st_drop_geometry() |> 
  mutate(
    # 根据全称生成简称
    short_name = gsub("(省|自治区|市|特别行政区)$", "", name), 
    short_name = gsub("(壮族|回族|维吾尔)", "", short_name), 
    # 生成标签内容并确定颜色
    label = paste(short_name, format(value, nsmall = 2), sep = "\n"), 
    type = ifelse(
      # 标签在外部的一律用黑色;在内部的,数值小颜色浅用黑色,数值大颜色深用白色
      value > 3 & !short_name %in% c("海南", "澳门", "香港", "台湾", "上海", "天津"), 
      "white_font", "black_font"
    )
  ) |> 
  # 匹配各省区标签位置
  merge(geo_coords, by = "short_name", all.y = TRUE) |> 
  # 各省区标签位置转为 CGCS2000 Albers
  st_as_sf(
    coords = c("geo_lon", "geo_lat"), crs = 4326
  ) |> 
  st_transform(albers_crs) |> 
  mutate(
    geo_lon = st_coordinates(geometry)[, 1],
    geo_lat = st_coordinates(geometry)[, 2]
  ) |>
  st_drop_geometry() |> 
  select(
    label, type, geo_lon, geo_lat
  ) -> prov_labels

# ------------------------------
# 南海插图参数设置和矢量坐标转换
# ------------------------------

# 能囊括完整南海四至的范围(CGCS2000 Albers)
nh_lon_min = 0
nh_lon_max = 1840000
nh_lat_min = 290000
nh_lat_max = 2780000

# 确定插图画布尺寸
nh_w = nh_lon_max - nh_lon_min  # 经度宽度
nh_h = nh_lat_max - nh_lat_min  # 纬度高度
nh_aspect_ratio = nh_h / nh_w

inset_w = 600000
inset_h = inset_w * nh_aspect_ratio

# 插图放置起点(平面直角坐标)
inset_x0 = -1500000
inset_y0 = 1890659

# 生成南海插图地图数据
clip_bbox = st_bbox(
  c(
    xmin = nh_lon_min, ymin = nh_lat_min, 
    xmax = nh_lon_max, ymax = nh_lat_max
  ),
  crs = albers_crs
)

scale_matrix = matrix(
  c(
    inset_w / (nh_lon_max - nh_lon_min), 0, 
    0, inset_h / (nh_lat_max - nh_lat_min)
  ), 
  ncol = 2
)

cn_map |> 
  st_make_valid() |> 
  st_crop(clip_bbox) |> 
  mutate(
    geometry = (geometry - c(nh_lon_min, nh_lat_min)) * 
      scale_matrix + c(inset_x0, inset_y0) 
  ) |> 
  st_set_geometry("geometry") |> 
  st_set_crs(albers_crs) -> nh_inset

# ------------------------------
# 生成最终地图
# ------------------------------

ggplot() + 
  # 绘制中国地图最底层图层
  geom_sf(
    data = cn_map, aes(fill = interval), 
    color = "gray60", linewidth = 0.25
  ) + 
  
  # 绘制南海插图背景
  geom_rect(
    aes(
      xmin = inset_x0, xmax = inset_x0 + inset_w, 
      ymin = inset_y0, ymax = inset_y0 + inset_h
    ), 
    fill = "white", color = "black", linewidth = 0.25
  ) + 
  
  # 插图内绘制南海诸岛
  geom_sf(
    data = nh_inset, aes(fill = interval), 
    color = "gray60", linewidth = 0.25
  ) + 
  
  # 标注各省区简称和艾滋病发病率
  geom_text(
    data = prov_labels, 
    aes(x = geo_lon, y = geo_lat, label = label, color = type), 
    size = 4, family = "SimHei", hjust = 0.5
  ) + 
  scale_color_manual(
    values = c("black_font" = "black", "white_font" = "white"), 
    guide = "none"
  ) + 

  # 各省区艾滋病发病率数值按区间填充颜色
  scale_fill_manual(
    values = interval_colors, 
    na.value = "white",    # 缺失值省区填充白色
    na.translate = FALSE,  # 隐藏缺失值图例
  ) + 
  
  # 设置地图标题、图例、范围和坐标系
  labs(title = "全国艾滋病总计发病率") + 
  guides(fill = guide_legend(reverse = TRUE, title = NULL)) + 
  coord_sf(
    xlim = c(-2700000, 2300000),
    ylim = c(1600000, 6000000),
    expand = FALSE, 
    crs = albers_crs
  ) + 

  # 正上方添加图例注解
  annotate(
    "text", x = 12432.82, y = 5643263, 
    label = "艾 滋 病 发 病 率(1/10万)", 
    family = "SimHei", size = 6
  ) + 
  annotate(
    "text", x = -622313.28, y = 5449290, 
    label = "HIV/AIDS", 
    family = "SimHei", size = 4
  ) + 
  
  # 左小角添加注解说明
  annotate(
    "text", x = -2000000, y = 2585919, 
    label = "全国总计发病率\n4.27", 
    family = "SimHei", size = 5
  ) + 
  annotate(
    "text", x = -2000000, y = 2159607, 
    label = "2022年(2021年数据)\n中国卫生健康统计年鉴\n香港、澳门数据缺失", 
    family = "SimHei", size = 4
  ) + 
  
  # 设置地图主题
  theme_void() + 
  theme(
    plot.title = element_text(
      family = "Noto Serif CJK SC", color = "#31415b", 
      size = 30, face = "bold", hjust = 0.5, vjust = 1.5
    ),
    legend.background = element_blank(), 
    legend.position = c(0.53, 0.80), 
    legend.key.size = unit(0.50, "cm"), 
    legend.text = element_text(family = "SimHei", size = 11)
  )