高德地图电子围栏/地图选区/地图打点

前言

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

效果图


一、引入插件

首先,我们需要在引入我们开发中所需要的高德地图插件,首先是创建多边形使用到MouseTool工具,右键点击多边形的菜单插件ContextMenu,以及多边形图形编辑工具PolygonEditor,以下引入为示例:

javascript 复制代码
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文档,以下为我的引入示例:传入的为多边形的配置项,比如面的颜色和透明度,边框的颜色等,具体也可参照官方文档

javascript 复制代码
this.mouseTool.polygon({
    strokeColor: "#000",
    fillColor: "#aaa",
    fillOpacity: 0.3,
});

三、多边形保存

创建好多边形后,会触发我们在上面定义的measureAreaOver方法,代码如下:

javascript 复制代码
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插件中;

多边形编辑后的保存,则就是更新我们在创建多边形存在全局的数据;

多边形删除,就是调用高德地图删除覆盖物的方法。

编辑、删除、和保存的代码如下:

javascript 复制代码
// 多边形右键点击事件
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);
},

五、完整代码

html 复制代码
<!-- 
 * @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>

结语

以上就是我今天分享的全部内容啦,在完整代码里,复制之后可以完整的运行上述功能,只不过在地图初始化的时候,可能要根据项目中引入高德地图的方式做一些调整,在使用中有任何问题欢迎在评论区有留言或私信我,如果觉得我的分享有帮助的话,别忘了给鸡米花一个点赞收藏~

相关推荐
知识分享小能手2 小时前
uni-app 入门学习教程,从入门到精通,uni-app基础扩展 —— 详细知识点与案例(3)
vue.js·学习·ui·微信小程序·小程序·uni-app·编程
demi_meng3 小时前
reactNative 遇到的问题记录
javascript·react native·react.js
MC丶科3 小时前
【SpringBoot 快速上手实战系列】5 分钟用 Spring Boot 搭建一个用户管理系统(含前后端分离)!新手也能一次跑通!
java·vue.js·spring boot·后端
千码君20164 小时前
React Native:从react的解构看编程众多语言中的解构
java·javascript·python·react native·react.js·解包·解构
lijun_xiao20096 小时前
前端最新Vue2+Vue3基础入门到实战项目全套教程
前端
90后的晨仔6 小时前
Pinia 状态管理原理与实战全解析
前端·vue.js
杰克尼6 小时前
JavaWeb_p165部门管理
java·开发语言·前端
EndingCoder6 小时前
WebSocket实时通信:Socket.io
服务器·javascript·网络·websocket·网络协议·node.js
90后的晨仔6 小时前
Vue3 状态管理完全指南:从响应式 API 到 Pinia
前端·vue.js
90后的晨仔6 小时前
Vue 内置组件全解析:提升开发效率的五大神器
前端·vue.js