Search code examples
iosswiftgeometrycore-graphics

iOS - Check [CGPoint] make a straight line


Assuming I have an array of CGPoints, how can I check if they produce a straight line?

For example, imagine a grid. Assuming I plotted all of the points on the grid, how can I check if all the points make either a straight horizontal, vertical or diagonal line?


Solution

  • One way to do this is to compute the Line of Best Fit for your points to determine if they lie on a line.

    func pointsFormALine(_ points: [CGPoint]) -> Bool {
    
        // helper function to test if CGFloat is close enough to zero
        // to be considered zero
        func isZero(_ f: CGFloat) -> Bool {
            let epsilon: CGFloat = 0.00001
    
            return abs(f) < epsilon
        }
    
        // variables for computing linear regression
        var sumXX: CGFloat = 0  // sum of X^2
        var sumXY: CGFloat = 0  // sum of X * Y
        var sumX:  CGFloat = 0  // sum of X
        var sumY:  CGFloat = 0  // sum of Y
    
        for point in points {
            sumXX += point.x * point.x
            sumXY += point.x * point.y
            sumX  += point.x
            sumY  += point.y
        }
    
        // n is the number of points
        let n = CGFloat(points.count)
    
        // compute numerator and denominator of the slope
        let num = n * sumXY - sumX * sumY
        let den = n * sumXX - sumX * sumX
    
        // is the line vertical or horizontal?
        if isZero(num) || isZero(den) {
            return true
        }
    
        // calculate slope of line
        let m = num / den
    
        // calculate the y-intercept
        let b = (sumY - m * sumX) / n
    
        print("y = \(m)x + \(b)")
    
        // check fit by summing the squares of the errors
        var error: CGFloat = 0
        for point in points {
            // apply equation of line y = mx + b to compute predicted y
            let predictedY = m * point.x + b
            error += pow(predictedY - point.y, 2)
        }
    
        return isZero(error)
    }
    

    Test:

    pointsFormALine([CGPoint(x: 1, y: 2), CGPoint(x: 2, y: 4), CGPoint(x: 5, y: 10)])  // true
    pointsFormALine([CGPoint(x: 1, y: 2), CGPoint(x: 1, y: 4), CGPoint(x: 1, y: 10)])  // true
    pointsFormALine([CGPoint(x: 1, y: 2), CGPoint(x: 2, y: 2), CGPoint(x: 5, y: 2)])   // true
    pointsFormALine([CGPoint(x: 1, y: 2), CGPoint(x: 2, y: 1), CGPoint(x: 2, y: 2)])   // false