R语言绘制中国地图改进版!博客里的旧教程可以丢掉了!

依稀记得2020年前后,搞定了标准中国地图(包括九段线)的分层设色图的绘制,非常得意地写了教程。但是最近,又需要做一个类似的任务,突然发现,原来的方法过时且老套了,遂用一个新的教程补充。

我们最后要达到的效果如下图所示:

Snipaste_2024-01-15_00-23-19.png

第一步:不用 shp 文件!找到好读取的标准地图文件

以前用的是老版的 shp 地理数据文件,还要分为多边形的省份地图和线条形的九段线地图。现在想要找标准中国地图,可以直接在阿里云的数据可视化平台上面找到。点击下图中红色箭头指向的位置,就可以下载到一个为 json 格式的地理数据文件了。

Snipaste_2024-01-15_00-25-53.png

第二步:读取地图文件和必要的文件

使用 sf 包里的 st_read 就可以直接读取了,确实比原来方便多了。而且我们等会儿还会用到 sf 包里面的其他函数。

library(sf)

# Import Map file
china_map <- st_read("map_file/中华人民共和国.json")

关于要可视化的数据,就不详细展示读取和清洗的过程了。大概可以这么理解,总共有 9 次 Inspection,每次 Inspection 都需要在地图上高亮若干个特定的省份。

第三步:合并地图数据和需要可视化的数据,建立基础地图

  1. 合并地图数据:合并地图数据也只需要 dplyr 包里经常用到的 left_join 就可以;
  2. 宽数据转长数据:非常重要的一步。如果想要用 ggplot 绘制多组数据的时候,这一步经常是必不可少的;
  3. 绘制基础地图:以前用 geom_polygon,现在我们直接用 geom_sf
  4. 调整地图坐标系投影:如果使用了 geom_sf,调整坐标系投影不能用以前的方法,而是必须使用 coord_sf,参数用我下面给的就可以;
  5. 按变量分组绘制:使用 facet_wrap,非常常用的一个指定分组绘制的方法,这里用到之后,9 个小图,按照 3×3 进行排列。

代码大致如下:

library(tidyverse)

# Main Map Plot with fill color
p1 <- china_map %>%
  left_join(df, by=c("name"="NAME")) %>%
  pivot_longer(
    insp_1:insp_9,
    names_to = "Var",
    values_to = "Val"
  ) %>%
  ggplot() +
  geom_sf(aes(fill=Val), col="grey20") +
  coord_sf(ylim = c(-2387082,1654989), crs='+proj=laea +lat_0=40 +lon_0=104') +
  scale_fill_manual(values=c("white", "#41a5ee")) +
  facet_wrap(
    ~ Var, ncol=3,
    labeller = labeller(Var=plot_labels)) +
  theme_void() +
  theme(
    strip.text=element_text(size=12, face="bold", family = "Times"),
    plot.margin=margin(1, 1, 1, 1, "cm"),
    legend.position="none")
p1

这样画好之后长这样:

Snipaste_2024-01-15_00-36-57.png

已经初具形状了,只是少了九段线。接下来我们就加上九段线:

第四步:绘制九段线小图

这里的思路还是先绘制整个地图,然后根据地图想要显示的范围,进行裁切,得到想要的小图即可。裁切使用的是 coord_sf,你可以看到我们通过设定经纬度范围来裁切。

这里需要强调一下,最后的 theme 里面设置边距设定为 -1,即不留任何边框,不然加在底图上时候会有一条白边边框。

# Mini map with 9 lines in the corner
china_mini <- ggplot(data = china_map) +
  geom_sf(fill="white", col="grey20") +
  coord_sf( xlim = c(105, 125),
            ylim = c(4, 26), 
            expand = FALSE, datum = NA)+
  theme_few() +
  theme(plot.margin=margin(-1, -1, -1, -1, "pt"))

china_mini

这一步绘制的小图长这样:

Snipaste_2024-01-15_00-42-16.png

第五步:将九段线小图覆盖在底图上

说实话,这一步是比较粗笨的,也是我稍微不太满意的一步。这里我研究了好久,目前还是只能用 cowplot 包里面的 draw_plot 来实现。因为我们有 9 个底图,因此,也需要加上 9 个九段线的小图:

# Figure arguments for position and ratio
ns_ratio <- .03
x_coords <- c(.308, .617, .928)
y_coords <- c(.655, .352, .045)

# Overlay 9 subplots on main map plot
ggdraw() +
  draw_plot(p1) +
  draw_plot(china_mini, x=x_coords[1], y=y_coords[1], width=ns_ratio, height=ns_ratio) +
  draw_plot(china_mini, x=x_coords[2], y=y_coords[1], width=ns_ratio, height=ns_ratio) +
  draw_plot(china_mini, x=x_coords[3], y=y_coords[1], width=ns_ratio, height=ns_ratio) +
  draw_plot(china_mini, x=x_coords[1], y=y_coords[2], width=ns_ratio, height=ns_ratio) +
  draw_plot(china_mini, x=x_coords[2], y=y_coords[2], width=ns_ratio, height=ns_ratio) +
  draw_plot(china_mini, x=x_coords[3], y=y_coords[2], width=ns_ratio, height=ns_ratio) +
  draw_plot(china_mini, x=x_coords[1], y=y_coords[3], width=ns_ratio, height=ns_ratio) +
  draw_plot(china_mini, x=x_coords[2], y=y_coords[3], width=ns_ratio, height=ns_ratio) +
  draw_plot(china_mini, x=x_coords[3], y=y_coords[3], width=ns_ratio, height=ns_ratio)

# Save pdf to local file
ggsave("final_plot.pdf", dpi=300, width=11, height=10)

这里比较丑陋的点主要在于:

  1. 9 个同样的 draw_plot 语句,一点也不优雅;
  2. x_coordsy_coords 的坐标参数,不是算出来的,是一点点调整出来的,直接用数字摆在这里,可能会让第一次看到的读者迷惑。

不过还好,最后还是绘制出来了想要的效果:

Snipaste_2024-01-15_00-45-24.png

总的来说

其实这次完成这个绘图任务还是比较简单的,最费时间的地方在于调整九段线小图在底图上的位置坐标。诸位读者如果不是非得要在 R 里输出成品,真的可以考虑在 Photoshop 甚至是 Powerpoint 里把九段线小图放置在合适的位置上。这样会节省大把的时间和精力。当然,如果各位有更好的方法能解决最后这一步,就更完美了。

另外,博客荒废多月,但是这几个月整体来说,相当充实,尤其是在 R 语言上又精进了许多,学会了很多很实用的包和技巧。之后会找时间和大家一一分享。