需求分析:
由于公司是做航运平台方面的,需要用到地图去展示商品运输过程中的状态以及位置,最终选用了国内优秀的地图提供商--高德地图去处理。在使用过程中遇到一个需求:需要根据卡车的运行轨迹进行画线,并且因为可能在一个订单中存在多辆卡车,所以就存在了多条轨迹线路,此时需要提供用户点击折线,切换展示不同的卡车信息的功能。
处理画卡车轨迹还是比较轻松就可以解决掉的,直接拿到卡车的轨迹经纬度数组,使用高德提供的绘制折线功能就可以轻松搞定了,但是在处理点击折线的时候遇到了问题,遍寻高德的开发文档以及API接口文档也没能找到对应的处理方法(可能是我比较菜😜),不过安卓倒是有对应的处理方法,这让我郁闷了好久,不过问题还是要解决啊...
在网上查了好久,终于找到一种比较合适的替代方法。传送门
实现方法:
- 判断点击的屏幕位置是否在折线的区域外接矩形对应的屏幕坐标系的rect内,在则继续操作,否则则判断此次点击无效。
- 将折线对应的经纬度转换为屏幕坐标点。
- 获取屏幕点击点到折线对应的屏幕坐标点每两个相邻点的的最短距离,并放入一个数组。
- 将距离数组排序,取出最小距离。
- 判断获取屏幕点击点到所有折线中最小距离的的折线,并判断距离是否大于50,如果小于50则判定点击有效,获取折线对应的卡车信息,进行处理。否则则判定此次点击无效。
代码实现:
class XHMapExtHandle: NSObject {
/// 判断一个点是否在一个折线范围内,并返回点到折线的最短距离
///
/// - Parameters:
/// - coordinate: 地图上的对应坐标
/// - polyline: 折线
/// - Returns:
class func isPointOnPloyline(_ point: CGPoint,_ polyline: MAPolyline,_ mapView: MAMapView) -> (Bool, CGFloat) {
let region = MACoordinateRegionForMapRect(polyline.boundingMapRect)
let rect = mapView.convertRegion(region, toRectTo: mapView)
//判断当前点击点是否在指定view坐标系的rect内,否者返回false
if rect.contains(point) == false {
return (false, 0.0)
}
//获取距离数组
var disArray: [CGFloat] = []
//获取折线的坐标点
let pts = polyline.points
//获取坐标点个数
let count = polyline.pointCount
for i in 0..<count - 1 {
let curPoint = pointFromCoordinate(mapPoint: pts?[Int(i)], mapView: mapView)
let nextPoint = pointFromCoordinate(mapPoint: pts?[Int(i + 1)], mapView: mapView)
let dis = disPointToLine(point, curPoint, nextPoint)
if dis.isNaN == false {
disArray.append(dis)
}
}
disArray.sort()
let mixDis = disArray.first ?? 100000
return (true, mixDis)
}
/// 获取一个点到一条线段的最短距离
///
/// - Parameters:
/// - point: 原点
/// - startPoint: 线段起点
/// - endPoint: 线段结束点
/// - Returns: 最短距离
class func disPointToLine(_ point: CGPoint, _ startPoint: CGPoint, _ endPoint: CGPoint) -> CGFloat {
//point到startPoint的长度,记作b线段
let distancePoToS = disBetweenTwoPoints(point, startPoint)
//point到endPoint的长度,记作c线段
let distancePoToE = disBetweenTwoPoints(point, endPoint)
//startPoint到endPoint的长度,记作a线段
let distanceSToE = disBetweenTwoPoints(startPoint, endPoint)
if distancePoToE + distancePoToS == distanceSToE {
//当distancePoToS+distancePoToE=distanceSToE时,point在startPoint和endPoint组成的线段上
return 0.0
} else if distancePoToE * distancePoToE >= distancePoToS * distancePoToS + distancePoToE * distancePoToE {
//当c*c>=a*a+b*b时组成直角三角形或钝角三角形,投影在distancePoToS延长线上
return distancePoToS;
} else if distancePoToS * distancePoToS >= distancePoToE * distancePoToE + distanceSToE * distanceSToE {
//当b*b>c*c+a*a时组成直角三角形或钝角三角形,投影在distancePoToE延长线上
return distancePoToE
} else {
//其他情况组成锐角三角形,则求三角形的高
let p = (distancePoToS + distancePoToE + distanceSToE) / 2.0 // 半周长
let s = sqrt(p * (p - distanceSToE) * (p - distancePoToS) * (p - distancePoToE)) // 海伦公式求面积
let dis = 2 * s / distanceSToE
return dis // 返回点到线的距离(利用三角形面积公式求高)
}
}
/// 计算两个平面点之间的距离
///
/// - Parameters:
/// - point1: 点1
/// - point2: 点2
/// - Returns: 两点之间的距离
class func disBetweenTwoPoints(_ point1: CGPoint,_ point2: CGPoint) -> CGFloat {
let disX = point1.x - point2.x
let disY = point1.y - point2.y
let distance = sqrtf(Float(disX * disX + disY * disY))
return CGFloat(distance)
}
/// 平面点和经纬度的转换
///
/// - Parameters:
/// - mapPoint: 平面点
/// - mapView: 对应的视图
/// - Returns: 平面点
class func pointFromCoordinate(mapPoint: MAMapPoint?, mapView: MAMapView) -> CGPoint {
guard let mapPoint = mapPoint else {
return CGPoint.init()
}
let coordinate = MACoordinateForMapPoint(mapPoint)
let point = mapView.convert(coordinate, toPointTo: mapView)
return point
}
}
网友评论