Cesium实现位置点的自定义弹框显示

实现自定义位置点广告牌的显示后,如果需要展示位置点的一些信息,就需要通过鼠标事件来触发自定义弹框的显示。下面通过鼠标左击和移入移出控制弹框的显示与隐藏。

1、鼠标左击LEFT_CLICK触发弹框显示,可参考ScreenSpaceEventHandler - Cesium Documentation

viewer.screenSpaceEventHandler.setInputAction(click => {
        const pickedEntity = viewer.scene.pick(click.position)
        this.selectedEntity = pickedEntity?.id
        // 判断点击物体是否为图标实体
        if (Cesium.defined(pickedEntity)) {
          this.dialogVisible = true // 显示弹窗
        }
      }, Cesium.ScreenSpaceEventType.LEFT_CLICK)

2、鼠标移入移出MOUSE_MOVE时触发弹框显示与隐藏:

viewer.screenSpaceEventHandler.setInputAction(movement => {
        const pickedEntity = viewer.scene.pick(movement.endPosition);
        this.selectedEntity = pickedEntity?.id;
        // 判断移入物体是否为图标实体
        if (Cesium.defined(pickedEntity) && this.selectedEntity) {
          // 鼠标移入图标
          this.dialogVisible = true;
        } else {
          // 鼠标移出图标
          this.dialogVisible = false;
          this.selectedEntity = null;
        }
      }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);

鼠标的事件类型可参考ScreenSpaceEventType - Cesium Documentation

3、问题

有时候弹框会溢出到屏幕外面,导致弹框信息展示不全,因此就需要根据位置点在屏幕中的位置来更新弹框的位置,使得弹框信息不会溢出屏幕;

实现思路:可参考Cesium.js实现显示点位对应的自定义信息弹窗(数据面板)_cesium弹窗-优快云博客

首先,获取当前选择的实体(例如点位或图标)this.selectedEntity在 Cesium 世界坐标系中的位置;

然后,将 3D 世界中的点位转换为 2D 屏幕上的像素位置;

最后,将弹窗的位置设置为计算出的屏幕坐标。

    updateDialogPosition () {
      // 计算弹窗位置
      // 这里获取当前选择的实体(例如点位或图标)在 Cesium 世界坐标系中的位置。
      // `this.selectedEntity.position.getValue` 方法根据当前时间返回实体的位置。
      const cesiumPosition = this.selectedEntity.position.getValue(
        viewer.clock.currentTime
      )

      // 将 Cesium 世界坐标转换为屏幕坐标。
      // 这一步是将 3D 世界中的点位转换为 2D 屏幕上的像素位置。
      const canvasPosition = Cesium.SceneTransforms.wgs84ToWindowCoordinates(
        viewer.scene,
        cesiumPosition
      )

      // 检查转换是否成功。有时候,如果点位不在当前视图中,则转换可能失败。
      if (canvasPosition) {
        // 更新弹窗位置
        this.dialogPosition = {
          x: canvasPosition.x, //距离屏幕上方距离,即top
          y: canvasPosition.y, //距离屏幕左边距离,即left
          width: document.getElementById('cesiumContainer').offsetWidth,
          height: document.getElementById('cesiumContainer').offsetHeight
        }
      }
    },

4、总结

详细完整代码如下:

父组件中的代码

<template>
    <div class="visualization">
        <div id="cesiumContainer"></div>
    <Dialog :position="dialogPosition" :dialogVisible="dialogVisible"
      @closeDialog="dialogVisible = false"></Dialog>
    </div>
  </template>
  
  <script>
  import Dialog from './Dialog.vue'
  var viewer = null
  var tileset = null
  var terrainProvider = null
  export default {
    components:{
    Dialog
    },
    data () {
        return {  
          dialogVisible: false,
          dialogPosition: { x: 0, y: 0 }, // 弹窗显示位置
          selectedEntity: null, // 选中的实体
    
        }
   },

    mounted () {
     
      this.initCesium();
    },
    methods: {
      initCesium () {
        // Ion是Cesium的在线资源服务,通过使用访问令牌,用户可以访问Ion上的高分辨率的地图和其他资源。
        // 获取访问令牌:你需要在Cesium Ion官网上注册账号,并创建一个应用以获取访问令牌。
        // 设置访问令牌:在你的代码中,你需要在创建Cesium Viewer之前,设置好这个访问令牌。
        Cesium.Ion.defaultAccessToken = '申请自己的access token'
        viewer = new Cesium.Viewer('cesiumContainer', {
          animation: false, // 是否显示动画控件
          shouldAnimate: false,//是否自动播放
          homeButton: false, // 是否显示Home按钮
          fullscreenButton: false, // 是否显示全屏按钮
          baseLayerPicker: false, // 是否显示图层选择控件
          geocoder: false, // 是否显示地名查找控件
          timeline: false, // 是否显示时间线控件
          sceneModePicker: false, // 是否显示投影方式控件
          navigationHelpButton: false, // 是否显示帮助信息控件
          navigationInstructionsInitiallyVisible: false, // 是否显示导航提示,默认为true,
          infoBox: true, // 是否显示点击要素之后显示的信息
          requestRenderMode: true, // 启用请求渲染模式
          // scene3DOnly: false, //每个几何实例将只能以3D渲染以节省GPU内存
          sceneMode: 3, // 初始场景模式 1 2D模式 2 2D循环模式 3 3D模式  Cesium.SceneMode
          creditContainer: document.createElement('div'), // 创建一个空的div来包裹版权信息,类似隐藏掉默认的版权信息
          // baseLayerPicker: false,//如果设置为false,则不会创建BaseLayerPicker小部件
          // 指定Cesium的底图图层实例,默认为ImageryProvider
          imageryProvider: new Cesium.WebMapTileServiceImageryProvider({
            url: 'http://t0.tianditu.gov.cn/img_w/wmts?tk=申请自己的key',
            layer: 'vec',
            style: 'default',
            tileMatrixSetID: 'w',
            format: 'tiles',
            maximumLevel: 18
          })
        });
        //加载具有地形高度的数字模型或图像,即抬高三维模型所在区域的地形高度
        terrainProvider = new Cesium.CesiumTerrainProvider({
          url: 'http://127.0.0.1:10004' + this.detailInfo.terrainPath,
          requestVertexNormals: true,
          requestWaterMask: true,
          tilingScheme: new Cesium.GeographicTilingScheme(),
          requestVertexNormals: true
        })
        viewer.terrainProvider = terrainProvider
        var creditElement = document.createElement('div')
        creditElement.style.position = 'absolute'
        creditElement.style.bottom = '10px'
        creditElement.style.right = '10px'
        creditElement.style.color = 'white'
        creditElement.style.fontSize = '20px'
        creditElement.innerHTML = '2024.6'
  
        // 将DOM元素添加到Viewer的容器中
        viewer.container.appendChild(creditElement)
        tileset = new Cesium.Cesium3DTileset({
          url: 'http://127.0.0.1:10004' + this.detailInfo.filePath,
          maximumMemoryUsage: 5120//控制 3D Tiles 的最大内存使用量,从而在保证数据流畅的前提下尽可能减小内存占用。
        })
        viewer.scene.primitives.add(tileset)
        viewer.zoomTo(tileset);
//添加自定义广告牌,具体见https://blog.youkuaiyun.com/dengkexin123456/article/details/144631670?spm=1001.2014.3001.5502
		this.addPoints();
		
		// 监听点击事件触发弹框显示
      viewer.screenSpaceEventHandler.setInputAction(click => {
        const pickedEntity = viewer.scene.pick(click.position)
        this.selectedEntity = pickedEntity?.id
        // 判断点击物体是否为图标实体
        if (Cesium.defined(pickedEntity)) {
          this.updateDialogPosition() // 更新弹窗的位置
          this.dialogVisible = true // 显示弹窗
        } else {
          this.dialogVisible = false // 隐藏弹窗
        }
      }, Cesium.ScreenSpaceEventType.LEFT_CLICK)

      // 监听鼠标移动事件来更新弹窗位置
      viewer.screenSpaceEventHandler.setInputAction(movement => {
        if (this.dialogVisible && this.selectedEntity) {
          this.updateDialogPosition(); // 更新弹窗的位置
        }
      }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);

      viewer.screenSpaceEventHandler.setInputAction(movement => {
        const pickedEntity = viewer.scene.pick(movement.endPosition);
        this.selectedEntity = pickedEntity?.id;
        // 判断移入物体是否为图标实体
        if (Cesium.defined(pickedEntity) && this.selectedEntity) {
          // 鼠标移入图标
          this.updateDialogPosition(); // 更新弹窗的位置
          this.dialogVisible = true;
        } else {
          // 鼠标移出图标
          this.dialogVisible = false;
          this.selectedEntity = null;
        }
      }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
      },
	  
	   // 更新弹窗的位置
    updateDialogPosition () {
      // 计算弹窗位置
      // 这里获取当前选择的实体(例如点位或图标)在 Cesium 世界坐标系中的位置。
      // `this.selectedEntity.position.getValue` 方法根据当前时间返回实体的位置。
      const cesiumPosition = this.selectedEntity.position.getValue(
        viewer.clock.currentTime
      )

      // 将 Cesium 世界坐标转换为屏幕坐标。
      // 这一步是将 3D 世界中的点位转换为 2D 屏幕上的像素位置。
      const canvasPosition = Cesium.SceneTransforms.wgs84ToWindowCoordinates(
        viewer.scene,
        cesiumPosition
      )

      // 检查转换是否成功。有时候,如果点位不在当前视图中,则转换可能失败。
      if (canvasPosition) {
        // 更新弹窗位置
        this.dialogPosition = {
          x: canvasPosition.x,
          y: canvasPosition.y, // 假设弹窗应该在图标上方 50px 的位置
          width: document.getElementById('cesiumContainer').offsetWidth,
          height: document.getElementById('cesiumContainer').offsetHeight
        }
      }
    },
    },
    beforeDestroy () {
      if (viewer) {
        // 组件销毁时,确保Cesium资源被释放
        viewer.destroy()
      }
    }
  }
  
  </script>
  
  <style scoped>
  .visualization {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    z-index: 99999;
  }
  </style>
  

子组件中设置弹窗的位置:

<template>
<!-- 自定义弹框---!>
  <div :style="styleObject" v-if="dialogVisible" class="dialogStyle">
      <span>弹框标题</span>
      <span>
        <i class="el-icon-close" style="color:#fff; font-size: 20px; cursor: pointer;"></i>
      </span>
    </div> -->
    <el-tabs v-model="activeName">
      <el-tab-pane label="基本信息" name="first">
       <div></div>
      </el-tab-pane>
      <el-tab-pane label="实时照片" name="second">
       <div></div>
      </el-tab-pane>
      <el-tab-pane label="实时监控" name="third" >
       <div></div>
      </el-tab-pane>
      <el-tab-pane disabled>
        <slot slot="label">
          <i class="el-icon-close" style="color:#fff; font-size: 20px; cursor: pointer;" @click="closeDialog"></i>
        </slot>
      </el-tab-pane>

    </el-tabs>
  </div>
</template>
<script>

export default {
  props: {
    dialogVisible: {
      type: Boolean,
      default: false
    },
    position: { type: Object, default: () => ({ x: 0, y: 0 }) },
  },
  data() {
    return {
      activeName: 'first',
      Diawidth: "65%",

    }
  },
  watch: {
    dialogVisible: {
      handler(newVal) {
        if (newVal) {
          this.activeName = 'first';
        } else {
          this.closeDialog();
        }
      },
      immediate: true,
    },
  },
  computed: {
    styleObject() {
//设置弹框具体位置
      let styleObject = {
        position: "absolute",
      };
      let left= this.position.x;
      let right = this.position.width - this.position.x;
      let top = this.position.y;
      let bottom = this.position.height - this.position.y;
      let dialogWidth = this.position.width * 0.45;
      let dialogHeight = 450;
      if(right < dialogWidth){       
        styleObject['right'] =  `${right+20}px`;
      }else{
        styleObject['left'] =  `${left+20}px`;
      }
      if(bottom < dialogHeight){
        styleObject['bottom'] =  `${bottom}px`;
      }else{
        styleObject['top'] =  `${top}px`;
      }
      return styleObject;     
    },
  },
  components: {
  },
  mounted() {
 
  },
  methods: {
  closeDialog() {
      this.$emit("closeDialog");
    },
}
}


</script>

<style lang='scss' scoped>
.dialogStyle {
  width: 45%;
  height: 450px;
  z-index: 9999999 !important;
  background-size: 100% 100%;
  color: #fff;
  padding: 10px
}

/deep/.el-tabs__item {
  color: #fff !important;
}

/deep/ .el-tabs__item.is-top:last-child {
  float: right;
}

/deep/ .el-tabs__nav {
  width: 100%;
}
</style>

 走过路过,如果对您有些许帮助,动动您的小手点个赞!

<think>好的,我现在需要帮助用户解决在Cesium实现窗功能的问题。根据用户提供的引用资料,特别是引用[1]和引用[2],我需要先理解这些资料的内容,然后整理出一个清晰的步骤。 首先,引用[1]提到了创建一个cesiumContainer容器,并在script标签内添加相关代码,使用JavaScript实现窗。这可能涉及到HTML元素的创建和Cesium的事件监听。然后,引用[2]提到了单窗和多窗的示例,可能需要通过点击不同的实体来触发不同的窗内容。 接下来,我需要将这些信息整合成一个分步骤的示例。可能需要以下步骤: 1. **初始化Cesium场景**:设置访问令牌并创建Viewer实例。 2. **设计窗HTML结构**:使用绝对定位的div作为窗容器,包含标题和内容区域。 3. **添加窗样式**:通过CSS控制窗的位置、背景颜色、边等,使其美观。 4. **绑定点击事件**:监听实体点击事件,当用户点击实体时触发显示。 5. **更新窗内容**:根据点击实体动态修改窗内的信息,并调整位置。 6. **隐藏窗**:点击关闭按钮或地图其他位置时关闭窗。 需要注意的是,如何将点击的坐标转换为屏幕坐标,以便正确显示窗的位置。同时,要处理多个实体的情况,可能需要为每个实体添加不同的描述信息,并在点击时更新窗内容。 可能遇到的问题包括事件冒泡,比如点击实体时可能触发其他事件,需要确保事件监听正确。另外,窗的定位可能需要使用Cesium的SceneTransforms将世界坐标转换为屏幕坐标。 最后,根据用户提供的示例代码,需要确保代码语法正确,比如正确使用Cesium的API,如viewer.entities.add和viewer.screenSpaceEventHandler.setInputAction等。 在整理步骤时,要确保每个部分都清晰,并且代码示例正确无误,用户能够复制粘贴并运行。同时,要引用提供的参考资料,特别是引用[1]和引用[2]中的示例链接,以增强可信度。 现在,我需要将上述思考过程转化为结构化的回答,包括步骤说明、代码示例和相关问题。确保使用正确的中文表达,并且LaTeX格式符合要求,尽管在这个问题中可能较少涉及数学公式,但如果有变量或代码内联,需要正确使用$...$格式。</think>在Cesium实现窗功能主要依赖HTML/CSS与Cesium事件监听的结合。以下是分步实现示例: ### 一、基础实现步骤 1. **初始化Cesium场景** ```html <div id="cesiumContainer"></div> <script> Cesium.Ion.defaultAccessToken = 'your_token'; const viewer = new Cesium.Viewer('cesiumContainer'); </script> ``` 2. **添加窗HTML结构** ```html <div id="popup" class="cesium-popup"> <div class="popup-header"> <h3>信息窗口</h3> <span class="close-btn">×</span> </div> <div class="popup-content"></div> </div> ``` 3. **窗CSS样式** ```css .cesium-popup { position: absolute; background: white; border: 1px solid #ddd; z-index: 999; width: 300px; display: none; } .popup-header { padding: 8px; border-bottom: 1px solid #eee; } ``` ### 二、事件绑定与交互 ```javascript const handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas); const popup = document.getElementById('popup'); // 添加测试实体 const entity = viewer.entities.add({ position: Cesium.Cartesian3.fromDegrees(116.39, 39.9), billboard: { image: 'pin.png' } }); // 点击事件监听 handler.setInputAction((click) => { const picked = viewer.scene.pick(click.position); if (picked && picked.id) { const position = Cesium.SceneTransforms.wgs84ToWindowCoordinates( viewer.scene, picked.id.position.getValue() ); popup.style.left = position.x + 'px'; popup.style.top = position.y + 'px'; popup.querySelector('.popup-content').innerHTML = ` <p>坐标:${picked.id.position.getValue()}</p> <p>描述:测试信息</p> `; popup.style.display = 'block'; } }, Cesium.ScreenSpaceEventType.LEFT_CLICK); // 关闭按钮事件 document.querySelector('.close-btn').addEventListener('click', () => { popup.style.display = 'none'; }); ``` ### 三、多窗优化方案 1. **动态窗管理**:使用`document.createElement`动态生成窗DOM 2. **位置追踪**:通过`postRender`事件更新位置 ```javascript viewer.scene.postRender.addEventListener(() => { if (trackedEntity) { const pos = Cesium.SceneTransforms.wgs84ToWindowCoordinates( viewer.scene, trackedEntity.position.getValue() ); popup.style.transform = `translate(${pos.x}px, ${pos.y}px)`; } }); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值