在Java中如何通过两个经纬度点来测量距离并创建一个边界框?

76

1
请参考此博客:http://xebee.xebia.in/2010/10/28/working-with-geolocations/,其中介绍了有关地理位置的工作。 - Robin
12个回答

156

这里是一个Java实现的Haversine公式。我在一个项目中使用它来计算经纬度之间的英里距离。

public static double distFrom(double lat1, double lng1, double lat2, double lng2) {
    double earthRadius = 3958.75; // miles (or 6371.0 kilometers)
    double dLat = Math.toRadians(lat2-lat1);
    double dLng = Math.toRadians(lng2-lng1);
    double sindLat = Math.sin(dLat / 2);
    double sindLng = Math.sin(dLng / 2);
    double a = Math.pow(sindLat, 2) + Math.pow(sindLng, 2)
            * Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2));
    double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
    double dist = earthRadius * c;

    return dist;
    }

23
关于这一点,需要注意的是,它会返回英里作为距离单位(因为设置了地球半径)。如果需要其他单位,请更改地球半径(详见http://en.wikipedia.org/wiki/Earth_radius)。 - John Meagher
你使用浮点数而不是双精度浮点数有什么原因吗?如果我理解正确,通过改变输入参数类型,你可以提高结果的准确性。 - Hamy
1
这个推导可以在http://mathforum.org/library/drmath/view/51879.html找到。 - Anand Sunderraman
地球不是一个完美的球体。 - Steve Kuo
7
使用地球半径6371来得到以千米为单位的结果。 - stevo.mit
显示剩余3条评论

45

或者您可以使用SimpleLatLng。它是Apache 2.0许可的,并且我知道有一款生产系统在使用它。

简而言之:

我正在寻找一个简单的地理库,但找不到一个符合我的需求。谁想在每个应用程序中一遍又一遍地编写、测试和调试这些小型地理工具呢?肯定有更好的方法!

因此,SimpleLatLng诞生了,作为一种存储纬度-经度数据、进行距离计算和创建形状边界的方式。

我知道我比原帖发布时间晚两年,但我的目标是帮助像我一样通过搜索找到这个问题的人。我希望有一些人使用它,并为这个小型轻量级实用程序的测试和愿景做出贡献。


这可能会对我有所帮助!你创建了它吗?你使用Haversine公式进行距离计算吗?如果我有时间,我会尝试加入! - Marsellus Wallace
正确,它使用Haversine进行距离计算,强调(虽然并非痴迷于)速度和低内存占用。我认为它还具有其他一些不错的数字处理属性,例如将“非常接近”的坐标视为相等。 - JavadocMD

21

我们使用OpenMap成功地绘制了大量位置数据。其中有一个LatLonPoint类,具有一些基本功能,包括距离。


5
潜在的采用者请注意:我刚刚遇到了一个与OpenMap相关的大问题;他们在内部使用浮点数表示小数位的纬度/经度,这会限制你所在位置的精度。他们计划从4.7版本开始使用OMGraphic类支持双精度选项,但当前稳定版本仅为4.6.5(截至2010年3月)。 - Marc
请注意,当前的OpenMap软件许可协议http://openmap.bbn.com/license.html已被认为是非自由的。http://web.archiveorange.com/archive/v/XyE55YoXwS3lME936I0U已经存档。 已经与BBN进行了一些讨论以更改许可证,但目前还没有任何进展。 - Leif Gruenwoldt
3
这个答案已经过时了,因为这两个链接都已经失效。 - xivo
我刚刚修复了指向http://openmap-java.org的新域名的链接。许可证现在在这里:http://openmap-java.org/License.html(不知道自从@LeifGruenwoldt的评论以来是否有更改)。 - Rüdiger Schulz

11

如果需要更精确的距离(0.5毫米),您可以使用文森蒂近似公式:

/**
 * Calculates geodetic distance between two points specified by latitude/longitude using Vincenty inverse formula
 * for ellipsoids
 * 
 * @param lat1
 *            first point latitude in decimal degrees
 * @param lon1
 *            first point longitude in decimal degrees
 * @param lat2
 *            second point latitude in decimal degrees
 * @param lon2
 *            second point longitude in decimal degrees
 * @returns distance in meters between points with 5.10<sup>-4</sup> precision
 * @see <a href="http://www.movable-type.co.uk/scripts/latlong-vincenty.html">Originally posted here</a>
 */
public static double distVincenty(double lat1, double lon1, double lat2, double lon2) {
    double a = 6378137, b = 6356752.314245, f = 1 / 298.257223563; // WGS-84 ellipsoid params
    double L = Math.toRadians(lon2 - lon1);
    double U1 = Math.atan((1 - f) * Math.tan(Math.toRadians(lat1)));
    double U2 = Math.atan((1 - f) * Math.tan(Math.toRadians(lat2)));
    double sinU1 = Math.sin(U1), cosU1 = Math.cos(U1);
    double sinU2 = Math.sin(U2), cosU2 = Math.cos(U2);

    double sinLambda, cosLambda, sinSigma, cosSigma, sigma, sinAlpha, cosSqAlpha, cos2SigmaM;
    double lambda = L, lambdaP, iterLimit = 100;
    do {
        sinLambda = Math.sin(lambda);
        cosLambda = Math.cos(lambda);
        sinSigma = Math.sqrt((cosU2 * sinLambda) * (cosU2 * sinLambda)
                + (cosU1 * sinU2 - sinU1 * cosU2 * cosLambda) * (cosU1 * sinU2 - sinU1 * cosU2 * cosLambda));
        if (sinSigma == 0)
            return 0; // co-incident points
        cosSigma = sinU1 * sinU2 + cosU1 * cosU2 * cosLambda;
        sigma = Math.atan2(sinSigma, cosSigma);
        sinAlpha = cosU1 * cosU2 * sinLambda / sinSigma;
        cosSqAlpha = 1 - sinAlpha * sinAlpha;
        cos2SigmaM = cosSigma - 2 * sinU1 * sinU2 / cosSqAlpha;
        if (Double.isNaN(cos2SigmaM))
            cos2SigmaM = 0; // equatorial line: cosSqAlpha=0 (§6)
        double C = f / 16 * cosSqAlpha * (4 + f * (4 - 3 * cosSqAlpha));
        lambdaP = lambda;
        lambda = L + (1 - C) * f * sinAlpha
                * (sigma + C * sinSigma * (cos2SigmaM + C * cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM)));
    } while (Math.abs(lambda - lambdaP) > 1e-12 && --iterLimit > 0);

    if (iterLimit == 0)
        return Double.NaN; // formula failed to converge

    double uSq = cosSqAlpha * (a * a - b * b) / (b * b);
    double A = 1 + uSq / 16384 * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq)));
    double B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq)));
    double deltaSigma = B
            * sinSigma
            * (cos2SigmaM + B
                    / 4
                    * (cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM) - B / 6 * cos2SigmaM
                            * (-3 + 4 * sinSigma * sinSigma) * (-3 + 4 * cos2SigmaM * cos2SigmaM)));
    double dist = b * A * (sigma - deltaSigma);

    return dist;
}

这段代码是从 http://www.movable-type.co.uk/scripts/latlong-vincenty.html 自由改编而来。


6

修正的Haversine距离公式....

public static double HaverSineDistance(double lat1, double lng1, double lat2, double lng2) 
{
    // mHager 08-12-2012
    // http://en.wikipedia.org/wiki/Haversine_formula
    // Implementation

    // convert to radians
    lat1 = Math.toRadians(lat1);
    lng1 = Math.toRadians(lng1);
    lat2 = Math.toRadians(lat2);
    lng2 = Math.toRadians(lng2);

    double dlon = lng2 - lng1;
    double dlat = lat2 - lat1;

    double a = Math.pow((Math.sin(dlat/2)),2) + Math.cos(lat1) * Math.cos(lat2) * Math.pow(Math.sin(dlon/2),2);

    double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));

    return EARTH_RADIUS * c;
}   

地球不是一个完美的球体。 - Steve Kuo
正确,有时候“接近”就足够了。例如:为什么Inman先生创建它。我会告诉他他错了,但他已经去世了。 :o( 如果你需要计算世界的长方形形状,有更好的公式可以使用。还有一些很棒的Apache库也可以使用。如果你只需要简单的东西,这是一个很好的快速示例。 :) - Matthew Hager
1
是的,Haversine公式建立在“越接近越好”的原则上。当时我们测量的距离小于50英里,以确定一个位置与另一个位置的接近程度作为一种“启发式方法”。 - Matthew Hager

2

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

public static Double distanceBetweenTwoLocationsInKm(Double latitudeOne, Double longitudeOne, Double latitudeTwo, Double longitudeTwo) {
        if (latitudeOne == null || latitudeTwo == null || longitudeOne == null || longitudeTwo == null) {
            return null;
        }

        Double earthRadius = 6371.0;
        Double diffBetweenLatitudeRadians = Math.toRadians(latitudeTwo - latitudeOne);
        Double diffBetweenLongitudeRadians = Math.toRadians(longitudeTwo - longitudeOne);
        Double latitudeOneInRadians = Math.toRadians(latitudeOne);
        Double latitudeTwoInRadians = Math.toRadians(latitudeTwo);
        Double a = Math.sin(diffBetweenLatitudeRadians / 2) * Math.sin(diffBetweenLatitudeRadians / 2) + Math.cos(latitudeOneInRadians) * Math.cos(latitudeTwoInRadians) * Math.sin(diffBetweenLongitudeRadians / 2)
                * Math.sin(diffBetweenLongitudeRadians / 2);
        Double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
        return (earthRadius * c);
    }

1

这种方法可以帮助您以公里为单位找到两个地理位置之间的距离。

private double getDist(double lat1, double lon1, double lat2, double lon2)
{
    int R = 6373; // radius of the earth in kilometres
    double lat1rad = Math.toRadians(lat1);
    double lat2rad = Math.toRadians(lat2);
    double deltaLat = Math.toRadians(lat2-lat1);
    double deltaLon = Math.toRadians(lon2-lon1);

    double a = Math.sin(deltaLat/2) * Math.sin(deltaLat/2) +
            Math.cos(lat1rad) * Math.cos(lat2rad) *
            Math.sin(deltaLon/2) * Math.sin(deltaLon/2);
    double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));

    double d = R * c;
    return d;
}

1
我知道有很多答案,但在研究这个主题时,我发现这里大多数答案使用Haversine公式,但Vincenty公式实际上更精确。有一个帖子从Javascript版本中改编了计算方法,但它非常笨重。我找到了一个更好的版本,因为:
  1. 它还具有开放许可证。
  2. 它使用面向对象编程原则。
  3. 它具有更大的灵活性,可以选择您想要使用的椭球体。
  4. 它具有更多的方法,以允许将来进行不同的计算。
  5. 它有很好的文档记录。

VincentyDistanceCalculator


已经消失了。可能是这个:https://github.com/manuelhartl/opensaft/blob/fb44617d6166f293ad32d5244c7abb4c9c0e4e2e/opensaft-input/src/main/java/de/hartlit/opensaft/common/geodetic/VincentyDistanceCalculator.java - debuglevel
好的,发现了!我进行了一些调查,Apache Lucene 工具可能更可靠。我会更新答案。 - mikeho

1
你可以使用Java大地测量库进行GPS定位,它使用文森提公式,考虑了地球表面的曲率。
实现方法如下:
import org.gavaghan.geodesy.*;
...
GeodeticCalculator geoCalc = new GeodeticCalculator();
Ellipsoid reference = Ellipsoid.WGS84;
GlobalPosition pointA = new GlobalPosition(latitude, longitude, 0.0);
GlobalPosition userPos = new GlobalPosition(userLat, userLon, 0.0);
double distance = geoCalc.calculateGeodeticCurve(reference, userPos, pointA).getEllipsoidalDistance();

得到的距离单位为米。


1

这是Haversine公式的Kotlin版本。返回结果以米为单位。已在https://www.vcalc.com/wiki/vCalc/Haversine+-+Distance上进行了测试。

const val EARTH_RADIUS_IN_METERS = 6371007.177356707

fun distance(lat1: Double, lng1: Double, lat2: Double, lng2: Double): Double {
    val latDiff = Math.toRadians(abs(lat2 - lat1))
    val lngDiff = Math.toRadians(abs(lng2 - lng1))
    val a = sin(latDiff / 2) * sin(latDiff / 2) +
        cos(Math.toRadians(lat1)) * cos(Math.toRadians(lat2)) *
        sin(lngDiff / 2) * sin(lngDiff / 2)
    val c = 2 * atan2(sqrt(a), sqrt(1 - a))
    return EARTH_RADIUS_IN_METERS * c
}

嗨,所以我可以把这个用作地理围栏,对吧????由于某些未知的原因,安卓上的地理围栏代码对我不起作用,我正在考虑使用这个作为替代方案。 - Pemba Tamang

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