Swift 已知地图上一线段和点,求点到线段的最短距离

需求

需要计算地图上点到线段的实际距离,此时有 2 种分布情况(不考虑点与线段在同一条直线上):

A 分布:

1
2
			*
————————————————————

B 分布:

1
2
*
————————————————————

最开始的思路

可参考:

  1. 計算 CGPoint 兩點距離的 hypotf & hypot
  2. 计算点到线段的最近距离
  3. 计算点到线段最短距离—矢量法
  4. 数学——点到线段的最短距离

采用这种方法,无论是 A 分布还是 B 分布,都能直接算出距离。

转换成 Swift 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/// - Parameters:
/// - point0: 点
/// - point1: 线段端点
/// - point2: 线段端点
/// - Returns: 距离
func getDistanceOfPointToSegment(point0: MKMapPoint, point1: MKMapPoint, point2: MKMapPoint) -> Float {

let k = -((point1.x - point0.x) * (point2.x - point1.x) + (point1.y - point0.y) * (point2.y - point1.y)) / ((point1.x - point2.x) * (point1.x - point2.x) + (point1.y - point2.y) * (point1.y - point2.y))

if k <= 0 {
let length1 = Float(point1.x - point0.x)
let length2 = Float(point1.y - point0.y)
return sqrtf(pow(length1, 2) + pow(length2, 2))
} else if k >= 1 {
let length1 = Float(point2.x - point0.x)
let length2 = Float(point2.y - point0.y)
return sqrtf(pow(length1, 2) + pow(length2, 2))
} else {
let xf = k * (point2.x - point1.x) + point1.x
let yf = k * (point2.y - point1.y) + point1.y
return sqrtf(pow(Float(xf - point0.x), 2) + pow(Float(yf - point0.y), 2))
}

}

使用:

1
2
3
4
5
6
// 将地图坐标转换成 MKMapPoint
let point0: MKMapPoint = MKMapPoint(dot0)
let point1: MKMapPoint = MKMapPoint(dot1)
let point2: MKMapPoint = MKMapPoint(dot2)

let distance = getDistanceOfPointToSegment(point0: point0, point1: point1, point2: point2)

不过这样存在问题,这里得出来的 distance 并非实际距离,应该是类似 CG 距离之类的。而实际上需要用到的是 CLLocationDistance。

新思路

AB 分布分开讨论。

首先要区分 AB 分布:可以发现 B 分布下点与线段端点的夹角大于 90 度;而 A 分布下点与线段端点的夹角均小于等于 90 度。

所以可以通过计算点与线段端点的夹角来确定是 A 分布还是 B 分布。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/// 计算三点之间的角度
///
/// - Parameters:
/// - point1: 点1
/// - point2: 点2(也是角度所在点)
/// - point3: 点3
/// - Returns: 角度(180度制)
func getAnglesWithThreePoints(point1: MKMapPoint, point2: MKMapPoint, point3: MKMapPoint) -> Double {
//排除特殊情况,三个点一条线
if (point1.x == point2.x && point2.x == point3.x) || ( point1.y == point2.x && point2.x == point3.x){
return 0
}

let a = fabs(point1.x - point2.x)
let b = fabs(point1.y - point2.y)
let c = fabs(point3.x - point2.x)
let d = fabs(point3.y - point2.y)

if (a < 1.0 && b < 1.0) || (c < 1.0 && d < 1.0){
return 0
}
let e = a*c+b*d
let f = sqrt(a*a+b*b)
let g = sqrt(c*c+d*d)
let r = Double(acos(e/(f*g)))
// return r //弧度值
return (180*r/Double.pi) //角度值
}

参考:Swift 计算三角形角度、两条边夹角

A 分布

1
2
			*
————————————————————

此时,点到线段的距离为从点出发的三角形垂线距离。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/// 计算三角形垂线长度
///
/// - Parameters:
/// - point1: 端点
/// - point2: 端点
/// - center: 垂线起始点
/// - Returns: 长度
func getVerticalVal(point1: MKMapPoint, point2: MKMapPoint, center: MKMapPoint) -> Double {

let a = point1.distance(to: point2)
let b = center.distance(to: point1)
let c = center.distance(to: point2)

let p = (a + b + c) / 2.0
let s = sqrt(p * (p-a) * (p-b) * (p-c))

return s * 2.0 / a
}

参考:

  1. 海伦公式求三角形垂线长度
  2. 已知三角形三点坐标求一边上的高(海伦公式和坐标推导)

B 分布

计算「钝角端点」与点的距离即可。


Swift 已知地图上一线段和点,求点到线段的最短距离
https://wonderhoi.com/2025/08/05/Swift-已知地图上一线段和点,求点到线段的最短距离/
作者
wonderhoi
发布于
2025年8月5日
许可协议