计算两点之间的距离,使用纬度和经度?

113

这是我尝试的代码片段:

final double RADIUS = 6371.01;
double temp = Math.cos(Math.toRadians(latA))
            * Math.cos(Math.toRadians(latB))
            * Math.cos(Math.toRadians((latB) - (latA)))
            + Math.sin(Math.toRadians(latA))
            * Math.sin(Math.toRadians(latB));
    return temp * RADIUS * Math.PI / 180;

我正在使用这个公式来获取纬度和经度:

x = Deg + (Min + Sec / 60) / 60)
8个回答

262

Dommer提供的Java代码给出的结果略有不准确,但如果处理例如GPS轨迹等大量数据时,这些小误差会不断累积。以下是一个实现了Haversine方法并考虑了两点之间高度差的Java代码。

/**
 * Calculate distance between two points in latitude and longitude taking
 * into account height difference. If you are not interested in height
 * difference pass 0.0. Uses Haversine method as its base.
 * 
 * lat1, lon1 Start point lat2, lon2 End point el1 Start altitude in meters
 * el2 End altitude in meters
 * @returns Distance in Meters
 */
public static double distance(double lat1, double lat2, double lon1,
        double lon2, double el1, double el2) {

    final int R = 6371; // Radius of the earth

    double latDistance = Math.toRadians(lat2 - lat1);
    double lonDistance = Math.toRadians(lon2 - lon1);
    double a = Math.sin(latDistance / 2) * Math.sin(latDistance / 2)
            + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2))
            * Math.sin(lonDistance / 2) * Math.sin(lonDistance / 2);
    double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
    double distance = R * c * 1000; // convert to meters

    double height = el1 - el2;

    distance = Math.pow(distance, 2) + Math.pow(height, 2);

    return Math.sqrt(distance);
}

8
为什么不使用Math.toRadians()而要用deg2rad()? 这样会更加自包含。 - Aron Lorincz
2
@Bala - 不好意思,这是我电脑上代码注释里的内容,但在这里漏掉了。单位是米。 - David George
4
我已经更新了方法,采用了您非常好的建议。 - David George
2
这里有一些评论:http://stackoverflow.com/questions/28510115/java-math-toradiansangle-vs-hard-calculated?lq=1 - David George
2
我犯了和Mladen一样的错误,传递了(lat1, lon1, lat2, lon2)而不是(lat1, lat2, lon1, lon2)。确保你不要交换这些值。 - Andras Kloczl
显示剩余9条评论

89

这里有一个Java函数可以计算两个经纬度点之间的距离,如下所示,以防它再次消失。

    private double distance(double lat1, double lon1, double lat2, double lon2, char unit) {
      double theta = lon1 - lon2;
      double dist = Math.sin(deg2rad(lat1)) * Math.sin(deg2rad(lat2)) + Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) * Math.cos(deg2rad(theta));
      dist = Math.acos(dist);
      dist = rad2deg(dist);
      dist = dist * 60 * 1.1515;
      if (unit == 'K') {
        dist = dist * 1.609344;
      } else if (unit == 'N') {
        dist = dist * 0.8684;
        }
      return (dist);
    }
    
    /*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::*/
    /*::  This function converts decimal degrees to radians             :*/
    /*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::*/
    private double deg2rad(double deg) {
      return (deg * Math.PI / 180.0);
    }
    
    /*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::*/
    /*::  This function converts radians to decimal degrees             :*/
    /*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::*/
    private double rad2deg(double rad) {
      return (rad * 180.0 / Math.PI);
    }
    
    System.out.println(distance(32.9697, -96.80322, 29.46786, -98.53506, 'M') + " Miles\n");
    System.out.println(distance(32.9697, -96.80322, 29.46786, -98.53506, 'K') + " Kilometers\n");
    System.out.println(distance(32.9697, -96.80322, 29.46786, -98.53506, 'N') + " Nautical Miles\n");

2
谷歌地图显示12.915700,77.632046,11.665154,78.145657之间距离为200公里,而上述代码显示距离为149.82公里。仍有错误。 - Samy
4
以上函数给出的是直线距离。 - Rahul_Pawar
1
我的王国需要开发人员用单位标记他们的变量。例如:double dist => double distInMiles(或类似)。我不是在批评发布这个答案的人,我会点赞……但是我批评原始代码的实现者。 - granadaCoder
这种方法对于小距离是有效的,我认为它可能比 https://dev59.com/I3A65IYBdhLWcg3wrQl4#20410612 更好,因为它使用正弦/余弦/反余弦,这是合乎逻辑的,因为地球有点像一个球体。点赞。 - Mladen Adamovic
1
此函数将返回 NaN 而不是 0.0,对于输入值 30.4570925、74.1676477、30.4570925、74.1676477,因为 Math.acos() 函数不接受大于 1 的值。 - Saurabh Verma

29

假设未来读者偶然发现这篇SOF文章。

显然,这个问题是在2010年提出的,现在已经是2019年了。 但它在互联网搜索中很早就出现了。原始问题并没有排除使用第三方库(当我写这篇答案时)。

public double calculateDistanceInMeters(double lat1, double long1, double lat2,
                                     double long2) {


    double dist = org.apache.lucene.util.SloppyMath.haversinMeters(lat1, long1, lat2, long2);
    return dist;
}

<dependency>
  <groupId>org.apache.lucene</groupId>
  <artifactId>lucene-spatial</artifactId>
  <version>8.2.0</version>
</dependency>

https://mvnrepository.com/artifact/org.apache.lucene/lucene-spatial/8.2.0

在深入研究之前,请阅读有关“SloppyMath”的文档!

https://lucene.apache.org/core/8_2_0/core/org/apache/lucene/util/SloppyMath.html


你确定这是正确的吗?我刚刚检查了一下,这个JAR文件没有“.util”。 - Adnan Ali
这就是为什么我总是发布带有答案的版本。确保你正在获取8.2.0版本。13个赞也表明答案是准确的。 - granadaCoder
3
我认为如果您可以用28行代码替换它,就不应该将一个依赖项添加到某个地方,除非您需要使用该依赖项的更多功能。这是一种不好的做法。 - Mladen Adamovic
我需要将这些 JavaScript 转换成 Android Studio 中的 Java - 有什么建议或解决方案的步骤吗?感谢您的帮助。 - bennyhardjono

17

注意:此解决方案只适用于短距离。

我尝试使用dommer发布的一个公式进行应用,并发现它在长距离上表现良好,但在我的数据中,我使用的全部都是非常短的距离,并且dommer的帖子表现非常差。我需要速度,而更复杂的地理计算效果很好,但速度太慢了。所以,在您需要速度并且所有计算都很短(可能小于100米左右)的情况下,我发现这个小估计值非常有效。请注意,它假设世界是平的,因此不要用于长距离,它通过近似给定纬度下的单个经纬度的距离,并返回米制下的勾股距离来工作。

public class FlatEarthDist {
    //returns distance in meters
    public static double distance(double lat1, double lng1, 
                                      double lat2, double lng2){
     double a = (lat1-lat2)*FlatEarthDist.distPerLat(lat1);
     double b = (lng1-lng2)*FlatEarthDist.distPerLng(lat1);
     return Math.sqrt(a*a+b*b);
    }

    private static double distPerLng(double lat){
      return 0.0003121092*Math.pow(lat, 4)
             +0.0101182384*Math.pow(lat, 3)
                 -17.2385140059*lat*lat
             +5.5485277537*lat+111301.967182595;
    }

    private static double distPerLat(double lat){
            return -0.000000487305676*Math.pow(lat, 4)
                -0.0033668574*Math.pow(lat, 3)
                +0.4601181791*lat*lat
                -1.4558127346*lat+110579.25662316;
    }
}

有人能否添加一些细节,说明这种方法在短距离上是否比此处接受的答案(https://dev59.com/I3A65IYBdhLWcg3wrQl4#16794680)更有效? - Mladen Adamovic
不,它对于短距离并不正常。我走了大约30米,但它显示了88米。 - Priyavrat Talyan

9

虽然已经提供了许多很好的答案,但我发现一些性能方面的不足之处,所以我提供了一个着眼于性能的版本。每个常量都是预先计算的,并引入了x、y变量,以避免重复计算相同的值。希望能对你有所帮助。

    private static final double r2d = 180.0D / 3.141592653589793D;
    private static final double d2r = 3.141592653589793D / 180.0D;
    private static final double d2km = 111189.57696D * r2d;
    public static double meters(double lt1, double ln1, double lt2, double ln2) {
        double x = lt1 * d2r;
        double y = lt2 * d2r;
        return Math.acos( Math.sin(x) * Math.sin(y) + Math.cos(x) * Math.cos(y) * Math.cos(d2r * (ln1 - ln2))) * d2km;
    }

6

这是一个包含各种球面计算的JavaScript示例页面。页面上的第一个示例应该可以满足您的需求。

http://www.movable-type.co.uk/scripts/latlong.html

以下是JavaScript代码:

var R = 6371; // km
var dLat = (lat2-lat1).toRad();
var dLon = (lon2-lon1).toRad(); 
var a = Math.sin(dLat/2) * Math.sin(dLat/2) + 
        Math.cos(lat1.toRad()) * Math.cos(lat2.toRad()) *
        Math.sin(dLon/2) * Math.sin(dLon/2); 
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); 
var d = R * c;

其中 'd' 将保存距离。


“a” 可以是负数吗? - Xi Wei

2
package distanceAlgorithm;

public class CalDistance {
    public static void main(String[] args) {
        // TODO Auto-generated method stub
    CalDistance obj=new CalDistance();
    /*obj.distance(38.898556, -77.037852, 38.897147, -77.043934);*/
        System.out.println(obj.distance(38.898556, -77.037852, 38.897147, -77.043934, "M") + " Miles\n");
        System.out.println(obj.distance(38.898556, -77.037852, 38.897147, -77.043934, "K") + " Kilometers\n");
        System.out.println(obj.distance(32.9697, -96.80322, 29.46786, -98.53506, "N") + " Nautical Miles\n");       
    }   
    public double distance(double lat1, double lon1, double lat2, double lon2, String sr) {


          double theta = lon1 - lon2;
          double dist = Math.sin(deg2rad(lat1)) * Math.sin(deg2rad(lat2)) + Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) * Math.cos(deg2rad(theta));
          dist = Math.acos(dist);
          dist = rad2deg(dist);
          dist = dist * 60 * 1.1515;
          if (sr.equals("K")) {
            dist = dist * 1.609344;
          } else if (sr.equals("N")) {
            dist = dist * 0.8684;
            }
          return (dist);
        }
    public double deg2rad(double deg) {
          return (deg * Math.PI / 180.0);
        }
    public double rad2deg(double rad) {
          return (rad * 180.0 / Math.PI);
        }


    }

2
稍微升级过的来自@David George的答案:
public static double distance(double lat1, double lat2, double lon1,
                              double lon2, double el1, double el2) {

    final int R = 6371; // Radius of the earth

    double latDistance = Math.toRadians(lat2 - lat1);
    double lonDistance = Math.toRadians(lon2 - lon1);
    double a = Math.sin(latDistance / 2) * Math.sin(latDistance / 2)
            + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2))
            * Math.sin(lonDistance / 2) * Math.sin(lonDistance / 2);
    double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
    double distance = R * c * 1000; // convert to meters

    double height = el1 - el2;

    distance = Math.pow(distance, 2) + Math.pow(height, 2);

    return Math.sqrt(distance);
}

public static double distanceBetweenLocations(Location l1, Location l2) {
    if(l1.hasAltitude() && l2.hasAltitude()) {
        return distance(l1.getLatitude(), l2.getLatitude(), l1.getLongitude(), l2.getLongitude(), l1.getAltitude(), l2.getAltitude());
    }
    return l1.distanceTo(l2);
}

distance函数相同,但我创建了一个小的包装函数,它接受2个Location对象。由于这样,只有在两个位置实际上都有海拔时才使用distance函数,因为有时它们没有。这可能会导致奇怪的结果(如果位置不知道其海拔高度,则返回0)。在这种情况下,我将退回到经典的distanceTo函数。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接