高德地图+vue实现页面点击绘制多边形及多边形切割拆分

4,364 阅读5分钟

最终效果

技术栈

项目中使用到的技术 高德基于vue的vue-amap,组件使用的element,算法是用的turf.js

配置步骤划分

1.创建vue文件,项目中引入vue-amap 官方有两种引入方式,一种npm 安装,一种是cdn引入,这里我选择的是npm安卓的

官方文档:vue-amap

2.根据文档的步骤,在main.js引入,页面中导入,就可以使用官方的组件的,这边主要使用的组件有三个,一个地图组件el-amap,一个点坐标el-amap-marker,一个多边形的遮盖物el-amap-polygon(最主要的实现组件)
多边形

3.地图的js算法是turf.js,turf是一个用于空间分析的js库。它包括传统的空间操作、用于创建GeoJSON数据的辅助功能以及数据分类和统计工具。Turf可以作为客户端插件添加到你的网站上,或者你可以用在Node.js开发后端上,感兴趣的可以看一下它的github,一个开发地图非常好用的库
github

使用npm(npm install @turf/turf)下载下来以后,在页面中引入它的js文件,并赋值给一个变量(const turf = require('@turf/turf');),

实现思路

1.监听页面中的点击,获取当前点击点的坐标,然后将经纬度存到data里面 通过地图组件绑定的events属性,绑定的属性对象中,监听click点击事件,将事件保存下来

2.通过vue的 watch来监听data中的lng或lat的变化,当值发生变化的时候,页面新增点位

3.当页面中markers点位数量变化的同时,监听markers的length长度是不是大于3,当点位多于三个的时候,在页面中通过遮盖物el-amap-polygon组件来把多边形画出来

但这边有一个问题,就是如果页面点击的坐标点顺序不能够构成一个标志的多边形的话,这个要怎么判断出来,因为el-amap-polygon组件是根据你数组的坐标顺序来构建多边形的,比如,一个不等的长方形,如果点位顺序不是顺时针或者逆时针的话,就会形成两个顶点相交的三角形,这个时候就需要用到turf来判断这个数组的坐标点顺序是不是顺时针或者逆时针的

4.通过turf的kinks方法,来判断这个多边形是否是合法多边形,即是否存在自相交情况

这四步下来,一个多边形就在页面中画了出来

网格拆分实现思路

上面已经将多边形画出来了以后,现在要做的就是将大的多边形划分为一个一个小的多边型,这一步的思路是先得出一个包围在多边形外部的四边形,然后拆分这个四边形,得出一个一个小的正四边形,然后判断每个正四边形是否与所画的多边形是否相交,然后将相交的部分裁剪显示出来,这一步共分为五步

第一步,先判断这个多边形的大小,是否符合规定

因为画一个太大的多边形的话,比如将整个中国都包括进来,如果按一公里的面积来拆分,那计算量是个比较巨大的数子,需要很高的性能,性能低的可能会直接卡死。这里通过vue的环境变量来控制这一个数组 判断大小 然后根据我的业务逻辑来判断这个多边形的面积是否是需要拆分的,或者是用户是否选择拆分如下图 当用户点击确定就继续进行,点击取消就结束整个的任务

第二步,获取覆盖绘画多边形的外多边形,然后拆分成一个一个小的多边形

这一步需要借助bboxPolygon来获取覆盖在所画多边形外围的四边形

然后将外四边形拆分,拆分成一个一个小的正方形,这里需要用到squareGrid它有三个参数,第一个bbox,第二个是所需拆分成小正方形的编程,第三个是可选项,包括使用什么单位(英里,公里等)

第三步,裁剪小网格,获取与所画多边形重合的部分

使用intersect获取裁剪后的网格

let clipped = turf.intersect(obj, polygon);

第四部,调用绘画函数,绘画一个一个小网格

  this.polygonArr[index] = {
    draggable: false,
    strokeColor: '#49B2FF',
    fillColor: '#47ABF5',
    fillOpacity: 0.5,
    path: []
  }; //  多边形的样式
  let coordinates = clipped.geometry.coordinates[0];
  let coordinatesLeng = coordinates.length;
  for (let j = 0; j < coordinatesLeng - 1; j++) {
    this.polygonArr[index].path[j] = coordinates[j];
  }

到此为止,一个多边形的拆分就已经出来了,但这样拆分有一个问题,如下图

通过上述四部绘画拆分出来的,会发现拆分的多边形它并不是完全的拆分,这是因为squareGrid是通过中心点向外分割的,所以如果外部面积不足以拆分一个网格的时候,就出现了上述的情况,所以我们要通过第五步来解决一下

第五步,解决与所画多边形重合部分拆分不完全问题 既然当前画出来的因为面积问题,拆分到最外层的时候剩余面积不足以继续拆分,那我们就讲外面包裹所花多边形的面积给他扩大一下,这样不就行了吗,这里就需要用到transformScale直接将它的面积给扩大,这样一来不就解决了吗

 /**
 * 放大外多边形的,获取覆盖绘画多边形的网格
 * @param {Object} polygon
 * @param {number} value
 * @param {number} sides
 */
  acoverage(polygon, value, sides) {
  this.polygonArr.splice(this.polygonArr.length - len, len);
  let bbox = turf.bbox(polygon);
  console.log('bbox');
  console.log(bbox);
  //bboxPolygon Takes a bbox and returns an equivalent polygon.
  let bboxPolygon = turf.bboxPolygon(bbox).geometry.coordinates[0]; //  获取覆盖所画多边形的外围四边形
  console.log(bboxPolygon);
  let polygon2 = turf.polygon([bboxPolygon], {
    name: 'poly2'
  });
  console.log('polygon2', polygon2);
  let scaledPoly = turf.transformScale(polygon2, sides); //  将外多边形放大
  console.log('scaledPoly', scaledPoly);
  let bbox2 = turf.bbox(scaledPoly);
  console.log(bbox2);

  let options = { units: 'kilometers', mask: scaledPoly };
  let squareGrid = turf.squareGrid(bbox2, value, options); //  获得覆盖多边形的网格坐标
  console.log('squareGrid');
  console.log(squareGrid);

  this.check(squareGrid.features, polygon);
  },

最终效果

关键步骤完整代码

 /**
 * 绘制完多边形判断Polygon是否为自相交
 * @param {Object} polygon turfpolygon类型
 */
isIntersect(polygon) {
  var kinks = turf.kinks(polygon);
  if (kinks.features.length > 0) {
    //存在自相交情况
    this.$message({
      showClose: true,
      message: '错误,多边形不合法',
      type: 'error'
    });
    this.obliterate();
  } else {
    //不存在自相交情况
    area = turf.area(polygon) / 1000000;
    console.log(area);
    console.log(process.env.VUE_APP_MAP_AREA);  //  vue环境变量,读取最大绘画面积
    if (area < process.env.VUE_APP_MAP_AREA) {
      area > 1 ? this.promptArea(polygon) : this.noPromptArea();
    } else {
      this.$message({
        message: '绘画面积过大',
        type: 'warning'
      });
    }
  }
},
/**
 * 选择多边形不需要拆分
 */
noPromptArea() {
  this.taskNauticaArr[0] = {
    flight_task_name: '任务1',
    task_coordinate_points: this.blockNautica
  };
},
/**
 * 判断多边形是否需要拆分
 */
promptArea(polygon) {
  this.polygonArr = []; // 清空网格
  this.$prompt('该测区面积较大,是否拆分?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    // inputPattern: /^[+]?[1-9][1-9]*$/,
    inputPattern: /^[+]?[1-9]$/,
    inputErrorMessage: '请输入大于0小于10的正整数,单位平方千米'
  })
    .then(({ value }) => {
      let sides = 1; //  绘画的方格边长
      if (value <= 2) {
        sides = 1.4;
      } else if (value <= 6) {
        sides = 1.6;
      } else {
        sides = 2;
      }
      this.acoverage(polygon, value, sides);
    })
    .catch(err => {
      console.log(err);
      this.$message({
        type: 'info',
        duration: 1500,
        message: '取消拆分'
      });
      this.noPromptArea();
    });
},
/**
 * 放大外多边形的,获取覆盖绘画多边形的网格
 * @param {Object} polygon
 * @param {number} value
 * @param {number} sides
 */
acoverage(polygon, value, sides) {
  this.polygonArr.splice(this.polygonArr.length - len, len);
  let bbox = turf.bbox(polygon);
  console.log('bbox');
  console.log(bbox);
  //bboxPolygon Takes a bbox and returns an equivalent polygon.
  let bboxPolygon = turf.bboxPolygon(bbox).geometry.coordinates[0]; //  获取覆盖所画多边形的外围四边形
  console.log(bboxPolygon);
  let polygon2 = turf.polygon([bboxPolygon], {
    name: 'poly2'
  });
  console.log('polygon2', polygon2);
  let scaledPoly = turf.transformScale(polygon2, sides); //  将外多边形放大
  console.log('scaledPoly', scaledPoly);
  let bbox2 = turf.bbox(scaledPoly);
  console.log(bbox2);

  let options = { units: 'kilometers', mask: scaledPoly };
  let squareGrid = turf.squareGrid(bbox2, value, options); //  获得覆盖多边形的网格坐标
  console.log('squareGrid');
  console.log(squareGrid);

  this.check(squareGrid.features, polygon);
},
/**
 *添加等待提示,循环绘画方法
 * @param {Array} arr
 * @param {Object} polygon
 */
check(arr, polygon) {
  // 循环函数,绘画拆分的每个正方形方格
  // console.log(arr);
  const loading = this.$loading({
    lock: true,
    text: 'Loading',
    spinner: 'el-icon-loading',
    background: 'rgba(0, 0, 0, 0.7)'
  });
  len = arr.length;

  this.taskNauticaArr = [];
  for (let i = 0; i < len; i++) {
    this.cropdef(arr[i], polygon);
  }
  loading.close();
},
/**
 * 裁剪函数,获取与内多边形 polygons 重合部分
 * @param {Object} obj
 * @param {Object} polygon
 */
cropdef(obj, polygon) {
  let clipped = turf.intersect(obj, polygon); //  裁剪每个小网格与多边形相交的补发部分显示出来
  if (clipped) {
    let clippedArr = clipped.geometry.coordinates[0];
    this.addDrawPolygon(clipped); // 绘画函数
  }
},
/**
 * 绘画函数
 * @param {Object} clipped
 */
addDrawPolygon(clipped) {
  let index = this.polygonArr.length;

  this.polygonArr[index] = {
    draggable: false,
    strokeColor: '#49B2FF',
    fillColor: '#47ABF5',
    fillOpacity: 0.5,
    path: []
  }; //  多边形的样式
  let coordinates = clipped.geometry.coordinates[0];
  let coordinatesLeng = coordinates.length;
  for (let j = 0; j < coordinatesLeng - 1; j++) {
    this.polygonArr[index].path[j] = coordinates[j];
  }
  }

watch中监听鼠标点击的点的经纬度变化

watch: {
/**
 * 当lng的值发生变化是,页面新增点位
 */
 lng() {
  if (this.alertShow != false) {
    let obj = {
      position: [this.lng, this.lat],
      events: {},
      visible: true,
      draggable: false,
      template:
        '<div style="width: 8px;height: 8px;background: #000;border: 2px solid #49B2FF;border-radius: 50%;margin: 28px 0 0 6px"></div>'
    };
    this.markers.push(obj);
    if (this.markers.length > 3 && this.alertShow != false) {
      this.polygons[indexes] = {
        draggable: false,
        strokeColor: '#49B2FF',
        fillColor: '#47ABF5',
        fillOpacity: 0.5,
        path: []
      };
      let arr = []; //  用来存储画的多边形的坐标数组
      let lengthNum = this.markers.length;
      for (let i = 0; i < lengthNum; i++) {
        this.polygons[indexes].path[i] = this.markers[i].position;
        arr[i] = this.markers[i].position;
      }
      arr.push(this.markers[0].position);
      let polygon = turf.polygon([arr], {
        name: 'poly1'
      });
      this.isIntersect(polygon);
    } else {
    }
  }
 }
 }