TTokenySpace
返回 Skills 列表

Road Trip Planner

基于高德地图 personal-map skill 规划多日自驾游路线,生成连续路线个人地图小程序二维码,并输出每日行程详情。当用户提到自驾、公路旅行、路线规划、旅游行程、生成地图、规划环线、控制驾驶时间或查询沿途里程时使用。支持闭合环线设计和跨天里程补全。 Plan multi-day road trip ro...

#中文
0

安装到 Tokeny(自动)

下载 ZIP
安装"road-trip-planner"技能
技能信息:
- 名称: Road Trip Planner
- 标识: road-trip-planner
- 描述: 基于高德地图 personal-map skill 规划多日自驾游路线,生成连续路线个人地图小程序二维码,并输出每日行程详情。当用户提到自驾、公路旅行、路线规划、旅游行程、生成地图、规划环线、控制驾驶时间或查询沿途里程时使用。支持闭合环线设计和跨天里程补全。 Plan multi-day road trip ro...
- 版本: 1.0.0
下载地址:
https://www.tokeny.space/api/skills/road-trip-planner/download
继续

复制上方内容到 Tokeny 客户端并在会话中发送即可自动安装;也可直接 下载 ZIP并拖动到技能窗口安装。

SKILL.md

Road Trip Planner / 自驾游路线规划

China only — 本 skill 基于高德地图 API,仅适用于中国大陆地区。
Dependency: @lbs-amap/personal-map (ClawHub) / GitHub

Trigger Conditions / 触发条件

Trigger when the user has any of these intents:

当用户出现以下任意意图时,主动调用本 skill:

  • Plan a road trip / road trip route / loop route (规划自驾游路线 / 公路旅行 / 环线)
  • Design a travel itinerary involving multi-city driving (设计涉及多地驾驶的旅游行程)
  • Generate a personal map / travel map / route QR code (生成个人地图 / 旅游地图 / 路线二维码)
  • Control daily driving time / avoid fatigued driving (控制每日驾驶时间 / 避免疲劳驾驶)
  • Query mileage, gas stations, hospitals along the route (查询沿途里程、加油站、医院等补给点)

Dependencies / 前置依赖

DependencySourceDescription
lbs-amap/personal-mapClawHubAmap Web Service API wrapper / 高德 Web 服务 API 封装。安装命令:openclaw skills install personal-map。提供 AMapPersonalMapClient 类(import 路径 scripts.amap_personal_map_client),本仓库自身不含该脚本。
Amap API KeyAmap Open PlatformRequired env var: AMAP_API_KEY / 需配置环境变量

Dependency Availability Check / 依赖可用性检查

Before any API call, verify that the personal-map skill is installed and the API key is configured. If either is missing, stop and tell the user what's needed — do not attempt to proceed.

在任何 API 调用之前,先检查 personal-map skill 是否已安装以及 API Key 是否已配置。任一缺失则停止并告知用户缺失项,不要尝试继续。

Checklist / 检查清单:
1. personal-map skill installed? → No → 提示: "请先安装 personal-map skill,运行: openclaw skills install personal-map"
2. AMAP_API_KEY set? → No → 提示: "请先配置高德 API Key,获取地址 https://lbs.amap.com/"
3. AMapPersonalMapClient importable? → No → 提示: "personal-map skill 版本可能不兼容,请确认其提供 AMapPersonalMapClient 类"

Set the environment variable in the active shell; never put a real key in the skill or source code:

# macOS / Linux
export AMAP_API_KEY='your_key'

# PowerShell
$env:AMAP_API_KEY = 'your_key'

# Windows cmd
set "AMAP_API_KEY=your_key"

Core Principles / 核心原则

  1. No fatigued driving / 不疲劳驾驶:Keep daily actual driving time under 6 hours; 4–5 hours is a planning target, while short sightseeing or transfer days may be shorter. For high-altitude, mountainous, or winding roads, further reduce driving time.(每日实际驾驶时间控制在 6 小时以内;4-5 小时是规划目标,纯玩或短途换乘日可更短。高原、山区、弯道多的路段进一步压缩。)

  2. Continuous route / 连续路线:Merge all daily waypoints into one continuous route, either a closed loop or with a clear endpoint.(所有天的节点合并为一条连续路线,首尾闭合或明确终点。)

  3. Closed loop / 闭合环线:If start and end are the same city, ensure the endpoint is preserved after deduplication.(如果起点和终点是同一城市,确保终点节点在去重后仍然保留在路线末尾。)

  4. Daily flexibility / 每日弹性:Leave buffer room in the itinerary; avoid over-scheduling.(行程中保留机动空间,避免把时间排得太满。)

Workflow / 工作流程

Step 1: Confirm itinerary framework / 确认行程框架

Confirm with the user:

  • Start point, end point (loop or not)
  • Total number of days
  • Must-visit attractions / cities
  • Travel season (affects road conditions and accommodation)
  • Exact departure dates, vehicle type, and any driving/health constraints when safety or road choice depends on them

与用户确认以下信息:起点、终点(是否环线)、总天数、必去景点/途经城市、出发季节、准确出发日期、车型及会影响路线选择的驾驶/健康限制。

If the date is unspecified, provide a provisional seasonal plan and explicitly mark the real-time checks as pending.

Step 2: Design daily waypoints / 设计每日节点

Split waypoints by day following these rules:

  • Target 4–6 hours of driving per day, never exceed 6 hours; short sightseeing or transfer days may be shorter
  • Each day's points list includes the core locations passed that day
  • The end city of each day must also serve as the start point for the next day (for mileage calculation)

按天拆分节点:每天驾驶 4-6 小时,不超过 6 小时;每天的 points 列表包含当天途经的核心地点;终点城市需同时作为下一天的起点。

Step 3: Call Amap APIs / 调用高德 API

Uses AMapPersonalMapClient provided by the personal-map skill. The class is at scripts.amap_personal_map_client inside that skill's directory — this is the canonical import path. This repo itself contains no scripts.

import os
from scripts.amap_personal_map_client import AMapPersonalMapClient

# Check API key BEFORE creating client
api_key = os.getenv("AMAP_API_KEY")
if not api_key:
    print("AMAP_API_KEY not set. Get a key at https://lbs.amap.com/ and run:")
    print("  Set AMAP_API_KEY in your active shell (see Dependencies above).")
    # stop here — do not proceed

# Create client (auto-reads AMAP_API_KEY from env):
client = AMapPersonalMapClient()
# Or pass explicitly: client = AMapPersonalMapClient(api_key="your_key")

Normalize every response before using it. The client does not raise API failures, but a failure can be either an error dict or an error dict as the first item of a list (for example, POI search). Always check both shapes:

def api_error_message(result):
    if isinstance(result, dict) and "error" in result:
        return result.get("message", str(result["error"]))
    if (
        isinstance(result, list)
        and result
        and isinstance(result[0], dict)
        and "error" in result[0]
    ):
        return result[0].get("message", str(result[0]["error"]))
    return None

geo = client.maps_geo("北京市朝阳区", "北京")
if error := api_error_message(geo):
    # Stop handling this location; never reuse another location's coordinates.
    raise ValueError(f"Cannot resolve this location: {error}")

# Convert external fields once. All later workflow steps use only this shape.
point = {
    "name": "地点名称",
    "lon": float(geo["longitude"]),
    "lat": float(geo["latitude"]),
    "poiId": "",             # fill from the verified POI-search result
    "required": False,         # True for the start, end, and user must-visit points
}

For each location:

  1. maps_geo(address, city) — get precise coordinates. Pass the city name as second arg for better accuracy.
  2. maps_text_search(keywords, city, offset=5) — get poiId. Use offset=5 to limit results (API default is 20).
  3. Verify search result coordinates are within 30km of geo coordinates to prevent cross-city mismatches
  4. time.sleep(0.3) between API calls to avoid QPS throttling

对每个地点执行:maps_geo 获取坐标 → 立即标准化为 lon/latmaps_text_search 获取 poiId → 验证距离 < 30km → sleep 0.3s。后续步骤不得直接混用 API 返回的 longitude/latitude 与内部 lon/lat 字段。

Error handling / 错误处理

  • If AMapPersonalMapClient is not found → check the pre-flight checklist in the Dependencies section, inform user, and stop
  • If AMAP_API_KEY is empty → inform user and stop
  • For every response → call api_error_message() before reading result fields
  • If maps_geo fails → do not substitute a previous coordinate. Stop QR generation when this is a start, end, or must-visit point; otherwise list the optional point as unresolved and exclude it from routing
  • If maps_text_search fails or has no result within 30km → retry with {city_name} {location_name}. For a required point, stop QR generation and ask the user to disambiguate; for an optional point, retain it only in the text itinerary as unresolved and do not give it a fake poiId
  • If QPS exceeded (CUQPS_HAS_EXCEEDED_THE_LIMIT in error message) → wait 1s and retry once

3.1 Supply point search per day / 每日补给搜索

After all waypoint coordinates are resolved, search for supply points near key nodes on days that enter sparsely populated areas or have long uninterrupted stretches:

对于进入人烟稀少区域或存在长距离无补给路段的日期,在关键节点附近搜索补给点:

# For each day flagging a supply concern (long stretch, remote area):
# Search gas stations and hospitals near the day's midpoint or last town before uninhabited area
result = client.maps_around_search(
    keywords="加油站|医院",
    location=f"{lon},{lat}",    # "经度,纬度" 格式
    radius=5000,               # 搜索半径 5km(默认 1000m)
    offset=5
)
if error := api_error_message(result):
    print(f"补给搜索失败: {error}")

When to search / 何时搜索

Condition / 条件Action / 动作
Day mileage > 300km / 日里程超 300kmSearch gas stations at midpoint / 在中点搜索加油站
Entering uninhabited area (>100km no town) / 进入无人区(>100km 无城镇)Search all supply points at the last town before entry / 在进入前最后城镇搜索全部补给点
High-altitude segment (>3500m) / 高海拔路段(>3500m)Search hospitals at the nearest low-altitude town / 在最近的低海拔城镇搜索医院
All days / 每天Search at least one hospital near the day's endpoint (overnight city) / 在每天终点城市至少搜索一家医院

Step 4: Driving route planning with cross-day completion / 驾车路径规划(跨天补全)

When calculating daily mileage, must insert the previous day's endpoint as the start of the current day's points list:

for i, day in enumerate(days):
    points = [p for p in day["points"] if "lon" in p]
    if i > 0:
        prev = [p for p in days[i-1]["points"] if "lon" in p]
        if prev:
            points.insert(0, prev[-1])
    # Drive segment by segment using maps_direction_driving:
    #   For each adjacent pair in points, call:
    #     result = client.maps_direction_driving(
    #         origin=f"{points[j]['lon']},{points[j]['lat']}",
    #         destination=f"{points[j+1]['lon']},{points[j+1]['lat']}"
    #     )
    #   Sum distances for the day's total.
    #   Parameter format: "longitude,latitude" (no spaces).

计算每日里程时,必须把前一天的终点插入当天 points 列表头部作为起点。 maps_direction_drivingorigin / destination 参数格式为 "经度,纬度"(逗号分隔,无空格)。

Step 5: Generate personal map QR code / 生成个人地图二维码

5.1 Collect valid points / 收集有效点

Collect only normalized points with a non-empty verified poiId. Preserve insertion order (day-by-day). Before collecting, confirm that all required points (start, end, user must-visits) resolved successfully. If any did not, do not generate a QR code that claims to be the complete route.

收集所有天的有效点(已标准化且 poiId 已验证),按天顺序保持插入顺序。收集前必须确认起点、终点和用户必去点均已解析;任一失败时不得生成声称完整的二维码路线。

5.2 Deduplicate by identity / 按身份去重

Rules (apply in order):

  1. Identity / 身份:Use poiId as the primary identity. Only when no poiId is available for a non-QR text item, treat points as duplicates when their coordinates are within a small tolerance; never deduplicate by name alone.
  2. Closed-loop anchor / 环线锚点:If the first and last points have the same identity, keep both, and remove only middle occurrences of that identity. This ensures the route stays closed.
  3. Intermediate duplicates / 中间重复:For all other repeated identities, keep the first occurrence and remove later ones. Keep the day-by-day itinerary data unchanged; this rule applies only to the QR route list.
  4. Adjacent duplicates / 连续重复:Remove one only when two adjacent QR points have the same identity. Do not infer a duplicate solely because their display names match.
Deduplication flow:
  identities = [p["poiId"] for p in points]
  if len(identities) < 2:
      → stop; do not generate a route QR code
  elif identities[0] == identities[-1]:
      → keep points[0] and points[-1], remove middle occurrences of this identity
  else:
      → for all repeated identities, keep first occurrence and remove rest

5.3 Trim to ≤ 16 nodes / 精简至 ≤ 16 节点

If the deduplicated list exceeds 16 nodes, trim using this priority order:

PriorityKeepDrop
1 (highest)Start and end points (环路锚点)
2User-specified must-visit attractions
3Major scenic spots / landmarks (景点/地标)Rest stops, service areas (服务区)
4Geographically isolated nodes (唯一覆盖该区域的点)Dense urban clusters (keep 1 per city)
5POI with verified poiIdOptional unresolved POI (text itinerary only; never add to QR route)

Geographic distribution principle / 地理分布原则:When choosing between nodes of equal priority, prefer nodes that are farther apart (ensure the route visually represents the full journey). Avoid dropping the only node in a sparse region.

Minimum trim / 最小精简:Only remove as many nodes as needed to reach 16. Never remove the start or end anchor.

5.4 Data integrity before calling API / 调用 API 前的数据完整性

# Build lineList structure
point_info_list = [
    {"name": p["name"], "lon": p["lon"], "lat": p["lat"], "poiId": p["poiId"]}
    for p in deduplicated_points
]
line_list = [{
    "title": "Route Name / 路线名称",
    "pointInfoList": point_info_list
}]

# Validate before calling maps_schema_personal_map
if len(point_info_list) > 16:
    raise ValueError(f"Still {len(point_info_list)} nodes, must be ≤ 16")
if len(point_info_list) < 2:
    raise ValueError("A route QR code needs at least a resolved start and end point")
if any(not p["poiId"] for p in point_info_list):
    raise ValueError("Empty poiId found")

5.5 Call API and download QR / 调用 API 并下载二维码

result = client.maps_schema_personal_map(
    orgName="路线名称",
    lineList=line_list,
    sceneType=3          # 3 = route planning mode (仅创建路线)
)

# Check for API errors before reading result fields
if error := api_error_message(result):
    print(f"地图生成失败: {error}")
    # fallback: output the coordinates as text so user can manually create route
else:
    qr_url = result["qr_code_url"]
    # Download QR image:
    import urllib.request
    qr_path = "/tmp/road_trip_qr.png"
    urllib.request.urlretrieve(qr_url, qr_path)
    # Display inline using the local absolute path:
    # ![路线地图](/tmp/road_trip_qr.png)
    # Fallback link: [在 Amap 中打开](qr_url)
  1. Call maps_schema_personal_map with sceneType=3 (route planning mode / 仅创建路线)
  2. Check for error in result dict (no exceptions thrown)
  3. Download QR code image and display inline
  4. Always include the fallback URL link

收集已验证节点 → 按 poiId/坐标身份去重(保留闭环首尾)→ 构建 lineList → 按优先级精简至 ≤ 16 节点 → 数据完整性校验 → 调用 maps_schema_personal_map(sceneType=3) → 检查错误 → 下载二维码 → 显示备用链接。

Step 6: Output Markdown itinerary / 输出 Markdown 行程表

Output directly in the conversation as Markdown, including:

  • Itinerary overview table (day, route, mileage, duration, road type, attractions) with total row
  • Daily detailed itinerary (waypoints table with coordinates and altitude, supply points)
  • Practical tips (altitude sickness, road conditions, tickets, temperature, seasonal notes)
  • QR code image and fallback link

在对话中直接输出 Markdown:行程概览表格(含总里程合计行)、每日详细行程(含海拔和补给点)、实用提示、二维码图片和备用链接。

Step 6.1: Verify date-sensitive facts / 核验时效信息

Before presenting a final itinerary, verify the route against the actual departure dates. Static seasonal guidance and route-planning API results are not proof that a road, attraction, or service is currently available.

出最终行程前,必须按准确出发日期核验。静态季节提示和路径规划 API 结果均不能证明道路、景区或服务设施当前可用。

  • Check official traffic/road-authority notices for closures, controls, and weather-related restrictions
  • Check current weather warnings for high-altitude, mountain, desert, or remote segments
  • Check attraction opening status, ticket/reservation rules, and holiday calendar/traffic-freeway policy for that year
  • Mark each unavailable source as 未核验 rather than inferring a current status; advise the user to recheck immediately before departure

Key Constraints / 关键约束

Constraint / 约束Description / 说明
Dependency check / 依赖检查Before any API call, verify personal-map skill is installed AND AMAP_API_KEY is set. Fail fast with clear user-facing messages if missing.
Max nodes per route / 单条路线节点上限Amap lineList max 16 nodes per pointInfoList
poiId required / poiId 必填maps_schema_personal_map requires non-empty poiId
poiId strategy / poiId 获取策略maps_text_search first → retry with city name. Required points must resolve; optional unresolved points stay in text only and never receive a fake poiId.
API error pattern / API 错误模式API failures do not throw. Check an error dict or an error dict in the first list item before reading result fields.
QPS limit / QPS 限制≥ 0.3s interval between calls, or CUQPS_HAS_EXCEEDED_THE_LIMIT
API Key / API KeySet AMAP_API_KEY env var (auto-read by AMapPersonalMapClient() with no args) or pass explicitly
Region / 地域限制China only — Amap (高德) covers mainland China
External dependency / 外部依赖This repo contains no scripts. AMapPersonalMapClient lives in the personal-map skill at scripts.amap_personal_map_client. Install via openclaw skills install personal-map.

Output Template / 输出模板

# [Route Name] Road Trip Guide / 自驾行程指南

> Start/End: [City] > Total: ~X km > Days: X > Max Altitude: ~Xm

## Itinerary Overview / 行程概览

| Day | Route | Est. Mileage | Est. Duration | Road Type | Key Attractions |
|-----|-------|-------------|--------------|-----------|-----------------|
| Day 1 | ... | ... | ... | 高速/国道/山路 | ... |
| **Total** | — | **X km** | **~X h** | — | — |

## Daily Itinerary / 每日详细行程

### Day 1: [Title]
- **Driving mileage**: X km (高速 Xkm / 国道 Xkm / 山路 Xkm)
- **Driving duration**: ~X hours
- **Max altitude**: ~Xm (仅高原线路标注 / only for high-altitude routes)

**Waypoints / 途经点**:
| # | Name | Coordinates (lon, lat) | Altitude | Note |
|---|------|------------|----------|------|
| 1 | ... | (lon, lat) | ~Xm | ... |

**Supply points / 沿途补给**:
- ⛽ Gas stations / 加油站: [name] at [location]
- 🏥 Hospitals / 医院: [name] at [location]

## Practical Tips / 实用提示
1. **Altitude sickness / 高原反应**:...(高海拔线路必须包含 / required for routes >3000m)
2. **Road conditions / 路况**:...
3. **Tickets & booking / 门票与预订**:...
4. **Temperature & clothing / 温差与衣物**:...

## Personal Map QR Code / 个人地图二维码
![Route Name](/absolute/path/to/road_trip_qr.png)
> Fallback link / 备用链接: [Open in Amap](url)

Advanced Capabilities / 进阶能力

  • Supply point search / 沿途补给搜索:Integrated into Step 3.1 — conditions and API calls are defined there. / 已集成到 Step 3.1,按条件触发。
  • Mileage anomaly troubleshooting / 里程异常排查:If a day's mileage is 0 or obviously wrong, check whether the previous day's endpoint was correctly inserted (Step 4 cross-day completion), and whether coordinates are duplicated.
  • Seasonal adjustments / 季节性调整:For routes affected by holidays, winter closures, rainy seasons, high altitude, desert heat, or peak tourism windows, read references/seasonal.md before finalizing the itinerary, then complete Step 6.1 with current official sources.

References / 参考

评论

加载中…