Cesium 大数据量优化加载方案
·
Cesium 大数据量优化加载方案
1. 使用 Primitive API 替代 Entity API
Entity API 虽然简单,但性能较差。对于大量数据,应使用 Primitive API。
// ❌ 不推荐:Entity API(性能差)
entities.forEach(data => {
viewer.entities.add({
polygon: {
hierarchy: Cesium.Cartesian3.fromDegreesArray(data.coordinates),
material: Cesium.Color.RED
}
});
});
// ✅ 推荐:Primitive API(性能好)
const instances = [];
entities.forEach(data => {
instances.push(new Cesium.GeometryInstance({
geometry: new Cesium.PolygonGeometry({
polygonHierarchy: new Cesium.PolygonHierarchy(
Cesium.Cartesian3.fromDegreesArray(data.coordinates)
)
}),
id: data.id,
attributes: {
color: Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.RED)
}
}));
});
viewer.scene.primitives.add(new Cesium.Primitive({
geometryInstances: instances,
appearance: new Cesium.PerInstanceColorAppearance({
closed: true
})
}));
2. 数据分批加载(异步加载)
async function loadDataInBatches(allData, batchSize = 1000) {
const totalBatches = Math.ceil(allData.length / batchSize);
for (let i = 0; i < totalBatches; i++) {
const start = i * batchSize;
const end = Math.min(start + batchSize, allData.length);
const batch = allData.slice(start, end);
// 加载当前批次
loadBatch(batch);
// 显示进度
console.log(`加载进度: ${((i + 1) / totalBatches * 100).toFixed(1)}%`);
// 让出主线程,避免UI卡顿
await new Promise(resolve => setTimeout(resolve, 0));
}
}
function loadBatch(batchData) {
const instances = batchData.map(data =>
new Cesium.GeometryInstance({
geometry: createGeometry(data),
id: data.id,
attributes: {
color: Cesium.ColorGeometryInstanceAttribute.fromColor(data.color)
}
})
);
viewer.scene.primitives.add(new Cesium.Primitive({
geometryInstances: instances,
appearance: new Cesium.PerInstanceColorAppearance()
}));
}
3. 使用聚合(Clustering)处理大量点数据
// 对于大量点数据,使用聚合显示
const dataSource = await Cesium.GeoJsonDataSource.load(url, {
clampToGround: true
});
viewer.dataSources.add(dataSource);
// 启用聚合
dataSource.clustering.enabled = true;
dataSource.clustering.pixelRange = 50; // 聚合像素范围
dataSource.clustering.minimumClusterSize = 3; // 最小聚合数量
// 自定义聚合样式
dataSource.clustering.clusterEvent.addEventListener((entities, cluster) => {
cluster.label.show = true;
cluster.label.text = entities.length.toString();
cluster.label.font = '16px sans-serif';
cluster.label.fillColor = Cesium.Color.WHITE;
cluster.label.outlineColor = Cesium.Color.BLACK;
cluster.label.outlineWidth = 2;
cluster.billboard.show = true;
cluster.billboard.image = createClusterIcon(entities.length);
cluster.billboard.width = 40;
cluster.billboard.height = 40;
});
4. LOD(Level of Detail)按距离加载
class LODDataManager {
constructor(viewer) {
this.viewer = viewer;
this.loadedTiles = new Map();
this.allData = []; // 所有数据
// 监听相机变化
this.viewer.camera.changed.addEventListener(() => {
this.updateVisibleData();
});
}
updateVisibleData() {
const cameraHeight = this.viewer.camera.positionCartographic.height;
// 根据相机高度决定显示精度
let lodLevel;
if (cameraHeight > 1000000) {
lodLevel = 'low'; // 只显示少量数据
} else if (cameraHeight > 100000) {
lodLevel = 'medium';
} else {
lodLevel = 'high'; // 显示所有细节
}
this.loadDataByLOD(lodLevel);
}
loadDataByLOD(level) {
// 根据LOD级别加载不同精度的数据
const dataToLoad = this.filterDataByLOD(this.allData, level);
this.renderData(dataToLoad);
}
}
5. 使用 3D Tiles 格式
对于超大数据量,转换为 3D Tiles 格式是最优方案:
// 加载 3D Tiles
const tileset = await Cesium.Cesium3DTileset.fromUrl('path/to/tileset.json');
viewer.scene.primitives.add(tileset);
// 3D Tiles 自动处理LOD和裁剪
tileset.maximumScreenSpaceError = 16; // 控制精度,值越小越精细
tileset.maximumMemoryUsage = 512; // 最大内存使用(MB)
6. 视口裁剪(Frustum Culling)
// 只加载视口内的数据
function loadVisibleData(allData) {
const frustum = viewer.camera.frustum;
const cullingVolume = frustum.computeCullingVolume(
viewer.camera.position,
viewer.camera.direction,
viewer.camera.up
);
const visibleData = allData.filter(data => {
const position = Cesium.Cartesian3.fromDegrees(data.lon, data.lat);
const boundingSphere = new Cesium.BoundingSphere(position, 1000);
// 检查是否在视口内
return cullingVolume.computeVisibility(boundingSphere) !== Cesium.Intersect.OUTSIDE;
});
renderData(visibleData);
}
7. 使用 Web Worker 处理数据
// main.js
const worker = new Worker('dataProcessor.worker.js');
worker.postMessage({
type: 'processGeoJSON',
data: largeGeoJSONData
});
worker.onmessage = (e) => {
const processedData = e.data;
renderDataToCesium(processedData);
};
// dataProcessor.worker.js
self.onmessage = (e) => {
if (e.data.type === 'processGeoJSON') {
const processed = processData(e.data.data);
self.postMessage(processed);
}
};
function processData(data) {
// 在 worker 中处理数据转换、简化等
return data.map(item => ({
positions: simplifyCoordinates(item.coordinates),
properties: item.properties
}));
}
8. 几何简化
// 使用 Turf.js 简化几何体
import * as turf from '@turf/turf';
function simplifyGeometry(coordinates, tolerance = 0.01) {
const line = turf.lineString(coordinates);
const simplified = turf.simplify(line, { tolerance: tolerance });
return simplified.geometry.coordinates;
}
// 或使用 Douglas-Peucker 算法
function douglasPeucker(points, epsilon) {
// 实现 Douglas-Peucker 简化算法
// ...
}
9. 使用 Billboard/Label Collection
对于大量点和标注:
// ❌ 不推荐
points.forEach(point => {
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(point.lon, point.lat),
billboard: { image: 'pin.png' }
});
});
// ✅ 推荐:使用 Collection
const billboards = viewer.scene.primitives.add(new Cesium.BillboardCollection());
points.forEach(point => {
billboards.add({
position: Cesium.Cartesian3.fromDegrees(point.lon, point.lat),
image: 'pin.png'
});
});
10. 完整优化示例
class OptimizedDataLoader {
constructor(viewer) {
this.viewer = viewer;
this.primitives = [];
this.batchSize = 1000;
}
async loadLargeDataset(data) {
// 1. 数据预处理(在 Worker 中)
const processedData = await this.preprocessInWorker(data);
// 2. 几何简化
const simplifiedData = this.simplifyGeometries(processedData);
// 3. 分批加载
await this.loadInBatches(simplifiedData);
// 4. 设置相机事件监听,实现LOD
this.setupLOD();
}
async preprocessInWorker(data) {
return new Promise((resolve) => {
const worker = new Worker('processor.worker.js');
worker.postMessage(data);
worker.onmessage = (e) => resolve(e.data);
});
}
simplifyGeometries(data) {
const cameraHeight = this.viewer.camera.positionCartographic.height;
const tolerance = cameraHeight / 1000000; // 动态容差
return data.map(item => ({
...item,
coordinates: this.simplify(item.coordinates, tolerance)
}));
}
async loadInBatches(data) {
const batches = Math.ceil(data.length / this.batchSize);
for (let i = 0; i < batches; i++) {
const batch = data.slice(
i * this.batchSize,
(i + 1) * this.batchSize
);
this.loadBatch(batch);
// 显示进度
this.updateProgress((i + 1) / batches * 100);
// 避免阻塞UI
await new Promise(resolve => requestAnimationFrame(resolve));
}
}
loadBatch(batch) {
const instances = batch.map(item =>
new Cesium.GeometryInstance({
geometry: this.createGeometry(item),
id: item.id,
attributes: {
color: Cesium.ColorGeometryInstanceAttribute.fromColor(
Cesium.Color.fromCssColorString(item.color)
)
}
})
);
const primitive = this.viewer.scene.primitives.add(
new Cesium.Primitive({
geometryInstances: instances,
appearance: new Cesium.PerInstanceColorAppearance({
closed: true
}),
asynchronous: true // 异步创建
})
);
this.primitives.push(primitive);
}
setupLOD() {
this.viewer.camera.changed.addEventListener(() => {
const height = this.viewer.camera.positionCartographic.height;
// 根据高度动态调整显示
this.primitives.forEach((primitive, index) => {
primitive.show = this.shouldShowAtHeight(index, height);
});
});
}
shouldShowAtHeight(index, height) {
// LOD 逻辑
if (height > 1000000) {
return index % 10 === 0; // 只显示 10%
} else if (height > 100000) {
return index % 2 === 0; // 显示 50%
}
return true; // 显示全部
}
}
// 使用
const loader = new OptimizedDataLoader(viewer);
await loader.loadLargeDataset(yourLargeDataset);
性能对比总结
| 方案 | 适用场景 | 性能提升 |
|---|---|---|
| Entity → Primitive | 所有场景 | 5-10倍 |
| 分批加载 | 初始加载 | 避免卡顿 |
| Clustering | 大量点数据(>10000) | 10-100倍 |
| LOD | 多尺度查看 | 2-5倍 |
| 3D Tiles | 超大数据(百万级) | 100+倍 |
| Collection API | 大量Billboard/Label | 3-5倍 |
| 几何简化 | 复杂几何 | 2-3倍 |
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐



所有评论(0)