cesium实现自定义弹窗

目录

前言

一、创建点

二、创建弹窗

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属性。实现过程如下。

1.创建弹窗div

代码如下:

  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);

2.绑定到点击事件

点击时打开弹窗,在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);

 实现后弹窗效果如图所示:

cesium实现自定义弹窗_第1张图片

 3.使弹窗跟随点移动

不论缩放地图或者移动点,都会造成弹窗的移动的需求,那么就要通过监听来完成弹窗移动的效果。本次测试中共使用了三种方法来对移动进行监听,下面逐一介绍。

方式一:LEFT_DOWN+MOUSE_MOVE

        原理是通过左键按下事件和鼠标移动事件进行嵌套,来模拟鼠标的拖拽事件,该方法可以实现弹窗的跟随移动但是不连贯。

代码如下所示:

        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);

效果如图所示:

 方式二:利用相机的changed事件

this.viewer.scene.camera.changed.addEventListener()

这个changed事件由于存在延迟,相机移动一定程度后才有变化,所以效果比方式一还差……就不展示了。

方式三:相机的moveStart和moveEnd事件中加入帧率监听事件

通过相机的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();
    })

 效果如图:

视觉效果上的最佳方案。

4.点转到地球背后弹窗隐藏 

一句代码搞定

this.isVisible = new Cesium.EllipsoidalOccluder(Cesium.Ellipsoid.WGS84,this.viewer.camera.position).isPointVisible(this.click_point);

效果如图

 是不是很简单(ง •_•)ง


总结

        以上就是本文内容,从点的创建到弹框的移动和隐藏,都做了基础的分析和展示,代码完整,因为只是测试,所以也不纠结样式等,可以随意改造,主要完成功能方面。

        因为网上的有效资源太少了,所以在此记录一下学习过程,希望对其他人也能有帮助,那当然再好不过啦。

本文方法中div构建方法由某公众号中内容改进,在此附上公众号链接,值得学习:

Cesium学习系列汇总 (qq.com)

你可能感兴趣的:(cesium,vue)