高德地图电子围栏/地图选区/地图打点
前言
今天鸡米花为大家带来的是基于高德地图的电子围栏效果,可以实现地图选区、选区编辑、选区删除等功能,这里我就跳过高德地图的初始化了,如果有需要的同学可以去我往期博客里看一下。
效果图


一、引入插件
首先,我们需要在引入我们开发中所需要的高德地图插件,首先是创建多边形使用到MouseTool工具,右键点击多边形的菜单插件ContextMenu,以及多边形图形编辑工具PolygonEditor,以下引入为示例:
this.map = new AMap.Map('myMap', {zoom: 12, //级别center: [120.209758, 30.246809], //中心点坐标
});
AMap.plugin(['AMap.MouseTool', 'AMap.ContextMenu', 'AMap.PolygonEditor'], () => {this.mouseTool = new AMap.MouseTool(this.map);this.mouseTool.on('draw', this.measureAreaOver);
});
其中measureAreaOver为创建多边形后的回调,下面会详细解释。
二、多边形的创建
创建多边形很简单,就只需要调用我们在上面引入的插件this.mouseTool的polygon方法即可 ,这里this.mouseTool不仅支持创建多边形,也支持创建点、线,矩形和圆形。具体配置项可参考mouseTool文档,以下为我的引入示例:传入的为多边形的配置项,比如面的颜色和透明度,边框的颜色等,具体也可参照官方文档。
this.mouseTool.polygon({strokeColor: "#000",fillColor: "#aaa",fillOpacity: 0.3,
});
三、多边形保存
创建好多边形后,会触发我们在上面定义的measureAreaOver方法,代码如下:
measureAreaOver(e) {// 增加覆盖物右键点击事件e.obj.on('rightclick', this.polygonRightClick);// 增加覆盖物提示文本let { areaName } = this.areaTypeList.find(item => item.areaType == this.areaTypeActive) || {};let textTitle = areaName;let updateData = {};if (!textTitle) {updateData = e.obj.getExtData();textTitle = updateData.areaName;}// 通过时间戳生成唯一标识,仅供前端使用let timer = Number(new Date());e.obj.setExtData(timer);// 将文字标签设置在多边形的第一个点周围let textPosition = e.obj.getPath()[0];let textContent = `${textTitle}:${(e.obj.getArea() / 1000000).toFixed(4)}平方公里`;// 创建文字标签var text = new AMap.Text({text: textContent,position: textPosition,style: {'font-size': '12px',},extData: timer,});text.setMap(this.map);// 将覆盖物信息整合加入到全局let area = this.areaTypeList.find(item => item.areaType == this.areaTypeActive) || {};area = {...updateData,...area,area: e.obj.getArea(),path: e.obj.getPath(),data: e.obj,type: 'polygon',};this.areaList.push(area);this.areaTypeActive = '';// 关闭创建多边形if (this.mouseTool) this.mouseTool.close();
},
其中polygonRightClick方法为多边形右键点击事件,我们在整个事件里做多边形的编辑和删除。
四、多边形的编辑和删除
创建好多边形后,就会使用到插件ContextMenu,这个插件主要是在地图区域创建右键菜单,我们只需要给多边形区域绑定右键点击事件,再在事件内调起ContextMenu即可。
多边形的编辑,就是将多边形的经纬度传入到PolygonEditor插件中;
多边形编辑后的保存,则就是更新我们在创建多边形存在全局的数据;
多边形删除,就是调用高德地图删除覆盖物的方法。
编辑、删除、和保存的代码如下:
// 多边形右键点击事件
polygonRightClick(event) {this.contextMenu = new AMap.ContextMenu();// 如果多边形为编辑状态,则右键菜单展示保存,反之则显示编辑和删除if (this.polygonEditor && this.polygonEditor.getTarget()) {this.contextMenu.addItem('保存', this.measureAreaSave, 0);} else {this.contextMenu.addItem('编辑', this.measureAreaUpdate, 0);this.contextMenu.addItem('删除', this.measureAreaDel, 1);}// 打开右键菜单this.contextMenu.open(this.map, event.lnglat);// 更新右键菜单的位置this.contextMenuPosition = event.lnglat;
},
// 面积编辑事件
measureAreaUpdate(e) {// 关闭右键菜单this.contextMenu.close();let { areaData } = this.getClickArea();this.polygonEditor = new AMap.PolygonEditor(this.map, areaData.data);this.polygonEditor.open();
},
// 编辑面积保存
measureAreaSave() {this.polygonEditor.close();this.polygonEditor = null;let { areaData, text, areaIndex } = this.getClickArea();text.setText(`${areaData.areaName}:${(areaData.data.getArea() / 1000000).toFixed(2)}平方公里`);let updateArea = {...areaData,area: areaData.data.getArea(),path: areaData.data.getPath(),};text.setPosition(updateArea.path[0]);this.areaList[areaIndex] = updateArea;
},
// 删除覆盖物
measureAreaDel() {this.contextMenu.close();let { areaData, text, areaIndex } = this.getClickArea() || {};if (text && text?.destroy) text.destroy(null);if (areaData?.data?.destroy) areaData.data.destroy(null);this.areaList = this.areaList.filter((item, index) => index !== areaIndex);
},
五、完整代码
<!-- * @description: 面积新增或修改* @fileName: areaOperate.vue * @author: jmh * @date: 2025-10-16
!-->
<template><div class="areaOperate"><div id="myMap"></div><div class="areaOperateBar"><ul><li:class="`areaOperateBarItem ${areaTypeActive == item.areaType ? 'areaOperateBarActive' : ''}`"v-for="item in areaTypeList":style="`--trendsColor:${item.colorType};`"@click.stop="measureArea(item)"><div class="barButton sl"><el-tooltip :content="item.areaName"><span>{{ item.areaName }}</span></el-tooltip></div></li></ul><div class="areaOperateBarItem" @click="closeMeasureArea"><div class="barButton">清除所有</div><div class="colorPicker"><i class="el-icon-circle-close"></i></div></div><div class="areaOperateBarItem" @click="onSubmit"><div class="barButton">确 定</div><div class="colorPicker"><i class="el-icon-check"></i></div></div></div></div>
</template><script>
export default {data() {return {// 中心坐标 默认为杭州市政府的坐标initDrop: [120.209758, 30.246809],// 地图实例map: null,zoom: 12,mouseTool: null,areaTypeList: [],areaTypeActive: '',areaList: [],contextMenu: null,contextMenuPosition: null,polygonEditor: null,areaTypeAddShow: false,areaTypeAddFrom: {colorType: '#409eff',areaName: '',},areaData: [],};},created() {this.areaTypeList = [{id: 1,areaType: '1',areaName: '占地面积',colorType: '#ec3636',orderNo: 1,delFlag: 'N',remark: null,},{id: 2,areaType: '2',areaName: '使用面积',colorType: '#3f2ec2',orderNo: 2,delFlag: 'N',remark: null,},{id: 3,areaType: '7',areaName: '出租',colorType: '#00f529',orderNo: 7,delFlag: 'N',remark: null,},{id: 4,areaType: '5',areaName: '办公',colorType: '#0824f7',orderNo: 5,delFlag: 'N',remark: null,},{id: 5,areaType: '4',areaName: '闲置',colorType: '#f9c301',orderNo: 4,delFlag: 'N',remark: null,},{id: 6,areaType: '6',areaName: '拌合楼',colorType: '#a78b8b',orderNo: 6,delFlag: 'N',remark: null,},{id: 7,areaType: '8',areaName: '代征地',colorType: '#20bc56',orderNo: 8,delFlag: 'N',remark: null,},{id: 8,areaType: '3',areaName: '盾构基地',colorType: '#44ff00',orderNo: 3,delFlag: 'N',remark: null,},{id: 9,areaType: '9',areaName: '政府美化',colorType: '#29d67d',orderNo: 9,delFlag: 'N',remark: null,},{id: 10,areaType: '10',areaName: '暂时出租土地',colorType: '#ff0000',orderNo: 10,delFlag: 'N',remark: null,},];},mounted() {this.initMap();},methods: {initMap(centerToe) {var centerToe = centerToe || this.initDrop;this.loadMap(centerToe).then(() => {this.areaData = [{type: 'marker',position: [120.529364, 30.356372],},{type: 'polygon',path: [['120.529011', '30.356501'],['120.528988', '30.35636'],['120.529846', '30.356193'],['120.529892', '30.356329'],['120.529871', '30.356325'],],searchValue: null,createBy: null,createTime: null,updateBy: null,updateTime: null,delFlag: 'N',remark: null,orderBy: null,params: {},id: 38,mainId: 960,coordinateGroup: '120.529011,30.356501;120.528988,30.35636;120.529846,30.356193;120.529892,30.356329;120.529871,30.356325',actualArea: 1312.1,colorType: '#ec3636',areaType: '1',areaName: '占地面积',},];// 初始化传入数据时回显let isInit = true;if (isInit) this.initData();});},// 开始绘制measureArea(row) {let { areaType, colorType } = row;this.areaTypeActive = areaType;this.mouseTool.polygon({strokeColor: colorType,fillColor: colorType,fillOpacity: 0.3,});},// 关闭绘制面积closeMeasureArea() {if (this.polygonEditor) this.polygonEditor.close();this.areaTypeActive = '';this.map.clearMap();this.mouseTool.close(true);this.areaList = [];},// 绘制面积结束measureAreaOver(e) {// 增加覆盖物右键点击事件e.obj.on('rightclick', this.polygonRightClick);e.obj.on('click', this.mapClick);// 增加覆盖物提示文本let { areaName } = this.areaTypeList.find(item => item.areaType == this.areaTypeActive) || {};let textTitle = areaName;let updateData = {};if (!textTitle) {updateData = e.obj.getExtData();textTitle = updateData.areaName;}let timer = Number(new Date());e.obj.setExtData(timer);let textPosition = e.obj.getPath()[0];let textContent = `${textTitle}:${(e.obj.getArea() / 1000000).toFixed(4)}平方公里`;var text = new AMap.Text({text: textContent,position: textPosition,style: {'font-size': '12px',},extData: timer,});text.setMap(this.map);// 将覆盖物信息整合加入到全局let area = this.areaTypeList.find(item => item.areaType == this.areaTypeActive) || {};area = {...updateData,...area,area: e.obj.getArea(),path: e.obj.getPath(),data: e.obj,type: 'polygon',};this.areaList.push(area);this.areaTypeActive = '';if (this.mouseTool) this.mouseTool.close();},// 获取右键点击的面积信息getClickArea() {let delAreaList = this.areaList.filter(item => {let { lng, lat } = this.contextMenuPosition || {};return item.data.contains(new AMap.LngLat(lng, lat));});if (!delAreaList?.length) return {};let delArea = delAreaList[delAreaList.length - 1];let allOverlays = this.map.getAllOverlays('text');let textMark = allOverlays.find(item => item.getExtData() == delArea.data.getExtData());let areaIndex = this.areaList.findIndex(item => item.data.getExtData() == delArea.data.getExtData());return { areaData: delArea, text: textMark, areaIndex };},// 面积编辑事件measureAreaUpdate(e) {this.contextMenu.close();let { areaData } = this.getClickArea();this.polygonEditor = new AMap.PolygonEditor(this.map, areaData.data);this.polygonEditor.open();},// 编辑面积保存measureAreaSave() {this.polygonEditor.close();this.polygonEditor = null;let { areaData, text, areaIndex } = this.getClickArea();text.setText(`${areaData.areaName}:${(areaData.data.getArea() / 1000000).toFixed(2)}平方公里`);let updateArea = {...areaData,area: areaData.data.getArea(),path: areaData.data.getPath(),};text.setPosition(updateArea.path[0]);this.areaList[areaIndex] = updateArea;},// 删除覆盖物measureAreaDel() {this.contextMenu.close();let { areaData, text, areaIndex } = this.getClickArea() || {};if (text && text?.destroy) text.destroy(null);if (areaData?.data?.destroy) areaData.data.destroy(null);this.areaList = this.areaList.filter((item, index) => index !== areaIndex);},// 加载地图loadMap(centerToe) {var centerToe = centerToe || this.initDrop;return new Promise((resolve, reject) => {this.map = new AMap.Map('myMap', {zoom: this.zoom, //级别center: centerToe, //中心点坐标});AMap.plugin(['AMap.MapType', 'AMap.MouseTool', 'AMap.ContextMenu', 'AMap.PolygonEditor'], () => {// 右键点击this.map.addControl(new AMap.MapType());this.mouseTool = new AMap.MouseTool(this.map);this.mouseTool.on('draw', this.measureAreaOver);});this.map.on('click', this.mapClick);resolve();});},initData() {this.areaData.forEach(item => {let { type } = item;switch (type) {case 'marker':this.mapMarker = this.createdMarker(item?.position || [], item?.content || null, item);break;case 'polygon':this.createPolygon(item?.path || [], item);break;default:break;}});if (!this.areaList?.length && this.mapMarker) {this.map.setCenter(this.mapMarker.getPosition());}},// 创建面createPolygon(path, row) {let { colorType } = row;colorType = colorType || this.rgb();var path = path.map(item => {if (!(item instanceof Array)) {return item;}return new AMap.LngLat(item[0], item[1]);});// 创建多边形 Polygon 实例const polygon = new AMap.Polygon({path: path,fillColor: colorType, // 多边形填充颜色fillOpacity: 0.3,strokeColor: colorType, // 线条颜色extData: { ...row },});this.measureAreaOver({ obj: polygon });this.map.add(polygon);this.map.setFitView([polygon]);},// 创建点createdMarker(LngLat, content, row) {let { colorType } = row || {};const position = LngLat;if (content) var content = `<div class='contentMarker' style='color:${colorType}'>${content}</div>`;const marker = new AMap.Marker({position: position,content,});this.map.add(marker);return marker;},rgb() {//rgb颜色随机const r = Math.floor(Math.random() * 256);const g = Math.floor(Math.random() * 256);const b = Math.floor(Math.random() * 256);return `rgb(${r},${g},${b})`;},// 地图点击事件mapClick(e) {if (this.areaTypeActive) return;if (this.mapMarker) this.mapMarker.remove();let LngLat = e.lnglat;this.mapMarker = this.createdMarker(LngLat);},polygonRightClick(event) {this.contextMenu = new AMap.ContextMenu();if (this.polygonEditor && this.polygonEditor.getTarget()) {this.contextMenu.addItem('保存', this.measureAreaSave, 0);} else {this.contextMenu.addItem('编辑', this.measureAreaUpdate, 0);this.contextMenu.addItem('删除', this.measureAreaDel, 1);}this.contextMenu.open(this.map, event.lnglat);this.contextMenuPosition = event.lnglat;},onSubmit() {let position = null;if (this.mapMarker) {position = this.mapMarker.getPosition();}this.$emit('change', this.areaList, position);},},beforeDestroy() {try {if (this.map) this.map.destroy();} catch (error) {console.log('error :>> ', error);}},
};
</script><style lang="scss" scoped>
.areaOperate {width: 100%;height: 70vh;position: relative;.sl {/* 强制不换行 */white-space: nowrap;overflow: hidden;/* 文本超出省略 */text-overflow: ellipsis;}#myMap {width: 100%;height: 100%;&::v-deep .amap-marker-label {color: #666;transform: translate(0, -6px);font-size: 12px;line-height: 1.5;}&::v-deep .amap-marker-label {background-color: #ffffffe3;border: 0;padding: 5px 10px;border-radius: 3px;}&::v-deep .amap-ctrl-overlay-layer,&::v-deep .amap-logo,&::v-deep .amap-copyright {display: none !important;}&::v-deep .amap-markers:has(.contentMarker) {position: static;.contentMarker {font-size: 12px;background-color: #ffffffcb;border: 0;padding: 3px 10px;border-radius: 3px;}}}.areaOperateBar {position: absolute;right: 10px;bottom: 10px;z-index: 11;background-color: #ffffff5b;width: 150px;box-sizing: border-box;padding: 5px 12px;border-radius: 5px;backdrop-filter: blur(5px);font-size: 12px;> ul {max-height: 200px;overflow-y: auto;}.areaOperateBarItem {color: var(--trendsColor);padding: 5px 10px;margin: 5px 0;border-radius: 500px;text-align: center;border: 1px solid;display: flex;align-items: center;transition: 0.3s;&:hover {opacity: 0.65;}.colorPicker {position: relative;font-size: 14px;}.barButton {flex: 1;cursor: pointer;}&::v-deep .el-color-picker {top: 0;opacity: 0;bottom: 0;left: 0;right: 0;margin: auto;position: absolute;.el-color-picker__trigger {width: 100%;height: 100%;}}}> div.areaOperateBarItem {background-color: #409eff;color: #fff;border-color: #409eff;}.areaOperateBarActive {background-color: var(--trendsColor);.barButton,.colorPicker {filter: grayscale(1) contrast(999) invert(1);}}}
}
</style>
结语
以上就是我今天分享的全部内容啦,在完整代码里,复制之后可以完整的运行上述功能,只不过在地图初始化的时候,可能要根据项目中引入高德地图的方式做一些调整,在使用中有任何问题欢迎在评论区有留言或私信我,如果觉得我的分享有帮助的话,别忘了给鸡米花一个点赞收藏~