旅游攻略地图技能
技能定位
本技能不提供攻略实际内容,仅依照用户提供的攻略内容生成地图路线图,方便用户查看。
如果用户尚未准备好攻略内容,请引导用户使用以下模板整理信息。
输出要求
文件要求
- 最终输出:必须是单独一个 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 搜索:获取景点精确坐标
- 静态地图:生成地图背景图片
申请方式:
- 访问 https://lbs.amap.com/ 注册账号
- 创建应用,开通以下服务:
- Web服务:用于 POI 搜索
- 静态地图:用于生成地图图片
- 获取 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
Scan to contact