本文来自读者厦门大学的李康国研究生投稿,讲述高德和 Leaflet 结合绘制地图。也欢迎其他小伙伴来分享你们的经验!
我们对 leaflet 包做了一期简单的入门教程:空间地理数据可视化之 leaflet 包及其拓展。之后,又将高德和该包相结合,介绍了前期需要准备的工作,见:Leaflet 与高德合并会擦出怎么样的火花?。这一期就到了绘制地图环节,下面将分享三类数据的绘制教程。
这篇推文讲的是第三节内容
由于 leaflet 自带的底图不是很合规,所以我们使用高德地图进行替换。
library(leaflet)
geo_map <- leaflet(width = '100%') %>%
addTiles(
urlTemplate = 'http://webrd02.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}',
layerId = tileOptions(minZoom = 3, maxZoom = 17),
attribution = NULL
)
使用addMarkers()
在geo_map
上增加散点图。
geo_map %>%
addMarkers(
data = datafile, ## 存放有坐标的文件
lat = ~lat, ## 变量纬度(latitude)所在列
lng = ~lng, ## 变量经度(longitude)所在列
label = ~address ## 变量标签所在列(这里设置了详细的地址作为标签)
)
高德地图地理编码的 api 每天可以查询 30 万次,所以可以将上述查询过程写成一个 function 来进行批量查询。代码如下,代码中的注释已做详细解释。
## 加载packages
library(dplyr)
library(tidyr)
library(leaflet)
## 设置文件
datafile <- data.frame(
id = 1:9,
address = c(
"北京市东城区天安门广场人民英雄纪念碑",
"上海市浦东新区东方明珠广播电视塔",
"台湾省台北市南京东路台北体育馆",
"海南省海口市海口站(出站口)",
"香港特别行政区湾仔区岛湾仔博览道1号会议展览中心香港回归祖国纪念碑",
"澳门特别行政区花王堂区澳门博物馆大炮台(北入口)",
"西藏拉萨市城关区布达拉宫广场西藏和平解放纪念碑",
"新疆克拉玛依市平南八路克拉玛依北站",
"黑龙江省哈尔滨市道里区斯大林街20号哈尔滨市人民防洪胜利纪念塔"
)
)
## 自定义function
gaodemap <- function(x){
address <- stringr::str_replace_all(datafile$address[x], "[^[:alnum:]]", "") ## 去除特殊字符,部分特殊字符会导致查询失败,所以在最开始直接去除
url <- paste0(
'https://restapi.amap.com/v3/geocode/geo?',
'&key=', key, ## key
'&address=', address, ## 详细地址
'&output=', 'JSON'
)
temp_geo <- fromJSON(paste(readLines(url,warn = F, encoding = 'UTF-8'), collapse = ""))
status <- temp_geo$status ## 判断查询是否成功
if (status == 1 & length(temp_geo$geocodes) != 0){ ## 有时候成功了但是没有收到数据,所以用了一个“且”进行判定
temp <- paste(datafile$address[x], temp_geo$geocodes$location, sep = ",")
} else {
temp <- paste(datafile$address[x], 'NULL,NULL', sep = ",")
}
return(temp)
}
## 地理编码
df <- data.frame(
geo = sapply(datafile$id, gaodemap, simplify = T)
) %>%
separate(col = geo, into = c('address', 'lng', 'lat'), sep = ',') ## 分割function的结果
df$lng <- as.numeric(df$lng)
df$lat <- as.numeric(df$lat)
df
## address lng
## 1 北京市东城区天安门广场人民英雄纪念碑 116.39768
## 2 上海市浦东新区东方明珠广播电视塔 121.50003
## 3 台湾省台北市南京东路台北体育馆 121.54067
## 4 海南省海口市海口站(出站口) 110.16203
## 5 香港特别行政区湾仔区岛湾仔博览道1号会议展览中心香港回归祖国纪念碑 114.17738
## 6 澳门特别行政区花王堂区澳门博物馆大炮台(北入口) 113.54620
## 7 西藏拉萨市城关区布达拉宫广场西藏和平解放纪念碑 91.11885
## 8 新疆克拉玛依市平南八路克拉玛依北站 85.08215
## 9 黑龙江省哈尔滨市道里区斯大林街20号哈尔滨市人民防洪胜利纪念塔 126.61724
## lat
## 1 39.90462
## 2 31.23925
## 3 25.05213
## 4 20.02724
## 5 22.28094
## 6 22.19486
## 7 29.65127
## 8 45.56794
## 9 45.78065
这里我们将上面获得的df
数据进行绘制,可以得到以下图形
geo_map <- leaflet(width = '100%') %>%
addTiles(
urlTemplate = 'http://webrd02.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}',
layerId = tileOptions(minZoom = 3, maxZoom = 17),
attribution = NULL
)
geo_map %>%
addMarkers(
data = df,
lat = ~lat,
lng = ~lng,
label = ~address
)
补充:如果数据量较大,可以考虑使用 parallel 和 foreach 等来实现并行访问和解析 api,因为 R 语言默认是单核运行的,所以会出现**“一核有难,多核围观”**的情形,使用并行运算可以使电脑发挥出多核的优势,提升数据处理速度。
您可能会发现高德限制每秒 api 访问量是 200 次,多核并行会超限,就我的经验而言每次访问和解析大概需要 0.1 秒,16 线程并行查询 api,一秒钟也就160次,更何况还存在网络波动,所以基本上不用担心超限问题。
df_line <- df %>%
mutate(
lat_start = lat[1], ## 新增起点纬度
lng_start = lng[1] ## 新增起点经度
)
df_line
## address lng
## 1 北京市东城区天安门广场人民英雄纪念碑 116.39768
## 2 上海市浦东新区东方明珠广播电视塔 121.50003
## 3 台湾省台北市南京东路台北体育馆 121.54067
## 4 海南省海口市海口站(出站口) 110.16203
## 5 香港特别行政区湾仔区岛湾仔博览道1号会议展览中心香港回归祖国纪念碑 114.17738
## 6 澳门特别行政区花王堂区澳门博物馆大炮台(北入口) 113.54620
## 7 西藏拉萨市城关区布达拉宫广场西藏和平解放纪念碑 91.11885
## 8 新疆克拉玛依市平南八路克拉玛依北站 85.08215
## 9 黑龙江省哈尔滨市道里区斯大林街20号哈尔滨市人民防洪胜利纪念塔 126.61724
## lat lat_start lng_start
## 1 39.90462 39.90462 116.3977
## 2 31.23925 39.90462 116.3977
## 3 25.05213 39.90462 116.3977
## 4 20.02724 39.90462 116.3977
## 5 22.28094 39.90462 116.3977
## 6 22.19486 39.90462 116.3977
## 7 29.65127 39.90462 116.3977
## 8 45.56794 39.90462 116.3977
## 9 45.78065 39.90462 116.3977
df_line <- data.frame(
group = rownames(df_line),
lat = c(df_line$lat, df_line$lat_start),
lng = c(df_line$lng, df_line$lng_start)
) %>%
group_by(group) %>%
arrange(.by_group = TRUE)
df_line
## # A tibble: 18 x 3
## # Groups: group [9]
## group lat lng
## <chr> <dbl> <dbl>
## 1 1 39.9 116.
## 2 1 39.9 116.
## 3 2 31.2 122.
## 4 2 39.9 116.
## 5 3 25.1 122.
## 6 3 39.9 116.
## 7 4 20.0 110.
## 8 4 39.9 116.
## 9 5 22.3 114.
## 10 5 39.9 116.
## 11 6 22.2 114.
## 12 6 39.9 116.
## 13 7 29.7 91.1
## 14 7 39.9 116.
## 15 8 45.6 85.1
## 16 8 39.9 116.
## 17 9 45.8 127.
## 18 9 39.9 116.
geo_map <- leaflet(width = '100%') %>%
addTiles(
urlTemplate = 'http://webrd02.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}',
layerId = tileOptions(minZoom = 3, maxZoom = 17),
attribution = NULL
)
geo_map %>%
addPolylines(
data = df_line, ## 存放有坐标文件
lng = ~lng, ## 变量经度(longitude)所在列
lat = ~lat, ## 变量纬度(latitude)所在列
group = ~group, ## 线条分组
weight = 1, ## 线条粗细
color = "red" ##线条颜色
)
这个是看高德地图的时候无意中发现的,高德还提供 api 来查询导航路径,详细可见:官方说明文档[1]
address1 <- '福建省厦门市厦门高崎国际机场' ## 起点详细地址
address2 <- '福建省厦门市厦门大学思明校区' ## 终点详细地址
url1 <- paste0( ## 查询起点的url
"https://restapi.amap.com/v3/geocode/geo?address=", address1,
"&output=JSON&key=", key
)
url2 <- paste0( ## 查询终点的url
"https://restapi.amap.com/v3/geocode/geo?address=", address2,
"&output=JSON&key=", key
)
address1 <- fromJSON(paste(readLines(url1,warn = F, encoding = 'UTF-8'), collapse = "")) ## 起点坐标
address2 <- fromJSON(paste(readLines(url2,warn = F, encoding = 'UTF-8'), collapse = "")) ## 终点坐标
print(address1$geocodes)
## formatted_address country province citycode city district
## 1 福建省厦门市湖里区高崎国际机场 中国 福建省 0592 厦门市 湖里区
## township neighborhood.name neighborhood.type building.name building.type
## 1 NULL NULL NULL NULL NULL
## adcode street number location level
## 1 350206 NULL NULL 118.137053,24.539473 兴趣点
print(address2$geocodes)
## formatted_address country province citycode city district
## 1 福建省厦门市思明区厦门大学思明校区 中国 福建省 0592 厦门市 思明区
## township neighborhood.name neighborhood.type building.name building.type
## 1 NULL NULL NULL NULL NULL
## adcode street number location level
## 1 350203 NULL NULL 118.093470,24.437154 兴趣点
url <- paste0(
"https://restapi.amap.com/v5/direction/bicycling?",
## 使用骑行路线的api,如果是步行或者驾车,需要按照说明文档修改即可
"&output=", "JSON",
"&key=", key,
'&origin=', address1$geocodes$location, ## 设置起点经纬度
'&destination=', address2$geocodes$location, ## 设置终点经纬度
'&show_fields=', 'polyline' ## 设置显示坐标点
)
## 查询api
address <- fromJSON(paste(readLines(url,warn = F, encoding = 'UTF-8'), collapse = ""))
“route-paths-steps-polyline”
中。head(address$route$paths$steps[[1]]$polyline)
## [1] "118.13766;....
## 经纬度分割开
datafile <- lapply(1:length(address[["route"]][["paths"]][["steps"]][[1]][["polyline"]]), function(x){
df <- strsplit(address[["route"]][["paths"]][["steps"]][[1]][["polyline"]][[x]], ";")
return(df)
})
## 转化成表格
df <- data.frame(
id = matrix(unlist(datafile))
) %>%
separate(id, c('lng', 'lat'), ",") %>%
mutate_if(is.character, as.numeric)
leaflet(df, width = '100%') %>%
addTiles(
urlTemplate = 'http://webrd02.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}',
layerId = tileOptions(minZoom = 3, maxZoom = 17),
attribution = NULL
) %>%
addPolylines( ## 绘制路线
lat = ~lat,
lng = ~lng
) %>%
addMarkers( ## 绘制起点所在点
lat = ~lat[1],
lng = ~lng[1],
label = paste0('起点:', address1$geocodes$formatted_address)
)%>%
addMarkers( ## 绘制终点所在点
lat = ~lat[nrow(df)],
lng = ~lng[nrow(df)],
label = paste0('终点:', address2$geocodes$formatted_address)
)
受限于微信平台问题,所有的 leaflet 画的图都是以截图方式呈现,但是实际上 leaflet 生成的是交互式的地图,也就是你可以像导航软件里一样放大和缩小地图,不用受限于分辨率问题;上述代码中设置的label都是鼠标悬停显示。
另外,很多教程都没有提一件事:
根据《中华人民共和国测绘法》等有关法律、法规规定:在中华人民共和国境内公开出版地图、引进地图、展示、登载地图以及在生产加工的产品上附加的地图图形都需要经审核,审核通过之后编发审图号。
本教程使用的是高德的底图,所以可以直接使用高德提供的审图号。如果是来历不明的地图数据,无法提供审图号可能会引来一些不必要的麻烦。
[1]
官方说明文档: https://lbs.amap.com/api/webservice/guide/api/newroute
推荐: 可以保存以下照片,在 b 站扫该二维码,或者 b 站搜索【庄闪闪
】观看 Rmarkdown 系列的视频教程。Rmarkdown 视频新增两节视频(写轮眼幻灯片制作)需要视频内的文档,可在公众号回复【rmarkdown
】