Cesium在空间数据可视化方面提供了两种类型的图元,一种是Primitive 图元,这种图元更底层,需要开发者对计算机图形学有一定的知识储备;另一种是Entity图元,相对于Primitive 图元,Entity图元实现起来更简单一些,特别建议初学者使用。Entity 图元实际上是对Primitive 图元的二次封装,目的就是降低初学者的学习难度,快速入门Cesium。本篇就是基于Entity图元完成常用的告警闪烁点的封装使用。
1 PointGraphics 类
该类是Entity图元包含的点对象类型。通过官网API,其构造函数包含如下参数:
各参数的解释可以查看官网api。本篇告警闪烁通过动态修改像素点的大小和轮廓线宽度及其透明度来实现。
2 CallbackProperty 类
该类将Entity图元和时间关联起来,在三维场景每帧绘过程中,Entity图元通过该类可以动态改变图元属性。比如改动图元位置、大小、颜色等。
3 BillboardGraphics 类
该类是Entity图元包含的广告牌对象类型,通过官网API,其构造函数包含如下参数:
各参数的解释可以查看官网api。该类可以产生一个始终朝向摄像机的图片。
4 AlertMarker类封装
通过对上诉三个 类的灵活使用,可以封装出告警闪烁效果。
4.1 依赖库安装
deepmerge
是一个轻量级的JavaScript库,用于深度地合并可枚举属性的两个或更多对象。
npm i deepmerge
4.2 AlertMarker类
/*
* @Description:
* @Author: maizi
* @Date: 2022-05-27 11:36:22
* @LastEditTime: 2024-07-22 15:34:21
* @LastEditors: maizi
*/
const merge = require('deepmerge')
const defaultStyle = {
minSize: 30,
maxSize: 80,
outLineWidth: 20,
color: "#ffff00",
step: 1
}
class AlertMarker {
constructor(viewer, coords, options = {}) {
this.viewer = viewer;
this.coords = coords;
this.options = options;
this.props = this.options.props;
this.baseHeight = this.coords[2] || 0;
this.style = merge(defaultStyle, this.options.style || {});
this.opacity = 0.6;
this.outLineOpacity = 0.4;
this.ratio = ((this.style.maxSize - this.style.minSize) / this.style.step) * 1.0;
this.pixelSize = this.style.minSize;
this.entity = null;
this.init();
}
init() {
this.entity = new Cesium.Entity({
id: Math.random().toString(36).substring(2),
type: "alert_point",
position: Cesium.Cartesian3.fromDegrees(this.coords[0], this.coords[1], this.baseHeight),
props: this.props,
point: {
pixelSize: new Cesium.CallbackProperty(() => {
this.pixelSize = this.pixelSize + this.style.step;
this.opacity = this.opacity - 0.6 / this.ratio;
this.outLineOpacity = this.outLineOpacity - 0.4 / this.ratio;
if (this.pixelSize > this.style.maxSize) {
this.pixelSize = this.style.minSize;
this.opacity = 0.6;
this.outLineOpacity = 0.4;
}
return this.pixelSize;
}, false),
disableDepthTestDistance: Number.POSITIVE_INFINITY,
color: new Cesium.CallbackProperty(() => new Cesium.Color.fromCssColorString(this.style.color).withAlpha(this.opacity), false),
outlineWidth: new Cesium.CallbackProperty(() => this.style.outLineWidth, false),
outlineColor: new Cesium.CallbackProperty(() => new Cesium.Color.fromCssColorString(this.style.color).withAlpha(this.outLineOpacity), false),
scaleByDistance: new Cesium.NearFarScalar(1, 1, 10000, 0.4),
},
billboard: {
image: require("@/assets/img/scene_point_icon.png"),
width: 50,
height: 62,
scale: 0.5,
color: new Cesium.Color(1, 1, 1),
disableDepthTestDistance: Number.POSITIVE_INFINITY,
},
});
}
setSelect(enabled) {
if (enabled) {
this.addPoint()
} else {
this.removePoint()
}
}
addPoint() {
this.point = new Cesium.Entity({
position: Cesium.Cartesian3.fromDegrees(this.coords[0], this.coords[1], this.baseHeight),
point: {
color: Cesium.Color.DARKBLUE.withAlpha(.4),
pixelSize: 6,
outlineColor: Cesium.Color.YELLOW.withAlpha(.8),
outlineWidth: 4,
disableDepthTestDistance: Number.POSITIVE_INFINITY
}
});
this.viewer.entities.add(this.point)
}
removePoint() {
if (this.point) {
this.viewer.entities.remove(this.point)
this.point = null
}
}
updateStyle(style) {
this.style = merge(defaultStyle, style);
this.ratio = ((this.style.maxSize - this.style.minSize) / this.style.step) * 1.0;
this.pixelSize = this.style.minSize;
}
}
export {
AlertMarker
}
4.3 CustomDataSource 类
该类可以用于管理一组实体。通过对该类的封装,可以将三维场景中的实体像二维概念里的图层那样进行管理。这里只是简单的使用,对该类的封装放在后续的图元绘制篇再做介绍。
4.4 ScreenSpaceEventHandler 类
该类通过名称即可知道是处理鼠标事件的。通过该类可以捕获鼠标各类事件。本示例中展示如何捕获鼠标移动和鼠标点击事件。后续篇章会对其他鼠标事件进行介绍。
5 完整示例代码
AlertMarker.vue
<!--
* @Description:
* @Author: maizi
* @Date: 2023-04-07 17:03:50
* @LastEditTime: 2023-04-11 09:53:45
* @LastEditors: maizi
-->
<template>
<div id="container">
</div>
</template>
<script>
import * as MapWorks from './js/MapWorks'
export default {
name: 'AlertMarker',
mounted() {
this.init();
},
methods:{
init(){
let container = document.getElementById("container");
MapWorks.initMap(container)
//创建告警点
let points = [
[104.074822, 30.659807, 60],
[104.076822, 30.653807, 60],
[104.075822, 30.652807, 60],
[104.072822, 30.654807, 60]
];
MapWorks.loadAlertMarker(points)
}
},
beforeDestroy(){
//实例被销毁前调用,页面关闭、路由跳转、v-if和改变key值
MapWorks.destroy();
}
}
</script>
<style lang="scss" scoped>
#container{
width: 100%;
height: 100%;
background: rgba(7, 12, 19, 1);
overflow: hidden;
background-size: 40px 40px, 40px 40px;
background-image: linear-gradient(hsla(0, 0%, 100%, 0.05) 1px, transparent 0), linear-gradient(90deg, hsla(0, 0%, 100%, 0.05) 1px, transparent 0);
}
</style>
MapWorks.js
import GUI from 'lil-gui';
// 初始视图定位在中国
import { AlertMarker } from './AlertMarker'
Cesium.Camera.DEFAULT_VIEW_RECTANGLE = Cesium.Rectangle.fromDegrees(90, -20, 110, 90);
//天地图key
const key = '39673271636382067f0b0937ab9a9677'
let viewer = null;
let eventHandler = null;
let alertLayer = null
let alertList = []
let selectGraphic = null
let gui = null
function initMap(container) {
viewer = new Cesium.Viewer(container, {
animation: false,
baseLayerPicker: false,
fullscreenButton: false,
geocoder: false,
homeButton: false,
infoBox: false,
sceneModePicker: false,
selectionIndicator: false,
timeline: false,
navigationHelpButton: false,
scene3DOnly: true,
orderIndependentTranslucency: false,
contextOptions: {
webgl: {
alpha: true
}
}
})
viewer._cesiumWidget._creditContainer.style.display = 'none'
viewer.scene.fxaa = true
viewer.scene.postProcessStages.fxaa.enabled = true
if (Cesium.FeatureDetection.supportsImageRenderingPixelated()) {
// 判断是否支持图像渲染像素化处理
viewer.resolutionScale = window.devicePixelRatio
}
// 移除默认影像
removeAll()
// 地形深度测试
viewer.scene.globe.depthTestAgainstTerrain = true
// 背景色
viewer.scene.globe.baseColor = new Cesium.Color(0.0, 0.0, 0.0, 0)
// 太阳光照
viewer.scene.globe.enableLighting = true;
// 初始化图层
initLayer()
// 鼠标事件
initClickEvent()
//调试
window.viewer = viewer
}
function initClickEvent() {
eventHandler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
initLeftClickEvent()
initMouseMoveEvent()
}
function initLeftClickEvent() {
eventHandler.setInputAction((e) => {
if (selectGraphic) {
selectGraphic.setSelect(false)
selectGraphic = null
}
if (gui) {
gui.destroy()
}
let pickedObj = viewer.scene.pick(e.position);
if (pickedObj && pickedObj.id) {
if (pickedObj.id.type === 'alert_point') {
selectGraphic = getGraphicById(pickedObj.id.id)
if (selectGraphic) {
selectGraphic.setSelect(true)
initGui()
}
}
}
},Cesium.ScreenSpaceEventType.LEFT_CLICK)
}
function initMouseMoveEvent() {
eventHandler.setInputAction((e) => {
const pickedObj = viewer.scene.pick(e.endPosition);
if (pickedObj && pickedObj.id) {
if (pickedObj.id.type === 'alert_point') {
// 改变鼠标状态
viewer._element.style.cursor = "";
document.body.style.cursor = "pointer";
} else {
viewer._element.style.cursor = "";
document.body.style.cursor = "default";
}
} else {
viewer._element.style.cursor = "";
document.body.style.cursor = "default";
}
},Cesium.ScreenSpaceEventType.MOUSE_MOVE)
}
function getGraphicById(id) {
let graphic = null
for (let i = 0; i < alertList.length; i++) {
if (alertList[i].entity.id === id) {
graphic = alertList[i]
break
}
}
return graphic
}
function initGui() {
let params = {
...selectGraphic.style
}
gui = new GUI()
let layerFolder = gui.title('样式设置')
layerFolder.add(params, 'minSize', 0, 100).step(5.0).onChange(function (value) {
selectGraphic.updateStyle(params)
})
layerFolder.add(params, 'maxSize', 60, 200).step(5.0).onChange(function (value) {
selectGraphic.updateStyle(params)
})
layerFolder.add(params, 'outLineWidth', 10, 100).step(5.0).onChange(function (value) {
selectGraphic.updateStyle(params)
})
layerFolder.add(params, 'step',1, 10).step(1.0).onChange(function (value) {
selectGraphic.updateStyle(params)
})
layerFolder.addColor(params, 'color').onChange(function (value) {
selectGraphic.updateStyle(params)
})
}
function addTdtLayer(options) {
let url = `https://t{s}.tianditu.gov.cn/DataServer?T=${options.type}&x={x}&y={y}&l={z}&tk=${key}`
const layerProvider = new Cesium.UrlTemplateImageryProvider({
url: url,
subdomains: ['0','1','2','3','4','5','6','7'],
tilingScheme: new Cesium.WebMercatorTilingScheme(),
maximumLevel: 18
});
viewer.imageryLayers.addImageryProvider(layerProvider);
}
function initLayer() {
addTdtLayer({
type: 'img_w'
})
addTdtLayer({
type: 'cia_w'
})
alertLayer = new Cesium.CustomDataSource('alertMarker')
viewer.dataSources.add(alertLayer)
viewer.flyTo(alertLayer)
}
function loadAlertMarker(points) {
points.forEach(item => {
const alertMarker = new AlertMarker(viewer, item)
alertList.push(alertMarker)
alertLayer.entities.add(alertMarker.entity)
});
}
function removeAll() {
viewer.imageryLayers.removeAll();
}
function destroy() {
viewer.entities.removeAll();
viewer.imageryLayers.removeAll();
viewer.destroy();
}
export {
initMap,
loadAlertMarker,
destroy
}