21-3d-tiles-terrain-baseline.md 13 KB

21: 3D Tiles 与地形基线

What to build: 接入本地或甲方提供的 3D Tiles、地形和倾斜摄影资源,实现地面视图与管线视图切换。首期不依赖 Cesium Ion。

Blocked by: 03, 04

Status: claimed Labels: ready-for-agent

认领:2026-09-17 会话接手。维护者已给出本机 GIS 数据位置(D:\GIS\GIS\yuanlingDEM 量化网格地形 + D:\GIS\GIS\yuanlingPic3 影像切片),本轮先做「地形 + 本地影像 + 免费全局底图」;地面/管线视图切换与 3D Tiles/倾斜摄影(数据未提供)留在本票后续。

依赖与事实

  • 引擎已就绪:票 27 已把 Cesium Viewer 接进地图服务层,本票在其上接真资源即可。
  • 影像底图已有配置化通道(票 29 已关闭):map.basemap.source 支持 none / natural-earth(Cesium 自带离线影像)/ tianditu(填 map.tianditu.token,开发走 /tianditu.map 代理)/ xyz(任意 {z}/{x}/{y} 模板)。甲方本地瓦片若已是 XYZ/WMTS 切片,先按 xyz 配即可;本票专注地形、3D Tiles、倾斜摄影与地面-管线视图切换,不要重做底图影像层。天地图的真实取图也还没用真 tk 联调过,可在本票顺手验收。
  • 维护者 2026-09-17 明确:本机已有本展示区域所需的 GIS 数据;具体地址、数据格式(瓦片/地形/模型)以及正式部署后如何编辑更新的方案,等维护者提供 —— 开始实施前先向维护者索取这两项。
  • 禁止项沿用决策地图调研:不使用 Ion token、Terrain.fromWorldTerrain、createOsmBuildings。

  • [x] 支持本地或甲方资源地址配置。

  • [ ] 地形、3D Tiles 和倾斜摄影资源可加载。—— 地形已可加载(本机 DEM 实测取到 14 级瓦片、零 404);3D Tiles 入口已实现并验证链路(map.tilesets[] → Cesium3DTileset,合法 tileset 能挂上、坏的单独记失败);甲方模型数据仍不可加载:D:\GIS\GIS\yuanlingSHP 是 cesiumlab model2tiles 导出,缺根 tileset.json 且分块 boundingVolume 是局部坐标(无 transform),需重导或提供模型原点。← 本票唯一的硬阻塞,待维护者动作

  • [x] 资源加载中、成功、失败和空状态可区分。—— 引擎 booting/ready/failed/disabled;地形 ready/invalid;3D Tiles ready/invalid(data-map-tilesets + stats.tilesetErrors)。 说明:「空状态」在本引擎里没有稳定信号——Cesium 按视锥懒加载,资源挂上了但当前视野无瓦片是正常态(此时 numberOfTilesWithContentReady 也为 0),据此报「空」会把好数据误判成空。故只保证三态如实,未伪造第四态;要不要在 UI 上区分「空」请维护者定夺。

  • [x] 地面视图与管线视图可切换:管线视图压低相机并把地形与 3D Tiles 压成半透明,地面视图还原。

  • [x] 切换视图不重置旋转中心,也不改变左键/右键/滚轮三键分工。

  • [x] 不使用硬编码 Cesium Ion token。

  • [x] 有资源加载状态单测或走查记录。

地形感知的相机已由票 30 做掉(原先记在本票「下一步」第 3 条):pickGroundAtCenter() 改为按地形 拾取,并在地形就绪后采样「家」视角的实测高程 —— 拉到距离下限 120 m 时中心高程 111.6 m,近景不再黑屏。

进度:90%

本轮(2026-09-17)做完「本地地形 + 本地影像 + 免费全局底图」这条主线,维护者要的「基础 GIS 地球地图」已经能看到沅陵县城真数据。

改动

  • 新增 src/common/localGisStaticPlugin.js:dev server 把 COCKPIT_GIS_MOUNTS(.env.local 里给的 {prefix,dir} 列表)挂成静态路径 —— DEM 与影像上百 MB,既不进仓库也不进产物;没配就什么也不挂,别的机器照常跑。
  • domain/mapLayers.js(新):planTerrain() / planOverlays() / planTilesets() 纯函数计划层;services/cockpitConfig.js 增加 map.terrain{url,requestVertexNormals}、map.overlays[]{url,minimumLevel,maximumLevel,bounds,credit}、map.tilesets[]{url,maximumScreenSpaceError,credit} 的归一化与校验。
  • services/cesiumEngine.js:applyTerrain() 走 CesiumTerrainProvider.fromUrl(量化网格,scheme:"tms" 由 Cesium 自己认,服务端不用翻 y);applyOverlays() 把本地精细切片叠在全局底图之上(TMS 用 {reverseY},并按 bounds 设 rectangle,范围外不发请求);applyTilesets() 逐项挂 Cesium3DTileset,单项失败不影响其它项。
  • domain/basemap.js 新增免费全局层 preset arcgis-imagery(ArcGIS 官方世界影像,免费免 key,模板顺序 {z}/{y}/{x});离线优先:默认与推荐都是 natural-earth。
  • services/mapService.js:引擎启动时按计划挂地形 / 叠加层 / 3D Tiles,宿主带 data-map-terrain、data-map-tilesets,getLifecycleStats() 增 terrain / terrainError / overlays / tilesets / tilesetErrors;浮标变成「底图:… · 地形:… · 叠加影像 N 层 · 3D Tiles N 个」。
  • 配置与数据对齐:public/cockpit/config.json(本机真实配置,gitignore)按 yuanlingPic3/meta.json 的 bounds 设 bounds、按 TMS 用 {reverseY};.env.local 挂目录并打开配置文件层。
  • 新增 docs/cockpit-deployment.md(给运维的生产部署手册):一页速查表、配置四层与开启方式、逐字段改法(后端/底图/地形/影像/3D Tiles)、nginx location 片段与两条硬要求(别走 SPA 回退、要 CORS)、完整最小配置、8 条自检表、常见问题。public/cockpit/README.md 顶部指路过去(它偏开发期接数据)。
  • 新增走查夹具 .scratch/cockpit-shell-panel-layout/verify/fixtures/tiles/tileset.json(只有包围盒、无 content 的合法 tileset,用来验证入口链路,不伪造任何模型/影像)。

验证证据(yarn dev @ 8001,Chrome 152 headless)

  • 纯逻辑 t21-terrain.mjs:通过。含配置与磁盘数据对齐核对(layer.json 的 quantized-mesh-1.0 / EPSG:4326 / scheme:"tms" / octvertexnormals,meta.json 的 tms / 3857 / bounds 与配置一致)。
  • 真机走查 t21-gis-walkthrough.mjs → 9/9:三层都挂上;细层影像只请求声明范围内的瓦片;压到沅陵拉近后 DEM 192 块(最高 14 级)+ 影像 199 块,全部 HTTP 200、零 404(这条同时证明 TMS 的 y 约定、availability、{reverseY} 都对);镜头停在沅陵上空 4.6 km;DEM 地址配错时 invalid + 明确失败文案 + 回退椭球;3D Tiles 入口:合法 tileset 挂上(stats.tilesets==1)而同批里坏的 URL 单独记失败(tilesetErrors 1 条)、引擎与其它图层不受影响;全程无页面级报错。
  • 视觉:yarn dev 不注入任何东西直接打开 /cockpit —— 离线全局影像球 + 顶栏「配置就绪 · 数据源 mock」+ 浮标「底图:内置离线影像 Natural Earth II · 地形:dem · 叠加影像 1 层」;换中心 + 滚轮拉近 4.6 km 后是沅陵县城真影像(河道、桥、体育场、建筑)。
  • 回归:t01 走查 20/20、t04 9/9、t29 6/6、t06/t28/t03-camera-contract/t01-panel-catalog/t21-terrain/t29-basemap 逻辑脚本全通过、yarn build exit 0。
  • 环境事实:本机外网瓦片不可达(ArcGIS / OSM 从 node 与浏览器都被挡),所以全局层实测用离线 natural-earth;在线源只做模板与署名断言,未真机验证(维护者明确"能用内网离线就不用在线",故不作阻塞项)。天地图无 key,同样不做主路径。

3D Tiles 数据的现状(需要维护者决定)

D:\GIS\GIS\yuanlingSHP 确实是 3D Tiles:cesiumlab model2tiles 导出,5 个层级(16–20)、769 个分块 *.json + 772 个 *.glb,每个分块 json 是自洽的 tileset 文档。但缺根 tileset.json,且分块 boundingVolume.box 是局部坐标(例:中心 6265/2293;叶子 20 级为 -3258, 9736, 105),没有任何 transform —— 也就是没有把模型放到地球上的地理参考。

2026-09-18 结论:维护者目测判定「明显错位 + 只是白色立方体,效果非常差」→ 不采用。 合成的根 tileset.json 已删除,/gis/models 挂载已移除,yuanlingSHP 那份数据不再使用。 替代方案见 票 34(参照 GIS 数据做轻量体块模型)。

当时的做法与机器验证留档(供以后判断"错位"到底出在哪):

  1. 源数据是地理坐标:h49c004002/*.prj 为 GEOGCS["CGCS_2000", ... UNIT["Degree"]],output/*.json 是 GeoJSON;
  2. 13 个 level-16 分块里,稠密簇(10 块)是县城核心,用目标坐标锚定它反推局部系原点,写成 ENU→ECEF transform;
  3. 回验:簇心经 transform 回算与目标差 ≈5 m、13 个分块中心全落在影像 bounds 内、真机加载零报错。

为什么仍然错位:锚定假设("稠密簇中心 == 维护者给的目标坐标")没有依据 —— 原始导出本就没有原点, 靠推测必然偏;且分块体块是单色白模,与影像里的城市形态对不上。这条路先放下。

本轮追加(2026-09-17 晚):地面/管线视图切换

决策地图票 05 已把语义锁死,本轮按它落地:

  • 新增 domain/viewModes.js(纯函数):planViewMode() 把「哪个视图」翻译成相机预设 + 图层透明度; viewHprFor() 只产出 {heading, pitch, range} —— 签名里根本没有中心参数,从构造上保证「切换视图不重置中心」。 管线视图:pitch 抬到 -18°(上限放宽到 +5° 允许平视/轻微仰视)、弹簧臂压到当前距离的 35%(夹在 400–6000 m)、 地形 α0.45 / 3D Tiles α0.4;地面视图:还原为 α1 与常规俯视。
  • stores/filterStore.js:视图条件并入 filterStore(决策地图指定 —— 与行业筛选同属全局条件,切一级模式不重置)。
  • services/cameraControl.js:新增 setPitchLimits() 与 setHpr() —— 只改限制与 pitch/range,不碰输入绑定、不碰中心。
  • services/cesiumEngine.js:applyTilesets() 现在回传实例(供改样式);新增 applyViewModeLayers() —— 地形走 globe.translucency,3D Tiles 走 Cesium3DTileStyle 的 color alpha;地面视图清掉样式而不是设成 1。
  • services/mapService.js:setViewMode() / getViewMode();进管线前记下地面基线(倾角+距离),回地面时按它还原; 宿主带 data-map-view-mode(启动即为 ground,不是切换过才有)。
  • CockpitShell.vue:筛选条加「视图:地面视图 / 管线视图」两个按钮(data-view-mode + aria-pressed), 与范围/行业同级、切一级模式不重置。
  • services/cockpitConfig.js + 部署手册 2.7:map.viewMode.pipeline{ pitch, rangeFactor, terrainAlpha, tilesetAlpha } 可覆盖默认值,脏值忽略退回默认。
  • 新增 verify/t21-view-modes.mjs(逻辑)与 verify/t21-view-modes-walkthrough.mjs(真机)。

验证证据(本轮追加)

  • 纯逻辑 t21-view-modes.mjs:通过。含构造性断言:viewHprFor() 的返回值里没有 pivot 字段、 计划里也没有中心 —— 「不重置中心」不是靠人眼比对,而是类型层面就不可能;另有地面还原(按基线)、 clamp 边界(min>max 的坑已修)、脏配置兜底。
  • 真机走查 t21-view-modes-walkthrough.mjs → 10/10:初始 ground;切管线后距离 12000 → 4200 m、 pitch -55° → -18°,而中心经纬高完全一致(110.39028164000001, 28.4436787, 111.57951503970997 前后逐位相同); 按钮 aria-pressed 与 data-map-view-mode 同步;管线视图里左键仍只平移(中心移动 −0.0036°、距离/倾角不变)、 滚轮仍只改距离(4200 → 6787 m、倾角不变);回地面按基线还原(-55° / 12000 m); 切一级模式后仍是管线视图(全局条件);全程无页面级报错。
  • 视觉:管线视图截图 —— 相机贴着地面侧看(河湾与县城呈侧视,不是俯视)。
  • 回归:7 个浏览器套件全绿(t01 20/20、t04 9/9、t21 9/9、t21 视图 10/10、t28 7/7、t29 6/6、t30 7/7)、 8 个逻辑脚本全通过、yarn build exit 0(57 s)。

下一步(本票未完成的部分)

  1. 3D Tiles / 倾斜摄影数据源 —— 甲方那份 yuanlingSHP 经目测否决(错位 + 白模), 替代方案已由 票 34(轻量体块模型)接手,等维护者对其远景效果判定; 若判定采用,本票的验收第 2 条改为「示意模型可加载」,并在部署文档里显著标注非实测。
  2. 「空状态」是否要在 UI 上区分 —— 技术上没有稳定信号(见验收第 2 条的说明),需维护者定夺要不要做。
  3. 若后续拿到带正确原点的真实 3D Tiles / 倾斜摄影(cesiumlab 重导并勾选生成根 tileset), 直接填 map.tilesets 即可替换示意模型 —— 入口与三态可见性都已就绪。