- 前言:今天总结关于point、label、billboard海量数据加载。后续会研究下大量model加载以及大bim(几百G上T)模型记载
海量点加载
- 弹窗
加载点位时,不加载弹窗。点击点位时在加载弹窗,及有效的减少加载量,优化性能。
const handler = new Cesium.ScreenSpaceEventHandler();
handler.setInputAction(function (movement: any) {
const pickedLabel = viewer.scene.pick(movement.position);
if (Cesium.defined(pickedLabel)) {
console.log(pickedLabel)
staticPoins.some(i => {
if (i.id === pickedLabel.id.id || i.id === pickedLabel.id) {
changePopWins(i)
return true
}
})
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
function changePopWins(item: Point) {
let flag = popWins.value.some(i => {
if (i.id === item.id) {
i.visible = !i.visible
return true
}
})
if (!flag) {
popWins.value.push({ ...item, visible: true })
}
}
- dom-tag组件
<template>
<div class="cesium-domPoint" ref="element">
<slot :data="props.data">
</slot>
</div>
</template>
<script lang="ts" setup>
import * as Cesium from "cesium";
import { onMounted, onUnmounted, ref, PropType } from "vue";
import type { EalignX, EalignY } from "../typing";
const props = defineProps({
eventClick: Function,
data: Object,
trackPos: {
type: Object as PropType<Cesium.Cartesian3>,
},
trackEntity: {
type: Object as PropType<Cesium.Entity>,
},
alignX: {
type: String as PropType<EalignX>,
},
alignY: {
type: String as PropType<EalignY>,
},
trackCursor: Boolean,
});
const { Viewer } = window;
let element = ref<HTMLDivElement | null>(null),
mousePos: Cesium.Cartesian2,
trackCursor = props.trackCursor;
const handler = new Cesium.ScreenSpaceEventHandler();
const onUpdate = () => {
if (element == null) return;
let screenPos;
if (trackCursor) {
screenPos = mousePos;
} else if (props.trackEntity) {
let pos =
(props.trackEntity.position &&
props.trackEntity.position.getValue(Cesium.JulianDate.now())) ||
Cesium.Cartesian3.ZERO;
screenPos = Cesium.SceneTransforms.wgs84ToWindowCoordinates(
Viewer.scene,
pos
);
} else if (props.trackPos) {
screenPos = Cesium.SceneTransforms.wgs84ToWindowCoordinates(
Viewer.scene,
props.trackPos
);
}
if (screenPos) {
if (element.value) {
switch (props.alignX) {
case "left":
element.value.style.left = screenPos.x + "px";
break;
case "center":
element.value.style.left =
screenPos.x + element.value.clientWidth * 0.5 + "px";
break;
case "right":
default:
element.value.style.left =
screenPos.x - element.value.clientWidth * 0.5 + "px";
}
switch (props.alignY) {
case "top":
element.value.style.top =
screenPos.y - element.value.clientHeight + "px";
break;
case "bottom":
element.value.style.top = screenPos.y + "px";
break;
case "center":
default:
element.value.style.top =
screenPos.y - element.value.clientHeight * 0.5 + "px";
}
if (parseFloat(element.value.style.top) < (-element.value.clientWidth) || parseFloat(element.value.style.left) < (-element.value.clientHeight) || parseFloat(element.value.style.left) > (document.body.clientWidth + element.value.clientWidth) || parseFloat(element.value.style.top) > (document.body.clientHeight + element.value.clientHeight)) {
element.value.style.display = "none";
}
else {
element.value.style.display = "";
}
}
} else {
if (element.value) element.value.style.display = "none";
}
};
onMounted(() => {
if (trackCursor) {
handler.setInputAction((event: any) => {
let offsetToLeftTop = Viewer.container.getBoundingClientRect();
mousePos = Cesium.Cartesian2.subtract(
event.endPosition,
new Cesium.Cartesian2(offsetToLeftTop.left, offsetToLeftTop.top),
new Cesium.Cartesian2()
);
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
}
Viewer.scene.preUpdate.addEventListener(onUpdate);
});
onUnmounted(() => {
if (Viewer && !Viewer.isDestroyed()) {
Viewer.scene.preUpdate.removeEventListener(onUpdate);
}
handler && handler.destroy()
});
</script>
<style lang="less" scoped>
.cesium-domPoint {
position: absolute;
z-index: 0;
}
</style>
<template v-for="item in popWins" :key="item.id">
<DomTag :trackPos="item.pos" v-if="item.visible">
<PopWin :point="item" @tooglePopWin="tooglePopWin1" />
</DomTag>
</template>
- 最常用的就是entity
不加载图片以及文字的时候20W的点都没问题,虽然帧数就10左右,但是流畅度还行
for (let i = 0; i < 500; i++) {
for (let j = 0; j < 400; j++) {
viewer.entities.add({
point: {
pixelSize: 5,
color: Cesium.Color.BEIGE
},
position: Cesium.Cartesian3.fromDegrees(103 + i * 0.1, 30 + j * 0.1)
})
}
}
添加文字后(20K),虽然也是10侦左右但是会感到明显的卡顿
for (let i = 0; i < 50; i++) {
for (let j = 0; j < 400; j++) {
viewer.entities.add({
point: {
pixelSize: 5,
color: Cesium.Color.BEIGE
},
label: {
text: i+""
},
position: Cesium.Cartesian3.fromDegrees(103 + i * 0.1, 30 + j * 0.1)
})
}
}
- 添加图片
图片较小只有179个字节。20k的数据流畅度可以,图片越大流畅度越低
viewer.entities.add({
point: {
pixelSize: 5,
color: Cesium.Color.BEIGE
},
billboard: {
image: 'icons/facility.gif'
},
// label: {
// text: i+""
// },
position: Cesium.Cartesian3.fromDegrees(103 + i * 0.1, 30 + j * 0.1)
})
- 总结:entity加载,图片越大流畅度越低,点越密集流畅度越低。层级越高流畅度越高(密度下降)。
entity聚合
添加(10k)聚合功能,首次加载时间延长,但是加载成功后流畅度显著提高,并且entity可以同时添加文字和图片
/*
本质上还是利用的是entity加载,还是慢加载时间长
加载完成后性能提升
*/
let staticPoins: Point[] = []
for (let i = 0; i < 500; i++) {
for (let j = 0; j < 200; j++) {
let obj = {
visible: false,
id: i + '_' + j,
onlinetime: "2023-02-21 11:32:22",
accountname: `${i}_${j}`,
differentialstate: 7,
curH: 474.59999999999997,
curB: 30 + i * 0.1,
curL: 104 + 0.1 * j,
satellitenumber: 50,
dopvalue: 0.6000000238418579,
delay: 0,
reserver1: "153403.172851563",
reserver2: "NTRIP GNSSInternetRadio/1.4.5",
pos: Cesium.Cartesian3.fromDegrees(104.06657490833334, 30.63132543),
}
obj.pos = Cesium.Cartesian3.fromDegrees(obj.curL, obj.curB)
staticPoins.push(obj)
}
}
staticPoins.forEach((i, j) => {
if (j < 10000)
dataSource.entities.add({
id: i.id,
point: {
pixelSize: 0
},
label: {
text: i.accountname,
font: '20px sans-serif',
showBackground: true,
// verticalOrigin: Cesium.VerticalOrigin.TOP,
pixelOffset: new Cesium.Cartesian2(0, -65),
distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0, 10e5),//根据Label与相机的距离来获取或设置Label的近和远像素偏移缩放比例属性
// eyeOffset: new Cesium.Cartesian3(0, 7.2, 0)
heightReference: Cesium.HeightReference.RELATIVE_TO_GROUND
},
billboard: {
image: 'icons/facility.gif',
scaleByDistance: new Cesium.NearFarScalar(1.5e2, 1, 1.5e7, 0),//根据相机距离缩放(下限、下限的值、上限、上限的值)
width: 16,
height: 16,
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
horizontalOrigin: Cesium.HorizontalOrigin.CENTER, // //相对于对象的原点(注意是原点的位置)的水平位置
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
},
position: i.pos
})
})
const dataSourcePromise = viewer.dataSources.add(dataSource);
dataSourcePromise.then(function (dataSource) {
const pixelRange = 15;
const minimumClusterSize = 3;
const enabled = true;
dataSource.clustering.enabled = enabled; //是否聚合
dataSource.clustering.pixelRange = pixelRange;
dataSource.clustering.minimumClusterSize = minimumClusterSize;
const pinBuilder = new Cesium.PinBuilder();
const pin1000 = pinBuilder
.fromText("1000+", Cesium.Color.RED, 48)
.toDataURL();
const pin500 = pinBuilder
.fromText("100+", Cesium.Color.RED, 48)
.toDataURL();
const pin100 = pinBuilder
.fromText("100+", Cesium.Color.RED, 48)
.toDataURL();
const pin50 = pinBuilder
.fromText("50+", Cesium.Color.RED, 48)
.toDataURL();
const pin40 = pinBuilder
.fromText("40+", Cesium.Color.ORANGE, 48)
.toDataURL();
const pin30 = pinBuilder
.fromText("30+", Cesium.Color.YELLOW, 48)
.toDataURL();
const pin20 = pinBuilder
.fromText("20+", Cesium.Color.GREEN, 48)
.toDataURL();
const pin10 = pinBuilder
.fromText("10+", Cesium.Color.BLUE, 48)
.toDataURL();
const singleDigitPins = new Array(8);
for (let i = 0; i < singleDigitPins.length; ++i) {
singleDigitPins[i] = pinBuilder
.fromText(`${i + 2}`, Cesium.Color.VIOLET, 48)
.toDataURL();
}
function customStyle() {
if (Cesium.defined(removeListener)) {
removeListener && removeListener();
removeListener = undefined;
} else {
removeListener = dataSource.clustering.clusterEvent.addEventListener(
function (clusteredEntities, cluster) {
cluster.label.show = false;
cluster.billboard.show = true;
cluster.billboard.id = cluster.label.id;
cluster.billboard.verticalOrigin =
Cesium.VerticalOrigin.BOTTOM;
if (clusteredEntities.length >= 1000) {
cluster.billboard.image = pin1000;
} else if (clusteredEntities.length >= 500) {
cluster.billboard.image = pin500;
} else if (clusteredEntities.length >= 100) {
cluster.billboard.image = pin100;
} else if (clusteredEntities.length >= 50) {
cluster.billboard.image = pin50;
} else if (clusteredEntities.length >= 40) {
cluster.billboard.image = pin40;
} else if (clusteredEntities.length >= 30) {
cluster.billboard.image = pin30;
} else if (clusteredEntities.length >= 20) {
cluster.billboard.image = pin20;
} else if (clusteredEntities.length >= 10) {
cluster.billboard.image = pin10;
} else {
cluster.billboard.image =
singleDigitPins[clusteredEntities.length - 2];
}
}
);
}
const pixelRange = dataSource.clustering.pixelRange;
dataSource.clustering.pixelRange = 0;
dataSource.clustering.pixelRange = pixelRange;
}
customStyle();
})
dom加载
利用坐标点的变化时候改变dom元素的位置
优点:自由度高
缺点:适合几百以内的数据。
<template v-for="item in points" :key="item.id">
<DomTag :trackPos="item.pos">
<Dev :point="item" @tooglePopWin="tooglePopWin" />
</DomTag>
</template>
- PointPrimitiveCollection
加载PointPrimitiveCollection点集合,速度快且流畅,也是推荐的方式。
缺点只能加载点
let pointPrimitives: Cesium.PointPrimitiveCollection;
pointPrimitives = scene.primitives.add(
new Cesium.PointPrimitiveCollection()
);
for(let i = 0; i < 100000; i++) {
pointPrimitives.add({
id: i.id,
pixelSize: 10,
color: color,
outlineColor: outlineColor,
outlineWidth: 0,
label: {
text: 222
},
distanceDisplayCondition: new Cesium.DistanceDisplayCondition(
5.5e3
),
position: i.pos,
});
}
- 点位移动
这个方法,移动十万个点位,很流畅
viewer.scene.preUpdate.addEventListener(animateBillboards);
function animateBillboards() {
const moveAmount = new Cesium.Cartesian3(100, 0.0, 0.0);
const positionScratch = new Cesium.Cartesian3();
// @ts-ignore
const billboards = pointPrimitives._pointPrimitives;
const length = billboards.length;
for (let i = 0; i < length; ++i) {
const billboard = billboards[i];
Cesium.Cartesian3.clone(billboard.position, positionScratch);
Cesium.Cartesian3.add(positionScratch, moveAmount, positionScratch);
billboard.position = positionScratch;
}
}
- billoardPrimitives
同上,只能加载图片。
let billoardPrimitives: Cesium.BillboardCollection
billoardPrimitives = viewer.scene.primitives.add(
new Cesium.BillboardCollection({
scene: scene,
})
);
for(let i = 0; i < 100000; i++) {
billoardPrimitives.add({
id: i.id,
image: 'icons/facility.gif',
width: 10,
height: 10,
position: i.pos
});
}
- Primitive和聚合搭配着使用
cesium官方未能提供primitive的聚合方法,但是可以用entity的聚合搭配着使用。
- CommomSiteTookit
import * as Cesium from "cesium";
import { defaultValue } from 'cesium'
type Option = {
delay?:number;
enabled?: boolean;
pixelRange?:number;
minimumClusterSize?:number;
}
/**
* @_v 引入外部创建的Viewer实例(new Cesium.Viewer(...))
* @myPrimitives 原语集合,可以包含页面显示的pointPrimitiveCollection、billboardCollection、labelCollection、primitiveCollection、primitiveCluster
* @myPrimitiveCluster 自定义原语集群
* @myBillboardCollection 广告牌集合(站点显示的内容数据)
*
* @desc 使用primitiveCollection原语集合与primitiveCluster原语集群,处理地图界面显示广告牌billboard数量 > 10w 级时,界面卡顿,浏览器崩溃等问题
*/
class CommomSiteTookit {
static _v: Cesium.Viewer;
myPrimitives: Cesium.PrimitiveCollection | null = null
myPrimitiveCluster:any = null;
myBillboardCollection:Cesium.BillboardCollection|null = null;
constructor() {}
/**
* @desc 使用commomSiteTookit实例前,必须先初始化该实例的_v对象
*/
init(viewer: Cesium.Viewer) {
CommomSiteTookit._v = viewer;
}
/**
* @param [options] 具有以下属性的对象
* @param [options.delay=800] 防抖处理定时器的time
* @param [options.enabled=true] 是否启用集群
* @param [options.pixelRange=15] 用于扩展屏幕空间包围框的像素范围
* @param [options.minimumClusterSize=2] 可集群的屏幕空间对象的最小数量
*
* @desc 处理原语集合,并实现聚合集群功能方法
* @return billboardCollection集合,可直接往集合里添加广告牌billboard,呈现在页面上
*/
load(options:Option = {}) {
let billboardCollection = new Cesium.BillboardCollection();
let labelboardCollection = new Cesium.LabelCollection();
if (Cesium.defined(this.myPrimitives)) {
CommomSiteTookit._v.scene.primitives.remove(this.myPrimitives);
}
this.myPrimitives = CommomSiteTookit._v.scene.primitives.add(
new Cesium.PrimitiveCollection()
);
//@ts-ignore
const primitiveCluster = new Cesium.PrimitiveCluster();
this.myPrimitives && this.myPrimitives.add(primitiveCluster);
primitiveCluster.delay = defaultValue(options.delay, 800);
primitiveCluster.enabled = defaultValue(options.enabled, true);
primitiveCluster.pixelRange = defaultValue(options.pixelRange, 15);
primitiveCluster.minimumClusterSize = defaultValue(
options.minimumClusterSize,
2
);
primitiveCluster._billboardCollection = billboardCollection;
primitiveCluster._initialize(CommomSiteTookit._v.scene);
let removeListener:any;
let pinBuilder = new Cesium.PinBuilder();
/* 定义广告牌 fromText(显示文字,颜色,大小) */
const pin1000 = pinBuilder
.fromText("1000+", Cesium.Color.RED, 48)
.toDataURL();
const pin500 = pinBuilder
.fromText("500+", Cesium.Color.RED, 48)
.toDataURL();
const pin100 = pinBuilder
.fromText("100+", Cesium.Color.RED, 48)
.toDataURL();
const pin50 = pinBuilder
.fromText("50+", Cesium.Color.RED, 48)
.toDataURL();
const pin40 = pinBuilder
.fromText("40+", Cesium.Color.ORANGE, 48)
.toDataURL();
const pin30 = pinBuilder
.fromText("30+", Cesium.Color.YELLOW, 48)
.toDataURL();
const pin20 = pinBuilder
.fromText("20+", Cesium.Color.GREEN, 48)
.toDataURL();
const pin10 = pinBuilder
.fromText("10+", Cesium.Color.BLUE, 48)
.toDataURL();
/* 数量小于十个的聚合广告牌 */
let singleDigitPins = new Array(8);
for (let i = 0; i < singleDigitPins.length; ++i) {
singleDigitPins[i] = pinBuilder
.fromText("" + (i + 2), Cesium.Color.VIOLET, 40)
.toDataURL();
}
const _ = this;
function customStyle() {
if (Cesium.defined(removeListener)) {
removeListener();
removeListener = undefined;
} else {
removeListener = primitiveCluster.clusterEvent.addEventListener(
function(clusteredEntities:any, cluster:any) {
cluster.label.show = false;
cluster.billboard.show = true;
cluster.billboard.id = cluster.label.id;
cluster.billboard.verticalOrigin = Cesium.VerticalOrigin.BOTTOM;
/* 根据站点(参数)的数量给予对应的广告牌 */
if (clusteredEntities.length >= 1000) {
cluster.billboard.image = pin1000;
} else if (clusteredEntities.length >= 500) {
cluster.billboard.image = pin500;
} else if (clusteredEntities.length >= 100) {
cluster.billboard.image = pin100;
} else if (clusteredEntities.length >= 50) {
cluster.billboard.image = pin50;
} else if (clusteredEntities.length >= 40) {
cluster.billboard.image = pin40;
} else if (clusteredEntities.length >= 30) {
cluster.billboard.image = pin30;
} else if (clusteredEntities.length >= 20) {
cluster.billboard.image = pin20;
} else if (clusteredEntities.length >= 10) {
cluster.billboard.image = pin10;
} else {
cluster.billboard.image =
singleDigitPins[clusteredEntities.length - 2];
}
}
);
}
// force a re-cluster with the new styling
let pixelRange = primitiveCluster.pixelRange;
primitiveCluster.pixelRange = 0;
primitiveCluster.pixelRange = pixelRange;
_.myPrimitiveCluster = primitiveCluster;
}
this.myBillboardCollection = billboardCollection;
// start with custom style
customStyle();
return billboardCollection;
}
/**
* @params enable bool值控制开启或关闭集群
* @desc 控制集群生效与否
*/
enableCluster(enable:boolean) {
if (Cesium.defined(this.myPrimitiveCluster)) {
this.myPrimitiveCluster!.enabled = enable;
}
}
/**
* @params id 站点ID
* @return 返回可操作的广告牌[siteBillboard.image = 'xxxx']
* @desc 根据id在集合中获取指定站点广告牌
*/
getSiteBillboardById(id: string) {
if (!Cesium.defined(this.myBillboardCollection)) return undefined;
const _b = this.myBillboardCollection!;
const l = _b.length;
let siteBillboard = undefined;
for (let i = 0; i < l; i++) {
if (id == _b.get(i).id) {
siteBillboard = _b.get(i);
break;
}
}
return siteBillboard;
}
/**
* @desc 删除所有站点广告牌
*/
removeAll() {
if (Cesium.defined(this.myPrimitives)) {
CommomSiteTookit._v.scene.primitives.remove(this.myPrimitives);
}
}
/**
* @params show bool值 控制显示或隐藏
* @desc 隐藏或显示所有站点广告牌
*/
showStatus(show = true) {
this.myPrimitives!.show = show;
}
/**
* @desc 根据id删除指定站点广告牌
*/
remove(id:string) {
const billboard = this.getSiteBillboardById(id);
billboard && this.myBillboardCollection!.remove(billboard);
}
/**
* @desc 销毁(目前退出页面时直接viewer销毁)
*/
destroy() {
this.myPrimitives = null;
this.myPrimitiveCluster = null;
this.myBillboardCollection = null;
// this._v.scene.primitives.destroy()
}
}
export default new CommomSiteTookit();
CommomSiteTookit.init(viewer)
const data = CommomSiteTookit.load({
enabled: true,
delay: 1200,
pixelRange: 20
});
for(let i = 0; i < 100000; i++) {
data.add({
image: 'icons/facility.gif',
scaleByDistance: new Cesium.NearFarScalar(1.5e2, 1, 1.5e7, 0.2),
width: 16,
height: 16,
position: i.pos,
id: i.id
});
}
```