1

There may have been questions asked about finding if a point is on a line but not in this context and they don't go into answering why various methods have different flaws. What is the best method for accurately finding if a point is on a line given the the coordinate of the point and the coordinates of the line? I've attempted to implement certain methods on my own but they all seem to have their own problems after tests. I'm sure there are other methods out there and I know Java has a method that calculates the distance of a point from a line and returns 0 if its on the line. What method does Java use to find out if a point is on a line?

Method One

The first method compares the distance from point A to point B with the distance from point A to C plus point C to B. The problem with this method is that it isn't precise since it uses Math.sqrt.

public static boolean inLine(double x1, double y1, double x2, double y2, double x3, double y3){
    if((distance(x1, y1, x3, y3) + distance(x2, y2, x3, y3)) == distance(x1, y1, x2, y2)){
        return true;
    }
    return false;
}
public static double distance(double x1, double y1, double x2, double y2){
    double base = x2 - x1;
    double height = y2 - y1;
    double hypotenuse = Math.sqrt((base * base) + (height * height));
    return hypotenuse;
}

Method Two

This is the second method I came up with. It checks if the point is on the line by comparing the the y value of the point to the y value on the line with the same x value as the point. The problem with this method is that it won't work when the line is vertical or horizontal so I implemented some tests to check if its on the line if the line is horizontal or vertical.

public static boolean inLine(double x1, double y1, double x2, double y2, double x3, double y3){

//Check if X and Y Values of the point are within the range of the line
    if(inBetween(x1, x2, x3) & inBetween(y1, y2, y3) ){
        //Check if denominator is going to equal 0 when finding the slope and x of point has the same value
        if(x1 == x2 && x2 == x3){
            if(inBetween(y1, y2, y3)){
                return true;
            }else{
                return false;
            }
        }else if(y1 == y2 && y2 == y3){
            if(inBetween(x1, x2, x3)){
                return true;
            }else{
                return false;
            }
        }else{
            double slope = (y2-y1)/(x2-x1);
            //Check if the y value of the line is equal to the y value of the point
            if(findYIntercept(slope, x1, y1)+slope*x3 == y3){
                return true;
            }
        }

    }else{
        return false;
    }
    return false;
}
public static double findYIntercept(double slope, double x, double y){
    return y-(slope*x);
}
public static boolean inBetween(double a, double b, double c){
    if(a <= c && c <= b){
        return true;
    }
    else if(a >= c && c >= b){
        return true;
    }
    return false;
}

Method Three

This method is similar to the second method but checks if the slopes are identical. It also doesn't work if the line is horizontal or vertical. I also have implemented a situation if the line is horizontal or vertical.

public static boolean inLine(double x1, double y1, double x2, double y2, double x3, double y3){

//Check if X and Y Values of the point are within the range of the line
        if(inBetween(x1, x2, x3) & inBetween(y1, y2, y3) ){
            //Check if denominator is going to equal 0 when finding the slope and x of point has the same value
            if(x1 == x2 && x2 == x3){
                if(inBetween(y1, y2, y3)){
                    return true;
                }else{
                    return false;
                }
            }else if(y1 == y2 && y2 == y3){
                if(inBetween(x1, x2, x3)){
                    return true;
                }else{
                    return false;
                }
            }else{
                double slope1 = (y2-y1)/(x2-x1);
                double slope2 = (y3-y1)/(x3-x1);
                //Check if the y value of the line is equal to the y value of the point
                if(slope1 == slope2){
                    return true;
                }
            }

        }else{
            return false;
        }
        return false;
    }
    public static double findYIntercept(double slope, double x, double y){
        return y-(slope*x);
    }
    public static boolean inBetween(double a, double b, double c){
        if(a <= c && c <= b){
            return true;
        }
        else if(a >= c && c >= b){
            return true;
        }
        return false;
    }
Andre Yonadam
  • 964
  • 1
  • 13
  • 30
  • When you say 'what method does Java use to find out if a point is on a line' are you referring to java.awt.geom.Point2D and java.awt.geom.Line2D? – sprinter Jan 11 '15 at 08:37
  • In method 3 instead of checking if `(y2-y1)/(x2-x1) == (y3-y1)/(x3-x1)` you could check if `(y2-y1)*(x3-x1) == (y3-y1)*(x2-x1)`, which is equivalent but avoids the division by zero issue. – Alan Stokes Jan 11 '15 at 08:41
  • @sprinter I am referring to ptLineDist() from java.awt.geom.Line2D – Andre Yonadam Jan 11 '15 at 08:42
  • But that returns a `double` not a `boolean`. Any method returning a `boolean` is broken because of the issue I mention in my answer. – Paul Boddington Jan 11 '15 at 08:45
  • @AlanStokes not quite equivalent. You can only cross multiply if the denominator is not zero. In this case the 'bug' is that the first line might be horizontal and the second vertical - your equation will say that they have the same slope. – sprinter Jan 11 '15 at 08:47
  • @sprinter Good point; needs some sanity checks. Still probably more robust than division though. – Alan Stokes Jan 11 '15 at 08:49
  • I don't think I will run into the vertical lines problem because I check if the lines are vertical before calculating the slope. Does that make the slope method more accurate then the first one? – Andre Yonadam Jan 11 '15 at 08:52
  • @AndreYonadam Looking at the code in java.awt.geom, I see that it uses vectors (as my answer). There (in Line2D) it actually computes the distance, which is more than you were asking for. – laune Jan 11 '15 at 09:06
  • @AndreYonadam Regarding your comment "precision" near "Method One": Anything using doubles is subject to imprecision, since the conversion from string/decimal to binary double implies that. For absolute precision in a graphics coordinate system you may have to use pixels as integer units and use vectors to avoid all divisions and sqrt. – laune Jan 11 '15 at 09:12

3 Answers3

5

The best method is probably the first. Any method using slopes is problematic because vertical lines have infinite slope.

The problem with your code for method one is not Math.sqrt, it's that floating point calculations are not exact. As a result it is almost always wrong to compare double values a and b using ==. Instead you should use something like Math.abs(a - b) < 0x1p-32 to see if they are close enough.

Because of the limitations of floating point calculations, it is extremely difficult to do this kind of thing properly. java.awt doesn't do it very well. For example, the following program prints 0.5, which is incredibly inaccurate.

double a = 18981256.0;
System.out.println(new Line2D.Double(1.0, 2.0, 2.0, 4.0).ptLineDist(a, 2 * a));
Paul Boddington
  • 37,127
  • 10
  • 65
  • 116
  • The slope method is problematic because vertical lines have infinite slope. With the conditions to check if it is vertical and solve by checking Y values, does that make it more accurate than my first method that compares distance? – Andre Yonadam Jan 11 '15 at 08:54
  • Plus one for mentioning that one should **never** use a "slope" for such computations, and that floating point comparisons should basically **never** be done with `==`, but **always** with some machine-precision "epsilon" comparison. – Marco13 Jan 11 '15 at 10:58
  • @Marco13 I did some tests and found out that using the slope for such computations was more accurate. For example, I ran inLine(4.0, 3.0, 7.0, 6.0, 5.0, 4.0) and it retuned false in the first method which is incorrect but returned true when using the slope method. I ended up approaching the problem using the first method but instead of comparing the distances I subtracted the distance of A to C plus A to B, which should always be greater than or equal to 0, and compared that value to see if it was less than a small number which is a margin of error. – Andre Yonadam Jan 11 '15 at 21:45
  • @AndreYonadam The slope is problematic when there are vertical lines. You'll end up with `NaN` and `Infinity` there, so any further computation will not make any sense at all. – Marco13 Jan 11 '15 at 23:47
  • @Marco13 That's correct. But in the second in third methods I checked if x1 == x2 & x2 == x3 which will check if the line is vertical and if the point is on the line prior to checking using the slope method. If the line is vertical and if the point is on the line it will just check if the point falls within the y range of the line. – Andre Yonadam Jan 12 '15 at 00:49
  • @AndreYonadam This check using `==` is problematic as well, to say the least (one could even call it "plainly wrong" - see http://stackoverflow.com/questions/10334688 for example). Nearly *every* computation will involve rounding errors. So you end up with a line, for example, with end points (0.0,0.0) and (1e-10,1.0), and since `x0 != x1`, the slope of this like will be computed as something like 10000000000. However, pbabcdefp gave some important hints, and the comments are not the right place for further discussion. (Maybe I'll add an answer later, pointing out these caveats with examples) – Marco13 Jan 12 '15 at 11:54
1

If this question refers to the java.awt.geom package then the answer is that there is no built in method. There is a Line2D.contains method but that always returns false because it refers to an area containing a point and a line has no area.

Personally I find your third method easiest to use. However you don't need to actually find the slope - you can compare the cross-multiplication to see if they have the same gradient.

That is, dy1 / dx1 = dy2 / dx2 => dy1 * dx2 = dx1 * dy2 (only if dx1 & dx2 != 0)

that takes care of the horizontal case (dy = 0).

sprinter
  • 27,148
  • 6
  • 47
  • 78
  • I looked in the Java documentation and found that there is a method ptLineDist() in Line2D that returns 0.0 if the point intersects the line. – Andre Yonadam Jan 11 '15 at 08:56
0

I think using vectors is preferable.

class Vector {
    private double x;
    private double y;
    Vector( double x, double y ){
        this.x = x;
        this.y = y;
    }
    Vector( Point p1, Point p2 ){
        this( p1.getX() - p2.getX(), p1.getY() - p2.getY() );
    }
    public double getX(){ return x; }
    public double getY(){ return y; }
    public boolean isColl( Vector v ){
       return y*v.getX() == x*v.getY();
    }
}

class Point extends Vector {
    Point( double x, double y ){
        super( x, y );
    }
}

And here's a test:

public static void main(String[] args) throws Exception {
{
    Point g1 = new Point( 3, 0 );
    Point g2 = new Point( 6, 0 );
    for( Point p: new Point[]{ new Point ( 9, 0 ),
                   new Point ( 0, 9 ),
                   new Point ( 3, 3 ) } ){
    Vector g = new Vector( g1, g2 );
    Vector v1 = new Vector( g1, p );
    Vector v2 = new Vector( g2, p );
    System.out.println( v1.isColl( v2 ) );
    }
}
{
    Point g1 = new Point( 0, 3 );
    Point g2 = new Point( 0, 6 );
    for( Point p: new Point[]{ new Point ( 9, 0 ),
                   new Point ( 0, 9 ),
                   new Point ( 3, 3 ) } ){
    Vector g = new Vector( g1, g2 );
    Vector v1 = new Vector( g1, p );
    Vector v2 = new Vector( g2, p );
    System.out.println( v1.isColl( v2 ) );
    }
}
{
    Point g1 = new Point( 2, 3 );
    Point g2 = new Point( 10, 15 );
    for( Point p: new Point[]{ new Point ( 9, 0 ),
                   new Point ( 0, 9 ),
                   new Point ( 20, 30 ) } ){
    Vector g = new Vector( g1, g2 );
    Vector v1 = new Vector( g1, p );
    Vector v2 = new Vector( g2, p );
    System.out.println( v1.isColl( v2 ) );
    }
}
}
laune
  • 31,114
  • 3
  • 29
  • 42
  • 1
    Does `isColl()` only checks for whether vectors are parallel ? And not necessarily on same line ? – S.D. Jan 11 '15 at 08:46
  • 2
    @S.D. yes but v1 is g1->p and v2 is g2->p - if they are parallel then they must be colinear because they both end in p. – sprinter Jan 11 '15 at 08:49