第06章 - 图层管理与地图控制
图层面板是 GeoLibre 的工作台核心:它既是数据的目录,也是绘制顺序、可见性、透明度、分组、刷新策略和一系列图层动作的入口。地图控制则是围绕地图画布的那一圈工具——控件、测量、右键分析、标注、评论、GPS 与录制。
本章有一条贯穿始终的线索:GeoLibre 不让你直接操作地图对象。你在面板上做的每一次改动,都是先写进状态仓库(store)里的图层记录,再由 MapController.syncLayers 把它协调成 MapLibre 的 source 与 layer。理解了这条单向数据流,很多事情就有了解释:为什么切换渲染引擎图层还在、为什么有的图层没有某个按钮、为什么拖动滑块很顺滑但不会产生几十条撤销记录。
说明:本章基于 GeoLibre 3.4。菜单项与参数名保留英文原文。素材来自
docs/user-guide/layers.md、docs/user-guide/map-controls.md、packages/map/src/layer-control-host.ts、packages/map/src/layer-sync.ts、packages/map/src/map-controller.ts、packages/core/src/history.ts与packages/core/src/types.ts。
6.1 图层栈:顺序、可见性与透明度
左侧的 Layers 面板按”最上面绘制 → 最下面绘制”的顺序列出工程里的所有图层,最后一行固定是底图 Background。选中某个图层会同时驱动右边的 Style 面板和下方的属性表。
| 操作 | 位置 | 行为 |
|---|---|---|
| 可见性 | 图层行上的眼睛按钮 | 切换单个图层的显示 |
| 隐藏全部 | 面板顶部的 Hide all layers | 一次隐藏所有图层 |
| 排序(拖拽) | 图层名旁的抓手(grip) | 拖到另一个图层上即改变层级;拖拽只在面板内部生效 |
| 排序(按钮) | 卡片上的 Move up / Move down | 上移/下移一位 |
| 透明度 | 图层行上的滑块 | 0–100% |
排列规则只有一句:列表越靠上越后绘制。底图(Background)永远在最底层,不参与排序。
提示:从 Finder/资源管理器往桌面应用里拖文件属于数据导入,与面板内部的图层拖拽是两条不同的路径,不会因为拖错位置而移动图层。
6.1.1 图层卡片上的图标按钮
选中图层后,卡片上会展开一排图标按钮。它们覆盖了高频动作:
| 按钮 | 作用 | 备注 |
|---|---|---|
| Move up / Move down | 在图层栈中上下移动一位 | —— |
| Zoom to layer | 把地图缩放到图层范围 | 仅对范围已知的图层有效 |
| Identify features | 点击地图上的要素查看属性 | 栅格图层改为读像素值;多波段栅格还会生成光谱剖面 |
| Open Style panel | 选中该图层并打开样式控件 | 与调色板按钮等价 |
| Layer actions | 打开完整菜单(见 6.1.2) | … 按钮 |
| Metadata | 检视来源与配置,编辑描述性元数据 | 可 Export as STAC Item |
| Remove layer | 从工程中删除 | 不删源文件 |
Identify features 在可编辑矢量图层上还会给每条结果附两个动作:
- Edit geometry:打开 GeoEditor 并选中该要素;
- Edit attributes:以编辑模式打开属性表并定位到该行。
Metadata 面板除了展示来源与配置,还能编辑描述性元数据:摘要(abstract)、关键字、许可、联系人、血缘(lineage)、时间范围、链接。Export as STAC Item 会把这份元数据写成一条 STAC Item——这是把工程里的图层变成可发布目录条目的最短路径。
6.1.2 Layer actions 完整菜单
… 菜单装下了其余全部动作。按用途归类如下。
图层与结构
| 菜单项 | 作用 |
|---|---|
| Rename | 改显示名(不改源) |
| Open Style panel | 打开样式面板 |
| New group from layer | 用该图层新建一个分组 |
| Edit geometry | 交给 GeoEditor 做顶点级编辑 |
| Load features into editor… | 把要素复制进 GeoEditor 的草图图层 |
| Open attribute table | 打开该图层的属性表 |
选择与分析
| 菜单项 | 作用 |
|---|---|
| Quick analysis | 无对话框地对整层跑 buffer、centroids、convex hull、bounding box |
| Select features | 交互选择:click / rectangle / polygon / freehand / radius,另有 Clear Selection |
| Select by Expression… | 用属性表达式构建选择;也可作为持久化图层过滤器 |
| Select by Location… | 用空间关系构建选择(需要第二个图层) |
| Bind to Time Slider… | 用该图层的日期或数值字段驱动时间轴 |
选择模式的修饰键是固定的:Shift 追加、Alt 移除、Shift+Alt 取交集、Esc 取消。
输出与互通
| 菜单项 | 作用 |
|---|---|
| Export | 导出数据、栅格、点云或三维模型(见 6.2.3) |
| Styles | 导入/导出符号化(见第 7 章) |
| Save to My Data | 把整层配置(来源、样式、标注、过滤、连接、虚拟字段、属性表单)存入个人库 |
| Copy style / Paste style | 在图层之间搬运符号化 |
同步与回写
| 菜单项 | 作用 |
|---|---|
| Refresh / Auto refresh | 立即重载或按间隔重载(WFS、GeoJSON URL) |
| Reload from disk / Watch file for changes | 桌面版本地文件图层:立即重读或监听磁盘变化(替换上面的两项) |
| Save edits to source file | 桌面版:把属性与几何改动写回 GeoJSON 或 GeoPackage 文件 |
| Save edits to ArcGIS service / PostGIS table / SQL Server table | 服务或数据库图层的回写项 |
| Materialize to editable layer | DuckDB 图层:把查询结果复制成可编辑的 GeoJSON 图层 |
| Edit query in SQL Workspace | SQL 查询图层:在 SQL 工作区重开该查询 |
不适用的条目会置灰:Select by Location 需要第二个图层,Paste style 需要剪贴板里有样式。
6.1.3 图层分组
分组是图层栈里的文件夹,可以嵌套,因此项目能拥有真正的层级而不是一条平铺清单。
| 操作 | 入口 | 说明 |
|---|---|---|
| 新建空组 | New group | 建一个空文件夹 |
| 用当前图层建组 | New group from layer | 把该图层包进新组 |
| 用多选建组 | New group from selected layers | 包住整个多选 |
| 移入 | Move to group | 移动一个图层 |
| 批量移入 | Move selected layers to group | 保持相对顺序 |
| 新建并定位 | Add data to group | 打开 Add Data,新图层直接进该组 |
| 组织 | 重命名、折叠/展开、上下移动、组级透明度 | 组级透明度作用于组内全部图层 |
| 排序 | Sort A to Z / Sort Z to A | 按名称排序,数字按自然序(Parcel 2 在 Parcel 10 前),忽略大小写 |
| 删除 | Ungroup (keep layers) / Delete group and layers | 前者解散保留图层,后者一起删除 |
分组有两条重要的语义规则:
- 组可见性与图层可见性是与关系。隐藏一个组会隐藏它内部的图层;一个图层若因为所在组不可见而不可见,面板会标注 Hidden because its group is not visible,以便与你手动关掉的图层区分。
- 组内图层必须连续。在共享的扁平图层数组里,同组图层必须挨在一起;一次会把某个图层与它的兄弟分开的移动(例如把它挪到一个子组之上,而兄弟还在下面)不会被执行,图层回到原位。
组级透明度是乘法:组的 opacity 乘进每个子图层自己的透明度,子图层存储的值不变。组与嵌套关系随工程保存,导入 QGIS 工程时也会把其分组树一并带过来。
6.1.4 混合模式(Blend)
Style 面板的符号化控件上方有一个 Blend 菜单,决定该图层的颜色如何与下方地图合成。透明度是把图层”稀释”,混合模式是把它”拌进去”。
| 模式 | 作用 | 典型用途 |
|---|---|---|
| Normal | 普通透明,默认 | 其余所有情况 |
| Multiply | 变暗:两色相乘 | 专题色或影像叠在山体阴影上 |
| Screen | 变亮:Multiply 的反向 | 把暗色图层从暗底图里提出来 |
| Lighten | 取较亮的颜色 | 叠加明亮要素而不压暗底图 |
| Add | 相加并趋向白色 | 发光、热力、密度叠加 |
混合作用于图层的填充、描边、点、标记与栅格瓦片。标注被刻意排除,始终按普通方式绘制——否则地名会跟着山体阴影一起变暗。
警告:点符号与三维建筑会混合两次。多边形、线与栅格瓦片以”整层”参与合成,同一图层内两个重叠多边形只与下方地图合成一次;但 MapLibre 对点符号与三维拉伸没有图层级合成步骤,每个符号各自合成。两个点符号在屏幕上重叠处会被混合两次,在 Multiply 下明显更暗。遇到这种情况,减小重叠(更小半径、聚合、比例尺相关的最小缩放)或把点转成多边形。
混合模式随工程保存,也会跟着复制样式的操作走,但它是 GeoLibre 的渲染设置,在 MapLibre/Mapbox 样式规范里没有对应项,因此导出为样式文件、SLD 或 QML 时会丢失。
为什么只有这五种?MapLibre 把所有图层画进一个 WebGL 画布,没有逐层混合 API,GeoLibre 只能用 GPU 的固定功能混合阶段实现——该阶段只能表达这五种。Photoshop 式的 Overlay、Color Dodge、Soft Light 需要在着色器里读画布已有颜色,每帧都要额外整帧拷贝,代价不可接受;Darken 与 Subtract 也不提供,因为它们的 GPU 方程会同时作用于透明通道,图层未覆盖的地方反而会把地图擦掉。
混合只在 MapLibre 绘制时应用,因此以下几类没有 Blend 菜单:
- 3D Tiles、高斯泼溅、LiDAR 点云、deck.gl 叠加层(自绘 WebGL);
- 通过 Add Data → Raster Layer 加的 COG(默认渲染引擎不是 MapLibre 图层);
- 通过 Add Data → Vector Layer 加的矢量图层(由面板自己的控件绘制,不存在应用混合的时机)。
其余都能混合:本地与远程 GeoJSON、普通栅格图层(XYZ、WMS、WMTS)、PMTiles、MBTiles、矢量瓦片。
6.1.5 悬停提示总开关
当至少一个图层配置了悬停提示(hover tooltip)时,Layers 面板(以及只读查看器)会出现一个 Hover tooltips 复选框。取消勾选会暂停所有图层的悬停提示,重新勾选则恢复各图层原来的字段选择。
这是一个纯查看开关:它不会修改工程、不会把工程标记为已修改,工程重新打开时会自动恢复为开启。
6.1.6 撤销与历史预算
撤销与重做在 Edit 菜单里,位于选择工具之上;快捷键不在菜单层暴露,具体键位见 Help → Keyboard Shortcuts(或按 ?)。撤销以快照实现:每次在图内编辑矢量图层,都会压入一份包含该图层完整 geojson 的快照。这套机制有两个写在 packages/core/src/history.ts 里的参数:
| 参数 | 默认值 | 作用 |
|---|---|---|
historyCoalesceMs |
400 ms | 把连续的、会产生历史记录的改动(例如拖动透明度或样式滑块)合并成一条撤销记录 |
maxHistoryFeatureCount |
500,000 | 跨快照保留的要素总数软预算 |
预算的计量方式值得一说:它统计去重后的要素数——多个快照共享同一份未改动图层的 geojson 时只算一次。内联 data: URL 的图层(例如被烘焙成像素的 NetCDF 网格)按每 200 字节折算一个要素计入,否则每次重设符号化都会写出一份新的数兆字节字符串,而快照看起来是”免费”的。
超过预算时丢弃最老的快照,但永远保留最新的一个——保证再大的图层也至少能撤销一步。所以在大图层上,可用的撤销深度会比小图层浅。
6.2 图层动作:缩放、识别、导出、刷新
6.2.1 缩放至图层
Zoom to layer 用图层的范围框住地图。三种情况需要区分:
- 范围已知:正常缩放;
- 范围未知(例如部分服务图层没有 bounds):不移动相机;
- 瓦片图层:可以从 TileJSON 的
bounds或已加载瓦片的要素范围推断(layer-control-host.ts里getLayerMetadataBounds专门处理这条路径)。
6.2.2 识别与选择
识别(Identify) 与选择(Select) 是两件事:识别只是”看”,选择会改变要素的选中状态,进而影响属性表、导出与后续分析。
| 维度 | Identify features | Select features |
|---|---|---|
| 触发 | 点击地图 | 选择工具激活后点击/框选 |
| 结果 | 弹窗列出属性 | 要素被标记为选中 |
| 栅格 | 读像素值;多波段生成光谱剖面 | 不适用 |
| 可编辑图层 | 附 Edit geometry / Edit attributes | 选中后可在属性表批量编辑 |
| 结束 | 关闭弹窗 | Esc 取消 |
选择模式共五种:click、rectangle、polygon、freehand、radius,另有 Clear Selection。
Select by Expression… 与 Select by Location… 把选择变成可表达的条件:
- Select by Expression 可以只做一次选择,也可以把表达式应用为持久化图层过滤器——不新建图层,直接隐藏不匹配的要素。这是”定义查询(definition query)”的行为。
- Select by Location 需要第二个图层作为参照,因此单图层工程里该项置灰。
注意:持久化表达式过滤器会保留表达式本身,但不会保留构建器里的
@变量。@map_zoom、@map_scale、@feature_count、@project_name、@layer_name在点击 Filter layer 时已被替换成当时的值。若希望过滤器继续随相机变化,请写["zoom"]而不是@map_zoom。
有活动过滤器的图层,其图层行上会出现一个漏斗图标——这条视觉线索很重要,它让”被过滤”与”数据缺失”不会被混淆。Layer actions → Clear filters 会一次清空所有过滤控件但保留控件本身。
6.2.3 导出
导出菜单的内容随图层类型变化:
| 图层类型 | 可导出格式 | 说明 |
|---|---|---|
| 矢量 | GeoJSON、GeoParquet、GeoPackage、KML、KMZ、zipped Shapefile、CSV(仅属性) | 全部在浏览器内完成 |
| 栅格 | GeoTIFF (COG)、Extract subset… | 子集按框裁剪 |
| LiDAR | LAS、LAZ、COPC | —— |
| 三维拉伸的多边形 | glTF(.glb)、OBJ、STL |
需先把 Visualization 设为 3D extrusion |
三维模型导出有几个必须知道的约定:
- 每个要素变成一个闭合实体(地面、墙面、屋顶),高度、基高、颜色与地图上绘制的一致;
- glTF 每个要素一个命名对象,属性作为自定义属性,每种颜色一个材质;Blender 用 File → Import → glTF 2.0 打开;
- OBJ 每个要素一个对象,颜色存在顶点上;
- STL 输出单个无颜色网格,面向三维打印与 CAD;
- 坐标以图层中心为原点的米为单位;glTF 与 OBJ 用 Y 轴向上,STL 用 Z 轴向上;glTF 在根对象上记录中心的经纬度;
- 高度不高于拉伸基高、或没有面积的要素会被跳过,并在提示里说明跳过了多少个。
6.2.4 样式互通与复制粘贴
图层级的样式互通是 Layer actions → Styles 子菜单,包含四类格式的导入导出(GeoLibre URL style、Mapbox GL style、OGC SLD、QGIS QML)、从文件或文本导入、以及 Style Manager 预设。这部分内容与”样式体系”高度耦合,放在第07章 样式符号化与图例展开。
Copy style / Paste style 是图层之间的快捷搬运:复制的是 LayerStyle 记录,不复制数据。批量场景请用 Project → Export → Export Layer Styles…(按图层名写出全部样式)与 Project → Import → Import Layer Styles…(按名字匹配应用)。
6.2.5 刷新与实时图层
可刷新的图层有两类:WFS 与 GeoJSON URL。
打开图层的刷新配置后可以选间隔:关闭、15 秒、30 秒、1 分钟、5 分钟、15 分钟,或自定义值;也可以随时手动刷新。
每个可重载图层都会把一条 connection 记录随工程保存,因此刷新节奏能跨保存与重开存活。记录字段(packages/core/src/types.ts):
export interface LayerConnection {
layerId: string;
interval: number | null; // 秒;null 表示仅手动同步
lastSyncedAt: string | null; // 最近一次成功的 ISO 时间
lastError: string | null; // 最近一次错误,成功后清空
onFailure: "keep-last" | "clear"; // 失败时保留旧数据还是清空
}
Layers 面板会显示同步状态(最近成功时间与最近错误)。onFailure 是两个完全不同的策略:
keep-last:刷新失败时保留上一份好数据,地图不会突然变空;clear:刷新失败即清空,适合”宁可空也不显示过期数据”的场景。
桌面版对本地文件图层用另一套动作:Reload from disk 立即重读,Watch file for changes 在磁盘文件变化时自动重载。它们替换了网络图层的 Refresh / Auto refresh。
6.2.6 DuckDB 图层与 SQL 查询图层
从 DuckDB 数据源添加的图层,或由 SQL 工作区产生的图层,支持与普通矢量图层一样的识别、选择与属性表。它们的特殊之处在”结果如何变成数据”:
| 动作 | 结果 |
|---|---|
| Materialize to editable layer | 把查询结果复制成可编辑的 GeoJSON 图层,此后可改几何与属性 |
| Edit query in SQL Workspace | 回到 SQL 工作区修改查询 |
| 刷新 | 重新执行查询,结果替换当前图层内容 |
SQL 查询图层在渲染上是一条外部 deck.gl 图层,判定条件被抽成一个共享函数(packages/core/src/types.ts):
export function isDuckDBQueryLayer(
layer: Pick<GeoLibreLayer, "metadata" | "type"> | undefined,
): boolean {
return (
layer?.type === "duckdb-query" &&
layer.metadata.sourceKind === "duckdb-query" &&
layer.metadata.externalDeckLayer === true
);
}
三个条件同时成立才算。这个函数被 @geolibre/map、@geolibre/plugins 与桌面应用共用,目的是防止三方对”这是不是一个 DuckDB 查询图层”的判断发生漂移——比如插件在做就地几何编辑时必须跳过这类图层。
6.3 底图、行星与区域底图
底图是 Layers 面板最下面那一行 Background。它有两个入口:
- 双击该行打开 Change background 对话框;
- 从 Plugins 菜单激活 Basemaps 插件。
底图的可见性与透明度同样可以在图层面板里调。
6.3.1 可选的底图族
| 类别 | 内容 | 备注 |
|---|---|---|
| OpenFreeMap 样式 | Liberty、Liberty 3D、Positron、Bright、Dark、Fiord | 无需密钥 |
| Protomaps 样式 | 构建提供时出现 | 取决于构建配置 |
| Regional(可折叠) | 区域底图分组 | 面板里以分组形式展开 |
| Blank | 纯色背景 | 颜色由引擎的 blank 背景设置决定 |
| Custom style URL | 自填样式地址 | 需要可访问且允许跨域 |
| 行星底图 | 见 6.3.2 | 会同时设定椭球体 |
底图切换是工程级设置:它随工程保存,也会在分屏的各个面板间共享(Mapbox 引擎下另有 preferences.map.mapboxStyleUrl,ArcGIS 引擎下有 preferences.map.arcgisBasemap,见第 8 章)。
6.3.2 其他天体
GeoLibre 能画地球之外的世界。Change background 对话框与 New project 对话框都把这些底图分组:
| 分组 | 天体 | 数据来源 |
|---|---|---|
| The Moon | 月球 | OpenPlanetaryMap |
| Mars | 火星 | OpenPlanetaryMap |
| Other celestial bodies(可折叠) | 水星、金星、伽利略卫星(Io、Europa、Ganymede、Callisto)、Titan、Pluto、Charon | USGS Astrogeology |
USGS Astrogeology 的底图在加载时实时重投影到 Web Mercator,这样 MapLibre 才能渲染。
快速切换用 Layers 面板标题栏的行星切换器(轨道图标)。选择某个天体不只是换底图——它会设定工程的 ellipsoid(椭球体),此后距离、面积与比例尺都按该天体的半径计算,而不是地球的。
提示:这一条比看起来重要。GeoLibre 内置的椭球体里,月球长半轴 1,737,400 m、火星(IAU 2000)3,396,190 m,相差约一倍,同一段像素距离在两者上代表的实际长度差别极大。换天体后如果测量结果”不对”,先检查椭球体是否跟着换了(见第 8 章 8.9.5)。
6.3.3 底图与图层的合成顺序
底图永远在最底层,且不参与图层排序。你可以:
- 关掉底图看纯数据(Blank 或隐藏 Background);
- 把底图透明度调低,让上面的栅格或矢量成为视觉主体;
- 用 Blend 的 Multiply 把专题色叠在山体阴影底图上(见 6.1.4)。
6.4 地图控件:Controls 菜单
Controls 菜单切换两类地图上的辅助元素:MapLibre 内置控件,以及提供 Measure、Bookmark 等工具的组件面板。菜单项前的勾表示当前显示。
6.4.1 内置控件
| 控件 | 说明 |
|---|---|
| Navigation | 放大/缩小按钮 |
| Compass | 指北针;点击把方位角复位为正北 |
| Fullscreen | 地图铺满屏幕 |
| Geolocate | 定位到当前位置 |
| Globe | 在平面地图与三维球面之间切换投影 |
| Terrain | 开关三维地形;双击控件设置垂直夸张系数 |
| Scale | 比例尺,单位由 Scale bar units 偏好决定 |
| Elevation | 在状态栏报告指针处地面高程(默认关闭) |
| Attribution | 显示数据归属 |
| Logos | 显示/隐藏 MapLibre 与提供方 logo |
6.4.2 组件工具
这些是 MapLibre components 插件提供的交互面板:
| 工具 | 说明 |
|---|---|
| Search | 按地名搜索并飞到结果 |
| Colorbar | 为栅格值显示连续色带 |
| Legend | 显示描述图上图层的图例 |
| HTML | 在地图面板里显示自定义 HTML |
| Image | 把 https:// 图片(logo、指北针、图例)放到任意角;每张图有可折叠的标题栏,一张地图最多 20 张 |
| Measure | 交互测量距离与面积,含方位角与地形感知的三维读数 |
| Bookmark | 保存命名视图并跳回;书签与文件夹随工程保存 |
| Minimap | 当前范围的鹰眼图 |
| View State | 读写精确的 center、zoom、bearing、pitch |
提示:由插件承载的控件可以放在任意角落。支持的插件在 Plugins 菜单里设置角落(top left、top right、bottom left、bottom right)。
Bookmark 有一个版本迁移细节:旧版本存在浏览器里的书签,会在你打开的第一个”没有任何书签”的工程时被复制进工程并保存;浏览器里的旧副本保留但不再更新。
6.4.3 默认可见性与默认位置
默认值写在 packages/map/src/map-engine.ts,所有引擎从同一张表出发,这样 Controls 菜单的勾选状态与地图上的实际状态不会分叉:
export const DEFAULT_BUILT_IN_CONTROL_VISIBILITY: Record<BuiltInMapControl, boolean> = {
navigation: false,
fullscreen: true,
compass: true,
geolocate: false,
globe: true,
terrain: false,
scale: true,
attribution: true,
logo: false,
"maptoolkit-logo": false,
"layer-control": true,
};
默认位置同样集中定义:导航、全屏、指北针、定位、球面、地形与图层控件在 top-right;比例尺与 logo 在 bottom-left;归属信息在 bottom-right。
需要注意的是这正是”看起来少了一个按钮”的常见原因:Navigation 与 Geolocate 默认是关闭的,需要在 Controls 菜单里手动打开。
6.4.4 控件与引擎的适配
内置控件集合由 BuiltInMapControl 联合类型描述:
export type BuiltInMapControl =
| "navigation" | "fullscreen" | "compass" | "geolocate" | "globe"
| "terrain" | "scale" | "attribution" | "logo" | "maptoolkit-logo"
| "layer-control";
不同引擎挂载同一套 id,只跳过自己无法承载的项。例如 Mapbox GL JS 没有自己的球面控件,于是引擎挂了一个替身控件来复刻 MapLibre 的按钮标记;Mapbox 的归属控件无法隐藏,Maptoolkit logo 又是 MapLibre 专属。详见第08章 渲染引擎与三维可视化。
6.4.5 地图上的 Layer Control
地图右上角的 Layer Control 是 Layers 面板的镜像。它与左侧面板共享同一份状态:
- 列出工程图层,包括当前隐藏或仍在加载的图层;
- 按相同的图层分组嵌套成可折叠文件夹,每个文件夹有自己的复选框与透明度滑块;
- 拖拽图层或使用右键 Move 动作会改变工程的图层顺序;
- 两处的开关、淡化、折叠互相同步;
- 分组自身的图层始终保持连续,破坏连续性的移动不会被应用。
分屏视图里不会挂载第二个 Layer Control——它只属于主图。
在实现上,这个控件是引擎中立的:它只需要共享的样式 API(getLayer、getStyle、paint/layout 读写、moveLayer、fitBounds、styledata 事件)加 IControl,而 Mapbox GL JS 同样实现这些。原先把它绑死在 MapLibre 上的是 GeoLibre 自己的粘合代码(store 适配器、结构变化重建、就地状态镜像、样式编辑器往返),这部分被抽到 layer-control-host.ts 的 LayerControlHost,两个二维引擎各自提供一份 LayerControlHostAdapter。Cesium 没有样式文档,因此没有 host。
6.5 测量、视域与右键快捷分析
6.5.1 Measure 面板
Controls → Measure 打开一个含三种模式的面板:
| 模式 | 产出 |
|---|---|
| Distance | 多段折线 |
| Area | 多边形 |
| Circle | 从中心点的半径 |
流程是固定的:选模式与 Unit → 点 Start → 在地图上依次点击 → 双击或右键结束 → Clear All 清除。
Exact segment 是键盘路线:输入长度与方位角,点 Add 就精确延伸一段,无需在地图上点。追查地契描述或导线测量时,这条比手点准得多。
除距离与面积,面板还给出:
| 读数 | 说明 |
|---|---|
| Heading | 测线方向,度数加 16 方位名(例如 310° NW) |
| Final heading | 到达端的方位 |
| Terrain (3D) | 开启三维地形后按坡度计算的距离 |
Heading 是真实的大圆初始方位,不是屏幕上的角度。 在墨卡托地图上,华盛顿到伦敦”看起来”正东,真实初始方位约 50°。三点以上的路径,Heading 取首点到末点的整体方向,而不是逐段一行。
Final heading 只在足够长的线上出现:大圆沿途会改变方向,只有收敛达到 1° 的线路才会单独给这一行。Terrain (3D) 在没有高程数据时自行隐藏;Heading 行不会隐藏,因为方位是坐标上的纯几何量,不需要 DEM。
6.5.2 右键快捷菜单
在地图上右键会打开以该坐标为中心的菜单:
| 菜单项 | 作用 |
|---|---|
| 坐标本身 | 等宽字体显示在顶部,点击复制到剪贴板 |
| What’s here? | 打开该地点的 Wikipedia 知识卡 |
| Copy as GeoJSON | 把点击点复制成 GeoJSON Feature |
| Center map here / Zoom in here | 把相机移到该点,可选再放大一级 |
| Quick analysis | 一键分析子菜单(见 6.5.3) |
| View in Google Maps / View in Google Earth | 在外部地图打开同一位置 |
6.5.3 从点击点做快捷分析
Quick analysis 直接在右键坐标上跑工具,不需要先建点图层:
| 动作 | 参数 |
|---|---|
| Buffer … here | 三档距离,跟随 Scale bar units:公制 500 m / 1 km / 5 km,英制 0.25 mi / 1 mi / 5 mi |
| Drive time from here | 5 / 10 / 15 分钟驾车等时圈 |
| Walk time from here | 同样的步行等时圈 |
| Viewshed from here | 2 km / 5 km / 15 km 的通视范围(见 6.5.4) |
| Open in Processing… | 打开完整的 Processing 矢量对话框,暴露预设没有的参数 |
结果作为新图层加入,横幅会报告进度与失败,并给出该次运行在 Processing History 里的入口。
同一套 Quick analysis 也出现在 Layers 面板每个矢量图层的动作菜单里,此时作用于整层:Buffer all features by …、Centroids、Convex hull、Bounding box。
警告:等时圈由 Valhalla 路径服务计算,默认是公共 FOSSGIS 实例
valhalla1.openstreetmap.de,因此你点击的坐标会离开本机。GeoLibre 首次使用会请求同意;自托管部署可以指向自己的服务。
6.5.4 从点击点计算视域
Quick analysis → Viewshed from here 计算从点击点可见的地形,并以半透明叠加层铺在地图上。
| 项 | 值 |
|---|---|
| 无需准备 DEM | 高程来自地图三维地形控件所用的同一套公共地形瓦片,按点周围的方形区域按需抓取 |
| 观察点高度 | 1.8 m(站立的人),在地面高程之上 |
| 半径 | 2 km / 5 km / 15 km 三档;底层工具接受 100 m 至 50 km |
| 结果 | 名为 Viewshed (5 km) 之类的图像叠加图层,因此有透明度、排序、缩放至、删除,并随工程保存 |
| 3D 地形 | 不需要开启 |
注意:快捷视域不建模地球曲率与大气折射。几公里内无所谓,但到 50 km 上限时,仅曲率造成的落差就约 180 m——做无线电视距研究时这就不可忽略了。公共地形瓦片也只是一套全球概化高程模型,不是测量级 DEM。需要严格分析时,请用 Processing → Whitebox Toolbox 里的 Viewshed 工具;这个快捷动作是刻意的补充而非替代。
6.6 相机、录制与环境工具
Controls 菜单还有一组工具:移动相机、把实时数据铺在地图上、或把看到的内容录下来。
| 工具 | 作用 | 是否访问外部服务 |
|---|---|---|
| Sun | 模拟任意日期时刻的太阳位置与昼夜分界线 | 否 |
| Weather | 叠加近实时的 Clouds(NASA 卫星影像,逐日动画)与 Precipitation(RainViewer 雷达,约近两小时动画) | 是(NASA、RainViewer) |
| Gridlines | 坐标网格与边缘标注,含 UTM 东距/北距模式与 MGRS/USNG 模式(含挪威与斯瓦尔巴特例外、带字母的 100 km 方格,放大后出现 10 km 与 1 km 线) | 否 |
| Spinning Globe | 缓慢旋转球面,可限定区域 | 否 |
| Route Animation | 让标记沿线图层运动:播放/暂停、速度、拖尾、相机跟随 | 否 |
| Flight Simulator | 用连续键盘控制飞越地形与三维图层 | 否 |
| Atmospheric Effects | 低缩放时渲染深空背景、星空、彗星与大气光晕 | 否 |
| Directions | 点击地图加途经点并获取路线 | 是(公共 OSRM 演示服务器) |
| Reverse Geocode | 点击地图反查地址 | 是(已配置的地理编码提供商) |
| Record Map Tour… | 录制关键帧动画视频,支持逐关键帧重拍、停留与转场时长、可保存的巡游设置 | 否 |
| Record Video… | 录制地图画布或框选区域,可把地图上的 HTML、图例、色带面板烧进视频 | 否 |
地图导航基础(这些键位在任何引擎下都可用):
- 平移:拖动地图,或在地图获得焦点时用方向键;
- 缩放:滚轮、捏合、导航控件,或
+/-; - 旋转:按住右键拖动、点指北针,或
Shift+←/→; - 俯仰:按住
Ctrl/Cmd拖动,或Shift+↑/↓; - 复位:
N正北朝上、U俯视、R同时复位俯仰与方位。
提示:
N、U、R三个键是排查”地图歪了”的最快手段。三维模式下俯仰角很容易被误触累积。
6.7 标注、Elements 面板与评论
6.7.1 标注工具
标注直接画在地图之上,共八种:
| 工具 | 形态 |
|---|---|
| Text | 文本 |
| Arrow | 箭头 |
| Rectangle highlight | 矩形高亮 |
| Ellipse highlight | 椭圆高亮 |
| Freehand highlight | 手绘高亮 |
| Pin marker | 图钉 |
| Sticky note | 便签 |
| Placed image | 放置图片 |
每种都有颜色;描边类(Arrow、Rectangle、Ellipse、Freehand)还认线宽。可以删除最后一条或清空全部。工具栏顶部的折叠箭头用于收起工具,给下方控件腾位置。
标注随工程保存。
6.7.2 Elements 面板
Elements 面板列出所有标注,便于查找与管理,不用在画布上一个个找。锚定方式有两种:
| 锚定 | 行为 |
|---|---|
| At Point | 绑定一个地理坐标,随之移动(多数元素默认) |
| Pinned to Extent | 拉伸到一个范围框,随视图缩放(Placed image 特有) |
理解这两种锚定,才能解释”为什么我缩放后图片变形了”——Pinned to Extent 本来就是按框铺开的设计。
6.7.3 评论
评论是锚定的评审便签,与标注分开,因为它是对话而不是地图装饰。
- 在右侧栏的 Comments 面板激活评论工具,或按
C; - 点击地图落一个图钉并写内容;
- 再按一次
C关闭工具; - GeoLibre 会问一次你的名字并记住。
每个图钉打开一个可回复的线程,可以 resolve、reopen、delete。面板可按 Open、Resolved、All 过滤;过滤为 Open 时,已解决的图钉不会显示在地图上。
在实时协作会话里,新增、回复、解决、删除都会同步给其他参与者。评论存在 .geolibre.json 里,因此即使没有会话在运行,它也会随保存或分享的工程一起走。
6.8 GPS 追踪与现场采集
Controls → GPS Tracking… 跟踪地图上的实时位置、记录轨迹日志,并可以直接从数据流数字化新要素。
Position source 有两个选项:
| 来源 | 说明 |
|---|---|
| This device | 浏览器或操作系统的定位 API,默认项,不需要额外硬件 |
| NMEA receiver (serial or Bluetooth) | 外接 GPS/GNSS 接收机,用于测量级或高频定位 |
选外接接收机时,需要选波特率,然后用 Connect serial 或 Connect Bluetooth 连接;对话框会显示设备名、已解析语句数与定位(fix)数的运行计数。
注意:读取接收机依赖 Web Serial 与 Web Bluetooth API,只有 Chromium 系浏览器(Chrome、Edge)提供,Firefox 与 Safari 没有。多数蓝牙 GPS 接收机讲的是经典蓝牙而不是低功耗蓝牙——这类先在操作系统设置里配对,配对后会以串口形式出现在这里;Connect Bluetooth 只用于低功耗蓝牙接收机。
启动 GPS 后可以:跟随位置、记录轨迹日志并存成图层、把轨迹导出为 GPX 或 GeoJSON、在你站立处捕获点。
Controls → Field Collection… 是相关工具,用于按自定义表单采集点、线、面观测,详见第10章 属性表、数据编辑与字段采集。
6.9 MapController.syncLayers:状态同步的概念模型
这是本章唯一需要读代码的部分,但它解释了很多现象。
6.9.1 单向数据流
用户操作 → Zustand store(layers 数组)→ MapCanvas 订阅
→ MapController.syncLayers(layers) → MapLibre source / layer
禁止 UI 直接改 MapLibre,改 store 让同步层去应用。这条规则来自 CLAUDE.md 与 docs/architecture.md,是整套架构的地基。
好处很直接:图层记录只有 id、类型、source、样式、可见性这些字段,与任何渲染引擎无关。因此切换引擎时相机、图层、分组、可见性、透明度与大部分样式都还在;也因此新增一个数据格式很便宜——只要能变成一条图层记录,就自动获得图层面板、透明度、排序、工程保存、样式导出的全部能力。
6.9.2 syncLayers 做的七件事
从 packages/map/src/map-controller.ts 的实现看,一次同步包含:
| 顺序 | 动作 | 细节 |
|---|---|---|
| 1 | 守卫 | 样式未就绪或地图未创建时直接返回 |
| 2 | 删除消失的图层 | 对比 id 集合,对不再存在的图层调用 removeLayerFromMap,并带上”仍然存活的 source id”集合,避免误删共享 source |
| 3 | 自顶向下放置 | layers 数组是”自下而上”,所以从末尾(最上层)往前走,每个图层插到”它上方那个图层”的第一个样式图层之下 |
| 4 | 记录快照 | 更新 layerIds 与 syncedLayers |
| 5 | 混合模式 | 混合模式在渲染循环里读,不是 paint 属性,因此模式变化需要主动触发重绘 |
| 6 | 底图状态 | 应用底图可见性与透明度 |
| 7 | 外围同步 | 发布图层显示名、刷新地图上的 Layer Control、同步其状态、重绑聚合缩放监听与待处理的原生过滤器监听 |
第 3 步的方向是刻意选的,注释里给了原因:如果自下而上放置,一个图层会被锚到”还没被移动过的邻居”上,只要某个图层的原生样式图层是带外出现的(例如 Add Vector Layer 的恢复:控件在数据加载完成后才把图层加到样式顶部,远晚于首次同步),整个图层栈会在那一趟里错位。后续同步会收敛,但 ?maponly 的纯地图嵌入没有任何面板去触发后续同步,于是错误顺序就成了查看者一直看到的样子(issue #1404)。
6.9.3 混合模式的特殊处理
syncLayerBlendModes(layers) 之后还有一段:
const blendSignature = blendModeSignature(layers);
if (blendSignature !== this.blendSignature) {
this.blendSignature = blendSignature;
map.triggerRepaint();
}
为什么要单独触发重绘?因为混合模式不是 paint 属性,只在渲染循环里读;一个只改了混合模式、没改任何 paint 的图层,如果没有这一帧请求,地图不会重画。而签名是每个控制器自己保存的:混合模式注册表是模块级的,分屏时若按”注册表变了没”判断,第一个同步的面板会赢下差异,导致另一个面板停留在旧模式,直到某个无关事件逼它重绘。
6.9.4 与 Layer Control 的同步
Layer Control 不持有可见性账本。控件”应不应该在地图上”由引擎的内置控件记录决定,由它调用 LayerControlHost.add / remove。控件内部维护的 layerStates(每个图层的 visible、opacity、name)需要被就地镜像——上游控件没有公开的 setter,所以 host 直接改写它并调用其内部的 buildLayerItems 重建行。
另一个细节是底图识别:host 若能拿到可 fetch 的样式 URL,就让控件自己去分辨哪些样式图层是底图;拿不到时(Blank、GeoLibre 哨兵值、mapbox:// 样式)改用引擎报告的底图图层 id 集合预置 basemapLayerIds。否则控件会退化成”我挂载时地图上有什么,什么就是底图”,把工程图层一并当成底图,Background 开关一点就把数据图层关掉了。
提示:理解这一节之后,”为什么我改 store 但地图没动”和”为什么地图上多了一个控件看不见的图层”这两类问题就有了统一的排查入口:先确认同步是否跑过,再检查该图层是否被标记为控件自有(plugin-owned)或外部原生图层。
6.10 常见问题排查
| 现象 | 最可能的原因 | 检查动作 |
|---|---|---|
| 图层在列表里但地图上没有 | 图层被隐藏、所在组被隐藏、透明度为 0 | 看图层行标注是否写着 Hidden because its group is not visible;查组透明度 |
| 图层有漏斗图标但”数据像缺了” | 有活动的快速过滤器或持久化表达式过滤 | Layer actions → Clear filters |
| 某按钮置灰 | 该动作对当前图层类型不适用 | 例如 Select by Location 需要第二个图层,Paste style 需要剪贴板有样式 |
| 缩放至图层无反应 | 图层范围未知 | 服务图层可能没有 bounds;瓦片图层需 TileJSON 或已加载瓦片 |
| 悬停提示不出现 | 全局 Hover tooltips 被取消勾选,或该图层没配 hover 字段 | 勾回复选框;检查 Popup 设计里的 In hover tooltip |
| 点符号重叠处明显更暗 | Multiply 混合对点符号逐符号生效 | 减小重叠或改用多边形(见 6.1.4) |
| 立体拉伸导出 OBJ 打开后上下颠倒 | 坐标系别(glTF/OBJ 为 Y 向上,STL 为 Z 向上) | 按目标软件改用对应格式 |
| Layer Control 与面板不同步 | 控件自有图层或外部原生图层未被镜像 | 见 6.9.4;必要时折叠再展开连接/面板 |
| 刷新后地图突然变空 | 连接的 onFailure 为 clear |
改为 keep-last |
| 大图层拖动卡顿 | 要素数过大,GeoJSON 直接渲染 | 转 PMTiles(见第 5 章 5.6.4) |
6.11 本章小结
- 图层栈:列表越靠上越后绘制,底图固定在最底层;顺序、可见性、透明度都有面板与地图控件两个入口。
- 图层卡片提供 Move、Zoom to layer、Identify、Style、Layer actions、Metadata、Remove 七个高频动作;
…菜单覆盖其余全部。 - 分组可嵌套,组可见性与图层可见性相与、组透明度相乘,组内图层必须连续,破坏连续性的移动会被拒绝。
- 混合模式只有 Normal/Multiply/Screen/Lighten/Add 五种,因为只能用 GPU 固定功能混合阶段实现;标注不参与混合;点符号与三维建筑会重复混合。
- 撤销以快照实现:400 ms 内合并成一条记录,跨快照要素软预算 500,000,超预算丢最老的快照但保留最新一个。
- 刷新策略存在
LayerConnection记录里(interval、lastSyncedAt、lastError、onFailure),随工程保存;本地文件图层改用 Reload/Watch。 - 底图支持 OpenFreeMap/Protomaps/Regional/Blank/自定义样式,以及月球、火星与其他天体;换天体会改工程椭球体,进而改变量算结果。
- Controls 菜单分内置控件与组件工具两类;导航与定位默认关闭,图层控件默认开启。
- 测量除距离面积外给出真实大圆初始方位;快捷分析可对点击点跑缓冲、等时圈、视域;快捷视域不建模曲率与折射。
- syncLayers 是单向数据流的落地处:删消失、自顶向下放置、触发重绘、同步底图与 Layer Control,共七步。
6.12 实践练习
-
图层栈与分组:加载三个矢量图层,建两个嵌套组,把其中一个组设为 50% 透明度,观察组内图层与组外图层在视觉上的差异;再尝试把一个图层拖到”会把它与兄弟分开”的位置,记录面板的反应。
-
混合模式对比:加载一份山体阴影(或深色底图)与一份专题多边形,分别用 Normal 叠 60% 透明度、Multiply 叠 100% 透明度,比较两种做法的视觉效果,并说明为什么混合模式比降透明度更适合”让地形透出来”。
-
撤销预算实验:对一个中等矢量图层连续拖动透明度滑块 10 秒,然后撤销,记录撤销了几步;再用一个要素数明显更大的图层重复,比较可用撤销步数,并解释 500,000 这个软预算的作用。
-
视域精度:用 Quick analysis → Viewshed from here 在 2 km 与 15 km 两个半径下各算一次,然后把同一观察点交给 Processing → Whitebox Toolbox → Viewshed 用一份真实 DEM 再算一次,比较三者的边界差异,并写出快捷视域忽略了哪些因素。
-
实时图层:把一个 GeoJSON URL 图层的自动刷新设为 15 秒,在浏览器里观察同步状态的更新时间;随后故意让 URL 失效,记录
onFailure为keep-last与clear两种设置下地图的不同表现。 -
控件复位:把地图旋转 40°、俯仰 50°,分别用
N、U、R三个键,记录每次视图的变化,并说明这三个键各自复位了什么。
延伸阅读
- 仓库文档:
docs/user-guide/layers.md、docs/user-guide/map-controls.md、docs/architecture.md - 源码:
packages/map/src/map-controller.ts(syncLayers)、packages/map/src/layer-sync.ts、packages/map/src/layer-control-host.ts、packages/map/src/map-engine.ts、packages/core/src/history.ts、packages/core/src/types.ts - 项目主页:https://geolibre.app