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

前言

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

效果图


一、引入插件

首先,我们需要在引入我们开发中所需要的高德地图插件,首先是创建多边形使用到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>

结语

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

相关推荐
崔庆才丨静觅7 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60618 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了8 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅8 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅9 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅9 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment9 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅9 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊9 小时前
jwt介绍
前端
爱敲代码的小鱼9 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax