ArcGIS JSAPI 高级教程 - 加载北斗网格码 - 大量几何体数据(Mesh - Graphic)性能优化(使用完整公共样式MeshSymbol3D)

在 arcgis js 中,一般不推荐在客户端创建、加载大量几何数据(Graphic 和 GraphicLayer)。
具体见:ArcGIS JSAPI 高级教程 - 异步(async - Promise)添加大量北斗网格码(Mesh - Graphic)
在加载大量几何体数据时,加载数量相近的情况下,出现帧率相差非常大。
经过研究测试,找到原因,这里记录一下,也算是进行一次性能优化。
本文包括 核心代码、完整代码以及在线示例。
核心代码
具体原因:
由于没有正确使用 MeshSymbol3D 创建样式对象,而是使用 {type: 'mesh-3d'} 创建。
这样会导致,在加载数据时,自动创建大量 MeshSymbol3D 对象,进而导致渲染帧率下降。
因此,建议进行优化:
1. 尽量使用原始样式类(MeshSymbol3D、FillSymbol3DLayer、SolidEdges3D)创建样式,避免使用 {type: 'mesh-3d'} 。
2. 尽量不添加边框:edges;若添加 edges,会导致每帧循环更新 edges,导致卡顿。
以下是部分核心代码:
javascript
// 获取网格样式
function getMeshSymbol3D(materialColor, edgesColor) {
return new MeshSymbol3D({
symbolLayers: [
new FillSymbol3DLayer({
material: {color: materialColor || colorFill.value},
edges: new SolidEdges3D({
color: edgesColor || colorOutline.value,
size: 1,
}),
})
]
})
}
// 获取网格样式
function getMesh3d(materialColor, edgesColor) {
return {
type: 'mesh-3d',
symbolLayers: [
{
type: 'fill',
material: {color: materialColor || colorFill.value},
edges: {
type: 'solid',
color: edgesColor || colorOutline.value,
size: 0,
},
},
],
};
}
完整代码
示例中,加载数据时切换创建样式,用以展示不同样式下的帧率。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>加载大量数据性能优化</title>
<script type="module" src="https://openlayers.vip/arcgis_api/calcite-components/2.8.1/calcite.esm.js"></script>
<link rel="stylesheet" type="text/css"
href="https://openlayers.vip/arcgis_api/calcite-components/2.8.1/calcite.css"/>
<link rel="stylesheet" href="https://openlayers.vip/arcgis_api/4.33/esri/themes/light/main.css"/>
<script src="https://openlayers.vip/arcgis_api/4.33/init.js"></script>
<script src="https://openlayers.vip/examples/resources/stats.min.js"></script>
<script src="https://openlayers.vip/examples/resources/data_.js"></script>
<style>
html,
body,
#viewDiv {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
}
/* 去掉地图点击蓝色边框 */
.esri-view .esri-view-surface--inset-outline:focus::after {
outline: auto 0px Highlight !important;
outline: auto 0px -webkit-focus-ring-color !important;
}
</style>
<script>
require([
"esri/config",
"esri/views/SceneView",
"esri/layers/GraphicsLayer",
"esri/Graphic",
"esri/geometry/Mesh",
"esri/widgets/LayerList",
"esri/Map",
"esri/geometry/Point",
"esri/geometry/SpatialReference",
"esri/symbols/FillSymbol3DLayer",
"esri/symbols/MeshSymbol3D",
"esri/symbols/edges/SolidEdges3D",
], function (
esriConfig, SceneView,
GraphicsLayer, Graphic, Mesh, LayerList, Map,
Point, SpatialReference, FillSymbol3DLayer, MeshSymbol3D, SolidEdges3D,
) {
let loadMode = true;
let batchSize = 50;
activeState('left');
const spatialReference = SpatialReference.WebMercator;
// Add graphic when GraphicsLayer is constructed
const layerGrid = new GraphicsLayer({
spatialReference,
popupTemplate: {
title: 'The grid',
content: 'PosX:{PosX},PosY:{PosY},PosZ:{PosZ},',
},
title: '网格图层',
});
const scene = new Map({
layers: [layerGrid]
})
const view = new SceneView({
map: scene,
container: "viewDiv",
qualityProfile: 'high',
camera: {
position: {
x: 12957235.377120316,
y: 4863943.339089994,
z: 540.7203787067715,
spatialReference
},
tilt: 60
}
});
// 默认边框颜色
const colorOutline = {
value: [255, 255, 255, 1]
}
// 默认填充颜色
const colorFill = {
value: [255, 255, 255, 0.5]
}
// 获取网格样式
function getMeshSymbol3D(materialColor, edgesColor) {
return new MeshSymbol3D({
symbolLayers: [
new FillSymbol3DLayer({
material: {color: materialColor || colorFill.value},
edges: new SolidEdges3D({
color: edgesColor || colorOutline.value,
size: 1,
}),
})
]
})
}
// 获取网格样式
function getMesh3d(materialColor, edgesColor) {
return {
type: 'mesh-3d',
symbolLayers: [
{
type: 'fill',
material: {color: materialColor || colorFill.value},
edges: {
type: 'solid',
color: edgesColor || colorOutline.value,
size: 1,
},
},
],
};
}
// @todo 定义全局变量,用于控制
// 网格数组
// 网格图层
// 过滤对象,用于隐藏显示单个建筑
// 网格样式
let symbol = getMeshSymbol3D();
async function addGridAsync(data, batchSize = 500) {
// 验证输入参数
if (!data || !data.BBox || !data.CenterPoint) {
throw new Error('无效的数据格式:缺少BBox或CenterPoint');
}
if (batchSize <= 0) {
throw new Error('batchSize必须大于0');
}
try {
// 清除现有图层
layerGrid.removeAll();
const {CenterPoint: centerWebMercator, BBox: bboxData} = data;
const totalLength = bboxData.length;
console.log('box 个数:', totalLength);
// 预计算总批次数用于进度跟踪
const totalBatches = Math.ceil(totalLength / batchSize);
for (let batchIndex = 0; batchIndex < totalBatches; batchIndex++) {
const startIndex = batchIndex * batchSize;
const endIndex = Math.min(startIndex + batchSize, totalLength);
const batch = bboxData.slice(startIndex, endIndex);
const graphics = batch.map(datum => {
const location = {
x: centerWebMercator.x + datum.posX,
y: centerWebMercator.y + datum.posZ,
z: centerWebMercator.z + datum.posY,
spatialReference: {wkid: 102100} // Web Mercator
};
const geometry = Mesh.createBox(
new Point(location),
{
size: {
width: datum.lenX,
height: datum.lenY,
depth: datum.lenZ
},
vertexSpace: 'georeferenced'
}
);
return new Graphic({
geometry: geometry,
symbol: symbol
});
});
// 批量添加图形
layerGrid.addMany(graphics);
const processedCount = Math.min(endIndex, totalLength);
const progress = ((processedCount / totalLength) * 100).toFixed(1);
console.log(`进度: ${processedCount}/${totalLength} (${progress}%)`);
// 让出主线程,但避免过短的延迟
if (batchIndex < totalBatches - 1) {
// 不是最后一批
await new Promise(resolve => requestAnimationFrame(resolve));
}
}
console.log('所有网格添加完成');
return {success: true, count: totalLength};
} catch (error) {
console.error("添加网格时出错:", error);
// 可以根据错误类型提供更具体的错误信息
if (error.message.includes('Invalid')) {
throw new Error('数据格式无效,请检查输入数据');
}
throw error; // 重新抛出错误让调用者处理
}
}
// 开启显示帧率
function activeState(left = 'right', bottom = 'top') {
let stats = initState();
function initState(type) {
let panelType = (typeof type != 'undefined' && type) && (!isNaN(type)) ? parseInt(type) : 0;
let stats = new Stats();
stats.domElement.style.position = 'absolute'; //绝对坐标
stats.domElement.style.left = 'auto';
stats.domElement.style[left] = '30px';//
stats.domElement.style[bottom] = '20px';
stats.showPanel(panelType);
document.body.appendChild(stats.domElement);
return stats;
}
function renderer() {
stats.update();
requestAnimationFrame(renderer);
}
renderer();
}
var layerList = new LayerList({
view: view
});
// Add widget to the top right corner of the view
view.ui.add(layerList, "top-right");
// 定义显示隐藏建筑控件
const loadModeToggle = document.getElementById("loadModeToggle");
loadModeToggle.addEventListener("calciteSwitchChange", () => {
loadMode ? symbol = getMeshSymbol3D() : symbol = getMesh3d('rgba(106, 106, 255, 0.4)');
addGridAsync(data_, batchSize);
loadMode = !loadMode;
});
view.ui.add("renderNodeUI", "bottom-left");
});
</script>
</head>
<body>
<div id="viewDiv">
<calcite-block open heading="" id="renderNodeUI">
<calcite-label layout="inline">
点击载入数据:
</calcite-label>
<calcite-label layout="inline">
高帧率/低帧率
<calcite-switch id="loadModeToggle" checked></calcite-switch>
</calcite-label>
</calcite-block>
</div>
</body>
</html>

在线示例
ArcGIS Maps SDK for JavaScript 在线示例:大量几何体数据(Mesh - Graphic)性能优化