高德地图 绘制元素:折线、多边形

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('、'));
    }
  }
}

作者:yiping5

出处:https://www.cnblogs.com/yiping5/p/18510093

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   Ping5-1  阅读(84)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
点击右上角即可分享
微信分享提示