cesiumJS 使用primitive加载大量点位

1,318 阅读4分钟

这篇笔记主要记录

  1. 使用primitive加载点位
  2. 使用primitive删除点位
  3. 使用primitive显示隐藏点位
  4. 给点位增加点击弹窗事件

地图打点是常见的需求,可以通过Entity或者Primitive实现,通过Primitive加载大量点位性能更好。

使用primitive加载点位

通过PrimitiveCollection添加 new Cesium.BillboardCollection(), 文档描述

image.png

定义增加点位方法,addedBillboards为添加的点位列表,把点位存起来,方便点位清除

let addedBillboards = [] // 点位列表
const imageUrl = '/yxkj.png'
const billboards = scene.primitives.add(new Cesium.BillboardCollection());
const addBillboard = () => {
  // 随机生成50个经纬度点位
  for (let i = 0; i < 50; i++) {
    const longitude = 113.5 + Math.random() * 5
    const latitude = 24.5 + Math.random() * 5.5
    // 因为通过`primitives.add()`方法返回的就是传的参数`primitive`,
    // 所以`billboards`可以调用`new Cesium.BillboardCollection`的`add`方法,
    const billboard = billboards.add({
      position: Cesium.Cartesian3.fromDegrees(longitude, latitude),
      image: imageUrl,
      scale: 1.0,
    })
    // 添加的点位列表,方便清除点位使用
    addedBillboards.push(billboard)
  }
  // 要请求渲染 因为使用了配置 requestRenderMode: true 不然地图会不响应 要操作地图点位才会出现
  viewer.scene.requestRender()
}

上面代码中通过BillboardCollectionadd方法,参考文档

image.png

如下图,点位正常上图

Vite-Vue-TS (2).gif

使用primitive删除点位

先前在添加的方法中,我们定义了addedBillboards,保存了添加的点位,那么我们就调用remove方法清除,看下remove文档,传入参数billboard就可以

image.png

const removeBillboard = () => {
  if (addedBillboards.length > 0) {
    for (const billboard of addedBillboards) {
      billboards.remove(billboard)
    }
    addedBillboards.length = 0 // 点位都清除后 把addedBillboards清空
  } else {
    alert('没有点位可以清除!')
  }
   viewer.scene.requestRender()
}

点位正常清除GIF

Vite-Vue-TS (1).gif

使用primitive显示隐藏点位

看下文档描述, 有show属性,默认为true,我们如果想切换可见性,添加的时候默认给隐藏,然后点击切换按钮的时候,切换show属性,这样第一次点击就会是可见的

image.png 隐藏可见性代码

// 加载点位 可见性变成动态传参
const addBillboard = (visibility = true) => {
  for (let i = 0; i < 50; i++) {
    const longitude = 113.5 + Math.random() * 5
    const latitude = 24.5 + Math.random() * 5.5
    const billboard = billboards.add({
      position: Cesium.Cartesian3.fromDegrees(longitude, latitude),
      image: imageUrl,
      scale: 1.0,
      show: visibility,
    })
    addedBillboards.push(billboard)
  }
  viewer.scene.requestRender()
}
// 默认给隐藏
addBillboard(false)

// 切换可见性
function toggleBillboardVisibility() {
  for (const billboard of addedBillboards) {
    billboard.show = !billboard.show
  }
  viewer.scene.requestRender()
}

Vite-Vue-TS (3).gif

给点位增加点击弹窗事件

我们经常需要加载点位信息,比如这个点位是一个加油站,我们可能会想知道这个加油站名称、位置、负责人联系方式、姓名、油品种类等等。 cesium支持点击事件,我们要做的事,确保我们点击这个点位,能够获取这个点位信息。

看下cesium事件文档,

  1. 用户事件处理器, new Cesium.ScreenSpaceEventHandler(element)接受的参数是要执行用户事件的元素
  2. 用户输入事件方法,setInputAction(action, type, modifier)用户输入事件触发的函数
  3. 事件类型 这里我们要加的是LEFT_CLICK,左键点击鼠标

image.png image.png image.png

写方法啦 先改造下先前添加点位方法,写一点数据到点位信息里去

const addBillboard = (visibility = true) => {
  for (let i = 0; i < 50; i++) {
    const longitude = 113.5 + Math.random() * 5
    const latitude = 24.5 + Math.random() * 5.5
    const billboard = billboards.add({
      position: Cesium.Cartesian3.fromDegrees(longitude, latitude),
      image: imageUrl,
      scale: 1.0,
      show: visibility,
      id: {
        // 把数据点位放到id里,看了下文档没有data属性,而id是文档提供给我们的可以任何数据类型
        data: {
          name: '点位' + i,
          description: ' 🐷 🐷.',
        },
      },
    })
    addedBillboards.push(billboard)
  }
  viewer.scene.requestRender()
}

文档里没有data属性,而id是文档提供给我们的,可以任何数据类型,所以把数据放到id里

image.png

添加事件方法

function addCesiumEvent() {
  viewer.screenSpaceEventHandler.setInputAction(function anyname(movement) {
    const pickedObject = viewer.scene.pick(movement.position)
    // 判断我们点击了点位 并且点位是primitive
    if (
      Cesium.defined(pickedObject) &&
      pickedObject.primitive instanceof Cesium.Billboard
    ) {
      const billboard = pickedObject.primitive
      let { data } = billboard.id
      alert('点击点位: ' + data.name + data.description)
    }
  }, Cesium.ScreenSpaceEventType.LEFT_CLICK)
}

验证下, 可以看到每个点位点击都可以获取到对应的点位id

Vite-Vue-TS (4).gif

完整代码

<template>
  <div>
    <div id="cesiumContainer" ref="cesiumContainer"></div>
    <div id="controls">
      <button @click="addBillboard">添加点位</button>
      <button @click="removeBillboard">清除点位</button>
      <button @click="toggleBillboardVisibility">显示/隐藏点位</button>
    </div>
  </div>
</template>

<script setup lang="ts">
import { onMounted, ref } from 'vue'
import * as Cesium from 'cesium'
import 'cesium/Build/Cesium/Widgets/widgets.css'
const cesiumContainer = ref(null)
let viewer
let billboards
const addedBillboards = []

const imageUrl = '/yxkj.png'
const defaultAccessToken =
  '你的ion token'

function initCesium() {
  viewer = new Cesium.Viewer(cesiumContainer.value, {
    geocoder: false,
    homeButton: false,
    sceneModePicker: false,
    baseLayerPicker: false,
    navigationHelpButton: false,
    animation: false,
    timeline: false,
    fullscreenButton: false,
    vrButton: false,
    selectionIndicator: false,
    maximumRenderTimeChange: Infinity, // 无操作时自动渲染帧率,设为数字会消耗性能,Infinity为无操作不渲染
    shadows: false, // 是否显示光照投射的阴影
    // targetFrameRate: 10, // 帧率
    shouldAnimate: false,
    requestRenderMode: true,
    cesiumLogo: false,
    // sceneMode: Cesium.SceneMode.SCENE3D,
    // terrainExaggeration: 3,
    infoBox: false,
  })
}

// 增加点位
const addBillboard = (visibility = true) => {
  for (let i = 0; i < 50; i++) {
    const longitude = 113.5 + Math.random() * 5
    const latitude = 24.5 + Math.random() * 5.5
    const billboard = billboards.add({
      position: Cesium.Cartesian3.fromDegrees(longitude, latitude),
      image: imageUrl,
      scale: 1.0,
      show: visibility,
      id: {
        data: {
          name: '点位' + i,
          description: ' 🐷 🐷.',
        },
      },
    })
    addedBillboards.push(billboard)
  }
  viewer.scene.requestRender()
}

// 清除点位
const removeBillboard = () => {
  if (addedBillboards.length > 0) {
    for (const billboard of addedBillboards) {
      billboards.remove(billboard)
    }
    addedBillboards.length = 0 // Clear the array after removing all billboards
  } else {
    alert('No billboards to remove!')
  }
  viewer.scene.requestRender()
}

// 显示/隐藏点位
function toggleBillboardVisibility() {
  // 如果没有打点 就打点并默认隐藏
  if (!addedBillboards.length) {
    addBillboard(false)
  }

  for (const billboard of addedBillboards) {
    billboard.show = !billboard.show
  }
  viewer.scene.requestRender()
}

// 点击事件
function addCesiumEvent() {
  viewer.screenSpaceEventHandler.setInputAction(function anyname(movement) {
    const pickedObject = viewer.scene.pick(movement.position)
    if (
      Cesium.defined(pickedObject) &&
      pickedObject.primitive instanceof Cesium.Billboard
    ) {
      const billboard = pickedObject.primitive
      let { data } = billboard.id
      alert('点击点位: ' + data.name + data.description)
      // Perform any action you want with the clicked billboard
    }
  }, Cesium.ScreenSpaceEventType.LEFT_CLICK)
}

onMounted(() => {
  Cesium.Ion.defaultAccessToken = defaultAccessToken
  initCesium()
  viewer.cesiumWidget.creditContainer.style.display = 'none'
  billboards = viewer.scene.primitives.add(new Cesium.BillboardCollection())
  addCesiumEvent()
  viewer.camera.flyTo({
    destination: Cesium.Cartesian3.fromDegrees(118.0, 27.0, 1750000.0),
  })
})
</script>

<style scoped>
@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;
}
#controls {
  position: absolute;
  top: 10px;
  left: 10px;
  z-index: 1;
  background: rgba(255, 255, 255, 0.8);
  padding: 10px;
  border-radius: 5px;
}
button {
  margin: 5px;
  padding: 10px;
}
</style>