Skip to content

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)

选项类型说明
crsstring工作 CRS,默认 'EPSG:3857'
featuresEditableFeature[]初始要素
rulesValidationRule[]校验规则(默认空)
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[]) => void409 冲突 rebase:采纳服务端状态并清史
remapIds(idMap: Record<FeatureId, FeatureId>) => void临时 id→真实 id(状态+历史栈一并替换)

EditSession

引擎之上的门面:选择集 + 剪贴板 + 吸附索引。构造 new EditSession(options?: EditSessionOptions)(继承 EditEngineOptions,省略 rules 时用 defaultLocalRules())。

成员签名说明
engineEditEngine底层引擎
snapSnapIndex吸附索引
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绑定交互+订阅渲染,返回解绑

命令

全部实现 Commandlabel + 可选 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>(每个句柄 propertiesVertexHandleRef;多边形逐环跳过闭合点,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

校验规则

导出形态说明
noZeroLengthLineValidationRule(error)零长折线
noSelfIntersectionValidationRule(error)折线/多边形自相交
minLengthRule(min, severity?) => ValidationRule最小长度
noDuplicateVertices(severity?) => ValidationRule相邻重复顶点
featureTypeConsistency(typeMap: Record<string, GeometryKind>, severity?) => ValidationRule类别↔几何一致性
defaultLocalRules() => ValidationRule[]默认本地规则集

吸附

class SnapIndexload(targets: SnapTarget[]): voidquery(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 SyncClientnew SyncClient(engine, transport)pendingCounthasPending()commit(): Promise<CommitOutcome>dispose()

class MemoryEditBackend implements EditTransportnew 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

相关