返回 Skill 列表
extension
分类: 其它需要 API Key

travel-map-mobile

旅游攻略地图生成技能。根据用户提供的攻略内容,生成交互式旅游路线地图。支持路线切换联动、分类筛选、地图缩放拖拽、移动端优化。注意:本技能仅生成地图可视化,不提供攻略规划内容。

person作者: user_ae76a857hubcommunity

旅游攻略地图技能

技能定位

本技能不提供攻略实际内容,仅依照用户提供的攻略内容生成地图路线图,方便用户查看。

如果用户尚未准备好攻略内容,请引导用户使用以下模板整理信息。

输出要求

文件要求

  • 最终输出:必须是单独一个 HTML 文件,可直接在浏览器中打开
  • 实现过程:可以拆分成多个文件(CSS、JS 模块),但最终必须合并为单个 HTML
  • 内联资源:所有样式和脚本必须内联,不依赖外部文件

移动端适配

  • 必须适配移动端浏览器打开效果
  • 支持触摸手势(滑动、点击、拖拽)
  • 适配刘海屏和安全区域
  • 响应式布局(375px - 768px)

交互效果

路线切换联动

  • 切换路线(A线/B线/全部)时,地图上的标记点同步显示/隐藏
  • 切换路线时,路线连线同步显示/隐藏
  • 切换路线时,底部景点列表同步更新
  • 非当前路线的景点标记显示为半透明或隐藏

地图交互

  • 支持缩放功能(+/- 按钮)
  • 支持拖拽功能(鼠标/触摸)
  • 缩放时 POI 标记大小动态调整
  • 底部面板收缩时地图区域自动扩展

触发条件

当用户满足以下条件时使用此技能:

  • 用户已有攻略内容,需要生成地图展示
  • 用户提供了景点列表和路线安排
  • 用户需要将文字攻略转换为可视化地图

攻略信息模板

如果用户尚未准备好攻略内容,请引导用户按以下模板提供信息:

任务目标:帮我整理一份 [时间段,如:两天一夜] 的 [目的地城市] 游玩攻略。

关键约束:
- 交通起点:我从 [具体的火车站/机场] 出发。
- 住宿/落脚点:我需要先去 [具体的酒店名或小区名] 放行李/入住。
- 人员配置:随行有 [如:3岁宝宝/老人/宠物],需要考虑体力分配和趣味性。

输出要求:
- 地图版思维:请按地理位置和动线(不走回头路)来规划,不要只罗列景点。
- 结构化展示:使用 Day 1 / Day 2 分块,包含"路线逻辑"、"景点特色"、"带娃/避坑提示"。
- 美食地图:单独列出景点附近的具体美食推荐。
- 总结表格:最后给出一个交通与地图总结表。

技能协作

本技能在执行过程中可能调用以下技能:

地图数据获取

amap-lbs-skill - 高德地图 LBS 服务

  • 用途:POI 搜索、获取景点坐标
  • 调用时机:从用户提供的景点名称获取精确坐标
  • 返回数据:景点名称、地址、GCJ-02 坐标

页面设计与编码

UI/UX Pro Max - UI/UX 设计

  • 用途:设计移动端友好的界面布局
  • 调用时机:确定页面结构和交互设计
  • 输出:界面设计规范、组件设计

HTML Designer - HTML 设计

  • 用途:HTML 结构设计和样式设计
  • 调用时机:编写页面 HTML 结构
  • 输出:语义化 HTML 结构

HTML Coder - HTML 编码

  • 用途:编写高质量 HTML/CSS/JS 代码
  • 调用时机:实现交互逻辑和样式
  • 输出:完整的 HTML 代码

前置依赖

必需:高德地图 API Key

本技能执行过程中需要用户提供高德地图 API Key,用于:

  • POI 搜索:获取景点精确坐标
  • 静态地图:生成地图背景图片

申请方式

  1. 访问 https://lbs.amap.com/ 注册账号
  2. 创建应用,开通以下服务:
    • Web服务:用于 POI 搜索
    • 静态地图:用于生成地图图片
  3. 获取 Key 后提供给本技能

安全保证

  • API Key 仅在技能执行过程中使用,用于获取地图数据
  • 最终生成的 HTML 文件中不包含 API Key
  • 生成的 HTML 文件完全离线可用,无需任何 API 请求
  • 静态地图图片以 Base64 格式内嵌到 HTML 中

其他依赖

  • 景点坐标数据:需使用 GCJ-02 坐标系(火星坐标系),与高德地图一致
  • 输出路径:确认用户指定的文件保存位置

使用流程

1. 收集攻略内容

确认用户已提供完整的攻略信息,包括:

  • 目的地城市和时间安排
  • 景点列表(名称、地址或坐标)
  • 路线安排(Day 1 / Day 2 等)
  • 美食推荐(可选)
  • 交通规划(可选)

如果用户未提供攻略内容,请引导用户使用"攻略信息模板"整理信息。

2. 获取 API Key

如果用户未提供 API Key,必须先请求用户提供

生成地图需要使用高德地图 API,请提供您的高德地图 API Key。

申请方式:
1. 访问 https://lbs.amap.com/ 注册账号
2. 创建应用,开通"Web服务"和"静态地图"服务
3. 复制 Key 提供给我

注意:API Key 仅在本次生成过程中使用,不会写入最终文件。

3. 获取景点坐标

使用用户提供的 API Key,调用高德 POI 搜索 API 获取景点坐标:

curl "https://restapi.amap.com/v3/place/text?keywords={景点名称}&city={城市}&key={API_KEY}"

对于每个景点,记录:

  • 景点 ID(唯一标识)
  • 景点名称
  • emoji 图标
  • 分类(station/lodging/nature/spot/food/transport)
  • 所属路线(A/B/C)
  • 坐标(lng, lat)
  • 地址
  • 门票信息
  • 开放时间
  • 简介
  • 攻略提示
  • 标签

4. 生成静态地图图片

重要:使用单张覆盖范围最大的地图图片,通过 CSS scale 实现缩放

推荐方案

  • 生成 一张 zoom=11 的地图图片(覆盖最大范围)
  • 通过 CSS transform: scale() 实现缩放效果
  • 这样拖动时不会出现空白区域

生成地图图片

# 生成覆盖范围最大的地图图片(zoom=11)
curl -s -o "map.png" \
  "https://restapi.amap.com/v3/staticmap?location={中心经度},{中心纬度}&zoom=11&size=1024*1024&scale=2&key=YOUR_KEY"

# 转换为 Base64
base64 -w 0 "map.png" > "map_base64.txt"

坐标范围计算

根据地图中心点和 zoom 级别计算坐标范围:

// 地图覆盖的地理范围(zoom 11 的范围,覆盖最大)
const MAP_BOUNDS = {
  minLng: 116.90,  // 最小经度
  maxLng: 117.40,  // 最大经度
  minLat: 38.90,   // 最小纬度
  maxLat: 39.40    // 最大纬度
};

5. 设计页面结构

设计要点

  • 顶部:城市标题 + 路线切换按钮
  • 中部:静态地图图片 + HTML 标记覆盖层 + 缩放控制按钮(左侧)
  • 底部:可展开的景点列表面板
  • 悬浮:快捷操作按钮(右侧)

交互设计

  • 路线切换:分段控件(全部/A线/B线)
  • 分类筛选:横向滚动的胶囊按钮(全部/交通/站点/住宿/公园/景点/美食)
  • 景点详情:底部弹出弹窗
  • 面板操作:上拉展开、下拉收起
  • 地图缩放:+/- 按钮(左侧),支持拖拽
  • 地图拖拽:鼠标/触摸拖动

6. 生成 HTML 文件

核心实现要点

6.1 单张地图 + CSS Scale 缩放

// 地图配置 - 使用单张覆盖范围最大的地图图片
const MAP_IMAGE = 'data:image/png;base64,{BASE64_MAP}';

// 地图覆盖的地理范围
const MAP_BOUNDS = {
  minLng: 116.90,
  maxLng: 117.40,
  minLat: 38.90,
  maxLat: 39.40
};

// 缩放级别配置
const MIN_ZOOM = 10;
const MAX_ZOOM = 16;

// 缩放级别对应的 scale 倍数(更平滑的递增)
const ZOOM_SCALE = {
  10: 0.8,
  11: 1,
  12: 1.25,
  13: 1.5,
  14: 2,
  15: 2.5,
  16: 3
};

// 缩放级别对应的 POI marker 大小(放大时变小)
const MARKER_SIZE = {
  10: 42,
  11: 38,
  12: 34,
  13: 30,
  14: 26,
  15: 22,
  16: 18
};

6.2 地图容器与拖拽功能

<div class="map-container" id="mapContainer">
  <div class="map-wrapper" id="mapWrapper">
    <img class="map-image" src="{BASE64_MAP}" alt="地图" id="mapImage">
    <svg class="map-routes" id="mapRoutes"></svg>
    <div class="map-markers" id="mapMarkers"></div>
  </div>
</div>
.map-container {
  position: fixed;
  top: var(--header-height);
  left: 0;
  right: 0;
  bottom: var(--bottom-height);
  overflow: hidden;
  transition: bottom 0.35s ease;
}

.map-container.expanded {
  bottom: 90px;
}

.map-wrapper {
  position: absolute;
  width: 100%;
  height: 100%;
  cursor: grab;
  transform-origin: center center;
}

.map-wrapper.dragging {
  cursor: grabbing;
}
function updateMapPosition() {
  const wrapper = document.getElementById('mapWrapper');
  const container = document.getElementById('mapContainer');
  
  const scale = ZOOM_SCALE[currentZoom];
  const dragRange = (scale - 1) / 2;
  
  const maxX = container.offsetWidth * dragRange;
  const maxY = container.offsetHeight * dragRange;
  const minX = -container.offsetWidth * dragRange;
  const minY = -container.offsetHeight * dragRange;
  
  offsetX = Math.min(maxX, Math.max(minX, offsetX));
  offsetY = Math.min(maxY, Math.max(minY, offsetY));
  
  wrapper.style.transform = `translate(${offsetX}px, ${offsetY}px) scale(${scale})`;
}

6.3 POI 标记大小动态调整

function updateMarkerSizes() {
  const size = MARKER_SIZE[currentZoom] || 32;
  const fontSize = Math.round(size * 0.45);
  
  POIS.forEach(poi => {
    const marker = markers[poi.id];
    if (marker) {
      marker.style.width = size + 'px';
      marker.style.height = size + 'px';
      
      const inner = marker.querySelector('.poi-marker-inner');
      if (inner) {
        inner.style.borderWidth = Math.max(2, Math.round(size / 12)) + 'px';
      }
      
      const icon = marker.querySelector('.icon');
      if (icon) {
        icon.style.fontSize = fontSize + 'px';
      }
    }
  });
}

6.4 缩放控制组件(左侧)

<div class="zoom-controls">
  <button class="zoom-btn" id="zoomInBtn" onclick="zoomIn()">+</button>
  <span class="zoom-level" id="zoomLevel">11</span>
  <button class="zoom-btn" id="zoomOutBtn" onclick="zoomOut()"></button>
</div>
.zoom-controls {
  position: absolute;
  left: 12px;
  top: 50%;
  transform: translateY(-50%);
  z-index: 30;
}

6.5 交通规划功能

const TRANSPORT_DATA = {
  A: {
    title: 'A线交通规划',
    subtitle: '近郊休闲路线',
    segments: [
      {
        from: '起点名称',
        to: '终点名称',
        methods: [
          { type: 'walk', text: '步行约10分钟', icon: '🚶' },
          { type: 'taxi', text: '打车约5分钟,约10元', icon: '🚕' },
          { type: 'metro', text: '地铁:xxx', icon: '🚇' },
          { type: 'bus', text: '公交:xxx', icon: '🚌' }
        ]
      }
    ],
    tips: '出行建议'
  }
};

7. 实现交互逻辑

缩放功能

function zoomIn() {
  if (currentZoom < MAX_ZOOM) {
    currentZoom++;
    updateMap();
  }
}

function zoomOut() {
  if (currentZoom > MIN_ZOOM) {
    currentZoom--;
    updateMap();
  }
}

function updateMap() {
  document.getElementById('zoomLevel').textContent = currentZoom;
  document.getElementById('zoomInBtn').disabled = (currentZoom >= MAX_ZOOM);
  document.getElementById('zoomOutBtn').disabled = (currentZoom <= MIN_ZOOM);
  
  updateMapPosition();
  updateMarkerSizes();
}

面板收缩时地图扩展

function togglePanel() {
  const panel = document.getElementById('bottomPanel');
  const mapContainer = document.getElementById('mapContainer');
  panel.classList.toggle('hidden');
  
  if (panel.classList.contains('hidden')) {
    mapContainer.classList.add('expanded');
  } else {
    mapContainer.classList.remove('expanded');
  }
}

8. 输出验证

生成后验证以下功能:

  • [ ] 单个 HTML 文件可直接在浏览器打开
  • [ ] HTML 文件中不包含 API Key
  • [ ] 地图图片已内嵌(Base64 格式)
  • [ ] 完全离线可用,无需网络请求
  • [ ] 移动端显示正常(375px 宽度)
  • [ ] 地图缩放功能正常(+/- 按钮,左侧)
  • [ ] 缩放时 POI 标记大小动态调整
  • [ ] 地图拖拽功能正常
  • [ ] 拖拽时不会出现空白区域
  • [ ] 底部面板收缩时地图区域扩展
  • [ ] 路线切换时地图标记同步显示/隐藏
  • [ ] 分类筛选联动列表和地图
  • [ ] 交通规划显示正常
  • [ ] 点击标记/卡片显示详情弹窗
  • [ ] 触摸滑动正常

分类规范

景点分类用于筛选和图标显示:

| category | 说明 | 图标 | |----------|------|------| | station | 站点(火车站/机场) | 🚉 | | transport | 交通规划 | 🚇 | | lodging | 住宿/落脚点 | 🏠 | | nature | 自然/公园 | 🌳 | | spot | 景点/打卡 | 🏰 | | food | 美食/餐饮 | 🍜 |

技术规范

  • 坐标系:GCJ-02(火星坐标系),与高德地图一致
  • 地图实现:单张静态地图图片 + CSS scale 缩放 + HTML 标记覆盖层
  • 缩放支持:7 级缩放(zoom 10-16),通过 CSS transform: scale() 实现
  • 拖拽支持:支持鼠标和触摸拖拽,动态计算拖拽范围
  • POI 标记:大小随缩放级别动态调整(放大时变小)
  • UI框架:iOS 风格,玻璃拟态设计
  • 响应式:移动端优先,支持 safe-area
  • 输出格式:单个 HTML 文件,内联所有资源
  • 离线支持:完全离线可用,无需任何网络请求

常见问题 FAQ

Q: 为什么需要提供 API Key?

A: API Key 用于在生成过程中获取景点坐标和静态地图图片。生成完成后,Key 不会被写入 HTML 文件,生成的地图完全离线可用。

Q: 生成的 HTML 文件会泄露我的 API Key 吗?

A: 不会。API Key 仅在技能执行过程中使用,用于调用高德 API 获取数据。最终生成的 HTML 文件中不包含任何 API Key,静态地图图片以 Base64 格式内嵌。

Q: 如何获取景点坐标?

A: 技能会使用您提供的 API Key 自动搜索景点坐标。如果自动搜索失败,可以使用高德地图拾取器手动获取:https://lbs.amap.com/tools/picker

Q: 生成的地图可以离线使用吗?

A: 可以。生成的 HTML 文件完全离线可用,所有资源(包括地图图片)都已内嵌,无需任何网络请求。

Q: 为什么放大地图后拖动会有空白?

A: 使用单张覆盖范围最大的地图图片 + CSS scale 缩放方案可以避免此问题。不要为每个缩放级别生成不同的图片。

Q: 缩放时 POI 标记太大遮挡地图怎么办?

A: 实现动态调整 marker 大小的功能,放大时 marker 变小,缩小时变大。

更新记忆

完成地图生成后,追加工作记忆:

## [城市名]旅游地图项目
- 输出文件: [文件名].html(单个 HTML 文件)
- 输出路径: 用户指定目录
- 景点数量: X 个
- 路线数量: X 条
- 使用技能: amap-lbs-skill, UI/UX Pro Max, HTML Designer, HTML Coder