ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

R语言+ggplot2绘制出版级世界地图:投影选择与实操指南

R语言+ggplot2绘制出版级世界地图:投影选择与实操指南 第一次用 R 语言 ggplot2 搭配 ggmapcn 画出能直接丢进论文配图的世界地图时我果断把桌面 GIS 软件的快捷方式从 Dock 上拖走了。你需要快速产出几十张不同投影的出版级世界地图、又想让整个流程可复现可修改纯靠鼠标点选图层属性那种方式真的会把人逼疯。这篇想分享的就是一套我自己跑了很久的 R 绘图方案从数据获取、坐标系与投影转换、5 种常用投影对比到最后怎么输出出版级文件都会一步步拆开讲。适合有一定 R 基础、但不想为出图去啃 GIS 软件的人参考也适合正在做数据报告、论文配图、成果展示时经常需要地图背景的读者。1. 项目背景为什么我放着 GIS 软件不用非要折腾 R1.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等距圆柱projeqc lon_00经线和纬线等距高纬地区严重拉伸快速预览、科研绘图、最简方案Web Mercator网络墨卡托EPSG:3857等角保形面积随纬度急剧膨胀在线地图、导航、Web 展示Robinson罗宾逊projrobin面积、形状、距离综合折衷教科书、新闻媒体、科普地图Mollweide摩尔韦德projmoll等面积全球面积关系准确全球性统计分布、资源分布图Winkel Tripel温克尔三重projwintri三个维度均衡折衷视觉自然国家地理风格、高端出版地图表格里的代码就是 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 projeqc lon_00 datumWGS84 unitsm no_defs, Web Mercator EPSG:3857, Robinson projrobin lon_00 x_00 y_00 datumWGS84 unitsm no_defs, Mollweide projmoll lon_00 x_00 y_00 datumWGS84 unitsm no_defs, Winkel Tripel projwintri lon_00 x_00 y_00 datumWGS84 unitsm 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 projrobin lon_00 x_00 y_00 datumWGS84 unitsm 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 或者 SVGggsave(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 里跑一遍。一开始可能不顺手但当你发现改一个参数就能重画整套图的时候你会愿意把更多重复劳动都交给脚本。说到底地图生产不该是手工作坊而应该是一条可复现、可维护的流水线。
返回列表