【免费下载链接】osiris
Open Source Global Intelligence Platform - Real-Time OSINT Dashboard - A Palantir Alternative - 2nZNHm3Lr9umG3DVrzYwHgktwkuKuJRXqqRqs3ewpump
导读
本文以仓库评审文档 docs/terrain-local-review.md 为主体,结合src/lib/下的源码实现与测试用例,系统讲解 Osiris 地图中“轻量级地形”功能的设计目标、运行时限制、源码级工作机制,以及如何在本地执行回归验证。读者读完将掌握:3D Terrain 在 Display 面板中的使用方式(桌面与移动端一致)、地形从激活到释放的完整生命周期、瓦片加载器的并发与缓存策略,以及用preview-smoke.mjs跑通terrain场景的完整命令。
功能定位:在现有渲染器内“轻量”叠加地形
Osiris 的地形功能刻意保持轻量:一个 MapLibre 渲染器、一块 canvas,不引入任何额外 3D 引擎(src/lib/map-terrain.ts 的注释明确为 “Lazy, bounded terrain using the existing renderer; no second map or imagery”)。这一点决定了整套架构的设计取向——所有能力都构建在 MapLibre 自身的raster-dem数据源、setTerrain与投影切换之上,而不是引入独立的 WebGL 地形渲染管线。
入口:Display 面板中的 3D Terrain 与 3D Buildings
地形与建筑开关位于Display(显示)面板,紧邻3D Buildings(3D 建筑),桌面端与移动端布局一致。在 src/components/LayerPanel.tsx 中可以看到两个独立开关:
terrain_3d:标签 “3D Buildings”,描述 “City detail · zoom 14.5+”(城市细节,14.5 级起);terrain_elevation:标签 “3D Terrain”,描述 “Mountains · zoom 10+”(山地,10 级起)。
用户的操作路径是:打开3D Terrain→ 状态面板出现地形状态提示 → 点击Zoom to terrain将相机移动到当前区域以观察地形。当地形状态为idle(“Terrain at zoom 10+ · zoom in”)时显示 “Zoom to terrain” 按钮,状态为error时显示 “Retry terrain” 按钮(src/components/LayerPanel.tsx)。
评审文档强调了一个重要的行为细节:开关地形本身会保留当前的缩放、中心与倾斜角,而 “Zoom to terrain” 是独立的可选按钮,并且在地图重挂载(theme remount)后绝不会自动重放此前显式的地形缩放请求。对应实现中,terrainFocus通过lastTerrainFocusref 去重(src/components/OsirisMap.tsx),只在值变化时执行一次 flyTo(src/components/OsirisMap.tsx),这正是“不重放”行为的源码依据。
运行时限制:一份可执行的“资源预算”
评审文档列出的运行时限制不是纸面设计,而是可以直接在源码与测试中逐条验证的硬性约束。整理如下:
| 限制项 | 数值/策略 | 源码证据 |
|---|---|---|
| 渲染器/画布 | 单个 MapLibre 渲染器、单个 canvas | src/lib/map-terrain.ts |
| 启动/概览缩放 | 不下载任何高程数据 | attachTerrain在 zoom < 10 时只上报idle,绝不addSource |
| 激活阈值 | zoom ≥ 10,且相机静止 500 ms 后 | TERRAIN_MIN_ZOOM = 10、TERRAIN_SETTLE_MS = 500(src/lib/map-terrain.ts) |
| 释放阈值 | 缩小到 zoom < 9.5 时释放地形资源 | DISABLE_ZOOM = 9.5(src/lib/map-terrain.ts) |
| 高程瓦片 | 256 px Terrarium 编码,最大源 zoom 10 | src/lib/map-terrain.ts |
| 渲染 LOD | setSourceTileLodParams(10, 1.25)约束渲染瓦片密度 | src/lib/map-terrain.ts |
| 垂直夸张 | exaggeration: 1(真实高度) | src/lib/map-terrain.ts |
| 并发请求 | 同时最多 2 个高程请求 | MAX_REQUESTS = 2(src/lib/terrain-tiles.ts) |
| 请求超时 | 12 秒 | TERRAIN_REQUEST_TIMEOUT_MS = 12_000(src/lib/terrain-tiles.ts) |
| 内存缓存 | 8 MiB 编码瓦片 LRU | MAX_BYTES = 8 * 1024 * 1024(src/lib/terrain-tiles.ts) |
| 俯仰角 | 所有视角上限 60 度,启用地形不会压平现有相机 | src/lib/map-terrain.ts |
| 像素比 | 地形激活时封顶 1.5,关闭后恢复原值 | src/lib/map-terrain.ts |
迟滞(Hysteresis)与静止等待
TERRAIN_MIN_ZOOM = 10与DISABLE_ZOOM = 9.5之间故意留出 0.5 级的迟滞区间,防止相机在激活阈值附近来回悬停时反复重建/销毁地形源——注释直言 “Hysteresis prevents reloading tiles when hovering around the activation zoom”(src/lib/map-terrain.ts)。测试用例 “unloads during zoom-out and avoids source churn near the zoom threshold” 精确验证了这一行为:缩小到 9.8 时源仍然保留,继续缩小到 9 才移除(src/lib/map-terrain.test.ts)。
激活前的 500 ms 静止等待由movestart取消、moveend重新排定的定时器实现:update()在map.isMoving()为真时不排定任务,只有相机静止且页面可见(!document.hidden)才setTimeout(activate, 500)(src/lib/map-terrain.ts)。测试同时覆盖了“后台标签页不启动瓦片工作”“移动恢复即取消待激活任务”两条路径(src/lib/map-terrain.test.ts)。
投影切换:为地形省下 Globe+Terrain 着色器
评审文档指出:globe 在 zoom 7 到 9 之间过渡到本地投影,早于高程激活(zoom 10),从而避免同时编译 globe-terrain 与 local-terrain 两种着色器变体。对应实现位于 src/lib/map-projection.ts:
export const TERRAIN_GLOBE_PROJECTION: ProjectionSpecification = { type: ['interpolate', ['linear'], ['zoom'], 7, 'vertical-perspective', 9, 'mercator'], };即:启用地形时,投影在 zoom 7 时是vertical-perspective(透视地球),zoom 9 时平滑插值到mercator(墨卡托本地平面)。而普通非地形模式使用 MapLibre 自带的GLOBE_PROJECTION(src/lib/map-projection.ts)。applyMapProjection只在投影类型真正变化时才调用setProjection,因此开关地形不会触发投影重建(src/lib/map-projection.ts);src/components/OsirisMap.tsx 的注释也说明投影变化与地形开关相互独立。评审文档引用了 MapLibre 官方的投影规范(supported projection expression),本文按仓库内实际代码描述该过渡区间。
地形瓦片加载器:共享、可取消、LRU
osiris-dem://协议由 src/lib/terrain-tiles.ts 通过installTerrainTileProtocol注册到 MapLibre 的addProtocol。底层createTerrainTileLoader实现了三层资源治理:
- 共享在途请求:同一
z/x/y瓦片有多个消费者时只发起一次 fetch,任一消费者取消不会取消其他消费者(src/lib/terrain-tiles.ts);测试 “shares in-flight tiles without one consumer cancelling another” 验证了取消首个消费者后仍只发生一次网络请求且第二消费者正常拿到数据(src/lib/terrain-tiles.test.ts)。 - 可取消队列 + 12 秒超时:排队中尚未开始的 job 被取消时会从队列剔除;已开始的 job 由
AbortController中止,超时通过setTimeout触发TimeoutError(src/lib/terrain-tiles.ts)。测试分别覆盖了超时释放队列槽位与取消在途请求(src/lib/terrain-tiles.test.ts)。 - 8 MiB LRU:命中即把 key 移到队尾(
cache.delete(key); cache.set(key, data)),超预算时逐出最久未用项;并且read返回data.slice(0)副本,防止 MapLibre 把缓冲区 transfer 到 worker 后破坏 LRU 缓存(src/lib/terrain-tiles.ts)。LRU 逐出、缓存复用均有对应测试(src/lib/terrain-tiles.test.ts)。
瓦片请求本身使用cache: 'force-cache', credentials: 'omit'(src/lib/terrain-tiles.ts),与评审文档“同时利用浏览器 HTTP 缓存”的表述一致;响应头cacheControl: 'max-age=86400'进一步给浏览器缓存一天。需要说明的限制:GPU 资源不会跨页面刷新持久化——这是浏览器 WebGL 上下文的固有行为,评审文档明确列出。
地形状态机与失败回退
attachTerrain对外通过TerrainStatus('idle' | 'waiting' | 'loading' | 'ready' | 'error')汇报状态(src/lib/map-terrain.ts):
idle:zoom < 10,未做任何高程工作;waiting:zoom ≥ 10,等待 500 ms 静止(或页面重新可见);loading:已添加 DEM 源与地形,等待首个瓦片;ready:首个瓦片加载完成(依据sourcedata事件中的tile或sourceDataType === 'idle',注释说明只等'content'会让指示器一直转圈,src/lib/map-terrain.ts);error:瓦片请求失败后进入,通过queueMicrotask延迟释放地形资源,且不会自动重试,需要用户点击 “Retry terrain”(src/lib/map-terrain.ts)。
测试 “falls back to the normal map after a tile error, without automatic retry loops” 验证了失败后移除源、地图恢复普通渲染、再次挂载可重新激活的完整路径(src/lib/map-terrain.test.ts)。失败回退在 UI 上由 LayerPanel 的 “Terrain unavailable; the map is still usable.” 文案与 “Retry terrain” 按钮承接(src/components/LayerPanel.tsx)。
图层批处理:让地表层被地形纹理正确覆盖
地形启用时,MapLibre 会把background/fill/line/raster/hillshade/color-relief等“可披覆”(draped)图层绘制到地形纹理上。若这些图层与标记(marker)交错排列,会导致同一地形网格被绘制多遍。batchTerrainLayers将可披覆的地面图层统一排到标记/符号之上,从而避免多遍绘制且不隐藏任何数据(src/lib/terrain-layer-order.ts)。它只在styledata时刷新(不做逐帧工作)、只移动需要移动的图层,并在卸载时恢复原始顺序;新增图层的插入锚点也会被记忆(src/lib/terrain-layer-order.ts)。测试夹具中的代表性图层序(day/night fill、网络线、SDK 路径、绘制区域等)验证了“地表连续块”的划分逻辑(src/lib/terrain-layer-order.test.ts)。
3D Buildings 与卫星图层的关系
评审文档明确:建筑复用现有 CARTO 建筑数据源,zoom 14.5 起显示;单独开启建筑不会下载高程数据(建筑是fill-extrusion,不依赖 DEM)。卫星图层的 GPU program 按投影变体缓存,未激活的卫星图层不编译 program;拾取(picking)与轨道(orbit)program 按需创建。这一点与 “轻量” 主题一致——高程下载、着色器编译都只在真正需要时发生。
本地回归验证:命令、场景与环境变量
评审文档给出了完整的回归检查命令,这里结合 tools/preview-smoke.mjs 的源码逐条展开:
npm test npm run build # 针对已在运行的生产预览执行,而非 dev server $env:PREVIEW_URL='http://127.0.0.1:3001' $env:SMOKE_SCENARIO='terrain' node tools/preview-smoke.mjs关键点拆解:
npm test/npm run build:跑全部单元测试与 Next.js 生产构建,是任何改动进入评审前的基础门槛。PREVIEW_URL:默认http://127.0.0.1:3001(tools/preview-smoke.mjs)。冒烟脚本通过 Chrome DevTools Protocol 驱动无头浏览器,针对的是已运行的生产预览(npm run build && npm start之后的产物),而不是开发服务器——这是为了度量真实的构建产物行为。SMOKE_SCENARIO:默认startup,除terrain外还有startup、zoom、terrain-camera、mobile、recovery、imagery、buildings、satellites等场景(tools/preview-smoke.mjs)。其中imagery、buildings、terrain-camera场景会启用Fetch.enable并对/api/geo*打桩(tools/preview-smoke.mjs),只替换本地地理定位响应;satellites场景替换一个 200 对象的本地 feed;地图瓦片与高程保持真实网络数据。CHROME_PATH:选择 Chromium 浏览器可执行文件,默认是 Windows 上安装的 Chrome(C:/Program Files/Google/Chrome/Application/chrome.exe,tools/preview-smoke.mjs)。PROFILE_TERRAIN=1:在测试的临时输出目录捕获 CPU profile(terrain.cpuprofile),便于分析激活/加载阶段的耗时([tools/preview-smoke.mjs](https://link.gitcode.com/i/06b737ab32ee10a46f8109e5626ebaf5#L130, L260-L262))。- 隔离的 headless profile:每个测试使用独立的无头浏览器 profile,绝不触碰用户日常浏览器 profile。
terrain场景的自动化断言覆盖(tools/preview-smoke.mjs):
- 选择地形后仍受 zoom 门控:文本显示 “Terrain at zoom 10+”,且
performance.getEntriesByType('resource')中不含elevation-tiles-prod请求; - 点击 “Zoom to terrain” 后地形加载完成(出现 “Terrain on”)并保存
terrain.png截图; - 同区域地形重新启用时统计前后高程请求次数(
terrainRequestsBeforeReenable/terrainRequestsAfterReenable)并记录重新就绪耗时(terrainReenableMs); - 主题重挂载后不重放旧的地形缩放请求(地图状态
ready且相机 zoom < 10,但 3D Terrain 开关仍为按下态); - 2D 模式同时关闭地形与建筑;
- 卫星影像与山地地形共存(地形开启且出现
World_Imagery资源); - 建筑与地形在同一 canvas 上协同工作(
document.querySelectorAll('.maplibregl-canvas').length === 1)。
评审文档列出的自动化覆盖范围(快速缩放点按、源/canvas 复用、地形门控、同区域缓存复用、移动端溢出、视角切换、失败/重试行为)在上述断言中均有对应;截图与 JSON 报告输出到测试打印的临时 profile 目录。
评审结论与已知边界
最新本地验证(2026-09-08):Next.js 16.3.4 生产构建与 TypeScript 检查通过,603 个测试通过(14 个 opt-in/联网测试跳过)。相机回归确认:在 zoom 8 与 zoom 13 启用、禁用地形以及从平面模式启用时,zoom、pitch、bearing、center 均保持不变。同区域地形重新启用复用了全部高程数据(前后各 22 个请求),最终隔离运行中 930 ms 就绪。注意:这是一台机器上的单次观测,不是延迟承诺。初始 zoom/scale 读数已反映实际加载的相机;三次快速点按正确地从 6.5 推进到 9.5;主题重挂载不重放显式地形缩放请求;移动端布局、真实阿尔卑斯影像、Edge 启动失败/重试检查均通过。
依赖加固方面:Next.js / eslint-config-next 升级至 16.3.4,sharp 至 0.35.4,lockfile 中修补了 PostCSS、nanoid、baseline-browser-mapping。npm audit --omit=dev报告零已知漏洞;完整审计仍报告 8 个开发工具链问题(3 moderate、4 high、1 critical),主要来自旧的 Vitest/Vite 工具链——不要暴露其测试 UI/dev server,升级该工具链与清理仓库既有 lint 债务是独立工作。
评审文档诚实标注了边界:
- 这是本地发布候选(release candidate)评审,不是全站生产认证;物理 iOS/Safari 与低端 Android 设备仍需人工测试;
- MapLibre 6 要求WebGL2;冷图形初始化仍可能产生短暂卡顿(hitch),headless 计时是诊断手段,不是承诺的帧率;
- 仓库存在既有 lint 债务;
- 外部数据源本身可能失败:CCTV 目录重试能保留已成功的区域,但无法让离线提供商或相机上线;既有提供商配置/速率限制与偶发的市场 feed CORS 失败,与地形渲染无关;
- 本地预览不部署、不合并,变更需通过评审 PR 发布。
快速复现清单
# 1. 全量单测 + 生产构建 npm test npm run build # 2. 启动生产预览(另开终端) npm start # 3. 跑地形冒烟场景(PowerShell) $env:PREVIEW_URL='http://127.0.0.1:3001' $env:SMOKE_SCENARIO='terrain' $env:PROFILE_TERRAIN='1' # 可选:捕获 CPU profile node tools/preview-smoke.mjs # 4. 结果产出 # 临时 profile 目录:terrain.png 截图、JSON 报告、terrain.cpuprofile一句话总结这套设计:Osiris 用“zoom 门控 + 静止等待 + 迟滞释放 + 共享可取消瓦片队列 + 图层批处理”把地形压进现有 MapLibre 渲染器,让全球概览零高程成本,而区域视角获得真实高度——这正是 “Lightweight terrain” 的全部含义。
【免费下载链接】osiris
Open Source Global Intelligence Platform - Real-Time OSINT Dashboard - A Palantir Alternative - 2nZNHm3Lr9umG3DVrzYwHgktwkuKuJRXqqRqs3ewpump
相关推荐
react-map-gl (maplibre) 3D Terrain 实战:terrain、sky 属性与 TerrainControl 构建地形地图
react map gl maplibre 3D Terrain 实战:terrain、sky 属性与 TerrainControl 构建地形地图 本文基于 r
前端UI组件Cesium Terrain Builder:构建3D地形的完整入门指南
Cesium Terrain Builder:构建3D地形的完整入门指南 想要在浏览器中创建令人惊叹的3D地球效果?Cesium Terrain Builder
GISCesium Terrain Builder:构建3D地形的终极完整指南
Cesium Terrain Builder:构建3D地形的终极完整指南 想要在浏览器中打造令人惊艳的3D地球效果?Cesium Terrain Builder
GIS
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考