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