Efficient method for finding closest point to a line segment from a set of points including line segment’s vertices

algorithmsgeometry

Let's say I have a list of points (in my case, point objects in a Python implementation). I then have a line segment connecting two of these points.

I want to know if there's a way to efficiently find the point from the list that is closest to the line segment. I realize I could step through all of the points and check distances individually, then select the smallest distance, but I'm eventually hoping to scale the program up to deal with perhaps millions of points. So, you know, maybe something more efficient than that?

If it helps, in terms of CCW all the points being questioned should either be to the "left"/"below" or the "right"/"above" of the line segment; I don't think my implementation will involve checking points to both sides of the segment.

The points will be point objects with (x,y) coordinates and some other junk not directly relevant to this question. The line segments are currently implemented as objects containing references to two point objects (its vertices) and its length.

As I mentioned this is part of a Python implementation. I'm trying to design a way to find a concave hull to a set of points (given some predefined parameters of how to decide whether a point not on the convex hull is on the concave hull or not). I want to find the convex hull first, then for each line segment on the convex hull find the closest point to it, make a triangle with that point, then decide if that triangle bears "deleting such that the internal point is now on the hull.

I considered putting this in Mathematics, but I don't need a distance equation – I need help with an efficient algorithm for finding points closest to a line segment. Note also that I am not looking for the closest point on a line to an input point; I'm looking for the closest point from a set to an input line segment.

Thank you all!

Best Answer

You're going to have to loop through all the points and calculate the distance. There is a great question on StackOverflow about how to calculate the distance: Shortest distance between a point and a line segment

Some of the work can be precalculated, given that you have to do this more than once for a given line segment. You also don't need to figure out the smallest distance, only the smallest distance squared (since squaring is strictly increasing). I converted the top voted answer in that question to a Java version that does this precalculation (in the constructor), and is easier to read and follow. Unfortunately I don't know Python well enough to give you Python code, but you should be able to figure it out.

import java.util.Arrays;
import java.util.List;

public class LineSegment {
    public static class Point {
        public final double x;
        public final double y;

        public Point(double x, double y) {
            this.x = x;
            this.y = y;
        }

        public String toString() {
            return "(" + x + "," + y + ")";
        }
    }

    public static void main(String[] args) {
        LineSegment segment = new LineSegment(new Point(0, 3), new Point(2, 0));
        List<Point> pointList =
                Arrays.asList(new Point[] { new Point(-5, 3), new Point(1, 1),
                        new Point(2, 3), new Point(0, 5) });

        Point answer = segment.closestPoint(pointList);
        System.out.println("The closest point is: " + answer);
    }

    private static double sqr(double x) {
        return x * x;
    }

    private static double distanceSquared(Point v, Point w) {
        return sqr(v.x - w.x) + sqr(v.y - w.y);
    }

    private final Point firstSegPoint;
    private final Point secondSegPoint;
    private final double segmentDistance;
    private double xDifference;
    private double yDifference;

    public LineSegment(Point firstSegPoint, Point secondSegPoint) {
        this.firstSegPoint = firstSegPoint;
        this.secondSegPoint = secondSegPoint;
        this.segmentDistance = distanceSquared(firstSegPoint, secondSegPoint);
        this.xDifference = secondSegPoint.x - firstSegPoint.x;
        this.yDifference = secondSegPoint.y - firstSegPoint.y;
    }

    public Point closestPoint(List<Point> pointList) {
        double minDistance = Double.POSITIVE_INFINITY;
        Point answer = null;

        for (Point point : pointList) {
            double distSquared = distToSegmentSquared(point);
            if (distSquared < minDistance) {
                answer = point;
                minDistance = distSquared;
            }
        }

        return answer;
    }

    private double distToSegmentSquared(Point input) {
        if (segmentDistance == 0)
            return distanceSquared(input, firstSegPoint);

        double xComponent = (input.x - firstSegPoint.x) * xDifference;
        double yComponent = (input.y - firstSegPoint.y) * yDifference;
        double t = (xComponent + yComponent) / segmentDistance;
        if (closestPointIsFirst(t))
            return distanceSquared(input, firstSegPoint);
        if (closestPointIsSecond(t))
            return distanceSquared(input, secondSegPoint);
        Point closestPointOnLine =
                new Point(firstSegPoint.x + t * xDifference, firstSegPoint.y
                        + t * yDifference);
        return distanceSquared(input, closestPointOnLine);
    }

    private boolean closestPointIsFirst(double t) {
        return t < 0;
    }

    private boolean closestPointIsSecond(double t) {
        return t > 1;
    }
}

See the full implementation here: http://ideone.com/fBFwda

Related Topic