R: R 地图与空间数据
最后更新:2026-08-26
真实项目里地理空间数据越来越重要——疫情地图、滴滴打车热力图、京东配送范围。这一课我们学 R 地图可视化:sf 处理矢量数据、ggplot2 画地图、leaflet 做交互地图。
读完这一课你能用 R 画 4 类地图:静态地图(ggplot2 + sf)、分级统计图(choropleth)、点密度图、交互地图(leaflet)。
1. 你将学到
- 空间数据基础(矢量/栅格、shapefile、坐标系)
- sf 包读取和处理 shapefile
- ggplot2 + geom_sf 画地图
- CRS 坐标系(WGS84 / Web Mercator)
- choropleth 区域填色图
- leaflet 交互地图
- 实战:中国城市销售热力图
2. 一个物流热力图的故事
(1) 痛点:销售数据没有空间感
Alice负责全国销售,manager问:"哪个省卖得最好?"表格看了半天,manager说:"给我看地图。"
(2) R 的解法
library(sf)
library(ggplot2)
# 1. 读取中国地图
china <- st_read("china.shp")
# 2. 合并销售数据
china_sales <- china |>
left_join(sales_by_province, by = c("name" = "province"))
# 3. 一行画分级统计图
ggplot(china_sales) +
geom_sf(aes(fill = sales)) +
scale_fill_gradient(low = "lightblue", high = "darkred") +
labs(title = "全国销售分布", fill = "销售额")
3 行代码 → 出版级中国销售地图。
3. 空间数据基础
(1) 矢量 vs 栅格
graph LR
A[空间数据] --> B[矢量数据<br/>Vector]
A --> C[栅格数据<br/>Raster]
B --> D[点 Point<br/>线 Line<br/>面 Polygon]
C --> E[像素 Pixel<br/>卫星图/温度图]
style A fill:#fff3cd
style B fill:#cce5ff
style C fill:#d4edda
本课讲矢量数据(点/线/面)。
(2) shapefile 是什么?
Shapefile = ESRI 公司的矢量数据标准,由 4 个文件组成:
data/
├── china.shp # 几何信息(坐标)
├── china.shx # 形状索引
├── china.dbf # 属性信息(名称、人口等)
└── china.prj # 投影信息(坐标系)
(3) GeoJSON
现代 Web 地图常用 GeoJSON(基于 JSON):
{
"type": "Feature",
"properties": {"name": "北京", "value": 1000},
"geometry": {
"type": "Point",
"coordinates": [116.4, 39.9]
}
}
4. sf 包:空间数据框架
(1) 为什么用 sf?
| 包 | 特点 | 推荐度 |
|---|---|---|
| sf | 现代、R tidyverse 风格、ggplot2 整合 | ⭐⭐⭐ |
| sp | 老式、R 基础语法 | ❌ 新代码不推荐 |
| raster | 栅格数据专用 | 用于栅格 |
(2) 安装和加载
install.packages("sf")
library(sf)
(3) 读取 shapefile
library(sf)
# 读取 shapefile
china <- st_read("data/china.shp")
# Reading layer `china' from data source `data/china.shp' using driver `ESRI Shapefile'
# Simple feature collection with 34 features and 5 fields
# Geometry type: MULTIPOLYGON
# Dimension: XY
# Bounding box: xmin: 73.5 ymin: 3.5 xmax: 135 ymax: 53.5
# CRS: 4326 ← WGS84 经纬度
# 查看结构
print(china)
# 实际就是 data.frame 多了 geometry 列
# A tibble: 34 × 6
# name population ... geometry
# <chr> <int> <MULTIPOLYGON>
# 几何信息
st_geometry(china)
st_crs(china) # 坐标系
st_bbox(china) # 边界框
(4) 读取 GeoJSON
# 读取 GeoJSON
cities <- st_read("data/cities.geojson")
# 或从 URL
cities <- st_read("https://example.com/cities.geojson")
(5) 写 shapefile / GeoJSON
# 写 shapefile
st_write(china, "output/china_new.shp")
# 写 GeoJSON
st_write(china, "output/china.geojson")
5. CRS 坐标系
(1) 什么是 CRS?
CRS (Coordinate Reference System) 坐标系——把地球的"球面"映射到"平面":
graph LR
A[地球球面] -->|WGS84 4326<br/>经纬度| B[平面坐标]
A -->|Web Mercator 3857<br/>米| B
A -->|中国 2000<br/>米| B
(2) 常用 CRS 代码
| EPSG | 名称 | 用途 |
|---|---|---|
| 4326 | WGS84 | GPS / 通用(最常用) |
| 3857 | Web Mercator | 谷歌地图、OpenStreetMap |
| 4490 | CGCS2000 | 中国国家坐标系 |
| 32650 | UTM 50N | 局部高精度 |
(3) 转换坐标系
# 查看当前 CRS
st_crs(china)
# Coordinate Reference System:
# EPSG: 4326
# proj4string: "+proj=longlat +datum=WGS84 ..."
# 转换为 Web Mercator(3857)
china_3857 <- st_transform(china, 3857)
# 转换为中国 2000
china_4490 <- st_transform(china, 4490)
(4) 经纬度提取
# 从几何对象提取经纬度
coords <- st_coordinates(china)
# X = 经度, Y = 纬度
6. ggplot2 + geom_sf 画地图
(1) 基础地图
library(ggplot2)
library(sf)
ggplot(china) +
geom_sf() +
labs(title = "中国地图")
(2) 填色地图(按属性填色)
# 按省份填色
ggplot(china) +
geom_sf(aes(fill = name)) +
labs(title = "中国省份", fill = "省份") +
theme_minimal()
(3) 简化几何(性能优化)
# 大地图(>10 万坐标点)用 st_simplify 简化
china_simple <- st_simplify(china, dTolerance = 1000)
# 画图(快 10 倍)
ggplot(china_simple) + geom_sf()
(4) 主题美化
ggplot(china) +
geom_sf(fill = "lightblue", color = "white", linewidth = 0.3) +
labs(title = "中国地图") +
theme_void() + # 去坐标轴
theme(
plot.title = element_text(face = "bold", hjust = 0.5),
legend.position = "none"
)
7. choropleth 分级统计图
(1) 合并数据 + 画分级图
# 模拟销售数据
sales_by_province <- data.frame(
name = c("北京", "上海", "广东", "江苏", "浙江", "四川", "湖北", "陕西"),
sales = c(5000, 4500, 4000, 3500, 3000, 2500, 2000, 1500)
)
# 合并到地图
china_sales <- china |>
left_join(sales_by_province, by = "name")
# 画分级统计图
ggplot(china_sales) +
geom_sf(aes(fill = sales), color = "white", linewidth = 0.3) +
scale_fill_gradient(
low = "lightyellow",
high = "darkred",
na.value = "gray90"
) +
labs(title = "全国销售分布", fill = "销售额(万元)") +
theme_void() +
theme(
plot.title = element_text(face = "bold", hjust = 0.5, size = 16),
legend.position = "right"
)
(2) 分级(连续 → 离散)
# 用 cut 把连续值分成 5 档
china_sales <- china_sales |>
mutate(sales_level = cut(sales,
breaks = c(0, 1000, 2000, 3000, 5000),
labels = c("低", "中", "高", "很高"),
include.lowest = TRUE))
ggplot(china_sales) +
geom_sf(aes(fill = sales_level), color = "white", linewidth = 0.3) +
scale_fill_brewer(palette = "YlOrRd", na.value = "gray90") +
labs(title = "全国销售等级", fill = "等级") +
theme_void()
8. 点密度图
# 城市点(经纬度 + 销售)
cities <- data.frame(
name = c("北京", "上海", "广州", "深圳"),
lon = c(116.4, 121.5, 113.3, 114.1),
lat = c(39.9, 31.2, 23.1, 22.5),
sales = c(5000, 4500, 4000, 3000)
) |>
st_as_sf(coords = c("lon", "lat"), crs = 4326)
# 在地图上画点
ggplot() +
geom_sf(data = china, fill = "lightgray", color = "white") +
geom_sf(data = cities, aes(size = sales), color = "red", alpha = 0.7) +
scale_size_continuous(range = c(2, 12), name = "销售额") +
labs(title = "城市销售点图") +
theme_void()
9. leaflet 交互地图
(1) 安装和基础
install.packages("leaflet")
library(leaflet)
# 基础交互地图
m <- leaflet() |>
addTiles() |> # OpenStreetMap 底图
setView(lng = 116.4, lat = 39.9, zoom = 4) # 北京为中心
m # 在 RStudio Viewer 显示
(2) 加点(标记)
# 加城市点
leaflet(cities) |>
addTiles() |>
addMarkers(
lng = ~lon, lat = ~lat,
popup = ~paste0("<b>", name, "</b><br>销售:", sales, "万元"),
label = ~name
)
(3) 加圆圈(按大小)
leaflet(cities) |>
addTiles() |>
addCircles(
lng = ~lon, lat = ~lat,
radius = ~sqrt(sales) * 1000, # 半径按销售
color = "red",
fillOpacity = 0.5,
popup = ~paste0(name, ": ", sales, "万元")
)
(4) 加分级填色
# 用 sf 数据
leaflet(china_sales) |>
addTiles() |>
addPolygons(
fillColor = ~colorNumeric("YlOrRd", sales)(sales),
fillOpacity = 0.7,
color = "white",
weight = 1,
popup = ~paste0("<b>", name, "</b><br>销售:", sales, " 万元")
) |>
addLegend(pal = colorNumeric("YlOrRd", china_sales$sales),
values = ~sales, title = "销售额")
(5) 保存为 HTML
# 保存为独立 HTML(可分享)
library(htmlwidgets)
saveWidget(m, "interactive_map.html", selfcontained = FALSE)
10. 完整示例:全国销售热力图
下面是一个完整工作流示例,把本课所有地图知识串起来。
▶ 示例:4 城市销售地图(静态 + 交互)
# ============================================
# 4 城市销售地图
# 功能:ggplot2 静态图 + leaflet 交互图
# ============================================
library(sf)
library(ggplot2)
library(dplyr)
library(leaflet)
library(htmlwidgets)
# 1. Prepare data
# 4 城市点(经纬度)
cities <- data.frame(
name = c("北京", "上海", "广州", "深圳"),
lon = c(116.4074, 121.4737, 113.2644, 114.0579),
lat = c(39.9042, 31.2304, 23.1291, 22.5431),
sales = c(5000, 4500, 4000, 3000),
region = c("华北", "华东", "华南", "华南")
) |>
st_as_sf(coords = c("lon", "lat"), crs = 4326)
# 中国省级简化多边形(这里模拟数据)
china_provinces <- data.frame(
name = c("北京市", "上海市", "广东省"),
lat = c(39.9, 31.2, 23.1),
lon = c(116.4, 121.5, 113.3)
)
# 2. 静态地图:ggplot2 + sf
p_static <- ggplot() +
# 简化:用 geom_point 替代 geom_sf(无 shapefile)
borders(database = "world", regions = "China",
fill = "lightgray", color = "white") +
geom_sf(data = cities, aes(size = sales, color = region),
alpha = 0.7) +
geom_text(data = as.data.frame(st_coordinates(cities)),
aes(X, Y, label = cities$name),
vjust = -1.5, size = 4, fontface = "bold") +
scale_size_continuous(range = c(3, 12), name = "销售额(万元)") +
scale_color_brewer(palette = "Set1", name = "区域") +
labs(title = "中国 4 大城市销售分布",
subtitle = "数据:2024 年 Q4",
caption = "底图:Natural Earth | 制图:R + ggplot2") +
coord_sf(xlim = c(100, 125), ylim = c(20, 45)) +
theme_minimal() +
theme(
plot.title = element_text(face = "bold", hjust = 0.5, size = 16),
plot.subtitle = element_text(hjust = 0.5, color = "gray40"),
legend.position = "bottom"
)
print(p_static)
# 3. 保存静态图
ggsave("static_sales_map.png", p_static,
width = 10, height = 8, dpi = 300)
cat("=== 静态地图已保存:static_sales_map.png ===\n")
# 4. 交互地图:leaflet
cities_df <- as.data.frame(st_coordinates(cities)) |>
rename(lon = X, lat = Y) |>
cbind(name = cities$name, sales = cities$sales, region = cities$region)
m_interactive <- leaflet(cities_df) |>
addTiles() |>
setView(lng = 114, lat = 33, zoom = 4) |>
addCircleMarkers(
lng = ~lon, lat = ~lat,
radius = ~sqrt(sales) / 5,
color = ~case_when(
region == "华北" ~ "red",
region == "华东" ~ "blue",
region == "华南" ~ "green"
),
fillOpacity = 0.6,
stroke = TRUE,
weight = 2,
popup = ~paste0(
"<div style='font-family: Arial; font-size: 14px;'>",
"<b>", name, "</b><br>",
"区域:", region, "<br>",
"销售:", sales, " 万元<br>",
"经度:", round(lon, 2), "<br>",
"纬度:", round(lat, 2),
"</div>"
),
label = ~paste0(name, ": ", sales, "万元")
) |>
addLegend(
"bottomright",
colors = c("red", "blue", "green"),
labels = c("华北", "华东", "华南"),
title = "区域"
) |>
addScaleBar(position = "bottomleft")
# 5. 显示(在 RStudio Viewer)
m_interactive
# 6. 保存为 HTML
saveWidget(m_interactive, "interactive_sales_map.html",
selfcontained = TRUE)
cat("=== 交互地图已保存:interactive_sales_map.html ===\n")
# 7. 输出统计
cat("\n=== 销售统计 ===\n")
print(cities_df |> select(name, region, sales))
预期输出:
- 静态地图(PNG 300 dpi,4 个圆点大小映射销售)
- 交互地图(HTML,鼠标悬停显示弹窗)
❓ 常见问题
Q:sf 和 sp 选哪个? A:sf(Simple Features)是 R 空间数据现代标准,tidyverse 风格,与 dplyr/ggplot2 无缝整合。新代码用 sf,老 sp 代码已停止维护。**
Q:4326 和 3857 怎么选? A:4326(WGS84 经纬度)通用 + GPS;3857(Web Mercator)地图服务 + 谷歌/OSM。ggplot2 + sf 一般用 4326,leaflet 默认 3857。**
Q:shapefile 从哪下载? A:
- 中国地图:阿里云 DataV.GeoAtlas(https://datav.aliyun.com/portal/school/atlas/area_selector)
- 全球地图:Natural Earth(https://www.naturalearthdata.com/)
- 中国详细:https://github.com/longwosion/geojson-map-china
family = "SimHei"。或者用 borders(database = "world", regions = "China") 时已内置中文名。rayshader 包(基于 ggplot2 转 3D)或 plotly 的 3D 散点 + 地图底图。本课不深入。- 报告/论文用 ggplot2(静态、出版级)
- 网页/应用用 leaflet(交互、可缩放)
- 可分享用 leaflet 导出 HTML
📖 小节
- 空间数据 = 矢量(点/线/面)+ 栅格(像素);本课讲矢量
- Shapefile 由 4 个文件组成(.shp + .shx + .dbf + .prj),GeoJSON 是现代 Web 格式
- sf 是 R 空间数据现代标准,tidyverse 风格
- 常用 CRS:4326(WGS84 经纬度)/ 3857(Web Mercator)
- ggplot2 +
geom_sf()画静态地图;choropleth 用aes(fill = 数值)+scale_fill_gradient - leaflet 做交互地图:addTiles(底图) + addMarkers/addCircles(点) + addPolygons(区域)
borders(database = "world")快速画世界地图(无需 shapefile)- 保存:静态用
ggsave()PNG/PDF;交互用saveWidget()HTML - R 地图应用:业务地理分布、疫情、物流、客户分布、热力图
📝 作业
-
基础题:从阿里云 DataV 下载中国省级 GeoJSON,用
st_read()读取,用ggplot() + geom_sf()画基础中国地图(不填色)。 -
基础题:在上题地图上按省份填色(用
aes(fill = name)),加theme_void()+ 主题美化。 -
基础题:用
borders(database = "world", regions = "China")+geom_point(data = cities, aes(lon, lat, size = sales))画中国城市点图,4 个城市自定义经纬度。 -
进阶题:模拟 5 个城市销售数据(含经纬度/销售/类别),用 leaflet 画交互地图:① 圆圈大小映射销售 ② 颜色映射类别 ③ 加弹窗显示详情 ④ 加图例 + 比例尺。保存为 HTML。
-
挑战题:完整工作流——下载中国省级 GeoJSON,模拟 31 省销售数据:① left_join 合并到地图 ② 用
cut()分成 5 档 ③ ggplot2 画分级统计图 ④ 美化(标题/副标题/中文字体) ⑤ leaflet 画交互版(含图例)⑥ 保存 PNG + HTML。截图保存。