如何获取直线上与另一点距离最小的点的坐标

How to get the coordinates of the point on a line that has the smallest distance from another point

我现在正在为这个几何问题而苦恼。

假设我们有一条由点 A(x1,y1) 和点 B(x2,y2) 定义的线 我们还有一个点 C(x3,y3).

SWIFT中写的什么函数可以给我离直线距离最小的点的坐标(X,Y)?换句话说,直线上的点是垂直线段与另一点的交点。

func getCoordsOfPointsWithSmallestDistanceBetweenLineAndPoint(lineX1: Double, lineY1: Double, lineX2: Double, lineY2: Double, pointX3: Double, pointY3: Double) -> [Double] {
    // ???
    return [x,y]
}

从数学的角度你可以:

  • 先求直线的方程:

        y1 = a1x1+b1 
        a1 = (y2-y1) / (x2-x1)
        b1 = y1-a1*x1
    
  • 然后计算第二行知道的梯度:

        a1 * a2 = -1 <-> 
        a2 = -1/a1
    
  • 用 a2 你可以找到第二个等式的 b 值:

        y3 = a2*x3 + b2 <-> 
        b2 = y3 - a2*x3
    
  • 最后计算出两条线的交点:

        xi = (b2-b1) / (a1-a2)
        y = a1*xi + b1
    

然后把它带到 swift 就很简单了:

typealias Line = (gradient:CGFloat, intercept:CGFloat)

func getLineEquation(point1:CGPoint, point2:CGPoint) -> Line {
    guard point1.x != point2.x else {
        if(point1.y != point2.y)
        {
            print("Vertical line : x = \(point1.x)")
        }
        return (gradient: .nan, intercept: .nan)
    }
    let gradient = (point2.y - point1.y)/(point2.x-point1.x)
    let intercept = point1.y - gradient*point1.x
    return (gradient: gradient, intercept: intercept)
}

func getPerpendicularGradient(gradient:CGFloat) -> CGFloat
{
    guard gradient != 0 else {
        print("horizontal line, the perpendicilar line is vertical")
        return .nan
    }
    return -1/gradient
}

func getIntercept(forPoint point:CGPoint, withGradient gradient:CGFloat) -> CGFloat
{
    return point.y - gradient * point.x
}

func getIntersectionPoint(line1:Line, line2:Line)-> CGPoint
{
    guard line1.gradient != line2.gradient else {return CGPoint(x: CGFloat.nan, y: CGFloat.nan)}
    let x = (line2.intercept - line1.intercept)/(line1.gradient-line2.gradient)
    return CGPoint(x:x, y: line1.gradient*x + line1.intercept)
}

func getClosestIntersectionPoint(forLine line:Line, point:CGPoint) -> CGPoint
{
    let line2Gradient = getPerpendicularGradient(gradient:line.gradient)
    let line2 = (
        gradient: line2Gradient,
        intercept: getIntercept(forPoint: point, withGradient: line2Gradient))
    
    return getIntersectionPoint(line1:line, line2:line2)
}

func getClosestIntersectionPoint(forLinePoint1 linePoint1:CGPoint, linePoint2:CGPoint, point:CGPoint) -> CGPoint
{
    return getClosestIntersectionPoint(
        forLine:getLineEquation(point1: linePoint1, point2: linePoint2),
        point:point)
}

您可以最小化 C 到直线 AB 上一点的平方距离:

(CA + t.AB)² = t²AB² + 2t AB.CA + CA²

最低达到

t = - AB.CA / AB²

CP = CA + t.AB

详细说明 如果转换为函数,其形式为

func closestPnt(x: Double, y: Double, x1: Double, y1: Double, px: Double, py: Double)->[Double]{
    let vx = x1 - x  // vector of line
    let vy = y1 - y
    let ax = px - x  // vector from line start to point
    let ay = py - y
    let u = (ax * vx + ay * vy) / (vx * vx + vy * vy) // unit distance on line
    if u >= 0 && u <= 1 {                             // is on line segment
        return [x + vx * u, y + vy * u]               // return closest point on line
    }
    if u < 0 {
        return [x, y]      // point is before start of line segment so return start point
    }
    return [x1, y1]       // point is past end of line so return end
}

注意该函数是针对线段的,如果最近的点单位距离在起点后面或超过终点则终点最近。

如果你想要一条线上的点(无限长),那么下面的方法就可以了。

func closestPnt(x: Double, y: Double, x1: Double, y1: Double, px: Double, py: Double)->[Double]{
    let vx = x1 - x  // vector of line
    let vy = y1 - y
    let ax = px - x  // vector from line start to point
    let ay = py - y
    let u = (ax * vx + ay * vy) / (vx * vx + vy * vy) // unit distance on line
    return [x + vx * u, y + vy * u]                 // return closest point on line
    
}

注意 这两个函数都假定 !(x1 == x && y1 == y) 为真。即线段的长度必须 > 0.