DrawingManager.js一些自定义需求的解决方案

786 阅读6分钟

前言

DrawingManager.js是对外开放的百度地图的鼠标绘制工具库 。
本文记录了日常需求开发中,对于部分功能的改造方法。

  1. 编辑状态下展示面积大小label;
  2. 限制最小面积和多边形最少顶点数;
  3. 禁止自定义多边形交叉;
  4. 标记多边形交叉点;
  5. 多边形图形支持可编辑。

官方Demo:lbsyun.baidu.com/jsdemo.htm#…

简单初始化一下

引入百度地图

<script type="text/javascript" src="https://api.map.baidu.com/api?v=1.0&&type=webgl&ak=你的密钥"></script>

初始化地图

 map = new BMapGL.Map("container");
 map.centerAndZoom(new BMapGL.Point(116.404, 39.915), 12);
 map.enableScrollWheelZoom(true);

引入绘图工具库

<link href="//mapopen.cdn.bcebos.com/github/BMapGLLib/DrawingManager/src/DrawingManager.min.css" rel="stylesheet">
<script type="text/javascript" src="//mapopen.cdn.bcebos.com/github/BMapGLLib/DrawingManager/src/DrawingManager.min.js"></script>

初始化drawingManager

var styleOptions = {
    strokeColor: "#5E87DB", // 边线颜色
    fillColor: "#5E87DB", // 填充颜色。当参数为空时,圆形没有填充颜色
    strokeWeight: 2, // 边线宽度,以像素为单位
    strokeOpacity: 1, // 边线透明度,取值范围0-1
    fillOpacity: 0.2, // 填充透明度,取值范围0-1
  };
var labelOptions = {
    borderRadius: "2px",
    background: "#FFFBCC",
    border: "1px solid #E1E1E1",
    color: "#703A04",
    fontSize: "12px",
    letterSpacing: "0",
    padding: "5px",
  };
var drawingManager = new BMapGLLib.DrawingManager(map, {
    // isOpen: true,        // 是否开启绘制模式
    enableDrawingTool: false, // 是否添加绘制工具栏控件
    enableCalculate: false, // 绘制是否进行测距测面
    enableSorption: true, // 是否开启边界吸附功能
    sorptiondistance: 20, // 边界吸附距离
    circleOptions: styleOptions, // 圆的样式
    polylineOptions: styleOptions, // 线的样式
    polygonOptions: styleOptions, // 多边形的样式
    rectangleOptions: styleOptions, // 矩形的样式
    labelOptions: labelOptions, // label样式
    enableLimit: true, // 是否开启超限提示
    limitOptions: {
      area: 500000000, // 面积超限值
      distance: 30000, // 距离超限值
      minArea: 500000 // 自定义最小面积
    },
    skipEditing: false, // 是否跳过编辑状态
  });

组件库实际效果:

 // 需要绘制完成后获取相关的信息(面积等),而不是编辑状态下展示
drawingManager.addEventListener('overlaycomplete', function(e) {
  alert(e.calculate);
});

源码中仅有不超过的提示;

var html = '<div><span id="confirmOperate"></span><span id="cancelOperate"></span><span id="warnOperate">' + overlyTypeText + '不超过' + this.limit / 10000 + unit + '!</span></div>';

发生交叉有明显缺陷,且可以继续操作;缺少重新编辑相关的方法。
image.png

显然实际情况无法满足我们的需求,需要改造下DrawingManager.js源文件。

从官方CDN引入改成本地引用静态资源的方式。

<link href="/outLib/DrawingManager.css" rel="stylesheet">
<script src="/outLib/DrawingManager.js" type="text/javascript"></script>

这里需要借助copy-webpack-plugin插件将静态资源复制到dist文件夹下。

yarn add copy-webpack-plugin -D

vue-cli5.0下的配置vue.config.js

  ...
  chainWebpack: config => {
    config.plugin('copy').use(require('copy-webpack-plugin'), [
      {
        patterns: [
          {
            from: path.resolve(__dirname, './outLib'),
            to: path.resolve(__dirname, 'dist/outLib')
          }
        ]
      }
    ]);
  }

DrawingManager.js改造:

源文件大概组成:

  1. DrawingManager类的构造函数,包含主要功能;
  2. Operate类 确认,取消操作覆盖物;
  3. Screenshot类 显示,编辑半径覆盖物;
  4. Mask类 创建遮罩对象;
  5. DrawingTool 绘制工具面板,自定义控件;
  6. 声明baidu包等。

实现第一、二个需求:

  1. 编辑状态下展示面积大小label
  2. 限制最小面积和多边形最少顶点数;

我们需要在某个位置添加面积提示,如下图位置,并且添加更多的错误提示。

image.png

这里涉及确认,取消操作,既在Operate类中添加相关代码:

 Operate.prototype.initialize = function (map) {
    var me = this;
    this._map = map;
    var overlyTypeText = (this.type === 'polyline' ? '长度' : '面积');
    var unit = (this.type === 'polyline' ? '万米' : '万平方米');
    var div = this.div = document.createElement('div');
    div.className = 'operateWindow';
    var html = '<div><span id="confirmOperate"></span><span id="cancelOperate"></span><span id="warnOperate">' + overlyTypeText + '不超过' + this.limit / 10000 + unit + '!</span></div>';
    // 新增自定义label内容:normalOperate展示面积信息;warnOperate:展示自定义告警信息
    var polygonHtml =  '<div><span id="confirmOperate"></span><span id="cancelOperate"></span><span id="normalOperate"></span><span id="warnOperate"></span></div>';
    // 对非线类型替换成自定义label内容
    div.innerHTML = (this.type === 'polyline' ? html : polygonHtml);
    this._map.getPanes().markerPane.appendChild(div);
    this.updateWindow();
    this._bind();
    return div;
  };

Operate的updateWindow方法中修改代码:

  Operate.prototype.updateWindow = function () {
    if (this.domElement === null) {
      return;
    }
    var overlay = this.overlay;
    var limit = this.limit;
    var calculate;
    if (this.type == 'rectangle') {
      calculate = this.DrawingManager._calculate(overlay, overlay.getPath());
    }
    else if (this.type == 'circle') {
      calculate = this.DrawingManager._calculate(overlay, this.point);
    }
    else if (this.type == 'polygon') {
      calculate = this.DrawingManager._calculate(overlay, overlay.getPath());
    }
    else if (this.type == 'polyline') {
      calculate = this.DrawingManager._calculate(overlay, overlay.getPath());
    }

    // if (Object.prototype.toString.call(limit) === '[object Number]' && calculate.data > limit) {
    // 自定义的错误信息
    if (calculate.error) {
      // 错误文本框内容
      document.getElementById('warnOperate').innerText = calculate.error;
      document.getElementById('confirmOperate').style.display = 'none';
      // 有错误告警,展示错误文本框
      document.getElementById('warnOperate').style.display = 'block';
      // 有错误告警,隐藏面积文本框
      document.getElementById('normalOperate').style.display = 'none';
    }
    else {
      // 面积文本框内容
      document.getElementById('normalOperate').innerText = '区域面积约为' + Math.round((calculate.data / 1000 / 1000) * 100) / 100 + '平方千米';
      document.getElementById('confirmOperate').style.display = 'block';
      // 隐藏错误文本框
      document.getElementById('warnOperate').style.display = 'none';
      // 正常展示面积文本框
      document.getElementById('normalOperate').style.display = 'block';
    }
  };

回头看一下DrawingManager._calculate方法,该方法用于添加显示所绘制图形的面积或者长度。

  DrawingManager.prototype._calculate = function (overlay, point) {
    var result = {
      data: 0, // 计算出来的长度或面积
      label: null, // 显示长度或面积的label对象
      error: null // 自定义错误信息
    };

    if (this._enableCalculate && BMapGLLib.GeoUtils) {
      var type = overlay.toString();
      // 不同覆盖物调用不同的计算方法
      switch (type) {
        case 'Polyline': //[object Polyline]==>在3D版本中已经转为了Polyline
          result.data = BMapGLLib.GeoUtils.getPolylineDistance(overlay);
          break;
        // 主要改造多边形,矩形包含在内
        case 'Polygon':
          var pts = overlay.getPath();
          // 判断最少顶点数
          // 最近发现只画两个点setPath阶段会填充到三个点,<3的判断条件不适用了
          // 还需要判断三个点头尾不相同的情况
          // 引入这个情况,很容易想到一个问题:其他位置多个重复点的情况
          // 测试下来除了首尾重复点其他位置有重复会报错,后续去重也是一个优化点
          if (pts.length < 3) {
            result.error = '小于3个顶点,不能构建面';
          }
          // 多边形面积
          result.data = BMapGLLib.GeoUtils.getPolygonArea(overlay);
          break;
        case 'Circle':
          var radius = overlay.getRadius();
          result.data = Math.PI * radius * radius;
          break;
      }
      // 异常情况处理
      // 已有错误
      if (result.error) {
        result.data = 0;
      } else if (!result.data ||result.data < 0) {
        result.error = '面积计算异常,请重新绘制!';
        result.data = 0;
        // console.error('计算函数异常处理');
      } else if (Object.prototype.toString.call(limit.area) === '[object Number]' && result.data > limit.area) {
        result.data = 0;
        result.error = '面积不能超过' + limit.area / 1000 / 1000 + '平方千米!';
      } else if (Object.prototype.toString.call(limit.minArea) === '[object Number]' && result.data < limit.minArea) {
        result.data = 0;
        result.error = '面积不能小于' + limit.minArea / 1000 / 1000 + '平方千米!'
      } else {
        // 保留2位小数位
        result.data = result.data.toFixed(2);
      }

    }
    return result;
  };
image.png image.png

image.png
面积展示缺少样式,在DrawingManager.css中添加:

.operateWindow div #normalOperate {
  float: left;
  text-align: left;
  padding-left: 8px;
  width: 200px;
  height: 30px;
  margin-left: 15px;
  line-height: 30px;
  background: #fffbcc;
  border: 1px solid #e1e1e1;
  border-radius: 2px;
  font-family: PingFangSC-Regular;
  font-size: 14px;
  color: #703a04;
  letter-spacing: 0;
  cursor: default;
}

image.png
到此算是实现了第一条和第二条需求。

实现第三、四个需求:

  1. 禁止自定义多边形交叉;
  2. 标记多边形交叉点(实际使用场景中,多点绘图处出现有交叉但是用户分辨不出具体位置从而无法调整的情况);

如何判断多边形交叉及找到标记点?

引入truf.js:一款地理空间分析库,用于处理各种地图算法。
官网 turfjs.fenxianglu.cn/

<script src="https://unpkg.com/@turf/turf/turf.min.js"></script>

turf.kinks方法返回交叉点。

// 数据收集
var poly = turf.polygon([[  [-12.034835, 8.901183],
  [-12.060413, 8.899826],
  [-12.03638, 8.873199],
  [-12.059383, 8.871418],
  [-12.034835, 8.901183]
]]);
// 返回交叉点
var kinks = turf.kinks(poly);

还需要用到turf.coordAll方法:从任何GeoJSON对象获取所有坐标。
Tips:百度坐标系(BD09),无法直接使用,需要转换为地心坐标系(WGS84);

var pts84 = []
// bd09转wgs84
for (var i = 0; i < pts.length; i++) {
   pts84.push(BMapGLLib.GeoUtils.bd09towgs84(pts[i].lng, pts[i].lat))
}
var turfPolygon = turf.polygon([pts84])
var turfKinks = turf.coordAll(turf.kinks(turfPolygon))
if (turfKinks.length) {
   result.error = '图形边界请勿交叉绘制';
}

这里执行报了个错
image.png
需要判断首尾是否相同行成闭环,添加判断:

// 判断首尾是否相同
if (!turf.booleanEqual(turf.point(pts84[0]), turf.point(pts84[pts84.length - 1]))) {
   pts84.push(pts84[0]);
}

image.png
接下来就是标记出交叉得地方:

// wgs84转回bd09
var exData = []
for (var i = 0; i < turfKinks.length; i++) {
  const aa = BMapGLLib.GeoUtils.wgs84togcj02(turfKinks[i][0], turfKinks[i][1])
  const bb = BMapGLLib.GeoUtils.gcj02tobd09(aa[0], aa[1])
  exData.push(bb)
}
// 遍历标记marker
for (var i = 0; i < exData.length; i++) {
  const pointItem = new BMapGL.Point(exData[i][0], exData[i][1])
  // 自定义Icon
  // const img = ''
  // var myIcon = new BMapGL.Icon(img, new BMapGL.Size(18, 18))
  var marker = new BMapGL.Marker(pointItem)
  // marker.setIcon(myIcon);
  this._map.addOverlay(marker)
}

image.png
引入问题:lineupdate调整更新后,原marker需要移除。
解决:定义了一个全局变量_markers记录交叉点 ,循环遍历removeOverlay移除标记marker。

for (var i = 0; i < _markers.length; i++) {
  this._map.removeOverlay(_markers[i]);
}
_markers.length = 0

实现第五个需求:

最后来看一下多边形编辑功能:我们希望将画好的多边形,恢复成第二张图的样子,重新进入编辑状态。
image.png

image.png
在DrawingManager的原型链上添加editOverlays方法,实现方法参考绑定鼠标画线或多边形事件DrawingManager.prototype._bindPolylineOrPolygon

  /**
   * 自定义编辑
   * drawingType:绘制模式
   * _overlay:覆盖物
   */
  DrawingManager.prototype.editOverlays = function (drawingType, _overlay) {
    var me = this
    var overlay = _overlay
    this._drawingType = drawingType
    var limit = null;
    function getNorthEast() {
      var bound = arguments[0];
      var maxlng = 0;
      var index = 0;
      for (var j = 0; j < bound.length; j++) {
        if (maxlng < bound[j].lng) {
          maxlng = bound[j].lng;
          index = j;
        }
      }
      return bound[index];
    }
    // 判断重复编辑
    var alreadyEdit = false
    var exitOverlays = this._map.getOverlays()
    for (var i = 0; i < exitOverlays.length; i++) {
      if (exitOverlays[i] instanceof Operate) {
        alreadyEdit = true
      }
    }
    // 多边形
    if (overlay && !alreadyEdit && drawingType === BMAP_DRAWING_POLYGON) {
      // 开启编辑功能
      overlay.enableEditing()
      
      if (me.limit) {
        limit = me.limit;
      }
      // 定义目标覆盖物相关参数
      var targetOverlay = {
        limit: limit,
        type: 'polygon',
        point: null,
        overlay: overlay,
        overlays: []
      };    
      var operateWindow = new Operate(targetOverlay, me);
      this._map.addOverlay(operateWindow);
   
      // 获得覆盖物上东北向的点坐标,文本生成位置
      var point = getNorthEast(overlay.getPath());
      operateWindow.setPosition(point, true);
      operateWindow.updateWindow();
      
      // 监听线段编辑更新
      overlay.addEventListener('lineupdate', function (e) {
        var point = getNorthEast(e.currentTarget.getPath());
        operateWindow.setPosition(point, true);
        operateWindow.updateWindow();
      });
    }
  }

调用

const editData = () => {
  // 
  if (drawMode.value === "polygon") {
    drawingManager.editOverlays(drawMode.value, curOverlay.value);
  }
};

需要注意的是首次开启编辑时需要更新视窗一次;进入编辑状态后需要添加判断防止重复触发。

结语

到此实现了上述需求。另外,官方BMapGLLib.GeoUtils库并没有坐标系转换方法,如bd09towgs84等需要在项目中自行添加;为了兼容性,新添加的代码中没有用到ES6语法。
以上就是本文全部内容,希望这篇文章对大家有所帮助。