唯杰地图 logo唯杰地图
VJMAP
EN

serialize

vjmapext/references/drawing/serialize.md 8.5 KB192 行
原文

本文回答:mapcadLayer.toJSON / fromJSON、底层 serializeStore / deserializeStore、DrawingDefaults —— 保存 / 还原绘图的完整结构。

一、SerializedDocument 结构

interface SerializedDocument {
  version: string;                 // "1.0",是字符串不是数字
  entities: SerializedEntity[];    // 全部实体
  blocks?: SerializedBlockDef[];   // 数组(不是 Record 映射)
}
// SerializedBlockDef = { blockId, name, basePoint: [x,y], items: SerializedEntity[] }

注意:drawingDefaults 不在文档里。 toJSON() 只有上面三个键——绘图默认值走 DrawingDefaults.save() 独立持久化到 localStorage(见第四节),不随 toJSON 走。

SerializedEntity 顶层字段:type / id / color / layerId / lineType / lineWidth? / editable? / selectableInReadonly? / minDisplayLevel? / maxDisplayLevel? / effectColor? / effectWidth? / effectOpacity? / effectBlur? / customProperties?,几何数据在 data 里(LINE → {start:[x,y], end:[x,y]},块引用 → type:"INSERT"、data:{blockId, insertionPoint, scaleFactor, rotation})。

⚠️ 往返不完整(1.0.1):

  • 只处理 POINT/LINE/PLINE/CIRCLE/ARC/HATCH/TEXT/MTEXT/SPLINE/ELLIPSE/INSERT。标注(DIMLINEAR/DIMALIGNED/DIMANGLE/DIMRADIUS)与 MLEADER 序列化成 data: {},反序列化无对应分支返回 null 被静默丢弃——fromJSON 后标注/引线全没了(自定义实体同理)。要保留只能自存构造参数(如 customProperties)读回后重建。
  • 顶层字段之外的 visible、ltScale、ltOffset、dynamic 不写入(读回恢复默认 true/1/0/false);TextEnt.widthFactor 也不写。

二、toJSON / fromJSON(图层级)

// 保存
const doc = mapcadLayer.toJSON();
const str = JSON.stringify(doc);
localStorage.setItem('drawing', str);

// 还原
const back = JSON.parse(localStorage.getItem('drawing'));
mapcadLayer.fromJSON(back);   // 内部:先清空 store 再逐个 add
mapcadLayer.zoomExtents();    // fromJSON 不会自动 fit,需要手动

三、底层 API:serializeStore / deserializeStore

文档级 toJSON/fromJSON 内部就是这两个导出函数,也可单独用(比如自定义存储后端):

const { serializeStore, deserializeStore } = vjmapext;

// serializeStore(store, blocks?) → SerializedDocument
const doc = serializeStore(mapcadLayer.store, mapcadLayer.blocks);

// deserializeStore(doc, store, blocks?) → void
// 注意三个行为差异:
//  1. 只调 store.add(entity),【不清空】已有实体——重复调用会累加
//  2. blocks 容器里已存在同名块时跳过该块定义(不覆盖)
//  3. 传入 blocks 参后,还原的 BlockRefEnt 自动 setBlocksRef,
//     blockName / getNestEntities() 直接可用
deserializeStore(doc, mapcadLayer.store, mapcadLayer.blocks);

fromJSON = 清 store + deserializeStore;直接调 deserializeStore 要自己决定是否先清。

四、DrawingDefaults(绘图默认值)

新实体的初始样式来源。字段(toJSON() 输出的键):

字段 类型 默认 说明
color number 0x7fd3ff 0xRRGGBB
fillColor number 0x3bb2d0 填充色
lineType string 'CONTINUOUS' 线型名
ltScale number 1 线型比例
lineWidth number 0 像素
fillOpacity number 0.4
borderOpacity number 1
layerId string '0' 图层名(默认是字符串 '0')

没有 layer、没有 textHeight、没有 revcloudRadius——字段名是 layerId;文字高是 TextEnt 构造参数(默认 10),云线弧长是 RevcloudCommand.arcLength(静态,默认 50)。

读 / 写

mapcadLayer.getDrawingDefaults();   // DrawingDefaultsData 快照

mapcadLayer.setDrawingDefaults({
  color: 0xff0000,
  layerId: '标注',        // 字段是 layerId
});
// 内部 = drawingDefaults.fromJSON(partial) 部分合并
//        + drawingDefaults.save() 写 localStorage
//        + 同步预览色

setDrawingDefaults 会自动 save() 持久化——刷新页面后默认值仍在(DrawingDefaults 构造时 loadFromStorage() 读回)。这与文档序列化是两条独立通道。

⚠️ 默认值只在交互命令路径生效:各命令内部 ctx.drawingDefaults.applyTo(ent) 后才 store.add。程序化 new LineEnt(...) + addEntity() 不套用 DrawingDefaults(新实体 color 是构造默认 0xffffff,不是当前设置的默认值)——要套用就显式 mapcadLayer.drawingDefaults.applyTo(ent)。

手动应用到实体

const dd = new vjmapext.DrawingDefaults({ color: 0xff0000 });
dd.applyTo(entity);   // 把 color/layerId 等刷到实体上

五、只导出实体(不含块定义)

EntityBase 没有 toJSON()(只有 toGeoJSON() 导几何)。要拿 SerializedEntity 只能走 serializeStore:

// 只要实体数组:调 serializeStore 后丢弃 blocks
const { entities } = serializeStore(mapcadLayer.store);   // 不传 blocks

toGeoJSON() 适合给外部 GIS 用,但不能喂回 deserializeStore(结构不同)。

六、增量保存 / 差分

没有内置 diff。注意一个坑:

  • 实体 id 在 deserialize 时重新生成,不会保留序列化里的 id——不能用实体 id 做跨会话 diff 键;要做差分请用 setCustomProperty 存自己的稳定业务键
  • customProperties 序列化形态是 {key: {type, value}};getCustomProperty(k) 返回的也是这个包装对象(不是裸值)

七、fromJSON 时的顺序

mapcadLayer.fromJSON(doc):
1. store.getAllIds() → 逐个 delete(清空现有实体)
2. deserializeStore: doc.blocks → 注册块定义(同名跳过)
3. doc.entities → 逐个反序列化为真实实体类实例后 store.add
   - Point2D 恢复为 Point2D 实例(不是 plain object)
   - INSERT 实体自动注入 blocks 引用(仅顶层;块定义内部的 INSERT 不注入)
   - 实体 id 重新生成
   - 标注 / MLEADER / 自定义实体无反序列化分支 → 静默跳过(见第一节)
   - visible / ltScale / ltOffset / dynamic 未入档 → 恢复为默认值
4. 不自动 fit——手动 mapcadLayer.zoomExtents()

八、导出 DWG

如果需要导出为 DWG 文件(不是 JSON),用 mapcadLayer.exportDwg(opts)(见 backend/export-dwg)。

方案 用途
toJSON 前端保存、后续在 vjmapext 里还原
exportDwg 下载真正的 DWG 文件,给 CAD 软件用

九、典型场景

1 保存到服务器

await fetch('/api/drawing/save', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ id, doc: mapcadLayer.toJSON() })
});

2 自动保存

let saveTimer;
// ⚠️ 'store:changed' 在 1.0.1 枚举有定义但从未 emit——改用 entity:* 三个真实事件
const markDirty = () => {
  clearTimeout(saveTimer);
  saveTimer = setTimeout(() => {
    localStorage.setItem('drawing', JSON.stringify(mapcadLayer.toJSON()));
  }, 2000);
};
for (const ev of ['entity:added', 'entity:modified', 'entity:deleted'])
  mapcadLayer.eventBus.on(ev, markDirty);

3 工具栏联动默认值

colorPicker.onChange = v => mapcadLayer.setDrawingDefaults({ color: v });
layerSelect.onChange = v => mapcadLayer.setDrawingDefaults({ layerId: v });

// 即将画的实体都会用新颜色 / 图层
mapcadLayer.executeCommand('LINE');

十、常见坑

  1. fromJSON 后位置看不到:没 zoomExtents;手动 fit。
  2. 先 fromJSON 再 setDrawingDefaults:不冲突——两者是独立通道(defaults 不随文档走),但默认值的 localStorage 持久化意味着它会跨文档存活,可能不是你想要的。
  3. 块定义丢失:serializeStore(store) 不传 blocks 参数就只导实体;BlockRefEnt 还原后拿不到 blockName/getNestEntities()(除非另行注入 _blocksRef)。
  4. deserializeStore 重复调用累加:它只 add 不清——要么用 mapcadLayer.fromJSON(自带清空),要么自己先清。
  5. 大图 toJSON 1 MB+:正常;建议 gzip 后上传。
  6. version 是字符串 "1.0":=== 1 数字比较永远不成立。

相关