第08章 - 渲染引擎与三维可视化
GeoLibre 有四种渲染引擎:MapLibre GL JS(默认)、Mapbox GL JS、CesiumJS、ArcGIS Maps SDK for JavaScript。同一个工程可以用其中任意一种绘制主图,也可以用任意一种绘制分屏里的任一面板。
这件事之所以成立,是因为状态仓库与渲染引擎彻底解耦:store 里只有普通的 GeoLibreLayer 记录、偏好设置与 MapViewState,没有任何一个 SDK 自己的对象。四个引擎各自实现同一个 MapEngine 接口,负责相机、能力声明、拾取、控件、落点、框选与截图。
本章先讲这套引擎模型,再逐个讲四种引擎的凭据、能力与限制,然后进入三维内容(3D Tiles、点云、高斯泼溅、地形、球面与椭球、Cesium 的时间与相机),最后是插件适配、性能、排错与验证方式。
说明:本章基于 GeoLibre 3.4。菜单与参数名保留英文原文。素材来自
docs/user-guide/rendering-engines.md、docs/maplibre-renderer.md、docs/mapbox-renderer.md、docs/cesium-renderer.md、docs/arcgis-renderer.md、docs/cesium-parity-testing.md、docs/architecture.md、packages/map/src/map-engine.ts与apps/geolibre-desktop/src/lib/add-data-renderer.ts。
8.1 引擎模型:一个接口,四个实现
8.1.1 为什么能力要用布尔量声明
packages/map/src/map-engine.ts 里的 MapEngine 接口开头写着一句关键注释:“以 capabilities 为准,不要按 kind 分支”。原因是按引擎名判断会同时错两个方向——它会把 Cesium 原生就能做的相机与地形工作藏起来,也会在出现第五个引擎时立刻失效。
能力集是冻结的常量对象(Object.freeze),因为它是关于引擎的事实而不是设置:所有 MapController 实例共享同一个对象,一次写入会把所有实例的认知一起改掉。
| 能力字段 | 含义 | 谁在读它 |
|---|---|---|
styleSpec |
Mapbox Style Spec 可用:setStyle、paint 属性编辑、样式图层 id、样式导入导出 |
样式面板、SLD/QML 导出 |
nativeMapInstance |
getMap() 返回活的 MapLibre 地图 |
AI 目标检测、segment-everything、直接写 maplibre-gl 的插件 |
customLayers |
能承载 MapLibre CustomLayerInterface 图层 |
自定义图层插件 |
deckOverlay |
能承载 @deck.gl/mapbox 的 MapboxOverlay |
Deck.gl Layer、glTF、DuckDB、3D Tiles、LiDAR |
terrain |
三维地形可开关、可夸张 | Controls → Terrain |
picking |
identifyFeatures 能返回要素 |
Identify |
onMapDrawing |
能在地图表面拖放/绘制 | 手动落点、栅格子集框选 |
domControls |
能挂载 IControl DOM 控件 |
内置控件、插件面板 |
screenOverlays |
屏幕空间投影足够精确,可用 SVG 自绘覆盖框 | 栅格子集面板 |
flatProjection |
工程要求 mercator 时确实画平面 |
球面专属功能的开关判断 |
terrainSource |
能装自定义 DEM(COG URL / 本地文件 / 已加载的栅格图层) | 地形设置里的 “Terrain source” |
ionTerrain |
能用 Cesium Ion 地形资产 | 地形对话框里的 Ion 资产字段 |
nativeZarr |
引擎自己从图层记录画 Zarr | Zarr 面板是否挂载 |
nativeDataSources |
用自家数据源加载器加载 KML/KMZ、CZML、ion 资产 | Add Data 的 3D layers 组 |
deferredEngineReady |
首次加载完成才对外声明就绪 | 需要等待引擎的 UI |
measureTool |
内置测量控件的草图能画出来 | Controls → Measure |
controlLayerPanels |
栅格与 PMTiles 的 IControl 面板可用 |
Add Raster / PMTiles 面板 |
supportedLayerKinds |
每种图层类型的处置:native / plugin / unsupported |
图层支持矩阵 |
接口本身就是这份能力表(节选自 packages/map/src/map-engine.ts,字段顺序与原文一致):
export interface MapEngineCapabilities {
readonly styleSpec: boolean;
readonly nativeMapInstance: boolean;
readonly customLayers: boolean;
readonly deckOverlay: boolean;
readonly terrain: boolean;
readonly picking: boolean;
readonly onMapDrawing: boolean;
readonly domControls: boolean;
readonly screenOverlays: boolean;
readonly flatProjection: boolean;
readonly terrainSource: boolean;
readonly ionTerrain: boolean;
readonly nativeZarr: boolean;
readonly nativeDataSources: boolean;
readonly deferredEngineReady: boolean;
readonly measureTool: boolean;
readonly controlLayerPanels: boolean;
readonly supportedLayerKinds: SupportedLayerKinds;
}
MapLibre 的能力常量几乎全开,只有四项为 false:ionTerrain、nativeZarr、nativeDataSources、deferredEngineReady——因为它通过自己的控件与同步加载路径达成同样的效果,而不是靠原生加载器。常量被 Object.freeze 冻结,因为 readonly 只在类型层生效,而外部插件是以纯 JavaScript 加载、通过插件 API 拿到这个对象的。
8.1.2 主图与分屏的引擎选择
引擎不是全局开关,而是两个层级的字段:
| 字段 | 位置 | 作用 |
|---|---|---|
primaryRenderer |
工程顶层 | 主画布用哪个引擎;默认值不写入工程 |
SecondaryMapView.viewKind |
分屏面板记录 | 该面板用哪个引擎 |
MapGridLayout |
工程 | rows、cols、syncView |
切换任一个值都会卸载上一个引擎(而不是把它藏起来继续吃 GPU),再把新引擎接到同一份工程状态上。所以:
- 相机、图层、分组、可见性、透明度与大部分样式在切换后仍在;
- 不支持的图层留在工程里,切回兼容引擎时回来;
- 每个面板的引擎与视图状态都随工程保存。
面板 0 永远是主图,其余面板共享同一份底图与同一份 layers,但可以各自覆盖哪些图层可见;syncView 打开时,平移缩放任一面板会镜像相机到其他面板。网格布局在记录里长这样:
export interface MapGridLayout {
/** Grid rows (>= 1). */
rows: number;
/** Grid columns (>= 1). `rows * cols` is the total number of panes. */
cols: number;
/** When true, all panes share a synchronized camera. */
syncView: boolean;
}
提示:四种引擎的自动化名称是固定的:
maplibre、mapbox、cesium、arcgis。Python API、MCP 工具、iframe API、工程格式与插件清单都接受这四个名字,写脚本时不要用界面上的显示名。
8.1.3 引擎边界是编译期强制的
packages/map/src 里,以引擎命名的文件(arcgis-*、cesium-*、mapbox-*,以及 MapLibre 的 layer-sync、map-controller、MapCanvas)不允许 import 另一个引擎的文件;两个引擎共用的代码放在按功能命名的中立模块里(pmtiles-archive.ts、protocol-tiles.ts、gl-style-compiler.ts、feature-style.ts 等)。
这条规则由 ESLint 的 local/no-cross-engine-imports 强制(eslint-rules/no-cross-engine-imports.mjs)。它解释了一个常见现象:为什么某个功能只在部分引擎上有——跨引擎共享需要显式抽取到中立模块,而不是顺手 import 一下就完事。
8.2 四种引擎能力对比
8.2.1 总览
| 维度 | MapLibre | Mapbox | Cesium | ArcGIS |
|---|---|---|---|---|
| 定位 | 默认、功能与插件支持最广 | Mapbox 样式与服务 | 原生三维球 | ArcGIS 服务与 Esri 场景 |
| 是否随包分发 | 是 | 否(选中时加载) | 否(首个画布挂载时加载) | 否(首个面板挂载时从 CDN 加载) |
| 凭据 | 不需要 | 必须 Mapbox access token | 可选 ion token | 可选 ArcGIS API key |
| Style Spec | 有 | 有(共享大部分) | 无 | 无(逐要素求值后烘焙) |
| 平面三维地形 | 有 | 有(Mapbox 地形) | 有 | 有(World Elevation 或 COG) |
| 自定义 COG 地形源 | 有 | 无(设置对话框隐藏该节并说明原因) | 有(含 Ion 地形资产) | 有(EPSG:3857/4326) |
| 原生 3D Tiles | deck.gl 叠加层 | deck.gl 叠加层 | 原生 | deck.gl 叠加层(限主平面图/局部场景) |
| 点云 LiDAR 图层 | 有 | 有 | 无该面板(原生走 3D Tiles/COPC) | 有(deck.gl) |
| 高斯泼溅 | 有 | 无 | 原生(KHR_gaussian_splatting 瓦片) | 无 |
| KML/KMZ | 宿主导入器 | 宿主导入器 | 原生 KmlDataSource | 宿主导入器 |
| CZML / Cesium ion | 无 | 无 | 有 | 无 |
| Zarr 原生绘制 | 无(控件) | 控件(@carbonplan/zarr-layer) | 原生(zarr-cesium) | 原生(平铺影像) |
| 非 Web Mercator 平面投影 | 有(任意平面 CRS) | 无 | 无(球面/2D/Columbus) | 有(WKID) |
| 混合模式(Blend) | 有 | 不复现(面板会说明) | 无 | 有(SDK blendMode) |
| 分屏承载 | 有 | 有 | 有 | 有(叠加层受限) |
| 开源许可 | MIT | Mapbox ToS(非开源) | Apache 2.0 | Esri 条款 |
8.2.2 数据源支持矩阵
这张表来自 add-data-renderer.ts 的三个”不支持集合”(详细解析见第 5 章 5.5.5):
| 数据源 | MapLibre | Mapbox | Cesium | ArcGIS |
|---|---|---|---|---|
| MBTiles | 有 | 无 | 有 | 有 |
| 高斯泼溅 | 有 | 无 | 无 | 无 |
| Cesium ion / CZML | 无 | 无 | 有 | 无 |
| STAC | 有 | 有 | 有 | 无 |
| Video | 有 | 有 | 有 | 无 |
| LiDAR | 有 | 有 | 无 | 有 |
| 3D Tiles / glTF / DuckDB / Deck.gl | 有 | 有 | 视能力 | 需 deck.gl 叠加层 |
ArcGIS 的 deck.gl 源还有一个附加条件:只在主平面图或局部三维场景生效;全局球面场景与分屏面板不承载该叠加层,菜单与图层徽标会反映这条限制。
8.3 切换主图与分屏混排
8.3.1 切换主图
View → Rendering engine → MapLibre / Mapbox / ArcGIS / Cesium。
GeoLibre 会替换活跃画布,而不是把旧引擎藏在后面——旧引擎被卸载,显存随之释放。切换后:
- 相机、图层、分组、可见性、透明度与大部分样式保留;
- 所选引擎写进工程,重开时恢复;
- 引擎专属的图层类型与工具保持原状,切回兼容引擎时重新出现。
8.3.2 分屏混排
View → Split View 创建多图布局。每个面板自己的渲染引擎菜单可以为该面板选四种引擎之一。
| 选项 | 行为 |
|---|---|
| 相机同步 | 所有面板跟随主图相机(syncView) |
| 相机独立 | 各面板保持自己的视图 |
| 引擎独立 | 每个面板可用不同引擎 |
典型用法是把 2D 与 3D 并排比较,或同一份数据在两个引擎下对照渲染差异。每个面板的引擎选择与视图状态随工程保存。
8.3.3 相机同步按地面分辨率
Cesium 的相机与 MapLibre 的 MapViewState 不是同一套参数:前者是米制距离与相对地平线的俯仰,后者是缩放级别与相对天底的俯仰、加上方位角。换算模块是 packages/map/src/cesium-camera.ts。
关键取舍:按地面分辨率(米/像素)对齐,而不是按 zoom 级别对齐。这样即使两个面板高度不同,屏幕比例尺也能保持一致。
同步是双向的:面板从共享 mapView 取初值,把 store 变化应用到球面,也把球面自己的移动写回;同时有一个容差检查抑制 “应用 → moveEnd → 又应用” 的回声,避免抖动。
8.4 凭据与加载方式
8.4.1 三个凭据字段
Settings → Environment Variables 提供三个字段:
| 字段 | 引擎 | 是否必需 |
|---|---|---|
| Mapbox token | Mapbox | 必需;缺失时显示设置说明 |
| Cesium Ion token | Cesium | 可选 |
| ArcGIS API key | ArcGIS | 可选 |
三条共同的约定:
- 存在当前设备上,不写进共享的工程文件(桌面走系统凭据存储,Web 走 localStorage)。
- 改动会重建对应引擎的地图实例。
- 报错信息里会抹掉访问令牌。
8.4.2 各引擎的加载时机
| 引擎 | 何时加载 | 体积/来源 | 离线行为 |
|---|---|---|---|
| MapLibre | 随应用打包 | 无外部请求 | 完全可用 |
| Mapbox | 首个 Mapbox 面板挂载时 | 独立 chunk,约 1.91 MB(gzip 532 KB,Mapbox GL JS 3.30.0) | 首次需网络 |
| Cesium | 首个 Cesium 画布挂载时 | Workers/Assets/Widgets 随应用暂存,由 CESIUM_BASE_URL 解析 |
首次需取暂存资源 |
| ArcGIS | 首个 ArcGIS 面板挂载时 | 从 https://js.arcgis.com/<version>/@arcgis/core/… 拉模块 |
首次需网络;之后 Service Worker 缓存 |
Mapbox 的 JS 与 CSS 在生产构建里单独分块,并排除在 PWA 的初次预缓存之外——因此 MapLibre 启动不会下载 Mapbox 引擎。但桌面与 Web 的发行产物里仍然包含它(@geolibre/map 的运行时依赖),只是没有打开 Mapbox 面板之前不会运行任何 Mapbox 代码、也不会联系任何 Mapbox 服务。
8.5 MapLibre:默认引擎
8.5.1 定位
MapLibre GL JS 随每个发行版打包,不需要账号或访问令牌,主图与分屏面板都能用。新工程默认用它;分屏面板省略 viewKind 时也意味着 MapLibre。
它的功能与插件兼容性最广,因为应用里的很多工具本来就写在 MapLibre 的样式 API 之上。
8.5.2 支持的路径
- GeoJSON 与导入的矢量数据:单值、分类、分级、规则、表达式、比例、热图、聚合、图案、标注、拉伸;
- 栅格与影像源:XYZ、WMS、WMTS、COG/GeoTIFF、图像与视频叠加、栅格 PMTiles、桌面 MBTiles;
- 矢量瓦片与矢量 PMTiles/MBTiles,含导入的 Mapbox 样式与 ArcGIS 矢量瓦片服务;
- 3D Tiles、I3S、LiDAR/COPC 点云、高斯泼溅、三维模型、Zarr、NetCDF/HDF、deck.gl 可视化图层——通过 GeoLibre 的专用渲染器与叠加层;
- 要素拾取、选择、编辑、测量、范围绘制、可拖动落点、打印与截图、以及完整的 MapLibre 向插件面。
8.5.3 投影:平面、球面与任意 CRS
在默认的 Web Mercator 之外,MapLibre 引擎还有两条路:
① 球面:Controls → Globe 在墨卡托与球面之间切换;Controls → Terrain 给两种视图都加上高程。这是一个 MapLibre 样式地图,不是原生三维球——要原生球面与 Cesium 原生场景格式请用 Cesium 引擎。
② 任意平面 CRS(MapLibre GL JS 6.15,issue #1128):Settings → Map → Map coordinate reference system 选一个,保存为 preferences.map.mapCrs;缺省即默认的 Web Mercator 地图或球面。
预设覆盖:
| 天体/投影 | 预设 |
|---|---|
| 地球、月球、火星 | 极射赤面投影 + 简单圆柱投影 |
| 水星、金星、Io、Europa、Ganymede、Callisto、Titan、Pluto、Charon | 简单圆柱投影 |
每个预设都带一份服务端已按该格网渲染的底图:地球用 NASA GIBS,其他天体用 USGS Astrogeology,直接读取而不是重投影到 Web Mercator。选预设同时会设定天体,因此量算使用该天体的半径。Custom EPSG code… 接受由内置 EPSG 表解析出的任意代码,但它没有底图,瓦片格网会拟合到它所映射的世界区域。
在其他 CRS 下,行为有明确的分界:
| 图层 | 其他 CRS 下 |
|---|---|
| GeoJSON | 绘制,由 MapLibre 的 Worker 里用 proj4 投影 |
WMS 与 ArcGIS export 图层 |
按瓦片在该 CRS 下的框请求(WMS 1.1.1,SRS=<code>);服务必须支持该 CRS |
| 行星预设 | 按服务能读的标签请求:简单圆柱用 EPSG:4326,极地格网用 EPSG:32661/EPSG:32761 |
| 图像与视频叠加 | 用四角坐标绘制 |
| XYZ、矢量瓦片、MBTiles、Wayback | 不绘制(瓦片是 Web Mercator) |
| 共享底图 | 不绘制 |
| 自己加瓦片或 deck.gl 叠加的插件 | 未适配 |
此外,球面按钮会被隐藏,地形关闭,受限边界设置被搁置(MapLibre 会把视图限制在 CRS 内)。切换 CRS 会重建样式并框住新地图的区域;打开工程仍保留其保存的视图。
注意:其他 CRS 下的 WMS 请求是按瓦片框逐块请求再拼接的,所以服务端必须支持你选的 CRS。一个只支持 EPSG:3857 的 WMS 在 Robinson 投影下会整片空白,这不是网络问题。
8.5.4 架构
MapController 是 MapEngine 的 MapLibre 实现。packages/map/src/layer-sync.ts 把工程图层记录协调成原生 source 与样式图层;为 MapLibre 不直接绘制的格式注册协议或共享叠加层的专用控件则另在其上。可复用的无头(headless)部分发布为 @geolibre/map。
8.6 Mapbox GL JS
8.6.1 凭据与底图
把 token 粘进 Settings → Environment Variables → Mapbox token,点 Save Settings。它与 Cesium token 一样存在本机、在工程文件之外。也可以在开发服务器环境里设置 MAPBOX_TOKEN。
- token 变化会重建 Mapbox 地图;
- 缺 token 时显示设置说明;
- 地图加载错误会抹掉访问令牌;
- 使用受该 token 所属账号约束,并适用 Mapbox 的条款与用量计费。
新工程的 Mapbox 渲染器默认用 Mapbox Standard。从 Layers 面板或 Add Data 打开共享的 Basemaps 面板可以选择 Mapbox 样式(含 Streets)、公开样式或堆叠栅格底图。Mapbox 激活时,所选样式保存为 preferences.map.mapboxStyleUrl,不影响共享的 MapLibre/Cesium 底图;所有 Mapbox 面板共享该选择。
8.6.2 支持的路径(要点)
- 原生 GeoJSON,含矢量导入器物化出的数据:点、线、面、拉伸、标注、数据驱动颜色、不透明度与过滤;
- 远程矢量 PMTiles(Mapbox GL JS 原生档案读取器);
- LiDAR(LAS/LAZ、COPC)与标准 3D Tiles(deck.gl 叠加层);
- Add Data → Deck.gl Layer 的每种类型,以及 3D Model 背后的 scenegraph 类型、矢量图层的 Z 值与图表渲染——都走同一个共享的交错 deck.gl 叠加层;
- Add Data → DuckDB 的查询图层(面板自己的 deck.gl 叠加层);
- Zarr(含 STAC Zarr 资产)与带时间轴的 NetCDF/HDF、Kerchunk 立方,由
@carbonplan/zarr-layer绘制——一个同时面向 Mapbox 与 MapLibre(球面与墨卡托)的CustomLayerInterface实现; - HTTP(S) 栅格瓦片(XYZ、WMS、WMTS)、带命名 source layer 的矢量瓦片、地理配准图像/视频源;
- 共享的图层/分组可见性、不透明度与顺序;同步或独立的相机;墨卡托/球面投影与 Mapbox 地形;
- 要素拾取、选择高亮、范围绘制、可拖动落点、引擎级图像捕获。
8.6.3 Mapbox 专属的取舍
| 项 | 行为 |
|---|---|
| MBTiles / 高斯泼溅 | Add Data 里可见但禁用,带 Mapbox 兼容性提示 |
| KML/KMZ | 走宿主 KML 导入器(与拖拽同一条路径) |
| 离线 PMTiles 底图 | 仅有 MapLibre 实现;Mapbox 面板会回退到默认底图并打控制台警告 |
| 自定义地形源(COG) | 无 raster-dem 源可承载,地形设置里的 “Terrain source” 一节被隐藏并说明原因 |
| 混合模式 | 不复现,Style 面板在设置了混合模式的图层上会说明 |
| 大 GeoJSON(> 50,000 要素) | 保留 mapbox-gl 自己的 GeoJSON source(它已在 Worker 里用 geojson-vt 分块) |
| 球面按钮 | Mapbox 没有自己的球面控件,引擎挂一个替身复刻 MapLibre 的按钮标记 |
| 归属控件 | 不可隐藏;Mapbox 画自己的 logo;Maptoolkit logo 仅 MapLibre |
| Add Data 时序 | 菜单会等 Mapbox 加载完成再接受导入,早期点击不会丢请求 |
性能对照(仓库文档给出的真实测量,200,000 个点):
| 操作 | Mapbox | MapLibre |
|---|---|---|
| 添加图层 | 约 2.1 s | 约 1.5 s |
| 重设样式 | 约 0.9 s | 约 0.8 s |
| 数据编辑 | 约 2.1 s | 约 1.2 s |
| 最长主线程卡顿 | 约 0.8 s | 约 0.85 s |
警告:GeoLibre 自身是 MIT,但 Mapbox 渲染器依赖的 Mapbox GL JS v3 不是开源软件,按 Mapbox 服务条款分发与计费。它是
@geolibre/map的运行时依赖,因此 npm 消费者与桌面/Web 发行版都包含它——但在你打开 Mapbox 面板之前不会有任何 Mapbox 代码运行。商用前请自行核对条款。
8.7 CesiumJS:原生三维球
8.7.1 无 token 也能用
球面不需要 Cesium ion token 就能工作:
- 把工程底图当作基础影像铺上去;
- 用无密钥的 Terrarium 高程(或配置的 COG)作地形;
- 显示一条指向 token 设置的提示。
token 带来三样东西:Cesium World Terrain、Ion World Imagery(作为”底图没有栅格形式”时的回退),以及 ion 托管资产的访问权。Web 版内置一个演示 token;桌面与移动版需要你自己的。
token 填在 Settings → Environment Variables → Cesium Ion token,存在本机。开发者也可以在构建时设置 CESIUM_TOKEN 或 VITE_CESIUM_TOKEN,或在运行时通过环境变量界面注入而不重新构建。
8.7.2 支持的路径
- 带样式的 GeoJSON:分类、分级、规则、表达式、比例、标记、聚合、标注、图案、拉伸、Z 值感知;标注遵循锚点、偏移、最大宽度与数据定义的字号/颜色/不透明度/可见性;
- XYZ、WMS、WMTS、COG、栅格 PMTiles、本地 MBTiles、图像叠加,以及其他通过 GeoLibre 协议桥接的瓦片源;
- 矢量瓦片、矢量 PMTiles、矢量 MBTiles——通过一个隐藏的 MapLibre 渲染器”披挂”上去,以保留其 Style Specification 输出;
- 原生 3D Tiles、Cesium ion 瓦片集与影像、CZML、KML/KMZ、I3S 场景图层、受支持的点云、高斯泼溅瓦片集;
- 球面原生拾取与高亮、范围绘制、可拖动落点、截图、打印布局、视频与相机巡游、地形感知的高程剖面、环境效果。
8.7.3 标注与符号的边界
| 能力 | 球面上 |
|---|---|
| 字段/表达式标注、光晕、锚点、偏移、最大宽度 | 有 |
| 数据定义的字号、颜色、不透明度、可见性 | 有 |
| 旋转 | 无 |
| 沿线绘制文字 | 无(线标注落在中点) |
| 碰撞避让 | 无(始终允许重叠) |
| 优先级表达式 | 无 |
| 地球背面的点与标注 | 隐藏(按地平线做深度测试,见 cesium-horizon.ts) |
| 热图渲染器 | 无球面形态,按普通圆绘制 |
8.7.4 图层同步与逐要素求值
CesiumLayerSync(packages/map/src/cesium-layer-sync.ts)像 MapController.syncLayers 那样把 GeoLibreLayer[] 协调到球面上,并复用同一套逐面板可见性覆盖与分组效果。
GeoJSON 的逐要素样式由 createFeatureStyleResolver(packages/map/src/feature-style.ts,issue #2278)求值:它用样式规范引擎评估与 2D 地图相同的那批 MapLibre 表达式——单值、分类、分级、带逐规则符号覆盖的规则、表达式模式、simplestyle 属性、比例尺寸、米制描边——因此面填充与轮廓、线宽与箭头装饰、圆半径、分类标记精灵、填充图案、图层缩放范围(共享的 DistanceDisplayCondition)都与 2D 地图一致。
几个实现上的取舍值得知道:
- 纯点图层若
pointRenderer: "cluster",用数据源的EntityCluster聚合,气泡与缩写计数按 2D 聚合图层的样式绘制,并在超过clusterMaxZoom后关闭; - 纯点图层超过 50,000 要素时不再用实体,改用一个
PointPrimitiveCollection,图元在id上携带要素引用,因此拾取、高亮与过滤仍然可用(cesium-points.ts,issue #2282); - 瓦片化的矢量图层通过一只隐藏的单瓦片 MapLibre 地图披挂:逐瓦片
jumpTo到中心、等idle、读回画布,再经ProtocolImageryProvider喂给 Cesium,于是 Style Spec 从未被重新实现; - 披挂内容是平面的,标注按瓦片放置,字形来自远程字体主机——因此离线查看本地档案时披挂没有文字,披挂内容上的拾取也被跳过。
8.7.5 场景、时间与相机
- 球面提供 3D、2D 与 Columbus 三种场景模式,以及地形与原生球面光照;
- 相机被翻译成与 GeoLibre 共享的经度、纬度、缩放、方位角、俯仰;
- 混合面板按地面分辨率同步(见 8.3.3)。
时间维度上,CZML 是 Cesium 原生的”动态场景”格式:轨道、轨迹、车辆路径这类随时间变化的场景以文档形式载入,由球面的时钟驱动。时间轴插件在各引擎下的适配不同(见 8.10)。
8.7.6 插件控件在球面上的落法
球面上的插件控件拿到的是一张形状像 MapLibre 的地图(issue #3088):相机、指针事件与 DOM 作用于球面,而它的 Style Spec 调用(addSource、addLayer、setPaintProperty、getStyle…)被记录进一份从不真正绘制的样式。这份样式通过两条路到达球面:
- 插件镜像进 store 的图层(Web Services 的 WMS 或瓦片图层、STAC 足迹)由球面的图层同步按 store 记录绘制;
- 其他 GeoJSON 图层(格网、经纬网、选择轮廓、绘制预览)作为贴地实体绘制:填充、线、圆、文字都支持过滤、缩放范围、数据驱动与缩放表达式、以及逐要素的文字锚点与偏移;线与面的边遵循等角航线,与 2D 地图上的直段一致;越过地平线的点与标注不画。
不支持的:图标、填充图案、拉伸、沿线文字;标注没有碰撞处理。自定义图层没有 WebGL 上下文可渲染,addLayer({ type: "custom" }) 会抛错,需要它的控件无法挂载——这正是 LiDAR 与高斯泼溅面板在球面上被禁用的原因。
图层级事件(map.on("click", layerId, …)、mouseenter、mouseleave)与点的 queryRenderedFeatures 由实际绘制它的那条路回答,包括 MapLibre 为 store 图层派生的 layer-<id>-fill 这类 id。dragPan.disable() 与 scrollZoom.disable() 会暂停球面自己的相机输入,好让控件的框选或画线拿到拖拽。
8.7.7 加载与架构
Cesium 的 JS 只在第一个 Cesium 画布挂载时加载。它的 Workers、Assets、Widgets 与应用一起暂存,由 CESIUM_BASE_URL 解析;切换到别的引擎会卸载画布并释放渲染资源。
CesiumEngine 实现共享的 MapEngine 接口,CesiumLayerSync 把工程图层翻译成 Cesium 数据源、影像图层与图元。
CesiumJS 是 Apache 2.0 开源;Cesium ion 是独立托管服务,有自己的账号条款与用量限制。
8.8 ArcGIS Maps SDK for JavaScript
8.8.1 什么都不打包
@arcgis/core 解包后 84 MB、18,810 个文件。打包进构建会让 Web 构建、Python wheel 与桌面安装包各涨几十兆。因此引擎在第一个 ArcGIS 面板挂载时,从 Esri 的版本化 ES 模块 CDN 导入所需模块(https://js.arcgis.com/<version>/@arcgis/core/…),随包只发 packages/map/src/arcgis-*.ts 的适配代码。
由此产生四条后果:
- 第一个 ArcGIS 面板需要网络,桌面版也一样。之后 Service Worker 会缓存 SDK 模块与样式表(
geolibre-arcgis-sdk),SDK 本身可以离线启动;Esri 底图、瓦片服务与远程图层仍然需要网络。 - SDK 版本钉在
packages/map/src/arcgis-sdk.ts的ARCGIS_SDK_VERSION,升级是一次有意的改动。 - Esri 把 ES 模块 CDN 定位为原型路径,每个会话会打印一次 “Only use ES modules from ArcGIS CDN for testing”;其受支持的生产路径是 npm 构建,而这正是本集成为控制体积所回避的。首次加载会拉几百个小模块,因此第一个 ArcGIS 面板比 Mapbox 慢。
- 桌面应用(
tauri.conf.json)与 Docker 镜像(docker/nginx.conf)的 CSP 在script-src与font-src里放行https://js.arcgis.com/;SDK 的样式表以文本形式取回后内联。
若 SDK 无法从 CDN 加载,地图横幅提供 Retry,它会重载页面——浏览器会把失败的模块导入保留到页面生命周期结束,只有重载才能重新取。
8.8.2 有 key 与无 key
渲染器不需要 key 就能工作:
- 新工程显示无密钥的 Esri World Imagery 作 ArcGIS 底图;
- 从共享 Basemaps 面板选的底图会被翻译成 SDK 能绘制的瓦片(与三维球用的是同一套翻译),没有瓦片形式的样式以 World Imagery 回退;
- 你自己的图层走 SDK 自己的图层类。
ArcGIS API key 带来 Esri 的底图样式。创建路径:ArcGIS 账号(ArcGIS Online 或 ArcGIS Location Platform)→ Content → New item → Developer credentials → API key credentials,勾选 Basemaps 权限,然后把 key 粘进 Settings → Environment Variables → ArcGIS API key。
有 key 时,新工程的 ArcGIS 渲染器用 ArcGIS Streets;Change background 提供 Streets、Navigation、Topographic、Light Gray、Dark Gray、Imagery、Imagery (no labels)、Oceans、Outdoor、OpenStreetMap。选择保存为 preferences.map.arcgisBasemap(例如 arcgis/streets、arcgis/imagery、osm/standard);若此时又从共享 Basemaps 面板选底图,这个覆盖会被清除,面板重新跟随共享底图。
8.8.3 支持的路径与”烘焙”策略
SDK 没有 MapLibre Style Spec,所以引擎的做法的逐要素求值后烘焙:它用样式规范引擎评估 @geolibre/core 为 2D 地图构建的同一批 MapLibre 表达式,把答案烘进要素,再把图层的渲染器设为”按符号键取值”的唯一值渲染器。于是:
- 点、线、面符号,填充与描边不透明度,圆半径,标注(字段或表达式、字号、颜色、光晕、位置、偏移、旋转、大小写变换),以及分类、分级、规则、表达式、simplestyle 五种数据驱动颜色都能渲染;
- 图层过滤、快速过滤、时间轴过滤与嵌入过滤在要素到达 SDK 之前同样被逐要素求值;
- 依赖缩放的表达式(米制描边、逐规则缩放范围)在整数缩放变化时重新求值;
- 符号化”附加包”用伴生图层绘制:反向填充做成”世界蒙版挖洞”(并被夹到 Web Mercator 纬度上限)、几何生成器(质心、包围盒、凸包、缓冲区)自成一套填充/描边/比例圆、线装饰用 CIM 标记线(仅平面
MapView,场景里 SDK 不画 CIM 线符号)、标注去重用一条”仅标注”图层标注聚合点。
服务能力的对接也有明确规则:
| 服务/形态 | 处理 |
|---|---|
| FeatureServer 图层 | 走 SDK 的 FeatureLayer;过滤(快速/表达式/时间/嵌入)变成服务的 SQL definitionExpression |
| 服务无 SQL 形式的过滤 | 在地图横幅报告,服务按无过滤绘制 |
MapServer 单个子图层(MapServer/2) |
也当 FeatureLayer |
| 缓存 MapServer / ImageServer | 存为瓦片模板;动态的(子图层、渲染规则、无可用缓存)存为 /export / /exportImage 的 bbox 模板 |
| 要素回读 | 越过服务单次记录上限分页,最多 50 次请求 |
矢量瓦片模板无法表达的({bbox-epsg-3857}、TMS、{-y}、{quadkey}、512 px、源 minzoom/maxzoom、多模板) |
走自定义瓦片图层,像 MapLibre 那样逐瓦片请求,裁剪 512 px 瓦片并在 maxzoom 之后过缩放 |
8.8.4 二维与三维
SDK 用两个不同的视图类画平面地图与三维场景,因此 ArcGIS 面板会按工程的地图偏好挑一个,并在选择变化时重建视图(相机随之带过去):
| 投影 | 地形 | 视图 |
|---|---|---|
| 球面 | 开或关 | SceneView,global(三维球) |
| 墨卡托 | 开 | SceneView,local(平面三维图) |
| 墨卡托 | 关 | MapView(平面二维图) |
新工程用球面投影,所以 ArcGIS 面板默认打开就是三维球。 指北针下方的球面按钮切换投影(与 MapLibre 一样,分屏面板的按钮只切自己),Controls → Terrain 开关地形。
场景里的关键行为:
- 相机可俯仰(右键拖动或工程保存的 pitch),受工程最大 pitch 限制;状态栏显示相机海拔;
- 光照跟随相机(SDK 的虚拟光照),因此可见地图整体被照亮——SDK 默认的模拟太阳会让地球一部分停在夜面;
- 地形铺在 Esri 的 World Elevation 服务上,不需要 API key;
- Controls → Terrain exaggeration 既缩放高度,也接受 EPSG:3857 或 EPSG:4326 的 COG DEM(本地或远程,与其它引擎共用读取器;本地文件留在设备上)。该来源与夸张系数在本次会话内跨平面图、局部场景、球面切换保持,但不保存进工程;DEM 缺像素与 COG 之外区域按 0 米处理;Use global terrain 恢复 Esri 的 World Elevation;
- 样式开了 3D extrusion 的面图层画成三维体块,高度、基高与颜色与 MapLibre 的 fill-extrusion 一致;在二维
MapView上仍是平涂; - 3D (Z values) 把矢量坐标放到其绝对海拔,按配置的垂直缩放与偏移;选择高亮使用同一套变换后的坐标。
8.8.5 ArcGIS 的投影
MapLibre 与 Mapbox 只画 Web Mercator 与球面,而 SDK 能在它按 well-known ID 认识的任意投影坐标系里画平面 MapView(issue #2708)。
Settings → Map → ArcGIS map projection 提供:
| 投影 | WKID |
|---|---|
| Spilhaus | 54099 |
| Equal Earth | 8857 |
| Robinson | 54030 |
| Winkel Tripel | 54042 |
| Mollweide | 54009 |
| Plate Carrée | 4326 |
| 北极极射赤面 | 3995 |
| 南极极射赤面 | 3031 |
| Custom WKID… | 任意其他 Esri 或 EPSG 代码 |
选择保存为 preferences.map.arcgisWkid;缺省即 Web Mercator。选投影会关掉球面(只有平面图用得上这个投影),球面按钮仍可在球面与投影之间来回切。
其他投影下的行为:
- 矢量图层(GeoJSON、要素服务、符号化附加包的伴生图层)由 SDK 的
projectOperator在客户端重投影(该算子只随投影地图一起加载); - COG 会被”翘曲”进该投影(
arcgis-reprojected-tiles.ts):SDK 不能重投影瓦片图层,所以 COG 图层改为按视图投影切片,再用 Web Mercator 地图上cog-tiler-wasm渲染出的同一批瓦片填充每片——把瓦片上 33×33 个点投影到经纬度,点之间插值;跨越投影接缝的格子(Spilhaus 会把陆地切开)逐像素投影;每片按其被投影拉伸的程度选择源缩放级别。Web Mercator 瓦片止于 ±85.05°,因此该纬度以上的极冠区域留空; - 不画底图。Esri 底图样式与 XYZ 瓦片不会被翘曲,显示的是 Blank 背景色;其他瓦片数据图层(XYZ、WMS 瓦片、PMTiles、Zarr)也不画;
- 相机、指针坐标、识别、选择与范围绘制在投影与经纬度之间换算,因此共享相机、其他分屏面板与保存的视图仍以度为单位;
- 平面图开着地形时仍是
MapView(局部场景不能用该投影),地形只在地球或 Web Mercator 下恢复作用; - 未知 WKID 会在图上显示 SDK 的错误;
- MapLibre、Mapbox、Cesium 面板忽略这个设置。
8.8.6 deck.gl 与”未支持”清单
Add Data → Deck.gl Layer / 3D Model 在主平面图与局部三维场景上可用。共享叠加层渲染保存的可视化与模型,跟随可见性、不透明度与 deck.gl 层之间的顺序,并在切换渲染器时释放 GPU 资源。但它是一个原生 SDK 图层,deck.gl 图层无法与原生 ArcGIS 图层逐个交错。适配器用 @deck.gl/arcgis 9.4.0 里 MIT 许可的合成器,避免打包 @arcgis/core;局部三维渲染用的是上游实验性相机近似,在极端相机角度会漂,且不与地形深度缓冲共享。全局场景与分屏面板不承载该叠加层,菜单与图层徽标会反映这条限制。
明确尚不支持的:
- 高斯泼溅、Cesium 专属源、STAC 搜索结果、视频叠加(Add Data 置灰,图层面板标 No ArcGIS);
- Esri 矢量瓦片服务自己的图标与标注图层(其存档样式里没有 sprite 与 glyphs),任何视图下都不行;
- 测量(Controls 里置灰)与几何编辑(会提示渲染器不支持);
- 图表、标注优先级表达式(SDK 没有逐要素标签优先级)、
SceneView里的聚合、填充图案、混合模式与线装饰、平面图上的拉伸; - 任意 MapLibre 自定义图层与渲染 API。
导航限制也与 MapLibre 略有差异:越过缩放范围或受限边界的视图会缓回范围内(平面上滚轮也会停在限制处),边界约束的是视图中心而不是整个视口,且 SDK 总会跨 180° 经线环绕——关闭世界复制只是钳制应用自己施加的视图。
注意:ArcGIS Maps SDK 由 Esri 按其自身条款分发,不是开源许可。GeoLibre 不再分发它;SDK 在运行时由用户浏览器从 Esri 的 CDN 取回,Esri 底图与位置服务的使用受 API key 所属账号约束。
8.9 三维内容:瓦片、点云、泼溅、地形与球面
8.9.1 3D Tiles 与 I3S
| 引擎 | 3D Tiles 的实现 | I3S |
|---|---|---|
| MapLibre | deck.gl Tile3DLayer |
loaders.gl I3SLoader(填 .../SceneServer) |
| Mapbox | 共享交错 deck.gl 叠加层 | 同上 |
| Cesium | 原生 Cesium3DTileset,直接消费 URL、请求头与高度偏移 |
Cesium 自己的 I3SDataProvider |
| ArcGIS | deck.gl 叠加层(限主平面图/局部场景) | 需其他渲染器 |
Cesium 的瓦片集还有一层样式翻译(issue #2290):图层的颜色表达式与组合后的要素过滤会被翻译成 3D Tiles 的样式语言,作为 Cesium3DTileStyle 应用,于是瓦片集能像拉伸矢量图层那样分类、分级或按规则树着色;图层不透明度作为一次白色相乘,让带纹理的瓦片淡出而不染色。无法翻译的颜色或过滤会让瓦片集保留自己的颜色并显示全部要素——而不是猜一个。样式面板列出的属性名来自第一个渲染出来的瓦片(瓦片集把 schema 放在瓦片里而不是图层记录里),发布到 metadata.fields。
提示:3D Tiles 在 Mapbox 与 ArcGIS 上都要求墨卡托。加入这类叠加层时球面视图会切回墨卡托。
8.9.2 点云
点云有两条完全不同的路径,别混淆:
| 形态 | 入口 | 说明 |
|---|---|---|
| LiDAR 图层 | Add Data → LiDAR Layer / 拖入 LAS、LAZ、COPC / EPT 的 ept.json |
走 maplibre-gl-lidar + deck.gl,MapLibre/Mapbox 可用,ArcGIS 走 deck.gl 适配 |
| 3D Tiles 形式 | Add Data → 3D Tiles Layer | Cesium 原生(含 pointCloudShading 的眼穹光照) |
| Cesium 原生点云 | 图层记录 | COPC 在浏览器里用 copc 包解码、按档案 WKT 重投影;EPT 按视图流式;纯 LAS/LAZ 整份下载 |
Cesium 点云解码的关键数字(来自 architecture.md):
| 项 | 值/行为 |
|---|---|
| 纯 LAS/LAZ 上限 | 512 MB(含解码后大小) |
| 抽稀 | 同一预算内每 n 个点取一个 |
| COPC/EPT 流式 | 相机停稳后按”最需要”顺序(按节点点间距在该距离下的粗糙度)选节点;倾斜视图优先细化脚下而非地平线 |
| 解码节点缓存 | 300 万点,平移回来免费 |
| 图元上限 | 150 万点 |
| LASzip 节点 | 每个上限 64 MB,一次并发取 4 个 |
| 拒绝的 EPT 形式 | binary 与 zstandard |
| 噪声剔除 | 丢弃 ASPRS 噪声类 7 与 18 |
| 高度色带 | 铺在解码高度的第 1–99 百分位,避免离群值压平颜色 |
| 无 CRS 的点云 | 拒绝,不会当成度来读 |
8.9.3 高斯泼溅
- MapLibre:
maplibre-gl-splat控件,存储层类型gaussian-splat; - Cesium:当资产本身就是 3D Tiles 瓦片集时,走原生
KHR_gaussian_splatting分支; - Mapbox 与 ArcGIS:不支持(Add Data 置灰;ArcGIS 另在图层上标 No ArcGIS);
- 原始
.ply/.splat文件在地球上被跳过,在 2D 面板里渲染。
8.9.4 地形
| 引擎 | 默认地形 | 自定义 DEM | 夸张 | 备注 |
|---|---|---|---|---|
| MapLibre | GeoLibre 全局源 | COG URL / 本地文件 / 已加载栅格图层 | Controls → Terrain 双击设夸张 | 球面投影下也可用 |
| Mapbox | Mapbox 地形 | 不支持(Terrain source 一节被隐藏) | 有 | 地形是场景设置,Controls 直接切换、没有按钮 |
| Cesium | 无密钥 Terrarium,或配置的 COG | COG;ion 地形资产 | 有 | ion token 才有 World Terrain |
| ArcGIS | Esri World Elevation(无需 key) | COG(EPSG:3857/4326) | Controls → Terrain exaggeration | 来源与夸张不写进工程 |
8.9.5 行星与椭球
天体切换在两个地方出现:Layers 面板标题栏的行星切换器(轨道图标),以及 Change background / New project 对话框里的天体分组。
切天体的效果是工程级的:
- 换底图(月球与火星来自 OpenPlanetaryMap,其他来自 USGS Astrogeology 并实时重投影到 Web Mercator);
- 设定工程的椭球体,此后距离、面积、比例尺都按该天体半径计算;
- 在 MapLibre 的
mapCrs预设里选行星投影时也会同时设定天体。
相关常量在 packages/core/src/ellipsoids.ts(ELLIPSOIDS)与 map-crs.ts。地球用 WGS 84;其他天体用 IAU 采用的数值,其中只有地球与火星明显扁(两极压扁),其余建模为球体:
| 天体 | 长半轴(m) | 1/f |
|---|---|---|
| Earth (WGS 84) | 6,378,137 | 298.257223563 |
| Moon | 1,737,400 | 0(完美球) |
| Mars (IAU 2000) | 3,396,190 | 169.894447 |
| Mercury | 2,439,400 | 0 |
| Venus | 6,051,800 | 0 |
Cesium 与 ArcGIS 的球面/场景也读同一份工程状态。
警告:换天体后量测”不对”,九成是椭球体没跟着换,或者工程里残留了另一颗星球的投影预设。先看 Layers 面板的行星图标,再看 Settings → Map 里的 CRS/WKID。
8.9.6 Cesium 的时间与相机
时间维度在四种引擎里的落点差别很大:
| 引擎 | 时间相关能力 |
|---|---|
| MapLibre | 时间轴插件(COG、XYZ/WMTS、WMS-Time、按时间过滤的 GeoJSON),图层上的 metadata.timeBinding 与瞬态 timeFilter |
| Mapbox | 与 MapLibre 同源的时间轴插件;绑定到 gpu/wasm 引擎的 COG 会通过 TiTiler 重新加入;mosaic 清单会被丢弃并打控制台警告 |
| Cesium | 原生 CZML 动态场景(轨道、轨迹、路径),球面时钟驱动;KML 的 <TimeSpan>/<TimeStamp> 与 GroundOverlay 动画 |
| ArcGIS | 时间轴过滤会变成要素服务的 SQL definitionExpression;不支持的过滤在横幅报告 |
相机方面,Cesium 的换算已在 8.3.3 说明。还有两条实用差异:
- Cesium 需要 ion token 才能用 Ion 地形资产与 ion 影像;没有 token 时球面仍能打开,只是少了这两项;
- ArcGIS 在球面上把工程的缩放范围映射成高度范围,而平面图上仍是缩放范围。
8.10 插件在各引擎的适配
8.10.1 声明式适配
插件通过清单里的 engines 列表声明支持哪些渲染器:
- 省略
engines时默认为 MapLibre; - Plugins 菜单与命令面板会把不支持的项置灰;
- 不支持的插件被挂起,而不是让它在运行中失败;
- 球面与 ArcGIS 上的控件拿到一张”形状像 MapLibre”的地图(
getControlMap,见packages/plugins/src/plugins/style-map.ts),Style Spec 调用被记录进影子样式(见 8.7.6 与 8.8.6 的对应段落)。
插件 API 里的相关成员(节选自 packages/plugins/src/types.ts):
export interface PluginManifest {
// ...
engines?: MapRendererKind[]; // 省略时默认为 MapLibre
}
// 插件拿到的应用门面(同文件):
// getMap?: () => MapLibreMap | null;
// getMapRenderer?: () => MapRendererKind; // 包含画布正在被替换的瞬间
// getViewBounds?: () => [number, number, number, number] | null;
app.getMap() 只对 MapLibre 有效,在球面上恒为 null。因此可移植插件应改读 getStyleMap(app)(回退到 app.getMapboxMap()),停留在两个引擎共享的 Style Spec 面上;需要视口范围时用 getViewBounds() 而不是 getMap()?.getBounds()——后者在球面上静默失败,会让”只搜当前视图”的勾选框形同虚设。具体规则与审计方式见插件 API 文档与第21章 插件系统与插件开发。
8.10.2 常见插件的引擎覆盖
| 插件/控件 | MapLibre | Mapbox | Cesium | ArcGIS |
|---|---|---|---|---|
| Layer Control | 有 | 有(同一 host) | 无(无样式文档) | 有(SDK 原生图层列表) |
| Basemaps | 有 | 有 | 有 | 有 |
| Web Services 目录族 | 有 | 有(GeoLens 私有栅格除外) | 有(镜像/实体) | 有(控件代理) |
| USGS 3DEP | 有 | 声明支持(未在浏览器验证) | 有 | 有 |
| Gridlines / DGGS 网格 | 有 | 有 | 有(实体) | 有 |
| Time Slider | 有 | 有 | 原生时间能力 | 转为 definitionExpression |
| Geo Editor | 有 | 有(geo-editor-mapbox.ts) |
有 | 无(几何编辑不支持) |
| Measure | 有 | 有 | 有 | 置灰 |
| Elements / 标注 | 有 | 有(annotation-marker.ts) |
有 | 有(文字) |
| Street View | 有 | 有(createMarker 工厂) |
有(cesium-dom-marker.ts) |
置灰 |
| Layer Swipe | 有 | 有(已知缺陷 #2430) | — | 置灰 |
| Atmospheric Effects / Sun / Flight Simulator | 有 | 有 | 有(原生环境插件) | 置灰 |
| LiDAR / 高斯泼溅面板 | 有 | 无(高斯泼溅)/ 有(LiDAR) | 无法挂载 | 有(deck.gl) |
| 私有 GeoLens 栅格 | 有 | 需 MapLibre(请求头注入) | 有 | 有 |
注意:这张表是”能不能用”的粗略索引,具体到某个插件版本仍以其清单里的
engines为准。菜单置灰时通常会带一句原因,那比这张表更新。
8.10.3 一次真实审计的数字
仓库文档记录了一次 2026 年 9 月的 Mapbox 插件审计:逐个打开了 Add Data 的 37 个启用条目,并对公开数据路径做了实际导入验证(例如 US states GeoJSON 52 个要素、Monaco OSM PBF 得到 4,249 点 / 4,002 线 / 2,341 面、Autzen COPC 的 10,653,336 个点)。验证分级明确写在表里——“面板打开了”不算导入成功。
这些检查作为可选的回归套件保留:
# 需要环境里有公开 token
npm exec -- playwright test e2e/mapbox-add-data.spec.ts
# PMTiles / COPC / 3D Tiles 的存取与重开
npx playwright test e2e/mapbox-archives-3d.spec.ts
# ArcGIS:需要 ARCGIS_API_KEY(或 ARCGIS_E2E=1 跑无密钥覆盖)
npx playwright test e2e/arcgis-renderer.spec.ts
8.11 性能与限制
8.11.1 逐引擎要点
| 引擎 | 要点 |
|---|---|
| MapLibre | 要素超过 50,000 时改在客户端切成矢量瓦片(geojson-vt + vt-pbf,自定义协议喂给地图);瓦片索引刻意不进 store |
| Mapbox | 大 GeoJSON 用 mapbox-gl 自己的 source(它已在 Worker 里分块);带来更短的最长主线程卡顿 |
| Cesium | 纯点图层 > 50,000 要素改用单个图元集合;点云有 300 万点缓存与 150 万图元上限;LAS 上限 512 MB |
| ArcGIS | 每个矢量源图层各自一个原生 VectorTileLayer,源图层多的档案解码与内存开销更大——只启当前视图需要的层 |
| 共享 | deck.gl 叠加层必须共用一个实例,否则后创建的可能覆盖前一个管理的图层 |
8.11.2 引擎切换的成本
切换引擎不是换一层皮:
- 旧引擎卸载,画布与 WebGL 上下文释放(不是隐藏);
- 新引擎若为 Mapbox/Cesium/ArcGIS,可能触发首次加载(见 8.4.2);
- 不支持当前引擎的图层留在工程里、在图层上标记(MapLibre 侧常见 2D only,ArcGIS 侧 No ArcGIS),切回兼容引擎后回来;
- 分屏面板各自保持独立的图层可见性覆盖。
8.11.3 已知的引擎特有性能问题
- Linux/WebKitGTK 桌面版:空白地图稳定 60 FPS,但只要加载瓦片图层,加载期间帧率会掉到个位数,加载完立刻回到 60。根因是 WebKitGTK 在主线程处理每张新瓦片的纹理上传(一个瓦片集成周期约 125 ms,而 Chromium 只要几毫秒)。缓解方向(加大瓦片缓存、512 px 栅格瓦片、关掉淡入)在文档里写明尚未实现——详见第25章 性能优化与故障排查。
- ArcGIS 首次加载:从 CDN 拉几百个小模块,第一个面板明显慢于 Mapbox;这是打包体积与首屏时间的取舍。
- ArcGIS 矢量瓦片档案:源图层多时解码工作按层数放大。
- Cesium 披挂内容:大量瓦片矢量图层会触发隐藏 MapLibre 地图的逐瓦片渲染与回读,代价高于 2D 直接绘制。
8.11.4 测试与门禁
| 位置 | 覆盖内容 |
|---|---|
tests/layer-support-matrix.test.ts |
把每张图层支持表钉到其引擎的派发逻辑上 |
tests/arcgis-layers.test.ts |
图层编译器与底图规划 |
tests/arcgis-engine.test.ts |
用假 SDK 驱动引擎 |
tests/arcgis-renderer.test.ts |
工程格式、设置与加载器边界 |
e2e/arcgis-renderer.spec.ts |
对真实 CDN 的可选浏览器检查 |
e2e/arcgis-offline.spec.ts |
生产 CSP 下的无密钥冷启动、缓存启动、CDN 不可用时的可见错误 |
e2e/mapbox-add-data.spec.ts |
Mapbox 下的 Add Data 边界与四个集成路径 |
e2e/mapbox-archives-3d.spec.ts |
PMTiles / COPC / 3D Tiles 的存取与重开 |
前四个不触网;后四个是可选的浏览器检查,缺凭据时自动跳过。
8.12 排错
| 现象 | 最可能的原因 | 处理 |
|---|---|---|
| ArcGIS 面板一片空白,横幅有错误 | 首次加载需要网络;CSP 或 CDN 不可达 | 点横幅的 Retry(会重载页面);检查 CSP 是否放行 js.arcgis.com |
| Mapbox 地图不显示并提示设置 | 缺 access token | 填 Settings → Environment Variables → Mapbox token 并保存 |
| Cesium 能打开但没有世界地形 | 没有 ion token | 接受无 token 的 Terrarium 地形,或填 token |
| 图层上有 2D only 标记 | 当前引擎不支持该图层类型 | 切回 MapLibre;或改用该引擎支持的等价形态(如把点云作为 3D Tiles 加入) |
| 图层上有 No ArcGIS 标记 | ArcGIS 明确不支持(高斯泼溅、STAC 结果、视频等) | 换引擎 |
| 切换引擎后图层”消失” | 该图层在当前引擎不支持,仍在工程里 | 切回兼容引擎;看图层徽标 |
| 分屏相机抖动 | 相机同步的回落检查被绕过 | 关掉 syncView 或用 View → Split View 重新建布局 |
| Mapbox 下混合模式无效 | Mapbox 不复现 Blend | 在 MapLibre 下配置,或接受平涂 |
| ArcGIS 球面下 Measure 置灰 | 测量走 MapLibre/Mapbox 的样式 API | 切到平面引擎测量 |
| ArcGIS 换投影后底图消失、极冠空白 | 其他投影下不画底图,Web Mercator 瓦片止于 ±85.05° | 属预期行为 |
| Cesium 上标注不旋转、不避让 | 球面没有旋转与碰撞处理 | 属预期;需要精细标注用 2D 引擎 |
| 3D Tiles 一加入球面就切回平面 | deck.gl 叠加层要求墨卡托 | 用 Cesium 的原生 3D Tiles 分支 |
8.13 本章小结
- 一个
MapEngine接口、四个实现;能力用冻结的布尔量声明,UI 按能力而不是按引擎名判断。 - 主图用
primaryRenderer、分屏用SecondaryMapView.viewKind;切换会卸载旧引擎,不支持的图层留在工程里。 - 相机同步按地面分辨率(米/像素),不是按 zoom,因此不同高度/尺寸的面板比例尺仍然对齐。
- 凭据只存本机:Mapbox token 必需、Cesium ion token 可选、ArcGIS API key 可选;三者都不写进工程文件。
- 加载策略各不同:MapLibre 随包;Mapbox 独立分块(约 1.91 MB,gzip 532 KB);Cesium 首个画布挂载时加载并暂存资源;ArcGIS 从 CDN 取模块并缓存。
- MapLibre 是默认与最全,也是唯一支持任意平面 CRS(含行星投影)的引擎;其他 CRS 下 XYZ/矢量瓦片/底图不画。
- Mapbox 的边界:MBTiles、高斯泼溅、ion/CZML 不可用;混合模式不复现;自定义 COG 地形源被隐藏;Mapbox GL JS v3 非开源。
- Cesium 是原生球:GeoJSON 逐要素求值、瓦片矢量”披挂”、3D Tiles 样式翻译、点云流式与上限、标注无旋转与碰撞。
- ArcGIS 逐要素烘焙 + 唯一值渲染器;默认打开就是三维球;支持 WKID 投影但投影下不画底图;测量与几何编辑不可用。
- 三维内容按引擎分流:3D Tiles 四种引擎都有但实现不同;点云分 LiDAR 图层与 3D Tiles 两条路;高斯泼溅只有 MapLibre 与 Cesium(原生瓦片)。
- 地形四套来源,其中 Mapbox 不支持自定义 COG 地形,ArcGIS 的 COG DEM 不写进工程。
- 插件用
engines声明,不支持时挂起而非失败;app.getMap()仅 MapLibre,可移植插件走getStyleMap/getControlMap。
8.14 实践练习
-
能力对照:把一个含 GeoJSON、COG、3D Tiles、LiDAR 与 KML 的工程在四种引擎间切换,记录每种引擎下哪些图层可用、哪些被标记,并对照 8.2.2 的矩阵解释差异。
-
分屏比例尺:用 View → Split View 建 2×1 布局,左 MapLibre、右 Cesium,打开
syncView,在两侧分别量同一段距离,记录结果并解释为什么按”地面分辨率”同步能让比例尺对齐。 -
凭据与加载:在没有 Mapbox token 的情况下切到 Mapbox,记录界面提示;填入 token 后观察引擎是否重建。再用浏览器开发者工具观察首次切到 ArcGIS 时对
js.arcgis.com的请求,说明为什么第一次明显更慢。 -
Cesium 点云预算:加载一个超过 500 MB 的 LAS 文件与一个同区域的 COPC 档案(Cesium 引擎),分别记录加载行为与内存表现,并解释 512 MB 上限、300 万点缓存与 150 万图元上限各自在什么阶段起作用。
-
ArcGIS 投影:把 ArcGIS 引擎的投影依次设为 Robinson(54030)、Plate Carrée(4326)与 Spilhaus(54099),记录底图、COG 与极区显示的变化,并说明为什么底图在非 Web Mercator 投影下会消失。
-
插件置灰排查:选三个插件分别在四种引擎下打开 Plugins 菜单,记录哪些被置灰,并从
engines声明与能力集两个角度解释原因。
延伸阅读
- 仓库文档:
docs/user-guide/rendering-engines.md、docs/maplibre-renderer.md、docs/mapbox-renderer.md、docs/cesium-renderer.md、docs/arcgis-renderer.md、docs/cesium-parity-testing.md、docs/architecture.md(Rendering-engine model 与 3D globe view 两节) - 源码:
packages/map/src/map-engine.ts(接口与能力集)、map-controller.ts、mapbox-engine.ts、cesium-engine.ts、cesium-layer-sync.ts、cesium-camera.ts、arcgis-engine.ts、feature-style.ts、apps/geolibre-desktop/src/lib/add-data-renderer.ts - 项目主页:https://geolibre.app