前言
一、创建点
二、创建弹窗
1.创建弹窗div
2.绑定到点击事件
3.使弹窗跟随点移动
方式一:LEFT_DOWN+MOUSE_MOVE
方式二:利用相机的changed事件
方式三:相机的moveStart和moveEnd事件中加入帧率监听事件
4.点转到地球背后弹窗隐藏
总结
公司有项目涉及cesium的部分,前辈便让我自己建个cesium的项目练练手,前两天让我尝试了一下自定义弹框的实现。
官方api中对点的描述中只有label这一属性可实现(不知是否是我看漏了啊哈哈哈哈哈哈),于是开始思考通过添加div的方式,来实现cesium自定义的弹窗效果。
本篇文章讲述从创建点开始到实现弹窗的过程。
通过对cesium的学习,绘制对象有Entity和Primitive两种方式,简述两者的区别就是前者调用简单,后者适用于绘制对象多的时候,绘制对象越多,primitive方式显现出的执行效率优势就越明显,此处不再赘述。
创建点代码如下:
addPoint(pointGeo) {
this.viewer.entities.add({
id: 'pick_id_' + this.pick_id++,
name: 'point',
position: Cesium.Cartesian3.fromDegrees(pointGeo.longitude, pointGeo.latitude),
point: { //点
pixelSize: 5,
color: Cesium.Color.RED,
outlineColor: Cesium.Color.WHITE,
outlineWidth: 2
},
label: { //文字标签
text: 'I am a point',
font: '14pt monospace',
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
outlineWidth: 2,
verticalOrigin: Cesium.VerticalOrigin.BOTTOM, //垂直方向以底部来计算标签的位置
pixelOffset: new Cesium.Cartesian2(0, -9) //偏移量
},
billboard: { //图标
image: '/img/leaf-green.png',
width: 64,
height: 64,
pixelOffset: new Cesium.Cartesian2(0, -32) //偏移量
},
}
);
},
代码中使用entities的方式创建点,addPoint方法传入的pointGeo是点坐标。
为了方便测试,我通过鼠标左键点击监听来动态添加点的坐标,代码如下:
//点击地图事件
const handler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas);
handler.setInputAction((click) => {
// 屏幕坐标转世界坐标——关键点
const ellipsoid = this.viewer.scene.globe.ellipsoid;
const cartesian = this.viewer.camera.pickEllipsoid(click.position, ellipsoid);
if (cartesian) { //判断点击的是否是地球
//将笛卡尔坐标转换为地理坐标
const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
//将弧度转为度的十进制度表示
const lon = Cesium.Math.toDegrees(cartographic.longitude);
const lat = Cesium.Math.toDegrees(cartographic.latitude);
const click_point = {longitude: lon, latitude: lat};
this.addPoint(click_point); //将点击点的经纬度传入addPoint
console.log(click_point)
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
点的样式如图所示:
弹窗的实现效果是点击某个存在的点模型后在点的右侧打开,原理是通过获取点击点的屏幕坐标,将坐标的y和x分别赋值给div的top和lieft属性。实现过程如下。
代码如下:
initTool(frameDiv) {
if (this.isInit) {
return 0;
}
//弹窗容器div
const rightdiv = document.createElement('DIV');
rightdiv.className = "tooltipdiv-right";
rightdiv.style = `
position:absolute;
width:200px;
min-height:100px;
max-height:300px;
background:#fff;
border-radius:4px;
box-shadow: 2px 4px 5px #888888;
`;
//弹窗箭头div
const arrow = document.createElement('DIV');
arrow.className = "tooltip-arrow";
arrow.style = `
position:absolute;
left:-24px;
top:38px;
width:0;
height:0;
border-top: 12px solid transparent;
border-right: 12px solid #fff;
border-bottom: 12px solid transparent;
border-left: 12px solid transparent;`;
rightdiv.appendChild(arrow);
//标题div
const title = document.createElement('DIV');
title.className = "tooltipdiv-inner";
title.style = `
width:100%;
height:25px;
line-height:25px;
text-align:center;
background:red;
`
rightdiv.appendChild(title);
//内容div
const content = document.createElement('DIV');
content.className = "tooltipdiv-content";
content.style = `
width:200px;
box-sizing:border-box;
padding:10px 0 10px 10px;
overflow-y:scroll;
word-break:break-all;
`
rightdiv.appendChild(content);
this.addDiv = rightdiv;
this.addtitle = title;
this.addcontent = content;
frameDiv.appendChild(rightdiv);
this.isInit = true;
},
调用方法创建div:
//初始化弹窗div
this.initTool(this.viewer.cesiumWidget.container);
点击时打开弹窗,在methods中新增了如下方法:
//控制弹窗显隐
setVisible: function (visible) {
if (!this.isInit) {
return 0;
}
this.addDiv.style.display = visible ? 'block' : 'none';
},
//控制弹窗定位
showAt: function (position, message, content) {
if (!this.isInit) {
return 0;
}
if (position && message && content) {
this.setVisible(true);
this.addtitle.innerHTML = message;
this.addcontent.innerHTML = content;
this.addDiv.style.left = position.x + 30 + "px";
this.addDiv.style.top = (position.y - 50) + "px";
}
}
以上方法在左键点击监听事件中调用,在点击事件中通过pick来判断是否选中点对象(该方法可在官方api中学习到),代码中的几个全局变量就不展示了,过度作用。
以下代码是在上文点击监听事件的基础上进行了扩展,通过pick判断是否选中对象,选中后打开弹窗,展示传入的信息,没有选中对象则创建新的点对象,点击至地球外部则没有操作。
//点击地图事件
const handler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas);
handler.setInputAction((click) => {
const pick = this.viewer.scene.pick(click.position);
console.log(pick)
//选中某模型 pick选中的对象
if (pick && pick.id) {
// 屏幕坐标转世界坐标——关键点
const ellipsoid = this.viewer.scene.globe.ellipsoid;
const cartesian = this.viewer.camera.pickEllipsoid(click.position, ellipsoid);
//将笛卡尔坐标转换为地理坐标
const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
//将弧度转为度的十进制度表示
const lon = Cesium.Math.toDegrees(cartographic.longitude);
const lat = Cesium.Math.toDegrees(cartographic.latitude);
const point2 = {longitude: lon, latitude: lat};
this.click_point = Cesium.Cartesian3.fromDegrees(point2.longitude, point2.latitude);
console.log(this.click_point)
this.c = new Cesium.Cartesian2(click.position.x, click.position.y);
this.target_position = click.position
this.cartesian_2 = cartesian
if (cartesian) {
this.showAt(click.position, 'LEFT_CLICK', 'i am content');
} else {
this.setVisible(false);
}
} else {
// 屏幕坐标转世界坐标——关键点
const ellipsoid = this.viewer.scene.globe.ellipsoid;
const cartesian = this.viewer.camera.pickEllipsoid(click.position, ellipsoid);
if (cartesian) {
//将笛卡尔坐标转换为地理坐标
const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
//将弧度转为度的十进制度表示
const lon = Cesium.Math.toDegrees(cartographic.longitude);
const lat = Cesium.Math.toDegrees(cartographic.latitude);
const click_point = {longitude: lon, latitude: lat};
this.addPoint(click_point);
console.log(click_point)
}
}
console.log(click.position)
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
实现后弹窗效果如图所示:
不论缩放地图或者移动点,都会造成弹窗的移动的需求,那么就要通过监听来完成弹窗移动的效果。本次测试中共使用了三种方法来对移动进行监听,下面逐一介绍。
原理是通过左键按下事件和鼠标移动事件进行嵌套,来模拟鼠标的拖拽事件,该方法可以实现弹窗的跟随移动但是不连贯。
代码如下所示:
handler.setInputAction(()=>{
handler.setInputAction(()=>{
if (this.c) {
this.isVisible = new Cesium.EllipsoidalOccluder(Cesium.Ellipsoid.WGS84, this.viewer.camera.position).isPointVisible(this.click_point);
if (this.isVisible === false){
this.setVisible(false)
}else {
this.changedC = Cesium.SceneTransforms.wgs84ToWindowCoordinates(this.viewer.scene, this.cartesian_2);
this.showAt(this.changedC, 'i have moved','yeap')
this.c = this.changedC
}
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE)
}, Cesium.ScreenSpaceEventType.LEFT_DOWN);
效果如图所示:
this.viewer.scene.camera.changed.addEventListener()
这个changed事件由于存在延迟,相机移动一定程度后才有变化,所以效果比方式一还差……就不展示了。
通过相机的moveStart和moveEnd事件中加入帧率监听事件,可以做到弹窗位置逐帧改变,视觉效果最佳,原理是moveStart中加入事件,moveEnd中结束事件。
代码如下:
//相机移动开始事件
this.viewer.scene.camera.moveStart.addEventListener(() => {
this.removeHandler = this.viewer.scene.postRender.addEventListener(() => {
console.log("start")
if (this.c) {
this.isVisible = new Cesium.EllipsoidalOccluder(Cesium.Ellipsoid.WGS84, this.viewer.camera.position).isPointVisible(this.click_point);
if (this.isVisible === false) {
this.setVisible(false)
} else {
this.changedC = Cesium.SceneTransforms.wgs84ToWindowCoordinates(this.viewer.scene, this.cartesian_2);
this.showAt(this.changedC, 'i have moved', 'yeapyeapyeapyeapyeapyeapyeapyeapyeapyeapyeapyeapyeapyeapyeapyeapyeapyeapyeapyeapyeapyeapyeapyeap')
this.c = this.changedC
}
}
})
})
//相机移动结束事件
this.viewer.scene.camera.moveEnd.addEventListener(() => {
console.log("end")
this.removeHandler.call();
})
效果如图:
视觉效果上的最佳方案。
一句代码搞定
this.isVisible = new Cesium.EllipsoidalOccluder(Cesium.Ellipsoid.WGS84,this.viewer.camera.position).isPointVisible(this.click_point);
效果如图
是不是很简单(ง •_•)ง
以上就是本文内容,从点的创建到弹框的移动和隐藏,都做了基础的分析和展示,代码完整,因为只是测试,所以也不纠结样式等,可以随意改造,主要完成功能方面。
因为网上的有效资源太少了,所以在此记录一下学习过程,希望对其他人也能有帮助,那当然再好不过啦。
本文方法中div构建方法由某公众号中内容改进,在此附上公众号链接,值得学习:
Cesium学习系列汇总 (qq.com)