---
title: "地图可视化(三):可交互地图"
author: "Rui"
date: "2023-04-04"
categories: [R, leaflet, rjson]
image: "peak.jpg"
format:
html:
code-fold: true
code-tools: true
---
```{r setup, include = FALSE}
# 设置默认参数
knitr::opts_chunk$set(
echo = TRUE,
fig.align = "center",
message = FALSE,
warning = FALSE,
collapse = TRUE
)
```
![](peak.jpg)
# 使用 `leaflet` 绘制可交互地图
使用 leaflet 来绘制可交互的地图,使用说明见:<https://rstudio.github.io/leaflet/>
## 加载数据
```{r}
library(tidyverse)
data <- read.csv("data/地址经纬度数据.csv", encoding = 'UTF-8')
data <- data %>%
filter(!is.na(longitude), !is.na(latitude))
data %>% head() %>% knitr::kable()
```
## 一点数据处理
由于企业/机构种类繁多,这里只选取频数最多的前 4 种类型:
```{r}
top4 <- data %>%
group_by(company_type) %>%
summarise(freq = n()) %>%
arrange(desc(freq)) %>%
slice(1:4) %>%
select(company_type)
top4 <- top4$company_type
print(top4)
```
```{r}
data <- data %>%
filter(company_type %in% top4)
```
## 使用 leaflet 绘制可交互地图
### 添加底图
```{r}
library(leaflet)
library(leafletCN)
```
```{r}
m1 <- leaflet() %>%
setView(lng = median(data$longitude), lat = median(data$latitude), zoom = 11) %>%
amap()
m1
```
需要注意的是,一般而言使用 `leaflet` 绘制地图的套路如下:
leaflet() %>%
addTiles() %>%
setView(lng, lat, zoom)
`leaflet` 中的 `addTiles()` 会使用默认的、国外的地图作为底图。但是在绘制中国地图时尽量不要使用国外平台的地图,这里我们使用了 `leafletCN` 来设置中国地图,其中 `amap()` 使用高德地图作为底图。当然也可以通过 `addTiles()` 来指定调用高德地图、百度地图或者腾讯地图等,见:<https://zhuanlan.zhihu.com/p/38168955?utm_oi=945412109924909056>。
### 添加点标记
```{r}
m2 <- m1 %>%
addMarkers(
lng = data$longitude,
lat = data$latitude,
popup = data$name,
label = data$name
)
m2
```
可以看出:在数据量较大、点比较密集时,使用默认的 pin 标记会显得非常拥挤。更麻烦的是无法更改这些 pin 的颜色。
另外,popup 参数用于设置 pin 的信息,只要将鼠标移动到 pin 图标上就可以看到设置的信息了。还可以做一些改进:`popup = ~paste("企业名称:", name, "<br>地址:", clean_address)` 来丰富显示的信息。
除了 pin 标记,还可以使用点标记。其中 `radius` 设置点的大小,`fillOpacity` 设置点的透明度。
```{r}
m3 <- m1 %>%
addCircleMarkers(
data = data,
lng = ~longitude,
lat = ~latitude,
color = "blue",
stroke = FALSE,
radius = 4,
fillOpacity = 0.3
)
m3
```
### 多类别点标记
设置调色盘:
```{r}
pal <- colorFactor(
c("blue", "green", "red", "orange"),
domain = data$company_type
)
```
```{r}
m4 <- m1 %>%
addCircleMarkers(
data = data,
color = ~pal(data$company_type),
lng = ~longitude,
lat = ~latitude,
#popup = ~paste("企业名称:", name, "<br>地址:", clean_address),
popup = ~paste0("<b>企业名称:</b>", name, "<br>",
"<b>企业类型:</b>", company_type, "<br>",
"<b>地址:</b>", clean_address),
stroke = FALSE,
radius = 3,
fillOpacity = 0.5
)
m4
```
::: column-margin
还可以使用以下代码创建一个复选框来过滤企业类型:
m4 <- m4 %>%
addLayersControl(
overlayGroups = unique(data$company_type),
options = layersControlOptions(collapsed = FALSE)
)
但是这在 `Rmarkdown` 和 `Quarto` 中仅仅显示复选框,不能够真正过滤数据。只能依靠 `shiny` 或者 `JavaScript` 来实现。
:::
### 绘制热力图
还可以根据点的密集程度绘制热力图,需要使用 `leaflet.extras` 这个拓展包:
```{r}
library(leaflet.extras)
m1 %>%
addHeatmap(lng = data$longitude, lat = data$latitude, radius = 8)
```
### 加入区域边界
绘制边界的原理和在地图中标点是一样的。首先需要获得区域边界点的坐标,再通过连接这些点创建多边形区域,最后填充颜色。
#### 使用 leaflet 内置数据
```{r}
# 获取绍兴市行政区信息
region <- regionNames("绍兴市")
# 将越城区标记为 1,其他区域标记为 0
dat <- data.frame(region) %>%
mutate(value = if_else(region == "越城区", 1, 0))
```
因为只需要绘制出越城区边界,所以将越城区的值设置为 1(其实可以设定为任意的数),其他区域的值设置为 0。
```{r}
#| column: margin
dat %>% knitr::kable()
```
```{r}
# 创建地理信息dataframe
map <- leafletGeo("绍兴市", dat)
map %>% knitr::kable()
```
::: callout-note
map 中包含的信息绝不仅限于表格中的内容。
:::
```{r}
# 设置调色盘
pal <- colorNumeric(palette = "Blues", domain = map$value)
# 使用高德地图,设置缩放大小
m5 <- leaflet(map) %>%
setView(lng = median(data$longitude), lat = median(data$latitude), zoom = 11) %>%
amap() %>%
#加入边界及颜色
addPolygons(
stroke = TRUE,
smoothFactor = 1,
fillOpacity = 0.2,
weight = 1,
color = ~pal(value),
popup = ~htmltools::htmlEscape(popup)
)
m5
```
::: callout-note
- 可能是因为函数所使用的数据源没有更新,图中的区域比目前绍兴市越城区要小。
- 同时区域形状过于简单,并不符合真实的行政区边界情况。
:::
#### 使用外部数据
还有一种方法,那就是导入外部数据,并通过外部数据来创建多边形区域。那么在哪里可以找到越城区的边界点数据呢?
个人推荐一个比较便捷的做法:进入 [阿里云数据可视化平台DataV.GeoAtlas](http://datav.aliyun.com/portal/school/atlas/area_selector) ,点击范围选择器,找到并点击"越城区"。
![](DataV1.png)
可以通过 `rvest` 包调用 API,也可以直接点击下载 JSON 文件。我选择下载 JSON 文件。
![](DataV2.png)
可以使用 `rjson` 来读取 JSON 文件。
:::{.callout-warning}
除了 `rjson` 以外,`jsonlite` 和 `tidyjson` 也可以处理 JSON 文件。但要注意的是:`jsonlite` 在读取中文名的 JSON 文件时会报错。
:::
```{r}
library(rjson)
area_json <- fromJSON(file = "data/越城区.json")
```
可以使用 `addGeoJSON` 将 GeoJSON 文件中的信息加入到地图中:
```{r}
m1 %>%
addGeoJSON(area_json)
```
也可以将 JSON 文件中的经纬度数据提取到数据框中,通过 `addPolygons` 函数绘制多边形区域。
可以使用 `View()` 来查看读取的 JSON 文件都包含哪些信息:
![](View.png)
可以发现读取进来的 JSON 文件被 R 保存为一个嵌套列表。通过一级一级向下查看,可以定位到边界点的经纬度坐标。
```{r}
area_list <- area_json[["features"]][[1]][["geometry"]][["coordinates"]][[1]][[1]]
```
这些点的坐标被存储在列表中。为了方便调用,需要将它们提取到一个数据框中。
一个非常便捷、强大、优雅的实现方式是使用 `purrr` 包中的 `map` 类函数。
这里使用的是 `map_dfr` 函数,它可以将提取出来的数据以数据框的形式返回,并按行合并为一个数据框。
`purrr` 包的用法参考:<https://zhuanlan.zhihu.com/p/168772624>
```{r}
library(purrr)
# 使用 map 函数将经纬度数据从列表提取到数据框中
area_df <- map_dfr(area_list, ~data.frame(longitude = .x[1], latitude = .x[2]))
# 保存数据
write_excel_csv(area_df, "越城区边界.csv")
```
```{r}
m1 %>%
addPolygons(
lng = area_df$longitude,
lat = area_df$latitude,
fillColor = "seagreen",
fillOpacity = 0.2,
color = "darkseagreen",
weight = 2
)
```
函数 `addGeoJSON` 和 `addPolygons` 中一些常用的、共有的参数:
- 参数 `fillColor` 设置区域填充颜色
- 参数 `fillOpacity` 设置区域填充颜色的透明度
- 参数 `color` 设置区域边界线的颜色
- 参数 `weight` 设置区域边界线的粗细