项目场景:
由于项目里需要加载很多三维立方体,之前用的方式是entity,但是数据量大的话会有点卡顿,于是换成primitive图元的方式加载。
简单记录一下遇到的问题。
问题描述
viewer.scene.primitives.add()添加primitive时,地球卡死了,没办法进行放大缩小拖拽等一系列地球操作。
值得注意的是:当用entity得方式是可以加载出来的,只不过会卡顿,但是也没有遇到过场景scene直接卡死的情况。也就是说可以用entity加载但是一旦换成primitive的方式scene就卡死了。
1.刚开始还以为是几何图形太复杂的问题,但是primitie比entity更接近底层,应该比entity渲染更快才对啊,但是相反entity能加载出来,primitive却卡死了,这也就排除了这换个原因。
2.还以为是电脑GPU的问题但是发现scene场景卡死的时候电脑GPU并没有占用多少,甚至为0%,这也就排除了电脑GPU这一项。
原因分析:
排查数据格式。
我们知道加载多边形primitive需要传入经纬度数组
const geometry = new Cesium.PolygonGeometry({
polygonHierarchy: new Cesium.PolygonHierarchy(
new Cesium.Cartesian3.fromDegreesArray(geoms) // geoms为经纬度数组
),
extrudedHeight: entityHeight,
});
于是就对数据进行一条一条加载。直到遇到scene卡死的那条数据。
知道找到了场景卡死的那条数据:
[107.24044625699061, 31.291311316851022, 107.24047198601231, 31.291311468987722, 107.24047250052004, 31.291311493744068, 107.2404730097246, 31.29131156168811, 107.24047350872203, 31.291311672165513,NaN,NaN,107.24047399270674,31.291311824112327, 107.24047445701771, 31.291312016065213]。
注意里面竟然有两条NaN,这里就排查到了是这条数据的问题,导致加载暂停,场景卡死的。所以我查看了原始的WKT格式的数据。
大概长这样:
"MULTIPOLYGON(((107.25941467940206 31.291917660957814,107.25941751810778 31.291915276197532,107.2594241475583 31.29190534342199,107.25941467940206 31.291917660957814)),((107.25942693970325 31.291901710998584,107.2594272814655 31.291901545782753,107.25942773447713 31.291871994888496,107.25949975585885 31.291838338096827,107.25947575902191 31.291838199756548,107.25942693970325 31.291901710998584)))"
注意:这是一个多重多边形,这条数据里包含了两个polygon,于其他数据不一样,其他数据一个multipolygon只包含了一个polygon数据。
由于是需要我这边手动将wkt转为经纬度数组的。所以看了一下写的方法,代码如下:
const geoms = item
.replace('MULTIPOLYGON(((', '')
.replace(')))', '')
.replace(/\s/g, ',')
.split(',')
.map(item => Number(item));
问题显而易见了,上面这种方法只处理了开头和结尾处的字段,并没有对中间的括号")),((" 进行处理。所以再数组中才会出现NaN这种情况。
从wkt数据里面提取经纬度方法就改为如下:
const geoms = item
.replace('MULTIPOLYGON', '')
.replace(/[()]/g, '')
.replace(/\s/g, ',')
.split(',')
.map(item => Number(item));
这样就能正常渲染加载primitive了
可以看到中间有条线,将两个polygon连为一体了。
我这里没有做处理,这种情况在项目里并不常见。
如果想要处理的话,可以跟后端商量一下谁来调整。
三维primitive贴地展示:
这个根据自己的接口数据类型来调整。由于我这里数据量较大,所以先定位到具体的bbox再进行渲染数据,这里是每100条进行一次渲染。所以采用:
1.先处理所有的wkt数据转为一个二维经纬度数组。每一条数据为一个生成primitive所需的经纬度数据。
2.遍历生成的二维经纬度数组,每100条加载一次数据。
3.由于需要贴地展示,而如果用GroundPrimitive的话,是可以进行贴地的,但是就没有了拉伸高度,所以还需要用Primitie来加载。 如果需要拉伸高度的话需要根据经纬度点查询位置点的地形高度,然后加上你需要拉伸的立方体高度。
// 采用1先漫游到bbox,2再渲染primitive
async loadGeoJSON(data) {
const viewer = window.viewer;
const chunkSize = 100; // 每100条数据加载一次
console.time();
//1
const coords = []; // 存储所有的坐标点,用于生成primitive和计算bbox
for (let i = 0; i < data.length; i++) {
const geoms = data[i]
.replace('MULTIPOLYGON', '')
.replace(/[()]/g, '')
.replace(/\s/g, ',')
.split(',')
.map(item => Number(item));
coords.push(geoms);
}
viewer.camera.flyTo({
destination: new Cesium.Rectangle.fromDegrees(...getBBoxByCoords(coords)),
});
//2
for (let index = 0; index < coords.length; index += chunkSize) {
const chunk = coords.slice(index, index + chunkSize);
const instances = await Promise.all(
chunk.map(async item => {
// 异步根据经纬度获取地形高度
const height = await getHeigthByLonLat(item[0], item[1]);
const entityHeight = height + 50;
const geometry = new Cesium.PolygonGeometry({
polygonHierarchy: new Cesium.PolygonHierarchy(
new Cesium.Cartesian3.fromDegreesArray(item)
),
extrudedHeight: entityHeight,
});
return new Cesium.GeometryInstance({
geometry,
});
})
);
const primitive = new Cesium.Primitive({
geometryInstances: instances,
appearance: new Cesium.MaterialAppearance({
material: new Cesium.Material({
fabric: {
type: 'Color',
uniforms: {
color: new Cesium.Color(1.0, 1.0, 0.0, 1.0),
},
},
}),
}),
});
viewer.scene.primitives.add(primitive);
}
console.timeEnd();
},
};
根据经纬度查询地形高度:
export const getHeigthByLonLat = async (lon, lat) => {
var positions = Cesium.Cartographic.fromDegrees(lon, lat);
const updatedPositions = await Cesium.sampleTerrain(window.viewer.terrainProvider, 13, [
positions,
]);
if (updatedPositions && updatedPositions.length) {
return updatedPositions[0].height;
} else {
return 0;
}
};