Cesium 飞行路线编辑

效果

Cesium 飞行路线编辑_3d

实现的主要功能

  1. 拖动点位图标修改位置
  2. 点击线,在线上新增点位
  3. 右击点位图标,删除点位

这些功能配合父页面使用,所以并没有从头开始绘制路线,父页面是具有增删改功能的飞行路线点位列表,包含时间点、经纬度、高度等字段信息

页面代码(vue2)

<template>
  <el-dialog :title="title" :visible.sync="open" width="90vw" append-to-body>
    <div style="height: 80vh;">
      <three ref="three"></three>
    </div>
    <div slot="footer" class="dialog-footer">
      <el-button type="primary" @click="ok">确 定</el-button>
      <el-button @click="cancel">取 消</el-button>
    </div>
  </el-dialog>
</template>

<script>
/**
 * 选择位置集合
 */

import Three from "@/views/three/index.vue";
import { getMap } from "@/views/three/js/index.js";
import { getHeightByLngLat } from "@/views/three/js/utils.js";

let map;
let graphicLayer;
let graphic;
let hander;
let picked;
let pickedIndex;
let move;

export default {
  name: "SelectPositions",
  data() {
    return {
      title: "选择位置集合", // 弹出层标题
      open: false, // 是否显示弹出层
      waitHandle: undefined,
      waitHandle2: undefined,
      tag: undefined,
      index: undefined,
      list: [
        {
          time: undefined,
          lng: 117.12,
          lat: 31.86,
          height: undefined,
          createUser: undefined,
          createTime: undefined,
          updateUser: undefined,
          updateTime: undefined,
        },
        {
          time: undefined,
          lng: undefined,
          lat: undefined,
          height: undefined,
          createUser: undefined,
          createTime: undefined,
          updateUser: undefined,
          updateTime: undefined,
        },
        {
          time: undefined,
          lng: 117.13,
          lat: 31.87,
          height: undefined,
          createUser: undefined,
          createTime: undefined,
          updateUser: undefined,
          updateTime: undefined,
        },
      ],
    };
  },
  components: {
    Three,
  },
  created() {},
  async mounted() {
    await this.waitThreeReady();
    this.$refs.three.containerId = "mars3dContainer2";
    await this.$refs.three.initMap({
      scene: {
        center: {
          lng: 117.119112,
          lat: 31.862912,
          alt: 10000,
          heading: 0,
          pitch: -89,
        },
      },
      control: {
        clockAnimate: false,
        timeline: false,
      },
    }); // 子组件调用父组件的方法
    map = getMap();
    graphicLayer = new mars3d.layer.GraphicLayer();
    map.addLayer(graphicLayer);

    this.bindMapClick(); // 绑定地图点击事件
    map.unbindContextMenu(); // 屏蔽右键菜单
  },
  beforeDestroy() {
    this.waitHandle && clearInterval(this.waitHandle);
    this.waitHandle2 && clearInterval(this.waitHandle2);
    this.unbindMapClick();
  },
  methods: {
    async openDialog(data) {
      this.open = true;
      this.index = data.index;
      this.tag = data.tag;
      if (data.list) this.list = data.list;

      await this.waitTerrainReady();
      this.showPoints();
    },
    // 显示点位集合
    async showPoints() {
      graphicLayer.clear();
      let positions = [];
      for (let i = 0; i < this.list.length; i++) {
        let item = this.list[i];
        let height = undefined;
        if (item.lng && item.lat) {
          height = await getHeightByLngLat(map, item.lng, item.lat);
          let entity = await this.createBillboardEntity(
            this.index == i ? i + 1 + "(当前点位)" : i + 1,
            item.lng,
            item.lat,
            height
          );
          entity.options.index = i; // 把index记下来
          graphicLayer.addGraphic(entity);
          if (this.index == i) {
            graphic = entity;
          }
          positions.push([item.lng, item.lat, height]);
        }
      }
      let graphicPolyline = new mars3d.graphic.PolylineEntity({
        positions: positions,
        style: {
          color: "rgba(255,255,0,1)",
          width: 5,
        },
      });
      graphicLayer.addGraphic(graphicPolyline);
    },
    async createBillboardEntity(text, lng, lat, height) {
      return new mars3d.graphic.BillboardEntity({
        position: [lng, lat, height],
        style: {
          image: "img/marker/mark-blue.png",
          scale: 1,
          horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
          verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
          label: {
            text: text,
            font_size: 18,
            color: "#ffffff",
            pixelOffsetY: -50,
          },
        },
      });
    },
    // 绑定地图点击事件
    bindMapClick() {
      hander = new Cesium.ScreenSpaceEventHandler(map.viewer.scene.canvas);
      // 鼠标单击
      hander.setInputAction(async (e) => {
        let position = this.pitchLngLat(e.position);
        if (!position) return;

        let pickedPolyline = false;
        map.viewer.scene.drillPick(e.position).map((item) => {
          if (item.id instanceof Cesium.Entity) {
            if (item.id.polyline) {
              pickedPolyline = true;
            }
          }
        });

        if (!pickedPolyline) {
          this.setText(this.index + "(当前点位)");
          await this.setPosition(graphic, position.lng, position.lat);
          let currentItem = this.list[this.index];
          currentItem.lng = position.lng;
          currentItem.lat = position.lat;
          this.showPoints();
        } else {
          let index = this.getIndex(e.position);
          if (index + 1 <= this.index) {
            this.index++;
          }
          this.list.splice(index + 1, 0, {
            time: undefined,
            lng: position.lng,
            lat: position.lat,
            height: undefined,
            createUser: undefined,
            createTime: undefined,
            updateUser: undefined,
            updateTime: undefined,
          });
          this.showPoints();
        }
      }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
      // 鼠标左键按下
      hander.setInputAction((e) => {
        map.viewer.scene.drillPick(e.position).map((item) => {
          let graphics = graphicLayer.getGraphics();
          for (let i = 0; i < graphics.length; i++) {
            let graphic = graphics[i];
            if (
              graphic.id == item.id.id &&
              graphic instanceof mars3d.graphic.BillboardEntity
            ) {
              picked = graphic;
              pickedIndex = graphic.options.index;
            }
          }
        });
        if (!picked) return;
        map.viewer.scene.screenSpaceCameraController.enableRotate = false; // 锁定相机
        move = true;
      }, Cesium.ScreenSpaceEventType.LEFT_DOWN);
      // 鼠标左键抬起
      hander.setInputAction((e) => {
        if (!move) return;
        picked = false;
        map.viewer.scene.screenSpaceCameraController.enableRotate = true; // 取消锁定相机
        move = false;
        this.showPoints();
      }, Cesium.ScreenSpaceEventType.LEFT_UP);
      // 鼠标移动
      hander.setInputAction((e) => {
        if (!move) return;
        let position = this.pitchLngLat(e.endPosition);
        this.setPosition(picked, position.lng, position.lat);
        this.list[pickedIndex].lng = position.lng;
        this.list[pickedIndex].lat = position.lat;
      }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
      // 鼠标右击
      hander.setInputAction((e) => {
        map.viewer.scene.drillPick(e.position).map((item) => {
          let graphics = graphicLayer.getGraphics();
          for (let i = 0; i < graphics.length; i++) {
            let graphic = graphics[i];
            if (
              graphic.id == item.id.id &&
              graphic instanceof mars3d.graphic.BillboardEntity
            ) {
              picked = graphic;
              pickedIndex = graphic.options.index;
            }
          }
        });
        if (!picked) return;
        if (
          picked.options.index != this.index &&
          picked.options.index != 0 &&
          picked.options.index != this.list.length - 1
        ) {
          this.list.splice(picked.options.index, 1);
          if (picked.options.index < this.index) {
            this.index--;
          }
          this.showPoints();
        }
        picked = undefined;
      }, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
    },
    // 取消绑定地图点击事件
    unbindMapClick() {
      if (hander && !hander.isDestroyed()) {
        hander.destroy();
        hander = undefined;
      }
    },
    //拾取经纬度
    pitchLngLat(position) {
      let cartesian = map.viewer.camera.pickEllipsoid(
        position,
        map.viewer.scene.globe.ellipsoid
      );
      if (!cartesian) return undefined;
      let cartographic = Cesium.Cartographic.fromCartesian(cartesian);
      let lng = Cesium.Math.toDegrees(cartographic.longitude);
      let lat = Cesium.Math.toDegrees(cartographic.latitude);
      return { lng, lat };
    },
    // 查询在线上拾取的点位在哪两个点之间
    getIndex(position) {
      let minDistance;
      let index = -1;

      let p = this.pitchLngLat(position);

      for (let i = 0; i < this.list.length - 1; i++) {
        let item1 = this.list[i];
        let item2 = this.list[i + 1];
        let p1 = [item1.lng, item1.lat];
        let p2 = [item2.lng, item2.lat];

        let point = turf.point([p.lng, p.lat]);
        let line = turf.lineString([p1, p2]);

        let distance = turf.pointToLineDistance(point, line, {
          units: "kilometers",
        });
        if (!minDistance) {
          minDistance = distance;
          index = i;
        } else {
          if (distance < minDistance) {
            minDistance = distance;
            index = i;
          }
        }
      }

      return index;
    },
    // 取消按钮
    cancel() {
      this.open = false;
    },
    // 确定
    ok() {
      this.open = false;
      this.$emit("selectPositionsOK", this.tag, this.index, this.list);
    },
    async setPosition(graphic, lng, lat) {
      this.lng = lng;
      this.lat = lat;
      if (graphic) {
        let height = await getHeightByLngLat(map, lng, lat);
        graphic.position = [lng, lat, height];
      }
    },
    setText(text) {
      if (graphic) {
        graphic.label.text = text;
      }
    },
    // 等待this.$refs.three准备就绪
    async waitThreeReady() {
      return new Promise((resolve, reject) => {
        this.waitHandle = setInterval(() => {
          if (this.$refs.three) {
            clearInterval(this.waitHandle);
            this.waitHandle = undefined;
            resolve();
          }
        }, 100);
      });
    },
    // 等待terrainProvider准备就绪
    async waitTerrainReady() {
      return new Promise((resolve, reject) => {
        this.waitHandle2 = setInterval(() => {
          if (map && map.viewer && map.viewer.terrainProvider.availability) {
            clearInterval(this.waitHandle2);
            this.waitHandle2 = undefined;
            resolve();
          }
        }, 100);
      });
    },
  },
};
</script>