唯杰地图 logo唯杰地图
VJMAP
EN

batch-rendering

vjmapext/references/perf/batch-rendering.md 4.6 KB166 行
原文

本文回答:批量新增 / 修改时的策略 —— vjmapext 侧渲染自动按帧合并;vjmap 底座侧一次 setData。

一、规则一句话

任何一个循环里有 >100 次实体 / source 修改 → 注意批处理。但 vjmapext 与 vjmap 底座的批处理机制完全不同,别套错。

二、vjmapext 实体:直接循环

for (const pt of data) {
  mapcadLayer.addEntity(new vjmapext.PointEnt(pt));
}

addEntity 只标脏;重绘由 requestAnimationFrame 合并,循环 1 万次也只渲染一帧。没有 beginBulk/endBulk/bulkRun API。要把整批记为一次 undo:

mapcadLayer.undoManager.beginTransaction();
try {
  for (const pt of data) mapcadLayer.addEntity(new vjmapext.PointEnt(pt));
} finally {
  mapcadLayer.undoManager.endTransaction();
}

三、vjmap 底座 source:一次 setData

// ❌ 错:10 次 setData
const fc = { type: 'FeatureCollection', features: [] };
for (const f of newFeatures) {
  fc.features.push(f);
  src.setData(fc);              // 每次都触发重绘
}

// ✅ 对:先凑齐再 setData
const fc2 = { type: 'FeatureCollection', features: [...oldFeatures, ...newFeatures] };
src.setData(fc2);

详见 batch/geojson-bulk。

四、feature-state(只改样式)

// 批量换色:不 setData,直接 feature-state
for (const f of features) {
  map.setFeatureState({ source: 'src', id: f.id }, { highlighted: true });
}
// layer 的 paint 用表达式读 state
map.addLineLayer('lyr', 'src', {
  lineColor: ['case',
    ['boolean', ['feature-state', 'highlighted'], false], '#f00',
    '#888'
  ]
});

1 万 feature 换色只要几十 ms。

五、FX 批量:addPoints

// 两种写法等效(推荐后者,代码更简洁、直接拿 handle 数组)
for (const p of list) fx.addPoint('fire', { cad: p, size: 30 });
fx.addPoints('fire', list.map(p => ({ cad: p, size: 30 })));

1.0.1 的 addPoints 内部就是循环 addPoint,两者都只写 CPU 影子缓冲、在下一帧 draw 时合并成一次 GPU 上传——没有数量级的速度差。

六、多批混合添加

for (const e of batch1) mapcadLayer.addEntity(e);
for (const e of batch2) mapcadLayer.addEntity(e);
// 同步循环内全部标脏,下一帧一次 rebuild

七、styleChanged 批量

改几百个实体颜色:

for (const e of selected) {
  e.color = 0xff0000;
  e.styleChanged();
}

配合 styleChanged(见 style-only-fast-path)更快。

八、Web Worker 预计算

大批量数据解析 / 转换应放 Worker:

// worker.js
self.onmessage = e => {
  const list = parseCSV(e.data);    // 解析 CSV 大字符串
  const features = list.map(...);
  self.postMessage({ features }, [features]);   // transfer
};

// main
const w = new Worker(new URL('./worker.js', import.meta.url));
w.postMessage(csv);
w.onmessage = ev => {
  src.setData({ type: 'FeatureCollection', features: ev.data.features });
};

九、流式加载(分批 setData)

百万点分 10 批:

const total = bigList;
const BATCH = 100_000;
let cursor = 0;
const fc = { type: 'FeatureCollection', features: [] };

function nextBatch() {
  const end = Math.min(cursor + BATCH, total.length);
  for (let i = cursor; i < end; i++) fc.features.push(total[i]);
  src.setData(fc);
  cursor = end;
  if (cursor < total.length) requestAnimationFrame(nextBatch);
}
nextBatch();

用户看到数据"流入"而不是一卡一卡。

十、删除:批量

for (const id of ids) mapcadLayer.deleteEntity(id);

十一、事务包装

mapcadLayer.undoManager.beginTransaction();
try {
  for (const e of entities) mapcadLayer.addEntity(e);
} finally {
  mapcadLayer.undoManager.endTransaction();
}
// 一次 Undo 撤回整批

十二、常见坑

  1. 循环里穿插 await:把添加摊到多个帧;同步循环一把加完。
  2. undoManager 事务忘 endTransaction:后续 undo 记录全挂在未闭合事务里;必须 try/finally。
  3. setData 频繁调:每次都全量重排索引;必须合并。
  4. 循环里每条都 entity.dynamic = true:dynamic 桶被撑爆;静态数据用 cold。
  5. addPoints 传错结构:opts 对象里必须有 cad / lngLat / mercator 其中一个。

相关