使用天地图API进行坐标反查

我习惯使用奥维地图进行旅行规划。但是奥维地图的搜索系统并不是很好使,特别是一些比较「野」的地方,很多名字都搜不着。

这个时候,就需要借助于坐标拾取器来进行搜索。常用的几个地图的坐标拾取器链接如下:

这里面最麻烦的是腾讯,每次搜索之前还要先选中所在地市。而最好使的则是百度,因为可以同时得到多个结果。高德和腾讯都只会返回一个结果,而这个结果大概率不是我想要的。

另外,奥维地图自带的卫星地图都是使用天地图的影像标注作为叠加层,而且其中有一些地理标记在上面的地图里都搜不到,但是却在天地图的数据库中。这时我们就需要使用天地图进行坐标反查。

可惜的是,天地图并没有提供类似上面的链接。有一个网页demo,但是也不是很好使,要显示具体的坐标还需要自己改造。

因此,我考虑直接使用它的Web服务API进行查询。

使用Web服务API,需要先注册并申请密钥。

需要注意的是,在创建应用的时候,「应用类型」要选择「服务器端」,并在「服务类型」里勾选上「地理编码服务」和「逆地理编码服务」(或者直接全选即可)。

为了不明文储存密钥,我使用了pass程序来保存并读取密钥:

1
2
3
4
5
6
## 初始化
pass init _gpg-id_
## 储存密钥
pass insert api/tianditu
## 获取密钥
pass show api/tianditu

1. 对地名进行地址解析

首先,是将地址数据转换为经纬度坐标,即地理编码查询,命令如下:

1
2
3
4
5
6
7
8
9
# 地名->CSG2000坐标
tianditu_point() {
local LOC="$1"
local TIANDITU_API_KEY=$(pass show api/tianditu)
curl -sG "http://api.tianditu.gov.cn/geocoder" \
--data-urlencode "ds={keyWord: ${LOC}}" \
--data "tk=${TIANDITU_API_KEY}" |
jq -r '., "---", "\(.location.keyWord): \(.location.lat)N, \(.location.lon)E"'
}

最后使用了jq将坐标转化为了可以直接复制到奥维地图里面的形式。

2. 对坐标进行反地址解析

反过来,我们也可以将经纬度坐标转换为地址数据,即逆地理编码查询。不过需要特别注意坐标系的问题。

  • 奥维地图:输入支持WSG84、GCJ-02、BD-09坐标系,输出(复制)只支持GCJ02坐标系。
  • 天地图:使用CGCS2000坐标系。

不过CGCS2000坐标和WSG84坐标之间的非常接近,两者差异在±6cm以内,因此在实际使用中我们可以基本默认二者是等同的。

由奥维地图复制得到的GCJ02坐标,我们先使用coordtransform转换为WSG84坐标,然后再进行查询:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 附近GCJ02坐标->地名信息
tianditu_loc() {
local GCJ="$1"
local TIANDITU_API_KEY=$(pass show api/tianditu)
curl -sG "http://api.tianditu.gov.cn/geocoder" \
--data-urlencode "$(
coordtransform -t gcj02towgs84 \
<(echo "${GCJ}" | jq -R -c '{type:"Point", coordinates:[((./",")[0]|gsub("g";"")|tonumber), (./",")[1]|tonumber]}') \
>(cat) 1>/dev/null |
jq -r '"postStr={lon: \(.coordinates[0]), lat: \(.coordinates[1]), ver: 1}"'
)" \
--data "type=geocode" \
--data "tk=${TIANDITU_API_KEY}" |
jq
}

中间比较繁琐的事情进行各种格式的转换:

  • 奥维地图复制得到的坐标:g95.93463182,30.73686994
  • coordtransform的输入/输出格式:{type: 'Point', coordinates: [95.934117, 30.739689]}
  • 天地图API的查询参数:postStr={'lon':95.934117,'lat':30.739689,'ver':1}

3. 二者相结合

直接对地名进行解析,经常得不到想要的结果。因为我们输入的地名只有具体的名字,而相近的名字可能有多个,甚至可能返回一个相隔十万八千里的地方。

例如,在川藏中线上一个德嘎拉垭口,如果直接查询tianditu_point 德嘎拉,得到的却是拉孜县的德嘎拉的坐标,并不是我们想要的。必须查询tianditu_point 洛隆县德嘎拉才能得到正确的坐标。

原因是天地图的地理编码API需要的是结构化地址数据(如:北京市海淀区莲花池西路28号),而我一般为了方便,都只输入地点的名称。

因此,我们可以把二者结合起来,可以直接先在奥维地图要找的点附近复制经纬度(直接右击➟选择「经纬度」➟点击「复制经纬度」即可),然后先进行反解析获取地名,再转化为精确的坐标即可:

1
2
3
tianditu_near() {
tianditu_point "$(tianditu_loc "$1" | jq .result.formatted_address)"
}

例如,在德嘎拉垭口附近随意复制一点的坐标,然后调用:tianditu_near g95.93524611,30.73651378,即可得到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"msg": "ok",
"location": {
"score": 95,
"level": "兴趣点",
"lon": "95.934117",
"lat": "30.739689",
"keyWord": "西藏自治区昌都市洛隆县康沙镇德嘎拉东南约71米"
},
"searchVersion": "7.5.0V",
"status": "0"
}
---
西藏自治区昌都市洛隆县康沙镇德嘎拉东南约71米: 30.739689N, 95.934117E

将最后的坐标复制进奥维地图,即可得到对应点。

为了方便使用,我们还可以使用更好输入的别名:

1
2
3
alias tpoint="tianditu_point"
alias tloc="tianditu_loc"
alias tnear="tianditu_near"