共计 2202 个字符,预计需要花费 6 分钟才能阅读完成。
背景痛点:为什么需要点位聚类?
在 WebGIS 开发中,渲染海量点数据(如 POI、传感器数据)时常见两大问题:
- 性能瓶颈 :万级以上的 Entity 直接渲染会导致:
- 帧率骤降至 10FPS 以下(实测 Chrome 开发者工具 Performance 面板数据)
-
内存占用超过 1GB(通过 Chrome Memory 面板观测)
-
视觉混乱 :密集区域点要素重叠,无法有效识别(如图 1 所示的地铁站出口聚类场景)

技术方案对比
方案一:Cesium 原生 Entity 聚类
viewer.dataSources.add(
Cesium.EntityCluster.enableClustering({
enabled: true,
pixelRange: 50,
minimumClusterSize: 5
})
);
优点 :
– 开箱即用,零配置
– 自动处理点击事件
缺点 :
– 聚合算法不可定制
– 万级以上数据仍存在卡顿
方案二:cesium-clustering 插件
特点 :
– 支持自定义聚合图标
– 提供四叉树空间索引
测试数据 (10,000 点对比):
| 方案 | 平均帧率 | 内存占用 |
|---|---|---|
| 原生 | 24FPS | 850MB |
| 插件 | 48FPS | 620MB |
方案三:自定义实现(推荐)
核心实现步骤
1. 四叉树空间索引构建
class QuadTreeNode {constructor(public bounds: Rectangle, public level: number) {this.children = [];
this.entities = [];}
insert(entity: Entity): boolean {if (!this.bounds.contains(entity.position)) return false;
if (this.level < MAX_LEVEL) {const child = this.getChildContaining(entity);
if (child) return child.insert(entity);
}
this.entities.push(entity);
return true;
}
}
2. 动态聚合算法
function updateClusters() {
// 视口范围计算
const visibleBounds = viewer.camera.computeViewRectangle();
// 递归查询可见节点
function traverse(node: QuadTreeNode) {if (!node.bounds.intersects(visibleBounds)) return;
if (node.entities.length > THRESHOLD) {createClusterIcon(node);
} else {node.entities.forEach(showEntity);
}
node.children.forEach(traverse);
}
}
3. 聚合图标分级策略
| 点数量区间 | 图标尺寸 | 颜色 |
|---|---|---|
| 1-10 | 24px | #4CAF50 |
| 11-50 | 32px | #FFC107 |
| 51+ | 40px | #F44336 |
性能优化实战
WebWorker 多线程方案
主线程 :
const worker = new Worker('cluster-worker.js');
viewer.scene.preRender.addEventListener(() => {
worker.postMessage({
cameraView: viewer.camera.viewRectangle,
entities: currentEntities
});
});
Worker 线程 :
self.onmessage = (e) => {const clusters = doClustering(e.data);
self.postMessage(clusters);
};
内存管理关键点
// 必须显式清理
viewer.entities.removeAll();
dataSource.entities.removeAll();
// 推荐使用 EntityCollection
const tempEntities = new EntityCollection();
tempEntities.removeAll(); // 防止内存泄漏
避坑指南
- 移动端适配 :
- 降低聚合计算频率(requestAnimationFrame 节流)
-
使用 canvas2D 代替 WebGL 渲染聚合图标
-
动态阈值调整 :
function getDynamicThreshold() { return Cesium.Math.lerp( 5, // 最小阈值 20, // 最大阈值 viewer.camera.positionCartographic.height / 10000 ); } -
点击事件穿透 :
viewer.screenSpaceEventHandler.setInputAction((movement) => {const picked = viewer.scene.pick(movement.position); if (picked?.id?.clusterId) {handleClusterClick(picked.id); } }, Cesium.ScreenSpaceEventType.LEFT_CLICK );
思考题与扩展
如何实现跨时间维度的动态聚合?
参考方案:
1. 为四叉树节点添加时间属性
2. 在聚类计算时加入时间范围过滤
3. 使用 Cesium 的 TimeDynamicPointCloud 特性
完整代码示例:
GitHub 仓库
正文完
