不知道大家有没有玩过一个“游戏”,就是在地上发现蚂蚁时,可以围绕它,在它周围画一个圈啥的,蚂蚁 🐜 就被圈起来了,出不去了,有种画地为牢的感觉。看过西游记的应该也记得,猴哥出门时总是会给师傅画个圈,也算是画地为牢...
本想进入正题,但是作为圈内人士,受圈内文化熏陶,不得不在此卷一下,凡事问个原理,为什么?
画地为牢 解析
如果没有玩过蚂蚁那个游戏的,可以看看 画地为牢的蚂蚁视频
进入正题吧,自身需求来源就是老板想在地图上画个圈,看看这个圈内范围的都有哪些标记点,这...
其实之前也看到过该类似效果,如链家找房的画圈找房功能,大家可以体验一下!确实做的呢不错。
那就简单的做个demo,先实现一下大概功能:
1、先来个地图吧
由于公司内部用的高德地图居多,所以我们就用高德地图来做这个demo,高德开放平台,记得申请自己的 key 啊!
用 vue2 吧,毕竟该需求还是在 vue2 项目里边做,其他vue3,React 框架用法参考链接,或者开源社区。
准备好的我的页面布局:
<template>
<div class="map-page">
<div id="container" class="map-container" />
<div class="btn-box">
<button v-if="!drawing" class="btn" @click="drawClick">画圈找点</button>
<button v-else class="btn" @click="noDrawClick">退出画圈</button>
</div>
<ul v-if="selectedArr.length>0" class="selectedArr">
<li v-for="item in selectedArr" :key="item[0]">
{{ `${item[2]}:坐标为 [${item[0]},${item[1]}]` }}
</li >
</ul >
</div>
</template>
<style scoped>
.map-page {
height: 100%;
}
.map-container {
height: 100%;
width: 100%;
}
.btn-box {
position: fixed;
top: 50px;
right: 80px;
padding: 12px 15px;
background: #fff;
box-shadow: 0 0 8px 0 rgb(0 0 0 / 8%);
border-radius: 4px;
font-family: PingFangSC-Medium;
font-size: 14px;
color: #333;
cursor: pointer;
display: flex;
justify-content: space-around;
}
.btn-box .btn {
outline: none;
border: none;
font-size: 17px;
color: #fff;
border-radius: 4px;
padding: 8px;
background-color: #909399;
cursor: pointer;
transition: all 0.5s;
}
.btn-box .btn:hover {
background-color: #409EFF;
}
.selectedArr{
position: fixed;
top: 150px;
right: 80px;
background: #fff;
padding: 20px 20px 10px;
}
.selectedArr li{
padding-bottom: 10px;
}
</style>
接下来,整活:
安装地图:
npm i @amap/amap-jsapi-loader --save
初始化地图:
import AMapLoader from '@amap/amap-jsapi-loader';
data(){
return{
AMap: null,
map:null,
}
},
mounted(){
//DOM初始化完成进行地图初始化
this.initMap();
}
methods:{
initMap(){
AMapLoader.load({
key:"", // 申请好的Web端开发者Key,首次调用 load 时必填
version:"2.0", // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
plugins:[''], // 需要使用的的插件列表,如比例尺'AMap.Scale'等
}).then((AMap)=>{
this.map = new AMap.Map("container",{ //设置地图容器id
viewMode:"3D", //是否为3D地图模式
zoom:5, //初始化地图级别
center:[105.602725,37.076636], //初始化地图中心点位置
});
}).catch(e=>{
console.log(e);
})
},
},
基本上得到一个“光秃秃”的地图了:
2、地图上标记的业务坐标点吧
//
dataList: [
[116.351951, 39.929543, '北京国宾酒店'],
[116.404556, 39.92069, '故宫博物院'],
[116.479008, 39.925781, '呼家楼'],
[116.368624, 39.870869, '首都医科大学'],
[116.4471, 39.849601, '宋家庄']
],
// 初始化点
initMapMarkers() {
this.dataList.forEach((item) => {
this.map.add(
new this.AMap.Marker({
position: [item[0], item[1]]
})
)
})
},
3、给按钮绑定地图鼠标事件吧
// 是否处于画圈状态下
drawing: false,
// 开始画圈
drawClick() {
this.map.setStatus({
doubleClickZoom: false,
scrollWheel: false,
dragEnable: false,
keyboardEnable: false
})
this.map.setDefaultCursor('crosshair')
this.drawing = true
},
// 取消画圈
noDrawClick() {
this.map.setStatus({
doubleClickZoom: true,
scrollWheel: true,
dragEnable: true,
keyboardEnable: true
})
this.map.setDefaultCursor('default')
this.drawing = false
this.selectedArr = []
this.map.remove(this.lastPolyLine)
this.map.remove(this.polygonAfterDraw)
}
4、实现地图上的画笔吧
脑暴开始...
如何实现这种类似画笔的画操作?我翻看了高德地图关于画图的所有api后,只发现高德地图提供了绘制圆,直线,多边形,矩形的api,官方示例
玩了半天,感觉其中最符合的就是画矩形或者圆,但是这样达不到那种随意连贯的效果,仔细研究了链家网画的圈,放大地图进行画图查看,发现看似很连贯画出的图在放大状态下是折线组成,这就说明了其实是用线段模拟画圈操作的。那折线api是 AMap.Polyline。
于是,逐渐有了思路。之前做过出行类的行程轨迹之类的功能,这就类似了。如果要是能获取这样一个由不同point组成的数组,然后调用该api就能在地图上画图。但是,如何获取数组?
其实,地图有自带的mousemove事件,获取到mousemove回调里的参数能够获取鼠标在地图上的坐标点。
既然点能获取到了,那么就用一个数组把这些点保存下来用于后续画线操作。然后整个画线逻辑就很明显了:每次mousemove触发都往数组中push当前鼠标所在点,然后调用api进行画线,同时用一个lastPolyLine变量记录下上次画的线,因为每次mousemove触发都会把从头到尾把整个数组的点画出来,所以需要擦除上次画的线段,然后画上新的线段,否则地图上的线段将会重叠起来越积越多。
继续分析,当鼠标抬起时表明画线完成,此时地图上会显示一个有填充颜色的多边形,这个怎么处理?也很简单,用高德地图提供的画多边形的api,AMap.Polygon。
其参数恰好也是一个由Point组成的数组,这个数组就是上述画线的数组。因此当鼠标抬起时,擦除上次画的线,然后再根据polyPointArray绘制一个多边形不就画出了整个圈了么!
脑暴结束...
小二,上代码!
// 是否处于画圈状态下
drawing: false,
// 是否处于鼠标左键按下状态下
isMouseDown: false,
// 存储画出折线点的数组
polyPointArray: [],
// 上次操作画出的折线
lastPolyLine: null,
// 画圈完成后生成的多边形
polygonAfterDraw: null
// 初始化鼠标画圈事件
initMapMouseHandle(AMap) {
this.map.on('mousemove', (e) => {
if (this.isMouseDown) {
const point = [e.lnglat.lng, e.lnglat.lat]
this.polyPointArray.push(point)
if (this.lastPolyLine) {
this.map.remove(this.lastPolyLine)
}
const polyline = new AMap.Polyline({
path: this.polyPointArray,
strokeColor: '#00ae66',
strokeOpacity: 2
})
this.map.add(polyline)
this.lastPolyLine = polyline
}
})
this.map.on('mousedown', () => {
if (this.drawing) {
this.lastPolyLine && this.map.remove(this.lastPolyLine)
this.lastPolyLine = null
this.polyPointArray = []
this.isMouseDown = true
}
})
document.addEventListener('mouseup', () => {
if (this.drawing && this.isMouseDown) {
// 退出画线状态
this.isMouseDown = false
// 添加多边形覆盖物,设置为禁止点击
this.polygonAfterDraw = new AMap.Polygon({
path: this.polyPointArray,
strokeColor: '#00ae66',
strokeOpacity: 1,
fillColor: '#00ae66',
fillOpacity: 0.3
})
this.map.add(this.polygonAfterDraw)
}
})
},
5、收集圈中标记点吧
这块的难点就是判断我们的标记点那些在这个圈内,看看高德地图是否有相关api。
找啊找啊找“朋友”,找到一个“好朋友”---AMap.GeometryUtil.isPointInPolygon
selectedArr: []
// 收集圈内点的数据
isInPolygon() {
this.dataList.forEach((item) => {
if (
this.AMap.GeometryUtil.isPointInPolygon(item, this.polyPointArray)
) {
this.selectedArr.push(item)
}
})
},
最终的展示效果:
至此,一个简单的“画地为牢”的demo就出来了!有相关功能的需求,大家可以尝试一下,有好的建议也请留言,帮助改进!下边附上整体最终代码!
<template>
<div class="map-page">
<div id="container" class="map-container" />
<div class="btn-box">
<button v-if="!drawing" class="btn" @click="drawClick">画圈找点</button>
<button v-else class="btn" @click="noDrawClick">退出画圈</button>
</div>
<ul v-if="selectedArr.length>0" class="selectedArr">
<li v-for="item in selectedArr" :key="item[0]">
{{ `${item[2]}:坐标为 [${item[0]},${item[1]}]` }}
</li >
</ul >
</div>
</template>
<script>
import AMapLoader from '@amap/amap-jsapi-loader'
export default {
name: 'Map',
data() {
return {
AMap: null, // 保存地图API,防止报错
map: null,
dataList: [
[116.351951, 39.929543, '北京国宾酒店'],
[116.404556, 39.92069, '故宫博物院'],
[116.479008, 39.925781, '呼家楼'],
[116.368624, 39.870869, '首都医科大学'],
[116.4471, 39.849601, '宋家庄']
],
// 是否处于画圈状态下
drawing: false,
// 是否处于鼠标左键按下状态下
isMouseDown: false,
// 存储画出折线点的数组
polyPointArray: [],
// 上次操作画出的折线
lastPolyLine: null,
// 画圈完成后生成的多边形
polygonAfterDraw: null,
selectedArr: []
}
},
mounted() {
this.initMap()
},
methods: {
// 初始化地图数据
initMap() {
AMapLoader.load({
key: '', // 申请好的Web端开发者Key,首次调用 load 时必填
version: '2.0', // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
plugins: [''] // 需要使用的的插件列表,如比例尺'AMap.Scale'等
})
.then((AMap) => {
this.map = new AMap.Map('container', {
// 设置地图容器id
// viewMode: '3D', // 是否为3D地图模式
zoom: 12, // 初始化地图级别
center: [116.404, 39.915] // 初始化地图中心点位置
})
this.AMap = AMap
//
this.initMapMarkers(AMap)
this.initMapMouseHandle(AMap)
})
.catch((e) => {
console.log(e)
})
},
// 初始化点
initMapMarkers(AMap) {
this.dataList.forEach((item) => {
this.map.add(
new AMap.Marker({
position: [item[0], item[1]]
})
)
})
},
// 初始化鼠标画圈事件
initMapMouseHandle(AMap) {
this.map.on('mousemove', (e) => {
if (this.isMouseDown) {
const point = [e.lnglat.lng, e.lnglat.lat]
this.polyPointArray.push(point)
if (this.lastPolyLine) {
this.map.remove(this.lastPolyLine)
}
const polyline = new AMap.Polyline({
path: this.polyPointArray,
strokeColor: '#00ae66',
strokeOpacity: 2
})
this.map.add(polyline)
this.lastPolyLine = polyline
}
})
this.map.on('mousedown', () => {
if (this.drawing) {
this.lastPolyLine && this.map.remove(this.lastPolyLine)
this.lastPolyLine = null
this.polyPointArray = []
this.isMouseDown = true
}
})
// todo 禁用scrollWheel属性map.on('mouseup')不生效?
document.addEventListener('mouseup', () => {
if (this.drawing && this.isMouseDown) {
// 退出画线状态
this.isMouseDown = false
// 添加多边形覆盖物,设置为禁止点击
this.polygonAfterDraw = new AMap.Polygon({
path: this.polyPointArray,
strokeColor: '#00ae66',
strokeOpacity: 1,
fillColor: '#00ae66',
fillOpacity: 0.3
})
this.map.add(this.polygonAfterDraw)
this.isInPolygon()
}
})
},
// 收集圈内点的数据
isInPolygon() {
this.dataList.forEach((item) => {
if (
this.AMap.GeometryUtil.isPointInPolygon(item, this.polyPointArray)
) {
this.selectedArr.push(item)
}
})
},
// 开始画圈
drawClick() {
this.map.setStatus({
doubleClickZoom: false,
scrollWheel: false,
dragEnable: false,
keyboardEnable: false
})
this.map.setDefaultCursor('crosshair')
this.drawing = true
},
// 取消画圈
noDrawClick() {
this.map.setStatus({
doubleClickZoom: true,
scrollWheel: true,
dragEnable: true,
keyboardEnable: true
})
this.map.setDefaultCursor('default')
this.drawing = false
this.selectedArr = []
this.map.remove(this.lastPolyLine)
this.map.remove(this.polygonAfterDraw)
}
}
}
</script>
<style scoped>
.map-page {
height: 100%;
}
.map-container {
height: 100%;
width: 100%;
}
.btn-box {
position: fixed;
top: 50px;
right: 80px;
padding: 12px 15px;
background: #fff;
box-shadow: 0 0 8px 0 rgb(0 0 0 / 8%);
border-radius: 4px;
font-family: PingFangSC-Medium;
font-size: 14px;
color: #333;
cursor: pointer;
display: flex;
justify-content: space-around;
}
.btn-box .btn {
outline: none;
border: none;
font-size: 17px;
color: #fff;
border-radius: 4px;
padding: 8px;
background-color: #909399;
cursor: pointer;
transition: all 0.5s;
}
.btn-box .btn:hover {
background-color: #409EFF;
}
.selectedArr{
position: fixed;
top: 150px;
right: 80px;
background: #fff;
padding: 20px 20px 10px;
}
.selectedArr li{
padding-bottom: 10px;
}
</style>