最终效果
技术栈
项目中使用到的技术 高德基于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 {
}
}
}
}