How to calculate distance from a point to a line segment, on a sphere?

后端 未结 7 1342
一向
一向 2020-11-27 04:47

I have a line segment (great circle part) on earth. The line segment is defined by the coordinates of its ends. Obviously, two points define two line segments, so assume I a

相关标签:
7条回答
  • 2020-11-27 04:51

    I'm basically looking for the same thing right now, except that I strictly speaking don't care about having a segment of a great circle, but rather just want the distance to any point on the full circle.

    Two links I am currently investigating:

    This page mentions "Cross-track distance", which basically seems to be what you are looking for.

    Also, in the following thread on the PostGIS mailing list, the attempt seems to (1) determine the closest point on the great circle with the same formula used for line-distance on a 2D-plane (with PostGIS' line_locate_point), and then (2) calculating the distance between that and the third point on a spheroid. I have no idea if mathematically step (1) is correct, but I would be surprised.

    http://postgis.refractions.net/pipermail/postgis-users/2009-July/023903.html

    Finally, I just saw that the following linked under "Related":

    Distance from Point To Line great circle function not working right.

    0 讨论(0)
  • 2020-11-27 04:53

    The shortest distance between two points on a sphere is the smaller side of the great circle passing through the two points. I'm sure you know this already. There is a similar question here http://www.physicsforums.com/archive/index.php/t-178252.html that may help you model it mathmatically.

    I'm not sure how likely you are to get a coded example of this, to be honest.

    0 讨论(0)
  • Here's my own solution, based on the idea in ask Dr. Math. I'd be happy to see your feedback.

    Disclaimer first. This solution is correct for spheres. Earth isn't a sphere, and the coordinates system (WGS 84) doesn't assume it's a sphere. So this is just an approximation, and I can't really estimate is error. Also, for very small distances, it's probably also possible to get good approximation by assuming everything is a just a coplanar. Again I don't know how "small" the distances have to be.

    Now to business. I will call the ends of the lines A,B and the third point C. Basically, the algorithm is to:

    1. convert the coordinates first to Cartesian coordinates (with the origin at the center of earth) - e.g. here.
    2. Calculate T, the point on the line AB that is nearest to C, using the following 3 vector products:

      G = A x B

      F = C x G

      T = G x F

    3. Normalize T and multiply by the radius of earth.

    4. Convert T back to longitude\latitude.
    5. Calculate the distance between T and C - e.g. here.

    These steps are enough if you are looking for the distance between C and the great circle defined by A and B. If like me you are interested in the distance between C and the shorter line segment, you need to take the extra step of verifying that T is indeed on this segment. If it isn't, then necessarily the nearest point is one of the ends A or B - the easiest way is to check which one.

    In general terms, the idea behind the three vector products is the following. The first one (G) gives us the the plane of the great circle of A and B (so the plane containing A,B and the origin). The second (F) gives us the great circle the goes through C and is perpendicular to the G. Then T is the intersection of the great circles defined by F and G, brought to the correct length by normalization and multiplication by R.

    Here's some partial Java code for doing it.

    Finding the nearest point on the great circle. The inputs and output are length-2 arrays. The intermediate arrays are of length 3.

    double[] nearestPointGreatCircle(double[] a, double[] b, double c[])
    {
        double[] a_ = toCartsian(a);
        double[] b_ = toCartsian(b);
        double[] c_ = toCartsian(c);
    
        double[] G = vectorProduct(a_, b_);
        double[] F = vectorProduct(c_, G);
        double[] t = vectorProduct(G, F);
        normalize(t);
        multiplyByScalar(t, R_EARTH);
        return fromCartsian(t);
    }
    

    Finding the nearest point on the segment:

    double[] nearestPointSegment (double[] a, double[] b, double[] c)
    {
       double[] t= nearestPointGreatCircle(a,b,c);
       if (onSegment(a,b,t))
         return t;
       return (distance(a,c) < distance(b,c)) ? a : c;
    } 
    

    This is a simple method of testing if point T, which we know is on the same great circle as A and B, is on the shorter segment of this great circle. However there are more efficient methods to do it:

       boolean onSegment (double[] a, double[] b, double[] t)
       {
         // should be   return distance(a,t)+distance(b,t)==distance(a,b), 
         // but due to rounding errors, we use: 
         return Math.abs(distance(a,b)-distance(a,t)-distance(b,t)) < PRECISION;
       }    
    
    0 讨论(0)
  • 2020-11-27 05:01

    Try Distance from a Point to a Great Circle, from Ask Dr. Math. You still need to transform the longitude/latitude to spherical coordinates and scale for the earth's radius, but this seems like a good direction.

    0 讨论(0)
  • 2020-11-27 05:12

    For distance up to a few thousands meters I would simplify the issue from sphere to plane. Then, the issue is pretty simply as a easy triangle calculation can be used:

    We have points A and B and look for a distance X to line AB. Then:

    Location a;
    Location b;
    Location x;
    
    double ax = a.distanceTo(x);
    double alfa = (Math.abs(a.bearingTo(b) - a.bearingTo(x))) / 180
                * Math.PI;
    double distance = Math.sin(alfa) * ax;
    
    0 讨论(0)
  • 2020-11-27 05:14

    This is complete code for accepted answer as ideone fiddle (found here):

    import java.util.*;
    import java.lang.*;
    import java.io.*;
    
    /* Name of the class has to be "Main" only if the class is public. */
    class Ideone
    {
    
    
    
        private static final double _eQuatorialEarthRadius = 6378.1370D;
        private static final double _d2r = (Math.PI / 180D);
        private static double PRECISION = 0.1;
    
    
    
    
    
        // Haversine Algorithm
        // source: http://stackoverflow.com/questions/365826/calculate-distance-between-2-gps-coordinates
    
        private static double HaversineInM(double lat1, double long1, double lat2, double long2) {
            return  (1000D * HaversineInKM(lat1, long1, lat2, long2));
        }
    
        private static double HaversineInKM(double lat1, double long1, double lat2, double long2) {
            double dlong = (long2 - long1) * _d2r;
            double dlat = (lat2 - lat1) * _d2r;
            double a = Math.pow(Math.sin(dlat / 2D), 2D) + Math.cos(lat1 * _d2r) * Math.cos(lat2 * _d2r)
                    * Math.pow(Math.sin(dlong / 2D), 2D);
            double c = 2D * Math.atan2(Math.sqrt(a), Math.sqrt(1D - a));
            double d = _eQuatorialEarthRadius * c;
            return d;
        }
    
        // Distance between a point and a line
    
        public static void pointLineDistanceTest() {
    
            //line
            //double [] a = {50.174315,19.054743};
            //double [] b = {50.176019,19.065042};
            double [] a = {52.00118, 17.53933};
            double [] b = {52.00278, 17.54008};
    
            //point
            //double [] c = {50.184373,19.054657};
            double [] c = {52.008308, 17.542927};
            double[] nearestNode = nearestPointGreatCircle(a, b, c);
            System.out.println("nearest node: " + Double.toString(nearestNode[0]) + "," + Double.toString(nearestNode[1]));
            double result =  HaversineInM(c[0], c[1], nearestNode[0], nearestNode[1]);
            System.out.println("result: " + Double.toString(result));
        }
    
        // source: http://stackoverflow.com/questions/1299567/how-to-calculate-distance-from-a-point-to-a-line-segment-on-a-sphere
        private static double[] nearestPointGreatCircle(double[] a, double[] b, double c[])
        {
            double[] a_ = toCartsian(a);
            double[] b_ = toCartsian(b);
            double[] c_ = toCartsian(c);
    
            double[] G = vectorProduct(a_, b_);
            double[] F = vectorProduct(c_, G);
            double[] t = vectorProduct(G, F);
    
            return fromCartsian(multiplyByScalar(normalize(t), _eQuatorialEarthRadius));
        }
    
        @SuppressWarnings("unused")
        private static double[] nearestPointSegment (double[] a, double[] b, double[] c)
        {
           double[] t= nearestPointGreatCircle(a,b,c);
           if (onSegment(a,b,t))
             return t;
           return (HaversineInKM(a[0], a[1], c[0], c[1]) < HaversineInKM(b[0], b[1], c[0], c[1])) ? a : b;
        }
    
         private static boolean onSegment (double[] a, double[] b, double[] t)
           {
             // should be   return distance(a,t)+distance(b,t)==distance(a,b), 
             // but due to rounding errors, we use: 
             return Math.abs(HaversineInKM(a[0], a[1], b[0], b[1])-HaversineInKM(a[0], a[1], t[0], t[1])-HaversineInKM(b[0], b[1], t[0], t[1])) < PRECISION;
           }
    
    
        // source: http://stackoverflow.com/questions/1185408/converting-from-longitude-latitude-to-cartesian-coordinates
        private static double[] toCartsian(double[] coord) {
            double[] result = new double[3];
            result[0] = _eQuatorialEarthRadius * Math.cos(Math.toRadians(coord[0])) * Math.cos(Math.toRadians(coord[1]));
            result[1] = _eQuatorialEarthRadius * Math.cos(Math.toRadians(coord[0])) * Math.sin(Math.toRadians(coord[1]));
            result[2] = _eQuatorialEarthRadius * Math.sin(Math.toRadians(coord[0]));
            return result;
        }
    
        private static double[] fromCartsian(double[] coord){
            double[] result = new double[2];
            result[0] = Math.toDegrees(Math.asin(coord[2] / _eQuatorialEarthRadius));
            result[1] = Math.toDegrees(Math.atan2(coord[1], coord[0]));
    
            return result;
        }
    
    
        // Basic functions
        private static double[] vectorProduct (double[] a, double[] b){
            double[] result = new double[3];
            result[0] = a[1] * b[2] - a[2] * b[1];
            result[1] = a[2] * b[0] - a[0] * b[2];
            result[2] = a[0] * b[1] - a[1] * b[0];
    
            return result;
        }
    
        private static double[] normalize(double[] t) {
            double length = Math.sqrt((t[0] * t[0]) + (t[1] * t[1]) + (t[2] * t[2]));
            double[] result = new double[3];
            result[0] = t[0]/length;
            result[1] = t[1]/length;
            result[2] = t[2]/length;
            return result;
        }
    
        private static double[] multiplyByScalar(double[] normalize, double k) {
            double[] result = new double[3];
            result[0] = normalize[0]*k;
            result[1] = normalize[1]*k;
            result[2] = normalize[2]*k;
            return result;
        }
    
         public static void main(String []args){
            System.out.println("Hello World");
            Ideone.pointLineDistanceTest();
    
         }
    
    
    
    }
    

    It works fine for commented data:

    //line
    double [] a = {50.174315,19.054743};
    double [] b = {50.176019,19.065042};
    //point
    double [] c = {50.184373,19.054657};
    

    Nearest node is: 50.17493121381319,19.05846668493702

    But I have problem with this data:

    double [] a = {52.00118, 17.53933};
    double [] b = {52.00278, 17.54008};
    //point
    double [] c = {52.008308, 17.542927};
    

    Nearest node is: 52.00834987257176,17.542691313436357 which is wrong.

    I think that line specified by two points is not a closed segment.

    0 讨论(0)
提交回复
热议问题