Metadata-Version: 2.4
Name: findmyway
Version: 0.0.1
Summary: 基于高德地图的多点最短路径（TSP）规划工具
Author-email: Xu Yang <shi_liu_zhi@sina.com>
License-Expression: MIT
Project-URL: Repository, https://github.com/shi-liu-zhi/findmyway
Project-URL: Issues, https://github.com/shi-liu-zhi/findmyway/issues
Keywords: amap,gaode,高德地图,tsp,route-planning,navigation
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: GIS
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests>=2.32
Requires-Dist: qrcode[pil]>=8.0
Provides-Extra: mcp
Requires-Dist: mcp>=2.0; extra == "mcp"
Dynamic: license-file

# findmyway

> 基于高德地图的多点最短路径（TSP）规划工具

[![CI](https://github.com/shi-liu-zhi/findmyway/actions/workflows/ci.yml/badge.svg)](https://github.com/shi-liu-zhi/findmyway/actions/workflows/ci.yml)
[![Python](https://img.shields.io/badge/python-3.10%2B-blue)](https://www.python.org/)
[![License](https://img.shields.io/badge/license-MIT-green)](LICENSE)

给定一批地点，`findmyway` 会：

1. 通过高德**地理编码**把地址转成经纬度；
2. 通过**批量算路**接口高效构建两两驾车耗时矩阵；
3. 用**状态压缩 DP** 求最优环形访问顺序（非对称 TSP）；
4. 通过高德**路径规划 2.0** 按最优顺序生成真实路线；
5. 生成可**直接调起高德 App** 的导航链接与二维码。

## 为什么用它

高德官方的"多点规划"（`waypoints`）虽然支持最多 16 个途经点，但**只能按你给的顺序规划，不会帮你优化顺序**。
`findmyway` 用批量算路 + TSP 补齐了"顺序优化"这一环，再用官方接口渲染真实路线。

## 安装

```bash
# 推荐：用 uv
uv add findmyway

# 或用 pip
pip install findmyway

# 需要 MCP 服务器功能（供 pi / Claude 等调用）
pip install "findmyway[mcp]"
```

## 配置高德凭据

需要一个高德 **Web 服务**类型的 API Key（在[控制台](https://console.amap.com/dev/key/app)创建，平台务必选 **「Web服务」**，不是 Web端JS/Android/iOS）。

关于**私钥**：

| Key 是否开启「数字签名」 | 是否要私钥 |
|---|---|
| 开启了（有安全密钥） | ✅ 必须提供私钥 |
| 未开启（安全密钥为空） | ❌ 不需要私钥 |

**方式一：命令行（写入配置文件，永久生效）**

```bash
# 未开启数字签名
findmyway config set --key <你的KEY>

# 开启了数字签名（会额外要求私钥）
findmyway config set --key <你的KEY> --private-key <你的私钥>

findmyway config show   # 查看（脱敏）
findmyway config path   # 查看配置文件路径
findmyway config clear  # 清除
```

**方式二：环境变量（优先级最高）**

```bash
export AMAP_KEY=<你的KEY>
export AMAP_PRIVATE_KEY=<你的私钥>   # 未开启数字签名时可省略
```

> 凭据解析顺序：**环境变量 > 配置文件**。配置文件默认位于 `~/.config/findmyway/config.json`（权限 600）。

## 快速开始

```python
import findmyway

# 1) 创建地点（resolve 会调用地理编码；直接 Node(...) 不联网）
nodes = [
    findmyway.Node.resolve("海底捞天一广场店", city="宁波"),
    findmyway.Node.resolve("星巴克宁波来福士", city="宁波"),
    findmyway.Node.resolve("外婆家天一广场店", city="宁波"),
]

# 2) 批量算路构建两两耗时矩阵（N 个点 = N 次请求）
matrix = findmyway.build_duration_matrix(nodes, sleep=0)

# 3) 求最优环形顺序
pairs = findmyway.pairwise_durations(*nodes)
path, total_seconds = findmyway.TspSolver.from_data(pairs, start=nodes[0]).solve()
print("最优顺序:", " -> ".join(n.address for n in path))
print("总耗时:", round(total_seconds / 60, 1), "分钟")

# 4) 用官方 v5 渲染真实路线（含里程 / 路况）
route = findmyway.multi_point_route(path)
print("总距离:", route["distance"], "米")

# 5) 导航链接 + 二维码
links = findmyway.links_for_path(path, nodes)
findmyway.qrcode_for_url(links[0]).save("nav.png")
```

### 一行搞定：地址列表 → 二维码 🎯

不想手动串流程？一个函数直达二维码：

```python
import findmyway

# 地址列表 -> 自动地理编码 -> 批量算路 -> TSP 定序 -> 官方路线 -> 二维码
trip = findmyway.plan_trip(
    ["海底捞天一广场店", "星巴克宁波来福士", "外婆家天一广场店"],
    city="宁波",
    start="海底捞天一广场店",     # 可选，默认第一个
)

print(trip.order)      # 最优顺序（含回到起点）
print(trip.distance)   # 总距离（米）
print(trip.duration)   # 总耗时（秒）
print(trip.links)      # 导航链接（点多时自动分段，返回多段）

trip.save_qrcodes("~/Pictures/nav")   # 每段二维码存成 PNG
```

> **默认回到起点**（环形）。若要**开放路径**（访问完所有点即结束、不回起点），传 `return_to_start=False`：
> ```python
> trip = findmyway.plan_trip(["地点A", "地点B", "地点C"], return_to_start=False)
> ```

只要二维码文件，一行即可：

```python
paths = findmyway.addresses_to_qrcodes(
    ["海底捞天一广场店", "星巴克宁波来福士", "外婆家天一广场店"],
    out_dir="~/Pictures/nav", prefix="trip",
)
```

### 不建 Node 也能用

```python
# 直接用坐标字符串
route = findmyway.multi_point_route(["121.55,29.87", "121.56,29.88", "121.57,29.89"])
link  = findmyway.navigation_link("121.55,29.87", "121.57,29.89", waypoint="121.56,29.88")
```

## API 一览

| 类别 | 函数 / 类 | 说明 |
|------|-----------|------|
| 凭据 | `get_amap_credentials()` | 返回 `(key, private_key)` |
| 地理编码 | `geocode(address, city="")` | 地址 → `"经度,纬度"`（带缓存）|
| 单点对 | `driving_duration(origin, dest, strategy=0)` | 两点驾车耗时（秒）|
| 路线 | `driving_route(origin, dest, waypoints, strategy=32)` | 官方 v5，支持有序途经点 |
| 批量 | `batch_driving_durations(origins, dest, calc_type=1)` | 多起点→单目的地，一次≤100 起点 |
| 规划 | `two_point_duration` / `two_node_duration` | 便捷封装 |
| 矩阵 | `build_duration_matrix(nodes)` | 两两耗时矩阵（N 次请求）|
| 规划 | `pairwise_durations(*nodes)` | 所有有序节点对（喂给 TSP）|
| 规划 | `multi_point_route(nodes, strategy=32)` | 按顺序的多点真实路线（自动分段）|
| 链接 | `navigation_link(...)` | 调起高德 App 的链接 |
| 链接 | `links_for_path(path, all_nodes)` | 把路径批量转为导航链接（自动分段）|
| 链接 | `qrcode_for_url(url)` | 链接 → 二维码 |
| 模型 | `Node(address, city="宁波")` | 不可变地点节点（不联网）|
| 模型 | `Node.resolve(address, city="宁波")` | 地理编码后构造节点 |
| 求解 | `TspSolver` | 非对称环形 TSP（状态压缩 DP）|
| 一站式 | `plan_trip(addresses, ..., return_to_start=True)` | 地址列表 → 最优顺序/路线/链接/二维码（默认回到起点）|
| 一站式 | `addresses_to_qrcodes(addresses, out_dir)` | 地址列表 → 直接存二维码 PNG |
| 一站式 | `TripPlan` | 规划结果（order/distance/duration/links/qrcodes）|
| 异常 | `AmapAPIError` | 高德 API 相关错误 |

## 命令行

```bash
findmyway --version
findmyway config set --key <KEY> --private-key <KEY>   # 交互式会提示输入
findmyway config show
findmyway config path
findmyway config clear
```

## 作为 MCP 服务器使用

`findmyway` 内置 MCP（Model Context Protocol）服务器，可被 pi / Claude Desktop / Cursor 等直接调用。

**安装 MCP 支持：**

```bash
pip install "findmyway[mcp]"
```

**运行方式一：stdio（默认，本地单客户端）**

```bash
findmyway-mcp
```

**运行方式二：streamable HTTP（远程 / 多客户端 / 容器）**

```bash
findmyway-mcp --http                              # http://127.0.0.1:8000/mcp
findmyway-mcp --http --host 0.0.0.0 --port 9000   # 自定义地址/端口
findmyway-mcp --http --path /srv                  # 自定义路径
```

> ⚠️ HTTP 模式若绑到 `0.0.0.0` 且无鉴权，等于把高德 Key 配额暴露给同网段，请仅在可信网络使用或自行加 Token。

**提供的工具：**

| 工具 | 说明 |
|------|------|
| `amap_geocode` | 地址 → 经纬度 |
| `amap_driving_duration` | 两点驾车耗时 |
| `amap_navigation_link` | 生成高德导航链接 |
| `plan_trip_route` | 地址列表 → 最优顺序 / 里程 / 耗时 / 链接 |
| `plan_trip_qrcodes` | 地址列表 → 导航二维码图片 |
| `qrcode_for_navigation_link` | 链接 → 二维码图片 |

**注册到 pi（stdio）：**

```bash
pi mcp add findmyway --env AMAP_KEY=<KEY> --env AMAP_PRIVATE_KEY=<PRIVATE_KEY> -- findmyway-mcp
```

**注册到 pi（HTTP）：**

```bash
pi mcp add findmyway --url http://127.0.0.1:8000/mcp
```

**Claude Desktop / 其他客户端**（`mcpServers` 配置）：

```json
{
  "mcpServers": {
    "findmyway": {
      "command": "findmyway-mcp",
      "env": {
        "AMAP_KEY": "<你的KEY>",
        "AMAP_PRIVATE_KEY": "<你的私钥>"
      }
    }
  }
}
```

## 设计与性能

- **批量算路**：距离矩阵从 `N×(N-1)` 次请求降到 **`N`** 次（20 个点：380 → 20）。
- **连接复用 + 指数退避重试**：`requests.Session` 复用连接，失败自动重试。
- **地理编码缓存**：相同 `(address, city)` 命中进程内缓存。
- **自动分段**：单次路线最多 16 途经点，超长自动切段、相邻段共享边界点。
- **TSP 复杂度** `O(2^n · n²)`，适合节点数 ≲16 的场景。

## 开发

```bash
git clone https://github.com/shi-liu-zhi/findmyway
cd findmyway
uv sync                 # 安装依赖（含 dev）
uv run pytest           # 运行测试（49 个用例，全 mock 无网络）
uv run pytest --cov     # 覆盖率
uv run ruff check src tests
```

> 本项目用 [uv](https://docs.astral.sh/uv/) 管理环境：`uv.lock` 锁定依赖，`uv.toml` 配置为使用系统 Python。

## 许可证

[MIT](LICENSE) © 2026 shi-liu-zhi
