前言

在Cesium中没有类似mapbox中的气泡(popup)弹框,在react-hooks中可以封装一个这样的类组件,思路参考来源,在此基础上进行了改进,实现为外部模型添加popup气泡框,在旋转缩放时仍能保持原位置,不会偏移。
在这里插入图片描述


基本思路

  • 为地图绑定鼠标事件,当拾取到模型时,触发响应函数
  • 将鼠标点击位置的屏幕坐标转为笛卡尔坐标,与拾取到的目标模型要素的属性一起传递给popup组件,创建构造函数,触发构造popup类
  • 在popup类初始化时,创建div,显示气泡框,同时在该类内部添加每一帧的监听函数,将传递来的笛卡尔坐标转为屏幕坐标,重新设置气泡框位置
  • 创建新的气泡框或者关闭气泡框时,移除div元素,解绑监听事件

代码

贴两部分的代码,popup工具类的声明和在主程序中的使用。

import * as Cesium from "cesium";
import './index.css'

interface InfoProrety {
  name: string,
  viewer: Cesium.Viewer,
  properties: PropertyObj,
  geometry: Cesium.Cartesian3
}

interface PropertyObj {
  [key: string]: string | number;
  // important: string;
  // other: string;
}

class Popup {
  id: number
  viewer: Cesium.Viewer
  geometry: Cesium.Cartesian3
  ctn: HTMLDivElement
  eventListener: any
  constructor(info: InfoProrety) {

    console.log(info)
    this.id = 0;
    // 展示新的popup时关闭前一个popup
    if (document.getElementsByClassName("bx-popup-ctn").length > 0) {
      console.log(document.getElementsByClassName("bx-popup-ctn"))
      document.getElementsByClassName("bx-popup-ctn")[0].remove()
    }
    this.viewer = info.viewer; // 弹窗创建的viewer
    this.geometry = info.geometry; // 弹窗挂载的位置
    this.ctn = document.createElement("div"); // 创建一个div

    // classList为html5的新语法,返回元素类名
    this.ctn.classList.add("bx-popup-ctn");

    this.viewer.container.append(this.ctn);  // Cesium.Viewer.container 获取当前viewer的父容器(cesiumContainer)
    this.ctn.innerHTML = this.createHtml(info.name, info.properties); //创建Html
    this.render(this.geometry);

    // 添加监听拖动重新渲染位置 viewer.clock.onTick时刻监听
    this.eventListener = this.viewer.clock.onTick.addEventListener(clock => {
      this.render(this.geometry);
    });

    // 关闭按钮绑定关闭事件
    document.getElementsByClassName(
      "popup-close-button"
      // @ts-ignore
    )[0].onclick = () => {
      this.close();
    };
  }

  
  //渲染位置
  render(geometry: Cesium.Cartesian3) {

    const position = Cesium.SceneTransforms.wgs84ToWindowCoordinates(
      this.viewer.scene,
      geometry
    );
    if (position) {
      this.ctn.style.left = position.x - this.ctn.offsetWidth / 2 + "px";
      this.ctn.style.top = position.y - this.ctn.offsetHeight - 30 + "px";
    }

  }
  createHtml(header: string, content: PropertyObj) {
    var html =
      '<div class="bx-popup-header-ctn">' +
      header +
      '<span class="popup-close-button" >' + "❌" + '</i></span>' +
      "</div>" +
      '<div class="bx-popup-content-ctn" >' +
      '<div class="bx-popup-content" >' +
      this.createTable(content) +
      "</div>" +
      "</div>" +
      '<div class="bx-popup-tip-container" >' +
      '<div class="bx-popup-tip" >' +
      "</div>" +
      "</div>";
    return html;
  }
  createTable(content: PropertyObj) {
    let html = '<table class="table-popup">';
    for (let key in content) {
      html += `<tr><td class="title-popup">${key}</td>
           <td class="value-popup">${content[key]}</td></tr>`;
    }
    html += "</table>";
    return html;
  }
  close() {
    this.ctn.remove();
    this.viewer.clock.onTick.removeEventListener(this.eventListener);
  }
}
export default Popup;

引用部分,只保留相关部分。

  const handleChange = (value: string, option: any) => {
    console.log(`selected ${value}`, option);

    const handler = new Cesium.ScreenSpaceEventHandler(viewer?.scene.canvas)
    if (value === 'popup') {
       // 添加鼠标事件:右键
    handler.setInputAction(function (movement:any) { // 鼠标点击是PositionedEvent类型 :{ position }
     
      // pick方法返回primitive对象,当对象为3d tiles时,返回Cesium3DTileFeature对象
      const feature = viewer!.scene.pick(movement.position);
      const point = new Cesium.Cartesian2(
        movement.position.x,
        movement.position.y
      )
      if (feature instanceof Cesium.Cesium3DTileFeature) {
        displaySelectFeature(feature, point)
      }
    }, Cesium.ScreenSpaceEventType.RIGHT_CLICK);

    }
  };

  const displaySelectFeature = (feature: Cesium.Cesium3DTileFeature, point: Cesium.Cartesian2) => {
    if (!Cesium.defined(feature)) {
      return;
    }
    // 获取当前鼠标位置三维坐标的一般流程(无模型):
    // 1:通过camera的getPickRay,将当前的屏幕坐标转为ray(射线)=> viewer.camera.getPickRay(windowCoordinates);
    // 2:找出ray和地形的交点,得出三维世界坐标 scene.globe.pick(ray, scene);
	
	// 获取当前鼠标位置三维坐标的一般流程(有模型):
	// viewer!.scene.pickPosition(Cartesian2)
    let propertyObj: PropertyObj = {} // 存储到对象中

    const propertyIds = feature.getPropertyIds()
    propertyIds.length && propertyIds.forEach(item => {
      propertyObj[item] = feature.getProperty(item)
    });
    // @ts-ignore
    PopupRef.current = new Popup({
      name: 'xxx',
      viewer: viewer!,
      properties: propertyObj,
      geometry:viewer!.scene.pickPosition(point)
    });
  }

总结

借助网上的思路实现的一个popup功能,核心的部分其实两篇来源中都已经实现了,修正了下存在的问题。

Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐