唯杰地图 logo唯杰地图
VJMAP
EN

custom-entity

vjmapext/references/entity/custom-entity.md 6.1 KB134 行
原文

本文回答:自定义实体 —— 继承 EntityBase、实现抽象方法(toGeoJSON / boundingBox / getGripPoints / gripEdit / clone / 变换四件套)、注册到 entityRegistry。

一、什么时候需要

内置实体覆盖不了的场景:

  • 业务对象:电线杆、井盖、信号灯,带业务数据 + 专属几何
  • 非标几何:星形、齿轮、特殊符号
  • 复合展示:一个实体产生多个 GeoJSON feature(线 + 标签 + 填充)

二、EntityBase 契约(按 d.ts 核对)

EntityBase 是抽象类,constructor() 无参。必须实现的抽象成员:

成员 签名 作用
type abstract readonly type: string 实体类型名(如 'GEAR')
toGeoJSON() (): GeoJSON.Feature[] 渲染入口——实体转成带样式属性的 feature 交给 GPU 层绘制
boundingBox() (): BBox CAD 空间外接盒 [minX,minY,maxX,maxY],框选 / 空间索引 / zoomExtents 依赖
getGripPoints() (): GripPointDef[] 夹点定义
gripEdit(newPos, gripKey) void 夹点拖拽回调
clone() (): EntityBase 复制(undo / 复制粘贴依赖)
move(from, to) void 平移:从 from 点移到 to 点(注意是两点参数,不是 dx/dy)
rotate(center, angle) void 绕 center 旋转 angle(弧度)
scale(center, factor) void 以 center 为基准缩放
mirror(p1, p2) void 沿 p1-p2 轴镜像

常用可写属性:color layerId lineWidth visible editable dynamic minDisplayLevel/maxDisplayLevel effectColor/effectOpacity 等;自定义业务字段走 setCustomProperty(key, value)。

变更通知:几何变了调 this.changed(),只变样式调 this.styleChanged()(快路径,见 batch/bulk-style-update)。

三、骨架示例

class GearEnt extends vjmapext.EntityBase {
  get type() { return 'GEAR'; }

  constructor(center = [0, 0], radius = 50, teeth = 12) {
    super();
    this.center = new vjmapext.Point2D(center[0], center[1]);
    this.radius = radius;
    this.teeth = teeth;
  }

  // 渲染:转成 GeoJSON features(用 makeFeature 带上公共样式属性)
  toGeoJSON() {
    return [this.makeFeature(
      { type: 'LineString', coordinates: [...this._teethPoints(), this._teethPoints()[0]] },
      'fill'   // role,可按需拆多条 feature 表达描边+填充
    )];
  }

  boundingBox() {
    const { x, y } = this.center, r = this.radius;
    return [x - r, y - r, x + r, y + r];
  }

  getGripPoints() {   // GripPointDef 字段是 key + position + type(没有 point/onMove)
    return [
      { key: 'center', position: this.center, type: 'center' },
      { key: 'radius', position: new vjmapext.Point2D(this.center.x + this.radius, this.center.y), type: 'quad' }
    ];
  }

  gripEdit(newPos, gripKey) {
    if (gripKey === 'center') this.move(this.center, newPos);
    else this.radius = this.center.distanceTo(newPos);
    this.changed();
  }

  clone() {
    const g = new GearEnt(this.center.toArray(), this.radius, this.teeth);
    this.copyBaseProps(g);
    return g;
  }

  // 变换四件套必须自己调 this.changed():MOVE/MIRROR/SCALE 命令、方向键微移等
  // 只调 ent.move()/rotate()… 不会再替你标脏(内置实体也是在方法内部 changed())
  move(from, to) {
    // from/to 是 Point2DLike(运行时会传 Point2D 对象,不能按下标取)
    this.center = this.center.add(to).subtract(from);
    this.changed();
  }
  rotate(center, angle) { this.center = this.center.rotateAround(center, angle); this.changed(); }
  scale(center, factor) {
    this.center = this.center.scaleFrom(center, factor);
    this.radius *= factor;
    this.changed();
  }
  mirror(p1, p2) { this.center = this.center.mirrorAcross(p1, p2); this.changed(); }

  _teethPoints() {
    const pts = [];
    for (let i = 0; i < this.teeth * 2; i++) {
      const a = (i / (this.teeth * 2)) * Math.PI * 2;
      const r = i % 2 === 0 ? this.radius : this.radius * 0.8;
      pts.push([this.center.x + r * Math.cos(a), this.center.y + r * Math.sin(a)]);
    }
    return pts;
  }
}

四、注册

实体注册表在每个 MapCadLayer 实例上:

mapcadLayer.entityRegistry.register({ type: 'GEAR', entityClass: GearEnt });

const gear = new GearEnt([100, 100], 60, 16);
gear.color = 0xff8800;
mapcadLayer.addEntity(gear);

EntityTypeDefinition = { type, entityClass, gripProvider? }。相关方法:entityRegistry.unregister(type) / create(type, ...args) / has(type) / getDefinition(type)。

注册的实际作用:①entityRegistry.create(type, ...) 程序化构造;②给该 type 挂自定义 gripProvider 接管夹点行为。注册不参与序列化(见常见坑 1)。

五、与 Custom 属性 vs 自定义实体

场景 推荐
只在标准实体上加业务字段 setCustomProperty('bizId', ...)
需要自定义外观 / 编辑逻辑 自定义实体类

六、常见坑

  1. toJSON/fromJSON 不支持自定义实体:Serializer 是硬编码 instanceof 链——序列化时自定义实体落到 data: {}(几何全丢),反序列化时返回 null 被静默跳过,注册 entityRegistry 也救不了(注册表只服务 create() 和 gripProvider 查找;clone() 是实例方法本来就不需要它)。要持久化只能自建:比如在 customProperties 里存构造参数(customProperties 会正常往返),读回 doc 后按 type 自己重建。
  2. boundingBox 不准:框选 / 空间索引 / zoomExtents 全错;务必精确。
  3. 变换四件套签名:move(from,to) 是两点参数(不是 dx,dy);rotate/scale 以 center 为基准。
  4. 改了字段忘 changed():界面不更新;只改样式用 styleChanged()。尤其 move/rotate/scale/mirror 内部必须 this.changed()——内置命令与微移快捷键调完变换方法后不会再标脏。
  5. copyBaseProps:clone() 里记得 this.copyBaseProps(target) 把 color/layerId/visible 等公共属性带过去。