☰
R语言+ggplot2绘制出版级世界地图:投影选择与实操指南
2026/10/3 11:04:33 网站建设 项目流程

第一次用 R 语言 ggplot2 搭配 ggmapcn 画出能直接丢进论文配图的世界地图时,我果断把桌面 GIS 软件的快捷方式从 Dock 上拖走了。你需要快速产出几十张不同投影的出版级世界地图、又想让整个流程可复现可修改,纯靠鼠标点选图层属性那种方式真的会把人逼疯。这篇想分享的就是一套我自己跑了很久的 R 绘图方案:从数据获取、坐标系与投影转换、5 种常用投影对比,到最后怎么输出出版级文件,都会一步步拆开讲。适合有一定 R 基础、但不想为出图去啃 GIS 软件的人参考,也适合正在做数据报告、论文配图、成果展示时经常需要地图背景的读者。


1. 项目背景:为什么我放着 GIS 软件不用,非要折腾 R

1.1 传统 GIS 软件的三个痛点

先说个我自己的经历。有一次项目里要出 40 多张世界地图,每张图的投影方式还不一样,有的要等面积投影,有的要折衷投影,数据源还要按年份拆分。用桌面 GIS 软件做,常规操作是先导入全球行政区划矢量,再一层一层设置坐标系、调符号、加图例,然后导出图片。如果只做一两张,这套流程没毛病,软件界面可视化程度高,图层关系一眼就能看明白。

但问题是地图一多,痛点就全冒出来了。

第一,批量出图特别笨重。40 张图意味着 40 次重复操作,哪怕你用模型构建器把流程串起来,也只是把点鼠标换成了连线,参数稍微改一点就得很小心地重新搭模型。整个过程对“图形界面经验”的依赖太强,中间任何一步没记录,后面的人根本不知道你这张图是怎么生产出来的。

第二,坐标系和投影的操作藏在多层菜单里。很多新手在软件里找“投影”就像在博物馆找厕所,明明感觉就在附近,兜兜转转就是找不到。就算找到了,软件默认给你一堆听不懂的代号,什么 WGS 84、CGCS2000、UTM Zone 49N,你也不知道选哪个,更不知道不同投影对最终成图的影响到底有多大。这种黑盒式操作,对需要严谨对待投影原理的人来说非常难受。

第三,数据分析和地图绘制是脱节的。处理数据用 Python 或 R,画地图又切到另一个软件,中间都要导出 CSV、再导入属性表、再关联字段。任何一个环节更新了数据,整条链路都要重新走一遍。这种割裂感在项目时间紧的时候尤其致命。

1.2 用 R 方案能解决什么

换成 R 语言之后,整个思路就变了:地图不是“画”出来的,而是“算”出来的。

用 R 的 ggplot2 配合 sf 空间数据处理库,地图本质上就是一个数据框(DataFrame),每个国家是一个多边形要素,所有属性字段都放在同一个表格里。这样你可以直接用 dplyr 的 filter 筛选国家、用 mutate 添加指标、用 group_by 汇总数据,然后再一行代码把结果画成地图。数据分析和地图绘制之间不再有断层。

更关键的是可复现性。一个脚本从头跑到尾,输入原始数据,输出最终图片,中间所有处理步骤都被记录下来。换一批数据,只要文件路径改一下,图就能自动重画;想改投影方式,把投影字符串换一个,重新执行一遍,全套图片全部更新。这个特性对学术研究、周期性报告、需要追踪修改过程的场景来说,价值怎么强调都不过分。

还有一点,R 是免费开源的。机构和个人的电脑都能装,不需要担心授权到期,不需要申请浮动许可,也不受操作系统限制。Windows、macOS、Linux 跑同一套代码,结果完全一致。

1.3 这份方案适合谁、不适合谁

如果你属于下面这几类人,R 画地图会很香:

  • 需要批量产出地图图片,并且经常要改参数重画的;
  • 做数据分析、统计建模,希望图表和数据在同一套流程里完成;
  • 写论文、出报告,需要“出版级”清晰度和规范地图;
  • 不想被商业 GIS 软件授权绑定,喜欢开源方案的;
  • 需要一个可追溯、可版本管理的地图生产流程。

但也要说实话,R 的地图方案不是万能的。如果你要做复杂的拓扑编辑、地理配准、卫星影像分类、大量矢量数据的空间分析,或者需要交互式地图编辑,那 ArcGIS 或 QGIS 依然是更趁手的工具。R 的长处是“出图”和“数据联动”,不是“空间编辑”。我的习惯是:地理数据预处理用 QGIS,出图和自动化用 R,两套工具互补。


2. 投影原理与 5 种投影的选型逻辑

2.1 投影是什么:一个剥橘子皮的问题

很多人看到“投影”两个字就头疼,我换个说法:地球是一个三维球体,地图是一张二维平面纸,你非要把球面的东西摊平,就像剥一个橘子,硬要把橘子皮完整地铺在桌上。铺出来的皮一定会裂开、拉伸或者折叠,不可能做到既不变形又不破损。

地图投影就是在研究“怎么撕橘子皮”的技术。有些投影尽量保住面积比例,但形状会扭曲;有些投影尽量保住角度关系,但面积会严重变形;还有些投影什么都保一点,但什么都不够精确。没有哪个投影是“绝对正确”的,只有“这个用途下最合适”的投影。

在 R 里处理投影,本质上就是给每个地图对象指定一套数学规则,告诉它“你从球面坐标转换到平面坐标时,用哪套公式”。

2.2 五种常用投影横向对比

这次我选了 5 种在出版世界地图时最常碰到的投影,把它们放在一起对比,能很清楚看到不同投影对同一份地理数据的处理差异。

投影名称代码/参数变形特征典型应用场景
Plate Carree(等距圆柱)+proj=eqc +lon_0=0经线和纬线等距,高纬地区严重拉伸快速预览、科研绘图、最简方案
Web Mercator(网络墨卡托)EPSG:3857等角保形,面积随纬度急剧膨胀在线地图、导航、Web 展示
Robinson(罗宾逊)+proj=robin面积、形状、距离综合折衷教科书、新闻媒体、科普地图
Mollweide(摩尔韦德)+proj=moll等面积,全球面积关系准确全球性统计分布、资源分布图
Winkel Tripel(温克尔三重)+proj=wintri三个维度均衡折衷,视觉自然国家地理风格、高端出版地图

表格里的代码就是 R 里设置投影时直接用到的 proj4string 投影字符串。后面实操章节会告诉你怎么用。

2.3 每种投影的性格,逐个聊

Plate Carree 是最朴素的投影,直接把经度当 X 坐标、纬度当 Y 坐标画出来。优点是概念简单、计算开销极小,非常适合做数据分析时的中间产物,但高纬度地区的国家被拉得又宽又扁,格陵兰看起来和非洲一样大,出版地图里很少单独用它。不过在科研论文里,如果只是示意分布范围,用它反而最不容易引发争议。

Web Mercator 是互联网地图的事实标准,你在各种在线地图里看到的都是它。它最大的特点是等角保形,也就是说小范围内的方向关系不变,所以导航软件都用它。但它的面积失真极其严重:俄罗斯看起来比整个非洲还大很多倍,实际非洲面积约 3000 万平方公里,俄罗斯约 1700 万平方公里,夸张的变形让它完全不适合用来表达统计数据的世界分布。

Robinson 是折衷投影的代表作,不追求任何单项精度,而是在面积、形状、距离之间找平衡。1946 年由 Arthur Robinson 设计,后来被大量用于世界地图集。它的视觉效果比较温和,国家轮廓辨认度高,作为出版物里的“世界总览图”非常合适。

Mollweide 是真正的等面积投影。如果你的地图想要表达“不同国家面积对比”或者“全球资源总量分布”,必须用等面积投影,否则读者会被视觉面积误导。Mollweide 的经纬网呈椭圆形,换个说法就是“把橘子皮压成橄榄球形状再摊平”,视觉效果优雅且统计严谨。

Winkel Tripel 是德国制图师 Oswald Winkel 在 1921 年提出的,核心思想是把两种基础投影的结果做平均,让三个变形指标都比较小。国家地理学会从 1998 年起用了很多年,视觉上最接近人们对“世界地图”的预期,适合强调美观和综合表现的出版场景。

2.4 选投影的两条实战经验

第一,先想清楚你这张地图要回答什么问题。如果是“哪些国家产量占比高”,必须用等面积投影;如果是“某条航线怎么飞”,等角投影更合理;如果只是“世界概况长什么样”,折衷投影最保险。投影选对了,地图才有统计意义上的说服力。

第二,注意投影中心参数lon_0。同一个投影,中心经线放在 0 度还是 -100 度,画出来的图完全不一样。R 里通过修改投影字符串中的+lon_0=数值来调整。出版地图时,尽量把区域中心放在投影中心附近,可以显著减少边缘变形。


3. 环境准备与地理数据获取

3.1 安装 R 包和系统依赖

开始写代码前,先把依赖装好。核心需要这几个包:

  • ggplot2:绘图主力
  • sf:空间矢量数据的读写与坐标转换
  • rnaturalearth:从 Natural Earth 获取全球地图数据
  • rnaturalearthdata:配套数据包
  • dplyr:数据处理
  • ggmapcn:辅助国内用户处理中国边界和地图样式

安装命令很简单:

install.packages(c("ggplot2", "sf", "rnaturalearth", "rnaturalearthdata", "dplyr", "ggmapcn"))

但这里有个常见坑:sf包在 Windows 上一般有现成的二进制包,问题不大;在 Linux 服务器上,它依赖 GDAL、GEOS、PROJ 三个地理底层库,缺一个都会编译失败。我的建议是,如果不想折腾系统库,直接用 conda 创建 R 环境,conda install -c conda-forge r-sf能省掉很多心累。

3.2 世界地图数据从哪里来

地图数据和普通表格数据不一样,它包含的不只是国家名称和人口数字,还包含每个国家的边界坐标集合。在空间数据术语里,这种格式叫矢量数据。

R 里最方便的世界地图数据源是rnaturalearth,它相当于把 Natural Earth 这个开源地图项目的数据直接下载到本地。Natural Earth 提供 1:10m、1:50m、1:110m 三种精度,数据量从大到小,绘图时根据需求选择:要打印大图选高精度,网页预览用低精度就够了。

代码里常用的是:

world <- ne_countries(scale = "medium", returnclass = "sf")

scale = "medium"对应 1:50m 的精度,轮廓清晰度和文件大小比较均衡。returnclass = "sf"表示返回 sf 格式数据,这是 ggplot2 绘图的推荐格式。

3.3 EPSG、proj4string 和坐标系速查

说到投影,必然绕不开 EPSG 和 proj4string 这两个概念。EPSG 是一套空间参考系统的编号,比如 EPSG:4326 代表 WGS84 经纬度坐标,EPSG:3857 代表 Web Mercator。它像一个图书馆索引,查到编号就能找到对应的完整参数。

proj4string 则是一段直接描述投影参数的文本串,以+proj=开头,后面跟各种参数。它的灵活性更高,Robinson 这类不在 EPSG 编号体系里的投影,只能用 proj4string 来定义。

在 R 的sf包中,查看一个对象的坐标系用st_crs(),转换坐标系用st_transform()。实际操作中经常碰到经纬度数据没有正确声明坐标系的情况,这时候要先st_set_crs()声明,再st_transform()转换,顺序不能乱。很多新手一上来就st_transform报错,十有八九是漏了声明这一步。


4. 实操:从零绘制一张出版级世界地图并对比 5 种投影

4.1 搭好画布:导入数据并转成 sf 对象

先看一段完整的准备工作代码:

library(sf) library(ggplot2) library(dplyr) library(rnaturalearth) library(rnaturalearthdata) library(ggmapcn) world <- ne_countries(scale = "medium", returnclass = "sf")

这一步执行完,world就是一个标准的 sf 数据框。可以用glimpse(world)查看结构,里面除了国家边界几何列geometry,还包括name、continent、pop_est、economy等常用属性字段。这意味着你后面可以用dplyr直接筛选某个大洲,或者按经济类型上色,不用再手动关联外部数据。

如果数据来自其他来源,比如自己下载的 shapefile,用st_read("路径/文件.shp")导入即可。需要注意,shapefile 由多个文件组成,导入时只选.shp文件就行。

4.2 一次性生成 5 种投影的转换结果

投影转换的核心函数是st_transform()。我先把 5 种投影字符串存成命名列表:

crs_list <- c( "Plate Carree" = "+proj=eqc +lon_0=0 +datum=WGS84 +units=m +no_defs", "Web Mercator" = "EPSG:3857", "Robinson" = "+proj=robin +lon_0=0 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs", "Mollweide" = "+proj=moll +lon_0=0 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs", "Winkel Tripel" = "+proj=wintri +lon_0=0 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs" )

然后用lapply对同一份世界数据分别转换,再合并起来,便于后期用facet_wrap一张图画 5 个小图对比。

world_by_proj <- lapply(names(crs_list), function(nm) { world %>% st_transform(crs = crs_list[[nm]]) %>% mutate(proj = nm) }) %>% bind_rows()

这段代码会报一个轻微警告,提示st_transform可能改变属性,不用慌,正常现象。

有一点要特别提一下:st_transform要求输入的 sf 对象坐标系必须先声明正确。ne_countries返回的数据默认是 EPSG:4326,所以直接转换没问题。如果你手动读入的数据没有坐标系信息,先执行:

world <- st_set_crs(world, 4326)

否则转换时会出现“不能确定源坐标系”之类的错误。

4.3 出版级主题与配色细节

出图之前,主题设置决定了这张图拿出去是不是“出版级”。我常用的世界地图主题代码如下:

theme_map <- theme_void() + theme( plot.background = element_rect(fill = "#ffffff", color = NA), panel.background = element_rect(fill = "#eaf2f8", color = NA), plot.margin = margin(10, 10, 10, 10), strip.text = element_text(size = 14, face = "bold", color = "#2c3e50") )

简单说明几个关键点:

  • 陆地和海洋的颜色尽量用低饱和度的色系。米白、浅灰、浅蓝搭配,不会抢图表主体的视觉权重。
  • 国界线不要太粗,linewidth = 0.15在打印时已经足够清晰,过粗会让地图显得粗糙。
  • 如果地图只是作为数据分析图的背景,可以把国家填充包成fill = "gray90",只保留轮廓。
  • 出版级地图一般不需要图例显示国家名称,除非地图本身就是在展示某个属性的空间分布。

4.4 五图合一的投影对比,核心绘图代码

合并后的数据包含一个proj列,直接用facet_wrap按投影类型分面展示:

p_all <- ggplot(world_by_proj) + geom_sf(fill = "#f5f0e6", color = "#6b6b6b", linewidth = 0.15) + facet_wrap(~ proj, ncol = 2) + theme_map + labs(caption = "Data: Natural Earth | 绘图: R ggplot2 + ggmapcn") ggsave("world_projection_compare.png", p_all, width = 12, height = 14, dpi = 300)

运行完会得到一张包含 5 个子图的对比总览,Robinson 和 Winkel Tripel 的视觉平衡度、Mollweide 的椭圆边界、Web Mercator 的高纬膨胀,一眼就能看出来。

4.5 单独出图:一张用于正式出版的单张世界地图

实际使用时,往往不需要一页画 5 个图,而是单出一张成品图。下面这个例子演示怎么绘制一张 Robinson 投影的世界地图,并用 ggplot2 的刻度细节优化:

world_robinson <- world %>% st_transform(crs = "+proj=robin +lon_0=0 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs") p_single <- ggplot(world_robinson) + geom_sf(aes(fill = pop_est), color = "white", linewidth = 0.1, show.legend = TRUE) + scale_fill_viridis_c(trans = "log10", labels = scales::label_number()) + theme_map + labs( title = "世界人口估计分布", subtitle = "Robinson 投影(WGS84)", fill = "人口估计值" ) ggsave("world_population_robinson.png", p_single, width = 10, height = 6, dpi = 300)

这段代码展示了如何把数据属性直接映射到颜色上,人口值采用了对数变换,避免少数人口大国把配色层次拉平。

如果你想输出投稿级别的矢量图,推荐保存成 PDF 或者 SVG:

ggsave("world_population_robinson.pdf", p_single, width = 10, height = 6, device = cairo_pdf)

矢量图放大到任何尺寸都不会糊,排版时导入 Illustrator 或 InDesign 还能继续编辑里面的元素,这是位图 PNG 做不到的。

4.6 关于经纬网和比例尺的补充

出版地图里经常要加经纬网。ggspatial包提供了比较方便的函数,思路是添加一个格网图层,并控制经线纬线的标注格式。由于不同投影下经纬网的形态差异很大,出版前真有必要开着 PDF 输出检查一遍,别让格网线把地图搞得乱糟糟。

世界地图一般不需要加比例尺,因为不同纬度的比例尺并不一致,强行加一个容易产生误导。但指北针方向在很多场景可以加上,尤其你要表达“哪边是北”的时候。对于全球范围的地图,通常直接在北方向的地方写字母 N 就够了。


5. 常见问题与避坑指南

5.1 sf 包安装不上,怎么处理

Windows 用户直接install.packages("sf")通常能拿到官方预编译包,但如果你的 R 版本太旧,可能会因为缺少系统库而失败。这时候第一步是升级 R,第二步是装最新版sf。Linux 用户比较麻烦,建议先确认系统里有没有 GEOS、GDAL、PROJ 的开发库,或者干脆用 conda 环境一条路走到底。我自己的经验是,在 Linux 服务器上折腾源码安装容易把人磨没耐心,conda 是最快最稳的。

5.2 投影后图形出现黑边、切割异常

这种情况常见于 Web Mercator,因为投影的数学定义有一条“无限延伸”的纬度界限,纬度超过约 85 度的数据被拉伸到无穷远,于是地图上下边缘出现难看的空白或切边。

解决方法是给绘图范围加限制,比如:

coord_sf( xlim = c(-20037508, 20037508), ylim = c(-20037508, 20037508) )

把坐标范围手动限定在 Web Mercator 的有效范围内。其他投影如果出现要素超出画面,也可以通过调整xlim、ylim解决,但要注意别把不该裁掉的数据裁没了。

5.3 关于中国地图边界的正确姿势

画中国地图或者包含中国地界的世界地图,是一个必须谨慎对待的环节。rnaturalearth数据来自 Natural Earth,在部分边界细节上不一定符合国内出版规范。ggmapcn这个包解决的核心痛点就在这里——它专门处理了中国地图边界数据,对需要出版规范的场景提供了更可靠的封装。

我个人的做法是:如果成图要印刷或者公开出版,一律使用国家权威部门发布的标准地图底图,或者直接用ggmapcn内置的合规数据,不要拿国外开源数据直接交上去。边界数据这种事,正规渠道最稳妥,省心也避免麻烦。

5.4 中文字体在地图里显示成方块

ggplot2 默认字体不含中文字形,直接写中文标题、图注,输出图片后会出现方框。解决办法是使用showtext包加载系统字体:

library(showtext) font_add("NotoSansCJK", "你的字体文件路径/NotoSansCJKsc-Regular.otf") showtext_auto()

配置完之后,ggplot2 里写中文就能正常渲染。如果是 Windows,也可以用windowsFonts(SimHei = windowsFont("SimHei"))把系统黑体注册进去,但在保存 PDF 时兼容性不如showtext稳定。

5.5 出图模糊、分辨率不够

出版级图片的底线一般是 300 DPI。ggsave默认 300 DPI 的效果,在普通屏幕上看着很清晰,但印刷到 A4 幅面可能就差强人意。我的建议是,尺寸和 DPI 一起设置:

ggsave("final_map.png", width = 16, height = 10, units = "cm", dpi = 600)

如果对印刷有更高要求,直接用 PDF 或 EPS 矢量格式,不需要再纠结 DPI。另外要注意,导出图片文件太大时,可以考虑先用st_simplify简化边界几何,保留主要轮廓、减少顶点数量,文件体积能缩到原来的十分之一,绘图速度也快很多。

5.6 数据量很大时绘图卡顿

全球 1:10m 的高精度数据包含几十万个顶点,用 ggplot2 绘制时会导致渲染极慢。这时候一个非常有效的调优方法是降低数据精度:

world_small <- ne_countries(scale = "small", returnclass = "sf")

或者对已有数据做简化处理:

world_simplified <- st_simplify(world, dTolerance = 0.02)

dTolerance越大,简化程度越高,图形越粗糙。具体取值看你的出图尺寸和用途,多试几次找到平衡点。

最后分享一点个人体会

这套 R 地图流程我用了快两年,踩过的坑不少,但越用越觉得值。让我印象最深的不是省下了多少时间,而是整个过程变得“透明”了——每一张图怎么来的、用什么投影、哪个参数设成多少,全部写在脚本里,同事接手项目看一眼代码就全明白了,再也不是某个人的电脑里躺着一个神秘工程文件。

如果你过去一直习惯用 GIS 软件出图,不妨从一张最简单的世界地图开始,试着把投影切换、配色调整、批量出图这些流程搬到 R 里跑一遍。一开始可能不顺手,但当你发现改一个参数就能重画整套图的时候,你会愿意把更多重复劳动都交给脚本。说到底,地图生产不该是手工作坊,而应该是一条可复现、可维护的流水线。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询