CesiumJS打点--悬浮点位显示点位信息

596 阅读4分钟

这篇笔记主要记录CesiumJS打点,悬浮点位显示点位信息。 点位上图详解可以看另一篇笔记📒

image.png

实现上图效果,鼠标悬浮到江西省九江市那个点位时,提示这是庐山国家公园,主要就是利用 ScreenSpaceEventType.MOUSE_MOVE 事件,获取点位信息,然后创建一个div,自定义样式把它渲染出来。

  1. 添加点位。
  2. 添加悬浮事件,获取点位信息
  3. 创建div,自定义样式把点位信息渲染出来
  4. 总结下用到的知识点关键词
  5. 完整代码demo 可直接运行 不用安装依赖

容易踩的坑:自定义div渲染,div布局一定要是相对于地图容器的(cesiumContainer),而不能是整个dom结构的body,html元素

添加点位
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>国家公园分布</title>
    <script src="https://cesium.com/downloads/cesiumjs/releases/1.85/Build/Cesium/Cesium.js"></script>
    <style>
      @import url('https://cesium.com/downloads/cesiumjs/releases/1.85/Build/Cesium/Widgets/widgets.css');
      html,
      body,
      #cesiumContainer {
        width: 100%;
        height: 100%;
        margin: 0;
        padding: 0;
        overflow: hidden;
      }
    </style>
  </head>
  <body>
    <div id="cesiumContainer"></div>
    <script>
      Cesium.Ion.defaultAccessToken =
        'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIzZmM4MWYwYS0xOWM3LTQ0ZWEtYTUzNC1mMWI3ODAyODA1ZmYiLCJpZCI6NDQ2OTEsImlhdCI6MTYxNDI0NDk1OX0.5wce5JelLgCOVQ513YI9QtLDuqTA_L9Y0u_s2oFkWR4'

      // 初始化
      const viewer = new Cesium.Viewer('cesiumContainer')
      let billboards = viewer.scene.primitives.add(
        new Cesium.BillboardCollection()
      )

      // 要上图的点位列表
      const nationalParks = [
        { name: '九寨沟国家公园', lat: 33.2554, lon: 103.918 },
        { name: '黄山国家公园', lat: 30.1313, lon: 118.1689 },
        { name: '张家界国家森林公园', lat: 29.3456, lon: 110.4859 },
        { name: '武陵源风景区', lat: 29.3472, lon: 110.5485 },
        { name: '亚丁自然保护区', lat: 29.0572, lon: 100.1852 },
        { name: '庐山国家公园', lat: 29.5803, lon: 116.0296 },
        { name: '神农架国家公园', lat: 31.592, lon: 110.4872 },
        { name: '武夷山', lat: 27.7271, lon: 117.6783 },
        { name: '长白山国家自然保护区', lat: 42.0116, lon: 128.0588 },
        { name: '普达措国家公园', lat: 27.8267, lon: 99.9885 },
      ]

      // 点位上图
      const addBillboard = (visibility = true) => {
        nationalParks.forEach((park, i) => {
          const billboard = billboards.add({
            position: Cesium.Cartesian3.fromDegrees(park.lon, park.lat),
            image: 'http://127.0.0.1:5501/yxkj.png',
            scale: 0.4, // Adjust scale to maintain image quality
            show: visibility,
            id: {
              data: {
                name: park.name,
                description: 'National Park ' + i,
              },
            },
          })
        })
      }

      addBillboard()

      // 设置视角可以看到所有国家公园
      const lushanCoordinates = {
        lat: 29.5803,
        lon: 116.0296,
        height: 2915000, // Adjust height to get a better view
      }

      viewer.camera.setView({
        destination: Cesium.Cartesian3.fromDegrees(
          lushanCoordinates.lon,
          lushanCoordinates.lat,
          lushanCoordinates.height
        ),
        orientation: {
          heading: Cesium.Math.toRadians(0.0), // Heading in degrees
          pitch: Cesium.Math.toRadians(-90.0), // Pitch in degrees
          roll: 0.0, // Roll in degrees
        },
      })
    </script>
  </body>
</html>

这样就实现点位上图了,点位上图解释可以看另一篇笔记📒

添加悬浮事件,获取点位信息

因为我们是需要悬浮显示点位信息,所以要添加MOUSE_MOVE事件,

我们怎么判断悬浮的是不是点位呢?

cesium的scene有一个pick方法,看文档

image.png

传入窗口坐标,如果有点位,就会返回该坐标的第一个点位,没有就返回undefined

const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas)
handler.setInputAction(function (movement) {
  const pickedObject = viewer.scene.pick(movement.endPosition)
  console.log(pickedObject, 'pickedObject')
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE)

动图展示--悬浮点位可以获取点位信息,悬浮没有点位的地方就显示undefined.

Cesium-National-Parks-Example.gif

因为空白地方viewer.scene.pick(movement.endPosition)返回是undefined, 再结合Cesium.defined(value)可以检测是不是悬浮到了点位,只有Cesium.defined(value)返回true,才说明悬浮非空白区域,再加上pickedObject.primitive instanceof Cesium.Billboard,悬浮的是billboard类型,就是我们要的悬浮点位类型

 if (
   Cesium.defined(pickedObject) &&
  pickedObject.primitive instanceof Cesium.Billboard
) {
}

Cesium.defined(value)文档链接

image.png

封装了一个事件类,其中的tooltip悬浮提示待会儿用到

 class CesiumEventHandler {
        constructor(viewer, tooltip) {
          this.viewer = viewer
          this.tooltip = tooltip
          this.handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas)

          this.initEventHandlers()
        }

        initEventHandlers() {
          // 悬浮事件
          this.handler.setInputAction(
            this.handleMouseMove.bind(this),
            Cesium.ScreenSpaceEventType.MOUSE_MOVE
          )
        }

        handleMouseMove(movement) {
          const pickedObject = this.viewer.scene.pick(movement.endPosition)
          // 判断悬浮点位
          if (Cesium.defined(pickedObject) && pickedObject.primitive instanceof Cesium.Billboard) {
            // 鼠标变成可点击状态
            this.viewer.canvas.style.cursor = 'pointer'
            const name = pickedObject.id.data.name
            const canvasPosition = movement.endPosition
            this.tooltip.show(name, canvasPosition)
          } else {
            this.viewer.canvas.style.cursor = 'default'
            this.tooltip.hide()
          }
          this.viewer.scene.requestRender()
        }
      }
创建div,自定义样式把点位信息渲染出来

数据也拿到了,现在就是创建一个div渲染出来,没有用cesium的Cesium.LabelCollection,那个没法自定义样式,比如加个渐变背景。创建div的代码,

  class Tooltip {
        constructor(container) {
            this.container = container
            this.tooltip = document.createElement('div')
            this.tooltip.className = 'cesium-tooltip'
            this.container.appendChild(this.tooltip)
          }

        // 显示提示信息  参数 显示的内容和位置
        show(content, position) {
          this.tooltip.style.display = 'block'
          // 提示信息位置在鼠标的位置(点位的位置)向上20px
          this.tooltip.style.left = `${position.x}px`
          this.tooltip.style.top = `${position.y - 20}px` // 20px offset to position above the cursor
          this.tooltip.innerHTML = content
        }
        // 隐藏提示信息
        hide() {
          this.tooltip.style.display = 'none'
        }
      }

tooltip接受一个容器作为参数,这个参数就是地图的dom节点, 布局要以地图参考,因为我们获取的坐标位置就是在地图画布的坐标

image.png

再看下前面的,拿到了数据,也有了位置,调用tooltip的showhide就行了。

const name = pickedObject.id.data.name
const canvasPosition = movement.endPosition
this.tooltip.show(name, canvasPosition)
总结下用到的知识点关键词
  1. 事件处理器 - new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas)
  2. 获取悬浮位置是否有点位
    • 2.1 viewer.scene.pick(movement.endPosition)
    • 2.2 Cesium.defined(pickedObject)
    • 2.3 pickedObject.primitive instanceof Cesium.Billboard
  3. 悬浮提示的位置要是交互的位置
    • this.tooltip.style.left = ${position.x}px`

最后的最后,如果发现悬浮提示没出来,记得查看是否加了,自定义悬浮框样式

 .cesium-tooltip {
        position: absolute;
        z-index: 2;
        padding: 10px;
        background: linear-gradient(
          to right,
          rgba(0, 123, 255, 0.75),
          rgba(0, 80, 160, 0.75)
        );
        color: white;
        border-radius: 5px;
        pointer-events: none;
        display: none;
        transform: translate(
          -50%,
          -100%
        ); /* 这个属性使悬浮提示位于点位居中、 朝上的位置 */
      }
完整代码demo,可以直接运行看效果

一个html文件,,不用安装依赖

<!DOCTYPE html>
<html lang="en">
 <head>
   <meta charset="UTF-8" />
   <meta name="viewport" content="width=device-width, initial-scale=1.0" />
   <title>国家公园分布</title>
   <script src="https://cesium.com/downloads/cesiumjs/releases/1.85/Build/Cesium/Cesium.js"></script>
   <style>
     @import url('https://cesium.com/downloads/cesiumjs/releases/1.85/Build/Cesium/Widgets/widgets.css');
     html,
     body,
     #cesiumContainer {
       width: 100%;
       height: 100%;
       margin: 0;
       padding: 0;
       overflow: hidden;
     }
     .cesium-tooltip {
       position: absolute;
       z-index: 2;
       padding: 10px;
       background: linear-gradient(
         to right,
         rgba(0, 123, 255, 0.75),
         rgba(0, 80, 160, 0.75)
       );
       color: white;
       border-radius: 5px;
       pointer-events: none;
       display: none;
       transform: translate(
         -50%,
         -100%
       ); /* 这个属性使悬浮提示位于点位居中、 朝上的位置 */
     }
   </style>
 </head>
 <body>
   <div id="cesiumContainer"></div>
   <script>
     Cesium.Ion.defaultAccessToken =
       'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIzZmM4MWYwYS0xOWM3LTQ0ZWEtYTUzNC1mMWI3ODAyODA1ZmYiLCJpZCI6NDQ2OTEsImlhdCI6MTYxNDI0NDk1OX0.5wce5JelLgCOVQ513YI9QtLDuqTA_L9Y0u_s2oFkWR4'

     const viewer = new Cesium.Viewer('cesiumContainer')

     let billboards = viewer.scene.primitives.add(
       new Cesium.BillboardCollection()
     )

     const nationalParks = [
       { name: '九寨沟国家公园', lat: 33.2554, lon: 103.918 },
       { name: '黄山国家公园', lat: 30.1313, lon: 118.1689 },
       { name: '张家界国家森林公园', lat: 29.3456, lon: 110.4859 },
       { name: '武陵源风景区', lat: 29.3472, lon: 110.5485 },
       { name: '亚丁自然保护区', lat: 29.0572, lon: 100.1852 },
       { name: '庐山国家公园', lat: 29.5803, lon: 116.0296 },
       { name: '神农架国家公园', lat: 31.592, lon: 110.4872 },
       { name: '武夷山', lat: 27.7271, lon: 117.6783 },
       { name: '长白山国家自然保护区', lat: 42.0116, lon: 128.0588 },
       { name: '普达措国家公园', lat: 27.8267, lon: 99.9885 },
     ]

     class Tooltip {
       constructor(container) {
           this.container = container
           this.tooltip = document.createElement('div')
           this.tooltip.className = 'cesium-tooltip'
           this.container.appendChild(this.tooltip)
       }

       show(content, position) {
         this.tooltip.style.display = 'block'
         this.tooltip.style.left = `${position.x}px`
         this.tooltip.style.top = `${position.y - 20}px` // 20px offset to position above the cursor
         this.tooltip.innerHTML = content
       }

       hide() {
         this.tooltip.style.display = 'none'
       }
     }

     class CesiumEventHandler {
       constructor(viewer, tooltip) {
         this.viewer = viewer
         this.tooltip = tooltip
         this.handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas)

         this.initEventHandlers()
       }

       initEventHandlers() {
         this.handler.setInputAction(
           this.handleMouseMove.bind(this),
           Cesium.ScreenSpaceEventType.MOUSE_MOVE
         )
       }

       handleMouseMove(movement) {
         const pickedObject = this.viewer.scene.pick(movement.endPosition)
         if (Cesium.defined(pickedObject) && pickedObject.primitive instanceof Cesium.Billboard) {
           this.viewer.canvas.style.cursor = 'pointer'
           const name = pickedObject.id.data.name
           const canvasPosition = movement.endPosition
           this.tooltip.show(name, canvasPosition)
         } else {
           this.viewer.canvas.style.cursor = 'default'
           this.tooltip.hide()
         }
         this.viewer.scene.requestRender()
       }
     }

     const tooltip = new Tooltip(document.getElementById('cesiumContainer'))
     const cesiumEventHandler = new CesiumEventHandler(viewer, tooltip)

     const addBillboard = (visibility = true) => {
       nationalParks.forEach((park, i) => {
         const billboard = billboards.add({
           position: Cesium.Cartesian3.fromDegrees(park.lon, park.lat),
           image: 'http://127.0.0.1:5501/yxkj.png',
           scale: 0.4, // Adjust scale to maintain image quality
           show: visibility,
           id: {
             data: {
               name: park.name,
               description: 'National Park ' + i,
             },
           },
         })
       })
       viewer.scene.requestRender()
     }

     addBillboard()

     // Center the camera on Lushan National Park
     const lushanCoordinates = {
       lat: 29.5803,
       lon: 116.0296,
       height: 2915000, // Adjust height to get a better view
     }

     viewer.camera.setView({
       destination: Cesium.Cartesian3.fromDegrees(
         lushanCoordinates.lon,
         lushanCoordinates.lat,
         lushanCoordinates.height
       ),
       orientation: {
         heading: Cesium.Math.toRadians(0.0), // Heading in degrees
         pitch: Cesium.Math.toRadians(-90.0), // Pitch in degrees
         roll: 0.0, // Roll in degrees
       },
     })
   </script>
 </body>
</html>