Appearance
editor-core API(引擎与命令)
@geoverse/editor-core 的全部导出。框架无关,可在 node 与浏览器使用;几何一律为 GeoJSON,工作 CRS 由 EditSession/EditEngine 声明。适配器 API 见适配器(OL · MapLibre)。
ts
import {
EditEngine,
EditSession,
SplitLineCommand /* … */,
} from '@geoverse/editor-core';EditEngine
无地图依赖的状态机:校验 → 应用 → 入撤销栈 → 通知渲染。
构造:new EditEngine(options?: EditEngineOptions)
| 选项 | 类型 | 说明 |
|---|---|---|
crs | string | 工作 CRS,默认 'EPSG:3857' |
features | EditableFeature[] | 初始要素 |
rules | ValidationRule[] | 校验规则(默认空) |
idGen | () => string | 临时 id 生成器(默认 nanoid),便于测试可重现 |
| 方法 | 签名 | 说明 |
|---|---|---|
dispatch | (cmd: Command) => EditResult | 校验→应用→入栈→emit;失败返回 { ok:false, issues },状态不变 |
undo / redo | () => boolean | 撤销 / 重做,栈空返回 false |
on | (l: (s: EditSnapshot) => void) => () => void | 订阅快照,返回退订 |
onTransaction | (l: (e: TransactionEvent) => void) => () => void | 订阅事务(apply/undo/redo),同步层用 |
snapshot | () => EditSnapshot | 当前快照 |
getFeature | (id) => EditableFeature | undefined | 取要素(克隆) |
setVersions | (versions: Record<FeatureId, number>) => void | 写回后端版本 |
adoptServerState | (features: EditableFeature[]) => void | 409 冲突 rebase:采纳服务端状态并清史 |
remapIds | (idMap: Record<FeatureId, FeatureId>) => void | 临时 id→真实 id(状态+历史栈一并替换) |
EditSession
引擎之上的门面:选择集 + 剪贴板 + 吸附索引。构造 new EditSession(options?: EditSessionOptions)(继承 EditEngineOptions,省略 rules 时用 defaultLocalRules())。
| 成员 | 签名 | 说明 |
|---|---|---|
engine | EditEngine | 底层引擎 |
snap | SnapIndex | 吸附索引 |
select | (ids: FeatureId[], additive?: boolean) => void | 选择(additive 追加) |
toggle / clearSelection | (id) => void / () => void | 切换 / 清空选择 |
getSelected / isSelected | () => FeatureId[] / (id) => boolean | 读选择集 |
onSelectionChange | (l: (sel: FeatureId[]) => void) => () => void | 订阅选择变化 |
copy | (ids?: FeatureId[]) => number | 复制到剪贴板(默认当前选择),返回条数 |
hasClipboard | () => boolean | 剪贴板是否非空 |
paste | (offset?: [number, number]) => EditResult | undefined | 粘贴(带偏移,可撤销) |
loadSnapTargets | (targets: SnapTarget[]) => void | 刷新吸附候选 |
resolveSnap | (near, tol) => SnapHit | { coord; distance; target: null } | 求吸附落点 |
attach | (adapter: MapAdapter) => () => void | 绑定交互+订阅渲染,返回解绑 |
命令
全部实现 Command(label + 可选 appliesTo + plan(ctx))。appliesTo 不匹配时命令抛 EditError(TYPE_MISMATCH),被 dispatch 转为 issues。
| 命令 | 构造签名 | 形态 / 适用 |
|---|---|---|
AddFeatureCommand | (geometry, properties?, label?) | 0→1,任意几何 |
AddFeaturesCommand | (inputs: NewFeatureInput[], label?) | 0→N(粘贴) |
TranslateCommand | (ids, dx, dy) | modified,全类型 |
RotateCommand | (ids, deg, origin?) | modified,全类型(默认绕选择集中心) |
ScaleCommand | (ids, factor, origin?) | modified,全类型 |
MirrorCommand | (ids, axis?: MirrorAxis) | modified,全类型 |
OffsetCommand | (id, distance) | 0→1,LineString / Polygon / MultiPolygon |
MoveVertexCommand | (targetId, ref: VertexRef, to) | 1→1,LineString / Polygon |
AddVertexCommand | (id, ref: SegmentRef, at) | 1→1,LineString / Polygon |
DeleteVertexCommand | (id, ref: DeleteVertexRef) | 1→1,守最小顶点数 |
SplitLineCommand | (targetId, at) | 1→2,LineString |
SplitMultiPointCommand | (id) | 1→N,MultiPoint |
SplitPolygonByLineCommand | (targetId, line) | 1→N,Polygon(JSTS) |
MergeLinesCommand | (...ids) | N→1,LineString |
MergePointsCommand | (ids) | N→1,Point / MultiPoint |
MergePolygonsCommand | (ids) | N→1,Polygon / MultiPolygon(JSTS union) |
SetGeometryCommand | (targetId, after, label?) | 1→1,整体替换几何 |
相关类型:
ts
type MirrorAxis = 'horizontal' | 'vertical' | { a: Position; b: Position };
interface NewFeatureInput {
geometry: Geometry;
properties?: Record<string, unknown>;
}
interface VertexRef {
ringIndex?: number;
index: number;
} // 移动顶点
interface SegmentRef {
ringIndex?: number;
segmentIndex: number;
} // 在段后插入
interface DeleteVertexRef {
ringIndex?: number;
index: number;
} // index 为去闭合后序号几何函数
拓扑(Turf):
| 函数 | 签名 |
|---|---|
splitLineAt | (line: LineString, at: Position) => [LineString, LineString] |
mergeLines | (a: LineString, b: LineString) => LineString |
lineLength | (line: LineString) => number |
isZeroLengthLine | (line: LineString) => boolean |
lineSelfIntersects | (coords: Position[]) => boolean |
duplicateVertexIndices | (coords: Position[]) => number[] |
coordsEqual | (a: Position, b: Position) => boolean |
仿射(工作 CRS 内笛卡尔变换):
| 函数 | 签名 |
|---|---|
mapCoordinates | (g: Geometry, fn: CoordFn) => Geometry |
translateGeometry | (g, dx, dy) => Geometry |
rotateGeometry | (g, deg, origin) => Geometry |
scaleGeometry | (g, fx, fy, origin) => Geometry |
mirrorGeometry | (g, a, b) => Geometry |
offsetLine | (coords: Position[], distance) => Position[] |
稳健面运算(JSTS):
| 函数 | 签名 |
|---|---|
unionPolygons | (geoms: (Polygon | MultiPolygon)[]) => Polygon | MultiPolygon |
differencePolygon | (a, b) => Polygon | MultiPolygon |
bufferGeometry | (g: Geometry, distance) => Polygon | MultiPolygon |
splitPolygonByLine | (polygon: Polygon, line: LineString) => Polygon[] |
顶点句柄(两端共用,适配器渲染用):
| 函数 | 签名 |
|---|---|
collectVertexHandles | (features: EditableFeature[]) => FeatureCollection<Point>(每个句柄 properties 为 VertexHandleRef;多边形逐环跳过闭合点,Multi* 不展开) |
ts
interface VertexHandleRef {
featureId: FeatureId;
ringIndex: number;
index: number;
}变换手柄(gizmo,纯计算)
| 函数 | 签名 |
|---|---|
featuresBBox | (geoms: Geometry[]) => BBox | null |
bboxCenter | (b: BBox) => Position |
gizmoHandles | (b: BBox, rotateGap: number) => GizmoHandle[] |
rotationFromDrag | (origin, start, current) => number(度增量) |
scaleFactorFromDrag | (origin, start, current) => number(等比因子) |
GizmoHandleRole = 'rotate' \| 'scale-nw' \| 'scale-ne' \| 'scale-se' \| 'scale-sw'。
类型校验
| 导出 | 签名 |
|---|---|
geometryKind | (g: Geometry) => GeometryKind | 'GeometryCollection' |
assertApplicable | (features, appliesTo: readonly GeometryKind[], label) => void(不匹配抛 TYPE_MISMATCH) |
assertHomogeneous | (features, label) => void(混类型抛 HETEROGENEOUS) |
校验规则
| 导出 | 形态 | 说明 |
|---|---|---|
noZeroLengthLine | ValidationRule(error) | 零长折线 |
noSelfIntersection | ValidationRule(error) | 折线/多边形自相交 |
minLengthRule | (min, severity?) => ValidationRule | 最小长度 |
noDuplicateVertices | (severity?) => ValidationRule | 相邻重复顶点 |
featureTypeConsistency | (typeMap: Record<string, GeometryKind>, severity?) => ValidationRule | 类别↔几何一致性 |
defaultLocalRules | () => ValidationRule[] | 默认本地规则集 |
吸附
class SnapIndex:load(targets: SnapTarget[]): void、query(near: Position, tol: number): SnapHit | null。
ts
interface SnapTarget {
kind: 'vertex' | 'edge';
coord: Position;
edgeEnd?: Position;
featureId?: FeatureId;
}
interface SnapHit {
coord: Position;
distance: number;
target: SnapTarget;
}后端同步
class SyncClient:new SyncClient(engine, transport)、pendingCount、hasPending()、commit(): Promise<CommitOutcome>、dispose()。
class MemoryEditBackend implements EditTransport:new MemoryEditBackend(options?)({ idGen?, serverRules? })、seed(features)、all()、get(id)、bumpVersion(id, geometry?)、submit(submission)。
ts
interface EditTransport {
submit(s: EditSubmission): Promise<SubmitResponse>;
}
interface EditSubmission {
changeSets: ChangeSet[];
baseVersions: Record<FeatureId, number>;
}
type SubmitResponse =
| {
ok: true;
idMap: Record<FeatureId, FeatureId>;
versions: Record<FeatureId, number>;
issues: ValidationIssue[];
}
| { ok: false; status: 409; conflicts: EditableFeature[] };
type CommitOutcome =
| { status: 'noop' }
| {
status: 'committed';
idMap: Record<FeatureId, FeatureId>;
versions: Record<FeatureId, number>;
issues: ValidationIssue[];
}
| { status: 'conflict'; conflicts: EditableFeature[] };核心类型
ts
type FeatureId = string;
type GeometryKind =
| 'Point'
| 'MultiPoint'
| 'LineString'
| 'MultiLineString'
| 'Polygon'
| 'MultiPolygon';
interface EditableFeature {
id: FeatureId;
geometry: Geometry;
properties: Record<string, unknown>;
version?: number;
}
interface ChangeSet {
txId: string;
label: string;
added: EditableFeature[];
removed: EditableFeature[];
modified: { id: FeatureId; before: Geometry; after: Geometry }[];
}
interface EditResult {
ok: boolean;
issues: ValidationIssue[];
}
interface EditSnapshot {
features: EditableFeature[];
canUndo: boolean;
canRedo: boolean;
undoLabel?: string;
redoLabel?: string;
}
interface ValidationIssue {
ruleId: string;
severity: 'error' | 'warning';
message: string;
featureIds: FeatureId[];
}
interface Command {
readonly label: string;
readonly appliesTo?: readonly GeometryKind[];
plan(ctx: EditContext): ChangeSet;
}EditError(含 code,如 TYPE_MISMATCH / HETEROGENEOUS / MERGE_TOO_FEW / SPLIT_NO_EFFECT)在命令规划/校验阶段抛出,由 dispatch 捕获转为 issues。
