amapGeocode: 使用R进行高德地图地理编码/逆编码

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

什么是地理编码/逆编码

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

地理编码指将地名的详细地址以地理坐标(如经纬度)表示的过程。其中,将地址信息映射为地理坐标的过程称之为地理编码;将地理坐标转换为地址信息的过程称之为逆地理编码。如图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("四川省中医院", "四川省人民医院", "兰州大学盘旋路校区",
    "成都中医药大学十二桥校区"))
##         lng      lat                          formatted_address country
## 1: 104.0431 30.66780             四川省成都市金牛区四川省中医院    中国
## 2: 104.0390 30.66362           四川省成都市青羊区四川省人民医院    中国
## 3: 103.8619 36.04619       甘肃省兰州市城关区兰州大学盘旋路校区    中国
## 4: 104.0439 30.66629 四川省成都市金牛区成都中医药大学十二桥校区    中国
##    province   city district township street number citycode adcode
## 1:   四川省 成都市   金牛区       NA     NA     NA      028 510106
## 2:   四川省 成都市   青羊区       NA     NA     NA      028 510105
## 3:   甘肃省 兰州市   城关区       NA     NA     NA     0931 620102
## 4:   四川省 成都市   金牛区       NA     NA     NA      028 510106

在默认状态下,该命令将返回按输入顺序排序的 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)
##         lng      lat                formatted_address country province   city
## 1: 104.0433 30.66686 四川省成都市金牛区成都中医药大学    中国   四川省 成都市
##    district township street number citycode adcode
## 1:   金牛区       NA     NA     NA      028 510106

地理逆编码

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

coord <- tibble::tribble(~lat, ~lon, 104.043284, 30.666864, 104.039, 30.66362)
getLocation(coord$lat, coord$lon)
##                                                                     formatted_address
## 1: 四川省成都市金牛区西安路街道成都中医药大学附属医院腹泻门诊成都中医药大学十二桥校区
## 2:                           四川省成都市青羊区草堂街道四川省医学科学院四川省人民医院
##    country province   city district   township citycode     towncode
## 1:    中国   四川省 成都市   金牛区 西安路街道      028 510106013000
## 2:    中国   四川省 成都市   青羊区   草堂街道      028 510105007000

行政区获取

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

getAdmin(c("四川省", "兰州市", "济宁市"))
## [[1]]
##          lng      lat               name level citycode adcode
##  1: 105.8440 32.43577             广元市  city     0839 510800
##  2: 106.1106 30.83723             南充市  city     0817 511300
##  3: 106.7475 31.86785             巴中市  city     0827 511900
##  4: 104.3978 31.12745             德阳市  city     0838 510600
##  5: 103.7661 29.55228             乐山市  city     0833 511100
##  6: 104.0663 30.57296             成都市  city      028 510100
##  7: 103.8484 30.07711             眉山市  city     1833 511400
##  8: 107.4678 31.20928             达州市  city     0818 511700
##  9: 104.6273 30.12924             资阳市  city     0832 512000
## 10: 106.6326 30.45635             广安市  city     0826 511600
## 11: 105.0580 29.58021             内江市  city     1832 511000
## 12: 104.6791 31.46767             绵阳市  city     0816 510700
## 13: 105.5926 30.53268             遂宁市  city     0825 510900
## 14: 102.2677 27.88140     凉山彝族自治州  city     0834 513400
## 15: 104.6428 28.75235             宜宾市  city     0831 511500
## 16: 104.7793 29.33924             自贡市  city     0813 510300
## 17: 101.7185 26.58242           攀枝花市  city     0812 510400
## 18: 105.4419 28.87098             泸州市  city     0830 510500
## 19: 103.0415 30.01000             雅安市  city     0835 511800
## 20: 102.2245 31.89943 阿坝藏族羌族自治州  city     0837 513200
## 21: 101.9623 30.04952     甘孜藏族自治州  city     0836 513300
##          lng      lat               name level citycode adcode
## 
## [[2]]
##         lng      lat     name    level citycode adcode
## 1: 103.9473 36.33243   皋兰县 district     0931 620122
## 2: 102.8593 36.34577   红古区 district     0931 620111
## 3: 103.6280 36.08845   西固区 district     0931 620104
## 4: 103.7863 36.06625 七里河区 district     0931 620103
## 5: 103.8253 36.05695   城关区 district     0931 620102
## 6: 103.7190 36.10449   安宁区 district     0931 620105
## 7: 104.1125 35.84335   榆中县 district     0931 620123
## 8: 103.2603 36.73646   永登县 district     0931 620121
## 
## [[3]]
##          lng      lat   name    level citycode adcode
##  1: 116.9862 35.58193 曲阜市 district     0537 370881
##  2: 116.4973 35.71189 汶上县 district     0537 370830
##  3: 117.2508 35.66472 泗水县 district     0537 370831
##  4: 117.0074 35.40254 邹城市 district     0537 370883
##  5: 116.3423 35.40794 嘉祥县 district     0537 370829
##  6: 116.1318 35.76596 梁山县 district     0537 370832
##  7: 116.3115 35.06658 金乡县 district     0537 370828
##  8: 116.7836 35.55194 兖州区 district     0537 370812
##  9: 116.6505 35.01271 鱼台县 district     0537 370827
## 10: 117.1292 34.80666 微山县 district     0537 370826
## 11: 116.6058 35.44423 任城区 district     0537 370811

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

实验性功能——坐标转换

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

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

convertCoord("116.481499,39.990475", coordsys = "gps")
##         lng      lat
## 1: 116.4876 39.99175

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

常见问题

会引入并行请求吗?

虽然在开发之初,笔者认为将来不会加入并行,不过现在的计划,大概是会吧,谁知道呢? 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 感兴趣,就再欢迎不过啦!