Appearance
OpenLayers 适配器(@geoverse/editor-ol)
@geoverse/editor-ol 把 OpenLayers 的原生交互(Draw / Modify / Translate / Pointer)归一成 editor-core 的命令。它接收一个 ol/Map(geoverse 的 GMap 继承自 ol/Map,可直接传入)。
安装
shell
pnpm add @geoverse/editor-core @geoverse/editor-ol geoverse ol上手
ts
import { GMap } from '@geoverse/core-ol';
import { EditSession } from '@geoverse/editor-core';
import { OlEditorAdapter } from '@geoverse/editor-ol';
const map = new GMap({
target: 'map',
base: 'gd-vec',
center: [118.18, 24.49],
zoom: 15,
});
const crs = map.getView().getProjection().getCode();
const session = new EditSession({ crs, features: seed }); // seed 坐标须为工作 CRS(投影坐标)
const adapter = new OlEditorAdapter(map, {
crs,
// select 模式单击要素时回调;shift 多选。由你同步到 session 并高亮:
onSelect: (id, additive) => {
if (id) session.select([id], additive);
else if (!additive) session.clearSelection();
adapter.setSelection(session.getSelected());
},
});
session.attach(adapter);
adapter.setMode('select');工作 CRS
适配器几何按工作 CRS 原样进出(dataProjection === featureProjection)。OL 轨通常用视图投影(默认 EPSG:3857,单位米),变换/偏移的距离即米。seed 要素坐标也须是该投影坐标——可用 ol/proj 的 fromLonLat 从经纬度转换。
工具模式(adapter.setMode(mode))
| 模式 | 操作 | 归一命令 |
|---|---|---|
select | 单击要素选中,Shift 多选(经 onSelect 回调) | — |
move | 在要素上拖拽整体平移 | TranslateCommand |
transform | 选中后用包围盒手柄:拖角点缩放、拖顶部圆点旋转 | ScaleCommand / RotateCommand |
modify | 拖动顶点句柄微调形状 | SetGeometryCommand |
add-vertex | 在线/面边上单击插入节点 | AddVertexCommand |
delete-vertex | 单击顶点删除(守最小顶点数) | DeleteVertexCommand |
split | 在线要素上单击一点打断 | SplitLineCommand |
split-polygon | 在选中的面上画切割线(Draw 折线) | SplitPolygonByLineCommand |
merge | 依次单击两条相接的线合并 | MergeLinesCommand |
draw-point / draw-line / draw-polygon | 绘制要素(线/面双击结束) | AddFeatureCommand |
transform 模式的手柄(gizmo)由适配器自动按当前选择集包围盒绘制;旋转/缩放在松手时落一个可撤销命令。
面拆分(split-polygon)的切割线约束
切割线必须完整贯穿外边界——从面的一侧外边界进、另一侧外边界出,才能把面分成两块。切割线可以顺带穿过洞(含洞面:穿到的洞按块裁回,各块保留所属洞),但穿洞并非必需(不碰洞的贯穿线照样切);反之,切割线若终止在面内或洞内(没穿出对侧外边界)则不构成切分,命令返回 SPLIT_NO_EFFECT 并在 UI 提示——这是按设计的正确行为,不是缺陷。
顶点句柄显示
句柄按模式 + 选择集分三档(详见核心概念 · 顶点句柄显示):modify / add-vertex / delete-vertex 显示实心可编辑句柄;select / move / transform 显示更淡的「幽灵」句柄(仅展示结构、不可拖);绘制/拆分/合并不显示既有顶点。有选择时只显示选中要素的句柄。整体平移与变换拖拽期间句柄临时隐藏,顶点拖拽则由 ol/Modify 自绘跟随句柄。
非模式操作(直接派发命令)
镜像、偏移、点/面合并、多点拆分、复制粘贴不依赖模式,作用于当前选择集,由应用层直接派发:
ts
import {
MirrorCommand,
OffsetCommand,
MergePolygonsCommand,
} from '@geoverse/editor-ol';
const sel = session.getSelected();
session.engine.dispatch(new MirrorCommand(sel, 'horizontal'));
session.engine.dispatch(new OffsetCommand(sel[0], 40)); // 平行偏移 40m
session.engine.dispatch(new MergePolygonsCommand(sel)); // 面并集(JSTS)
adapter.setIssues(/* 上一步 EditResult.issues */ []); // 把校验问题喂回适配器以标红
session.copy(); // 复制选中
session.paste([60, 60]); // 粘贴(偏移 60m)面/多面运算(合并不相邻面会得到 MultiPolygon,可再拆分;差集与挖洞为两个面的二元运算,第一个选中为目标 A):
ts
import {
MergePolygonsCommand,
SplitMultiPolygonCommand,
DifferencePolygonsCommand,
PunchHoleCommand,
FillHoleCommand,
} from '@geoverse/editor-ol';
session.engine.dispatch(new MergePolygonsCommand([a, b])); // 不相邻 → MultiPolygon
session.engine.dispatch(new SplitMultiPolygonCommand(mpId)); // MultiPolygon → 多个 Polygon
session.engine.dispatch(new DifferencePolygonsCommand([a, b])); // 布尔 A−B(B 在内则成洞、跨界则切缺口)
session.engine.dispatch(new PunchHoleCommand([a, b])); // 结构挖洞:B 作为 A 的内环(B 须在 A 内部,A 须为单面)
session.engine.dispatch(new FillHoleCommand(a)); // 填洞:移除面的所有内环(PunchHole 的逆操作)差集 vs 挖洞:差集是布尔运算(通用,B 跨边界会切缺口);挖洞是结构插入内环(非布尔,精确保留 A 外边界,要求 B 完全在 A 内部)。挖洞 ↔ 填洞互为逆操作。
面开洞 / 消洞(线驱动的「非分离切」,1→1;命令吃「面 id + 线」,通常经适配器 open-hole / close-hole 模式画线触发):
ts
import { OpenHoleCommand, CloseHoleCommand } from '@geoverse/editor-ol';
// 开洞(线驱动):切割线从外边界进入某个内环(洞)、不贯穿到对侧 → 洞经细通道连到外边界成凹湾
session.engine.dispatch(new OpenHoleCommand(id, cutLine)); // cutLine: GeoJSON LineString(工作 CRS = 视图投影,单位米)
// 消洞(一键,无需交互):把面外边界的所有凹湾自动封回内环洞(开洞的逆向能力,与面填洞对称)
session.engine.dispatch(new CloseHoleCommand(id));
// 通道宽度缺省按面 bbox 对角线 0.5% 自适应,可覆写:
session.engine.dispatch(new OpenHoleCommand(id, cutLine, { width: 20 })); // 20m 宽通道
// 适配器交互:选中含洞面 → open-hole 模式画切割线(双击结束);消洞是一次性命令、直接 dispatch 即可
adapter.setMode('open-hole');开洞 vs 按线拆面:拆面须贯穿外环、把面切成 2 块;开洞的切割线从外边界进洞、不穿到对侧,面仍 1 块、洞环并入外环成凹湾。若切割线贯穿到对侧(切成多块),
OpenHoleCommand报OPEN_HOLE_SPLIT,提示改用「按线拆面」。消洞是一次性命令(无需交互,类似面填洞):
CloseHoleCommand(id)用「凸包 − 外环」自动求出面上的所有凹湾并逐个封成内环洞——不必点选位置。面的外边界为凸、无凹湾时报CLOSE_HOLE_NO_BAY(普通带洞面若只想去掉洞,请用FillHoleCommand)。开洞 ↔ 消洞互为逆操作。
线/多线运算(MergeLinesCommand 要求端点相接;不相接的多条线用 CombineLinesCommand 归并为 MultiLineString,可再拆分):
ts
import {
MergeLinesCommand,
CombineLinesCommand,
SplitMultiLineStringCommand,
} from '@geoverse/editor-ol';
session.engine.dispatch(new MergeLinesCommand(a, b)); // 端点相接 → 单条 LineString
session.engine.dispatch(new CombineLinesCommand([a, b])); // 不要求相接 → MultiLineString
session.engine.dispatch(new SplitMultiLineStringCommand(mlsId)); // MultiLineString → 多条 LineString常用命令已从
@geoverse/editor-ol转出(TranslateCommand、RotateCommand、ScaleCommand、MirrorCommand、OffsetCommand、MergeLinesCommand、CombineLinesCommand、MergePointsCommand、MergePolygonsCommand、DifferencePolygonsCommand、PunchHoleCommand、FillHoleCommand、SplitMultiPointCommand、SplitMultiPolygonCommand、SplitMultiLineStringCommand、SplitPolygonByLineCommand、AddFeaturesCommand),也可直接从@geoverse/editor-core引入。
拓扑标红
适配器按要素的 issues 给出红(error)/橙(warning)描边。引擎 dispatch 的返回值带 issues;把它喂回 adapter.setIssues(res.issues) 即可高亮(撤销/重做后传 [] 清除)。
吸附
拖动顶点(modify)或绘制(draw-*)时,落点会自动吸附到既有要素的顶点与边——底层用 ol/interaction/Snap 绑定编辑源。命中吸附点时该处显示橙色高亮环,光标也按模式给出可操作提示。默认开启,可在构造时关闭或调整像素容差,也可运行时切换:
ts
const adapter = new OlEditorAdapter(map, { snap: true, snapTolerance: 10 });
adapter.setSnap(false); // 运行时关闭下一步
- 核心概念 · MapLibre 适配器 · 编辑器 API
在线示例:仓库
examples/editor/(pnpm dev:examples→/editor/)。
