likes
comments
collection
share

百度地图-多区域绘制、编辑、面积计算、重叠判断、清除、回显

作者站长头像
站长
· 阅读数 14

写在开头

本章来介绍一下使用百度地图上的区域绘制功能,最终实现效果如上动图。官方DEMO

话不多说,我们先来初始化项目:

npm init @vitejs/app

为了方便快速开发,小编这里采用的是 Vue3+Vite2 技术直接来开搞。

引入百度地图:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" href="/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite App</title>
    <link href="//mapopen.cdn.bcebos.com/github/BMapGLLib/DrawingManager/src/DrawingManager.min.css" rel="stylesheet">
    <script type="text/javascript" src="//api.map.baidu.com/getscript?type=webgl&v=1.0&ak=你的key"></script>
    <script type="text/javascript" src="//mapopen.bj.bcebos.com/github/BMapGLLib/DrawingManager/src/DrawingManager.min.js"></script>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.js"></script>
  </body>
</html>

我们直接在 index.html 文件中引入百度地图相关的 CDN 链接。上面引入的 DrawingManager 库是 百度地图GL工具库 中的一个绘制库,它能给我们提供在地图上绘制的能力。DrawingManager API文档

注意:上面代码中需要把 key 换成你自己的,获取自己的 key 你需要先去百度地图那边申请一下账号,传送门

初始化地图

完成上面地图的引入步骤后,开头第一步我们要先来把地图初始化出来,这就是基本操作了,我们直接来看代码,还不会的小伙伴可以先读读 文档 唷。

<!-- App.vue -->
<template>
  <h1>百度地图-多区域绘制、区域编辑、区域面积计算、区域重叠判断、区域清除</h1>
  <div class="map-container">
    <div id="mapContainer" class="map"></div>
  </div>
</template>

<script>
import { defineComponent, onMounted } from 'vue';
export default defineComponent({
  setup() {
    let map = null;
    //初始化-地图
    function initMap () {
      return new Promise(resolve => {
        map = new BMapGL.Map('mapContainer', {
          minZoom: 1,
          maxZoom: 20,
          enableMapClick: false
        });
        // 设置中心点和缩放级别
        map.centerAndZoom(new BMapGL.Point(116.404, 39.915), 15);
        // 开启滚轮缩放
        map.enableScrollWheelZoom(true);
        // 地图加载完成
        map.addEventListener('tilesloaded', () => {
          resolve();
        });
      });
    }
    // 需要等dom渲染完成后再初始化地图
    onMounted(() => {
      initMap();
    });
    return {};
  }
})
</script>

<style scoped>
.map-container{
  width: 1200px;
  height: 550px;
  position: relative;
  margin-bottom: 10px
}
.map{
  width: 100%;
  height: 100%;
}
</style>

运行项目后,如果能正常看到一幅地图,就说明初始化成功啦。

百度地图-多区域绘制、编辑、面积计算、重叠判断、清除、回显

多区域绘制

地图初始化完成后,接下来我们要来初始化绘制工具。

<template>
  <div class="map-container">
   <div id="mapContainer" class="map"></div>
     <!-- 添加不同绘制图形的按钮, 会把它们定位到左下角 -->  
     <ul class="drawing-panel">
	<li class="bmap-btn bmap-rectangle" id="rectangle" @click="draw($event, 'rectangle')"></li>
	<li class="bmap-btn bmap-polygon" id="polygon" @click="draw($event, 'polygon')"></li>
     </ul>
    </div>
</template>

<script>
import { defineComponent, onMounted } from 'vue';
export default defineComponent({
  setup() {
    let map = null;
    let drawingManager = null;
    const styleOptions = {
      strokeColor: '#e38932',   // 边线颜色
      fillColor: '#e38932',     // 填充颜色。当参数为空时,圆形没有填充颜色
      strokeWeight: 1,          // 边线宽度,以像素为单位
      strokeOpacity: 1,         // 边线透明度,取值范围0-1
      fillOpacity: 0.2          // 填充透明度,取值范围0-1
    };
    const labelOptions = {
      borderRadius: '2px',
      background: '#FFFBCC',
      border: '1px solid #E1E1E1',
      color: '#703A04',
      fontSize: '12px',
      letterSpacing: '0',
      padding: '5px'
    };
    // 初始化-绘制工具
    function initDrawingManager() {
      // 实例化鼠标绘制工具
      drawingManager = new BMapGLLib.DrawingManager(map, {
        enableCalculate: true,  // 绘制是否进行测距测面
        enableSorption: true,   // 是否开启边界吸附功能
        sorptiondistance: 20,   // 边界吸附距离
        enableGpc: false,        // 是否开启延边裁剪功能
        enableLimit: true,      // 是否开启超限提示
        limitOptions: {
          area: 500000000,     // 面积超限值
          distance: 300000     // 距离超限值
        },
        circleOptions: styleOptions,     // 圆的样式
        polylineOptions: styleOptions,   // 线的样式
        polygonOptions: styleOptions,    // 多边形的样式
        rectangleOptions: styleOptions,  // 矩形的样式
        labelOptions: labelOptions,      // label样式
      });
      // 绘制完成后获取相关的信息(面积等)
      drawingManager.addEventListener('overlaycomplete', (e, instance) => {
	console.log('绘制完成:', e, instance);
        console.log('绘制面积:', (e.calculate / 1000 / 1000).toFixed(2) + 'km²');
      });
    }
    function initMap () {
      return new Promise(resolve => {
        map = new BMapGL.Map('mapContainer', {
          minZoom: 1,
          maxZoom: 20,
          enableMapClick: false
        });
        map.centerAndZoom(new BMapGL.Point(116.404, 39.915), 15);
        map.enableScrollWheelZoom(true);
	// 初始化-绘制工具
        initDrawingManager();
        map.addEventListener('tilesloaded', () => { resolve(); });
      });
    }
    // 绘制功能
    function draw(e, id) {
      e.target.style.backgroundPositionY = '-52px';
      let drawingType = '';
      switch (id) {
        case 'polyline':
          drawingType = 'polyline'; break;
        case 'rectangle':
          drawingType = 'rectangle'; break;
        case 'polygon':
          drawingType = 'polygon'; break;
        case 'circle':
          drawingType = 'circle'; break;
      }
      // 进行绘制
      if (drawingManager._isOpen && drawingManager.getDrawingMode() === drawingType) {
        drawingManager.close();
      } else {
        drawingManager.setDrawingMode(drawingType);
        drawingManager.open();
      }
    }
    onMounted(() => {
      initMap();
    });
    return {
      draw
    };
  }
})
</script>

<style scoped>
...
.drawing-panel {
  z-index: 999;
  position: absolute;
  bottom: 3.5rem;
  margin-left: 2.5rem;
  padding-left: 0;
  border-radius: .25rem;
  height: 47px;
  box-shadow: 0 2px 6px 0 rgba(27, 142, 236, 0.5);
  list-style: none;
}
.bmap-btn {
  border-right: 1px solid #d2d2d2;
  float: left;
  width: 64px;
  height: 100%;
  background-image: url(//api.map.baidu.com/library/DrawingManager/1.4/src/bg_drawing_tool.png);
  cursor: pointer;
}
.drawing-panel .bmap-marker {
  background-position: -65px 0;
}
.drawing-panel .bmap-polyline {
  background-position: -195px 0;
}
.drawing-panel .bmap-rectangle {
  background-position: -325px 0;
}
.drawing-panel .bmap-polygon {
  background-position: -260px 0;
}
.drawing-panel .bmap-circle {
  background-position: -130px 0;
}
</style>

上面我们初始化了绘制工具 BMapGLLib.DrawingManager 对象,通过调用它的 drawingManager.setDrawingMode(DrawingType) 方法,我们可以绘制不同形状的图形区域,百度地图一共提供了五种图形绘制,DrawingType 对应五个常量。

方法返回值描述
close()关闭地图的绘制状态
disableCalculate()关闭距离或面积计算
enableCalculate()打开距离或面积计算
getDrawingMode()DrawingType获取当前的绘制模式
open()开启地图的绘制模式
setDrawingMode(DrawingType)Boolean设置当前的绘制模式,参数DrawingType,为5个可选常量: BMAP_DRAWING_MARKER 画点 BMAP_DRAWING_CIRCLE 画圆 BMAP_DRAWING_POLYLINE 画线 BMAP_DRAWING_POLYGON 画多边形 BMAP_DRAWING_RECTANGLE 画矩形

上面小编 DrawingType 直接使用的是常量的值,你也可以在 window 身上找到这五个常量。

百度地图-多区域绘制、编辑、面积计算、重叠判断、清除、回显

当我们完成一个图形区域的绘制后,会触发 overlaycomplete 回调方法,从这个方法我们可以获取到图形区域的实例、面积等等信息。

百度地图-多区域绘制、编辑、面积计算、重叠判断、清除、回显

这里得到的面积实际并不能满足小编的任务需求,下面会单独写一个计算多边形面积的方法,之所以不能满足呢,是因为有这样的场景,当编辑的时候,调整了图形的大小,并不能及时的获取最新的图形面积。

还有,你也可以单独监听特定图形完成后的方法:

事件参数描述
circlecomplete(overlay){Circle}绘制圆完成后,派发的事件接口
markercomplete(overlay){Marker}绘制点完成后,派发的事件接口
overlaycomplete(e){Event Object}鼠标绘制完成后,派发总事件的接口
polygoncomplete(overlay){Polygon}绘制多边形完成后,派发的事件接口
polylinecomplete(overlay){Polyline}绘制线完成后,派发的事件接口
rectanglecomplete(overlay){Polygon}绘制矩形完成后,派发的事件接口

区域编辑

既然已经实现了绘制图形,那么接下来,我们来看看如何实现图形的再编辑。这个功能主要来自于官方这个 DEMO

通过示例可以看到,调用每个 图形实例.enableEditing() / .disableEditing() 方法就能控制图形的编辑操作。

百度地图-多区域绘制、编辑、面积计算、重叠判断、清除、回显

图形实例 身上的其他方法可以去文档上查阅,重点可以关注画圈圈的几个类。

百度地图-多区域绘制、编辑、面积计算、重叠判断、清除、回显

了解完相关知识后,我们来看看具体的实现过程:

<template>
  <!-- 添加一些按钮来控制逻辑 -->
  <div class="btns">
    <button v-if="!isDescribe && !isDescribeEdit" @click="describe" type="primary">绘制区域</button>
    <button v-if="!isDescribe && !isDescribeEdit && drawItemMap.length" @click="describeEdit">开启区域编辑</button>
    <template v-if="isDescribe">
      <div class="tip">请点击左下角图标进行对应图形的绘制</div>
      <button @click="describe">绘制中</button>
    </template>
    <button v-if="isDescribeEdit" @click="describeEdit">编辑完成</button>
  </div>
  <div class="map-container">
    <div id="mapContainer" class="map"></div>
      <!-- 绘制按钮可控 -->
      <ul v-show="isDescribe" class="drawing-panel">
        <li class="bmap-btn bmap-rectangle" id="rectangle" @click="draw($event, 'rectangle')"></li>
        <li class="bmap-btn bmap-polygon" id="polygon" @click="draw($event, 'polygon')"></li>
      </ul>
  </div>
</template>

<script>
import { defineComponent, onMounted, ref } from 'vue';
export default defineComponent({
  setup() {
    ...
    let isDescribe = ref(false); // 是否能描绘
    let isDescribeEdit = ref(false); // 是否能编辑
    let drawItemMap = ref([]); // 储存每个图形实例: overlay是覆盖物的抽象基类, 所有覆盖物均继承基类的方法, 详情可以查看文档说明
    
    // 重置图标显示状态
    function resetIconStatus() {
      let btnDoms = document.querySelectorAll('.bmap-btn');
      btnDoms.forEach(item => {
        item.style.backgroundPositionY = '0';
      });
    }
    function initDrawingManager() {
      ...
      drawingManager.addEventListener('overlaycomplete', (e, instance) => {
	isDescribe.value = false; // 绘制完成就隐藏绘制按钮
        resetIconStatus(); // 重置按钮的显示状态
	drawItemMap.value.push(instance.overlay);
      });
    }
    function initMap () { ... }
    function draw(e, id) {
      // 重置按钮的显示状态
      resetIconStatus();
      ...
    }
    onMounted(() => { initMap(); });

    return {
      draw,
      isDescribe,
      isDescribeEdit,
      drawItemMap,
      // 绘制
      describe() {
        isDescribe.value = !isDescribe.value
        if (!isDescribe.value) {
          resetIconStatus();
          drawingManager.close(); // 关闭绘制功能
        }
      },
      // 编辑
      describeEdit() {
        isDescribeEdit.value = !isDescribeEdit.value
        drawItemMap.value.forEach((item) => {
          if (isDescribeEdit.value) {
            item.enableEditing();
          } else {
            item.disableEditing();
          }
        })
      },
    };
  }
})
</script>
...
.btns{
  width: 1200px;
  display: flex;
  justify-content: right;
  margin-bottom: 8px;
}
</style>

上面我们添加了一些按钮来控制整体的逻辑,主要过程就是每次绘制完成后,保存好图形的实例,编辑的时候,遍历所有图形实例然后调用编辑方法即可。

百度地图-多区域绘制、编辑、面积计算、重叠判断、清除、回显

区域面积计算

前面小编讲过计算图形区域的面积,通过 overlaycomplete 等回调方法返回的值并不能满足某些场景,那只能另寻它路了。

先来回想一下,我们现在能获取到图形的实例,那么实例身上都有些啥呢?

这个时候,我们又该翻翻文档了。实例身上并没有能直接获取到面积的方法,但是有个 getPath() 方法,能获取到图形所有点的经纬度坐标。

百度地图-多区域绘制、编辑、面积计算、重叠判断、清除、回显

后面,小编在翻阅 BMapGLLib 库源码中找到另一个工具包 GeoUtils 里面有个方法,能把经纬度坐标转成面积,这不正好嘛。(✪ω✪)

当然,事情肯定没那么简单,看看这注释,还有“意外之喜” ???

百度地图-多区域绘制、编辑、面积计算、重叠判断、清除、回显

看来这个方法还不能直接使用了,又查了一些资料(百度半天),终于找到了一个方法(还是网友牛逼):

<template>
  ...
  <div style="color: red;width:1200px">
    <div>区域总面积:{{data.totalArea}}</div>
    <div>区域所有点:</div>
    <div v-for="(item, index) in data.totalPoint" :key="index">{{item}}</div>
  </div>
</template>

<script>
import { defineComponent, onMounted, ref, reactive } from 'vue';
export default defineComponent({
  setup() {
    ...
    let data = reactive({
        totalArea: '',
        totalPoint: []
    });
    
    function resetIconStatus() { ... }
    // 计算图形面积
    function computeSignedArea(path) {
      // path: [{lat, lng}, {lat, lng}]
      let polarTriangleArea = (tan1, lng1, tan2, lng2) => {
        let deltaLng = lng1 - lng2;
        let t = tan1 * tan2;
        return 2 * Math.atan2(t * Math.sin(deltaLng), 1 + t * Math.cos(deltaLng));
      }
      let radius = 6371009
      let len = path.length;
      if (len < 3) return 0;
      let total = 0;
      let  prev = path[len - 1];
      let prevTanLat = Math.tan(((Math.PI / 2 - prev.lat / 180 * Math.PI) / 2));
      let prevLng = (prev.lng) / 180 * Math.PI;
      for (let i in path) {
        let tanLat = Math.tan((Math.PI / 2 -
            (path[i].lat) / 180 * Math.PI) / 2);
        let lng = (path[i].lng) / 180 * Math.PI;
        total += polarTriangleArea(tanLat, lng, prevTanLat, prevLng);
        prevTanLat = tanLat;
        prevLng = lng;
      }
      return Math.abs(total * (radius * radius));
    }
    // 计算所有图形总面积
    function calcTotalArea() {
      if (drawItemMap.value.length === 0) return data.totalArea = '';
      let total = 0;
      drawItemMap.value.forEach(item => {
        let points = item.getPath();
        let area = computeSignedArea(points);
        total += area;
      });
      data.totalArea = (total / 1000 / 1000).toFixed(2) + 'km²';
    }
    // 获取所有区域的点的经纬度
    function getAllPoint() {
        let points = [];
        drawItemMap.value.forEach(item => {
           points.push(item.getPath()); // .getPath() 获取图形所有点的经纬度坐标
        })
        data.totalPoint = points;
    }
    function initDrawingManager() {
      ...
      drawingManager.addEventListener('overlaycomplete', (e, instance) => {
	isDescribe.value = false; 
        resetIconStatus(); 
	drawItemMap.value.push(instance.overlay);
        getAllPoint(); // 获取所有点
	calcTotalArea(); // 计算面积
      });
    }
    function initMap () { ... }
    function draw(e, id) { ... }
    onMounted(() => { initMap(); });

    return {
      ...
      // 编辑
      describeEdit() {
        isDescribeEdit.value = !isDescribeEdit.value
        drawItemMap.value.forEach((item) => {
          if (isDescribeEdit.value) {
            item.enableEditing();
          } else {
            item.disableEditing();
          }
        })
        calcTotalArea(); // 编辑完成后,计算面积
        getAllPoint();
      },
      data
    };
  }
})
</script>

测试了广州几个景点区域面积,这个方法结果还是比较精准的,可用。(◕ˇ∀ˇ◕)

百度地图-多区域绘制、编辑、面积计算、重叠判断、清除、回显

百度地图-多区域绘制、编辑、面积计算、重叠判断、清除、回显

区域重叠判断

既然支持绘制多个图形区域,那么肯定会发生区域绘制重叠的情况,这在业务上产品经理肯定是要挑理的,那我们还得来解决这个问题。

这个问题也挺好解决,因为在阅读工具包 GeoUtils 源码的时候,小编瞄到有这方面相关的 API 提供。

百度地图-多区域绘制、编辑、面积计算、重叠判断、清除、回显

使用方式很简单,我们先直接来看代码:

<script>
import { defineComponent, onMounted, ref, reactive } from 'vue';
export default defineComponent({
  setup() {
    ...
    
    function resetIconStatus() { ... }
    function computeSignedArea(path) { ... }
    function calcTotalArea() { ... }
    function getAllPoint() { ... }
    // 判断两多变形是否存在点与区域的包含关系(A的点在B的区域内或B的点在A的区域内)
    function isPointInPolygonBidirectional(plyA, plyB) {
      const [pA, pB] = [[], []];
      plyA.forEach((item) => {
        pA.push(new BMapGL.Point(item.lng, item.lat));
      });
      plyB.forEach((item) => {
        pB.push(new BMapGL.Point(item.lng, item.lat));
      });
      let [a, b] = [false, false];
      a = pA.some(item => BMapGLLib.GeoUtils.isPointInPolygon(item, new BMapGL.Polygon(pB)));
      if (!a) {
        b = pB.some(item => BMapGLLib.GeoUtils.isPointInPolygon(item, new BMapGL.Polygon(pA)));
      }
      return a || b;
    }
    // 判断多边形是否重叠
    function isPolygonsOverlap(plyA, plyB) {
      return isPointInPolygonBidirectional(plyA, plyB);
    }
    function initDrawingManager() {
      ...
      drawingManager.addEventListener('overlaycomplete', (e, instance) => {
	isDescribe.value = false;
        resetIconStatus();
        // 判断区域是否重叠
        let isOverlap = drawItemMap.value.some(item => isPolygonsOverlap(item.getPath(), instance.overlay.getPath()))
        if (isOverlap) {
            alert('区域不能重叠')
            map.removeOverlay(instance.overlay); // 清除图形
        } else {
            drawItemMap.value.push(instance.overlay);
            calcTotalArea();
            getAllPoint();
        }
      });
    }
    function initMap () { ... }
    function draw(e, id) { ... }
    onMounted(() => { initMap(); });

    return {
      ...
    };
  }
})
</script>

GeoUtils.isPointInPolygon(points: Array<Point>, polygon: Polygon) 方法接收两个参数,第一个参数是图形点的经纬度坐标数组,另一个参数是目标多边形对象的实例,这个实例可以通过 Polygon 类来构造。

代码大概思路就是,把一个图形所有点的经纬度坐标,遍历一遍,判断是否是在另一个图形身上。

百度地图-多区域绘制、编辑、面积计算、重叠判断、清除、回显

区域清除

最后,当我们绘制图形区域有误时,又该如何清除呢?同样也是直接调用相关 API 就行啦。

  • map.removeOverlay(overlay) :清除某个图形区域,overlay 为图形实例。
  • map.clearOverlays():清除地图上所有的区域。

你以为写到这里就完了嘛? 当然还没,还有一丢丢内容。

百度地图-多区域绘制、编辑、面积计算、重叠判断、清除、回显

区域回显

在我们实际的业务中,我们在地图上所绘制的图形区域,一般会提交到后端保存起来,而我们提交的会是图形所有点的经纬度坐标。然后,当我们查看业务详情的时候,肯定是需要把这些图形回显在地图上,那我们应该如何通过点的经纬度坐标来绘制出这些图形呢?

下方小编写了一个绘制多边形的方法:

// 绘制多边形区域
function drawChart(points) {
  if (!points || points.length === 0) return;
  let ps = points.map(p => new BMapGL.Point(p.lng, p.lat));
  let polygon = new BMapGL.Polygon(ps, styleOptions);
  map.addOverlay(polygon); // 把图形绘制在地图上
  return polygon;
}

更多其他图形的绘制可以看看官方 DEMO 示例,这里需要注意的是绘制圆的 API 需要一个半径值,所以保存的时候,半径也需要保存起来,这需要提前告知后端人员,以免接口设计有错误。

百度地图-多区域绘制、编辑、面积计算、重叠判断、清除、回显

那么,写到这里本篇文章就完结啦,下面再贴上完整的源码,如你有任何疑问,欢迎在评论区留言告知我。

百度地图-多区域绘制、编辑、面积计算、重叠判断、清除、回显

完整源码

<template>
  <h1>百度地图-多区域绘制、区域编辑、区域面积计算、区域重叠判断、区域清除</h1>
    <div class="btns">
        <button v-if="!isDescribe && !isDescribeEdit" @click="describe" type="primary">绘制区域</button>
        <button v-if="!isDescribe && !isDescribeEdit && drawItemMap.length" @click="describeEdit">开启区域编辑</button>
        <button v-if="!isDescribe && !isDescribeEdit && drawItemMap.length" @click="clearAllOverlay" type="danger">清除所有区域</button>
        <template v-if="isDescribe">
            <div class="tip">请点击左下角图标进行对应图形的绘制</div>
            <button @click="describe">绘制中</button>
        </template>
        <button v-if="isDescribeEdit" @click="describeEdit">编辑完成</button>
	</div>
	<div class="map-container">
            <div id="mapContainer" class="map"></div>
            <ul v-show="isDescribe" class="drawing-panel">
                <li class="bmap-btn bmap-rectangle" id="rectangle" @click="draw($event, 'rectangle')"></li>
                <li class="bmap-btn bmap-polygon" id="polygon" @click="draw($event, 'polygon')"></li>
            </ul>
	</div>
	<div style="color: red;width:1200px">
            <div>区域总面积:{{data.totalArea}}</div>
            <div>区域所有点:</div>
            <div v-for="(item, index) in data.totalPoint" :key="index">{{item}}</div>
	</div>
</template>

<script>
import { defineComponent, onMounted, ref, reactive } from 'vue';
export default defineComponent({
  setup() {
    let data = reactive({
        totalArea: '',
        totalPoint: []
    });
    let map = null;
    let drawingManager = null;
    let isDescribe = ref(false); 
    let isDescribeEdit = ref(false); 
    let drawItemMap = ref([]); 
     const styleOptions = {
      strokeColor: '#e38932', 
      fillColor: '#e38932',  
      strokeWeight: 1,        
      strokeOpacity: 1,       
      fillOpacity: 0.2        
    };
    const labelOptions = {
      borderRadius: '2px',
      background: '#FFFBCC',
      border: '1px solid #E1E1E1',
      color: '#703A04',
      fontSize: '12px',
      letterSpacing: '0',
      padding: '5px'
    };
    function computeSignedArea(path) {
      // path: [{lat:,lng:}, {lat:,lng:}]
      let polarTriangleArea = (tan1, lng1, tan2, lng2) => {
        let deltaLng = lng1 - lng2;
        let t = tan1 * tan2;
        return 2 * Math.atan2(t * Math.sin(deltaLng), 1 + t * Math.cos(deltaLng));
      }
      let radius = 6371009
      let len = path.length;
      if (len < 3) return 0;
      let total = 0;
      let  prev = path[len - 1];
      let prevTanLat = Math.tan(((Math.PI / 2 - prev.lat / 180 * Math.PI) / 2));
      let prevLng = (prev.lng) / 180 * Math.PI;
      for (let i in path) {
        let tanLat = Math.tan((Math.PI / 2 -
            (path[i].lat) / 180 * Math.PI) / 2);
        let lng = (path[i].lng) / 180 * Math.PI;
        total += polarTriangleArea(tanLat, lng, prevTanLat, prevLng);
        prevTanLat = tanLat;
        prevLng = lng;
      }
      return Math.abs(total * (radius * radius));
    }
    function calcTotalArea() {
      if (drawItemMap.value.length === 0) return data.totalArea = '';
      let total = 0;
      drawItemMap.value.forEach(item => {
        let points = item.getPath();
        let area = computeSignedArea(points);
        total += area;
      });
      data.totalArea = (total / 1000 / 1000).toFixed(2) + 'km²';
    }
    function getAllPoint() {
        let points = [];
        drawItemMap.value.forEach(item => {
            points.push(item.getPath());
        })
        data.totalPoint = points
    }
    function resetIconStatus() {
      let btnDoms = document.querySelectorAll('.bmap-btn');
      btnDoms.forEach(item => {
        item.style.backgroundPositionY = '0';
      });
    }
    function isPointInPolygonBidirectional(plyA, plyB) {
        const [pA, pB] = [[], []];
        plyA.forEach((item) => {
            pA.push(new BMapGL.Point(item.lng, item.lat));
        });
        plyB.forEach((item) => {
            pB.push(new BMapGL.Point(item.lng, item.lat));
        });
        let [a, b] = [false, false];
        a = pA.some(item => BMapGLLib.GeoUtils.isPointInPolygon(item, new BMapGL.Polygon(pB)));
        if (!a) {
            b = pB.some(item => BMapGLLib.GeoUtils.isPointInPolygon(item, new BMapGL.Polygon(pA)));
        }
        return a || b;
    }
    function isPolygonsOverlap(plyA, plyB) {
      return isPointInPolygonBidirectional(plyA, plyB);
    }
    function initDrawingManager() {
      drawingManager = new BMapGLLib.DrawingManager(map, {
        enableCalculate: true,  
        enableSorption: true,   
        sorptiondistance: 20,   
        enableGpc: false,       
        enableLimit: true,     
        limitOptions: {
          area: 500000000,     
          distance: 300000     
        },
        circleOptions: styleOptions,     
        polylineOptions: styleOptions,   
        polygonOptions: styleOptions,    
        rectangleOptions: styleOptions,  
        labelOptions: labelOptions,      
      });
      drawingManager.addEventListener('overlaycomplete', (e, instance) => {
        isDescribe.value = false;
        resetIconStatus();
	let isOverlap = drawItemMap.value.some(item => isPolygonsOverlap(item.getPath(), instance.overlay.getPath()))
            if (isOverlap) {
                alert('区域不能重叠')
                map.removeOverlay(instance.overlay);
            } else {
                drawItemMap.value.push(instance.overlay);
                calcTotalArea();
                getAllPoint();
            }
      });
    }
    function initMap () {
      return new Promise(resolve => {
        map = new BMapGL.Map('mapContainer', {
          minZoom: 1,
          maxZoom: 20,
          enableMapClick: false
        });
        map.centerAndZoom(new BMapGL.Point(116.404, 39.915), 15);
        map.enableScrollWheelZoom(true);
        initDrawingManager();
        map.addEventListener('tilesloaded', () => {
          resolve();
        });
      });
    }
    function draw(e, id) {
      e.target.style.backgroundPositionY = '-52px';
      let drawingType = '';
      switch (id) {
        case 'polyline':
          drawingType = 'polyline'; break;
        case 'rectangle':
          drawingType = 'rectangle'; break;
        case 'polygon':
          drawingType = 'polygon'; break;
        case 'circle':
          drawingType = 'circle'; break;
      }
      if (drawingManager._isOpen && drawingManager.getDrawingMode() === drawingType) {
        drawingManager.close();
      } else {
        drawingManager.setDrawingMode(drawingType);
        drawingManager.open();
      }
    }
    onMounted(() => {
       initMap();
    })

    return {
      data,
      isDescribe,
      isDescribeEdit,
      drawItemMap,
      draw,
      describe() {
        isDescribe.value = !isDescribe.value
        if (!isDescribe.value) {
          resetIconStatus();
          drawingManager.close();
        }
      },
      describeEdit() {
        isDescribeEdit.value = !isDescribeEdit.value
        drawItemMap.value.forEach((item) => {
          if (isDescribeEdit.value) {
            item.enableEditing();
          } else {
            item.disableEditing();
          }
        })
        calcTotalArea();
	getAllPoint();
      },
      clearAllOverlay() {
	 if (confirm("您确定清除当前地图上所绘制的区域吗?")) {
          map.clearOverlays();
          drawItemMap.value = [];
          calcTotalArea();
          getAllPoint();
        }
      },
    };
  }
})
</script>

<style scoped>
.map-container{
  width: 1200px;
  height: 550px;
  position: relative;
  margin-bottom: 10px
}
.map{
  width: 100%;
  height: 100%;
}
.drawing-panel {
  z-index: 999;
  position: absolute;
  bottom: 3.5rem;
  margin-left: 2.5rem;
  padding-left: 0;
  border-radius: .25rem;
  height: 47px;
  box-shadow: 0 2px 6px 0 rgba(27, 142, 236, 0.5);
  list-style: none;
}
.bmap-btn {
  border-right: 1px solid #d2d2d2;
  float: left;
  width: 64px;
  height: 100%;
  background-image: url(//api.map.baidu.com/library/DrawingManager/1.4/src/bg_drawing_tool.png);
  cursor: pointer;
}
.drawing-panel .bmap-marker {
  background-position: -65px 0;
}
.drawing-panel .bmap-polyline {
  background-position: -195px 0;
}
.drawing-panel .bmap-rectangle {
  background-position: -325px 0;
}
.drawing-panel .bmap-polygon {
  background-position: -260px 0;
}
.drawing-panel .bmap-circle {
  background-position: -130px 0;
}
.btns{
  width: 1200px;
  display: flex;
  justify-content: right;
  margin-bottom: 8px;
}
</style>

至此,本篇文章就写完啦,撒花撒花。

百度地图-多区域绘制、编辑、面积计算、重叠判断、清除、回显

希望本文对你有所帮助,如有任何疑问,期待你的留言哦。 老样子,点赞+评论=你会了,收藏=你精通了。