# 效果
图中效果源代码在下面的封装栏中
# 基本思路
添加一个鼠标左键点击事件,当鼠标点击时,利用vue2.0中 Vue.extend() 动态添加一个dom元素,将DOM元素渲染到cesium容器中,并利用cesium中提供的 viewer.scene.postRender 实时更新坐标位置。思路很简单,接下来我们进行实现。
# 实现方法
1. 首先我们需要生成一个球体做我们标记的容器。
viewer = new Cesium.Viewer('cesiumContainer',{
// terrainProvider: Cesium.createWorldTerrain(),
// animation: false, // 控制场景动画的播放速度控件
// baseLayerPicker: true, // 底图切换控件
// baselLayerPicker:false,// 将图层选择的控件关掉,才能添加其他影像数据
// // fullscreenButton: false, // 全屏控件
// geocoder: false, // 地理位置查询定位控件
// homeButton: true, // 默认相机位置控件
// timeline: false, // 时间滚动条控件
// infoBox: false, //是否显示信息框
// sceneModePicker: false, //是否显示3D/2D选择器
// selectionIndicator: false, // 点击点绿色弹出 是否显示选取指示器组件
// sceneMode: Cesium.SceneMode.SCENE3D, //设定3维地图的默认场景模式:Cesium.SceneMode.SCENE2D、Cesium.SceneMode.SCENE3D、Cesium.SceneMode.MORPHING
// navigationHelpButton: false, // 默认的相机控制提示控件
// scene3DOnly: true, // 每个几何实例仅以3D渲染以节省GPU内存
// navigationInstructionsInitiallyVisible: false,
// showRenderLoopErrors: false, //是否显示渲染错误
// orderIndependentTranslucency:false,//设置背景透明
});
2. 然后利用cesium中 billboard 来添加目标点位
添加点位的数据格式
poin : [{id:'12321321' , name: "北京西路测试点", type: "固定枪机", state: "在线", position: { x: 116.4568, y: 39.8926} ,text:'X'},
{id:'43244324' , name: "阿乐修理厂门口", type: "固定枪机", state: "在线", position: { x: 116.4568, y: 39.8944 } ,text:'+'},
{id:'43764324', name: "裕华路加油站", type: "固定枪机", state: "在线", position: { x: 116.4566, y: 39.8923 } ,text:'?'},
{id:'437543345', name: "康佳大药房", type: "固定枪机", state: "在线", position: { x: 116.4513, y: 39.8923 } ,text:'!'},],
添加点位先上代码(class封装)
//加载点
dragEntity(){
let drag = new DragEntity({
viewer:this.$store.state.viewer,
})
let _this = this
// this.poin = [{id:234,position:[122.8,39.9],text:"L"},{id:432,position:[122,39],text:"C"}]
this.poin.forEach(item => {
let entity = drag.addEntity(item);
_this.poinEntity[item.id] = entity;
})
},
/**
* @param {Viewer} viewer
*
*/
export default class DragEntity{
constructor(val){
this.viewer = val.viewer,
}
addEntity(value){
//数据格式{id:543595234324_432423,position:[122.8,39.9],text:"L"}
let pinBuilder = new Cesium.PinBuilder();
let poin = this.viewer.entities.add({
id:value.id,
name: value.name,
position: Cesium.Cartesian3.fromDegrees(value.position.x, value.position.y),
billboard: {
image: pinBuilder.fromText(value.text,Cesium.Color.ROYALBLUE, 48).toDataURL(),
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
},
monitoItems:{
data:value
},
});
return poin
}
}
解读一下,我们封装了一个class类,将添加点的方法封装到类中方便调用,我们用“billboard”方法与cesium中PinBuilder来进行添加目标点,"PinBuilder"不会的可以看一下官网https://sandcastle.cesium.com/?src=Map%20Pins.html&label=All,然后我们将创建好的实体,return出来,用一个对象来进行接收,用对象的目的就是为了以后方便查找。来看一眼效果
3. 第三步我们来添加一个左键点击事件,当点击时获取实体,在methods()中添加leftDownAction()方法,mounted掉用
leftDownAction(){
let viewer = this.$store.state.viewer
this.handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
let _this = this
let id
_this.handler.setInputAction(function (movement) {
let pick = viewer.scene.pick(movement.position);
if (Cesium.defined(pick) && (pick.id.id) ) {
// _this.leftDownFlag = true;
id= pick.id.id;
console.log(id)
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
},
代码解读,我们先创建一个LEFT_CLICK 事件,返回值为经纬度,运用 viewer.scene.pick判断拿到实体,打印到控制台;
4. 第四步我们来完成弹窗部分(下面代码没有优化,优化的代码在封装部分)。
export default class Bubble {
constructor(val){
this.viewer = val.viewer
this.div=document.createElement("div");
// this.addDynamicLabel({id:1,position:val.position,title:"cl弹窗"});
}
addDynamicLabel(data){
let div = this.div
div.id = data.id;
// div.style.display="inline"
div.style.position = "absolute";
div.style.width = "300px";
div.style.height = "30px";
let HTMLTable = `
<div style="background:#00ffef66;height:200px;border:"1px soild #08f8a7">${data.text}
<div style="">
</div>
`;
div.innerHTML = HTMLTable;
this.viewer.cesiumWidget.container.appendChild(div);
let gisPosition = data.position._value
this.viewer.scene.postRender.addEventListener(() => {
const canvasHeight = this.viewer.scene.canvas.height;
const windowPosition = new Cesium.Cartesian2();
Cesium.SceneTransforms.wgs84ToWindowCoordinates(
this.viewer.scene,
gisPosition,
windowPosition
);
div.style.bottom = canvasHeight - windowPosition.y +220 + "px";
const elWidth = div.offsetWidth;
div.style.left = windowPosition.x - elWidth / 2 + "px";
}, this);
}
clearDiv(id){
if(this.div){
var parent = this.div.parentElement;
parent.removeChild(this.div);
// this.div.removeNode(true);
this.viewer.scene.postRender.removeEventListener(this.addDynamicLabel,this)
}
}
}
修改点击事件代码
import Bubble from './bubble/index.js'
leftDownAction(){
let viewer = this.$store.state.viewer
let bubble = new Bubble({
viewer:viewer
})
this.handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
let _this = this
let id
_this.handler.setInputAction(function (movement) {
let pick = viewer.scene.pick(movement.position);
if (Cesium.defined(pick) && (pick.id.id) ) {
// _this.leftDownFlag = true;
id= pick.id.id;
let entiy = this.poinEntity[id];
bubble.addDynamicLabel(entiy);
}else{
bubble.clearDiv();
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
},
第四步的完整解读在https://blog.csdn.net/weixin_46730573/article/details/119061305?spm=1001.2014.3001.5502 cesium自定义标记中,本文不做多讲解。当点击时显示弹窗,当点击空白初,删除弹窗。
# 封装(完整代码)
1、在vue中
import Bubble from './bubble/index.js'
import DragEntity from './dragentity.js'
data(){
return{
fullSizenum:'fullSize',
poinEntity:{},
poin : [{id:'12321321' , name: "北京西路测试点", type: "固定枪机", state: "在线", position: { x: 116.4568, y: 39.8926} ,text:'X'},
{id:'43244324' , name: "阿乐修理厂门口", type: "固定枪机", state: "在线", position: { x: 116.4568, y: 39.8944 } ,text:'+'},
{id:'43764324', name: "裕华路加油站", type: "固定枪机", state: "在线", position: { x: 116.4566, y: 39.8923 } ,text:'?'},
{id:'437543345', name: "康佳大药房", type: "固定枪机", state: "在线", position: { x: 116.4513, y: 39.8923 } ,text:'!'},],
}
},
mounted(){
this.dragEntity()
this.leftDownAction()
},
methods:{
leftDownAction(){
let viewer = this.$store.state.viewer
this.handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
let _this = this
let id
_this.handler.setInputAction(function (movement) {
let pick = viewer.scene.pick(movement.position);
if (Cesium.defined(pick) && (pick.id.id) ) {
// _this.leftDownFlag = true;
id= pick.id.id;
_this.bubble(id)
}else{
// console.log(_this.bubbles)
if(_this.bubbles){
_this.bubbles.windowClose()
}
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
},
bubble(id){
if(this.bubbles){
this.bubbles.windowClose()
}
console.log(this.poinEntity[id])
this.bubbles = new Bubble(Object.assign(this.poinEntity[id],{
viewer:this.$store.state.viewer
}))
},
//加载点
dragEntity(){
let drag = new DragEntity({
viewer:this.$store.state.viewer,
})
let _this = this
// this.poin = [{id:234,position:[122.8,39.9],text:"L"},{id:432,position:[122,39],text:"C"}]
this.poin.forEach(item => {
let entity = drag.addEntity(item);
_this.poinEntity[item.id] = entity;
})
},
}
2、创建dragentity.js文件
/**
* @param {Viewer} viewer
*
*/
export default class DragEntity{
constructor(val){
this.viewer = val.viewer,
}
addEntity(value){
let pinBuilder = new Cesium.PinBuilder();
let poin = this.viewer.entities.add({
id:value.id,
name: value.name,
position: Cesium.Cartesian3.fromDegrees(value.position.x, value.position.y),
billboard: {
image: pinBuilder.fromText(value.text,Cesium.Color.ROYALBLUE, 48).toDataURL(),
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
},
monitoItems:{
data:value
},
});
return poin
}
}
3、创建以bubble命名文件夹,里面分别创建一个index.js文件,和index.vue文件
index.js
/**
* @descripion:
* @param {Viewer} viewer
* @param {Cartesian2} position
* @param {String} title
* @param {String} id
* @return {*}
*/
import Vue from "vue";
import Label from "./index.vue";
let WindowVm = Vue.extend(Label);
export default class Bubble {
constructor(val) {
console.log(val.monitoItems.data.name)
this.viewer = val.viewer;
// this.height = val.height;
this.position = val.position._value;
let title = val.monitoItems.data.name;
let state = val.monitoItems.data.state;
let id = val.id
this.vmInstance = new WindowVm({
propsData: {
title,
state,
id
}
}).$mount(); //根据模板创建一个面板
this.vmInstance.closeEvent = e => {
this.windowClose();
}
val.viewer.cesiumWidget.container.appendChild(this.vmInstance.$el); //将字符串模板生成的内容添加到DOM上
this.addPostRender();
}
//添加场景事件
addPostRender() {
this.viewer.scene.postRender.addEventListener(this.postRender, this);
}
//场景渲染事件 实时更新窗口的位置 使其与笛卡尔坐标一致
postRender() {
if (!this.vmInstance.$el || !this.vmInstance.$el.style) return;
const canvasHeight = this.viewer.scene.canvas.height;
const windowPosition = new Cesium.Cartesian2();
Cesium.SceneTransforms.wgs84ToWindowCoordinates(
this.viewer.scene,
this.position,
windowPosition
);
this.vmInstance.$el.style.bottom =
canvasHeight - windowPosition.y +260+ "px";
const elWidth = this.vmInstance.$el.offsetWidth;
this.vmInstance.$el.style.left = windowPosition.x - elWidth / 2 +110 + "px";
const camerPosition = this.viewer.camera.position;
let height = this.viewer.scene.globe.ellipsoid.cartesianToCartographic(camerPosition).height;
height += this.viewer.scene.globe.ellipsoid.maximumRadius;
if((!(Cesium.Cartesian3.distance(camerPosition,this.position) > height))&&this.viewer.camera.positionCartographic.height<50000000){
this.vmInstance.$el.style.display = "block";
}else{
this.vmInstance.$el.style.display = "none";
}
}
//关闭
windowClose() {
if(this.vmInstance){
this.vmInstance.$el.remove();
this.vmInstance.$destroy();
}
//this.vmInstance.$el.style.display = "none"; //删除dom
this.viewer.scene.postRender.removeEventListener(this.postRender, this); //移除事件监听
}
}
index.vue
<template>
<div :id="id" class="box">
<div class="pine"></div>
<div class="box-wrap">
<div class="close" @click="closeClick">X</div>
<div class="area">
<div class="area-title fontColor">{{ title }}</div>
</div>
<div class="content">
<div class="data-li">
<div class="data-label textColor">状态:</div>
<div class="data-value">
<span class="label-num yellowColor">{{state}}</span>
</div>
</div>
<div class="data-li">
<div class="data-label textColor">实时水位:</div>
<div class="data-value">
<span class="label-num yellowColor">100</span>
<span class="label-unit textColor">m³/s</span>
</div>
</div>
</div>
</div>
<!-- <img src="./layer_border.png" alt="Norway"> -->
</div>
</template>
<script>
export default {
name: "DynamicLabel",
data() {
return {
show: true,
};
},
props: {
title: {
type: String,
default: "标题",
},
id: {
type: String,
default: "001",
},
state:{
type: String,
default: "001",
}
},
methods:{
closeClick(){
if(this.closeEvent){
this.closeEvent();
}
}
}
};
</script>
<style lang="scss">
.box {
width: 200px;
position: relative;
bottom: 0;
left: 0;
}
.close{
position: absolute;
color: #fff;
top: 1px;
right: 10px;
text-shadow: 2px 2px 2px #022122;
cursor: pointer;
animation: fontColor 1s;
}
.box-wrap {
position: absolute;
left: 21%;
top: 0;
width: 100%;
height: 163px;
border-radius: 50px 0px 50px 0px;
border: 1px solid #38e1ff;
background-color: #38e1ff4a;
box-shadow: 0 0 10px 2px #29baf1;
animation: slide 2s;
}
.box-wrap .area {
position: absolute;
top: 20px;
right: 0;
width: 95%;
height: 30px;
background-image: linear-gradient(to left, #4cdef9, #4cdef96b);
border-radius: 30px 0px 0px 0px;
animation: area 1s;
}
.pine {
position: absolute;
// left: 0;
// bottom: -83px;
width: 100px;
height: 100px;
box-sizing: border-box;
line-height: 120px;
text-indent: 5px;
}
.pine::before {
content: "";
position: absolute;
left: 0;
bottom: -83px;
width: 40%;
height: 60px;
box-sizing: border-box;
border-bottom: 1px solid #38e1ff;
transform-origin: bottom center;
transform: rotateZ(135deg) scale(1.5);
animation: slash 0.5s;
filter: drop-shadow(1px 0px 2px #03abb4);
/* transition: slash 2s; */
}
.area .area-title {
text-align: center;
line-height: 30px;
}
.textColor {
font-size: 14px;
font-weight: 600;
color: #ffffff;
text-shadow: 1px 1px 5px #002520d2;
animation: fontColor 1s;
}
.yellowColor {
font-size: 14px;
font-weight: 600;
color: #f09e28;
text-shadow: 1px 1px 5px #002520d2;
animation: fontColor 1s;
}
.fontColor {
font-size: 16px;
font-weight: 800;
color: #ffffff;
text-shadow: 1px 1px 5px #002520d2;
animation: fontColor 1s;
}
.content {
padding: 55px 10px 10px 10px;
}
.content .data-li {
display: flex;
}
@keyframes fontColor {
0% {
color: #ffffff00;
text-shadow: 1px 1px 5px #00252000;
}
40% {
color: #ffffff00;
text-shadow: 1px 1px 5px #00252000;
}
100% {
color: #ffffff;
text-shadow: 1px 1px 5px #002520d2;
}
}
@keyframes slide {
0% {
border: 1px solid #38e1ff00;
background-color: #38e1ff00;
box-shadow: 0 0 10px 2px #29baf100;
}
100% {
border: 1px solid #38e1ff;
background-color: #38e1ff4a;
box-shadow: 0 0 10px 2px #29baf1;
}
}
@keyframes area {
0% {
width: 0%;
}
25% {
width: 0%;
}
100% {
width: 95%;
}
}
/* img{
position:absolute;
left:30%;
top:0;
width: 100%;
box-shadow: 0 0 10px 2px #29baf1;
} */
@keyframes slash {
0% {
transform: rotateZ(135deg) scale(0);
}
100% {
transform: rotateZ(135deg) scale(1.5);
}
}
</style>
然后在封装栏中第一步调用即可,最终效果与展示效果一样。