在最近的学习中,大量使用到了地理编码与地理逆编码,然而不幸的是目前广泛在 R 中介绍的能完成该任务的包因为 API 的 Breaking Upgrade 很多功能已经失效了。尤其是意外的发现,目前的包主要由百度地图 API 作数据源,于是为了满足在 R 中使用高德地图 API 进行地理编码与逆编码的需求,开发了 amapGeocode 这款包,如果能对有同样需求的同学有任何帮助,将不胜荣幸。

什么是地理编码/逆编码

根据地理小子在文章《地理编码与逆地理编码(上)——理解地理编码》中的陈述:

地理编码指将地名的详细地址以地理坐标(如经纬度)表示的过程。其中,将地址信息映射为地理坐标的过程称之为地理编码;将地理坐标转换为地址信息的过程称之为逆地理编码。如图1.1所示为地理编码和逆地理编码的关系。

图1.1-地理小子-《地理编码与逆地理编码(上)——理解地理编码》

无论是在科学研究还是数据处理的过程中,凡是涉及到位置信息的情况,经常会使用到地理编码与逆编码:例如给定样本的地址,将其转换为经纬度以便进行量化计算并进行可视化;亦或者给定样本地址信息,将其按照行政区域进行分类以便进行分组并统计。

如何在 R 中进行地理编码/逆编码

目前进行地理编码/逆编码的方法通常是使用地图供应商提供的 API 获取信息。在 R 中目前常使用 tidygeocoderbaidumap 以及 baidugeo 等 Package 完成这类任务。然而,其中 tidygeocoder 使用国际地理信息供应商,其准确度以及信息覆盖程度尚不能令人满意;而后两者因为百度地图 API 的升级,导致目前 Package 不能被新用户调用(其中前者笔者已经提交了 Request,不过是否会被合并还需要进一步等待;后者笔者正在和开发者进行更新,具体修复时间还需等待)。

此外,通过笔者在实际项目的使用过程中发现,在相当多的情况下,高德地图提供的地理编码/逆编码信息似乎好于百度地图,当然因为没有进行客观的对照分析,仅作为个人体验以作参考,如有谬误,您说的对

amapGeocode

基于以上原因,笔者使用高德开放平台 API 编写了用于 R 的地理编码/逆编码的 Package amapGeocode。代码托管于 Github。具体的高德文档可以查看这里这里

目前 amapGeocode 支持通过 getCoord() 进行地理编码,即根据地址获取坐标等信息;以及通过 getLocation() 进行地理逆编码,即根据经纬度获取地址等信息。此外还可以通过 getAdmin() 获取给定行政区其下属行政区(JSONXML 支持多级行政区)。

上述 function 支持输入单条的文本地址/坐标/行政区,同时也支持批量输入信息,其默认返回结果为 tibble, 同时也可以返回原生的 JSON 格式以及 XML 格式,此时批量查询的地址将以 list 的结构保存。

安装

目前可以通过下列命令从 CRAN 安装稳定版:

install.packages("amapGeocode")

此外也可以通过 Github 安装开发版:

remotes::install_github('womeimingzi11/amapGeocode')

申请 API Key

由于 amapGeocode 使用了高的开放平台的的 API 服务,因此在使用 amapGeocode 之前,用户必须首先申请 API Key,申请地址如下 https://console.amap.com/dev/index

点击管理Key进入管理页面,选择创建新应用,应用名称和用途可以根据自己的实际用途填写。之后在刚刚创建的应用右侧点击添加按钮添加一个新的 API Key,Key 名称可以自行命名,但是注意一定要把服务平台选为 Web服务。之后就能查看申请到的 Key 了。请注意,Key 的作用相当于访问高德服务的密码,切勿分享给其他人使用。如果怀疑 Key 泄露,可以通过删除按钮撤销该 Key,但同时也意味着您之前在程序中引用的该 Key 也需要一并更换,否者 amapGeocode 无法正常工作。

此外,截止到 2020 年 10 月 9 日,根据高德开放平台配额管理页面显示:高德开放平台地理编码/逆地理编码/行政区查询服务对于实名认证免费用户分别提供了单日 300,000次/300,000次/30,000 次的使用额度,超过此额度后,当日无法继续使用查询服务。理论上来讲,除非主动购买额外的查询次数,否则不会产生费用,但 amapGeocode 开发者暨笔者不对高德开放平台其规定与收费政策作任何保证,请用户自行判断。

使用方法

地理编码

library(amapGeocode)

加载 amapGeocode 后,用户可以选择每次执行命令之时手动指定 key 参数来设定之前申请到的 Key,同时也可以通过下列命令将 key 设置为全局可用,则单条命令无需再次手动输入之前申请到的 Key

options(amap_key = 'REPLACE THIS BY YOUR KEY')

通过 getCoord() 来获取给定地址的经纬度:

getCoord(c("四川省中医院", "四川省人民医院", "兰州大学盘旋路校区", "成都中医药大学十二桥校区"))
## # A tibble: 4 x 12
##     lng   lat formatted_addre~ country province city  district township street
##   <dbl> <dbl> <chr>            <chr>   <chr>    <chr> <chr>    <lgl>    <lgl> 
## 1  104.  30.7 四川省成都市金牛区四川省中医院~ 中国    四川省   成都市~ 金牛区   NA       NA    
## 2  104.  30.7 四川省成都市青羊区四川省人民医~ 中国    四川省   成都市~ 青羊区   NA       NA    
## 3  104.  36.0 甘肃省兰州市城关区兰州大学盘旋~ 中国    甘肃省   兰州市~ 城关区   NA       NA    
## 4  104.  30.7 四川省成都市金牛区成都中医药大~ 中国    四川省   成都市~ 金牛区   NA       NA    
## # ... with 3 more variables: number <lgl>, citycode <chr>, adcode <chr>

在默认状态下,该命令将返回按输入顺序排序的 tibble 表格。不过用户依然可以通过指定 output = 'JSON'output = 'XML' 并配合 to_table = FALSE 来直接获得高德地图 API 返回的 JSON/XML 格式的结果。

# An individual request
res <- getCoord("成都中医药大学", output = "XML", to_table = FALSE)
res
## {xml_document}
## <response>
## [1] <status>1</status>
## [2] <info>OK</info>
## [3] <infocode>10000</infocode>
## [4] <count>1</count>
## [5] <geocodes type="list">\n  <geocode>\n    <formatted_address>四川省成都市金牛区成都中医 ...

这类结果可以使用 amapGeocode 内置的 extractCoord() 处理为与之前格式相同的 tibble 格式。

extractCoord(res)
## # A tibble: 1 x 12
##     lng   lat formatted_addre~ country province city  district township street
##   <dbl> <dbl> <chr>            <chr>   <chr>    <chr> <chr>    <lgl>    <lgl> 
## 1  104.  30.7 四川省成都市金牛区成都中医药大~ 中国    四川省   成都市~ 金牛区   NA       NA    
## # ... with 3 more variables: number <lgl>, citycode <chr>, adcode <chr>

地理逆编码

与地理编码的使用方法基本相同,getLocation() 示例如下:

coord <- tibble::tribble(~lat, ~lon, 104.043284, 30.666864, 104.039, 30.66362)
getLocation(coord$lat, coord$lon)
## # A tibble: 2 x 8
##   formatted_address   country province city  district township citycode towncode
##   <chr>               <chr>   <chr>    <chr> <chr>    <chr>    <chr>    <chr>   
## 1 四川省成都市金牛区西安路街道成都中医~ 中国    四川省   成都市~ 金牛区   西安路街道~ 028      5101060~
## 2 四川省成都市青羊区草堂街道四川省医学~ 中国    四川省   成都市~ 青羊区   草堂街道 028      5101050~

行政区获取

与地理编码/逆编码方法基本相同,getAdmin()示例如下:

getAdmin(c("四川省", "兰州市", "济宁市"))
## [[1]]
## # A tibble: 21 x 6
##      lng   lat name   level citycode adcode
##    <dbl> <dbl> <chr>  <chr> <chr>    <chr> 
##  1  107.  31.9 巴中市 city  0827     511900
##  2  104.  30.7 成都市 city  028      510100
##  3  104.  31.1 德阳市 city  0838     510600
##  4  106.  32.4 广元市 city  0839     510800
##  5  106.  30.5 遂宁市 city  0825     510900
##  6  105.  30.1 资阳市 city  0832     512000
##  7  105.  31.5 绵阳市 city  0816     510700
##  8  107.  30.5 广安市 city  0826     511600
##  9  108.  31.2 达州市 city  0818     511700
## 10  106.  30.8 南充市 city  0817     511300
## # ... with 11 more rows
## 
## [[2]]
## # A tibble: 8 x 6
##     lng   lat name     level    citycode adcode
##   <dbl> <dbl> <chr>    <chr>    <chr>    <chr> 
## 1  103.  36.7 永登县   district 0931     620121
## 2  104.  36.3 皋兰县   district 0931     620122
## 3  104.  36.1 西固区   district 0931     620104
## 4  103.  36.3 红古区   district 0931     620111
## 5  104.  36.1 安宁区   district 0931     620105
## 6  104.  36.0 城关区   district 0931     620102
## 7  104.  36.1 七里河区 district 0931     620103
## 8  104.  35.8 榆中县   district 0931     620123
## 
## [[3]]
## # A tibble: 11 x 6
##      lng   lat name   level    citycode adcode
##    <dbl> <dbl> <chr>  <chr>    <chr>    <chr> 
##  1  116.  35.7 汶上县 district 0537     370830
##  2  117.  35.4 邹城市 district 0537     370883
##  3  117.  35.7 泗水县 district 0537     370831
##  4  117.  35.6 曲阜市 district 0537     370881
##  5  117.  35.4 任城区 district 0537     370811
##  6  116.  35.4 嘉祥县 district 0537     370829
##  7  116.  35.8 梁山县 district 0537     370832
##  8  117.  35.0 鱼台县 district 0537     370827
##  9  116.  35.1 金乡县 district 0537     370828
## 10  117.  34.8 微山县 district 0537     370826
## 11  117.  35.6 兖州区 district 0537     370812

注意,由于 getAdmin() 可以对互相独立的行政区域进行批量查询,因此默认将查询结果也许并无意义,因此需要用户自行从 list 中提取元素以便进一步使用。

实验性功能——坐标转换

由于不同的地图服务,其坐标有时会加入一定程度的偏移,因此为了使坐标在高德地图中查询准确,可以首先对原始坐标进行转换,目前高德开放平台 API 提供将 GPS 坐标、mapbar 坐标、baidu 坐标转换为高德坐标

但是因为开发者目前并没有真正使用过这类功能,因此目前该方法尚处于实验阶段。无论是结果准确性,以及使用的便捷程度以及合理性均不作任何保证。同时在将来也许会进行 Breaking Upgrade 造成目前的调用方式不可用,因此极不推荐将该 function 引入生产环境。同时也希望大家对于该功能的完善给出建议。

convertCoord("116.481499,39.990475", coordsys = "gps")
## # A tibble: 1 x 2
##     lng   lat
##   <dbl> <dbl>
## 1  116.  40.0

更多功能以及改进还在开发中。

常见问题

会引入并行请求吗?

虽然在开发之初,笔者认为将来不会加入并行,不过现在的计划,大概是会吧,谁知道呢? Unfortunately, there is no plan to add internal parallel support to amapGeocode. Here are some reasons:

1. The aim of amapGeocode is to create a package which is easy to use. Indeed, the parallel operation can make many times performance improvement, especially there are half million queries. However, the parallel operation often platform limited, I don’t have enough time and machine to test on different platforms. In fact even in macOS, the system I’m relatively familiar with, I have already encountered a lot of weird parallel issues and I don’t have the intention or the experience to fix them.

2. The queries limitation. For most of free users or developers, the daily query limitation and queries per second is absolutely enough: 30,000 queries per day and 200 queries per second. But for parallel operation, the limitation is relatively easy to exceed. For purchase developers, it may cause serious financial troubles.

So for anybody who wants to send millions of request by amapGeocode, you are welcomed to make the parallel operations manually.

Bug Report

对于 API Wrap 类型的包,上游 API 更新造成的功能不可用在所难免。如果你遇到这类故障或者任何其它 Bug,请第一时间让我知道 Issue

如果 amapGeocode 对你有任何帮助,希望可以帮我 Star


欢迎通过邮箱微博, Twitter以及知乎与我联系。也欢迎关注我的博客。如果能对我的 Github 感兴趣,就再欢迎不过啦!