Skill: mahongting/amap-search-skill (version 2.0.0)

--- AIGC: ContentProducer: Minimax Agent AI ContentPropagator: Minimax Agent AI Label: AIGC ProduceID: 419f142dffc2786f70c781514aae295d PropagateID: 419f142dffc2786f70c781514aae295d ReservedCode1: 3045022100f0a82fe3f70a96ea776cd6f5eb99567be33ce61f08fa12f0e0a9bfb962f853b50220795b188df61cf38c33d2c6326e01946a47ab16c9efc3c5e8195794683bef4a72 ReservedCode2: 30460221009b63253a328bf0551118bab8d26be628665ca7933d06e030f81d5bb83dc879a2022100aab0156baa6ad550357ea8aee375d5211026970299e2fcc89a93df76a1673d21 description: 高德地图全功能工具 - 集成POI搜索、路径规划、天气查询、公交查询、实时路况等。/Gaode Map All-in-One - POI search, route planning, weather, bus, traffic. icon: "\U0001F5FA️" name: amap-search version: 2.0.0 --- # 🗺️ 高德地图全功能工具 (amap-search) 调用高德地图 Web 服务 API，提供完整的位置服务功能。 ## ✨ 功能一览 | 命令 | 功能 | 说明 | |------|------|------| | `ip` | IP定位 | 自动获取用户当前位置（精确到城市） | | `geo` | 地理编码 | 地址 → 经纬度坐标 | | `regeo` | 逆地理编码 | 经纬度坐标 → 地址 | | `poi` | POI搜索 | 关键字搜索、周边搜索、城市搜索 | | `route` | 路径规划 | 驾车、步行、公交路线规划 | | `weather` | 天气查询 | 当前天气 + 未来预报 | | `traffic` | 实时路况 | 城市/道路拥堵情况 | | `tips` | 输入提示 | 智能自动补全建议 | ## 📋 快速开始 / Quick Start ### 第一步：申请高德 API Key 1. 访问 **高德开放平台**：https://lbs.amap.com/ 2. 点击「注册/登录」→ 使用账号登录 3. 进入「控制台」→ 「应用管理」→ 「创建应用」 4. 应用名称：`高德地图工具`（任意名称） 5. 点击「添加 Key」→ 填写 Key 名称 6. **服务平台**：勾选 **「Web服务」** 7. 点击「提交」，获取 Key > 💡 **免费额度**：每个 Key 每天 2000 次调用，个人使用足够 ### 第二步：配置 API Key（推荐使用环境变量） **方式一：设置环境变量（推荐）** ```bash # 在终端中设置环境变量 export AMAP_API_KEY="你的API_KEY" # 或在 ~/.bashrc 中永久添加 echo 'export AMAP_API_KEY="你的API_KEY"' >> ~/.bashrc source ~/.bashrc ``` **方式二：命令行参数** ```bash python3 gaode_map.py <命令> --key 你的API_KEY [参数] ``` > ⚠️ **优先级**：环境变量 > 命令行参数。建议使用环境变量，避免每次输入。 --- ## 📖 详细使用说明 ### 1️⃣ IP定位 (ip) 获取用户当前所在城市。 ```bash python3 gaode_map.py ip --key 你的KEY ``` **输出示例**： ``` Your location: 四川省 成都市 City code: 510100 ``` --- ### 2️⃣ 地理编码 (geo) 将地址转换为经纬度坐标。 ```bash # 基本用法 python3 gaode_map.py geo --key 你的KEY --address "成都市天府广场" # 指定城市（更精确） python3 gaode_map.py geo --key 你的KEY --address "春熙路" --city 成都 ``` **输出示例**： ``` Address: 四川省成都市锦江区春熙路 Location: 104.085329,30.658137 ``` --- ### 3️⃣ 逆地理编码 (regeo) 将经纬度坐标转换为地址。 ```bash python3 gaode_map.py regeo --key 你的KEY --location "104.085329,30.658137" ``` --- ### 4️⃣ POI 搜索 (poi) 搜索附近的商家、服务设施。 #### 按城市关键字搜索 ```bash python3 gaode_map.py poi --key 你的KEY --city 成都 --keyword "火锅" ``` #### 周边搜索（指定位置附近） ```bash python3 gaode_map.py poi --key 你的KEY --location "104.085,30.658" --radius 3000 --keyword "餐厅" ``` **输出示例**： ``` Found 21 results: 1. 蜀九香火锅(春熙路店) Address: 锦江区春熙路南段8号 Location: 104.085123,30.657891 Phone: 028-86612345 ``` #### 周边搜索（带距离） ```bash # 搜索附近POI并显示每个结果到起点的实际驾驶距离 python3 gaode_map.py poi --location "104.069,30.229" --radius 2000 --keyword "酒店" --with-distance ``` **输出示例**： ``` Found 10 results: 1. 乐游酒店 | 地址：视高街道xxx | 距离：313米 2. 橙苇公寓 | 地址：xxx | 距离：853米 ... ``` --- ### 5️⃣ 路径规划 (route) 规划出行路线。 #### 驾车路线 ```bash python3 gaode_map.py route --key 你的KEY \ --origin "104.065,30.657" \ --destination "104.085,30.675" \ --mode driving ``` #### 步行路线 ```bash python3 gaode_map.py route --key 你的KEY \ --origin "104.065,30.657" \ --destination "104.085,30.675" \ --mode walking ``` #### 公交/地铁路线 ```bash python3 gaode_map.py route --key 你的KEY \ --origin "104.065,30.657" \ --destination "104.085,30.675" \ --mode transit \ --city 成都 ``` **输出示例**： ``` Distance: 3418 meters Duration: 828 seconds (约14分钟) Steps (12): 1. 向南行驶23米右转 2. 向西行驶46米左转 ... ``` --- ### 6️⃣ 天气查询 (weather) 查询天气信息。 #### 当前天气 ```bash python3 gaode_map.py weather --key 你的KEY --city 成都 ``` #### 天气预报（包含未来几天） ```bash python3 gaode_map.py weather --key 你的KEY --city 成都 --forecast ``` **输出示例**： ``` City: 成都市 Weather: 阴 Temperature: 15°C Wind: 北风 ≤3级 Humidity: 79% Forecast: 2026-03-12: 阴/多云, 12°C ~ 18°C 2026-03-13: 小雨/阴, 10°C ~ 15°C ``` --- ### 7️⃣ 实时路况 (traffic) 查询道路交通状况。 #### 城市整体路况 ```bash python3 gaode_map.py traffic --key 你的KEY --city 成都 ``` #### 特定道路路况 ```bash python3 gaode_map.py traffic --key 你的KEY --city 成都 --road "天府大道" ``` --- ### 8️⃣ 输入提示 (tips) 智能自动补全，输入关键词时给出建议。 ```bash python3 gaode_map.py tips --key 你的KEY --keyword "春熙" --city 成都 ``` --- ## 📊 JSON 输出 所有命令都支持 `--json` 参数，输出 JSON 格式，方便程序处理： ```bash python3 gaode_map.py weather --key 你的KEY --city 成都 --json ``` --- ## ⚠️ 注意事项 1. **API Key 必需**：每个用户需要自己申请高德 API Key 2. **调用限制**：免费版每天 2000 次，大部分场景足够 3. **坐标格式**：经度在前，纬度在后（如 `104.065,30.657`，不是 `30.657,104.065`） 4. **城市参数**：部分 API 需要城市名称或城市代码 --- ## 🔧 常见问题 **Q: 为什么提示 "SERVICE_NOT_AVAILABLE"？** A: 部分 API（如输入提示）可能需要企业认证，个人免费版可能无法使用。 **Q: 如何获取经纬度？** A: 使用 `geo` 命令，将地址转换为坐标。 **Q: 路径规划支持哪些方式？** A: 驾车(driving)、步行(walking)、公交(transit)三种方式。 --- ## 📝 更新日志 - **v2.1.0**: 新增 `--with-distance` 参数，POI搜索时显示每个结果的距离 - **v2.0.0**: 新增路径规划、天气查询、实时路况、输入提示功能 - **v1.0.0**: 初始版本，包含 IP定位、地理编码、POI搜索 --- ## 📞 支持 - 高德开放平台：https://lbs.amap.com/ - OpenClaw 社区：https://clawd.org.cn/ --- ## ⚠️ 免责声明 **本技能仅供学习交流使用，免费提供，不收取任何费用。** 1. **数据准确性**：本工具依赖高德开放平台 API，返回的数据由高德提供，我们不对数据的准确性、完整性、及时性做任何保证。 2. **使用风险**：因使用本技能导致的任何直接或间接损失（包括但不限于商业损失、数据丢失、时间损失），我们不承担任何责任。 3. **API 稳定性**：高德 API 可能存在服务不稳定、调用限制、接口变更等情况，可能导致功能不可用。 4. **用户责任**：用户需自行确保 API Key 的安全妥善使用，遵守高德开放平台的服务条款。 5. **无担保**：本技能按「原样」提供，不提供任何明示或暗示的担保。 **使用本技能即表示您理解并同意上述免责声明。**

path: _meta.json
content: {
  "owner": "mahongting",
  "slug": "amap-search-skill",
  "displayName": "高德地图全功能工具 - Gaode Map All-in-One",
  "latest": {
    "version": "2.0.0",
    "publishedAt": 1770585600000,
    "commit": "https://github.com/mahongting/skills/commit/main"
  },
  "history": []
}


path: scripts/gaode_map.py
content: #!/usr/bin/env python3
"""
Gaode Map All-in-One Search Script
Usage: python gaode_map.py <command> [options]

Commands:
- ip        : IP location - get user location
- geo       : Geocoding - address to coordinates
- poi       : POI search (keyword, nearby, city)
- route     : Route planning (driving, walking, transit)
- weather   : Weather query
- bus       : Bus/Transit search
- traffic   : Traffic status
- tip       : Input tips (autocomplete)
"""

import argparse
import json
import math
import os
import time
import urllib.error
import urllib.parse
import urllib.request
import sys
from functools import wraps
from typing import Any, Callable, Dict, List, Optional, TypeVar

# Type alias
F = TypeVar('F', bound=Callable[..., Any])


# ============ API Key Helper ============
def get_api_key() -> Optional[str]:
    """获取API Key，优先使用环境变量
    
    Returns:
        API Key 字符串，如果环境变量未设置则返回 None
    """
    env_key = os.getenv("AMAP_API_KEY")
    if env_key:
        return env_key
    return None


# ============ Custom Exception ============
class APIError(Exception):
    """API Error Exception"""
    pass


# ============ Retry Decorator ============
def retry(max_attempts: int = 3, delay: float = 1.0, backoff: float = 2.0) -> Callable[[F], F]:
    """
    Retry decorator with exponential backoff
    
    Args:
        max_attempts: Maximum number of retry attempts
        delay: Initial delay between retries in seconds
        backoff: Multiplier for delay after each retry
    """
    def decorator(func: F) -> F:
        @wraps(func)
        def wrapper(*args: Any, **kwargs: Any) -> Any:
            attempt_delay = delay
            last_exception: Exception = APIError("Unknown error")
            
            for attempt in range(1, max_attempts + 1):
                try:
                    return func(*args, **kwargs)
                except APIError as e:
                    last_exception = e
                    if attempt < max_attempts:
                        time.sleep(attempt_delay)
                        attempt_delay *= backoff
                    else:
                        raise last_exception
                    
            raise last_exception
        return wrapper  # type: ignore
    return decorator


# ============ Parameter Validation ============
def validate_coords(loc: str) -> bool:
    """
    Validate coordinate format (lon,lat)
    
    Args:
        loc: Coordinate string in format "lon,lat"
        
    Returns:
        True if valid, False otherwise
    """
    try:
        lon, lat = map(float, loc.split(","))
        return -180 <= lon <= 180 and -90 <= lat <= 90
    except (ValueError, AttributeError, TypeError):
        return False


def validate_api_key(key: Optional[str]) -> bool:
    """
    Validate API key format
    
    Args:
        key: API key string
        
    Returns:
        True if valid, False otherwise
    """
    if not key or not isinstance(key, str):
        return False
    return len(key) >= 10


# ============ Common Request Layer ============
@retry(max_attempts=3, delay=1.0, backoff=2.0)
def _request(url: str, timeout: int = 10) -> Dict[str, Any]:
    """
    Common HTTP request method with retry mechanism
    
    Args:
        url: Full URL to request
        timeout: Request timeout in seconds
        
    Returns:
        JSON response as dictionary
        
    Raises:
        APIError: If request fails or API returns error status
    """
    try:
        with urllib.request.urlopen(url, timeout=timeout) as resp:
            data = json.loads(resp.read().decode("utf-8"))
            if data.get("status") != "1":
                raise APIError(data.get("info", "API error"))
            return data
    except urllib.error.URLError as e:
        raise APIError(f"Network error: {e}")
    except json.JSONDecodeError as e:
        raise APIError(f"JSON decode error: {e}")


# ============ IP Location ============
@retry()
def ip_location(key: str, ip: Optional[str] = None) -> Dict[str, Any]:
    """IP location - Get user's current city"""
    if not validate_api_key(key):
        raise APIError("Invalid API key")
    
    base_url = "https://restapi.amap.com/v3/ip"
    params: Dict[str, Any] = {"key": key, "output": "json"}
    if ip:
        params["ip"] = ip
    url = f"{base_url}?{urllib.parse.urlencode(params)}"
    return _request(url)


# ============ Geocoding ============
@retry()
def geocode(key: str, address: str, city: Optional[str] = None) -> Dict[str, Any]:
    """Geocoding - Address to coordinates"""
    if not validate_api_key(key):
        raise APIError("Invalid API key")
    if not address:
        raise APIError("Address is required")
    
    base_url = "https://restapi.amap.com/v3/geocode/geo"
    params: Dict[str, Any] = {"key": key, "address": address, "output": "json"}
    if city:
        params["city"] = city
    url = f"{base_url}?{urllib.parse.urlencode(params)}"
    return _request(url)


@retry()
def regeo(key: str, location: str) -> Dict[str, Any]:
    """Reverse geocoding - Coordinates to address"""
    if not validate_api_key(key):
        raise APIError("Invalid API key")
    if not validate_coords(location):
        raise APIError(f"Invalid coordinates format: {location}. Expected format: lon,lat")
    
    base_url = "https://restapi.amap.com/v3/geocode/regeo"
    params: Dict[str, Any] = {"key": key, "location": location, "output": "json"}
    url = f"{base_url}?{urllib.parse.urlencode(params)}"
    return _request(url)


# ============ POI Search ============
@retry()
def search_poi(
    key: str,
    keyword: str,
    city: Optional[str] = None,
    citylimit: bool = False,
    page: int = 1,
    offset: int = 20
) -> Dict[str, Any]:
    """Keyword search POI"""
    if not validate_api_key(key):
        raise APIError("Invalid API key")
    if not keyword:
        raise APIError("Keyword is required")
    
    base_url = "https://restapi.amap.com/v3/place/text"
    params: Dict[str, Any] = {
        "key": key,
        "keywords": keyword,
        "offset": offset,
        "page": page,
        "extensions": "all",
        "output": "json"
    }
    if city:
        params["city"] = city
    if citylimit:
        params["citylimit"] = "true"
    url = f"{base_url}?{urllib.parse.urlencode(params)}"
    return _request(url)


@retry()
def search_nearby(
    key: str,
    location: str,
    radius: int,
    keyword: str,
    page: int = 1,
    offset: int = 20
) -> Dict[str, Any]:
    """Nearby search POI"""
    if not validate_api_key(key):
        raise APIError("Invalid API key")
    if not validate_coords(location):
        raise APIError(f"Invalid coordinates format: {location}. Expected format: lon,lat")
    if radius <= 0 or radius > 50000:
        raise APIError("Radius must be between 1 and 50000 meters")
    if not keyword:
        raise APIError("Keyword is required")
    
    base_url = "https://restapi.amap.com/v3/place/around"
    params: Dict[str, Any] = {
        "key": key,
        "location": location,
        "radius": radius,
        "keywords": keyword,
        "offset": offset,
        "page": page,
        "extensions": "all",
        "output": "json"
    }
    url = f"{base_url}?{urllib.parse.urlencode(params)}"
    return _request(url)


# ============ Route Planning ============
@retry()
def route_driving(
    key: str,
    origin: str,
    destination: str,
    strategy: int = 5
) -> Dict[str, Any]:
    """Driving route planning"""
    if not validate_api_key(key):
        raise APIError("Invalid API key")
    if not validate_coords(origin):
        raise APIError(f"Invalid origin coordinates: {origin}. Expected format: lon,lat")
    if not validate_coords(destination):
        raise APIError(f"Invalid destination coordinates: {destination}. Expected format: lon,lat")
    if strategy < 1 or strategy > 10:
        raise APIError("Strategy must be between 1 and 10")
    
    base_url = "https://restapi.amap.com/v3/direction/driving"
    params: Dict[str, Any] = {
        "key": key,
        "origin": origin,
        "destination": destination,
        "strategy": strategy,
        "output": "json"
    }
    url = f"{base_url}?{urllib.parse.urlencode(params)}"
    return _request(url)


@retry()
def route_walking(key: str, origin: str, destination: str) -> Dict[str, Any]:
    """Walking route planning"""
    if not validate_api_key(key):
        raise APIError("Invalid API key")
    if not validate_coords(origin):
        raise APIError(f"Invalid origin coordinates: {origin}. Expected format: lon,lat")
    if not validate_coords(destination):
        raise APIError(f"Invalid destination coordinates: {destination}. Expected format: lon,lat")
    
    base_url = "https://restapi.amap.com/v3/direction/walking"
    params: Dict[str, Any] = {
        "key": key,
        "origin": origin,
        "destination": destination,
        "output": "json"
    }
    url = f"{base_url}?{urllib.parse.urlencode(params)}"
    return _request(url)


@retry()
def route_transit(
    key: str,
    origin: str,
    destination: str,
    city: str,
    strategy: int = 0
) -> Dict[str, Any]:
    """Transit/bus route planning"""
    if not validate_api_key(key):
        raise APIError("Invalid API key")
    if not validate_coords(origin):
        raise APIError(f"Invalid origin coordinates: {origin}. Expected format: lon,lat")
    if not validate_coords(destination):
        raise APIError(f"Invalid destination coordinates: {destination}. Expected format: lon,lat")
    if not city:
        raise APIError("City is required for transit mode")
    if strategy < 0 or strategy > 2:
        raise APIError("Strategy must be between 0 and 2")
    
    base_url = "https://restapi.amap.com/v3/direction/transit"
    params: Dict[str, Any] = {
        "key": key,
        "origin": origin,
        "destination": destination,
        "city": city,
        "strategy": strategy,
        "output": "json"
    }
    url = f"{base_url}?{urllib.parse.urlencode(params)}"
    return _request(url, timeout=15)


# ============ Weather ============
@retry()
def weather(key: str, city: str, extensions: str = "base") -> Dict[str, Any]:
    """Weather query"""
    if not validate_api_key(key):
        raise APIError("Invalid API key")
    if not city:
        raise APIError("City is required")
    if extensions not in ("base", "all"):
        raise APIError("Extensions must be 'base' or 'all'")
    
    base_url = "https://restapi.amap.com/v3/weather/weatherInfo"
    params: Dict[str, Any] = {
        "key": key,
        "city": city,
        "extensions": extensions,
        "output": "json"
    }
    url = f"{base_url}?{urllib.parse.urlencode(params)}"
    return _request(url)


# ============ Traffic ============
@retry()
def traffic(key: str, city: str, road: Optional[str] = None) -> Dict[str, Any]:
    """Traffic status query"""
    if not validate_api_key(key):
        raise APIError("Invalid API key")
    if not city:
        raise APIError("City is required")
    
    base_url = "https://restapi.amap.com/v3/traffic/status/road"
    params: Dict[str, Any] = {"key": key, "city": city, "output": "json"}
    if road:
        params["roadname"] = road
    url = f"{base_url}?{urllib.parse.urlencode(params)}"
    return _request(url)


# ============ Input Tips ============
@retry()
def input_tips(key: str, keyword: str, city: Optional[str] = None) -> Dict[str, Any]:
    """Input tips (autocomplete)"""
    if not validate_api_key(key):
        raise APIError("Invalid API key")
    if not keyword:
        raise APIError("Keyword is required")
    
    base_url = "https://restapi.amap.com/v3/assistant/tips"
    params: Dict[str, Any] = {"key": key, "keywords": keyword, "output": "json"}
    if city:
        params["city"] = city
    url = f"{base_url}?{urllib.parse.urlencode(params)}"
    return _request(url)


# ============ Distance Calculation ============
def calculate_distance(lat1: float, lon1: float, lat2: float, lon2: float) -> float:
    """
    Calculate distance between two points using Haversine formula
    
    Args:
        lat1: Latitude of first point
        lon1: Longitude of first point
        lat2: Latitude of second point
        lon2: Longitude of second point
        
    Returns:
        Distance in meters
    """
    R = 6371000
    phi1, phi2 = math.radians(lat1), math.radians(lat2)
    delta_phi = math.radians(lat2 - lat1)
    delta_lambda = math.radians(lon2 - lon1)
    a = math.sin(delta_phi/2)**2 + math.cos(phi1)*math.cos(phi2)*math.sin(delta_lambda/2)**2
    c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a))
    return R * c


@retry()
def route_distance(key: str, origin: str, destination: str) -> int:
    """
    Get driving distance between two points using route planning API
    
    Args:
        key: API key
        origin: Origin coordinates (lon,lat)
        destination: Destination coordinates (lon,lat)
        
    Returns:
        Distance in meters, or -1 if failed
    """
    if not validate_api_key(key):
        raise APIError("Invalid API key")
    if not validate_coords(origin):
        raise APIError(f"Invalid origin coordinates: {origin}")
    if not validate_coords(destination):
        raise APIError(f"Invalid destination coordinates: {destination}")
    
    base_url = "https://restapi.amap.com/v3/direction/driving"
    params: Dict[str, Any] = {
        "key": key,
        "origin": origin,
        "destination": destination,
        "strategy": 5,  # 距离最短
        "output": "json"
    }
    url = f"{base_url}?{urllib.parse.urlencode(params)}"
    
    try:
        data = _request(url)
        paths = data.get("route", {}).get("paths", [])
        if paths:
            return int(paths[0].get("distance", 0))
    except APIError:
        pass
    return -1


# ============ Output Formatters ============
def format_output(data: Dict[str, Any], json_output: bool = False, format_type: str = "default", poi_distances: Optional[List[int]] = None) -> None:
    """Format output data"""
    if data.get("status") != "1":
        msg = f"Error: {data.get('info', 'Unknown error')}"
        if json_output:
            print(json.dumps({"error": msg}, ensure_ascii=False))
        else:
            print(msg)
        return
    
    if json_output:
        print(json.dumps(data, ensure_ascii=False, indent=2))
        return
    
    # Text format
    if format_type == "ip":
        print(f"Your location: {data.get('province', 'N/A')} {data.get('city', 'N/A')}")
        print(f"City code: {data.get('adcode', 'N/A')}")
    
    elif format_type == "geo" or format_type == "regeo":
        for gc in data.get("geocodes", [data]):
            print(f"Address: {gc.get('province','')}{gc.get('city','')}{gc.get('district','')}{gc.get('township','')}")
            print(f"Location: {gc.get('location', 'N/A')}")
    
    elif format_type == "poi":
        pois = data.get("pois", [])
        count = data.get("count", len(pois))
        print(f"Found {count} results:\n")
        for i, poi in enumerate(pois[:10], 1):
            name = poi.get('name', 'N/A')
            address = poi.get('address', 'N/A')
            
            # 如果有距离信息，显示距离
            if poi_distances and i - 1 < len(poi_distances):
                dist = poi_distances[i - 1]
                if dist >= 0:
                    print(f"{i}. {name} | 地址：{address} | 距离：{dist}米")
                else:
                    print(f"{i}. {name} | 地址：{address} | 距离：计算失败")
            else:
                print(f"{i}. {name}")
                print(f"   Address: {address}")
                print(f"   Location: {poi.get('location', 'N/A')}")
                if poi.get('tel'):
                    print(f"   Phone: {poi.get('tel')}")
    
    elif format_type == "route":
        route = data.get("route", {})
        paths = route.get("paths", [])
        if paths:
            path = paths[0]
            print(f"Distance: {path.get('distance', 'N/A')} meters")
            print(f"Duration: {path.get('duration', 'N/A')} seconds")
            print(f"Strategy: {route.get('strategy', 'N/A')}")
            steps = path.get("steps", [])
            print(f"Steps ({len(steps)}):")
            for i, step in enumerate(steps[:5], 1):
                print(f"  {i}. {step.get('instruction', 'N/A')[:80]}")
            if len(steps) > 5:
                print(f"  ... and {len(steps)-5} more steps")
    
    elif format_type == "weather":
        lives = data.get("lives", [])
        forecasts = data.get("forecasts", [])
        if lives:
            live = lives[0]
            print(f"City: {live.get('city', 'N/A')}")
            print(f"Weather: {live.get('weather', 'N/A')}")
            print(f"Temperature: {live.get('temperature', 'N/A')}°C")
            print(f"Wind: {live.get('winddirection', 'N/A')} {live.get('windpower', 'N/A')}")
            print(f"Humidity: {live.get('humidity', 'N/A')}%")
        if forecasts:
            forecast = forecasts[0]
            print(f"\nForecast:")
            for fc in forecast.get("casts", []):
                print(f"  {fc.get('date')}: {fc.get('dayWeather')} / {fc.get('nightWeather')}, {fc.get('nightTemp')}°C ~ {fc.get('dayTemp')}°C")
    
    elif format_type == "traffic":
        roads = data.get("roads", [])
        print(f"Found {len(roads)} roads with traffic info:")
        for road in roads[:5]:
            print(f"  - {road.get('name', 'N/A')}: {road.get('status', 'N/A')}")
    
    elif format_type == "tips":
        tips = data.get("tips", [])
        print(f"Found {len(tips)} suggestions:\n")
        for i, tip in enumerate(tips[:10], 1):
            print(f"{i}. {tip.get('name', 'N/A')}")
            print(f"   Address: {tip.get('address', 'N/A')}")
            print(f"   Location: {tip.get('location', 'N/A')}")
            print()
    
    else:
        print(json.dumps(data, ensure_ascii=False, indent=2))


def main() -> None:
    parser = argparse.ArgumentParser(description="Gaode Map All-in-One Tool")
    parser.add_argument("--json", action="store_true", help="Output JSON format")
    subparsers = parser.add_subparsers(dest="command", help="Commands")
    
    # IP
    ip_parser = subparsers.add_parser("ip", help="IP location")
    ip_parser.add_argument("--key", help="Gaode API Key (或设置环境变量 AMAP_API_KEY)")
    ip_parser.add_argument("--ip", help="IP address")
    
    # Geo
    geo_parser = subparsers.add_parser("geo", help="Geocoding - address to coordinates")
    geo_parser.add_argument("--key", help="Gaode API Key (或设置环境变量 AMAP_API_KEY)")
    geo_parser.add_argument("--address", required=True, help="Address")
    geo_parser.add_argument("--city", help="City")
    
    # Regeo
    regeo_parser = subparsers.add_parser("regeo", help="Reverse geocoding - coordinates to address")
    regeo_parser.add_argument("--key", help="Gaode API Key (或设置环境变量 AMAP_API_KEY)")
    regeo_parser.add_argument("--location", required=True, help="Coordinates (lon,lat)")
    
    # POI
    poi_parser = subparsers.add_parser("poi", help="POI search")
    poi_parser.add_argument("--key", help="Gaode API Key (或设置环境变量 AMAP_API_KEY)")
    poi_parser.add_argument("--keyword", required=True, help="Keyword")
    poi_parser.add_argument("--city", help="City")
    poi_parser.add_argument("--citylimit", action="store_true", help="Limit to city")
    poi_parser.add_argument("--location", help="Location (lon,lat) for nearby search or distance calculation")
    poi_parser.add_argument("--radius", type=int, default=5000, help="Radius (meters)")
    poi_parser.add_argument("--page", type=int, default=1, help="Page")
    poi_parser.add_argument("--with-distance", action="store_true", help="Calculate and display distance for each POI (requires --location)")
    
    # Route
    route_parser = subparsers.add_parser("route", help="Route planning")
    route_parser.add_argument("--key", help="Gaode API Key (或设置环境变量 AMAP_API_KEY)")
    route_parser.add_argument("--origin", required=True, help="Origin (lon,lat)")
    route_parser.add_argument("--destination", required=True, help="Destination (lon,lat)")
    route_parser.add_argument("--mode", default="driving", choices=["driving", "walking", "transit"], help="Mode")
    route_parser.add_argument("--city", help="City (for transit)")
    route_parser.add_argument("--strategy", type=int, default=5, help="Strategy")
    
    # Weather
    weather_parser = subparsers.add_parser("weather", help="Weather query")
    weather_parser.add_argument("--key", help="Gaode API Key (或设置环境变量 AMAP_API_KEY)")
    weather_parser.add_argument("--city", required=True, help="City")
    weather_parser.add_argument("--forecast", action="store_true", help="Include forecast")
    
    # Traffic
    traffic_parser = subparsers.add_parser("traffic", help="Traffic status")
    traffic_parser.add_argument("--key", help="Gaode API Key (或设置环境变量 AMAP_API_KEY)")
    traffic_parser.add_argument("--city", required=True, help="City")
    traffic_parser.add_argument("--road", help="Road name")
    
    # Tips
    tips_parser = subparsers.add_parser("tips", help="Input tips (autocomplete)")
    tips_parser.add_argument("--key", help="Gaode API Key (或设置环境变量 AMAP_API_KEY)")
    tips_parser.add_argument("--keyword", required=True, help="Keyword")
    tips_parser.add_argument("--city", help="City")
    
    args = parser.parse_args()
    
    if not args.command:
        parser.print_help()
        return
    
    # API Key 获取逻辑：优先使用环境变量，其次使用命令行参数
    if args.key is None:
        args.key = os.getenv("AMAP_API_KEY")
    
    if args.key is None:
        raise ValueError("请通过环境变量 AMAP_API_KEY 或命令行参数 --key 提供API Key")
    
    json_out = args.json
    
    try:
        if args.command == "ip":
            data = ip_location(args.key, args.ip)
            format_output(data, json_out, "ip")
        
        elif args.command == "geo":
            data = geocode(args.key, args.address, args.city)
            format_output(data, json_out, "geo")
        
        elif args.command == "regeo":
            data = regeo(args.key, args.location)
            format_output(data, json_out, "regeo")
        
        elif args.command == "poi":
            poi_distances = None
            
            if args.location:
                data = search_nearby(args.key, args.location, args.radius, args.keyword, args.page)
            else:
                data = search_poi(args.key, args.keyword, args.city, args.citylimit, args.page)
            
            # 如果启用了 --with-distance 且提供了 location，计算每个POI的距离
            if args.with_distance:
                if not args.location:
                    print("Warning: --with-distance requires --location, ignoring distance calculation")
                    format_output(data, json_out, "poi")
                else:
                    origin = args.location
                    pois = data.get("pois", [])[:10]  # 最多处理前10个
                    poi_distances = []
                    
                    print("Calculating distances...")
                    for poi in pois:
                        poi_loc = poi.get("location", "")
                        if poi_loc:
                            # 调用路径规划API获取驾驶距离
                            dist = route_distance(args.key, origin, poi_loc)
                            poi_distances.append(dist)
                        else:
                            poi_distances.append(-1)
                    
                    format_output(data, json_out, "poi", poi_distances)
                    return
            
            format_output(data, json_out, "poi")
        
        elif args.command == "route":
            if args.mode == "driving":
                data = route_driving(args.key, args.origin, args.destination, args.strategy)
                format_output(data, json_out, "route")
            elif args.mode == "walking":
                data = route_walking(args.key, args.origin, args.destination)
                format_output(data, json_out, "route")
            elif args.mode == "transit":
                if not args.city:
                    print("Error: --city is required for transit mode")
                    return
                data = route_transit(args.key, args.origin, args.destination, args.city, args.strategy)
                format_output(data, json_out, "route")
        
        elif args.command == "weather":
            ext = "all" if args.forecast else "base"
            data = weather(args.key, args.city, ext)
            format_output(data, json_out, "weather")
        
        elif args.command == "traffic":
            data = traffic(args.key, args.city, args.road)
            format_output(data, json_out, "traffic")
        
        elif args.command == "tips":
            data = input_tips(args.key, args.keyword, args.city)
            format_output(data, json_out, "tips")
            
    except APIError as e:
        print(f"API Error: {e}")
        sys.exit(1)
    except Exception as e:
        print(f"Error: {e}")
        sys.exit(1)


if __name__ == "__main__":
    main()


path: scripts/poi_search.py
content: #!/usr/bin/env python3
"""
Gaode Map POI Search Script
Usage: python poi_search.py <command> [options]
"""

import argparse
import json
import math
import urllib.parse
import urllib.request
import sys


def ip_location(key, ip=None):
    """IP location - Get user's current city"""
    base_url = "https://restapi.amap.com/v3/ip"
    
    params = {
        "key": key,
        "output": "json"
    }
    
    if ip:
        params["ip"] = ip
    
    url = f"{base_url}?{urllib.parse.urlencode(params)}"
    
    try:
        with urllib.request.urlopen(url, timeout=10) as response:
            data = json.loads(response.read().decode("utf-8"))
            return data
    except Exception as e:
        return {"status": "0", "info": str(e)}


def search_poi(key, keyword, city=None, location=None, radius=5000, page=1, offset=20):
    """Keyword search POI"""
    base_url = "https://restapi.amap.com/v3/place/text"
    
    params = {
        "key": key,
        "keywords": keyword,
        "offset": offset,
        "page": page,
        "extensions": "all",
        "output": "json"
    }
    
    if city:
        params["city"] = city
    
    if location:
        params["citylimit"] = "true"
    
    url = f"{base_url}?{urllib.parse.urlencode(params)}"
    
    try:
        with urllib.request.urlopen(url, timeout=10) as response:
            data = json.loads(response.read().decode("utf-8"))
            return data
    except Exception as e:
        return {"status": "0", "info": str(e)}


def search_nearby(key, location, radius, keyword, page=1, offset=20):
    """Nearby search POI"""
    base_url = "https://restapi.amap.com/v3/place/around"
    
    params = {
        "key": key,
        "location": location,
        "radius": radius,
        "keywords": keyword,
        "offset": offset,
        "page": page,
        "extensions": "all",
        "output": "json"
    }
    
    url = f"{base_url}?{urllib.parse.urlencode(params)}"
    
    try:
        with urllib.request.urlopen(url, timeout=10) as response:
            data = json.loads(response.read().decode("utf-8"))
            return data
    except Exception as e:
        return {"status": "0", "info": str(e)}


def geocode(key, address, city=None):
    """Geocoding - Address to coordinates"""
    base_url = "https://restapi.amap.com/v3/geocode/geo"
    
    params = {
        "key": key,
        "address": address,
        "output": "json"
    }
    
    if city:
        params["city"] = city
    
    url = f"{base_url}?{urllib.parse.urlencode(params)}"
    
    try:
        with urllib.request.urlopen(url, timeout=10) as response:
            data = json.loads(response.read().decode("utf-8"))
            return data
    except Exception as e:
        return {"status": "0", "info": str(e)}


def calculate_distance(lat1, lon1, lat2, lon2):
    """Calculate distance between two points (in meters)"""
    R = 6371000  # Earth radius in meters
    phi1 = math.radians(lat1)
    phi2 = math.radians(lat2)
    delta_phi = math.radians(lat2 - lat1)
    delta_lambda = math.radians(lon2 - lon1)
    
    a = math.sin(delta_phi/2) * math.sin(delta_phi/2) + \
        math.cos(phi1) * math.cos(phi2) * \
        math.sin(delta_lambda/2) * math.sin(delta_lambda/2)
    c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a))
    
    return R * c


def format_ip_results(data, json_output=False):
    """Format IP location results"""
    if data.get("status") != "1":
        msg = f"Query failed: {data.get('info', 'Unknown error')}"
        if json_output:
            return {"error": msg}
        print(msg)
        return None
    
    province = data.get("province", "Unknown")
    city = data.get("city", "Unknown")
    adcode = data.get("adcode", "")
    
    result = {"province": province, "city": city, "adcode": adcode}
    
    if json_output:
        print(json.dumps(result, ensure_ascii=False, indent=2))
    else:
        print(f"Your location: {province} {city}")
        print(f"City code: {adcode}")
        print()
    
    return result


def format_poi_results(data, user_location=None, json_output=False):
    """Format POI search results"""
    if data.get("status") != "1":
        msg = f"Search failed: {data.get('info', 'Unknown error')}"
        if json_output:
            print(json.dumps({"error": msg}, ensure_ascii=False))
        else:
            print(msg)
        return
    
    pois = data.get("pois", [])
    if not pois:
        msg = "No results found"
        if json_output:
            print(json.dumps({"results": [], "count": 0}, ensure_ascii=False))
        else:
            print(msg)
        return
    
    count = data.get("count", len(pois))
    
    results = []
    for poi in pois[:10]:
        name = poi.get("name", "")
        address = poi.get("address", "")
        location = poi.get("location", "")
        telephone = poi.get("tel", "")
        province = poi.get("province", "")
        city = poi.get("city", "")
        district = poi.get("district", "")
        
        # Calculate distance (for keyword search, need manual calculation)
        distance = poi.get("distance", None)
        if not distance and user_location and location:
            try:
                user_lat, user_lon = map(float, user_location.split(","))
                poi_lat, poi_lon = map(float, location.split(","))
                dist = calculate_distance(user_lat, user_lon, poi_lat, poi_lon)
                distance = int(dist)
            except:
                pass
        
        # Format distance
        distance_str = ""
        if distance:
            try:
                d = int(distance)
                if d >= 1000:
                    distance_str = f"{d/1000:.1f}km"
                else:
                    distance_str = f"{d}m"
            except:
                distance_str = str(distance)
        
        item = {
            "name": name,
            "address": f"{province}{city}{district}{address}" if address else f"{province}{city}{district}",
            "location": location,
            "telephone": telephone
        }
        if distance_str:
            item["distance"] = distance_str
        
        results.append(item)
    
    output = {"results": results, "count": count}
    
    if json_output:
        print(json.dumps(output, ensure_ascii=False, indent=2))
    else:
        print(f"Found {count} results:\n")
        for i, item in enumerate(results, 1):
            print(f"{i}. {item['name']}")
            print(f"   Address: {item['address']}")
            if item.get("distance"):
                print(f"   Distance: {item['distance']}")
            if item["telephone"]:
                print(f"   Phone: {item['telephone']}")
            print()


def format_geocode_results(data, json_output=False):
    """Format geocoding results"""
    if data.get("status") != "1":
        msg = f"Query failed: {data.get('info', 'Unknown error')}"
        if json_output:
            print(json.dumps({"error": msg}, ensure_ascii=False))
        else:
            print(msg)
        return
    
    geocodes = data.get("geocodes", [])
    if not geocodes:
        msg = "No coordinates found for this address"
        if json_output:
            print(json.dumps({"error": msg}, ensure_ascii=False))
        else:
            print(msg)
        return
    
    results = []
    for gc in geocodes:
        results.append({
            "address": f"{gc.get('province', '')}{gc.get('city', '')}{gc.get('district', '')}{gc.get('township', '')}",
            "location": gc.get("location", ""),
            "level": gc.get("level", "")
        })
    
    if json_output:
        print(json.dumps({"results": results}, ensure_ascii=False, indent=2))
    else:
        for item in results:
            print(f"Address: {item['address']}")
            print(f"Coordinates: {item['location']}")
            print(f"Level: {item['level']}")
            print()


def main():
    parser = argparse.ArgumentParser(description="Gaode Map POI Search Tool")
    parser.add_argument("--json", action="store_true", help="Output JSON format")
    subparsers = parser.add_subparsers(dest="command", help="Subcommands")
    
    # ip subcommand
    ip_parser = subparsers.add_parser("ip", help="IP location - Get current location")
    ip_parser.add_argument("--key", required=True, help="Gaode API Key")
    ip_parser.add_argument("--ip", help="Specify IP, auto-detect if not provided")
    
    # poi subcommand
    poi_parser = subparsers.add_parser("poi", help="Keyword search POI")
    poi_parser.add_argument("--key", required=True, help="Gaode API Key")
    poi_parser.add_argument("--keyword", required=True, help="Search keyword")
    poi_parser.add_argument("--city", help="City name")
    poi_parser.add_argument("--location", help="Coordinates (lat,lon) for distance calculation")
    poi_parser.add_argument("--radius", type=int, default=5000, help="Search radius (meters)")
    poi_parser.add_argument("--page", type=int, default=1, help="Page number")
    poi_parser.add_argument("--offset", type=int, default=20, help="Results per page")
    
    # geo subcommand
    geo_parser = subparsers.add_parser("geo", help="Geocoding - Address to coordinates")
    geo_parser.add_argument("--key", required=True, help="Gaode API Key")
    geo_parser.add_argument("--address", required=True, help="Address")
    geo_parser.add_argument("--city", help="City name")
    
    args = parser.parse_args()
    
    # Common parameter
    json_output = args.json
    
    if args.command == "ip":
        data = ip_location(args.key, args.ip)
        format_ip_results(data, json_output)
        
    elif args.command == "poi":
        if args.location:
            # Nearby search
            data = search_nearby(args.key, args.location, args.radius, args.keyword, args.page, args.offset)
        else:
            # Keyword search
            data = search_poi(args.key, args.keyword, args.city, args.location, args.radius, args.page, args.offset)
        format_poi_results(data, args.location, json_output)
        
    elif args.command == "geo":
        data = geocode(args.key, args.address, args.city)
        format_geocode_results(data, json_output)
        
    else:
        parser.print_help()


if __name__ == "__main__":
    main()


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (MEDIUM)