实现效果

在这里插入图片描述
在这里插入图片描述

  1. 区域裁剪
  2. hover高亮
  3. 点击选中(高亮当前,置灰其他,飞向当前,展示html信息)
  4. 信息跟随视角
  5. 进行hook封装

非常简单的demo,抛砖引玉

区域裁剪

  1. 选择裁剪的区域
  2. 设置裁剪区域
/**
 * @description 添加四川区域
 * @param {Cesium.Viewer} viewer
 */
function addSCRegion(viewer: Cesium.Viewer) {
  viewer.scene.skyAtmosphere!.show = false; // 大气
  const coors = SC.features[0].geometry.coordinates[0];
  const geojson = turf.polygon(coors);
  const simplified = turf.simplify(geojson, {
    tolerance: 0.05, // 简化容差,值越小,越精确
    highQuality: true, // 是否花费更多时间使用其他算法创建更高质量的简化
  });
  // 根据简化后的几何体创建裁剪区域
  const areas = new Cesium.ClippingPolygon({
    positions: Cesium.Cartesian3.fromDegreesArray(
      simplified.geometry.coordinates[0].flat()
    ),
  });
  // 设置裁剪区域
  viewer.scene.globe.clippingPolygons = new Cesium.ClippingPolygonCollection({
    polygons: [areas],
    inverse: true,
  });

  // 设置30度向下视角 (pitch = -30度)
  const pitch = (-40 * Math.PI) / 180; // 转换为弧度
  viewer.camera.setView({
    destination: Cesium.Cartesian3.fromDegrees(103.06, 22.45, 600000),
    orientation: {
      heading: 0,
      pitch: pitch,
      roll: 0,
    },
  });
}

注意:直接应用裁剪的区域会十分卡,因为要计算的点非常多,所以需要优化

添加数据源和实体

  1. 使用的是数据源,把box、label、value当成一个整体,操作视角时更方便
  /**
   * @description 初始化bar
   */
  const initBar = (item: any) => {
    const dataSource = new Cesium.CustomDataSource("bar");
    const boxPosition = Cesium.Cartesian3.fromDegrees(
      item.position[0],
      item.position[1],
      0
    );
    let material = Cesium.Color.BLUE.withAlpha(0.8);
    if (item.value >= 200) {
      material = Cesium.Color.RED.withAlpha(0.8);
    } else if (item.value >= 100) {
      material = Cesium.Color.YELLOW.withAlpha(0.8);
    } else if (item.value >= 60) {
      material = Cesium.Color.GREEN.withAlpha(0.8);
    } else {
      material = Cesium.Color.BLUE.withAlpha(0.8);
    }
    const box = new Cesium.Entity({
      id: "box" + item.name,
      name: "box",
      position: boxPosition,
      box: {
        dimensions: new Cesium.Cartesian3(10000, 10000, item.value * 1000),
        material: material,
        heightReference: Cesium.HeightReference.RELATIVE_TO_GROUND,
      },
      properties: item,
    });

    const labelPosition = Cesium.Cartesian3.fromDegrees(
      item.position[0],
      item.position[1],
      item.value * 1000 + 5000
    );
    const label = new Cesium.Entity({
      id: "label" + item.name,
      name: "label",
      position: labelPosition,
      label: {
        text: item.name,
        font: "20px sans-serif",
        fillColor: Cesium.Color.WHITE,
        outlineWidth: 2,
        outlineColor: Cesium.Color.BLACK,
        style: Cesium.LabelStyle.FILL_AND_OUTLINE,
        // heightReference: Cesium.HeightReference.RELATIVE_TO_GROUND,
      },
      properties: item,
    });

    const valuePosition = Cesium.Cartesian3.fromDegrees(
      item.position[0],
      item.position[1],
      item.value * 1000 + 5000
    );
    const value = new Cesium.Entity({
      id: "value" + item.name,
      name: "value",
      position: valuePosition,
      label: {
        text: String(item.value),
        font: "16px sans-serif",
        fillColor: Cesium.Color.SKYBLUE,
        outlineWidth: 2,
        outlineColor: Cesium.Color.WHITE,
        style: Cesium.LabelStyle.FILL_AND_OUTLINE,
        pixelOffset: new Cesium.Cartesian2(0, -20),
        // heightReference: Cesium.HeightReference.RELATIVE_TO_GROUND,
      },
      properties: item,
    });

    dataSource.entities.add(box);
    dataSource.entities.add(label);
    dataSource.entities.add(value);

    return dataSource;
  };

hover高亮

目前只设置了label的颜色,可以看自己的需求

  /**
   * @description 初始化hover事件,hover到要素上时,要素高亮
   */
  const initHoverEvent = () => {
    const handler = new Cesium.ScreenSpaceEventHandler(viewer.value!.canvas);
    let entity = null as null | Cesium.Entity;
    handler.setInputAction(
      (move: Cesium.ScreenSpaceEventHandler.MotionEvent) => {
        const pick = viewer.value?.scene.pick(move.endPosition);
        if (pick && pick.id) {
          // hover到另一个entity上,移除上一个entity的label
          if (entity && entity !== pick.id) {
            const entityCollection = entity.entityCollection;
            const item = entity.properties as any;
            const label = entityCollection.getById("label" + item.name);
            label!.label!.fillColor = new Cesium.ConstantProperty(
              Cesium.Color.WHITE
            );
          }
          // hover到新的entity上,给新的entity添加label
          entity = pick.id as Cesium.Entity;
          (viewer.value as any)._container.style.cursor = "pointer";
          const entityCollection = entity.entityCollection;
          const item = entity.properties as any;
          const label = entityCollection.getById("label" + item.name);
          label!.label!.fillColor = new Cesium.ConstantProperty(
            Cesium.Color.PINK
          );
          viewer.value?.scene.requestRender();
        } else {
          // 移除上一个entity的label
          (viewer.value as any)._container.style.cursor = "default";
          if (!pick && entity) {
            const entityCollection = entity.entityCollection;
            const item = entity.properties as any;
            const label = entityCollection.getById("label" + item.name);
            label!.label!.fillColor = new Cesium.ConstantProperty(
              Cesium.Color.WHITE
            );
          }
          viewer.value?.scene.requestRender();
        }
      },
      Cesium.ScreenSpaceEventType.MOUSE_MOVE
    );
  };

click选中

  1. 高亮当前选中
  2. 置灰其他
  3. 视角移动到当前选中
  4. 展示信息
  /**
   * @description 初始化点击事件,点击聚焦实体并展示信息
   */
  const initClickEvent = () => {
    let selectDataSource = null as null | Cesium.DataSource; // 选中数据源
    viewer.value?.screenSpaceEventHandler.setInputAction(
      (click: Cesium.ScreenSpaceEventHandler.PositionedEvent) => {
        const pick = viewer.value?.scene.pick(click.position);
        if (pick && pick.id) {
          const entity = pick.id as Cesium.Entity;
          const entityCollection = entity.entityCollection;
          const currentDataSource =
            entityCollection.owner as Cesium.CustomDataSource;
          // 同一数据源点击不重复处理
          if (selectDataSource === currentDataSource) {
            return;
          }
          tooltips.value.show = false;
          // 飞向选中实体
          viewer.value
            ?.flyTo(entityCollection, {
              duration: 0.5,
            })
            .then(() => {
              // 动画结束后展示信息
              currentDataSource?.entities.values.forEach((entity) => {
                if (entity.name === "box") {
                  activeEntity.value = entity;
                  updatePosition(entity);
                  tooltips.value.info = entity.properties?.getValue();
                  tooltips.value.show = true;
                }
              });
            });

          selectDataSource = currentDataSource;

          // 处理选中和未选中的透明度
          dataSourceList?.forEach((dataSource) => {
            dataSource?.entities.values.forEach((entity) => {
              if (entity.name === "box") {
                if (dataSource === currentDataSource) {
                  // 高亮选中
                  const material = entity.box!
                    .material as Cesium.ColorMaterialProperty;
                  const color = material.color as Cesium.ConstantProperty;
                  color.setValue(Cesium.Color.fromAlpha(color.getValue(), 0.8));
                } else {
                  // 其他置灰
                  const material = entity.box!
                    .material as Cesium.ColorMaterialProperty;
                  const color = material.color as Cesium.ConstantProperty;
                  color.setValue(Cesium.Color.fromAlpha(color.getValue(), 0.1));
                }
              }
            });
          });
        } else {
          // 点击空白区域恢复所有实体透明度
          if (selectDataSource) {
            activeEntity.value = null;
            tooltips.value.show = false;
            dataSourceList?.forEach((dataSource) => {
              dataSource?.entities.values.forEach((entity) => {
                if (entity.name === "box") {
                  const material = entity.box!
                    .material as Cesium.ColorMaterialProperty;
                  const color = material.color as Cesium.ConstantProperty;
                  color.setValue(Cesium.Color.fromAlpha(color.getValue(), 0.8));
                }
              });
            });
            selectDataSource = null;
          }
        }
        // 统一在最后请求渲染,避免多次触发
        viewer.value?.scene.requestRender();
        // 解决点击空白区域后,会有几率box为空的问题
        setTimeout(() => {
          viewer.value?.scene.requestRender();
        }, 100);
      },
      Cesium.ScreenSpaceEventType.LEFT_CLICK
    );
  };

画布移动更新信息位置

主要就是监听屏幕,进行坐标转化

  1. box的笛卡尔坐标转经纬度(因为box的position高度是0,直接使用将会在柱状图底下并与box有重叠,我想要在柱状图右上方)
  2. 将经纬度右移0.1,避免和box重叠
  3. 经纬度+高度*0.9倍,展示在box的右上方,重新获取笛卡尔坐标
  4. 将笛卡尔坐标转屏幕坐标
  const initRenderListener = () => {
    // 监听场景渲染事件,实时更新坐标
    viewer.value?.scene.postRender.addEventListener(addRenderEventListener);
  };

  /**
   * @description 添加屏幕事件监听
   */
  function addRenderEventListener() {
    if (activeEntity.value && tooltips.value.show) {
      updatePosition(activeEntity.value);
    }
  }

  /**
   * @description 更新tooltips位置
   * @param {Cesium.Entity} entity
   */
  function updatePosition(entity: Cesium.Entity) {
    // 获取box的笛卡尔坐标
    const position = entity.position?.getValue();
    // 将笛卡尔坐标转换为经纬度坐标
    let p1 = getDegreesFromCartesian3(position!);
    p1[0] = p1[0] + 0.1; // 右偏移0.1度
    // 经纬度坐标转换为笛卡尔坐标,加了高度
    let p2 = getCartesian3FromDegrees(
      p1!,
      entity.properties?.value.getValue() * 1000 * 0.9
    ); // 高度在0.9倍位置
    // 将笛卡尔坐标转换为屏幕坐标
    const p = getScreenPositionFromCartesian3(p2!, viewer.value!);
    tooltips.value.x = p!.x;
    tooltips.value.y = p!.y;
  }

在线预览

预览地址
仓库地址

Logo

欢迎大家加入成都城市开发者社区,“和我在成都的街头走一走”,让我们一起携手,汇聚IT技术潮流,共建社区文明生态!

更多推荐