高德地图 绘制元素:折线、多边形
map.vue
<template>
<div>
<button @click="clearMap">清除地图</button>
<button @click="startDraw('polyline')">开始画线</button>
<button @click="initAndDrawPolyline">初始线段</button>
<button @click="startDraw('polygon')">开始画多边形</button>
<button @click="initAndDrawPolygon">初始多边形</button>
<button @click="closeDraw">结束绘制</button>
<button @click="getDrawedPolyline">获取画线结果</button>
<button @click="clearDraw">清除画线结果</button>
{{ drawedDatas }}
<div id="mapContainer"></div>
</div>
</template>
<script>
import { MapDraw } from './draw';
export default {
name: 'MapPloyline',
data() {
return {
map: null,
mapDraw: null,
drawedDatas: [], // 存储用户选择的点的经纬度数组
};
},
mounted() {
this.initMap();
},
beforeDestroy() {
this.destroyMap();
},
methods: {
initMap() {
this.map = new AMap.Map('mapContainer', {
zoom: this.$mapZoom,
center: this.$mapCenter, // 设置地图中心点
doubleClickZoom: false,
});
this.mapDraw = new MapDraw(this.map, 'polyline');
},
destroyMap() {
this.mapDraw.destroy();
this.map.clearMap();
this.map = null;
},
clearMap() {
this.map.clearMap();
},
startDraw(drawType = 'polyline') {
this.mapDraw.startDraw(drawType);
},
initAndDrawPolyline() {
this.mapDraw.initDraw('polyline', [
[
[121.409749, 28.653363],
[121.426915, 28.661196],
[121.450261, 28.654719],
],
[
[121.396188, 28.66692],
[121.405286, 28.680173],
[121.433953, 28.680173],
[121.437386, 28.669329],
],
]);
},
initAndDrawPolygon() {
this.mapDraw.initDraw('polygon', [
[
[115.008933, 30.15226],
[115.010307, 30.120936],
[115.052364, 30.146323],
],
[
[114.955203, 30.140088],
[115.002239, 30.153299],
[115.000694, 30.113957],
],
]);
},
getDrawedPolyline() {
this.drawedDatas = this.mapDraw.getDrawedPoints();
},
clearDraw() {
this.mapDraw.clearAll();
},
closeDraw() {
this.mapDraw.closeEdit();
},
},
};
</script>
<style>
#mapContainer {
width: 100%;
height: 500px;
}
</style>
draw.js
import { Message } from 'element-ui';
const drawTypes = ['polyline', 'polygon']; // 目前只实现这两类,后续再做补充 'marker', 'circle', 'rectangle', 'distance'
const drawTip = {
polyline: '在地图上单击鼠标左键开始绘制折线,鼠标左键双击或右键单击结束当前折线绘制。',
polygon: '在地图上单击鼠标左键开始绘制多边形,鼠标左键双击或右键单击结束当前多边形的绘制。',
};
export class MapDraw {
mapObj = null;
mouseTool = null;
infoWindow = null;
drawEditor = null;
drawEditorActiveId = null;
isEdit = false;
mouseToolActive = false; // 鼠标正在绘制
drawType = null;
drawedData = {};
constructor(mapObj, drawType) {
if (!this.mapObj) {
this.mapObj = mapObj;
}
this.drawType = drawType;
this.#initInfoWindow();
this.#initMouseTool();
this.#initEditor();
this.mapObj.on('mousemove', (e) => this.#onMapMouseMove(e));
this.mapObj.on('click', (e) => this.#onMapClick(e));
}
/* 初始化信息窗口 */
#initInfoWindow() {
if (this.infoWindow) return;
this.infoWindow = new AMap.InfoWindow({
anchor: 'top-left',
isCustom: true,
autoMove: false,
offset: new AMap.Pixel(20, 20),
});
}
/* 初始化鼠标工具 */
#initMouseTool() {
if (this.mouseTool) return;
this.mapObj.plugin(['AMap.MouseTool'], () => {
this.mouseTool = new AMap.MouseTool(this.mapObj);
this.mouseTool.on('draw', (e) => this.#drawEnd(e)); // 添加绘制触发事件draw()
});
}
/* 初始化编辑工具 */
#initEditor() {
if (this.drawEditor) return;
this.drawEditor = this.#initDrawEditor(this.drawType);
this.drawEditor.setTarget();
this.drawEditorActiveId = null;
this.drawEditor.on('add', (v) => this.#onEditorAdd(v));
this.drawEditor.on('removenode', (v) => this.#onEditorRemovenode(v));
}
/* 给指定元素添加编辑器 */
#initDrawEditor(drawType, element) {
if (!drawType) {
throw new Error('请指定drawType');
}
let editor;
switch (drawType) {
case 'marker':
break;
case 'polyline':
this.mapObj.plugin(['AMap.PolylineEditor'], () => {
editor = new AMap.PolylineEditor(this.mapObj, element, {
createOptions: {
strokeColor: '#ff00ff',
borderWeight: 6, // 线条宽度,默认为2
lineJoin: 'round', // 折线拐点连接处样式
},
// 顶点样式
controlPoint: {
bubble: false,
extData: {
isControl: true,
},
},
// 中间点样式
midControlPoint: {},
});
});
break;
case 'polygon':
this.mapObj.plugin(['AMap.PolygonEditor'], () => {
editor = new AMap.PolygonEditor(this.mapObj, element);
});
break;
case 'rectangle':
this.mapObj.plugin(['AMap.RectangleEditor'], () => {
editor = new AMap.RectangleEditor(this.mapObj, element);
});
break;
case 'circle':
this.mapObj.plugin(['AMap.CircleEditor'], () => {
editor = new AMap.CircleEditor(this.mapObj, element);
});
break;
case 'distance':
break;
}
return editor;
}
/* 销毁 */
destroy() {
this.mapObj?.off('mousemove', (e) => this.#onMapMouseMove(e));
this.mapObj?.off('click', (e) => this.#onMapClick(e));
this.drawEditor?.off('add', (v) => this.#onEditorAdd(v));
this.drawEditor?.off('removenode', (v) => this.#onEditorRemovenode(v));
this.mouseTool?.off('draw', (e) => this.#drawEnd(e));
this.clearAllAndClose();
this.drawType = null;
}
/* 数据上图 */
initDraw(drawType, datas) {
this.validateType(drawType);
this.drawType = drawType;
if (!datas.length) return;
datas.forEach((v) => {
this.#createElement(drawType, v);
});
}
/* 开始绘制 */
startDraw(drawType) {
if (drawType) {
this.validateType(drawType);
this.drawType = drawType;
}
if (!this.drawType) {
throw new Error('请先设置绘制类型');
}
this.isEdit = true;
this.mouseToolActive = true;
const style = this.getDefaultStyle(this.drawType);
switch (this.drawType) {
case 'marker':
this.mouseTool.marker(style);
break;
case 'polyline':
this.mouseTool.polyline(style);
break;
case 'polygon':
this.mouseTool.polygon(style);
break;
case 'rectangle':
this.mouseTool.rectangle(style);
break;
case 'circle':
this.mouseTool.circle(style);
break;
case 'distance':
this.mouseTool.rule();
break;
}
}
/* 关闭鼠标工具*/
closeMouseTool() {
this.mouseTool?.close(true);
this.mouseToolActive = false;
this.infoWindow?.close();
}
/* 关闭编辑 */
closeEdit() {
this.drawEditor?.close();
this.isEdit = false;
this.drawEditorActiveId = null;
this.closeMouseTool();
}
/* 清除所有绘制对象 */
clearAll() {
this.mapObj.remove(Object.values(this.drawedData).map((v) => v.element));
this.drawedData = {};
}
/* 清除所有绘制对象并关闭编辑 */
clearAllAndClose() {
this.closeEdit();
this.clearAll();
}
/* 获取绘制数据 */
getDrawedPoints() {
let data = [];
Object.values(this.drawedData).forEach(({ drawType, element }) => {
const points = element?.getPath()?.map((v) => [v.lng, v.lat]);
switch (drawType) {
case 'polyline':
// 过滤掉无法构成线的点
points?.length > 1 && data.push(points);
break;
case 'polygon':
// 过滤掉无法构成面的点
data.push(points);
break;
}
});
return data;
}
/* 鼠标绘制-回调函数 */
#drawEnd(e) {
const element = e.obj;
const points = element?.getPath()?.map((v) => [v.lng, v.lat]);
this.closeMouseTool();
// 选中元素需要双击,会造成鼠标绘制捕捉到长度为1的点,但这里不做处理,因为线及多边形对最少点的要求更高,如果要兼容其他类型需要更仔细的判断
if (['polyline', 'polygon'].includes(this.drawType) && points.length < 2) return;
if (this.drawType === 'polygon' && points.length < 3) {
Message({ message: '请确保至少绘制3点形成一个面', type: 'warning' });
return;
}
const newElememt = this.#createElement(this.drawType, points);
this.drawEditorActiveId = newElememt._amap_id;
this.drawEditor.setTarget(newElememt);
this.drawEditor.open();
}
/* 编辑器-添加元素:绑定事件 */
#onEditorAdd(event) {
const elementId = event.target._amap_id;
this.drawedData[elementId] = {
elementId,
drawType: this.drawType,
element: event.target,
};
this.#elementBindEvent(event.target);
}
/* 编辑器-结束绘制:处理高德地图交互bug
绘制线段及多边形:当只剩下两个顶点构成线段又点击顶点删除时,高德视觉上移除了元素但实际地图上数据上并没有同步清除,
【同时,当三个点删除成只剩两个点,两个点删除只剩一个点,path值均为2,且无任何属性或方法能区分开这两种情况】
这里只能针对path为2的元素,删除再渲染,以达到还原视觉上被移除的元素的效果
*/
#onEditorRemovenode(event) {
if (!['polygon', 'polyline'].includes(this.drawType)) return;
const path = event.target?.getPath();
// 线段:只要path长度为2,则重新渲染
if (this.drawType === 'polyline' && path.length === 2) {
delete this.drawedData[event.target._amap_id];
const points = path?.map((v) => [v.lng, v.lat]);
this.mapObj.remove(event.target);
this.#createElement(this.drawType, points);
}
// 多边形:只要path长度小于3,自动删除
if (this.drawType === 'polygon' && path?.length < 3) {
this.drawEditor.close();
this.drawEditorActiveId = null;
delete this.drawedData[event.target._amap_id];
this.mapObj.remove(event.target);
}
}
/* 地图事件-单击:启用鼠标绘制模式 */
#onMapClick() {
if (!this.isEdit || this.mouseToolActive) return;
if (this.drawEditorActiveId) {
this.drawEditor.setTarget();
this.drawEditorActiveId = null;
}
this.startDraw();
}
/* 地图事件-鼠标移动:显示信息窗 */
#onMapMouseMove(e) {
if (!this.infoWindow || !this.mouseToolActive) return;
let content = drawTip[this.drawType];
if (!content) return;
this.infoWindow.setContent(content);
this.infoWindow.open(this.mapObj, e.lnglat);
}
/* 元素绑定事件*/
#elementBindEvent(element) {
// 双击选中元素
element.on('mouseover', () => {
if (!this.isEdit || this.drawEditorActiveId === element._amap_id) return;
if (this.mouseToolActive) {
this.closeMouseTool();
}
this.drawEditorActiveId = element._amap_id;
this.drawEditor.setTarget(element);
this.drawEditor.open();
});
// 右键删除元素(注:直接右键删除有些粗暴,目前无此交互要求,先注释
// element.on('rightclick', () => {
// if (!this.isEdit) return;
// const elementId = element._amap_id;
// delete this.drawedData[elementId];
// this.mapObj.remove(element);
// });
}
/* 绘制元素上图,并绑定事件、缓存数据 */
#createElement(drawType, data) {
let element = this.#createMapElement(drawType, data);
if (!element) return;
this.#elementBindEvent(element);
const elementId = element._amap_id;
this.drawedData[elementId] = {
elementId,
drawType,
element,
};
return element;
}
/* 创建地图元素*/
#createMapElement(drawType, data) {
this.validateType(drawType);
const style = this.getDefaultStyle(drawType);
let element;
switch (this.drawType) {
case 'marker':
break;
case 'polyline':
element = new AMap.Polyline({
path: data,
...style,
});
break;
case 'polygon':
element = new AMap.Polygon({
path: data,
...style,
});
break;
case 'rectangle':
element = new AMap.Rectangle({
bounds: data,
...style,
});
break;
case 'circle':
break;
case 'distance':
break;
}
element && this.mapObj.add(element);
return element;
}
/* 返回默认的样式 */
getDefaultStyle(drawType) {
this.validateType(drawType);
let style = null;
switch (this.drawType) {
case 'marker':
break;
case 'polyline':
style = {
strokeColor: '#165dff',
borderWeight: 6, // 线条宽度,默认为2
lineJoin: 'round', // 折线拐点连接处样式
};
break;
case 'polygon':
style = {
fillColor: '#00b0ff',
strokeColor: '#165dff',
};
break;
case 'rectangle':
style = {
fillColor: '#00b0ff',
strokeColor: '#165dff',
};
break;
case 'circle':
style = {
fillColor: '#00b0ff',
strokeColor: '#165dff',
};
break;
case 'distance':
break;
}
return style;
}
/* 校验绘制类型 */
validateType(drawType) {
if (!drawTypes.includes(drawType)) {
throw new Error('drawType参数错误,请输入:' + drawTypes.join('、'));
}
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?