第25章 - 性能优化与故障排查

GeoLibre 的性能问题很少是「软件慢」这么简单:它同时跑在浏览器、Tauri WebView、Jupyter 三种宿主里,数据可能来自本地文件、HTTP range 请求、瓦片服务或数据库,渲染由 MapLibre、deck.gl、Cesium、ArcGIS 四套引擎分担。排查时必须先判断瓶颈落在哪一层,否则很容易在错误的方向上优化。

本章先给出一套可复用的性能模型,再给出数据侧与应用侧的可操作清单,最后落到诊断工具与一份逐条覆盖官方 troubleshooting 文档的排障手册。所有阈值、变量名、命令都来自仓库源码与文档:apps/geolibre-desktop/vite.config.ts、boot-budget.json、src/lib/diagnostics.ts、src/lib/project-history-store.ts、src/lib/feature-search.ts、docs/architecture.md、docs/maintenance.md 与 docs/user-guide/troubleshooting.md。

提示:本章的「优化」默认指不损失数据正确性的调整。任何删除属性、简化几何、降低精度的操作都要先留原始副本,并在结果里注明精度损失。


25.1 性能模型:五个瓶颈面

把 GeoLibre 的运行时拆成五层,问题基本都能归到其中一层:

层面 主要成本 典型症状 主要手段
启动 / 包体 首屏必须下载并解析的 JS、WASM 打开应用白屏数秒、首屏很慢 懒加载、分包、boot budget、减小构建体积
瓦片 / 渲染 每张新瓦片的 GPU 上传、矢量解析、绘制 平移时掉帧、缩放卡顿、地图闪烁 换数据格式、减少瓦片数量、换渲染引擎
要素规模 GeoJSON 对象数量、内存占用、DOM 行数 加图层卡、属性表滚动卡、搜索慢 服务端切片、简化、虚拟化、换瓦片
查询 / 计算 DuckDB-WASM 扫描与连接、WASM 引擎 SQL 跑很久、处理工具无响应 减少扫描量、预过滤、用索引格式
持久化 工程序列化、快照、上传 保存失败、自动保存暂停、上传 413 外置数据、拆分工程、提高上限

注意:同一现象可能跨层。例如「地图平移掉帧」可能是瓦片层(WebKitGTK 逐瓦片上传),也可能是要素层(一次渲染 20 万个点)。定位手段见 25.9 节的诊断工具。

25.1.1 先量后调

在改动任何东西之前,先记录三个基线:

  1. 首屏时间:从刷新到地图可交互(可用浏览器 Performance 面板的 First Contentful Paint 与 Long Task)。
  2. 平移帧率:在目标区域来回平移 10 秒,记录最低帧率(25.3 节给了现成的 FPS 计数器)。
  3. 一次典型操作耗时:例如打开工程、跑一条 SQL、切换图层样式。

没有基线就无法判断优化是否有效,也无法在报告中让别人复现。


25.2 启动与包体

25.2.1 首屏预算(boot budget)

GeoLibre 对「应用入口静态导入的 JS」有硬性预算,构建时超标即失败:

预算 上限 设定时的实测
rawBytes 3 MiB(minified) 约 2.2 MB
gzipBytes 900 KiB(gzip level 9) 约 680 kB

预算写在 apps/geolibre-desktop/boot-budget.json,检查逻辑是 apps/geolibre-desktop/vite.config.ts 里的 bootBundleBudgetPlugin。构建失败时会列出最大的几个 boot chunk。这通常意味着一次分包调整把整个懒加载包(Cesium、DuckDB、某个 maplibre-gl-* 插件)拖到了启动路径上。

提示:修法是调整 CODE_SPLITTING_GROUPS / manualChunks 的分组,而不是抬高预算。确实有新代码必须进首屏时,才在 PR 里说明理由并提高预算。

25.2.2 懒加载与分包

GeoLibre 的三种重引擎都走「独立 chunk + 按需加载」:

  • Cesium:约 4.7 MB 的独立 chunk,只有在某个分屏切到 3D 地球时才加载。源码注释记录过一个真实事故:Cesium chunk 一度吞掉了共享 helper,导致「整个 4.7 MB 的 Cesium chunk 在每次启动时都被 modulepreload」。
  • DuckDB-WASM:只在 SQL 工作区或需要时初始化,轻量构建(npm run lite:build)把 wasm 指向 jsDelivr 以缩小自托管体积。
  • 各 WASM 引擎:一律懒加载 + 独立 chunk + 在 Worker 中执行 + 固定版本 + 可替换生命周期,避免占用主线程与首屏渲染路径。

一个容易忽略的点:Worker 不与主构建共享 chunk。Vite 把每个 new Worker(new URL(...)) 当成独立构建,worker 引入的东西会在主线程之外再打一份。仓库里曾因此把 h5wasm(libhdf5,约 4.8 MB)打了两遍——一次给本地 NetCDF 读取器,一次在 netcdf-remote.worker.ts 里。现在主构建把 h5wasm 显式 emit 成 chunk,worker 按 URL 引用(apps/geolibre-desktop/vite-plugins/shared-h5wasm.ts),并且构建会在出现多于一个 hdf5_hl-*.js 时直接失败。

25.2.3 用 bundle report 看真实体积

npm run build
npm run bundle:report

产物是 bundle-report/bundle-report.md 与 .json:dist/assets 下每个 JS/WASM 资产的 raw 与 gzip 体积、从构建后的 index.html 读出并与两条预算对比的 boot 集合,以及 dist 顶层每个入口的体积。CI 的 build job 会把它作为 bundle-report 工件上传并把 Markdown 追加到 job summary,所以两次运行可以直接对比,不必重新构建。

提示:体积异常先看「顶层 dist 入口」和「dist/assets 里同名资源的第二份」。后者往往是 Worker 重复打包,前者往往是整包被某个 import 拖进来。

25.2.4 受限网络的构建选项

企业内网或严格 CSP 场景会用 GEOLIBRE_NO_EXTERNAL_CDN=1 构建,把 GeoLibre 对外部 CDN(unpkg.com、cdn.jsdelivr.net)的引用剥掉。代价是明确的功能降级:storymap HTML 导出、内置目标检测模型、ONNX WASM、3D Tiles 的 Draco/KTX2 解码器、gdal3.js 导出会被禁用或降级;Pyodide 只丢失默认索引 URL,设置 VITE_PYODIDE_INDEX_URL 指向镜像仍可用。同时强制 GEOLIBRE_PGLITE_CDN=0、GEOLIBRE_CEREUS_CDN=0、GEOLIBRE_GDAL_CDN=0、GEOLIBRE_DUCKDB_WASM_CDN=0,于是 PGlite/PostGIS、CereusDB、DuckDB-WASM 改为随构建 vendored 到 /assets/(构建体积变大,但不联网)。

警告:某些第三方包(DuckDB-WASM、loaders.gl、maplibre-gl-3d-tiles)自带内部 CDN URL,这个开关去不掉。完全离线部署要逐项验证。


25.3 瓦片、缓存与渲染管线

25.3.1 一张瓦片要经过什么

  1. 网络请求(HTTP/2 或 range 请求)。
  2. 解析:栅格解码成纹理,矢量瓦片在 Worker 里解析并构建绘制用的 bucket。
  3. GPU 上传:纹理或顶点缓冲进入显存。
  4. 绘制与淡入:新瓦片通常带一段淡入动画,会产生额外重绘帧。

第 2 步在 MapLibre 的 Web Workers 里,通常不是瓶颈;第 3、4 步在 WebView 引擎的 WebGL 实现里,是桌面 Linux 上的主要瓶颈。

25.3.2 Linux 桌面(WebKitGTK)的已知限制

docs/architecture.md 有一整节记录这个问题,结论很明确:

  • 空白地图(无瓦片图层)在任何缩放级别都稳定 60 FPS。
  • 只要存在任意瓦片图层(矢量或栅格 XYZ),瓦片加载期间帧率会掉到个位数,加载一停立刻回到 60;低缩放级别看起来「一直卡」,只是因为平移整个世界时瓦片持续加载、缓存从不稳定。
  • 根因是 WebKitGTK 在主线程上处理每张新瓦片的 GPU 上传(栅格的纹理、矢量的顶点缓冲),走同步 WebGL 调用,加上随后的淡入重绘。单个瓦片集成周期实测约 125 ms,而 Chromium 只要几毫秒。差距在 WebGL 实现与合成器管线(驱动命令流刷新、TextureMapper GL 表面合成),不只是 JavaScriptCore。
  • 已排除的方向(不要重复排查):软件渲染(确实用了 GPU,Intel i915 已确认)、GPU 饱和(渲染引擎约 20% 空闲)、Tauri IPC 文件读取(22 MB GeoJSON 约 126 ms)、JSON.parse(约 36 ms)、KWin 合成器延迟、renderWorldCopies、globe 与 mercator 投影差异、preserveDrawingBuffer。

这是 WebView 引擎限制,不是 GeoLibre 的 bug,也不影响浏览器构建;macOS/Windows 的 WebView 尚未测试。

复现测量最快的方法是这段帧率计数器(粘到 WebView 的 devtools 控制台,然后一边平移一边看日志):

let f = 0,
  t = performance.now();
(function loop(n) {
  f++;
  if (n - t >= 1000) {
    console.log("FPS", f);
    f = 0;
    t = n;
  }
  requestAnimationFrame(loop);
})(t);

注意:文档里列出的三条缓解措施——加大 maxTileCacheSize、把栅格瓦片从 256px 改成 512px、fadeDuration: 0——尚未实现,并且计划只在 WebKitGTK 上门控,以免拖累 Chromium 构建。也就是说,当前版本不能靠配置开关消除这个现象。

25.3.3 实用对策

既然每瓦片成本由引擎固定,能改的只有「一次加载多少瓦片」和「用不用瓦片」:

做法 效果 代价
缩小窗口或减少分屏 pane 单帧瓦片数下降 可用面积变小
用 PMTiles 单个归档替代大量小瓦片请求 请求数下降,range 读取更可预测 需要先转换数据
关闭不必要的图层与 3D 叠加 减少瓦片/几何源 视觉信息减少
在 Linux 桌面改用浏览器构建 避开 WebKitGTK 的 WebGL 路径 失去本地文件对话框、本地 MBTiles
让地图停在一个缩放级别,减少平移 缓存能稳定 交互受限

25.3.4 WebGL 上下文丢失

当地图 canvas 丢失 WebGL 上下文(驱动重置或 GPU 显存耗尽)时,GeoLibre 会显示 The map lost its graphics context 警告和 Reload 按钮。上下文自己恢复时警告会自动消失;不恢复就刷新页面。刷新前尽量保存工程——最近的自动保存可能在项目历史里,但自动保存有延迟并且会跳过超过快照上限的工程,未必包含最新改动。

25.3.5 渲染引擎与投影

四套引擎的成本差别很大,遇到性能问题时应把「换引擎」当成一个正经选项:

  • MapLibre:默认引擎,矢量与栅格瓦片、大部分图层类型的基准路径。
  • Mapbox GL JS:能力接近,但有访问令牌与条款约束。
  • CesiumJS:3D 地球与 3D Tiles,首次进入时加载约 4.7 MB 的独立 chunk。
  • ArcGIS Maps SDK:与 ArcGIS 服务生态配合最好,但同样有额外体积与初始化成本。

投影也会影响成本:Web Mercator 是瓦片生态的原生投影;在大范围下切到 globe 或其它投影会带来额外的重投影与几何细分。行星底图场景(第 08 章)天然使用非地球椭球,测量与投影都要单独考虑。


25.4 要素规模与属性表

25.4.1 阈值在哪里

位置 默认上限 行为
属性表搜索(feature-search.ts) 每图层 50,000 个要素 超出部分不参与本地搜索
OGC API Features 图层(ogc-api-features.ts) 1,000 个要素 面板可调,但受服务端上限约束
从 URL 打开工程(project-files.ts) 25 MiB 超过则拒绝打开
插件打开工程(plugin-open-project.ts) 25 MiB 同上
工程历史快照(project-history-store.ts) 10 MiB / 20 个 超过则自动保存暂停
服务端上传工程 50 MiB 返回 413

这些数字不是「性能建议」,而是代码里的硬性分支:到了阈值行为会改变(搜索不全、要素被截断、保存被拒),所以要在到达之前就规划数据规模。

25.4.2 属性表为什么能撑住大表

属性表用 @tanstack/react-virtual 做行虚拟化(apps/geolibre-desktop/src/components/panels/AttributeTable.tsx):只有视口内与附近的行真正挂载到 DOM,滚动时按估算行高动态测量。命令面板(CommandPalette.tsx)也是同样的思路——它的注释写得很清楚:列表是虚拟化的,只有视口内外的少量行会挂载。

这意味着:

  • 10 万行的属性表依然可以流畅滚动,但排序、筛选、统计仍然要遍历全部数据,这些操作的成本与行数线性相关。
  • 列很多时,横向渲染的单元格数量也会成为成本;用列显示/隐藏关掉暂时不看的字段比纵向裁剪更有效。

25.4.3 要素进入内存的路径

数据入口决定内存占用(源码里对应 duckdb-vector-loader.ts 与 add-data 渲染流程):

  1. 本地矢量文件 → DuckDB-WASM Spatial 读取 → 转成 GeoJSON → addGeoJsonLayer。
  2. GeoParquet → Parquet reader(可流式,见 25.5)。
  3. zip Shapefile → shpjs,失败时 DuckDB 兜底;KMZ 在客户端解压。
  4. 远程服务 → 按服务端分页/限制取要素。

第 1 步会把整个文件变成内存中的 GeoJSON 对象树。一个 100 MB 的 GeoJSON 在内存里往往膨胀数倍,再加上样式与索引,很容易触发内存压力。转换格式永远比调参数有效。


25.5 DuckDB 与查询性能

25.5.1 查询在哪里执行

SQL 工作区(Processing → SQL Workspace)默认跑 DuckDB-WASM,因此浏览器构建与桌面端都能用;同一面板还可以切到 PostGIS(PGlite)与 Apache Sedona 引擎:

引擎 执行位置 适用 注意
DuckDB Spatial(DuckDB-WASM) 浏览器 Web Worker 本地图层、远程 Parquet/CSV/JSON 内存受浏览器限制
PostGIS(PGlite) 浏览器内的 WASM Postgres 需要 PostGIS 语义与函数 首次加载体积较大
Apache Sedona 桌面端 sidecar(SedonaDB),缺失时回退到浏览器内的 CereusDB 更大规模的空间 SQL 需要 sedona extra

25.5.2 让 DuckDB 少读数据

DuckDB 很快,但快不过「不读」。四条最有效的习惯:

  1. 只选需要的列。列式存储按列读取,SELECT * 会把不用的列也解码出来。
  2. 在扫描前过滤。把 WHERE 写在最内层,让行组裁剪生效;对按空间排序(如 Hilbert)写出的 GeoParquet,只读与范围相交的 row group。
  3. 先 DESCRIBE 再写查询。字段名猜错会浪费一整轮调试:
DESCRIBE SELECT * FROM 'https://data.source.coop/giswqs/opengeos/countries.parquet';
  1. 直接在远程文件上算,不要先全量下载。SQL 工作区会把 FROM 里的裸 URL 自动包成对应的 reader,并用 HTTP range 流式读取:
SELECT COUNT(*) AS n
FROM https://data.source.coop/giswqs/opengeos/countries.parquet;

提示:裸 URL 简写是 SQL 工作区的便利,不是标准 DuckDB 语法;在 DuckDB CLI 里要写 read_parquet('https://...')。

25.5.3 空间函数的成本

DuckDB Spatial 的 ST_* 函数大多要遍历几何。两点注意:

  • 坐标系决定单位。在 EPSG:4326 上 ST_Area 得到的是「平方度」,排序可用但不能当面积用。要平方公里得用球面面积函数并注意经纬顺序:
-- 面积单位是平方度;排序有效,但换算成 km² 需要
-- ST_Area_Spheroid(ST_FlipCoordinates(geom)) / 1e6
SELECT NAME, ST_Area(geom) AS area, geom
FROM https://data.source.coop/giswqs/opengeos/countries.parquet
WHERE CONTINENT = 'Africa'
ORDER BY area DESC;
  • 先投影、后测量。大范围上做缓冲、距离、面积时,投影带来的误差可能比性能更值得担心(见第 09 章)。

25.5.4 结果落地

查询返回几何列时,Add as layer 会从结果建图层;Export CSV 与 Export GeoParquet 直接落盘。对大结果集:

  • 直接导出 GeoParquet 比「先加图层再导出」省一次内存拷贝。
  • 结果需要反复使用时,落地成 GeoParquet 再用 Add Data → GeoParquet Layer 加载,比重复跑查询划算。
  • 用 History… 重跑历史查询,用 Sample queries… 从可用模板起步。

25.5.5 WASM 引擎的固有开销

DuckDB-WASM 与原生 DuckDB 有量级差距,而且宿主引擎会放大它。一个已验证的例子:在没有 AVX 的 x86-64 CPU 上,WebKitGTK 的 JavaScriptCore 会走一条会崩渲染进程的 WebAssembly tier-up 路径;GeoLibre 在启动时检测 AVX 并关掉两个 JSC 选项(JSC_useWasmOSR=false 与 JSC_useBBQTierUpChecks=false),代价是 WebKitGTK 2.52.6 上一条 DuckDB-WASM 查询大约慢 2 倍——而彻底关掉 WebAssembly JIT 会慢 34 倍。所以「桌面端 SQL 比浏览器慢一截」在无 AVX 的 Linux 机器上是预期行为。

25.5.6 查询优化检查表

  • 用 DESCRIBE 确认字段名与类型,避免隐式转换。
  • 只选需要的列,过滤条件尽量下推。
  • 对大文件优先用 GeoParquet / Parquet,而不是 CSV/GeoJSON。
  • 空间连接前先缩小两侧范围(先过滤再 ST_Intersects)。
  • 重复使用的中间结果落地为 GeoParquet。
  • 需要重复执行时,考虑 Model Builder 或 Python 脚本(第 13、19 章)。

25.6 大工程与保存上限

25.6.1 三个不同的「太大」

现象 触发条件 含义
Autosave paused(状态栏) 工程序列化超过 10 MiB,或快照数已达 20 不再产生新的历史快照,但工程本身还能保存
This project is too large to save 序列化结果超过 JS 引擎的字符串上限(V8 约 536 MB) 无法写成单个字符串,保存/分享直接失败
上传返回 413 超过服务端 GEOLIBRE_MAX_PROJECT_BYTES(默认 50 MiB) 服务端拒绝接收

三者完全不是一回事:第一个是历史功能的自我限制,第二个是引擎硬上限,第三个是服务端策略。所谓「工程过大」通常指第二个,修法是把内嵌数据搬出去。

25.6.2 自动保存与项目历史

源码常量(src/lib/project-history-store.ts):

常量 值 作用
MAX_SNAPSHOTS 20 最多保留的快照数量
MAX_SNAPSHOT_BYTES 10 MiB 单次快照体积上限;超过则跳过并暂停自动保存

状态栏会显示 Autosave paused,悬停给出完整解释(文案由 autosave-status.ts 统一生成,包含以 MiB 表示的上限)。此时应当保存到磁盘来保住工作。

25.6.3 把数据搬出工程文件

  1. 转成云原生格式并作为远程图层引用:PMTiles(瓦片)、FlatGeobuf(带空间索引的矢量)、GeoParquet(列式,可流式)。工程里只留 URL 与样式。
  2. 本地大文件用桌面端读取:桌面端能直接读本地 MBTiles、栅格与矢量文件,不必把内容塞进工程。
  3. 拆分工程:按主题或区域拆成多个 .geolibre.json,复用公共底图。
  4. 导出为静态成果:一次性分析结果导出为 GeoParquet/COG 后再引用。

警告:分享(Project → Share)只上传工程文件,永远不上传数据文件。对话框会在上传前列出「读取本机文件」的图层并标记为缺失。要让别人看到数据,必须自己托管并改成远程 URL。


25.7 数据侧优化清单

数据格式的选择通常比任何前端调参都有效。目标只有一个:让客户端只读它需要的那一小块。

25.7.1 格式选择

场景 推荐 理由
底图 / 大规模矢量瓦片 PMTiles 单文件归档,range 请求即可取瓦片,无需瓦片服务器
大表矢量分析 GeoParquet 列式 + 压缩,可按 row group 裁剪,支持无拷贝流式
需要空间索引的矢量 FlatGeobuf 流式友好,文件内含空间索引,只取视野内要素
栅格影像 COG 内建概览与瓦片布局,按需读取
桌面端离线瓦片 MBTiles 桌面端专用,无需服务器
多维 / 时序栅格 Zarr 分块读取,适合时间序列

25.7.2 转换命令(GeoLibre 内)

Processing → GeoLibre Toolbox → Conversion 提供:

  • Vector to GeoParquet / CSV to GeoParquet:浏览器端由 DuckDB-WASM 完成,输出按 Hilbert 排序。
  • Vector to FlatGeobuf、Vector to PMTiles、Raster to COG:浏览器端由 WebAssembly(geolibre-wasm)完成;桌面端改由 sidecar 执行,支持更多输入格式并能保留浏览器写入器无法保留的数据类型。

提示:Hilbert 排序不是装饰。GeoParquet 的 row group 只有按空间聚簇,范围查询才能只读相关块——这是「流式 GeoParquet」好用的前提。

25.7.3 几何与属性裁剪

GeoLibre 内的处理工具适合做流程内的裁剪;批量预处理用 GDAL 更省事:

# 只保留需要的字段,并把几何简化到 10 个图层单位
ogr2ogr -f FlatGeobuf roads.fgb roads.gpkg \
  -select name,class,speed \
  -simplify 10

# 把栅格转成带内部概览与压缩的 COG
gdal_translate -of COG -co COMPRESS=DEFLATE -co OVERVIEWS=AUTO dem.tif dem_cog.tif

要点:

  • 属性裁剪优先于几何简化,因为它不改变形状,风险最低。
  • 几何简化会改变面积与拓扑,简化后再做面积统计会得到不同结果;保留原始数据。
  • 栅格优先降分辨率或裁范围,而不是只换压缩方式;概览(overview)决定缩小查看时的读取量。

25.7.4 属性与索引

  • 去掉大文本字段(长描述、JSON 字符串)能显著缩小体积;把它们存到外部表按 id 关联。
  • 分类字段用短码而不是长字符串,既省体积也省渲染时的样式匹配。
  • FlatGeobuf 的空间索引是格式内建的,不需要额外建;GeoParquet 靠排序与 row group 统计量,注意写入时的排序与 group 大小。

25.7.5 服务端侧

若数据在 GeoLens 或自己的服务后面:

  • 目录检索用服务端搜索,不要把全表拉进浏览器再过滤。
  • 瓦片服务优先用签名矢量瓦片(MVT)而不是 GeoJSON;需要属性时再用 OGC API Features 取当前视野。
  • GeoLens 的矢量数据集面板允许设置 OGC API Features 的要素上限,默认 1,000——按用途调,而不是无脑调大。

25.8 应用侧优化清单

25.8.1 图层与控件

做法 收益 备注
关掉不需要的图层 直接减少数据源与绘制 用图层可见性而非删除,便于恢复
删除或折叠不用的面板 减少重排与重绘 属性表、样式面板、命令行都是 DOM 大户
减少 3D 叠加与 deck.gl 图层 减少 GPU 合成压力 尤其与底图瓦片叠加时
用 maponly / layout=viewer 嵌入 去掉工具栏与面板 适合展示型页面
关闭不必要的控件(比例尺、小地图等) 减少持续渲染 见 Controls 菜单

25.8.2 底图与离网

  • 底图瓦片是最大宗的网络请求。私有部署应自托管样式 + PMTiles 底图(见第22章 服务端、自托管与部署运维)。
  • Project → Offline Basemap… 只能缓存底图服务器允许缓存的内容:
    • 「Offline caching needs the installed web app」表示浏览器构建没有以已安装应用方式运行,瓦片不会被存储。
    • 部分底图源不允许离线下载,GeoLibre 会在下载前列出它们。
    • 大范围 + 多缩放级别会超出离线缓存上限,最旧的瓦片会被新下载的挤掉;缩小范围或减少层级。
  • Manage Offline Areas 显示各离线区域占用的存储。
  • PWA 注册(src/main.tsx 的 registerSW)让已安装应用能离线启动;浏览器里刷新即回到在线版本。

25.8.3 每浏览器的环境变量

Settings → Environment variables 允许在不重建应用的前提下覆盖部分构建期变量,例如:

变量 用途
VITE_ROUTING_ENDPOINT 指向自建 Valhalla,替代公共路由服务
VITE_PYODIDE_INDEX_URL Pyodide 发行镜像地址
Share.GeoLibre API token 分享用的个人 API 令牌(兜底方案)

这些覆盖只影响当前浏览器,适合排障与临时切换;团队统一配置应走部署策略(第 22 章)。

25.8.4 渲染引擎与投影

  • 纯 2D 展示优先 MapLibre;只有在需要 Cesium 的地球/3D Tiles 或 ArcGIS 生态时才切引擎;切回时 Cesium 的 chunk 不会立即释放,刷新页面最干净。
  • 投影切换(globe/mercator)会重建瓦片索引与几何;在大工程上频繁切换会明显卡顿。
  • 行星底图(第 08 章)使用各自椭球,距离/面积/比例尺都按该天体计算,不要用地球参数估算。

25.8.5 PWA 与缓存

GeoLibre Web 是 SPA,浏览器会激进缓存。发布新版本后,旧缓存会造成「控件消失」「插件不加载」「数据陈旧」等现象——这是 25.10.1 的第一条排查项。安装成 PWA 后,Service Worker 的更新策略也参与其中:更新后首次打开可能仍是旧版,刷新一次或关闭所有标签页再打开。

25.8.6 打包与自托管的分工

症状 客户端能做的 只能由部署方做的
首屏太大 无(构建产物决定) 减依赖、调 CODE_SPLITTING_GROUPS、用 lite:build
数据加载慢 换格式、换来源 就近部署、开压缩、加缓存头
瓦片请求过多 换 PMTiles、减图层 自托管瓦片服务、合并请求
第三方请求慢 切到内网源 CSP、代理、镜像

25.9 诊断工具

25.9.1 Diagnostics 对话框

GeoLibre 在运行时会记录出错的内容。三个入口等价:

  • 菜单 Help → Diagnostics;
  • 状态栏右端的 Diagnostics: N 按钮(N 是错误与警告的总数,有错误时变红,只有警告时变琥珀色);
  • 命令面板(Ctrl/Cmd + K)里搜 “Diagnostics”。

对话框顶部是过滤按钮(全部 / 错误 / 警告 / 网络),每条记录包含级别(error、warning、info)、时间、类别与消息。

25.9.2 五个类别

类别 记录什么
app 应用主动展示给你的每一个错误通知
console 浏览器控制台里的错误与警告,可能来自 GeoLibre、某个库或插件
map 地图引擎抛出的错误:瓦片、数据源、样式或 3D 图层失败
network 失败的请求:HTTP 错误状态(如 GET 404 Not Found)、未完成的请求(request failed (network/TLS/CORS) 或 timed out),并附可能原因提示
runtime 未捕获异常与 promise rejection、界面局部崩溃、插件失败

行为细节(来自 src/lib/diagnostics.ts):最多保留 500 条,单字段截断到 3,000 字符,日志只在内存中(每次启动清空),并且会剥掉 URL 里的访问令牌、API key 等查询参数。

25.9.3 网络记录与筛选

默认只记录失败的请求。勾选 Log all network requests 后,从那一刻起也记录成功与中止的请求——它会拖慢应用,非排障期间保持关闭。

  • Copy JSON 复制当前筛选后可见的条目,适合贴进 issue。
  • Clear 清空。

提示:排查「哪个请求挂了」时,先清空、再复现一次操作,然后立刻 Copy JSON。这比在 Network 面板里翻几百条请求快得多。

25.9.4 状态栏与通知

  • 状态栏的 Diagnostics: N 是最快的问题指示器。
  • 图层加载失败会以通知形式每个会话报告一次;返回 204/404 的瓦片请求不会报告,因为稀疏瓦片集在空区域就是这么返回的(这也意味着「空白区域」不一定是故障)。
  • Autosave paused 表示工程超过快照上限,见 25.6.2。
  • 地图丢失 WebGL 上下文时会显示 The map lost its graphics context 与 Reload 按钮。

25.9.5 浏览器开发者工具

Diagnostics 是应用层的汇总,浏览器 DevTools 提供底层细节,两者要配合:

面板 看什么
Console 完整堆栈、未捕获异常、CSP 拦截信息
Network 单个请求的状态、耗时、响应头、是否被缓存;按 XHR/Fetch/Img 过滤可区分 API 与瓦片
Performance 长任务、帧率、主线程阻塞
Application Service Worker、缓存、IndexedDB(离线瓦片与项目历史)
Memory 堆快照,确认大 GeoJSON 是否被重复持有

注意:chrome://gpu(Chrome/Edge)能确认 WebGL 是否硬件加速,这是 25.10.8 的第一步。

25.9.6 构建期诊断

前端性能问题不一定能在运行时看出来。构建侧的两个工具:

  • npm run bundle:report:bundle 体积报告(见 25.2.3)。
  • npm run typecheck:等价于完整构建,能暴露打包期的解析问题。

25.10 故障排查手册

本节逐条对应 docs/user-guide/troubleshooting.md 的问题清单,补充了判断顺序与命令。

25.10.1 浏览器缓存陈旧

GeoLibre Web 是单页应用,浏览器缓存很激进。新版本发布后,旧缓存会导致显示错误、控件缺失或无响应、插件看起来没加载、数据陈旧。

按顺序尝试:

  1. 强制刷新绕过缓存一次:
    • Windows / Linux:Ctrl + Shift + R(Chrome、Edge、Firefox)。
    • macOS Chrome / Edge / Firefox:Cmd + Shift + R。
    • macOS Safari:Cmd + Option + R。
  2. 用隐私/无痕窗口打开。如果那里正常,问题就在浏览器配置(多半是缓存)。
  3. 清除该站点的缓存文件后重新加载。
  4. Safari 用户:Develop → Empty Caches(Cmd + Option + E)或 Safari → Clear History;Develop 菜单被隐藏时在 Safari → Settings → Advanced → Show features for web developers 打开。

提示:绝大多数「更新后界面坏了」的报告都能被一次干净加载解决。

25.10.2 「Could not reach the service」

远程图层或服务失败并提示 Could not reach the service…,或 network 诊断以 request failed (network/TLS/CORS) 结尾时,按以下顺序判断:

可能原因 判断方法 处理
CORS 浏览器拦截,桌面端正常 服务端加 Access-Control-Allow-Origin;S3 桶要在 CORS 配置里列出应用来源
混合内容 https:// 页面加载 http:// 数据 换成 HTTPS,或用桌面端
TLS / 可达性 证书过期、自签名、防火墙、主机下线 修证书或换网络;桌面端可作对照

最快的判别法是换桌面端试同一个 URL:桌面端不受 CORS 约束,能加载就说明是 CORS/混合内容;同样失败才是网络或 TLS。

25.10.3 GeoTIFF 无法显示

普通(条带式)GeoTIFF 不能逐瓦片流式读取。GeoLibre 会识别并提示转换成 Cloud-Optimized GeoTIFF 后加载。转换在浏览器内完成,因此过大的文件会被拒绝(避免浏览器侧转换失败)。这类文件应在外部转换:

gdal_translate -of COG input.tif output_cog.tif
# 或
rio cogeo create input.tif output_cog.tif

转换后再用 Add Data → Raster Layer 加载,或在 STAC / 影像面板里作为 COG 添加。

25.10.4 「This project is too large to save」

工程内嵌了太多矢量数据,超过了可保存的体积。处理方向是把内嵌数据改成远程图层:转成 PMTiles 或 FlatGeobuf 并作为远程图层添加,而不是把要素存进工程。相关提示 Autosave paused 表示工程已超过自动保存上限,不再产生新的历史快照,此时应保存到磁盘保住工作(见 25.6)。

25.10.5 Sidecar 相关消息

栅格工具、格式转换与矢量工具的 GeoPandas 引擎依赖可选的 Python sidecar。出现下列消息说明它没运行,或缺少某个工具需要的 extra:

  • Raster tools need the GeoLibre desktop app with a running sidecar
  • Could not connect to sidecar
  • The GeoPandas sidecar is not available

矢量工具会自动回退到浏览器引擎;栅格工具中 Hillshade、Slope、Aspect 等有客户端实现,Contour、Clip by mask layer 没有。启动方式与各工具所需的 extra 见 docs/user-guide/processing.md。

提示:浏览器构建里 sidecar 走同源 /sidecar,不需要 CORS;如果 Docker 部署里它一律 403,先看部署策略是否给了 processing:run 或 data:add,以及有没有设 GEOLIBRE_DISABLE_SIDECAR=1。

25.10.6 离线地图缺少瓦片

Project → Offline Basemap… 只能缓存底图服务器允许缓存的内容:

  • 「Offline caching needs the installed web app」:浏览器构建没有以已安装应用运行,瓦片不会被存储。
  • 部分底图源不允许离线下载;GeoLibre 会在下载前列出它们,这些瓦片必然缺失。
  • 大范围 + 多缩放级别可能超出离线缓存上限,最旧的瓦片会被新下载的挤掉。缩小范围或减少细节层级。

Manage Offline Areas 可以查看各区域占用的存储量。

25.10.7 「Something went wrong」

界面局部崩溃时,GeoLibre 显示「{name} failed to render. The rest of GeoLibre is still available.」并继续运行。整个应用崩溃时会出现全窗口的 Something went wrong 面板,提供 Try again 与 Reload app。

  1. 先点 Try again——它保留你的工作。
  2. 需要重载时,工程可能还在项目历史里,可从历史恢复。
  3. 崩溃会记录在 Diagnostics 的 runtime 类别,可以直接上报。

25.10.8 地图空白或渲染异常

地图用 WebGL 在显卡上绘制。空白、全黑、闪烁或 3D 图层缺失时:

  1. 确认浏览器开启了硬件加速。Chrome/Edge:Settings → System → Use graphics acceleration when available;然后在 chrome://gpu 里确认 “WebGL: Hardware accelerated”。没有硬件加速时会退化为缓慢的软件渲染,甚至不可用。
  2. 更新显卡驱动;双显卡笔记本换另一块 GPU 试。
  3. 换渲染引擎:View → Rendering engine(见第 08 章)。
  4. 看 Diagnostics 的 map 与 console 条目,它们会指名失败的图层或着色器。

WebGL 上下文丢失的处理见 25.3.4。

25.10.9 Linux 桌面应用

Linux 桌面跑在 WebKitGTK 里,GPU 相关问题较多。应用会在启动时按 WebKitGTK 版本与 GPU 设置已知可用的渲染选项(包括 NVIDIA 上的 DMA-BUF 渲染器设置),并规避无 AVX CPU 上的 WebAssembly 崩溃。你自己在环境里设置的值永远优先,所以窗口空白或地图闪烁时的第一招是显式设置:

环境 建议
WebKitGTK 低于 2.48 WEBKIT_DISABLE_DMABUF_RENDERER=1
NVIDIA GPU,WebKitGTK 2.48–2.51 WEBKIT_DISABLE_DMABUF_RENDERER=1
NVIDIA GPU,WebKitGTK 2.52 及以上 WEBKIT_DISABLE_DMABUF_RENDERER=0 且 WEBKIT_DMABUF_RENDERER_FORCE_SHM=1
其它 无需设置,使用默认 DMA-BUF 渲染器

无 AVX 的 CPU 上,应用会在启动时关掉 JSC_useWasmOSR 与 JSC_useBBQTierUpChecks;这两个选项必须成对,单独把自己设成 false 不算退出该规避(GeoLibre 会保留你的值并应用另一半)。若渲染进程仍崩溃,可整体关掉 WebAssembly JIT:JSC_useBBQJIT=false,代价是 DuckDB-WASM 之类的 WASM 操作会慢得多。

25.10.10 插件看起来没激活

  1. 先排除缓存:强制刷新或用隐私窗口(25.10.1)。
  2. 在最新构建上,激活的插件在 Plugins 菜单条目旁有对勾,其面板会出现在侧边栏;少数带地图控件的插件(如 GeoEditor、Annotations)会在配置的地图角出现控件。
  3. 灰色的插件表示不支持当前渲染引擎——悬停可看到原因。
  4. 启动失败的插件会以通知与 Diagnostics 条目报告。

25.10.11 Processing 菜单里同一分类出现两次

Processing → Vector 与 Processing → GeoLibre Toolbox → Vector 是两件不同的东西,Conversion、Network、Raster 同理。Processing 菜单里有两个独立工具箱:

  • Whitebox Toolbox:九个分类子菜单,位于菜单顶部;
  • GeoLibre Toolbox:GeoLibre 自己的内置工具。

两个列表互不为子集。需要哪个就点哪个完整路径,不要因为「名字一样」而选错。

25.10.12 浏览器里的桌面专属功能

以下能力需要桌面应用,浏览器构建没有:本地文件系统对话框、本地 MBTiles、本地栅格文件读取、工程保存/打开。如果你发现它们缺失,很可能运行的是 GeoLibre Web 而不是桌面应用。三种构建的差别见第 02 章。

25.10.13 自托管部署

自托管时「在 web.geolibre.app 上能用,这里不行」的问题,多数来自部署的安全设置而不是应用本身:

  • Content Security Policy。Docker 镜像的 nginx 允许 https: 的数据与瓦片主机,但阻止明文 http:// 远程数据,以及除已配置协作服务器之外的 wss:// 主机。被 CSP 拦截的请求会出现在浏览器控制台与 Diagnostics 的 console 里。桌面端允许任意 https: 或 http: 数据主机,但脚本与 WebSocket 只允许固定列表。
  • sidecar 代理。浏览器构建在同源 /sidecar 访问 sidecar,因此无需 CORS,且只能读取 GEOLIBRE_CONVERSION_ROOTS(默认 /data)下的文件。/sidecar 返回 403 意味着 sidecar 被禁用(GEOLIBRE_DISABLE_SIDECAR)或部署策略没有授予相应能力。
  • 部署策略。deployment.json 可以关掉功能、服务与插件,所以菜单项缺失可能是有意的。

更完整的同源部署原理与检查单见第22章。


25.11 问题上报与获取帮助

25.11.1 带错误的上报:Report issue

错误通知与 Diagnostics 里的错误行都带 Report issue 按钮。它会用系统浏览器打开 GeoLibre 在 GitHub 上的 bug 报告表单,并预填:

  • 错误消息作为标题;
  • 应用版本,以及当前是桌面应用还是 Web 应用;
  • 平台(浏览器的 user agent)与当前使用的渲染引擎;
  • 该条 Diagnostics 记录(JSON)。

在你审阅并自己提交之前,什么都不会发送。预填内容在生成前会被擦洗:URL 的 query string 与 fragment、URL 里的用户名与密码、内嵌的 data: URI、形似凭据的 key=value 对、可识别的令牌(bearer token、Mapbox / GitHub / AWS / Google 密钥、JWT),以及你主目录下的用户名都会被替换。即便如此,提交前仍要自己读一遍,并补上「出错时你在做什么」。

25.11.2 没有错误的上报:Give Feedback

没有错误可挂的问题用 Help → Give Feedback,它直接打开 GitHub issue 跟踪器。发帖时把下面几项补齐,能显著提高被复现的概率:

项 说明
版本与形态 例如 3.4 桌面版 / Web 版 / Jupyter
平台 操作系统与版本、浏览器或 WebView 版本
渲染引擎 MapLibre / Mapbox / Cesium / ArcGIS 中当前使用的那一个
复现步骤 从打开应用开始,写成编号步骤
数据来源 URL 或格式(不要把私有数据贴上去),以及数据规模
期望与实际 各一句话
诊断信息 Diagnostics 的 Copy JSON 结果(必要时勾选 Log all network requests 再复现一次)

警告:不要贴个人 API 令牌、组织内部 URL、私有数据集内容或截图里的个人信息。Report issue 的自动擦洗只覆盖可识别模式,不保证覆盖你的业务数据。

25.11.3 上报之前的六项自检

  1. 已是最新版本(桌面端更新、Web 端强制刷新)。
  2. 在隐私窗口里复现过,排除缓存与扩展干扰。
  3. 换过渲染引擎或换过桌面端/Web 端做对照。
  4. 在 Diagnostics 里确认了类别与第一条错误,而不是只看表面现象。
  5. 对网络问题,记录失败的 URL、状态码与响应头(含 CORS 头)。
  6. 对性能问题,给出基线数字:首屏时间、平移最低帧率、一次操作的耗时。

25.11.4 获取帮助的渠道

渠道 适合
官方文档 https://geolibre.app 功能说明、教程、变更记录
GitHub Issues https://github.com/opengeos/GeoLibre/issues 可复现的 bug 与功能请求
GitHub Discussions 用法提问、部署经验
视频教程 端到端工作流演示(见第 26 章)

25.12 本章小结

本章从「先量后调」出发,把 GeoLibre 的性能与排障拆成可执行的几块:

  1. 五个瓶颈面:启动/包体、瓦片/渲染、要素规模、查询/计算、持久化。先判断问题落在哪一层,再优化。
  2. 启动与包体:boot budget 是 3 MiB raw / 900 KiB gzip(实测约 2.2 MB / 680 kB),Cesium 约 4.7 MB 且只在 3D 时加载,Worker 不与主构建共享 chunk,npm run bundle:report 给出真实体积。
  3. 瓦片与渲染:Linux WebKitGTK 逐瓦片 GPU 上传约 125 ms(Chromium 几毫秒),载入期间掉帧属引擎限制;三条缓解措施尚未实现。WebGL 上下文丢失有专门警告与 Reload。
  4. 要素规模:属性表用 @tanstack/react-virtual 行虚拟化,搜索每层最多 50,000 要素,OGC API Features 默认 1,000,远程工程打开上限 25 MiB。
  5. 大工程:10 MiB / 20 个快照触发 Autosave paused;引擎字符串上限(V8 约 536 MB)触发「too large to save」;服务端默认 50 MiB。修法是把数据搬成 PMTiles / FlatGeobuf / GeoParquet 远程图层。
  6. 数据侧清单:按场景选格式,用 Conversion 工具或 GDAL 做属性裁剪与几何简化,栅格转带概览的 COG。
  7. 应用侧清单:减图层与面板、换轻量嵌入、控制离线区域、按需覆盖环境变量、慎切引擎与投影。
  8. 诊断工具:Diagnostics(500 条、五类别、Copy JSON)、状态栏计数、通知、DevTools 五个面板、bundle report。
  9. 排障手册:覆盖缓存、服务不可达、GeoTIFF、工程过大、sidecar、离线瓦片、局部/整体崩溃、空白地图、Linux WebKitGTK、插件、双工具箱、桌面专属功能、自托管 CSP 与策略。
  10. 上报:Report issue 的预填与擦洗机制、Give Feedback、六项自检与渠道。

下一章把这些能力放进十个端到端案例里,串成完整工作流。


25.13 实践练习

  1. 建立基线:在你常用的机器上,记录 GeoLibre Web 的首屏时间、平移最低帧率(用 25.3.2 的计数器)与打开一个中型工程的时间。然后在 Linux 桌面端重复一次,写出两者的差异与可能原因。
  2. bundle 体检:在仓库里跑 npm run build 与 npm run bundle:report,从报告中找出最大的三个资产与 boot 集合的体积,对照 3 MiB / 900 KiB 两条预算判断余量。说明若要把某个懒加载包挪进首屏会触发什么。
  3. 格式对照:把同一个 GeoJSON(几万要素)分别转成 FlatGeobuf、GeoParquet 与 PMTiles 后加载,记录文件体积、首次渲染时间与平移体验,解释差异来自哪里。
  4. 制造并读懂故障:故意加载一个 http:// 的远程数据、一个条带式 GeoTIFF、一个不存在的 URL,分别记录 Diagnostics 里的类别与消息,并写出你会如何修复。
  5. 离线区域实验:在浏览器构建与已安装 PWA 中分别尝试 Project → Offline Basemap…,记录提示文案差异,并验证 Manage Offline Areas 的存储数字。
  6. 写一份可复现的上报:按 25.11.2 的表格,为你遇到的问题写一份 issue 草稿(含诊断 JSON 的字段名),确认其中没有任何令牌或私有数据。