在Cesium上加载自定义的Dom元素

使用技术:

Vue + Cesium

产品需求:

需要在地球上指定经纬度展示项目数据,展示内容包含项目进度项目类型项目名称项目负责人。点击不同的信息,展示不同的弹窗。并且地球视角转动,展示地球上展示的数据也要随之转动。

UI设计图:


关于文字:

Cesium提供的label只能实现简单的文字加载,支持修改文字大小、边框、背景等

关于Icon:

Cesium提供的billboard可以加载指定图片,对于项目类型这种可枚举的字段倒是可以通过if判断来加载指定类型的图片,但是类似于项目进度这种可选值数量巨大的字段,总不至于要切100张图片吧 🤯

开发思路:

经过度娘的一番搜索,发现大部分思路都是先写自定义的<div>标签,然后使用绝对定位,给定位到指定经纬度的屏幕坐标上,当地球发生视角变化时,重新计算定位位置。

这种方法当数据量小的时候还好说,需要展示的数据增多,获取最新位置的计算量也会随之增长。😟

🤔 实现思路:

因为项目中有将Dom元素保存为本地图片的需求和实现方式。所以就想到了,那么可以将自定义的<div>标签转成image,然后Cesium通过billboard直接把图片加载到地球上么?

经过一番尝试,证明这个思路是可行的。


Cesium加载图片

引入自定义Vue组件,初始化所需数据,并挂载到Dom树上

js 复制代码
import ProjectProgress from './ProjectProgress.vue'
const ProjectProgressConstructor = Vue.extend(ProjectProgress);
const ProjectProgressDom = new ProjectProgressConstructor({
  data: { progress: progress }
}).$mount();
document.body.appendChild(ProjectProgressDom.$el);

将挂载在Dom树上的元素转换为图片,并清除元素

js 复制代码
const progressImage = await domConvertToImage(ProjectProgressDom.$el); // 进度组件Image
ProjectProgressDom.$el.remove(); // 将使用完成的DOM元素清除

Cesium加载billboard

js 复制代码
app.$Viewer.entities.add({
  name: "mapPorjectProgressElement",
  useful: item,
  position,
  billboard: {
    image: progressImage,
    width: 26, // 宽度(以像素为单位)
    height: 26, // 高度(以像素为单位)
    verticalOrigin: Cesium.VerticalOrigin.BOTTOM, // 相对于坐标的垂直位置
    horizontalOrigin: Cesium.HorizontalOrigin.CENTER, // 相对于坐标的水平位置
    pixelOffset: new Cesium.Cartesian2(0, -26) // 该属性指定标签在屏幕空间中距此标签原点的像素偏移量
  }
});

Dom元素转image

使用了html2canvas依赖

js 复制代码
import html2canvas from 'html2canvas'
/**
 * 将DOM元素转换为图片
 * @param {DOM} dom
 * @returns {String} ImageUrl
 */
export async function domConvertToImage(dom) {
  let saveUrl
  await html2canvas(dom, {
    backgroundColor: null // 设置图片背景为透明
  })
    .then((canvas) => {
      saveUrl = canvas.toDataURL('image/png')
    })
    .catch((err) => {
      console.error('组件转换图片失败:', err)
    })
  return saveUrl
}

实现效果图

🧐 存在问题

当然这种方式也有缺点,因为是将元素转换为image加载的,所有无法实现加载数据的动画效果鼠标交互效果

实际开发,根据需求的情况择优选择。


完整代码

自定组件

进度百分比的圆环使用了element的组件,也可以自己实现

html 复制代码
<template>
  <div class="project_progress_main_content">
    <el-progress
      type="circle"
      :percentage="progress"
      :stroke-width="3"
      color="#FCB718"
      define-back-color="rgba(255, 255, 255, 0.2)"
      :width="26"
      :show-text="false"
    />
    <div class="progress_number">
      {{ progress }}
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      progress: 0
    }
  }
}
</script>

<style lang="scss" scoped>
.project_progress_main_content {
  position: fixed;
  top: -999px;
  left: -999px;
  width: 26px;
  height: 26px;

  .progress_number {
    position: absolute;
    top: 3px;
    left: 3px;
    width: 20px;
    height: 20px;
    background: rgba(0, 0, 0, 0.4);
    border-radius: 50%;

    font-family: D-DIN;
    font-size: 12px;
    font-weight: bold;
    line-height: 10px;
    color: #ffffff;
    text-align: center;
    line-height: 20px;
  }
}
</style>

Cesium加载元素

因为项目的每个属性都需要单独的点击交互,所以把四个信息拆成独立的元素。

如果不需要多个点击事件,也可把所有信息都写到自定义的Vue组件中

js 复制代码
import Vue from "vue";
import app from "@/main";

// 绘制项目打卡icon
export function drawPorjectRecordIcon(projectRecordList) {
  projectRecordList.forEach(async (item) => {
    const position = Cesium.Cartesian3.fromDegrees(
      parseFloat(item.longitude),
      parseFloat(item.latitude),
      0
    ); // 项目坐标

    /* ---------------------------------- 绘制项目Icon ----------------------------------- */
    const image = ""; // 项目类别图标 --自定义补充
    app.$Viewer.entities.add({
      name: "mapPorjectRecordIconElement",
      useful: item,
      position,
      billboard: {
        image,
        show: true,
        width: 24, // 宽度(以像素为单位)
        height: 30, // 高度(以像素为单位)
        verticalOrigin: Cesium.VerticalOrigin.BOTTOM, // 相对于坐标的垂直位置
        horizontalOrigin: Cesium.HorizontalOrigin.CENTER // 相对于坐标的水平位置
      }
    });

    /* ---------------------------------- 绘制进度组件 ----------------------------------- */
    const ProjectProgressConstructor = Vue.extend(ProjectProgress);
    const ProjectProgressDom = new ProjectProgressConstructor({
      data: { progress: item.progress }
    }).$mount();
    document.body.appendChild(ProjectProgressDom.$el);
    const progressImage = await domConvertToImage(ProjectProgressDom.$el); // 进度组件Image
    ProjectProgressDom.$el.remove(); // 将使用完成的DOM元素清除

    const progressBillboard = {
      image: progressImage,
      width: 26, // 宽度(以像素为单位)
      height: 26, // 高度(以像素为单位)
      verticalOrigin: Cesium.VerticalOrigin.BOTTOM, // 相对于坐标的垂直位置
      horizontalOrigin: Cesium.HorizontalOrigin.CENTER, // 相对于坐标的水平位置
      pixelOffset: new Cesium.Cartesian2(0, -26) // 该属性指定标签在屏幕空间中距此标签原点的像素偏移量
    };
    app.$Viewer.entities.add({
      name: "mapPorjectProgressElement",
      useful: item,
      position,
      billboard: progressBillboard
    });

    /* ---------------------------------- 绘制人员信息 ----------------------------------- */
    const userLabel = {
      text: item.responsibleName,
      font: "14px Source Han Sans CN", // 字体样式
      fillColor: Cesium.Color.WHITE, // 字体颜色
      backgroundColor: Cesium.Color.BLACK.withAlpha(0.4), // 背景颜色
      showBackground: true, // 是否显示背景颜色
      outlineWidth: 20, // 文字外边框宽度
      verticalOrigin: Cesium.VerticalOrigin.BOTTOM, // 相对于坐标的水平位置
      horizontalOrigin: Cesium.HorizontalOrigin.LEFT, // 相对于坐标的垂直位置
      pixelOffset: new Cesium.Cartesian2(20, -22) // 该属性指定标签在屏幕空间中距此标签原点的像素偏移量
    };
    app.$Viewer.entities.add({
      name: "mapProjectResponsibleElement",
      useful: item,
      position,
      label: userLabel
    });

    /* ---------------------------------- 绘制项目名称 ----------------------------------- */
    app.$Viewer.entities.add({
      name: "mapProjectNameElement",
      useful: item,
      position,
      label: {
        text: item.projectName,
        font: "16px Source Han Sans CN", // 字体样式
        style: Cesium.LabelStyle.FILL_AND_OUTLINE, // 文字描边
        fillColor: Cesium.Color.WHITE, // 字体颜色
        outlineWidth: 20, // 文字外边框宽度
        outlineColor: Cesium.Color.BLACK.withAlpha(0.6), // 文字外边框颜色
        verticalOrigin: Cesium.VerticalOrigin.TOP, // 相对于坐标的水平位置
        horizontalOrigin: Cesium.HorizontalOrigin.LEFT, // 相对于坐标的垂直位置
        pixelOffset: new Cesium.Cartesian2(20, -30) // 该属性指定标签在屏幕空间中距此标签原点的像素偏移量
      }
    });
  });

  /* ------------------------------ 项目点击事件 ------------------------------ */
  const mapPorjectRecordHandler = new Cesium.ScreenSpaceEventHandler(
    app.$Viewer.scene.canvas
  );
  mapPorjectRecordHandler.setInputAction((click) => {
    var pick = app.$Viewer.scene.pick(click.position);
    if (pick && pick.id && pick.id.name === "mapPorjectRecordIconElement") {
      console.log("点击了项目类别图标,项目信息为:", pick.id.useful);
    }
    if (pick && pick.id && pick.id.name === "mapPorjectProgressElement") {
      console.log("点击了绘制进度组件,项目信息为:", pick.id.useful);
    }
    if (pick && pick.id && pick.id.name === "mapProjectResponsibleElement") {
      console.log("点击了项目人员信息,项目信息为:", pick.id.useful);
    }
    if (pick && pick.id && pick.id.name === "mapProjectNameElement") {
      console.log("点击了项目名称,项目信息为:", pick.id.useful);
    }
  }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
}
相关推荐
qbbmnnnnnn1 天前
【WebGis开发 - Cesium】三维可视化项目教程---初始化场景
gis·三维可视化·cesium·webgis
qbbmnnnnnn2 天前
【WebGis开发 - Cesium】如何确保Cesium场景加载完毕
前端·javascript·vue.js·gis·cesium·webgis·三维可视化开发
汪洪墩6 天前
循环生成管道线PolylineVolumeEntity,生成一个添加一个
vue.js·3d·地图·cesium·webgis
用你的胜利博我一笑吧14 天前
supermap iclient3d for cesium中entity使用
前端·javascript·vue.js·3d·cesium·supermap
敲敲敲敲暴你脑袋15 天前
【cesium】绘制贴地线面和自定义Primitive
javascript·webgl·cesium
用你的胜利博我一笑吧19 天前
vue3+ts+supermap iclient3d for cesium功能集合
前端·javascript·vue.js·3d·cesium·supermap
涛涛英语学不进去1 个月前
3D Tiles的4x4的仿射变换矩阵
线性代数·3d·矩阵·cesium·3d tiles
GIS瞧葩菜1 个月前
Cesium.ScreenSpaceEventHandler是 CesiumJS 中用于处理屏幕空间事件(如鼠标点击、移动、滚轮等)的工具
前端·javascript·cesium
BJ-Giser1 个月前
cesium 水波纹扩散圆材质
前端·javascript·cesium
激动的兔子1 个月前
使用Vue创建cesium项目模版该如何选择?
vue.js·cesium