serialize
本文回答:
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');
十、常见坑
- fromJSON 后位置看不到:没
zoomExtents;手动 fit。 - 先 fromJSON 再 setDrawingDefaults:不冲突——两者是独立通道(defaults 不随文档走),但默认值的 localStorage 持久化意味着它会跨文档存活,可能不是你想要的。
- 块定义丢失:
serializeStore(store)不传blocks参数就只导实体;BlockRefEnt 还原后拿不到blockName/getNestEntities()(除非另行注入_blocksRef)。 - deserializeStore 重复调用累加:它只 add 不清——要么用
mapcadLayer.fromJSON(自带清空),要么自己先清。 - 大图 toJSON 1 MB+:正常;建议 gzip 后上传。
- version 是字符串
"1.0":=== 1数字比较永远不成立。
相关
- 批量加载 → batch/entity-bulk-add
- 后端 DWG 导出 → backend/export-dwg
- 实体属性 → entity/style-props