amap-cli 0.1.2__tar.gz → 0.1.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {amap_cli-0.1.2 → amap_cli-0.1.4}/PKG-INFO +55 -2
- {amap_cli-0.1.2 → amap_cli-0.1.4}/README.md +54 -1
- {amap_cli-0.1.2 → amap_cli-0.1.4}/docs/manual-verification.md +34 -7
- {amap_cli-0.1.2 → amap_cli-0.1.4}/pyproject.toml +1 -1
- {amap_cli-0.1.2 → amap_cli-0.1.4}/skills/amap-cli/SKILL.md +40 -3
- {amap_cli-0.1.2 → amap_cli-0.1.4}/src/amap_cli/api.py +29 -24
- {amap_cli-0.1.2 → amap_cli-0.1.4}/src/amap_cli/cli.py +10 -1
- {amap_cli-0.1.2 → amap_cli-0.1.4}/src/amap_cli/config.py +18 -0
- amap_cli-0.1.4/src/amap_cli/geocode_command.py +91 -0
- {amap_cli-0.1.2 → amap_cli-0.1.4}/uv.lock +1 -1
- {amap_cli-0.1.2 → amap_cli-0.1.4}/.gitignore +0 -0
- {amap_cli-0.1.2 → amap_cli-0.1.4}/.trae/specs/build-amap-cli/checklist.md +0 -0
- {amap_cli-0.1.2 → amap_cli-0.1.4}/.trae/specs/build-amap-cli/spec.md +0 -0
- {amap_cli-0.1.2 → amap_cli-0.1.4}/.trae/specs/build-amap-cli/tasks.md +0 -0
- {amap_cli-0.1.2 → amap_cli-0.1.4}/amap-gui-reference.md +0 -0
- {amap_cli-0.1.2 → amap_cli-0.1.4}/src/amap_cli/__init__.py +0 -0
- {amap_cli-0.1.2 → amap_cli-0.1.4}/src/amap_cli/__main__.py +0 -0
- {amap_cli-0.1.2 → amap_cli-0.1.4}/src/amap_cli/distance.py +0 -0
- {amap_cli-0.1.2 → amap_cli-0.1.4}/src/amap_cli/errors.py +0 -0
- {amap_cli-0.1.2 → amap_cli-0.1.4}/src/amap_cli/geocode.py +0 -0
- {amap_cli-0.1.2 → amap_cli-0.1.4}/src/amap_cli/output.py +0 -0
- {amap_cli-0.1.2 → amap_cli-0.1.4}/src/amap_cli/params.py +0 -0
- {amap_cli-0.1.2 → amap_cli-0.1.4}/src/amap_cli/route.py +0 -0
- {amap_cli-0.1.2 → amap_cli-0.1.4}/src/amap_cli/search_poi.py +0 -0
- {amap_cli-0.1.2 → amap_cli-0.1.4}/src/amap_cli/skill.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: amap-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: Pure command-line CLI for Amap APIs.
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -13,6 +13,7 @@ Description-Content-Type: text/markdown
|
|
|
13
13
|
|
|
14
14
|
- 纯命令行调用高德地图 API
|
|
15
15
|
- 支持命令行写入和读取高德 API Key
|
|
16
|
+
- 支持地理编码
|
|
16
17
|
- 支持两点直线距离计算
|
|
17
18
|
- 支持路径规划
|
|
18
19
|
- 支持 POI 搜索
|
|
@@ -35,6 +36,19 @@ uvx amap-cli --help
|
|
|
35
36
|
uvx amap-cli config set --api-key <YOUR_AMAP_KEY>
|
|
36
37
|
```
|
|
37
38
|
|
|
39
|
+
高德免费账号默认限流较严,工具默认会在每次 API 调用结束后 sleep `0.34` 秒;如需调整或关闭,可配置:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
uvx amap-cli config set --request-sleep-seconds 0.34
|
|
43
|
+
uvx amap-cli config set --request-sleep-seconds 0
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
地理编码示例:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
uvx amap-cli geocode --address 北京南站
|
|
50
|
+
```
|
|
51
|
+
|
|
38
52
|
路径规划示例:
|
|
39
53
|
|
|
40
54
|
```bash
|
|
@@ -79,6 +93,7 @@ amap-cli --help
|
|
|
79
93
|
|
|
80
94
|
```bash
|
|
81
95
|
amap-cli config set --api-key <YOUR_AMAP_KEY>
|
|
96
|
+
amap-cli geocode --address 北京南站
|
|
82
97
|
amap-cli distance --from 116.397,39.909 --to 116.407,39.904
|
|
83
98
|
amap-cli route --from 北京南站 --to 天安门 --type driving
|
|
84
99
|
amap-cli search-poi --keyword 星巴克 --city 北京
|
|
@@ -90,7 +105,7 @@ amap-cli search-poi --keyword 星巴克 --city 北京
|
|
|
90
105
|
|
|
91
106
|
- skill 的目标是让 Agent 直接通过 `uvx amap-cli` 调用本工具
|
|
92
107
|
- 使用方式与上面的“直接使用 `uvx` 运行”一致
|
|
93
|
-
- 使用前先配置高德 API Key
|
|
108
|
+
- 使用前先配置高德 API Key,之后即可发起地理编码、路径规划和 POI 搜索
|
|
94
109
|
- `distance` 命令在起终点都为坐标时可直接本地计算,不依赖 API Key
|
|
95
110
|
- 建议 Agent 直接解析命令返回的 JSON 结果
|
|
96
111
|
|
|
@@ -122,6 +137,18 @@ amap-cli install-skill --dir ~/.agents/skills --force
|
|
|
122
137
|
amap-cli config set --api-key <YOUR_AMAP_KEY>
|
|
123
138
|
```
|
|
124
139
|
|
|
140
|
+
设置每次高德 API 调用结束后的 sleep 时间:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
amap-cli config set --request-sleep-seconds 0.34
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
关闭该 sleep:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
amap-cli config set --request-sleep-seconds 0
|
|
150
|
+
```
|
|
151
|
+
|
|
125
152
|
查看当前配置:
|
|
126
153
|
|
|
127
154
|
```bash
|
|
@@ -134,6 +161,12 @@ amap-cli config show
|
|
|
134
161
|
~/Library/Application Support/amap-cli/config.json
|
|
135
162
|
```
|
|
136
163
|
|
|
164
|
+
说明:
|
|
165
|
+
|
|
166
|
+
- `request_sleep_seconds` 默认为 `0.34`
|
|
167
|
+
- 该配置会在每次真实的高德 API 调用结束后执行一次 sleep
|
|
168
|
+
- 设置为 `0` 表示关闭节流等待
|
|
169
|
+
|
|
137
170
|
### 安装 Skill
|
|
138
171
|
|
|
139
172
|
基础格式:
|
|
@@ -152,6 +185,26 @@ amap-cli install-skill --dir <skills目录>
|
|
|
152
185
|
- 当目标目录不存在时会自动创建
|
|
153
186
|
- 当目标目录中已存在同名 skill 时,默认报错;追加 `--force` 后会覆盖
|
|
154
187
|
|
|
188
|
+
### 地理编码
|
|
189
|
+
|
|
190
|
+
基础格式:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
amap-cli geocode --address <结构化地址|地标名称>
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
可选参数:
|
|
197
|
+
|
|
198
|
+
- `--city`
|
|
199
|
+
|
|
200
|
+
说明:
|
|
201
|
+
|
|
202
|
+
- 调用高德地理编码接口,将结构化地址或地标性名胜景区、建筑物名称解析为坐标
|
|
203
|
+
- 传入 `--city` 时,会优先在对应城市范围内解析
|
|
204
|
+
- 返回结果中的 `geocode.location` 为 `[经度, 纬度]`
|
|
205
|
+
- 返回结果中的 `geocode.formattedAddress` 为高德返回的标准化地址
|
|
206
|
+
- 对于较模糊的名称,建议补充更完整地址或增加 `--city`
|
|
207
|
+
|
|
155
208
|
### 路径规划
|
|
156
209
|
|
|
157
210
|
基础格式:
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
- 纯命令行调用高德地图 API
|
|
8
8
|
- 支持命令行写入和读取高德 API Key
|
|
9
|
+
- 支持地理编码
|
|
9
10
|
- 支持两点直线距离计算
|
|
10
11
|
- 支持路径规划
|
|
11
12
|
- 支持 POI 搜索
|
|
@@ -28,6 +29,19 @@ uvx amap-cli --help
|
|
|
28
29
|
uvx amap-cli config set --api-key <YOUR_AMAP_KEY>
|
|
29
30
|
```
|
|
30
31
|
|
|
32
|
+
高德免费账号默认限流较严,工具默认会在每次 API 调用结束后 sleep `0.34` 秒;如需调整或关闭,可配置:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
uvx amap-cli config set --request-sleep-seconds 0.34
|
|
36
|
+
uvx amap-cli config set --request-sleep-seconds 0
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
地理编码示例:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
uvx amap-cli geocode --address 北京南站
|
|
43
|
+
```
|
|
44
|
+
|
|
31
45
|
路径规划示例:
|
|
32
46
|
|
|
33
47
|
```bash
|
|
@@ -72,6 +86,7 @@ amap-cli --help
|
|
|
72
86
|
|
|
73
87
|
```bash
|
|
74
88
|
amap-cli config set --api-key <YOUR_AMAP_KEY>
|
|
89
|
+
amap-cli geocode --address 北京南站
|
|
75
90
|
amap-cli distance --from 116.397,39.909 --to 116.407,39.904
|
|
76
91
|
amap-cli route --from 北京南站 --to 天安门 --type driving
|
|
77
92
|
amap-cli search-poi --keyword 星巴克 --city 北京
|
|
@@ -83,7 +98,7 @@ amap-cli search-poi --keyword 星巴克 --city 北京
|
|
|
83
98
|
|
|
84
99
|
- skill 的目标是让 Agent 直接通过 `uvx amap-cli` 调用本工具
|
|
85
100
|
- 使用方式与上面的“直接使用 `uvx` 运行”一致
|
|
86
|
-
- 使用前先配置高德 API Key
|
|
101
|
+
- 使用前先配置高德 API Key,之后即可发起地理编码、路径规划和 POI 搜索
|
|
87
102
|
- `distance` 命令在起终点都为坐标时可直接本地计算,不依赖 API Key
|
|
88
103
|
- 建议 Agent 直接解析命令返回的 JSON 结果
|
|
89
104
|
|
|
@@ -115,6 +130,18 @@ amap-cli install-skill --dir ~/.agents/skills --force
|
|
|
115
130
|
amap-cli config set --api-key <YOUR_AMAP_KEY>
|
|
116
131
|
```
|
|
117
132
|
|
|
133
|
+
设置每次高德 API 调用结束后的 sleep 时间:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
amap-cli config set --request-sleep-seconds 0.34
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
关闭该 sleep:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
amap-cli config set --request-sleep-seconds 0
|
|
143
|
+
```
|
|
144
|
+
|
|
118
145
|
查看当前配置:
|
|
119
146
|
|
|
120
147
|
```bash
|
|
@@ -127,6 +154,12 @@ amap-cli config show
|
|
|
127
154
|
~/Library/Application Support/amap-cli/config.json
|
|
128
155
|
```
|
|
129
156
|
|
|
157
|
+
说明:
|
|
158
|
+
|
|
159
|
+
- `request_sleep_seconds` 默认为 `0.34`
|
|
160
|
+
- 该配置会在每次真实的高德 API 调用结束后执行一次 sleep
|
|
161
|
+
- 设置为 `0` 表示关闭节流等待
|
|
162
|
+
|
|
130
163
|
### 安装 Skill
|
|
131
164
|
|
|
132
165
|
基础格式:
|
|
@@ -145,6 +178,26 @@ amap-cli install-skill --dir <skills目录>
|
|
|
145
178
|
- 当目标目录不存在时会自动创建
|
|
146
179
|
- 当目标目录中已存在同名 skill 时,默认报错;追加 `--force` 后会覆盖
|
|
147
180
|
|
|
181
|
+
### 地理编码
|
|
182
|
+
|
|
183
|
+
基础格式:
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
amap-cli geocode --address <结构化地址|地标名称>
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
可选参数:
|
|
190
|
+
|
|
191
|
+
- `--city`
|
|
192
|
+
|
|
193
|
+
说明:
|
|
194
|
+
|
|
195
|
+
- 调用高德地理编码接口,将结构化地址或地标性名胜景区、建筑物名称解析为坐标
|
|
196
|
+
- 传入 `--city` 时,会优先在对应城市范围内解析
|
|
197
|
+
- 返回结果中的 `geocode.location` 为 `[经度, 纬度]`
|
|
198
|
+
- 返回结果中的 `geocode.formattedAddress` 为高德返回的标准化地址
|
|
199
|
+
- 对于较模糊的名称,建议补充更完整地址或增加 `--city`
|
|
200
|
+
|
|
148
201
|
### 路径规划
|
|
149
202
|
|
|
150
203
|
基础格式:
|
|
@@ -24,10 +24,35 @@ uv run python -m amap_cli --help
|
|
|
24
24
|
结果:
|
|
25
25
|
|
|
26
26
|
- `uv sync` 成功完成本地安装
|
|
27
|
-
- `amap-cli --help` 正确显示 `config`、`distance`、`route`、`search-poi`
|
|
27
|
+
- `amap-cli --help` 正确显示 `config`、`geocode`、`distance`、`route`、`search-poi` 五个子命令
|
|
28
28
|
- `python -m amap_cli --help` 与脚本入口行为一致
|
|
29
29
|
|
|
30
|
-
### 2. `
|
|
30
|
+
### 2. `geocode` 典型输入通过 CLI 主入口走通到 handler
|
|
31
|
+
|
|
32
|
+
说明:
|
|
33
|
+
|
|
34
|
+
- 使用 `amap_cli.cli.main(...)` 作为 CLI 主入口
|
|
35
|
+
- 通过 monkeypatch 将 `amap_cli.geocode_command.AmapApiClient` 替换为假 client
|
|
36
|
+
- 仍然走真实参数解析、handler 与统一 JSON 输出
|
|
37
|
+
|
|
38
|
+
验证输入:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
geocode --address 北京南站
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
假 client 返回:
|
|
45
|
+
|
|
46
|
+
- `/v3/geocode/geo`:北京南站的坐标与标准化地址
|
|
47
|
+
|
|
48
|
+
结果:
|
|
49
|
+
|
|
50
|
+
- CLI 返回 `success=true`
|
|
51
|
+
- `state.address` 正确保留原始查询
|
|
52
|
+
- `geocode.location` 返回坐标数组
|
|
53
|
+
- `geocode.formattedAddress` 正确透传
|
|
54
|
+
|
|
55
|
+
### 3. `distance` 坐标输入走本地直线距离计算
|
|
31
56
|
|
|
32
57
|
执行:
|
|
33
58
|
|
|
@@ -42,13 +67,14 @@ uv run amap-cli distance --from 116.397,39.909 --to 116.407,39.904
|
|
|
42
67
|
- `state.mode=straight_line`
|
|
43
68
|
- `summary.distance` 返回两点之间的直线距离(米)
|
|
44
69
|
|
|
45
|
-
###
|
|
70
|
+
### 4. 配置写入与自动读取
|
|
46
71
|
|
|
47
72
|
为避免污染真实用户配置,验证时使用隔离 `HOME`:
|
|
48
73
|
|
|
49
74
|
```bash
|
|
50
75
|
TMP_HOME=$(mktemp -d)
|
|
51
76
|
HOME="$TMP_HOME" uv run amap-cli config set --api-key test-demo-key --timeout-seconds 12
|
|
77
|
+
HOME="$TMP_HOME" uv run amap-cli config set --request-sleep-seconds 0
|
|
52
78
|
HOME="$TMP_HOME" uv run amap-cli config show
|
|
53
79
|
```
|
|
54
80
|
|
|
@@ -58,8 +84,9 @@ HOME="$TMP_HOME" uv run amap-cli config show
|
|
|
58
84
|
- macOS 下配置落在 `~/Library/Application Support/amap-cli/config.json`
|
|
59
85
|
- 再次执行 `config show` 时可自动读取刚写入的配置
|
|
60
86
|
- `api_key` 输出已脱敏
|
|
87
|
+
- `request_sleep_seconds` 可写入为 `0`,用于关闭默认的请求后 sleep
|
|
61
88
|
|
|
62
|
-
###
|
|
89
|
+
### 5. `route` 典型输入通过 CLI 主入口走通到 handler
|
|
63
90
|
|
|
64
91
|
说明:
|
|
65
92
|
|
|
@@ -85,7 +112,7 @@ route --from 北京南站 --to 天安门 --type driving
|
|
|
85
112
|
- 起终点地名与坐标正确进入输出
|
|
86
113
|
- `summary.distance`、`summary.time`、`summary.steps` 正常生成
|
|
87
114
|
|
|
88
|
-
###
|
|
115
|
+
### 6. `search-poi` 典型输入通过 CLI 主入口走通到 handler
|
|
89
116
|
|
|
90
117
|
说明:
|
|
91
118
|
|
|
@@ -130,5 +157,5 @@ search-poi --keyword 星巴克 --city 北京 --pageSize 1
|
|
|
130
157
|
|
|
131
158
|
结论:
|
|
132
159
|
|
|
133
|
-
-
|
|
134
|
-
- CLI 主入口已完成 `distance`、`route`、`search-poi` 整合
|
|
160
|
+
- 当前六项手动验证均已通过
|
|
161
|
+
- CLI 主入口已完成 `geocode`、`distance`、`route`、`search-poi` 整合
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: amap-cli
|
|
3
|
-
description: Use amap-cli for terminal-based Amap distance calculation, route planning, and POI search. Use when the user needs machine-readable map results in this project. Do not use for GUI-based map interaction.
|
|
3
|
+
description: Use amap-cli for terminal-based Amap geocoding, distance calculation, route planning, and POI search. Use when the user needs machine-readable map results in this project. Do not use for GUI-based map interaction.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Amap CLI
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
在需要高德地图地理编码、直线距离计算、路径规划或 POI 搜索时,直接调用 `amap-cli`。
|
|
9
9
|
|
|
10
10
|
## 操作规则
|
|
11
11
|
|
|
12
12
|
- 统一使用 `uvx amap-cli` 调用命令
|
|
13
13
|
- 优先返回和解析 JSON 结果,不依赖自然语言输出
|
|
14
|
-
- 在执行 `route` 或 `search-poi` 前,先确认高德 API Key 已配置
|
|
14
|
+
- 在执行 `geocode`、`route` 或 `search-poi` 前,先确认高德 API Key 已配置
|
|
15
15
|
- 在执行 `distance` 前,若起终点包含地名,也需要先确认高德 API Key 已配置
|
|
16
|
+
- 如果同一个地点后续还要重复用于 `distance`、`route` 或 `search-poi` 周边搜索,先调用 `geocode` 或 `search-poi` 获取坐标,后续优先传 `经度,纬度`,不要重复传地名,以减少地理编码 API 调用量
|
|
17
|
+
- 结构化地址、地标性名胜景区或建筑物名称优先使用 `geocode` 获取坐标;较模糊的地点名称优先使用 `search-poi` 获取目标坐标
|
|
16
18
|
- 如果返回 `MISSING_CONFIG`,先执行配置命令,再继续业务调用
|
|
17
19
|
|
|
18
20
|
## 初始化
|
|
@@ -35,6 +37,32 @@ uvx amap-cli config set --api-key <YOUR_AMAP_KEY>
|
|
|
35
37
|
uvx amap-cli config show
|
|
36
38
|
```
|
|
37
39
|
|
|
40
|
+
## Geocode
|
|
41
|
+
|
|
42
|
+
需要将结构化地址或地标名称解析为坐标时,使用 `geocode`:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
uvx amap-cli geocode --address 北京南站
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
如果需要缩小解析范围,可追加城市:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
uvx amap-cli geocode --address 软件园二期 --city 厦门
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
关键参数:
|
|
55
|
+
|
|
56
|
+
- `--address`:必填,结构化地址、地标性名胜景区或建筑物名称
|
|
57
|
+
- `--city`:可选,用于缩小地理编码范围
|
|
58
|
+
|
|
59
|
+
关键约束:
|
|
60
|
+
|
|
61
|
+
- 地理编码仅支持详细的结构化地址,以及地标性名胜景区、建筑物名称
|
|
62
|
+
- 返回结果中的 `geocode.location` 为 `[经度, 纬度]`
|
|
63
|
+
- 返回结果中的 `geocode.formattedAddress` 可用于判断解析是否符合预期
|
|
64
|
+
- 对于较模糊的名称,优先补充更完整的地址信息或追加 `--city`
|
|
65
|
+
|
|
38
66
|
## Route
|
|
39
67
|
|
|
40
68
|
需要路径规划时,使用 `route`:
|
|
@@ -71,10 +99,17 @@ uvx amap-cli route \
|
|
|
71
99
|
- `--type transit` 必须显式传 `--city`
|
|
72
100
|
- `--waypoints` 和 `--policy` 只能和 `--type driving` 一起使用
|
|
73
101
|
- `--strategy` 只能和 `--type transit` 一起使用
|
|
102
|
+
- 如果起点、终点或途经点后续会复用,先用 `geocode` 或 `search-poi` 拿到坐标,再将坐标传给 `--from`、`--to`、`--waypoints`
|
|
74
103
|
- 地理编码仅支持详细的结构化地址,以及地标性名胜景区、建筑物名称
|
|
75
104
|
- 当输入为上述名称时,可从 `state.from.formattedAddress`、`state.to.formattedAddress` 以及 `state.waypoints[*].formattedAddress` 判断解析是否符合预期
|
|
76
105
|
- 对于其他较模糊的名称,先使用 `search-poi` 获取目标坐标,再调用 `route`
|
|
77
106
|
|
|
107
|
+
推荐流程:
|
|
108
|
+
|
|
109
|
+
1. 首次解析地点时,使用 `geocode` 或 `search-poi` 获取坐标
|
|
110
|
+
2. 后续多次规划路线时,统一复用坐标
|
|
111
|
+
3. 如需保留地点语义,可配合 `--from-name` / `--to-name`
|
|
112
|
+
|
|
78
113
|
## Distance
|
|
79
114
|
|
|
80
115
|
需要计算两个地点之间的直线距离时,使用 `distance`:
|
|
@@ -103,6 +138,7 @@ uvx amap-cli distance \
|
|
|
103
138
|
|
|
104
139
|
- 当 `--from` 和 `--to` 都是坐标时,直接本地计算,不依赖 API Key
|
|
105
140
|
- 任一输入为结构化地址或地标性名胜景区、建筑物名称时,会先调用地理编码接口解析坐标
|
|
141
|
+
- 如果某个地点后续还会继续参与其他计算,首次解析后应复用坐标,避免再次以地名触发地理编码
|
|
106
142
|
- 当输入为上述名称时,可从 `state.from.formattedAddress` / `state.to.formattedAddress` 判断地理编码是否符合预期
|
|
107
143
|
- 对于其他较模糊的名称,先使用 `search-poi` 获取目标坐标,再调用 `distance`
|
|
108
144
|
- 结果中的 `summary.distance` 单位为米
|
|
@@ -141,6 +177,7 @@ uvx amap-cli search-poi \
|
|
|
141
177
|
- 传入 `--center` 时执行周边搜索
|
|
142
178
|
- 未传入 `--center` 时执行关键词搜索
|
|
143
179
|
- `--radius` 只有在传入 `--center` 时才应该使用
|
|
180
|
+
- 如果要围绕某个地点反复做周边搜索,先使用 `geocode` 或 `search-poi` 获取该地点坐标,再持续复用 `--center`,不要每次都传地点名称重新解析
|
|
144
181
|
|
|
145
182
|
## 输出约定
|
|
146
183
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
|
+
import time
|
|
6
7
|
from typing import Any
|
|
7
8
|
from urllib.error import HTTPError, URLError
|
|
8
9
|
from urllib.parse import urlencode
|
|
@@ -39,32 +40,36 @@ class AmapApiClient:
|
|
|
39
40
|
)
|
|
40
41
|
|
|
41
42
|
try:
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
43
|
+
try:
|
|
44
|
+
with urlopen(request, timeout=self.config.timeout_seconds) as response:
|
|
45
|
+
body = response.read().decode("utf-8")
|
|
46
|
+
except HTTPError as exc:
|
|
47
|
+
response_body = exc.read().decode("utf-8", errors="ignore")
|
|
48
|
+
raise ApiRequestError(
|
|
49
|
+
f"高德 API HTTP 请求失败:{exc.code}",
|
|
50
|
+
details={"status": exc.code, "body": response_body},
|
|
51
|
+
) from exc
|
|
52
|
+
except URLError as exc:
|
|
53
|
+
raise ApiRequestError(
|
|
54
|
+
"无法连接高德 API。",
|
|
55
|
+
details={"reason": str(exc.reason)},
|
|
56
|
+
) from exc
|
|
57
|
+
except TimeoutError as exc:
|
|
58
|
+
raise ApiRequestError("请求高德 API 超时。") from exc
|
|
57
59
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
60
|
+
try:
|
|
61
|
+
payload = json.loads(body)
|
|
62
|
+
except json.JSONDecodeError as exc:
|
|
63
|
+
raise ApiRequestError(
|
|
64
|
+
"高德 API 返回了非 JSON 响应。",
|
|
65
|
+
details={"body": body[:500]},
|
|
66
|
+
) from exc
|
|
65
67
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
+
self._raise_for_amap_error(payload)
|
|
69
|
+
return payload
|
|
70
|
+
finally:
|
|
71
|
+
if self.config.request_sleep_seconds > 0:
|
|
72
|
+
time.sleep(self.config.request_sleep_seconds)
|
|
68
73
|
|
|
69
74
|
def _build_url(self, path: str, params: dict[str, Any]) -> str:
|
|
70
75
|
"""Build a full request URL from config and query params."""
|
|
@@ -8,6 +8,7 @@ from typing import Any, Sequence
|
|
|
8
8
|
from amap_cli.config import get_config_path, load_config, save_config
|
|
9
9
|
from amap_cli.distance import register_distance_command
|
|
10
10
|
from amap_cli.errors import AmapCliError, ValidationError
|
|
11
|
+
from amap_cli.geocode_command import register_geocode_command
|
|
11
12
|
from amap_cli.output import emit_error, emit_success
|
|
12
13
|
from amap_cli.route import register_route_command
|
|
13
14
|
from amap_cli.search_poi import register_search_poi_command
|
|
@@ -31,6 +32,7 @@ def build_parser() -> JsonArgumentParser:
|
|
|
31
32
|
|
|
32
33
|
_register_config_command(subparsers)
|
|
33
34
|
register_install_skill_command(subparsers)
|
|
35
|
+
register_geocode_command(subparsers)
|
|
34
36
|
register_distance_command(subparsers)
|
|
35
37
|
register_route_command(subparsers)
|
|
36
38
|
register_search_poi_command(subparsers)
|
|
@@ -54,6 +56,11 @@ def _register_config_command(
|
|
|
54
56
|
type=float,
|
|
55
57
|
help="请求超时时间(秒)",
|
|
56
58
|
)
|
|
59
|
+
set_parser.add_argument(
|
|
60
|
+
"--request-sleep-seconds",
|
|
61
|
+
type=float,
|
|
62
|
+
help="每次高德 API 调用结束后的 sleep 时间(秒,0 表示关闭)",
|
|
63
|
+
)
|
|
57
64
|
set_parser.set_defaults(handler=_handle_config_set)
|
|
58
65
|
|
|
59
66
|
show_parser = config_subparsers.add_parser("show", help="查看当前配置(敏感信息脱敏)")
|
|
@@ -71,15 +78,17 @@ def _handle_config_set(args: argparse.Namespace) -> dict[str, Any]:
|
|
|
71
78
|
args.api_key is None
|
|
72
79
|
and args.base_url is None
|
|
73
80
|
and args.timeout_seconds is None
|
|
81
|
+
and args.request_sleep_seconds is None
|
|
74
82
|
):
|
|
75
83
|
raise ValidationError(
|
|
76
|
-
"请至少提供一个配置项,例如 `--api-key`、`--base-url` 或 `--
|
|
84
|
+
"请至少提供一个配置项,例如 `--api-key`、`--base-url`、`--timeout-seconds` 或 `--request-sleep-seconds`。"
|
|
77
85
|
)
|
|
78
86
|
|
|
79
87
|
config = save_config(
|
|
80
88
|
api_key=args.api_key,
|
|
81
89
|
base_url=args.base_url,
|
|
82
90
|
timeout_seconds=args.timeout_seconds,
|
|
91
|
+
request_sleep_seconds=args.request_sleep_seconds,
|
|
83
92
|
)
|
|
84
93
|
return {
|
|
85
94
|
"message": "配置已保存。",
|
|
@@ -15,6 +15,7 @@ APP_NAME = "amap-cli"
|
|
|
15
15
|
CONFIG_FILE_NAME = "config.json"
|
|
16
16
|
DEFAULT_BASE_URL = "https://restapi.amap.com"
|
|
17
17
|
DEFAULT_TIMEOUT_SECONDS = 10.0
|
|
18
|
+
DEFAULT_REQUEST_SLEEP_SECONDS = 0.34
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
@dataclass(slots=True)
|
|
@@ -24,6 +25,7 @@ class AmapConfig:
|
|
|
24
25
|
api_key: str | None = None
|
|
25
26
|
base_url: str = DEFAULT_BASE_URL
|
|
26
27
|
timeout_seconds: float = DEFAULT_TIMEOUT_SECONDS
|
|
28
|
+
request_sleep_seconds: float = DEFAULT_REQUEST_SLEEP_SECONDS
|
|
27
29
|
|
|
28
30
|
@classmethod
|
|
29
31
|
def from_dict(cls, raw: dict[str, Any]) -> "AmapConfig":
|
|
@@ -31,6 +33,10 @@ class AmapConfig:
|
|
|
31
33
|
api_key = raw.get("api_key")
|
|
32
34
|
base_url = raw.get("base_url", DEFAULT_BASE_URL)
|
|
33
35
|
timeout_seconds = raw.get("timeout_seconds", DEFAULT_TIMEOUT_SECONDS)
|
|
36
|
+
request_sleep_seconds = raw.get(
|
|
37
|
+
"request_sleep_seconds",
|
|
38
|
+
DEFAULT_REQUEST_SLEEP_SECONDS,
|
|
39
|
+
)
|
|
34
40
|
|
|
35
41
|
if api_key is not None and not isinstance(api_key, str):
|
|
36
42
|
raise ConfigError("配置文件中的 api_key 格式无效。")
|
|
@@ -38,6 +44,11 @@ class AmapConfig:
|
|
|
38
44
|
raise ConfigError("配置文件中的 base_url 格式无效。")
|
|
39
45
|
if not isinstance(timeout_seconds, (int, float)) or timeout_seconds <= 0:
|
|
40
46
|
raise ConfigError("配置文件中的 timeout_seconds 格式无效。")
|
|
47
|
+
if (
|
|
48
|
+
not isinstance(request_sleep_seconds, (int, float))
|
|
49
|
+
or request_sleep_seconds < 0
|
|
50
|
+
):
|
|
51
|
+
raise ConfigError("配置文件中的 request_sleep_seconds 格式无效。")
|
|
41
52
|
|
|
42
53
|
normalized_api_key = None
|
|
43
54
|
if api_key is not None:
|
|
@@ -47,6 +58,7 @@ class AmapConfig:
|
|
|
47
58
|
api_key=normalized_api_key,
|
|
48
59
|
base_url=base_url.strip(),
|
|
49
60
|
timeout_seconds=float(timeout_seconds),
|
|
61
|
+
request_sleep_seconds=float(request_sleep_seconds),
|
|
50
62
|
)
|
|
51
63
|
|
|
52
64
|
def to_dict(self, *, mask_secrets: bool = False) -> dict[str, Any]:
|
|
@@ -149,6 +161,7 @@ def save_config(
|
|
|
149
161
|
api_key: str | None = None,
|
|
150
162
|
base_url: str | None = None,
|
|
151
163
|
timeout_seconds: float | None = None,
|
|
164
|
+
request_sleep_seconds: float | None = None,
|
|
152
165
|
) -> AmapConfig:
|
|
153
166
|
"""Persist config updates to the OS-specific config path."""
|
|
154
167
|
config = load_config(required=False) or AmapConfig()
|
|
@@ -170,6 +183,11 @@ def save_config(
|
|
|
170
183
|
raise ValidationError("`--timeout-seconds` 必须大于 0。")
|
|
171
184
|
config.timeout_seconds = float(timeout_seconds)
|
|
172
185
|
|
|
186
|
+
if request_sleep_seconds is not None:
|
|
187
|
+
if request_sleep_seconds < 0:
|
|
188
|
+
raise ValidationError("`--request-sleep-seconds` 不能小于 0。")
|
|
189
|
+
config.request_sleep_seconds = float(request_sleep_seconds)
|
|
190
|
+
|
|
173
191
|
path = get_config_path()
|
|
174
192
|
try:
|
|
175
193
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Geocode command registration and handlers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from amap_cli.api import AmapApiClient
|
|
10
|
+
from amap_cli.geocode import GeocodeResult, geocode_text
|
|
11
|
+
from amap_cli.params import normalize_text_argument
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True, slots=True)
|
|
15
|
+
class GeocodeRequest:
|
|
16
|
+
"""Validated geocode command input."""
|
|
17
|
+
|
|
18
|
+
address: str
|
|
19
|
+
city: str | None
|
|
20
|
+
|
|
21
|
+
@classmethod
|
|
22
|
+
def from_args(cls, args: argparse.Namespace) -> "GeocodeRequest":
|
|
23
|
+
"""Build a validated geocode request from parsed CLI arguments."""
|
|
24
|
+
return cls(
|
|
25
|
+
address=normalize_text_argument(args.address, "address"),
|
|
26
|
+
city=(
|
|
27
|
+
normalize_text_argument(args.city, "city")
|
|
28
|
+
if args.city is not None
|
|
29
|
+
else None
|
|
30
|
+
),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
def build_state(self) -> dict[str, Any]:
|
|
34
|
+
"""Build CLI-facing state metadata."""
|
|
35
|
+
state: dict[str, Any] = {"address": self.address}
|
|
36
|
+
if self.city is not None:
|
|
37
|
+
state["city"] = self.city
|
|
38
|
+
return state
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def register_geocode_command(
|
|
42
|
+
subparsers: argparse._SubParsersAction[argparse.ArgumentParser],
|
|
43
|
+
) -> argparse.ArgumentParser:
|
|
44
|
+
"""Register the `geocode` command on a subparser collection."""
|
|
45
|
+
parser = subparsers.add_parser(
|
|
46
|
+
"geocode",
|
|
47
|
+
help="将地点名称解析为坐标",
|
|
48
|
+
description="调用高德地理编码接口,将结构化地址或地标名称解析为坐标。",
|
|
49
|
+
)
|
|
50
|
+
parser.add_argument(
|
|
51
|
+
"--address",
|
|
52
|
+
required=True,
|
|
53
|
+
help="待解析的结构化地址或地标名称",
|
|
54
|
+
)
|
|
55
|
+
parser.add_argument(
|
|
56
|
+
"--city",
|
|
57
|
+
help="可选城市,用于缩小地理编码范围",
|
|
58
|
+
)
|
|
59
|
+
parser.set_defaults(handler=handle_geocode_command)
|
|
60
|
+
return parser
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def handle_geocode_command(
|
|
64
|
+
args: argparse.Namespace,
|
|
65
|
+
*,
|
|
66
|
+
client: AmapApiClient | None = None,
|
|
67
|
+
) -> dict[str, Any]:
|
|
68
|
+
"""Handle the `geocode` command and normalize the response."""
|
|
69
|
+
request = GeocodeRequest.from_args(args)
|
|
70
|
+
result = geocode_text(
|
|
71
|
+
request.address,
|
|
72
|
+
client=client or AmapApiClient(),
|
|
73
|
+
city=request.city,
|
|
74
|
+
)
|
|
75
|
+
return {
|
|
76
|
+
"state": request.build_state(),
|
|
77
|
+
"geocode": _normalize_geocode_result(result),
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _normalize_geocode_result(result: GeocodeResult) -> dict[str, Any]:
|
|
82
|
+
"""Normalize the geocode result to the CLI output shape."""
|
|
83
|
+
payload: dict[str, Any] = {
|
|
84
|
+
"location": [
|
|
85
|
+
round(result.coordinate.longitude, 6),
|
|
86
|
+
round(result.coordinate.latitude, 6),
|
|
87
|
+
]
|
|
88
|
+
}
|
|
89
|
+
if result.formatted_address is not None:
|
|
90
|
+
payload["formattedAddress"] = result.formatted_address
|
|
91
|
+
return payload
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|