百度地图 - 基础学习(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 的配套运用,开发人员可以在地图上进行各种交互操作及计算、判定,如:区域面积、行程长度、地址点是否在某个区域以及地址点是否在某个行程上;然后根据这些计算值、判定值形成其他功能实际需要的基础数据。