百度地图 - 基础学习(7): GeoUtils类其他方法的使用(折线长度、点是否在区域内、点是否在线上)


上一次  完成了鼠标绘制工具的添加,GeoUtils类的引入和密闭图形面积的计算,这次在上一次基础上继续 GeoUtils类其他方法的使用(折线长度、点是否在区域内)。

一、计算折线长度

计算折线长度,实际上就是计算行程长度,通过对折线长度的计算,可以得到整个行程的里程长度,进而可以进行一系列附加计算。折线长度计算方法为 getPolylineDistance(polyline),使用方式:

BMapLib.GeoUtils.getPolylineDistance(polyline) // 折线对象或坐标点数组
// 计算折线长度
getPolylineLength() {
  if (this.polylineObj) {
    let polylineLength = BMapLib.GeoUtils.getPolylineDistance(
      this.polylineObj.pointList
    );
    alert(`当前路线长度为${polylineLength}米`);
  } else {
    alert("请先绘制路线!");
  }
}

以故宫博物院为例:故宫博物院南北961米东西753米,城墙的外沿周长3428米。从实际资料和计算结果可以看出计算结果相当精准。

(南北长)、(东西长)、(外沿周长)

(折线图)

二、点是否在区域内

判断坐标点是否在某个区域内,方法有三种:1、判断点是否在圆形内:isPointInCircle(point, circle);2、判断点是否多边形内:isPointInPolygon(point, polygon);3、判断点是否在矩形内:isPointInRect(point, bounds)

// 判断点是否在圆形内
BMapLib.GeoUtils.isPointInCircle(
  this.markerPoint, // 标点坐标
  this.circleObj.overlay // 对应的绘制模式返回的覆盖物对象
);

// 判断点是否在多边形、矩形内(多边形和矩形都是密闭多边形,故而可以用同一个方法判定。另外不用矩形专用方法判定,后面会阐述)
let overlay = this.polygonObj
  ? this.polygonObj.overlay
  : this.rectangleObj.overlay;
BMapLib.GeoUtils.isPointInPolygon(this.markerPoint, overlay);

(标点在圆形区域内)、(标点在圆形区域外)

 (标点在多边形区域内)、(标点在j矩形区域外)

isPointInRect(point, bounds) 方法特殊处理

1、明明矩形有一个专用判定方法 isPointInRect(point, bounds),那为啥上面反而要用多边形的判定方法而不是用这个专用方法。因为该方法调用时还需要另外处理如果不处理,那对不起,判定不准看下面例子:

// 判断点是否在矩形内
BMapLib.GeoUtils.isPointInRect(
  this.markerPoint,
  this.rectangleObj.overlay  // 按照其他方法将绘制完成的 矩形覆盖物对象(注:这里传入的是覆盖物对象) 传入
)

// 翻了翻方法源码,发现 (bounds instanceof BMap.Bounds)在进行判定时一直是false,故而一直判定不对
GeoUtils.isPointInRect = function(point, bounds) {
  // 检查类型是否正确
  if (!(point instanceof BMap.Point) || !(bounds instanceof BMap.Bounds)) {
    return false;
  }
  var sw = bounds.getSouthWest(); // 西南脚点
  var ne = bounds.getNorthEast(); // 东北脚点
  return (
    point.lng >= sw.lng &&
    point.lng <= ne.lng &&
    point.lat >= sw.lat &&
    point.lat <= ne.lat
  );
};

(标点明明在矩形正中间,判定结果居然在区域外)

2、那具体该怎么处理,看文档其他两个方法,参数说明都是“圆形对象”、“多边形对象”,这些都是 对应的绘制模式返回的覆盖物对象;但到了矩形这不一样了,人家要的是“矩形边界对象”,问题就出在这个“边界对象”上,边界对象 != 覆盖物对象

所以需要将覆盖物对象转成“边界对象” ,覆盖物对象调用 getBounds() 方法可以转成边界对象。

this.rectangleObj.overlay.getBounds()
// 覆盖物对象调用 getBounds()方法将覆盖物对象转成边界对象
BMapLib.GeoUtils.isPointInRect(
  this.markerPoint,
  this.rectangleObj.overlay.getBounds()
);

、  ;参数处理后,立即就准了。

三、点是否在线上

判定点是否在线上,实际运用中可以用来判定某个地址是否在既有行程路线(公路、铁路)上,或者判定行程(自驾、骑行)是否会过某个地址。判断点是否在折线上方法为 isPointOnPolyline(point, polyline),使用方式:

BMapLib.GeoUtils.isPointOnPolyline(
  this.markerPoint,
  this.polylineObj.overlay  // 返回的折线覆盖物对象
)

方法的使用没得问题,但是其判定结果却有问题,如下所示:标点是在比例尺放大到 1:20时准确标定的(故宫午门位置)。这不是一次偶然的标点结果,就标点实测了不下40次,结果却一次都没对过(也可能是手抖了吧)。

一度以为是方法的算法有问题,但在阅读了源码后,发现算法没得问题,有问题的是最后判定的精度(精度太高了,1:20比例尺标出的点超出了折线范围)。实测了下这个判定的精度应该是米级精度,即折线如果是双向四车道马路的中间线的话,标点应该不能超出两边的人行道。

// 判断点是否在直线上公式
var precision =
  (curPt.lng - point.lng) * (nextPt.lat - point.lat) -
  (nextPt.lng - point.lng) * (curPt.lat - point.lat);
if (precision < 2e-10 && precision > -2e-10) {
  // 实质判断是否接近0
  return true;
}

试着将判定的精度往下降降,实测降到 2e-8 和 -2e-8,则在 1:20比例尺下,标注的点可以正确判定。同理在1:200比例尺下(比例尺越大,标点位置差距越大,误差也越大),又不准了,需要再次降低精度。

(还是午门位置)

源码修改后的方法及调用:

/**
 * 判断点是否在折线上
 * @param {Object} point 点对象
 * @param {Object} polyline 折线对象
 * @param {Number} zoom 地图缩放比例
 * @returns {Boolean} 点在折线上返回true,否则返回false
 */
// [1:20米(简称20米,后同),50米,100米,200米,500米,1公里,2公里,5公里,10公里,20公里,25公里,50公里,100公里,200公里,500公里,1000公里,2000公里,5000公里,10000公里]
// 比例尺分别对应地图缩放级别:
// [19级,18级,17级,16级,15级,14级,13级,12级,11级,10级,9级,8级,7级,6级,5级,4级,3级,2级,1级]
// 注:在3级缩放级别下,世界地图大小已经和容器大小一致;
// 2及缩放级别下,地图大小是容器的一半;
// 1级缩放级别下,地图的长宽约为容器的四分之一,而且极容易找不到地图,因此,在使用百度地图时最好加上放大倍数限制。
isPointOnPolyline(point, polyline, zoom) {
  // 检查类型
  if (
    !(point instanceof BMap.Point) ||
    !(polyline instanceof BMap.Polyline)
  ) {
    return false;
  }

  // 首先判断点是否在线的外包矩形内,如果在,则进一步判断,否则返回false
  var lineBounds = polyline.getBounds();
  if (!BMapLib.GeoUtils.isPointInRect(point, lineBounds)) {
    return false;
  }

  // 判断点是否在线段上,设点为Q,线段为P1P2 ,
  // 判断点Q在该线段上的依据是:( Q - P1 ) × ( P2 - P1 ) = 0,且 Q 在以 P1,P2为对角顶点的矩形内
  var pts = polyline.getPath();
  for (var i = 0; i < pts.length - 1; i++) {
    var curPt = pts[i];
    var nextPt = pts[i + 1];
    // 首先判断point是否在curPt和nextPt之间,即:此判断该点是否在该线段的外包矩形内
    if (
      point.lng >= Math.min(curPt.lng, nextPt.lng) &&
      point.lng <= Math.max(curPt.lng, nextPt.lng) &&
      point.lat >= Math.min(curPt.lat, nextPt.lat) &&
      point.lat <= Math.max(curPt.lat, nextPt.lat)
    ) {
      // 判断点是否在直线上公式
      var precision =
        (curPt.lng - point.lng) * (nextPt.lat - point.lat) -
        (nextPt.lng - point.lng) * (curPt.lat - point.lat);
      return this.getDecisionAccuracy(zoom, precision);
    }
  }
  return false;
},

// 精度判定,根据地图缩放比例zoom的大小,修正判定精度
getDecisionAccuracy(zoom, precisionVal) {
  switch (zoom) {
    case 20: {
      // 百度地图缩放层级最高19级,这里写20,只是表明要想达到原方法的判定精度,缩放比例估计要20才行
      if (precisionVal < 2e-10 && precisionVal > -2e-10) {
        // 实质判断是否接近0
        return true;
      }
      break;
    }
    case 19: {
      if (precisionVal < 2e-8 && precisionVal > -2e-8) {
        // 实质判断是否接近0
        return true;
      }
      break;
    }
    // ... 其他缩放层级对应精度有待实测
    default: {
      return false;
    }
  }
},

// 获取点与折线的关系
getPointAndPolylinesRelation() {
  // 判定折线是否存在
  // 判定点是否存在(鼠标绘制工具标注的点,非鼠标事件点击点)
  if (this.polylineObj && this.markerObj) {
    if (
      this.isPointOnPolyline(
        this.markerPoint,
        this.polylineObj.overlay,
        this.mapInstance.getZoom()
      )
    ) {
      alert("~当前标点在折线上~");
    } else {
      alert("!当前标点不在折线上!");
    }
  } else {
    if (!this.polylineObj) {
      alert("请绘制折线!");
      return;
    }
    if (!this.markerObj) {
      alert("请添加坐标点!");
    }
  }
},

 

四、总结

通过 鼠标绘制工具管理类DrawingManager 和 几何运算类GeoUtils 的配套运用,开发人员可以在地图上进行各种交互操作及计算、判定,如:区域面积、行程长度、地址点是否在某个区域以及地址点是否在某个行程上;然后根据这些计算值、判定值形成其他功能实际需要的基础数据。