使用高德地图实现画多边形、矩形、圆,并进行编辑保存和回显。
1、准备工作
参考高德地图官网,进行项目key申请,链接: 准备
2、项目安装依赖
npm i @amap/amap-jsapi-loader --save
3、地图容器
html
<template>
<!-- 绘制地图区域组件(圆、矩形、多边形) -->
<el-dialog
v-model="state.showDialog"
:title="props.title"
@close="handleClose"
destroy-on-close
:close-on-click-modal="false"
:close-on-press-escape="false"
align-center
draggable
:style="{ padding: 0, width: dialogWidth, height: dialogHeight }">
<div class="position" v-if="state.showDialog">
<div class="hover-txt" :style="{left: state.pointX+'px', top: state.pointY+'px'}">{{ hoverTxt }}</div>
<div ref="mapRef" id="container"></div>
<div class="search_box">
<div class="change-btn">
<el-button :type="state.stateLayer === 0 ? '' : 'primary'" @click="changeMap"> 卫星图 </el-button>
<el-button :type="state.stateLayer === 1 ? '' : 'primary'" @click="changeMap"> 标准地图 </el-button>
</div>
<div class="btn">
<el-button
type="primary"
@click="handleEditor(1)"
:disabled="state.drawType === 2 || state.drawType === 3 || props.isView">
{{ state.areaLayer && state.drawType === 1 ? '编辑圆' : '添加圆' }}
</el-button>
<el-button
type="primary"
@click="handleEditor(2)"
:disabled="state.drawType === 1 || state.drawType === 3 || props.isView">
{{ state.areaLayer && state.drawType === 2 ? '编辑矩形' : '添加矩形' }}
</el-button>
<el-button
type="primary"
@click="handleEditor(3)"
:disabled="state.drawType === 1 || state.drawType === 2 || props.isView">
{{ state.areaLayer && state.drawType === 3 ? '编辑多边形' : '添加多边形' }}
</el-button>
<el-button type="danger" @click="handleClear" :disabled="props.isView">清除</el-button>
<el-button type="primary" @click="handleEmit" :disabled="props.isView">完成</el-button>
</div>
</div>
</div>
</el-dialog>
</template>
css
<style lang="scss" scoped>
.position {
box-sizing: border-box;
padding: 0 20px;
width: 100%;
height: 100%;
position: relative;
#container {
width: 100%;
height: 100%;
}
.hover-txt {
position: absolute;
z-index: 50;
border: 1px solid #303133;
background: #fff;
color: #303133;
padding: 2px 4px;
}
.search_box {
width: 100%;
height: 50px;
background-color: #fff;
position: absolute;
top: 0;
left: 20px;
.change-btn {
position: absolute;
right: 40px;
}
.btn {
margin-right: 20px;
display: flex;
align-items: center;
.search_input {
width: 100px;
margin-right: 10px;
}
}
}
}
</style>
4、业务逻辑
需求是只画多边形、矩形、圆三者中的一个,保存完之后再进行编辑。如果实现画多个图形,须把保存图层变量换成数组,每次编辑完之后,drawState要恢复为新增状态,让按钮可点击。
<script lang="ts" setup>
import { onBeforeUnmount, onMounted, reactive, shallowRef, computed } from 'vue';
import AMapLoader from '@amap/amap-jsapi-loader';
import { getCompanyInfo } from '@/api/base/enterpriseInfo';
import { ElMessage } from 'element-plus';
const props = defineProps({
// 区域范围
areaRangeInfo: {
type: String,
default: '',
},
// 弹窗大小
size: {
type: Number,
default: 2,
},
// 查看定位
isView: {
type: Boolean,
default: false,
},
// 弹窗标题
title: {
type: String,
default: '选取区域',
},
});
//一定要加上密钥,否则服务类插件会失效,个人账户每天只有5000次提示机会
(window as any)._AMapSecurityConfig = {
key: 'xxxxxxxxxxxxxxxxxxxxxxxxx',
securityJsCode: 'xxxxxxxxxx',
};
onBeforeUnmount(() => {
// 移除监听
map.off('click', (e: any) => {
console.log('移除点击事件');
});
// 移除悬浮事件
if(state.mousemoveEvent) {
state.mousemoveEvent.removeEventListener('mousemove', (e:any) => {
state.pointX = -500;
state.pointY = -500;
});
}
});
onMounted(async () => {
if(props.areaRangeInfo) {
state.areaLayerInfo = JSON.parse(props.areaRangeInfo);
}
if (state.areaLayerInfo && state.areaLayerInfo.type && state.areaLayerInfo.type != 0) {
state.drawType = Number(state.areaLayerInfo.type);
state.drawState = 1;
state.lng = state.areaLayerInfo.center[0];
state.lat = state.areaLayerInfo.center[1];
} else {
state.drawState = -1;
await getCompanyInfo('').then((res: any) => {
if (res.code == 0) {
if (!res.data.dzLng || !res.data.dzLat) {
state.lng = 116.397935;
state.lat = 39.900402;
} else {
state.lng = Number(res.data.dzLng);
state.lat = Number(res.data.dzLat);
}
}
});
}
await initMap();
});
let map = shallowRef(null as any); // 高德地图实例
const state = reactive({
pointX: -500,
pointY: -500,
lng: 0 as number, // 经度
lat: 0 as number, // 纬度
AMap: null as any, // 高德地图对象
showDialog: true, // 控制弹窗状态
stateLayer: 1 as number, // 0 街道 1 卫星
drawType: 0 as number, // 绘制图形类型 0 未绘制 1 圆 2 矩形 3 多边形
drawState: -1 as number, // -1 未绘制或已结束绘制 0 新增状态 1 编辑状态
areaEditor: null as any, // 画圆、矩形、多边形的map实例对象
linePoints: [] as any, // 点位坐标
areaLayer: null as any, // 画的图层实例
mouseTool: null as any, // 绘图工具
mousemoveEvent: null as any,
areaLayerInfo: null as any, // 图层位置信息
});
const emits = defineEmits(['submit', 'handleClose']);
const dialogWidth = computed(() => {
if (props.size == 1) {
return '86%';
} else if (props.size == 2) {
return '78%';
} else {
return '72%';
}
});
const dialogHeight = computed(() => {
if (props.size == 1) {
return '90vh';
} else if (props.size == 2) {
return '82vh';
} else {
return '76vh';
}
});
let satelliteLayer: any = null;
let defaultStreet: any = null;
// 初始化地图
const initMap = () => {
AMapLoader.load({
key: '1e7b6041cfc63f32e8eafdf98005e45f',
version: '2.0',
plugins: [
'AMap.ToolBar', // 工具条,控制地图的缩放、平移
'AMap.Scale', // 比例尺
'AMap.ControlBar', // 组合了旋转、倾斜、复位在内的地图控件
'AMap.Geolocation', // 定位插件
],
}).then((AMap: any) => {
state.AMap = AMap;
map = new AMap.Map('container', {
viewMode: '2D',
zoom: 17,
center: [state.lng, state.lat],
});
// 添加卫星图覆盖
satelliteLayer = new AMap.TileLayer.Satellite();
// 默认矢量街道图
defaultStreet = new AMap.TileLayer();
map.add([satelliteLayer]);
// 加载工具类插件
map.addControl(new AMap.ToolBar({position:{right: '40px', top: '160px'}}));
map.addControl(new AMap.Scale());
map.addControl(new AMap.ControlBar({position:{right: '10px', top: '60px'}}));
map.addControl(new AMap.Geolocation());
// 判断当前区域类型
if(state.areaLayerInfo && state.areaLayerInfo.type) {
// 绘制图形类型 0 未绘制 1 圆 2 矩形 3 多边形
state.drawType = Number(state.areaLayerInfo.type);
if(state.drawType == 1) {
state.areaLayer = new AMap.Circle({
center: state.areaLayerInfo.path,
radius: state.areaLayerInfo.radius, //半径
strokeColor: '#1791FC',
strokeOpacity: 1,
strokeWeight: 2,
fillColor: '#1791FC',
fillOpacity: 0.4,
cursor:'pointer',
strokeStyle: 'solid'
});
map.add(state.areaLayer);
} else if(state.drawType == 2) {
let southWest:any = new AMap.LngLat(state.areaLayerInfo.southWest[0], state.areaLayerInfo.southWest[1])
let northEast:any = new AMap.LngLat(state.areaLayerInfo.northEast[0], state.areaLayerInfo.northEast[1])
let bounds:any = new AMap.Bounds(southWest, northEast)
state.areaLayer = new AMap.Rectangle({
bounds: bounds,
strokeColor: '#1791FC',
strokeOpacity: 1,
strokeWeight: 2,
fillColor: '#1791FC',
fillOpacity: 0.4,
cursor:'pointer',
strokeStyle: 'solid'
});
map.add(state.areaLayer);
} else if(state.drawType == 3) {
state.areaLayer = new AMap.Polygon({
path: state.areaLayerInfo.path,
strokeColor: '#1791FC',
strokeOpacity: 1,
strokeWeight: 2,
fillColor: '#1791FC',
fillOpacity: 0.4,
cursor:'pointer',
strokeStyle: 'solid'
});
map.add(state.areaLayer);
}
}
// 地图点击事件
map.on('click', (e: any) => {
// console.log(e)
});
let domEvent:any = document.getElementById('container');
state.mousemoveEvent = domEvent.addEventListener('mousemove', (e:any) => {
if(state.drawType !== 0 && state.drawState !== -1) {
state.pointX = e.offsetX + 30;
state.pointY = e.offsetY + 15;
}
});
})
};
// 获取图层位置信息
const getAreaLayerInfo = () => {
let centerInfo:any = map.getCenter();
if(state.drawType === 1) {
// 圆
let centerPoint:any = state.areaLayer.getCenter();
let radius:any = state.areaLayer.getRadius();
state.areaLayerInfo = {
type: '1',
path: [centerPoint.lng, centerPoint.lat],
radius: radius,
center: [centerInfo.lng, centerInfo.lat]
}
} else if(state.drawType === 2) {
let pathObj:any = state.areaLayer.getBounds();
state.areaLayerInfo = {
type: state.drawType,
path: [[pathObj.southWest.lng,pathObj.southWest.lat],[pathObj.northEast.lng,pathObj.northEast.lat]],
southWest: [pathObj.southWest.lng,pathObj.southWest.lat],
northEast: [pathObj.northEast.lng,pathObj.northEast.lat],
center: [centerInfo.lng, centerInfo.lat]
}
} else if(state.drawType === 3) {
// 多边形
let pathArr:Array<any> = state.areaLayer.getPath();
let pointArr:Array<any> = [];
if(pathArr.length > 0) {
pathArr.map((point:any) => {
pointArr.push([point.lng, point.lat])
});
state.areaLayerInfo = {
type: state.drawType,
path: pointArr,
center: [centerInfo.lng, centerInfo.lat]
}
} else {
state.areaLayerInfo = {
type: '0',
path: [],
center: []
}
}
} else {
state.areaLayerInfo = {
type: '0',
path: []
}
}
emits('submit', state.areaLayerInfo);
state.showDialog = false;
};
// 开始画区域
const handleEditor = (type: number) => {
if(state.areaLayer) {
state.drawState = 1;
} else {
state.drawState = 0;
}
state.AMap.plugin('AMap.MouseTool', () => {
// 异步加载插件
state.mouseTool = new state.AMap.MouseTool(map);
state.drawType = type;
switch (state.drawType) {
case 1:
if(state.drawState === 0) {
// 新增圆
handleCircleDraw();
} else {
// 编辑圆
handleCircleEditor();
}
break;
case 2:
if(state.drawState === 0) {
// 新增矩形
handleRectangleDraw();
} else {
// 编辑矩形
handleRectangleEditor();
}
break;
case 3:
if(state.drawState === 0) {
// 新增多边形
handlePolygonDraw();
} else {
// 编辑多边形
handlePolygonEditor();
}
break;
}
state.mouseTool.on('draw',(e:any) => {
// 绘制结束
state.mouseTool.close(false); // 关闭
state.drawState = -1;
state.pointX = -500;
state.pointY = -500;
map.setDefaultCursor('pointer');
if(state.drawType === 1) {
state.areaLayer = map.getAllOverlays('circle')[0];
} else if(state.drawType === 2) {
state.areaLayer = map.getAllOverlays('rectangle')[0];
} else if(state.drawType === 3) {
state.areaLayer = map.getAllOverlays('polygon')[0];
}
})
});
};
// 开始画圆
const handleCircleDraw = () => {
map.setDefaultCursor('crosshair');
state.mouseTool.circle({
strokeColor: '#1791FC',
strokeOpacity: 1,
strokeWeight: 2,
fillColor: '#1791FC',
fillOpacity: 0.4,
cursor:'pointer',
strokeStyle: 'solid'
});
};
// 开始编辑圆
const handleCircleEditor = () => {
state.AMap.plugin('AMap.CircleEditor', () => {
// 异步加载插件
if (state.areaLayer) {
state.areaEditor = new state.AMap.CircleEditor(map, state.areaLayer);
state.areaEditor.open();
}
});
};
// 开始画矩形
const handleRectangleDraw = () => {
map.setDefaultCursor('crosshair');
state.mouseTool.rectangle({
strokeColor: '#1791FC',
strokeOpacity: 1,
strokeWeight: 2,
fillColor: '#1791FC',
fillOpacity: 0.4,
cursor:'pointer',
strokeStyle: 'solid'
});
};
// 开始编辑矩形
const handleRectangleEditor = () => {
state.AMap.plugin('AMap.RectangleEditor', () => {
// 异步加载插件
if (state.areaLayer) {
state.areaEditor = new state.AMap.RectangleEditor(map, state.areaLayer);
state.areaEditor.open();
}
});
};
// 开始画多边形
const handlePolygonDraw = () => {
map.setDefaultCursor('pointer');
state.mouseTool.polygon({
strokeColor: '#1791FC',
strokeOpacity: 1,
strokeWeight: 2,
fillColor: '#1791FC',
fillOpacity: 0.4,
cursor:'pointer',
strokeStyle: 'solid'
});
};
// 开始编辑多边形
const handlePolygonEditor = () => {
state.AMap.plugin('AMap.PolyEditor', () => {
// 异步加载插件
if (state.areaLayer) {
state.areaEditor = new state.AMap.PolyEditor(map, state.areaLayer);
state.areaEditor.open();
}
});
};
// 清除矢量图形
const handleClear = () => {
// 清除画区域实例对象
if(state.areaEditor) {
state.areaEditor.close();
}
// 清除图层
if(state.areaLayer) {
map.clearMap();
}
state.areaEditor = null;
state.linePoints = [];
state.areaLayer = null;
state.drawState = 0;
state.drawType = 0;
};
// 完成
const handleEmit = () => {
// 退出编辑状态
if(state.areaEditor) {
state.areaEditor.close();
state.areaEditor = null;
}
state.pointX = -500;
state.pointY = -500;
state.drawState = -1;
getAreaLayerInfo();
};
// 关闭弹窗
const handleClose = () => {
emits('handleClose');
};
// 切换地图
const changeMap = () => {
if (state.stateLayer == 0) {
map.remove([defaultStreet]);
map.add([satelliteLayer]);
state.stateLayer = 1;
} else {
map.remove([satelliteLayer]);
map.add([defaultStreet]);
state.stateLayer = 0;
}
};
// 悬浮提示
const hoverTxt = computed(() => {
if (state.drawType === 1) {
return state.areaLayer ? '点击完成结束编辑圆' : '按住左键并拖拽绘制圆';
} else if (state.drawType === 2) {
return state.areaLayer ? '点击完成结束编辑矩形' : '按住左键并拖拽绘制矩形';
} else if (state.drawType === 3) {
return state.areaLayer ? '点击完成结束编辑多边形' : '点击地图选择拐点,右键结束绘制多边形';
} else {
return ''
}
});
</script>