资讯动态

RStudio地图绘制教程:sf与ggplot2空间可视化全流程指南

发布时间:2026/10/18 15:37:21 来源:尧图企业网站定制
简介针对R语言地图绘制的实操代码包面向数据分析和地理信息可视化用户专注解决从数据准备到地图输出的完整流程包括矢量与栅格数据读取、坐标系设置、中国省级底图导入和属性填充并结合ggplot2与ggspatial完成地图渲染。包体结构精简压缩包仅7KB共6个文件含3个R脚本、1个Markdown说明文档、1个inscode配置和1个gitignore代码量虽小但覆盖读取数据、分组分段、图例配置、比例尺与指北针添加等关键环节可直接运行或修改复用。目前已有64人浏览学习适合具备一定R基础、想系统上手地图绘制的数据分析与GIS爱好者。通过学习代码示例不仅能掌握省级地图数据的处理方法和批量制图的循环写法还能理解地理坐标系与投影坐标系的选择对出图效果的影响是一份轻量而实用的入门参考资料。1. RStudio地图绘制教程能解决什么一整套可直接改用的空间可视化代码包如果你在RStudio里折腾过地图大概率遇到过这几个问题读进来的行政区划数据画出来是变形的、经纬度点和区域边界对不上、中文字体在导出PDF时变成方块、分面图里的图例顺序莫名其妙翻转。这通常不是你的代码有错而是从数据清洗、坐标系转换到图层叠加的链路里缺少一套能直接复用的规范流程。这份RStudio地图绘制教程代码包解决的正是从GeoJSON边界数据到可发表统计地图的完整链路问题它面向有R语言基础、但没系统做过空间可视化的数据分析师和科研人员提供从sf对象构建、坐标系纠偏、ggplot2图层叠加到批量导出的全套可运行脚本。2. 先搞清楚三个问题再动手坐标系、数据源与统计图叠加方式初学者拿到地图数据后最常见的翻车是不看坐标系就开画结果区域边界和散点图完全错位。地图绘制和普通统计图最大的差异在于它同时涉及空间参照、几何对象和业务属性三类信息。在写第一行绘图代码之前有三个问题必须确认清楚否则后边每一步都是填坑。2.1 空间数据的两种形态sf对象与经纬度DataFrameR语言里处理空间数据实际工作中最常遇到的形态是两种一种是sf包创建的sf对象本质上是把几何信息点、线、面作为一列特殊变量塞进数据框里另一种是普通数据框只有lon和lat两列经纬度。这两者的适用边界完全不同。# 读取GeoJSON边界数据返回sf对象 library(sf) boundary - st_read(city_boundary.geojson, quiet TRUE) # 查看几何类型和坐标系 st_geometry_type(boundary) st_crs(boundary) # 手工构造经纬度数据框 stations - data.frame( station_id c(A01, A02, A03), lon c(110.12, 110.45, 110.33), lat c(30.21, 30.08, 30.42), value c(48.5, 62.3, 55.1) )st_read返回的对象可以直接用ggplot2的geom_sf绘制但前提是你需要确认边界数据本身带合法的几何字段。经纬度数据框则不能直接进入geom_sf必须先转换成sf对象。这里的关键区别是sf对象有明确的空间参照可以参与裁剪、合并、缓冲区分析经纬度数据框只是普通表格好在它可以直接作为散点图层叠加在底图上。日常经验是底图用sf对象业务点位用经纬度数据框两者在绘图层各司其职不需要强行把所有数据都转成sf。2.2 三套坐标系的取舍WGS84、GCJ02与投影坐标系坐标系是地图绘制里最容易变成玄学的地方实测中至少有一半的偏移问题出在这里。国际公开数据多用WGS84经纬度国内某在线地图服务返回的坐标是GCJ02火星坐标两者之间差几百米在街道尺度上直接画出来就是点位漂到马路对面。所以动手前必须确认数据来源和坐标类型。# 查看当前数据的坐标系信息 st_crs(boundary)$input # 如果发现是GCJ02需要用下方函数纠偏到WGS84 # 注意这是一个极简示例完整版在教程代码包的coordinate_fix.R中 gcj02_to_wgs84 - function(lon, lat) { # 为避免演示代码过长这里只保留函数骨架 # 实际运行时请使用包内完整实现 data.frame(lon lon, lat lat) } # 投影坐标转换示例WGS84经纬度转UTM 50N投影 boundary_utm - st_transform(boundary, crs 32650)投影坐标系的选择原则是区域范围大、关注面积和距离时用UTM或Albers等距投影只想把经纬度和散点对上、不做空间计算时直接用WGS84经纬度画反而最省事。我的建议是先用WGS84经纬度把图跑通确认点位对得上再做投影转换不要一次性叠太多变换。2.3 数据源怎么选行政区划GeoJSON、在线瓦片与公开统计数据的匹配数据源的质量直接决定成图效果。行政区划边界最常见的来源是公开GeoJSON仓库但下载后一定要检查几何是否闭合、属性表里是否有你需要的行政区代码字段。如果你打算把业务数据按区域填色就需要把业务表的区域名字段和GeoJSON属性表的区域名字段完全对齐——这里的坑在于两个表一个用“市辖区”一个用“区”或者一个带“省”字一个不带。# 检查边界数据的字段名 names(boundary) # 常见标准字段name, adcode, geometry # 业务数据与边界数据合并前先统一区域字段格式 library(dplyr) biz_data - biz_data %% mutate(region stringr::str_replace_all(region, 省|市, )) # 合并并检查是否有匹配不上的区域 merged - boundary %% left_join(biz_data, by c(name region))在线瓦片作为底图的做法在R里也能实现但受网络环境影响大。我会优先选择把瓦片缓存到本地在没有离线瓦片的情况下动态地图用某开源交互地图库兜底静态发布图则直接用GeoJSON边界代替底图等值线加在地界之上。教程代码包里给出的是一个通用的数据体检脚本跑一遍就能看到每个区域的属性匹配率和缺失情况。3. 把静态地图画出来从数据清洗到导出高分辨率PNG的全流程静态区域填色图是日常出镜率最高的地图类型也是这份教程代码包的核心内容。整个流程可以拆成四步清洗数据、合并边界、绘制图层、调整输出细节。每一步都有固定的写法照着抄就能跑通。3.1 数据清洗与字段统一过滤缺失几何、修正类型、处理空值拿到边界数据后先不要急着画先做三件体检几何字段是否全有效、属性字段是否有NA、业务表主键是否和边界表主键能匹配上。缺失几何会导致geom_sf直接报错NA值会导致填色图出现空洞字段匹配不上则会出现整片区域无数据的白板。# 过滤缺失几何并保留需要的字段 library(dplyr) boundary_clean - boundary %% filter(!st_is_empty(geometry)) %% select(name, adcode, geometry) %% mutate(name as.character(name)) # 业务数据的区域字段统一为字符型 biz_data - biz_data %% mutate(region as.character(region)) # 合并并检查匹配情况 plot_data - boundary_clean %% left_join(biz_data, by c(name region)) # 查看哪些区域没有匹配上业务数据 plot_data %% filter(is.na(value)) %% pull(name)这段代码里最关键的是st_is_empty这个函数它能识别出几何字段为空的行这些行在绘图时会直接让整个图层崩溃。as.character这一步容易被忽略如果区域编号一边是字符另一边是数值left_join会静默产生大量NA排查难度极高。建议每次合并后都跑一遍filter(is.na(value))把匹配不上的区域打印出来看。3.2 用geom_sf叠加图层区域填充、边界线宽与空白边距控制底图画法是整个流程里最稳定的部分ggplot2的geom_sf对sf对象的支持已经很完善。区域填充图的典型写法是这样的library(ggplot2) p - ggplot(plot_data) geom_sf(aes(fill value), color grey40, linewidth 0.3) scale_fill_viridis_c(option plasma, na.value grey90) theme_minimal(base_size 14) theme( axis.text element_blank(), axis.ticks element_blank(), panel.grid element_blank(), legend.position bottom, plot.margin margin(10, 10, 10, 10) ) labs(title 某区域监测值分布, fill 监测值) ggsave(map_basic.png, p, width 8, height 6, dpi 300)这里color控制边界线颜色linewidth控制边界粗细na.value决定缺失区域的填充色。theme_minimal之后必须把axis.text和panel.grid都清掉否则会显示经纬度网格破坏地图的完整感。ggsave的dpi设成300是为了保证后续印刷或放大不模糊宽高比建议先按8:6出草稿定稿后再微调。3.3 色板与图例的精细控制连续变量分段、离散变量配色与图例位置填色图最容易翻车的地方是图例。连续变量的色板如果跨度大默认的图例刻度间隔可能不合理离散变量则会遇到图例顺序翻转的问题——ggplot2会按字母序排列分类而不是按你业务上的自然顺序。解决方法是提前把分类变量转成因子并指定水平顺序。# 连续变量按分位数分段转成离散因子 plot_data - plot_data %% mutate(value_group cut(value, breaks quantile(value, probs c(0, 0.25, 0.5, 0.75, 1), na.rm TRUE), include.lowest TRUE )) # 离散配色并指定图例顺序 p_discrete - ggplot(plot_data) geom_sf(aes(fill value_group), color grey40, linewidth 0.3) scale_fill_brewer(palette YlOrRd, na.value grey90) theme_void() theme(legend.position bottom) labs(title 某区域监测值四分位分布) ggsave(map_discrete.png, p_discrete, width 8, height 6, dpi 300)cut函数默认左开右闭include.lowest TRUE保证最小值能落入第一段否则会出现一个NA分组。离散分段的优势是图例更易读劣势是丢掉了数值细节所以报告用的图建议保留一个连续版本做附录正文用分段版本。4. 分面与业务图层叠加把时间、点位和轨迹压进同一张图单张区域图只能承载一个维度。真实项目里经常要把多个月份的监测数据并排对比或者把监测点位、物流轨迹叠到底图上。这两个需求对应的分别是分面图和业务图层叠加也是教程代码包里第二个模块的内容。4.1 facet_wrap分面出图多时段并列与自由比例尺的设置分面是把同一个区域的多张图拼到一块的标准做法。操作上并不复杂只需要在ggplot对象上追加一个facet_wrap但参数设置有几个容易忽略的细节多张图的色板范围是否要统一图例是否要共享每张图的坐标轴是否要固定# 构造多时点数据假设有1月、2月、3月三个时段 map_data_month - bind_rows( plot_data %% mutate(month 2024-01), plot_data %% mutate(month 2024-02), plot_data %% mutate(month 2024-03) ) p_facet - ggplot(map_data_month) geom_sf(aes(fill value), color grey40, linewidth 0.2) scale_fill_viridis_c(option plasma, limits range(map_data_month$value, na.rm TRUE)) facet_wrap(~month, ncol 3) theme_void() theme(legend.position bottom) ggsave(map_facet.png, p_facet, width 12, height 5, dpi 300)limits参数是在分面场景里必须手动设置的否则每个分面会独立计算色板范围导致不同月份之间的颜色没有可比性。ncol 3控制每行放几张图当时段数是奇数时这样排比较均衡。theme_void()在分面场景里比theme_minimal()更合适因为去掉坐标轴后整张图更干净。4.2 叠加业务点位与路径geom_point与geom_path的坐标对齐点位和路径叠加最容易出问题的是坐标系不统一。如果你的底图是WGS84业务点位坐标也必须是WGS84。实际操作中业务系统导出的点位常常是GCJ02这种错位在视觉上表现为点位整体朝某个方向偏移离边界越远偏移越明显。# 把业务点位转成sf对象确保与底图坐标系一致 library(sf) stations_sf - st_as_sf(stations, coords c(lon, lat), crs 4326 ) # 底图和点位叠加 p_points - ggplot() geom_sf(data plot_data, fill grey95, color grey40, linewidth 0.3) geom_sf(data stations_sf, aes(size value), color #D73027, alpha 0.8) scale_size_continuous(range c(2, 8)) theme_void() theme(legend.position bottom) ggsave(map_points.png, p_points, width 8, height 6, dpi 300)点位图叠加的注意点先画底图再画点位点位的图层顺序在上层alpha建议设置在0.7到0.9之间避免点太多时互相遮挡到看不清边界。路径图同理用geom_path连接点位时注意group参数要指定路径编号否则ggplot会把所有点连成一条线。4.3 保存分面图的两种方式剪裁空白边距与调整多图拼版出图阶段最容易被人忽略的是空白边距。分面图因为坐标轴被隐藏经常会在上下左右留下大面积空白直接放进报告里显得很业余。这里我一般会用coord_sf配合xlim和ylim把显示范围裁剪到边界数据的外接框附近或者用theme(plot.margin margin(0, 0, 0, 0))压掉边距。p_facet_cropped - p_facet coord_sf( xlim c(st_bbox(plot_data)[1], st_bbox(plot_data)[3]), ylim c(st_bbox(plot_data)[2], st_bbox(plot_data)[4]) ) ggsave(map_facet_cropped.png, p_facet_cropped, width 12, height 5, dpi 300, bg white)st_bbox获取边界数据的外接框四个角点coord_sf把绘图区域裁剪到这个范围两个结合能有效消掉多余空白。注意coord_sf的参数顺序xlim是经度范围ylim是纬度范围写反的话图形会被裁得只剩一半。5. 地图绘制的五个隐蔽坑位坐标系偏移、字体豆腐块与图例失灵地图绘制里真正消耗时间的不是画图本身而是排坑。下面是这几年反复踩过的五类问题每一条都按现象、原因、解决的思路记录能帮你少走一个月的弯路。5.1 现象点位整体偏移离边界越远越明显原因是底图用的是WGS84点位坐标却是GCJ02火星坐标两者差值约几百米。解决方式是在数据清洗阶段就把点位坐标批量纠偏。教程代码包里的coordinate_fix.R提供了一个可用的批处理版本处理完后再用st_as_sf指定坐标系为4326。切记不要在画图之后再做纠偏因为已经进入ggplot管线的数据改起来更麻烦。5.2 现象中文标题和图例变成方块原因是R自带的PDF和PNG设备对中文字体支持有限尤其是Windows环境下经常缺中文字体。解决方式有两种一是用showtext包动态加载系统字体二是导出前手动指定family参数并确保字体名称正确。我一般用前者在脚本开头统一加载字体配置这样整个项目的图风格一致。5.3 现象离散图例顺序翻转数据段的顺序和业务逻辑相反原因是ggplot2会按因子水平的顺序排列图例如果你的字段是字符型它默认按字母序排列。比如“高、中、低”会排成“低、中、高”。解决方式是在mutate阶段就把字段转成有序因子用factor(levels c(低, 中, 高))显式指定顺序。图例名称用labs(fill ...)修改不要直接在字段名里带中文。5.4 现象区域填色溢出边界颜色漫到相邻区域原因是边界数据本身存在几何错误常见的有自相交多边形和未闭合的环。解决方式是先用st_make_valid()处理几何再去做合并和绘图。这个函数是sf包自带的处理速度很快强烈建议在读取GeoJSON后默认跑一遍。5.5 现象导出的PNG模糊或空白过多原因是dpi设置过低或者coord_sf没有裁剪显示范围。低dpi的问题用ggsave(dpi 300)解决空白过多的问题用第4.3节里的coord_sf配合st_bbox压边距。另外一个容易被忽略的点是ggsave的bg参数默认是白色但如果你的主题背景是透明的导出后会出现网格背景所以要显式指定bg white。6. 把出图流程固化成模板每次动手先跑数据体检的四个习惯地图绘制和普通数据分析有一个本质区别数据问题几乎不会报错只会以错位、空白、变形的方式呈现在图上。所以我现在接到任何地图任务第一件事不是打开RStudio写ggplot而是先跑一遍教程代码包里的00_check_data.R体检脚本确认边界几何有效、坐标系明确、字段匹配率达标然后再谈配色和出图。# 数据体检脚本的简化版骨架 library(sf) library(dplyr) # 第一步检查几何有效性 cat(几何有效比例:, mean(st_is_valid(boundary)), \n) # 第二步检查坐标系 print(st_crs(boundary)$input) # 第三步检查字段匹配率 match_rate - mean(plot_data$value 不是 NA) cat(业务字段匹配率:, round(match_rate * 100, 2), %\n)这段骨架代码里有几个习惯值得坚持体检和绘图分离体检脚本只输出诊断信息不做任何可视化所有坐标系转换集中在同一个脚本里绘图脚本不再出现st_transform避免同事拿到代码后各自为政每个输出文件统一用日期前缀命名比如20240628_map_basic.png方便回溯版本。从那以后我每次替别人排查地图问题第一句话都是先问你的数据体检脚本跑过了吗如果对方答不上来那问题多半就出在数据源头而不是绘图语句上。这套流程希望帮到你让你的地图出图从玄学变成稳定可复现的流水线。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑