首页 文章

如何计算球体上从点到线段的距离?

提问于
浏览
25

我在地球上有一个线段(大圆圈部分) . 线段由其末端的坐标定义 . 显然,两个点定义了两个线段,所以假设我对较短的线段感兴趣 .

我得到了第三点,我正在寻找线和点之间的(最短)距离 .

所有坐标均以经度\纬度(WGS 84)给出 .

我该如何计算距离?

任何合理的编程语言都可以使用解决方案 .

7 回答

  • 0

    这是我自己的解决方案,基于ask Dr. Math中的想法 . 我很乐意看到您的反馈 .

    首先免责声明 . 这种解决方案适用于球体 . 地球不是一个球体,坐标系统(WGS 84)并不认为它是一个球体 . 所以这只是一个近似值,我无法估计是错误 . 此外,对于非常小的距离,通过假设所有东西都只是一个共面,它也可能得到很好的近似 . 我再一次不知道距离必须“小” .

    现在去做生意 . 我将调用线A,B和第三点C的末端 . 基本上,算法是:

    • 首先将坐标转换为笛卡尔坐标(原点位于地球中心) - e.g. here .

    • 使用以下3个矢量积计算T,AB线上最接近C的点:

    G = A x B

    F = C x G.

    T = G×F

    • 标准化T并乘以地球半径 .

    • 将T转换回经度\纬度 .

    • 计算T和C之间的距离 - e.g. here .

    如果你正在寻找C和A和B定义的大圆之间的距离,这些步骤就足够了 . 如果像我一样你对C和较短线段之间的距离感兴趣,你需要采取额外的步骤来验证T确实在这一部分 . 如果不是,那么最近的点必然是A或B两端之一 - 最简单的方法是检查哪一个 .

    一般而言,三种载体产品背后的想法如下 . 第一个(G)给出了A和B大圆的平面(所以包含A,B和原点的平面) . 第二个(F)给出了通过C并且垂直于G的大圆 . 然后T是由F和G定义的大圆的交点,通过归一化和乘以R得到正确的长度 .

    这是一些部分Java代码 .

    找到大圆上最近的点 . 输入和输出是长度为2的数组 . 中间阵列的长度为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);
    }
    

    找到段上最近的点:

    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;
    }
    

    这是一种简单的测试方法,如果我们知道的点T与A和B在同一个大圆上,则位于这个大圆的较短段上 . 但是有更有效的方法可以做到:

    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;
       }
    
  • 17

    从Ask Dr. Math尝试Distance from a Point to a Great Circle . 您仍然需要将经度/纬度转换为球面坐标并缩放地球半径,但这似乎是一个很好的方向 .

  • 3

    这是接受的答案的完整代码作为ideone小提琴(找到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();
    
         }
    
    
    
    }
    

    它适用于注释数据:

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

    最近的节点是:50.17493121381319,19.05846668493702

    但我对这些数据有疑问:

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

    最近的节点是:52.00834987257176,17.542691313436357哪个错了 .

    我认为由两点指定的行不是封闭的段 .

  • 1

    如果有人需要它,这是一个移植到c#的loleksy答案

    private static double _eQuatorialEarthRadius = 6378.1370D;
            private static 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
            static double pointLineDistanceGEO(double[] a, double[] b, double[] c)
            {
    
                double[] nearestNode = nearestPointGreatCircle(a, b, c);
                double result = HaversineInKM(c[0], c[1], nearestNode[0], nearestNode[1]);
    
                return 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));
            }
    
            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 bool 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(deg2rad(coord[0])) * Math.Cos(deg2rad(coord[1]));
                result[1] = _eQuatorialEarthRadius * Math.Cos(deg2rad(coord[0])) * Math.Sin(deg2rad(coord[1]));
                result[2] = _eQuatorialEarthRadius * Math.Sin(deg2rad(coord[0]));
                return result;
            }
    
            private static double[] fromCartsian(double[] coord){
                double[] result = new double[2];
                result[0] = rad2deg(Math.Asin(coord[2] / _eQuatorialEarthRadius));
                result[1] = rad2deg(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;
            }
    
  • 0

    对于几千米的距离,我会简化从球到平面的问题 . 然后,问题非常简单,因为可以使用简单的三角形计算:

    我们有A点和B点,并寻找到AB线的距离X.然后:

    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;
    
  • 1

    球体上两点之间的最短距离是通过这两点的大圆的较小边 . 我相信你已经知道了 . 这里有一个类似的问题http://www.physicsforums.com/archive/index.php/t-178252.html可以帮助您以数学方式对其进行建模 .

    老实说,我不确定你有多大可能得到一个这样的编码例子 .

  • 1

    我现在基本上都在寻找相同的东西,除了我严格地说不关心有一个大圆的一段,而只是想要到整圆的任何一点的距离 .

    我正在调查的两个链接:

    This page提及"Cross-track distance",这基本上就是你要找的东西 .

    另外,在以下主题中在PostGIS邮件列表中,尝试似乎(1)使用与2D平面上的线距相同的公式(使用PostGIS'line_locate_point)确定大圆上的最近点,然后(2)计算距离在球体之间和第三点之间 . 我不知道数学上步骤(1)是否正确,但我会感到惊讶 .

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

    最后,我刚看到以下链接在“相关”下:

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

相关问题