Search code examples
swifttrigonometrycomputational-geometry

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


i'm struggling with this geometry problem right now.

Let's say we have a line defined by point A(x1,y1) and point B(x2,y2) We also have a point C(x3,y3).

What function written in SWIFT could give me the coordinates (X,Y) of the point that has the smallest distance from the line ? In other words, the point on the line which is the intersection between a perpendicular segment and the other point.

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

Solution

  • In a mathematical point of view you can :

    • first find the equation of the line :

          y1 = a1x1+b1 
          a1 = (y2-y1) / (x2-x1)
          b1 = y1-a1*x1
      
    • Then calculate the gradient of the second line knowing :

          a1 * a2 = -1 <-> 
          a2 = -1/a1
      
    • with a2 you can find the value of b for the second equation :

          y3 = a2*x3 + b2 <-> 
          b2 = y3 - a2*x3
      
    • Finally calculate the intersection of the 2 lines :

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

    Then it's quite straightforward to bring that to 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)
    }