给地图工具加上高德逆地理编码

作者 mcx 日期 2026-06-14
给地图工具加上高德逆地理编码

背景

我日常用的地图工具(maps_client.py)基于 OpenStreetMap 生态 —— Nominatim 做 geocode/reverse geocode,OSRM 算路径,Overpass 查 POI。这套组合在全球大部分地区够用,但在中国有个硬伤:

Nominatim 的中国地址数据极其稀疏。 输入一组经纬度,返回的往往只有街道名(如”玉清东街”),没有小区名、没有楼栋、没有 landmark。对于 “我在哪个小区附近” 这种日常需求,完全不可用。

方案:接入高德 Web 服务 API

高德开放平台提供 Web 服务 API,免费个人开发者每天 5000 次调用额度。regeo 接口(逆地理编码)直接返回:

  • 格式化地址(省市区街道+地标)
  • 小区/建筑物名称
  • 周边 POI 列表(医院、学校、商场等)

接入成本极低:一个 HTTP GET 请求。

实现

1. 申请 Key

高德开放平台控制台 注册个人开发者 → 创建应用 → 添加 Web 服务 API 类型的 Key。不需要绑域名,不需要备案。

Key 是一串 32 位 hex 字符串,像这样:

1
5bcc282ebffd771255da0aca935b6636

2. API 调用

高德的 regeo 接口极其简单:

1
GET https://restapi.amap.com/v3/geocode/regeo?key=YOUR_KEY&location=116.3974,39.9042&output=json&extensions=all

返回示例(北京天安门):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"status": "1",
"regeocode": {
"formatted_address": "北京市东城区东华门街道天安门广场人民英雄纪念碑",
"addressComponent": {
"province": "北京市",
"district": "东城区",
"township": "东华门街道",
"building": {"name": "天安门广场", "type": "风景名胜;公园广场;城市广场"}
},
"pois": [
{"name": "人民英雄纪念碑", "type": "风景名胜;纪念馆", "distance": "54"},
{"name": "天安门广场", "type": "风景名胜;公园广场", "distance": "117"},
{"name": "毛主席纪念堂", "type": "风景名胜;纪念馆", "distance": "190"}
]
}
}

对比 Nominatim 同一位置的返回:

1
2
3
4
{
"display_name": "东长安街, 南池子社区, 东华门街道, 东城区, 北京市, 100010, 中国",
"address": {"road": "东长安街", "suburb": "东华门街道", "city": "东城区"}
}

差距明显:Nominatim 只给到街道级,高德给到”人民英雄纪念碑” + 周边 5 个 POI。

3. 代码改动

maps_client.pycmd_reverse 函数里,优先尝试 Amap,失败则回退 Nominatim:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
AMAP_REVERSE = "https://restapi.amap.com/v3/geocode/regeo"
AMAP_KEY = os.environ.get("AMAP_KEY")

def cmd_reverse(args):
# ... 参数校验 ...

# 先试高德
if AMAP_KEY:
try:
params = {
"key": AMAP_KEY,
"location": f"{lon},{lat}",
"output": "json",
"extensions": "all",
"radius": 1000,
}
data = http_get(AMAP_REVERSE, params=params, silent=True)
if data.get("status") == "1" and data.get("regeocode"):
regeo = data["regeocode"]
# 提取小区名
for poi in regeo.get("pois", [])[:10]:
if "住宅小区" in poi.get("type", ""):
community = poi["name"]
# 输出结构化结果
return print_json({...})
except:
pass # 失败则 fall through

# 回退 Nominatim
return nominatim_reverse(lat, lon)

完整改动在 maps_client.py v1.3.0。Python stdlib 即可,零外部依赖。

4. 效果对比

青岛市区某点(36.07, 120.38):

Nominatim(之前)

1
仅返回街道名,无小区信息

Amap/高德地图(之后)

1
2
3
4
5
6
7
8
山东省青岛市市南区香港中路街道华佗路中国人民解放军海军第九七一医院 · 在正一家园附近

周边 POI:
- 中国人民解放军海军第九七一医院 (226m)
- 闽江路52号小区 (87m)
- 正一家园 (126m) ← 小区级别
- 青岛市教育局 (177m)
- 府新大厦 (179m)

“在正一家园附近” —— 这才是日常问路需要的精度。

配置方式

设置环境变量即可启用:

1
export AMAP_KEY="你的32位key"

也可以写进 shell profile:

1
echo 'export AMAP_KEY="你的32位key"' >> ~/.zshrc

之后 maps_client.py reverse 纬度 经度 自动走高德,无需任何其他配置。

踩坑记录

1. Key 类型必须选 Web 服务 API

高德有多个产品线:JS API、Android SDK、iOS SDK、Web 服务 API。如果选了 JS API 或 SDK 类型的 key,调用 REST 接口会返回:

1
{"status": "0", "info": "USERKEY_PLAT_NOMATCH", "infocode": "10009"}

解决方法:在控制台重新创建一个”Web 服务 API”类型的 key。

2. 免费的够用

个人开发者免费额度 5000 次/天。对于个人工具使用,完全不可能用完 —— 一天手动查几十次已经很多了。

3. 非中国坐标自动回退

高德只覆盖中国地区。代码里做了 fallback:如果高德请求失败(网络超时、非中国坐标等),自动降级到 Nominatim,不影响海外使用。

总结

  • 问题:Nominatim 中国数据稀疏,只能到街道级
  • 方案:优先调用高德 Web 服务 API,失败回退 Nominatim
  • 成效:小区级地址 + 周边 POI,零外部依赖
  • 成本:免费(5000次/天),一行环境变量即可启用