第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 先量后调
在改动任何东西之前,先记录三个基线:
- 首屏时间:从刷新到地图可交互(可用浏览器 Performance 面板的 First Contentful Paint 与 Long Task)。
- 平移帧率:在目标区域来回平移 10 秒,记录最低帧率(25.3 节给了现成的 FPS 计数器)。
- 一次典型操作耗时:例如打开工程、跑一条 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 一张瓦片要经过什么
- 网络请求(HTTP/2 或 range 请求)。
- 解析:栅格解码成纹理,矢量瓦片在 Worker 里解析并构建绘制用的 bucket。
- GPU 上传:纹理或顶点缓冲进入显存。
- 绘制与淡入:新瓦片通常带一段淡入动画,会产生额外重绘帧。
第 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 渲染流程):
- 本地矢量文件 → DuckDB-WASM Spatial 读取 → 转成 GeoJSON →
addGeoJsonLayer。 - GeoParquet → Parquet reader(可流式,见 25.5)。
- zip Shapefile → shpjs,失败时 DuckDB 兜底;KMZ 在客户端解压。
- 远程服务 → 按服务端分页/限制取要素。
第 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 很快,但快不过「不读」。四条最有效的习惯:
- 只选需要的列。列式存储按列读取,
SELECT *会把不用的列也解码出来。 - 在扫描前过滤。把
WHERE写在最内层,让行组裁剪生效;对按空间排序(如 Hilbert)写出的 GeoParquet,只读与范围相交的 row group。 - 先
DESCRIBE再写查询。字段名猜错会浪费一整轮调试:
DESCRIBE SELECT * FROM 'https://data.source.coop/giswqs/opengeos/countries.parquet';
- 直接在远程文件上算,不要先全量下载。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 把数据搬出工程文件
- 转成云原生格式并作为远程图层引用:PMTiles(瓦片)、FlatGeobuf(带空间索引的矢量)、GeoParquet(列式,可流式)。工程里只留 URL 与样式。
- 本地大文件用桌面端读取:桌面端能直接读本地 MBTiles、栅格与矢量文件,不必把内容塞进工程。
- 拆分工程:按主题或区域拆成多个
.geolibre.json,复用公共底图。 - 导出为静态成果:一次性分析结果导出为 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 是单页应用,浏览器缓存很激进。新版本发布后,旧缓存会导致显示错误、控件缺失或无响应、插件看起来没加载、数据陈旧。
按顺序尝试:
- 强制刷新绕过缓存一次:
- Windows / Linux:
Ctrl + Shift + R(Chrome、Edge、Firefox)。 - macOS Chrome / Edge / Firefox:
Cmd + Shift + R。 - macOS Safari:
Cmd + Option + R。
- Windows / Linux:
- 用隐私/无痕窗口打开。如果那里正常,问题就在浏览器配置(多半是缓存)。
- 清除该站点的缓存文件后重新加载。
- 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 sidecarCould not connect to sidecarThe 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。
- 先点 Try again——它保留你的工作。
- 需要重载时,工程可能还在项目历史里,可从历史恢复。
- 崩溃会记录在 Diagnostics 的
runtime类别,可以直接上报。
25.10.8 地图空白或渲染异常
地图用 WebGL 在显卡上绘制。空白、全黑、闪烁或 3D 图层缺失时:
- 确认浏览器开启了硬件加速。Chrome/Edge:Settings → System → Use graphics acceleration when available;然后在
chrome://gpu里确认 “WebGL: Hardware accelerated”。没有硬件加速时会退化为缓慢的软件渲染,甚至不可用。 - 更新显卡驱动;双显卡笔记本换另一块 GPU 试。
- 换渲染引擎:View → Rendering engine(见第 08 章)。
- 看 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 插件看起来没激活
- 先排除缓存:强制刷新或用隐私窗口(25.10.1)。
- 在最新构建上,激活的插件在 Plugins 菜单条目旁有对勾,其面板会出现在侧边栏;少数带地图控件的插件(如 GeoEditor、Annotations)会在配置的地图角出现控件。
- 灰色的插件表示不支持当前渲染引擎——悬停可看到原因。
- 启动失败的插件会以通知与 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 上报之前的六项自检
- 已是最新版本(桌面端更新、Web 端强制刷新)。
- 在隐私窗口里复现过,排除缓存与扩展干扰。
- 换过渲染引擎或换过桌面端/Web 端做对照。
- 在 Diagnostics 里确认了类别与第一条错误,而不是只看表面现象。
- 对网络问题,记录失败的 URL、状态码与响应头(含 CORS 头)。
- 对性能问题,给出基线数字:首屏时间、平移最低帧率、一次操作的耗时。
25.11.4 获取帮助的渠道
| 渠道 | 适合 |
|---|---|
官方文档 https://geolibre.app |
功能说明、教程、变更记录 |
GitHub Issues https://github.com/opengeos/GeoLibre/issues |
可复现的 bug 与功能请求 |
| GitHub Discussions | 用法提问、部署经验 |
| 视频教程 | 端到端工作流演示(见第 26 章) |
25.12 本章小结
本章从「先量后调」出发,把 GeoLibre 的性能与排障拆成可执行的几块:
- 五个瓶颈面:启动/包体、瓦片/渲染、要素规模、查询/计算、持久化。先判断问题落在哪一层,再优化。
- 启动与包体:boot budget 是 3 MiB raw / 900 KiB gzip(实测约 2.2 MB / 680 kB),Cesium 约 4.7 MB 且只在 3D 时加载,Worker 不与主构建共享 chunk,
npm run bundle:report给出真实体积。 - 瓦片与渲染:Linux WebKitGTK 逐瓦片 GPU 上传约 125 ms(Chromium 几毫秒),载入期间掉帧属引擎限制;三条缓解措施尚未实现。WebGL 上下文丢失有专门警告与 Reload。
- 要素规模:属性表用
@tanstack/react-virtual行虚拟化,搜索每层最多 50,000 要素,OGC API Features 默认 1,000,远程工程打开上限 25 MiB。 - 大工程:10 MiB / 20 个快照触发 Autosave paused;引擎字符串上限(V8 约 536 MB)触发「too large to save」;服务端默认 50 MiB。修法是把数据搬成 PMTiles / FlatGeobuf / GeoParquet 远程图层。
- 数据侧清单:按场景选格式,用 Conversion 工具或 GDAL 做属性裁剪与几何简化,栅格转带概览的 COG。
- 应用侧清单:减图层与面板、换轻量嵌入、控制离线区域、按需覆盖环境变量、慎切引擎与投影。
- 诊断工具:Diagnostics(500 条、五类别、Copy JSON)、状态栏计数、通知、DevTools 五个面板、bundle report。
- 排障手册:覆盖缓存、服务不可达、GeoTIFF、工程过大、sidecar、离线瓦片、局部/整体崩溃、空白地图、Linux WebKitGTK、插件、双工具箱、桌面专属功能、自托管 CSP 与策略。
- 上报:Report issue 的预填与擦洗机制、Give Feedback、六项自检与渠道。
下一章把这些能力放进十个端到端案例里,串成完整工作流。
25.13 实践练习
- 建立基线:在你常用的机器上,记录 GeoLibre Web 的首屏时间、平移最低帧率(用 25.3.2 的计数器)与打开一个中型工程的时间。然后在 Linux 桌面端重复一次,写出两者的差异与可能原因。
- bundle 体检:在仓库里跑
npm run build与npm run bundle:report,从报告中找出最大的三个资产与 boot 集合的体积,对照 3 MiB / 900 KiB 两条预算判断余量。说明若要把某个懒加载包挪进首屏会触发什么。 - 格式对照:把同一个 GeoJSON(几万要素)分别转成 FlatGeobuf、GeoParquet 与 PMTiles 后加载,记录文件体积、首次渲染时间与平移体验,解释差异来自哪里。
- 制造并读懂故障:故意加载一个
http://的远程数据、一个条带式 GeoTIFF、一个不存在的 URL,分别记录 Diagnostics 里的类别与消息,并写出你会如何修复。 - 离线区域实验:在浏览器构建与已安装 PWA 中分别尝试 Project → Offline Basemap…,记录提示文案差异,并验证 Manage Offline Areas 的存储数字。
- 写一份可复现的上报:按 25.11.2 的表格,为你遇到的问题写一份 issue 草稿(含诊断 JSON 的字段名),确认其中没有任何令牌或私有数据。