Vue3+Cesium实现地图标点画出点、线、面,并获取其坐标

实现功能

  1. 通过标点来画点、线、面,画好的点线面可以通过拖动端点改变位置,并获取其坐标
  2. 地图文字图层的隐藏展示
  3. 地图下钻定位
  4. 全屏
  5. 点和线的动画
  6. 根据cesium-navigation-es6实现类型高德地图的视角控制器
    在这里插入图片描述
    在这里插入图片描述

初始化Cesium

我封装为了hooks,方便使用。导出的对象除了viewerinit,其他都是工具函数

/**
 * @description 初始化cesium
 * @param {CesiumConfig} [config] 聚焦点/缩放/飞行高度
 * @return {*}
 */
export const initCesium = (config?: CesiumConfig) => {
  const viewer = shallowRef<Cesium.Viewer>();//浅层响应式
  const roadMap = shallowRef<Cesium.ImageryLayer>();//浅层响应式
  const hasRoadMap = ref(true);
  const defaultConfig = {
    lookAt: [108.904967, 34.313311, 500_0000], //首次聚焦位置
    max: 500_0000, //最大缩放
    min: 200, //最小缩放
    flyHeight: 8000, //飞行高度
    ...config,
  };

  const init = async () => {
    viewer.value = new Cesium.Viewer("cesium", {
      // 是否展示查询按钮 如果设置为false,将不会创建右上角查询(放大镜)按钮。
      geocoder: false,
      //查看器的显示模式 如果设置为false,将不会创建右上角投影方式控件(显示二三维切换按钮)。
      sceneModePicker: false,
      // 是否展示图层选择器 如果设置为false,将不会创建右上角图层按钮。
      baseLayerPicker: false,
      // 是否显示帮助 如果设置为false,则不会创建右上角帮助(问号)按钮。
      navigationHelpButton: false,
      // 是否展示home控件 如果设置为false,将不会创建右上角主页(房子)按钮。
      homeButton: false,
      // 是否展示动画控件 如果设置为false,将不会创建左下角动画小部件。
      animation: false,
      // 是否展示全屏按钮 如果设置为false,将不会创建右下角全屏按钮。
      fullscreenButton: false,
      // 是否展示时间轴 如果设置为false,则不会创建正下方时间轴小部件。
      timeline: false,
      // 是否展示场景控制 为 true 时,每个几何实例将仅以3D渲染以节省GPU内存。
      scene3DOnly: true,
      // 是否点击要素之后展示信息
      infoBox: false,
      // // 是否展示场景的底部
      // shadows: true,
      // 启用请求渲染模式
      requestRenderMode: true,
      // 初始场景模式 1 2D模式 2 2D循环模式 3 3D模式  Cesium.SceneMode
      sceneMode: Cesium.SceneMode.SCENE3D,
      // 点击选中元素
      // selectionIndicator: false,
      // 地形
      // terrainProvider: await Cesium.createWorldTerrainAsync({
      //   requestVertexNormals: true,
      //   requestWaterMask: true
      // }),

      baseLayer: new Cesium.ImageryLayer(
        new Cesium.UrlTemplateImageryProvider({
          url: "https://webst02.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}",
        })
      ),
    });
    // 最小缩放高度(米)
    viewer.value.scene.screenSpaceCameraController.minimumZoomDistance =
      defaultConfig.min;
    // 最大缩放高度(米)
    viewer.value.scene.screenSpaceCameraController.maximumZoomDistance =
      defaultConfig.max;
    // 去除版权样式
    (viewer.value.cesiumWidget.creditContainer as HTMLElement).style.display =
      "none"; //去除版权样式
    // 再加上高德影像注记地图
    roadMap.value = viewer.value.imageryLayers.addImageryProvider(
      new Cesium.UrlTemplateImageryProvider({
        url: "http://webst02.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scale=1&style=8",
      })
    );
    // 获取比例 渲染清晰 抗锯齿
    viewer.value.resolutionScale = window.devicePixelRatio;
    viewer.value.scene.postProcessStages.fxaa.enabled = true;
    //默认视角定位中国上空
    viewer.value.camera.setView({
      destination: Cesium.Cartesian3.fromDegrees(
        defaultConfig.lookAt[0],
        defaultConfig.lookAt[1],
        defaultConfig.lookAt[2]
      ),
      orientation: {
        heading: 0,
        pitch: -Cesium.Math.PI / 2,
        roll: 0,
      },
    });
  };

  /**
   * @description 放大缩小地图
   */
  const zoomFn = (type: "zoomIn" | "zoomOut", step = 200) => {
    debugger;
    if (type === "zoomIn") {
      viewer.value?.camera.zoomIn(step);
    } else {
      viewer.value?.camera.zoomOut(step);
    }
  };

  /**
   * @description 调整地图的方向为正北
   */
  const adjustNorthUp = () => {
    // 如果地图方向已经为正北方向,则直接返回
    if (
      viewer.value?.scene.camera.heading === 6.283185307179586 &&
      viewer.value?.scene.camera.pitch === -1.5707963267948966 &&
      viewer.value?.scene.camera.roll === 0
    ) {
      return;
    }
    // 旋转到正北方向
    const centerResult = viewer.value!.camera.pickEllipsoid(
      new Cesium.Cartesian2(
        viewer.value!.canvas.clientWidth / 2,
        viewer.value!.canvas.clientHeight / 2
      )
    );
    const curPosition = Cesium.Ellipsoid.WGS84.cartesianToCartographic(
      centerResult!
    );
    const curLongitude = (curPosition.longitude * 180) / Math.PI;
    const curLatitude = (curPosition.latitude * 180) / Math.PI;
    viewer.value?.camera.flyTo({
      destination: Cesium.Cartesian3.fromDegrees(
        curLongitude,
        curLatitude,
        defaultConfig.flyHeight
      ),
      orientation: {
        heading: 0, //偏航角 摇头
        pitch: -Cesium.Math.PI / 2, //俯仰角 点头
        roll: 0, //翻滚角 歪头
      },
      duration: 2,
    });
  };

  /**
   * @description 是否加载道路图
   */
  const loadRoadNetwork = () => {
    if (hasRoadMap.value) {
      viewer.value?.imageryLayers.remove(roadMap.value!, true);
      hasRoadMap.value = false;
    } else {
      roadMap.value = viewer.value!.imageryLayers.addImageryProvider(
        new Cesium.UrlTemplateImageryProvider({
          url: "http://webst02.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scale=1&style=8",
        })
      );
      hasRoadMap.value = true;
    }
  };

  /**
   * @description 根据经纬度,移动视角
   */
  const flyTo = (longitude: number, latitude: number) => {
    viewer.value?.camera.flyTo({
      destination: Cesium.Cartesian3.fromDegrees(
        longitude,
        latitude,
        defaultConfig.flyHeight
      ),
      orientation: {
        heading: 0, //偏航角 摇头
        pitch: -Cesium.Math.PI / 2, //俯仰角 点头
        roll: 0, //翻滚角 歪头
      },
    });
  };

  const destroyed = () => {
    viewer.value?.entities.removeAll();
    viewer.value?.destroy();
    viewer.value = undefined;
  };

  return {
    viewer, //视图
    zoomFn,
    adjustNorthUp,
    loadRoadNetwork,
    flyTo,
    destroyed,
    init, //初始化
  };
};

通过鼠标事件来画点、线、面并获取坐标,逻辑相对复杂

  • 点:点的话比较简单,单个单个的画出来就行
  • 线:连续的,需要把点击的数据存储下来,顺序
  • 面:同线,顺序
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
/**
 * @description 画图获取坐标
 * @param {(Ref<Cesium.Viewer | undefined>)} viewer
 * @return {*}
 */
export const useDrawAndGetData = (viewer: Ref<Cesium.Viewer | undefined>) => {
  const data = ref<Array<CesiumDrawGetData>>([]); //存储坐标数据
  const index = ref<number>(0); //当前点的索引
  const pickEntity = shallowRef<Cesium.Entity>(); //当前点击的实体
  const alpha = 0.3;
  const labelAlpha = 0.5;

  // 多边形
  const polygonHierarchy = new Cesium.PolygonHierarchy();
  const polygonEntity = new Cesium.Entity({
    name: "polygon",
    isStatic: false,
    polygon: {
      hierarchy: new Cesium.CallbackProperty(function () {
        return polygonHierarchy;
      }, false),// 必须使用回调函数 否则画不出来
      outline: true,
      outlineColor: Cesium.Color.RED,
      outlineWidth: 10,
      fill: true,
      material: Cesium.Color.RED.withAlpha(alpha),
    },
  } as unknown as MyConstructorOptions);

  // 线段
  let polylinePositions: Cesium.Cartesian3[] = [];
  const polylineEntity = new Cesium.Entity({
    name: "polyline",
    isStatic: false,
    polyline: {
      positions: new Cesium.CallbackProperty(function () {
        return polylinePositions;
      }, false),// 必须使用回调函数 否则画不出来
      width: 20,
      material: new Cesium.PolylineArrowMaterialProperty(Cesium.Color.WHITE),
    },
  } as unknown as MyConstructorOptions);

  /**
   * @description 初始化画图事件
   * @param {CesiumDataType} type
   * @return {*}
   */
  const initEvent = (type: CesiumDataType) => {
    if (!viewer.value) {
      return;
    }
    if (type === CesiumDataType.Polyline) {
      viewer.value.entities.add(polylineEntity);
    }
    if (type === CesiumDataType.Polygon) {
      viewer.value.entities.add(polygonEntity);
    }
    // 禁用双击事件
    viewer.value.screenSpaceEventHandler.removeInputAction(
      Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK
    );
    // 监听地图点击事件
    viewer.value.screenSpaceEventHandler.setInputAction(
      (click: Cesium.ScreenSpaceEventHandler.PositionedEvent) => {
        const pick = viewer.value?.scene.pick(click.position);
        if (!pick || pick.id.isStatic) {
          // 获取点击位置笛卡尔坐标
          const cartesian = viewer.value?.camera.pickEllipsoid(
            click.position,
            viewer.value?.scene.globe.ellipsoid
          );
          // 获取点击位置的经纬度坐标
          const cartographic = Cesium.Cartographic.fromCartesian(cartesian!);
          const longitude = Number(
            Cesium.Math.toDegrees(cartographic.longitude).toFixed(6)
          );
          const latitude = Number(
            Cesium.Math.toDegrees(cartographic.latitude).toFixed(6)
          );
          // 画点
          if (type === CesiumDataType.Point) {
            // 画单个点 重绘
            index.value = 0;
            data.value = [
              { degrees: [longitude, latitude], index: index.value },
            ];
            removeNonStaticEntites();
            // 画点
            drawPointFn([longitude, latitude], index.value, 16);
            viewer.value?.scene.requestRender();
          } else {
            // 画线和面的端点 连续
            data.value.push({
              degrees: [longitude, latitude],
              index: index.value,
            });
            drawPointFn([longitude, latitude], index.value);
          }
          // 画线
          if (type === CesiumDataType.Polyline) {
            polylinePositions.push(cartesian!.clone());
          }
          // 画面
          if (type === CesiumDataType.Polygon) {
            polygonHierarchy.positions.push(cartesian!.clone());
          }
          index.value += 1;
        }
      },
      Cesium.ScreenSpaceEventType.LEFT_CLICK
    );

    // 鼠标左键按下
    viewer.value.screenSpaceEventHandler.setInputAction(
      (click: Cesium.ScreenSpaceEventHandler.PositionedEvent) => {
        const pick = viewer.value?.scene.pick(click.position);
        if (
          pick &&
          pick.id &&
          pick.id.name !== "polygon" &&
          pick.id.name !== "polyline" &&
          pick.id.name !== "areaLine" &&
          pick.id.name !== "area" &&
          pick.id.name !== undefined
        ) {
          // 点击点
          pickEntity.value = pick.id;
          // 禁用拖动地图
          viewer.value!.scene.screenSpaceCameraController.enableRotate = false;
          viewer.value!.scene.screenSpaceCameraController.enableTranslate =
            false;
          viewer.value!.scene.screenSpaceCameraController.enableZoom = false;
        } else {
          pickEntity.value = undefined;
        }
      },
      Cesium.ScreenSpaceEventType.LEFT_DOWN
    );

    // 鼠标左键放开
    viewer.value.screenSpaceEventHandler.setInputAction(() => {
      pickEntity.value = undefined;
      viewer.value!.scene.screenSpaceCameraController.enableRotate = true;
      viewer.value!.scene.screenSpaceCameraController.enableTranslate = true;
      viewer.value!.scene.screenSpaceCameraController.enableZoom = true;
      // 鼠标抬起重新渲染 非常重要
      viewer.value?.scene.requestRender();
    }, Cesium.ScreenSpaceEventType.LEFT_UP);

    // 鼠标移入事件
    viewer.value.screenSpaceEventHandler.setInputAction(
      (move: Cesium.ScreenSpaceEventHandler.MotionEvent) => {
        // 获取地图上的点位实体(entity)坐标
        const pick = viewer.value?.scene.pick(move.endPosition);
        // 如果pick不是undefined,那么就是点到点位了
        if (
          pick &&
          pick.id &&
          pick.id.name !== "polygon" &&
          pick.id.name !== "polyline" &&
          pick.id.name !== "areaLine" &&
          pick.id.name !== "area" &&
          pick.id.name !== undefined
        ) {
          (viewer.value as any)._container.style.cursor = "pointer";
          // 有data才展示
          if (pick.id.data) {
            showTooltip(move.endPosition.x, move.endPosition.y, pick.id.data);
          }
        } else {
          // 移除弹框
          (viewer.value as any)._container.style.cursor = "default";
          const tipsEl = document.getElementById("tips") as HTMLElement;
          if (tipsEl) {
            tipsEl.style.display = "none";
          }
        }
        if (pickEntity.value) {
          // 获取点位实体的坐标
          const index = Number(pickEntity.value.id);
          const cartesian = getCartesian(move.endPosition);
          const degrees = getDegreesFromCartesian3(cartesian!);

          // 修改坐标
          data.value[index].degrees = degrees;
          // 更新点位置
          const point = viewer.value!.entities.getById(String(index))!;
          (point.position as any) = getCartesian3FromDegrees(degrees, 10);
          (point.label!.text as any) = `${index + 1}--[${degrees[0]},${
            degrees[1]
          }]`;
          // 更新线的位置
          if (type === CesiumDataType.Polyline) {
            polylinePositions[index] = cartesian!;
          }
          // 更新面的位置
          if (type === CesiumDataType.Polygon) {
            polygonHierarchy.positions[index] = cartesian!;
          }

          // 鼠标抬起重新渲染 非常重要
          viewer.value?.scene.requestRender();
        }
      },
      Cesium.ScreenSpaceEventType.MOUSE_MOVE
    );
  };

  /**
   * @description 画点
   * @param {number[]} position 经纬度
   * @param {number} index 索引
   * @param {*} [data] 信息
   */
  const drawPointFn = (
    position: number[],
    index: number,
    size = 6,
    data?: any
  ) => {
    // 添加点
    viewer.value?.entities.add({
      id: String(index),
      name: "point",
      data: data,
      position: Cesium.Cartesian3.fromDegrees(position[0], position[1], 10),
      isStatic: false,
      point: {
        pixelSize: size,
        color: Cesium.Color.RED,
        outlineColor: Cesium.Color.WHITE,
        outlineWidth: 2,
      },
      label: {
        text: `${index + 1}--[${position[0]},${position[1]}]`,
        font: "12px sans-serif",
        pixelOffset: new Cesium.Cartesian2(0, -20),
        fillColor: Cesium.Color.RED,
        backgroundColor: Cesium.Color.WHITE.withAlpha(labelAlpha),
        showBackground: true,
      },
    } as unknown as MyConstructorOptions);
  };

  /**
   * @description 获取笛卡尔坐标
   */
  const getCartesian = (position: Cesium.Cartesian2) => {
    const cartesian = viewer.value!.camera.pickEllipsoid(
      position,
      viewer.value!.scene.globe.ellipsoid
    );
    return cartesian;
  };

  /**
   * @description 编辑数据 重画展示
   * @param {CesiumDrawGetData[]} oldData 渲染数据
   * @param {CesiumDataType} type 画的类型
   */
  const reDraw = (oldData: CesiumDrawGetData[], type: CesiumDataType) => {
    // 清空之前画的数据 不然会复用数据
    reset();
    oldData.forEach((item, index) => {
      // 重画 点线面
      if (type === CesiumDataType.Point) {
        drawPointFn(item.degrees, index, 16, item.data);
      }
      if (type === CesiumDataType.Polyline) {
        drawPointFn(item.degrees, index, 6, item.data);
        polylinePositions.push(getCartesian3FromDegrees(item.degrees, 10));
      }
      if (type === CesiumDataType.Polygon) {
        drawPointFn(item.degrees, index, 6, item.data);
        polygonHierarchy.positions.push(
          getCartesian3FromDegrees(item.degrees, 10)
        );
      }
    });
    data.value = oldData;
    index.value = oldData.length;
  };

  /**
   * @description 重置数据
   */
  const reset = () => {
    data.value = [];
    index.value = 0;
    polylinePositions = [];
    polygonHierarchy.positions = [];
  };

  const removeStaticEntites = () => {
    const entities = viewer.value?.entities.values.filter(
      (item: MyConstructorOptions) => item.isStatic
    );
    entities?.forEach((item: MyConstructorOptions) => {
      viewer.value?.entities.remove(item);
    });
  };

  const removeNonStaticEntites = () => {
    const entities = viewer.value?.entities.values.filter(
      (item: MyConstructorOptions) => !item.isStatic
    );
    entities?.forEach((item: MyConstructorOptions) => {
      viewer.value?.entities.remove(item);
    });
  };

  return {
    data,
    initEvent,
    reDraw,
    removeStaticEntites,
    removeNonStaticEntites,
    reset,
  };
};

数据格式

import * as Cesium from "cesium";

export enum CesiumDataType {
  Point = "point",
  Polyline = "polyline",
  Polygon = "polygon",
}

export enum CesiumFilterType {
  Plan,
  Block,
  Platform,
  Well,
  Pipeline,
  Device,
}

export interface CesiumConfig {
  lookAt?: [number, number, number];
}

export interface CesiumDrawGetData {
  degrees: number[];
  index: number;
  data?: any;
}

export interface CesiumData {
  type: CesiumFilterType;
  name: string;
  businessId?: string;
  legendUrl?: string;
  pointData: CesiumDataItem[];
}

export interface CesiumDataItem {
  lon: number;
  lat: number;
  id?: string;
  flag?: string;
  remark?: string;
}

export interface MyConstructorOptions extends Cesium.Entity {
  data?: any;
  type?: CesiumFilterType;
  isStatic?: boolean;
}
/**
 * @description 大屏根据类型获取下拉
 */
export interface ScreenOptionParams {
  type: number;
  businessName: string;
}
/**
 * @description 获取大屏数据
 */
export interface ScreenDataParams {
  type: number;
  businessId: string;
}

单独把画点、线、面做成hooks

  1. 只做绘画和hover效果,不需要太多事件和逻辑判断
  2. 专注渲染和样式
  3. 添加点和线的动画。点的动画主要时改变size,线的动画主要是改变position

给的仓库中没有这段代码的示例,使用也很简单只要符合数据格式就行

/**
 * @description 画点线面
 * @param {(Ref<Cesium.Viewer | undefined>)} viewer
 * @return {*}
 */
export const useDraw = (viewer: Ref<Cesium.Viewer | undefined>) => {
  const alpha = 0.3;
  /**
   * @description 画区域
   * @param {Cesium.PolygonHierarchy} polygonHierarchy 区域坐标
   * @param {string} type 类型
   * @param {('area' | 'line')} [drawType='area'] 展示区域类型
   * @return {*}
   */
  const drawArea = (
    polygonHierarchy: Cesium.PolygonHierarchy,
    type: number,
    drawType: "area" | "line" = "area"
  ) => {
    if (drawType === "area") {
      viewer.value?.entities.add({
        name: "area",
        type,
        isStatic: true,
        polygon: {
          hierarchy: polygonHierarchy,
          fill: true,
          material: Cesium.Color.RED.withAlpha(alpha),
        },
      } as unknown as MyConstructorOptions);
    } else {
      const data = [
        ...polygonHierarchy.positions,
        polygonHierarchy.positions[0],
      ];
      viewer.value?.entities.add({
        name: "areaLine",
        type,
        isStatic: true,
        polygon: {
          hierarchy: polygonHierarchy,
          fill: false,
          material: Cesium.Color.RED.withAlpha(0.2),
        },
        polyline: {
          positions: data,
          width: 6,
          material: Cesium.Color.RED.withAlpha(0.8),
          clampToGround: true,
        },
      } as unknown as MyConstructorOptions);
    }
  };

  /**
   * @description 画线
   * @param {Cesium.Cartesian3[]} polylinePositions 线坐标
   * @param {string} type 类型
   * @param {boolean} [animate=false] 是否动画
   * @return {*}
   */
  const drawLine = (
    polylinePositions: Cesium.Cartesian3[],
    type: number,
    info?: any,
    animate = false
  ) => {
    viewer.value?.entities.add({
      name: "polyline",
      type,
      isStatic: true,
      polyline: {
        positions: polylinePositions,
        width: 6,
        material: Cesium.Color.WHITE.withAlpha(alpha),
        clampToGround: true,
      },
    } as unknown as MyConstructorOptions);
    animate && animateLine(polylinePositions, type);
    drawLineLabel(polylinePositions, type, info);
  };

  const drawLineLabel = (
    polylinePositions: Cesium.Cartesian3[],
    type: number,
    info: any
  ) => {
    for (let i = 0; i < polylinePositions.length - 1; i++) {
      const p1 = polylinePositions[i];
      const p2 = polylinePositions[i + 1];
      const midpoint = Cesium.Cartesian3.midpoint(
        p1,
        p2,
        new Cesium.Cartesian3()
      );
      viewer.value?.entities.add({
        name: "polyline",
        type,
        position: midpoint,
        isStatic: true,
        label: {
          text: info,
          font: "12px sans-serif",
          scale: 0.8,
          // pixelOffset: new Cesium.Cartesian2(0, -16),
          fillColor: Cesium.Color.GREEN,
          backgroundColor: Cesium.Color.WHITE.withAlpha(0.7),
          showBackground: true,
          backgroundPadding: new Cesium.Cartesian2(4, 4),
        },
      } as unknown as MyConstructorOptions);
    }
  };

  /**
   * @description 画点
   * @param {Cesium.Cartesian3} position
   * @param {string} type
   * @param {{
   *       name?: string;
   *       content?: string;
   *     }} [data]
   * @param {{
   *       size?: number;
   *       img?: string;
   *       animate?: boolean;
   *       billboard?: any;
   *       label?: any;
   *       point?: any;
   *     }} [config]
   * @return {*}
   */
  const drawPoint = (
    position: Cesium.Cartesian3,
    type: number,
    data?: {
      name?: string;
      content?: string;
    },
    config?: {
      size?: number;
      img?: string;
      animate?: boolean;
      billboard?: any;
      label?: any;
      point?: any;
    }
  ) => {
    config = {
      size: 4,
      img: "",
      animate: false,
      billboard: {},
      label: {},
      point: {},
      ...config,
    };
    if (config?.img) {
      config.animate && animatePoint(position, type);
      viewer.value?.entities.add({
        position: position,
        type,
        data,
        isStatic: true,
        billboard: {
          image: config.img,
          width: config.size,
          height: config.size,
          color: Cesium.Color.RED,
          ...config.billboard,
        },
        label: {
          text: data?.name,
          font: "12px sans-serif",
          pixelOffset: new Cesium.Cartesian2(0, -16),
          fillColor: Cesium.Color.BLACK,
          backgroundColor: Cesium.Color.WHITE.withAlpha(0.7),
          showBackground: true,
          ...config.billboard,
        },
      } as unknown as MyConstructorOptions);
    } else {
      config.animate && animatePoint(position, type);
      viewer.value?.entities.add({
        position: position,
        type,
        data,
        isStatic: true,
        point: {
          pixelSize: config.size,
          color: Cesium.Color.RED,
          outlineColor: Cesium.Color.WHITE,
          outlineWidth: 2,
          ...config.point,
        },
        label: {
          text: data?.name,
          font: "12px sans-serif",
          pixelOffset: new Cesium.Cartesian2(0, -16),
          fillColor: Cesium.Color.BLACK,
          backgroundColor: Cesium.Color.WHITE.withAlpha(0.7),
          showBackground: true,
          ...config.label,
        },
      } as unknown as MyConstructorOptions);
    }
  };
  /**
   * @description 初始化鼠标移入事件
   */
  const initHoverEvent = () => {
    // 当创建图形HOOK和绘画HOOK同时存在,移动的事件会覆盖,绘画优先
    const moveFn = viewer.value?.screenSpaceEventHandler.getInputAction(
      Cesium.ScreenSpaceEventType.MOUSE_MOVE
    );
    if (moveFn) {
      return;
    }
    // 鼠标移入事件
    viewer.value?.screenSpaceEventHandler.setInputAction(
      (move: Cesium.ScreenSpaceEventHandler.MotionEvent) => {
        // 获取地图上的点位实体(entity)坐标
        const pick = viewer.value?.scene.pick(move.endPosition);
        // 如果pick不是undefined,那么就是点到点位了  忽略区域的线框
        if (
          pick &&
          pick.id &&
          pick.id.name !== "areaLine" &&
          pick.id.name !== "area" &&
          pick.id.name !== "polyline"
        ) {
          if (pick.id.data && pick.id.data.content) {
            (viewer.value as any)._container.style.cursor = "pointer";
            showTooltip(move.endPosition.x, move.endPosition.y, pick.id);
          }
        } else {
          // 移除弹框
          (viewer.value as any)._container.style.cursor = "default";
          const tipsEl = document.getElementById("tips") as HTMLElement;
          if (tipsEl) {
            tipsEl.style.display = "none";
          }
        }
      },
      Cesium.ScreenSpaceEventType.MOUSE_MOVE
    );
  };
  /**
   * @description 管道动画
   */
  const animateLine = (
    polylinePositions: Cesium.Cartesian3[],
    type: number
  ) => {
    // 定义运动时间(以秒为单位)
    const duration = 2.0;
    let index = 1;
    let startPoint = polylinePositions[index - 1];
    let endPoint = polylinePositions[index];
    let positions: Cesium.Cartesian3[] = [polylinePositions[0]];

    viewer.value?.entities.add({
      name: "polyline",
      type,
      isStatic: true,
      polyline: {
        positions: new Cesium.CallbackProperty(function () {
          return positions;
        }, false),
        width: 6,
        material: Cesium.Color.GREEN,
        clampToGround: true,
      },
    } as unknown as MyConstructorOptions);

    // 定义一个函数来计算轨迹上任意时间点的位置
    function calculatePosition(t: number) {
      // 使用简单的线性插值计算位置
      let ratio = t / duration;
      // // 解决动画到下一个时位置不准确的问题
      if (ratio > 1) {
        ratio = 1;
      }
      if (ratio >= 0.97) {
        ratio = 1;
      }
      return Cesium.Cartesian3.lerp(
        startPoint,
        endPoint,
        ratio,
        new Cesium.Cartesian3()
      );
    }
    // 使用requestAnimationFrame来更新实体位置
    let startTime = 0;
    function animate(time: number) {
      if (!startTime) {
        startTime = time;
      }
      let t = (time - startTime) / 1000; // 将时间转换为秒
      t = Number(t.toFixed(2));
      if (t > duration) {
        // 每个线段周期重置
        startTime = 0;
        index++;
        if (index > polylinePositions.length - 1) {
          index = 1;
          positions = [polylinePositions[index - 1]];
        }
        startPoint = polylinePositions[index - 1];
        endPoint = polylinePositions[index];
      }
      const position = calculatePosition(t);
      if (startTime) {
        // 重置之后 calculatePosition 函数会返回第一个点,需要跳过 否则会闪一下
        positions[index] = position.clone(); // 更新实体位置
      }
      viewer.value?.scene.requestRender();
      requestAnimationFrame(animate); // 递归调用,继续动画
    }
    requestAnimationFrame(animate); // 开始动画
  };
  /**
   * @description 点动画
   */
  const animatePoint = (position: Cesium.Cartesian3, type: number) => {
    // 定义运动时间(以秒为单位)
    const duration = 2.0;
    const size = 0;

    const p1 = viewer.value?.entities.add({
      type,
      position: position,
      isStatic: true,
      point: {
        pixelSize: size,
        color: Cesium.Color.TRANSPARENT,
        outlineColor: Cesium.Color.WHITE.withAlpha(0.3),
        outlineWidth: 2,
      },
    } as unknown as MyConstructorOptions);
    const p2 = viewer.value?.entities.add({
      type,
      position: position,
      isStatic: true,
      point: {
        pixelSize: size,
        color: Cesium.Color.TRANSPARENT,
        outlineColor: Cesium.Color.WHITE.withAlpha(0.3),
        outlineWidth: 2,
      },
    } as unknown as MyConstructorOptions);
    const p3 = viewer.value?.entities.add({
      type,
      position: position,
      isStatic: true,
      point: {
        pixelSize: size,
        color: Cesium.Color.TRANSPARENT,
        outlineColor: Cesium.Color.WHITE.withAlpha(0.3),
        outlineWidth: 2,
      },
    } as unknown as MyConstructorOptions);

    // 使用requestAnimationFrame来更新实体位置
    let startTime = 0;
    function animate(time: number) {
      if (!startTime) {
        startTime = time;
      }
      const t = (time - startTime) / 1000; // 将时间转换为秒
      if (t > duration) {
        // 每个线段周期重置
        startTime = 0;
      }
      (p1!.point!.pixelSize as any) = size + t * 12;
      (p1!.point!.outlineColor as any) = Cesium.Color.WHITE.withAlpha(
        0.3 + t * 0.2
      );
      (p2!.point!.pixelSize as any) = size + t * 9;
      (p2!.point!.outlineColor as any) = Cesium.Color.WHITE.withAlpha(
        0.3 + t * 0.1
      );
      (p3!.point!.pixelSize as any) = size + t * 6;
      (p3!.point!.outlineColor as any) = Cesium.Color.WHITE.withAlpha(
        0.3 + t * 0.05
      );
      viewer.value?.scene.requestRender();
      requestAnimationFrame(animate); // 递归调用,继续动画
    }
    requestAnimationFrame(animate); // 开始动画
  };
  /**
   * @description 根据搜索数据画图
   */
  const drawMapByData = (data: CesiumData[]) => {
    data.forEach((item) => {
      // 画面
      if (
        item.type === CesiumFilterType.Block ||
        item.type === CesiumFilterType.Platform
      ) {
        const positions = item.pointData?.map((p) =>
          getCartesian3FromDegrees([p.lon, p.lat])
        );
        const polygonHierarchy = new Cesium.PolygonHierarchy(positions);
        drawArea(polygonHierarchy, item.type);
        item.pointData?.forEach((p) => {
          const position = getCartesian3FromDegrees([p.lon, p.lat]);
          drawPoint(position, item.type, {
            name: p.flag,
            content: p.remark,
          });
        });
      }
      // 画线
      if (item.type === CesiumFilterType.Pipeline) {
        const positions = item.pointData?.map((p) =>
          getCartesian3FromDegrees([p.lon, p.lat])
        );
        drawLine(positions!, item.type, item.name, true);
        item.pointData?.forEach((p) => {
          const position = getCartesian3FromDegrees([p.lon, p.lat]);
          drawPoint(
            position,
            item.type,
            {
              name: p.flag,
              content: p.remark,
            },
            {
              point: {
                color: Cesium.Color.WHITE,
                pixelSize: 6,
                outlineWidth: 0,
              },
            }
          );
        });
      }
      // 画点
      if (
        item.type === CesiumFilterType.Well ||
        item.type === CesiumFilterType.Device
      ) {
        item.pointData?.forEach((p) => {
          const position = getCartesian3FromDegrees([p.lon, p.lat], 2);
          drawPoint(
            position,
            item.type,
            {
              name: p.flag,
              content: p.remark,
            },
            {
              size: 16,
              img: item.legendUrl,
              animate: true,
            }
          );
        });
      }
    });

    // 获取所有可见实体点的笛卡尔坐标
    const allEntities = viewer.value?.entities.values;
    const showEntities = allEntities?.filter((item) => item.show);
    // 获取所有实体的笛卡尔坐标
    const pointData: Cesium.Cartesian3[] = [];
    showEntities?.forEach((item) => {
      // 线和面没有position 但是线和面有端点,端点有position
      const data = item.position?.getValue(
        viewer.value!.clock.currentTime,
        new Cesium.Cartesian3()
      );
      if (data) {
        pointData.push(data);
      }
    });

    return pointData;
  };

  return {
    // drawArea,
    // drawLine,
    // drawPoint,
    drawMapByData,
    initHoverEvent,
  };
};

const showTooltip = (x: number, y: number, entity: any) => {
  if (entity.data && entity.data.content) {
    const tipsEl = document.getElementById("tips") as HTMLElement;
    if (tipsEl) {
      tipsEl.style.display = "block";
      tipsEl.style.left = x + 10 + "px";
      tipsEl.style.top = y + "px";
      tipsEl.innerHTML = `<div>${entity.data.content}</div>`;
    } else {
      const tipsEl = document.createElement("div");
      tipsEl.id = "tips";
      tipsEl.style.position = "absolute";
      tipsEl.style.backgroundColor = "rgba(0,0,0,0.5)";
      tipsEl.style.color = "white";
      tipsEl.style.padding = "5px";
      tipsEl.style.borderRadius = "5px";
      document.getElementById("cesium-container")!.appendChild(tipsEl);
      tipsEl.style.left = x + 10 + "px";
      tipsEl.style.top = y + "px";
    }
  }
};

/**
 * @description 根据笛卡尔坐标获取经纬度
 * @param {Cesium.Cartesian3} cartesian
 * @return {*}
 */
export const getDegreesFromCartesian3 = (cartesian: Cesium.Cartesian3) => {
  const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
  const longitude = Number(
    Cesium.Math.toDegrees(cartographic.longitude).toFixed(6)
  );
  const latitude = Number(
    Cesium.Math.toDegrees(cartographic.latitude).toFixed(6)
  );
  return [longitude, latitude];
};

/**
 * @description 根据经纬度获取笛卡尔坐标
 * @param {number[]} degrees
 * @param {number} height 渲染高度 防止重叠
 * @return {*}
 */
export const getCartesian3FromDegrees = (
  degrees: number[],
  height?: number
) => {
  const cartesian = Cesium.Cartesian3.fromDegrees(
    degrees[0],
    degrees[1],
    height
  );
  return cartesian;
};

/**
 * @description 获取范围的中心坐标
 * @param {Cesium.Cartesian3[]} positions
 * @return {*}
 */
export const getCenterPoint = (positions: Cesium.Cartesian3[]) => {
  // 初始化中心点坐标
  let center = Cesium.Cartesian3.ZERO;
  // 计算坐标总数
  const count = positions.length;
  // 计算坐标总和
  positions.forEach((position) => {
    center = Cesium.Cartesian3.add(center, position, new Cesium.Cartesian3());
  });
  // 取坐标平均值
  center = Cesium.Cartesian3.divideByScalar(
    center,
    count,
    new Cesium.Cartesian3()
  );
  // 将中心点坐标转换为地理坐标
  const centerCartographic = Cesium.Cartographic.fromCartesian(center);
  // 将地理坐标转换为经纬度坐标
  const centerLongitude = Number(
    Cesium.Math.toDegrees(centerCartographic.longitude).toFixed(6)
  );
  const centerLatitude = Number(
    Cesium.Math.toDegrees(centerCartographic.latitude).toFixed(6)
  );

  return [centerLongitude, centerLatitude];
};

使用

<template>
  <el-dialog
    title="地图标点"
    width="80%"
    v-model="state.visible"
    center
    top="0"
    :fullscreen="true"
    @close="close"
    class="gis-dialog"
    destroy-on-close
    :append-to-body="true"
  >
    <div id="cesium-container">
      <div id="cesium"></div>
      <div class="shortcut-keys">
        <el-button-group>
          <el-button title="文字图层控制" :icon="Cpu" @click="loadRoadMap" />
          <el-button title="定位" :icon="Compass" @click="toNorth" />
          <el-button title="放大" :icon="ZoomIn" @click="zoom('zoomIn')" />
          <el-button title="缩小" :icon="ZoomOut" @click="zoom('zoomOut')" />
          <el-button
            title="全屏控制"
            @click="fullScreen"
            :icon="FullScreen"
          ></el-button>
        </el-button-group>
      </div>
      <el-button type="primary" class="save" @click="save">保存</el-button>
    </div>
  </el-dialog>
</template>

<script lang="ts">
export default { name: "Cesium" };
</script>
<script lang="ts" setup>
import { reactive } from "vue";
import {
  useDrawAndGetData,
  initCesium,
  getCartesian3FromDegrees,
  getCenterPoint,
  useDraw,
} from "./index";
import {
  Cpu,
  Compass,
  ZoomIn,
  ZoomOut,
  FullScreen,
} from "@element-plus/icons-vue";
import {
  CesiumData,
  CesiumDataItem,
  CesiumDataType,
  CesiumFilterType,
  MyConstructorOptions,
} from "@/types/gis";
import { nextTick } from "vue";

const state = reactive({
  filter: "",
  type: undefined as CesiumFilterType | undefined,
  search: "",
  radio: "",
  visible: false,
  drawType: CesiumDataType.Point as CesiumDataType,
  isFull: false,
});
let { viewer, zoomFn, loadRoadNetwork, destroyed, init, adjustNorthUp, flyTo } =
  initCesium();
const { data, initEvent, reDraw, reset } = useDrawAndGetData(viewer);

const emits = defineEmits(["save"]);

const zoom = (type: "zoomIn" | "zoomOut") => {
  zoomFn(type, 200);
};
const fullScreen = () => {
  // 是否已经进入全屏
  var isFull = document.fullscreenElement;
  if (isFull) {
    document.exitFullscreen();
    state.isFull = false;
  } else {
    const element = document.getElementById("cesium-container")!;
    if (element.requestFullscreen) {
      element.requestFullscreen();
      state.isFull = true;
    }
  }
};
const toNorth = () => {
  adjustNorthUp();
};
const loadRoadMap = () => {
  loadRoadNetwork();
};

const save = () => {
  emits("save", data.value);
  close();
};

const close = () => {
  reset();
  destroyed();
  data.value = [];
  state.visible = false;
};

/**
 * @description 编辑的时候 重绘出来
 */
const drawOldGis = (data: CesiumDataItem[]) => {
  if (!data.length) {
    return;
  }
  const oldData = data.map((item, index) => {
    return {
      degrees: [item.lon, item.lat],
      index,
      data: item.flag,
    };
  });
  reDraw(oldData, state.drawType);
  const Cartesian3Arr = data.map((item) =>
    getCartesian3FromDegrees([item.lon, item.lat])
  );
  const [longitude, latitude] = getCenterPoint(Cartesian3Arr);
  flyTo(longitude, latitude);
};

const open = (type: CesiumDataType, data?: CesiumDataItem[]) => {
  state.drawType = type;
  state.visible = true;
  nextTick(() => {
    // 初始化cesium
    init();
    // 初始化鼠标一些事件
    initEvent(state.drawType);
    // 编辑重绘
    if (data && viewer.value) {
      drawOldGis(data);
    }
  });
};

defineExpose({
  open,
});
</script>

<style lang="scss" scoped>
#cesium {
  height: calc(100vh - 54px);
}
#cesium-container {
  position: relative;
  overflow: hidden;
  .save {
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    bottom: 10px;
  }
  .filter {
    position: absolute;
    left: 10px;
    top: 10px;
  }
  .search {
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    top: 10px;
  }
  .shortcut-keys {
    position: absolute;
    right: 10px;
    top: 10px;
  }
}
.shortcut-keys {
  .el-button {
    background-color: rgba(27, 27, 27, 0.8);
    border-color: var(--el-color-primary);
    color: #fff;
  }
}
:deep(.el-popper) {
  background-color: rgba(27, 27, 27, 0.8);
  border-color: var(--el-color-primary);
  color: #fff;
  .el-select-dropdown__item {
    color: #fff;
  }
  .el-select-dropdown__item.is-hovering {
    background-color: rgba(63, 63, 63, 0.7);
  }
  .el-popper__arrow::before {
    background-color: rgba(63, 63, 63, 0.7);
    border-color: var(--el-color-primary);
  }
  .el-checkbox {
    color: #fff;
  }
}
</style>

新增功能:视角控制

在这里插入图片描述
下面是主要实现的2个函数

  /**
   * @description 获取当前笛卡尔坐标和固定参考帧
   */
  const getFrame = () => {
    // 定义一个变量result,用于存储笛卡尔坐标
    let result = new Cesium.Cartesian3();
    // 获取viewer中的camera和scene
    const camera = viewer.value!.camera;
    const scene = viewer.value!.scene;
    // 定义一个变量rayScratch,用于存储射线
    const rayScratch = new Cesium.Ray();
    // 设置rayScratch的起点和方向
    rayScratch.origin = camera.positionWC;
    rayScratch.direction = camera.directionWC;
    // 获取场景中地球的pick值,如果没有则返回camera的坐标
    result =
      scene.globe.pick(rayScratch, scene, result) ||
      viewer.value!.camera.position;
    // focusPoint = result; // 设置focusPoint为result,用于调整视角时使用
    // 将笛卡尔坐标转换为相机坐标
    result = camera.worldToCameraCoordinatesPoint(result, result);
    // 定义一个变量newTransformScratch,用于存储矩阵
    const newTransformScratch = new Cesium.Matrix4();
    // 将笛卡尔坐标转换为固定参考帧
    frame = Cesium.Transforms.eastNorthUpToFixedFrame(
      result,
      scene.globe.ellipsoid,
      newTransformScratch
    );
  };

  /**
   * @description 调整视角
   * @param {number} x
   * @param {number} y
   */
  const orbitTickFunction = (x: number, y: number) => {
    viewer.value?.camera.lookAtTransform(frame);
    viewer.value?.camera.rotateLeft(x);
    viewer.value?.camera.rotateUp(y);
    viewer.value?.camera.lookAtTransform(Cesium.Matrix4.IDENTITY);
  };

仓库代码

github仓库,因为是demo代码可能比较乱,见谅。现仓库代码可能与博客不同以仓库为准,但功能的实现是没问题的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值