cesium绘制缓冲区

549 阅读3分钟

cesium绘制缓冲区,导出位置信息,查询范围数据

使用class 封装,库: @turf/turf,cesium

使用方式

import { BufferDraw } from "buffer-draw";

/**
 * @type {BufferDraw}
 */
let bufferDraw

export default {
   data() {
     type: "point", // point, line, area 类型
     radius: 5 // 缓冲区距离
   },
   mounted() {
     bufferDraw = new BufferDraw(viewer);
     // 可在加载后,初始化一个点位缓冲数据
     // bufferDraw.initPointBuffer([100, 90], this.radius);
     // bufferDraw.startDraw({ type: "point" }, this.drawDone);
   },
   beforeDestroy() {
     if (bufferDraw) {
       bufferDraw.destroy();
       bufferDraw = null;
     }
   },
   methods: {
     drawDone() {
       console.log("绘制完成");
       // 获取结果
       console.log(bufferDraw.getResult());
     },
     // 点线面绘制类型切换
     onChangeDrawType(type) {
      bufferDraw.startDraw({ type, radius: this.radius }, this.drawDone);
    },
    onRadiusChange(radius) {
      bufferDraw.paramsChange({ type: this.type, radius: this.radius });
    }
   }
}

// 

下面是完整的缓冲区代码

/**
 * 绘制缓冲区
 */
import * as turf from "@turf/turf";
import * as Cesium from "cesium";

const BUFFER_ID = "BUFFER_ID"; // 缓冲面
const POINT_ID = "POINT_ID"; // 点
const POLYLINE_ID = "POLYLINE_ID"; // 线
const POLYGON_ID = "POLYGON_ID"; // 面
const BUFFER_RADIUS_ID = "BUFFER_RADIUS_ID"; // 半径
const BUFFER_DATA_SOURCE = "BUFFER_DATA_SOURCE";

const typeList = {
  point: POINT_ID,
  line: POLYLINE_ID,
  area: POLYGON_ID
};

export class BufferDraw {
  /**
   * @type {import('cesium').ScreenSpaceEventHandler}
   */
  #handler = null;
  params = {
    position: null,
    radius: null
  };

  /**
   * @param {import('cesium').Viewer} viewer
   */
  constructor(viewer, name = BUFFER_DATA_SOURCE) {
    this.viewer = viewer;
    this.t = Date.now();
    this.name = `${name}_${this.t}`;
    this.createDataSource(this.name);
  }

  createDataSource(name) {
    let dataSource = this.viewer.dataSources.getByName(name)?.[0];
    if (!Cesium.defined(dataSource)) {
      dataSource = new Cesium.CustomDataSource(name);
      this.viewer.dataSources.add(dataSource);
    }
    this.dataSource = dataSource;
    return this.dataSource;
  }

  getDataSource() {
    return this.dataSource;
  }

  /**
   * 设置参数
   */
  setParams({ position, radius }) {
    this.params = {
      position: position ?? this.params.position,
      radius: radius ?? this.params.radius
    };
  }

  /**
   * 获取结果
   */
  getResult() {
    const type = this.getType();
    if (type === "other") return null;
    const positionString = this.getPositionString(this.params.position, type);
    return { ...this.params, positionString, type };
  }

  /**
   * 销毁
   */
  destroy() {
    this.destroyDataSource();
    this.destroyHandle();
  }

  destroyDataSource() {
    if (this.viewer && this.dataSource) {
      this.viewer.dataSources.remove(this.dataSource);
    }
  }

  destroyHandle() {
    if (
      this.#handler &&
      this.#handler instanceof Cesium.ScreenSpaceEventHandler
    ) {
      this.#handler.destroy();
      this.#handler = null;
    }
  }

  getType() {
    if (this.findEntityById(typeList.point)) return "point";
    if (this.findEntityById(typeList.line)) return "line";
    if (this.findEntityById(typeList.area)) return "area";
    return "other";
  }

  getPositionString(position, type) {
    if (type === "point") {
      return `POINT(${position[0]} ${position[1]})`;
    }
    if (type === "line") {
      return `LINESTRING(${position.map(v => v.join(" ")).join(",")})`;
    }
    if (type === "area") {
      return `POLYGON((${position.map(v => v.join(" ")).join(",")}))`;
    }
  }

  getBuffered(point, radius, units = "meters") {
    return turf.buffer(point, radius, { units });
  }

  getDegreesArray(point, radius) {
    const buffered = this.getBuffered(point, radius);
    const coordinates = buffered.geometry.coordinates;
    const points = coordinates[0];
    const degreesArray = this.pointsToDegreesArray(points);
    return degreesArray;
  }

  findEntityById(id) {
    if (this.dataSource) {
      return this.dataSource.entities.getById(id);
    }
    return null;
  }

  // 格式转换
  pointsToDegreesArray(points) {
    const degreesArray = [];
    points.map(item => {
      degreesArray.push(item[0]);
      degreesArray.push(item[1]);
    });
    return degreesArray;
  }

  // 笛卡尔坐标转世界坐标
  getWGS84ByCartesian3(cartesian3) {
    const ellipsoid = this.viewer.scene.globe.ellipsoid;
    const cartographic = ellipsoid.cartesianToCartographic(cartesian3);
    const lat = Cesium.Math.toDegrees(cartographic.latitude);
    const lng = Cesium.Math.toDegrees(cartographic.longitude);
    const alt = cartographic.height;
    return { lat, lng, alt };
  }

  getCartesian3ByScreen(position) {
    const ray = this.viewer.camera.getPickRay(position);
    const cartesian3 = this.viewer.scene.globe.pick(ray, this.viewer.scene);
    return cartesian3;
  }

  getWGS84ByScreen(position) {
    const cartesian3 = this.getCartesian3ByScreen(position);
    return this.getWGS84ByCartesian3(cartesian3);
  }

  // 初始化点缓冲
  initPointBuffer(positions, radius) {
    this.setParams({ position: positions, radius });
    const point = positions;
    radius = radius * 1000;
    this.addPoint(point);
    const pointF = turf.point(point);
    const degreesArray = this.getDegreesArray(pointF, radius);
    this.addBufferPolygon(Cesium.Cartesian3.fromDegreesArray(degreesArray));
    const radiusArray = [point[0], point[1], degreesArray[0], degreesArray[1]];
    this.addRadius(radiusArray, radius);
  }

  // 初始化线缓冲
  initPolylineBuffer(positions, radius) {
    this.setParams({ position: positions, radius });
    const points = positions;
    radius = radius * 1000;
    // let degreesArray = this.pointsToDegreesArray(points);
    // this.addPolyline(Cesium.Cartesian3.fromDegreesArray(degreesArray))

    const polylineF = turf.lineString(points);
    const degreesArray = this.getDegreesArray(polylineF, radius);
    this.addBufferPolygon(Cesium.Cartesian3.fromDegreesArray(degreesArray));
  }

  // 初始化面缓冲
  initPolygonBuffer(positions, radius) {
    this.setParams({ position: positions, radius });
    const points = positions;
    radius = radius * 1000;
    // let degreesArray = this.pointsToDegreesArray(points);
    // this.addPolygon(Cesium.Cartesian3.fromDegreesArray(degreesArray))

    const polygonF = turf.polygon([points]);
    const degreesArray = this.getDegreesArray(polygonF, radius);
    this.addBufferPolygon(Cesium.Cartesian3.fromDegreesArray(degreesArray));
  }

  // 半径和Label
  addRadius(radiusArray, radius) {
    const ID = BUFFER_RADIUS_ID;
    const entity = this.findEntityById(ID);
    const point1 = turf.point([radiusArray[0], radiusArray[1]]);
    const point2 = turf.point([radiusArray[2], radiusArray[3]]);
    const midpoint = turf.midpoint(point1, point2);
    const minPoint = midpoint.geometry.coordinates;
    const position = Cesium.Cartesian3.fromDegrees(minPoint[0], minPoint[1]);
    const positions = Cesium.Cartesian3.fromDegreesArray(radiusArray);
    const labelText = radius / 1000 + "KM";
    if (entity) {
      entity.label.text = labelText;
      entity.position = position;
      entity.polyline.positions = positions;
      return;
    }
    this.dataSource.entities.add({
      id: ID,
      position,
      label: {
        text: labelText,
        heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
        fillColor: Cesium.Color.YELLOW,
        scale: 0.7,
        pixelOffset: new Cesium.Cartesian2(0, -10),
        disableDepthTestDistance: Number.POSITIVE_INFINITY
      },
      polyline: {
        positions,
        width: 3,
        clampToGround: true,
        material: Cesium.Color.YELLOW.withAlpha(0.7)
      }
    });
  }

  // 添加缓冲面
  addBufferPolygon(positions) {
    const ID = BUFFER_ID;
    const entity = this.findEntityById(ID);
    const hierarchy = new Cesium.PolygonHierarchy(positions);
    if (entity) {
      entity.polygon.hierarchy = hierarchy;
      entity.polyline.positions = positions;
      return;
    }
    this.dataSource.entities.add({
      id: ID,
      polygon: {
        hierarchy,
        // material: Cesium.Color.BLUE.withAlpha(0.3),
        material: Cesium.Color.BLUE.withAlpha(0.0),
        classificationType: Cesium.ClassificationType.BOTH
      },
      polyline: {
        positions: positions,
        width: 3,
        clampToGround: true,
        material: Cesium.Color.YELLOW.withAlpha(0.7)
      }
    });
  }

  removeBufferPolygon() {
    const dataSource = this.dataSource;
    if (dataSource.entities.getById(BUFFER_ID)) {
      dataSource.entities.removeById(BUFFER_ID);
    }
    if (dataSource.entities.getById(BUFFER_RADIUS_ID)) {
      dataSource.entities.removeById(BUFFER_RADIUS_ID);
    }
  }

  // 添加点
  addPoint(point) {
    const ID = POINT_ID;
    const entity = this.findEntityById(ID);
    const position = Cesium.Cartesian3.fromDegrees(point[0], point[1], 0);
    if (entity) {
      entity.position = position;
      return;
    }
    return this.dataSource.entities.add({
      id: ID,
      position,
      point: {
        pixelSize: 10,
        outlineWidth: 3,
        color: Cesium.Color.RED,
        outlineColor: Cesium.Color.RED.withAlpha(0.4),
        disableDepthTestDistance: Number.POSITIVE_INFINITY,
        heightReference: Cesium.HeightReference.CLAMP_TO_GROUND
      }
    });
  }

  // 添加线
  addPolyline(positions) {
    return this.dataSource.entities.add({
      id: POLYLINE_ID,
      polyline: {
        positions,
        width: 2,
        clampToGround: true,
        material: Cesium.Color.YELLOW
      }
    });
  }

  // 添加面
  addPolygon(positions) {
    return this.dataSource.entities.add({
      id: POLYGON_ID,
      polygon: {
        hierarchy: new Cesium.PolygonHierarchy(positions),
        material: Cesium.Color.YELLOW.withAlpha(0.6),
        classificationType: Cesium.ClassificationType.BOTH
      },
      polyline: {
        positions: positions,
        width: 2,
        material: Cesium.Color.YELLOW.withAlpha(0.4)
      }
    });
  }

  /**
   * 过滤
   * @param {string} type
   */
  filterDraw(type) {
    const list = ["point", "line", "area"];
    const delList = list.filter(v => v !== type);

    delList.forEach(v => {
      const id = typeList[v];
      this.dataSource.entities.removeById(id);
    });
  }

  createHandler() {
    const handler = new Cesium.ScreenSpaceEventHandler(
      this.viewer.scene.canvas
    );
    return handler;
  }

  // 绘制点
  drawPoint(cb) {
    this.#handler = this.createHandler();
    this.#handler.setInputAction(movement => {
      this.filterDraw("point");
      this.removeBufferPolygon();
      const WGS84 = this.getWGS84ByScreen(movement.position);
      const position = [WGS84.lng, WGS84.lat];
      this.initPointBuffer(position, this.params.radius);
      cb && cb();
    }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
  }

  // 绘制线
  drawLine(cb) {
    const ID = POLYLINE_ID;
    let entity = this.findEntityById(ID);
    let positions = [];
    this.#handler = this.createHandler();

    this.#handler.setInputAction(movement => {
      this.filterDraw("line");
      this.removeBufferPolygon();
      if (!positions.length && entity) {
        this.dataSource.entities.remove(entity);
        entity = null;
      }
      const cartesian3 = this.getCartesian3ByScreen(movement.position);
      !positions.length && positions.push(cartesian3);
      positions.push(cartesian3);
    }, Cesium.ScreenSpaceEventType.LEFT_CLICK);

    this.#handler.setInputAction(movement => {
      if (positions.length > 0) {
        const movePosition = this.getCartesian3ByScreen(movement.startPosition);
        if (positions.length >= 2) {
          // tool.showAt(movement1.endPosition, '<p>左键点击确定折线中间点</p><p>右键单击结束绘制</p>');
          if (!entity) {
            entity = this.addPolyline(positions);
            this.updatePositions(entity, positions);
          } else {
            positions.pop();
            positions.push(movePosition);
          }
        }
      }
    }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);

    this.#handler.setInputAction(() => {
      positions.pop();
      if (entity && positions.length < 2) {
        this.dataSource.entities.remove(entity);
        this.setParams({ position: null });
      } else {
        const WGS84 = positions.map(v => {
          const item = this.getWGS84ByCartesian3(v);
          return [item.lng, item.lat];
        });
        this.initPolylineBuffer(WGS84, this.params.radius);
        cb && cb();
      }
      positions = [];
    }, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
  }

  // 绘制面
  drawArea(cb) {
    const ID = POLYGON_ID;
    let entity = this.findEntityById(ID);
    let positions = [];
    let firstPoint;
    this.#handler = this.createHandler();

    this.#handler.setInputAction(movement => {
      this.filterDraw("area");
      this.removeBufferPolygon();
      if (!positions.length && entity) {
        this.dataSource.entities.remove(entity);
        entity = null;
      }
      const cartesian3 = this.getCartesian3ByScreen(movement.position);
      if (!positions.length) {
        firstPoint = cartesian3;
        positions.push(cartesian3);
      }
      positions.push(cartesian3);
    }, Cesium.ScreenSpaceEventType.LEFT_CLICK);

    this.#handler.setInputAction(movement => {
      if (positions.length > 0) {
        const movePosition = this.getCartesian3ByScreen(movement.startPosition);
        if (positions.length >= 2) {
          // tool.showAt(movement1.endPosition, '<p>左键点击确定折线中间点</p><p>右键单击结束绘制</p>');
          if (!entity) {
            entity = this.addPolygon(positions);
            this.updatePositions(entity, positions);
          } else {
            positions.pop();
            positions.push(movePosition);
          }
        }
      }
    }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);

    this.#handler.setInputAction(() => {
      positions.pop();
      if (entity && positions.length < 3) {
        this.dataSource.entities.remove(entity);
        this.setParams({ position: null });
      } else {
        positions.push(firstPoint);
        const WGS84 = positions.map(v => {
          const item = this.getWGS84ByCartesian3(v);
          return [item.lng, item.lat];
        });
        this.initPolygonBuffer(WGS84, this.params.radius);
        cb && cb();
      }
      positions = [];
    }, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
  }

  // 实时更新位置
  updatePositions(entity, positions) {
    if (entity.polyline) {
      entity.polyline.positions = new Cesium.CallbackProperty(
        () => positions,
        false
      );
    }
    if (entity.polygon) {
      const hierarchy = new Cesium.PolygonHierarchy(positions);
      entity.polygon.hierarchy = new Cesium.CallbackProperty(
        () => hierarchy,
        false
      );
    }
  }

  /**
   *
   * @param {{position: array, radius: number}} params 参数
   * @param {function} cb 画完的回调函数
   */
  startDraw(params, cb) {
    this.destroyHandle();
    this.setParams({ radius: params.radius });
    switch (params.type) {
      case "point":
        this.drawPoint(cb);
        break;
      case "line":
        this.drawLine(cb);
        break;

      case "area":
        this.drawArea(cb);
        break;
      default:
        break;
    }
  }

  /**
   * 参数改变时重绘
   * @param {object} params
   */
  paramsChange(params) {
    const _params = this.params;
    const {
      type,
      radius = _params.radius,
      position = _params.position
    } = params;
    if (position === void 0 || position === null) return;
    if (radius === void 0 || radius === null) return;
    switch (type) {
      case "point":
        this.initPointBuffer(position, radius);
        break;
      case "line":
        this.initPolylineBuffer(position, radius);
        break;

      case "area":
        this.initPolygonBuffer(position, radius);
        break;
      default:
        break;
    }
  }
}