3D 地形 map-3d
一份 spec("mode": "3d")→ 一段竖屏 3D 片子。渲染引擎是 map-motion(scripts/terrain3d.py 算镜头,scripts/runtime/t3d.js + MapLibre GL 出画面),本 skill 管流程:选路线来源 → 写 spec → 连续小段检查 → 出片。
| 片型 | 节奏(缺省) | 用在 |
|---|---|---|
路线(有 track) | 0–6s 地球俯冲 → 6–8.6s 整条路线鸟瞰、白线画出、标题 → 2.4s 压低贴到起点 → 跟随 10–26s(按长度)→ 6s 拉回全景、绕一点、总里程/爬升/最高海拔 | 徒步、登山、自驾、骑行、跑步、步道 |
光线("mode": "sun") | 0–6s 俯冲到山峰 → 从机位方向看山,14s 时间快进(关键时刻附近慢放):山体明暗跟真实太阳方位走、天空变色、太阳方向光线、顶部时钟+罗盘、底部关键时刻表到点打勾 → 拉远看机位和视线 | 日照金山、日出日落机位规划、「几点有光」 |
单点(只有 place) | 0–6s 俯冲 → 8s 绕着转(缺省 90°),底部地名卡+经纬度+海拔滚动 | 雪山、景点、营地、「我在这里」 |
光线片 spec
json{"mode": "sun", "peak": [98.6833, 28.4367], "name": "卡瓦格博", "ele": 6740, "spot": {"name": "飞来寺观景台", "at": [98.9177, 28.4114]}, "date": "2026-10-15", "event": "sunrise"}
| 字段 | 说明 |
|---|---|
peak name ele | 要拍的山(坐标或地名);ele 写公开海拔(峰顶受光时刻按它算) |
spot | 机位,可省(省了就背对太阳看受光面,没有「机位见光」一行) |
date event tz | 日期、sunrise/sunset、时区(缺省 8) |
zoom pitch bearing lapse | 看山镜头(缺省 12.6 / 70° / 机位看山的方向)、快进秒数(14) |
| 编译时打印关键时刻(天文日出、山峰受光=日照金山开始、金色褪去=太阳高度 5°、机位见光),交付时把这几行原样告诉用户,并说明:按 30 m 高程推算,云和雾算不了,误差约 ±3 分钟;机位见光受身边小山坡影响大,坐标要准。 |
准备
- map-motion 引擎(同一仓库根目录
scripts/)、uv、ffmpeg、Chromium(uv run --with playwright playwright install chromium)。高德 key 只在用地名或高德路线时需要(见 map-motion 主 skill)。 X=~/.claude/skills/map-3d/scripts/m3d.sh$X check spec.json在每个关键时刻前渲 0.6 秒连续小段并自动质检(出画、文字互压、黑块),输出文字结论;拼图照样生成(5–6 分钟)$X make spec.json 成片.mp4整片 + 预览 GIF(约 1 秒一帧:30 秒片子 15–25 分钟,放后台跑);边渲边每 0.5 秒抽检,结束只出几行摘要$X status一行看进度(别反复读日志)
流程
1. 定路线来源(按可信度)
| 用户给了什么 | track 写法 | 说明 |
|---|---|---|
| GPX / KML 转的 GeoJSON(两步路、六只脚、Strava、Keep、手表) | "track": "xx.gpx" | 最准,就是真实走过的线。WGS-84,不用转 |
| 只说了起终点,开车/骑车/城里走 | {"mode": "driving" | "bicycling" | "walking", "from": "地名", "to": "地名", "via": [...]} | 高德路线规划(GCJ → 自动转 WGS)。景区台阶路高德步行也常有(泰山红门→南天门实测可用) |
| 山野徒步,没有 GPX | {"via": [途经点…], "margin": 0.02} | 沿 OpenStreetMap 小路取最短路连起来。山里 OSM 常断头:报「不连通」就加中间点、缩短,或请用户给 GPX |
| 一串坐标 | [[lng, lat], …] | 直接用 |
| 只有一个地方 | 不写 track,写 "place" | 单点环绕 |
地名一律经高德解析,编译会打印「地点 X → 解析结果」——每行核对(同名 POI 会解析错)。山峰 POI 可能偏几公里,主峰直接写公开资料的 WGS 坐标。
2. 写 spec
从 examples/ 挑最像的复制改:
| 例子 | 内容 |
|---|---|
hike_osm_yubeng.json | 雨崩:南争垭口 → 上村 → 下村 → 神瀑,OSM 小路连线,9.8 km |
walk_amap_taishan.json | 泰山红门 → 中天门 → 南天门,高德步行,6.1 km 爬升 1181 m |
drive_amap_shangrila.json | 香格里拉 → 飞来寺,高德驾车 171 km(长路线自动拉远镜头) |
place_orbit_meili.json | 卡瓦格博单点俯冲 + 环绕 |
sun_meili_sunrise.json | 10 月 15 日飞来寺看卡瓦格博日照金山:07:21 受光,07:52 褪色,飞来寺 09:54 才见光 |
| 字段 | 说明 |
|---|---|
title / sub | 鸟瞰和片尾标题;长标题自动缩字号 |
marks | 地名小标:{"name", "at"},at 可写地名;名字里可以带海拔(「卡瓦格博 6740m」) |
photos | 沿途照片:{"image", "at" 或 "km", "label"};走到那里停 pause(2.2s)弹大图,之后缩成小牌子留在原地。有人像的照片提醒用户这是要公开的 |
gcj | true = spec 里的数字坐标是高德坐标;缺省按 WGS-84 |
zoom / pitch | 跟随镜头,缺省按长度自动(10 km 内 15.1 级;170 km 约 11.7 级)/ 62° |
exaggeration | 地形夸张,缺省 1.35;平原路线可加到 2 |
follow pause dive overview swoop end | 各段秒数 |
color | 走过的线和数字颜色,缺省 #f2ee3a |
单点:place name ele zoom pitch bearing turn orbit | ele 写已知海拔(高程数据 30 m 网格会削峰:卡瓦格博 DEM 只有 6469 m) |
3. 检查(必做,用连续小段,不要只看单帧)
shcd 项目目录 && $X check spec.json
- 「✓ 质检通过」:机器能查的都没问题,不用读拼图;只有要判断构图好不好看时才读一次。
- 「✗ 质检 N 项」:按报的时刻和内容改,必要时读拼图对照。 机器查不了、要人看的:
- 俯冲末/鸟瞰:整条线在画面里、标题不压线;没有黑块、没有直边断口(远处地形缺瓦片的样子)
- 跟随:人在画面中部、前方能看到白线;顶部里程和小地图、底部海拔剖面清楚
- 片尾:整条黄线都在画面内,总数据卡不压线
- 不对就改
zoom/pitch/exaggeration或 marks,再 check
4. 出片(后台)
sh$X make spec.json 名字.mp4 # 同时出 名字.gif(270px 预览)
首次跑会下载几千张卫星/高程瓦片(缓存在 ~/.cache/map-motion/tiles/,重渲很快)。
5. 交付
说清:文件位置、时长;路线来自哪(GPX / 高德规划 / OSM 小路推测——推测的要明说「不是你实际走的线」);里程、爬升是按 AWS 高程数据算的(和手表数字会差 5–15%);影像 Esri、地形 Mapzen/AWS 已在画面右下角署名。
踩过的坑
- 单帧
--stills一切正常,连续出片却露黑块/直边断口——MapLibre 连续换相机时远处瓦片会漏。所以检查一律用check(连续小段)。黑块判定只算纯黑像素(RGB < 3),山体阴影不会误报。引擎已处理:12.2 级后换平面投影、天空 2D 自画、全景俯角 40–44°。 - MapLibre 5.6 在地球↔地形切换时抛
clearFadeHold,已锁 5.24。 - 国内也用 Esri 卫星(WGS-84):高德卫星是 GCJ,和地形、GPX 对不上。所以 3D 片里所有高德来的点都转回 WGS。
- 雪山上白字看不清 → HUD 上下有压暗渐变;顶部 290px 内的地名小标会自动淡出,避免压住里程。
- 软件 WebGL(SwiftShader)慢且吃 CPU,别同时跑两个整片。
公共组件(加新片型时复用,别重写)
| 件 | 管什么 |
|---|---|
scripts/geo.py | 距离、方位、沿方位走 N km、墨卡托、缓动、fit_cam(把一堆点装进画面)、dive(地球俯冲)、blend(两镜头过渡)、frame、resolve(地名/坐标→WGS) |
scripts/dem.py / sun.py | 任意点海拔;太阳方位高度、地平线遮挡、过零时刻 |
scripts/runtime/hud.js | 字体、描影文字、斜体大数字、压暗渐变、底板、照片牌、脉冲落点、度分秒、标题、署名 |
render.py / qa.py / status.py | 按 runtime 字段加载 runtime/<名>.html,逐帧出片、自动质检、进度 |
m3d.py check | 读时间轴里的 checks(关键时刻),不用为新片型改 |
新片型=scripts/<模块>.py 的 compile(SPEC, sp, out, W, H)(在 compile.py 的 MODES 登记)出时间轴,带 checks;画面能用 t3d.js 就在里面加一块(如 SUN),否则新写 runtime/<名>.html 实现 prepare(t) / renderFrame(t) / __canvas。


