42

给定一个坐标(纬度,经度),我试图计算一个距离坐标给定距离(例如 50 公里)的方形边界框。因此,作为输入,我有纬度、长距离和距离,作为输出,我想要两个坐标;一个是西南角(左下角),一个是东北角(右上角)。我在这里看到了几个试图在 Python 中解决这个问题的答案,但我特别在寻找 Java 实现。

为了清楚起见,我打算仅在地球上使用该算法,因此我不需要适应可变半径。

它不必非常准确(+/-20% 很好),它只用于计算小距离(不超过 150 公里)的边界框。所以我很乐意为高效的算法牺牲一些准确性。任何帮助深表感谢。

编辑:我应该更清楚,我真的在追求一个正方形,而不是一个圆圈。我知道正方形的中心与正方形周边的各个点之间的距离不是一个恒定值,就像圆形一样。我想我的意思是一个正方形,如果你从中心画一条线到周长上的四个点中的任何一个,导致一条垂直于周长一侧的线,那么这 4 条线的长度相同。

4

6 回答 6

58

我写了一篇关于找到边界坐标的文章:

http://JanMatuschek.de/LatitudeLongitudeBoundingCoordinates

文章解释了这些公式,还提供了一个 Java 实现。(这也说明了为什么 IronMan 的最小/最大经度公式不准确。)

于 2010-05-26T13:15:31.423 回答
16
double R = 6371;  // earth radius in km

double radius = 50; // km

double x1 = lon - Math.toDegrees(radius/R/Math.cos(Math.toRadians(lat)));

double x2 = lon + Math.toDegrees(radius/R/Math.cos(Math.toRadians(lat)));

double y1 = lat + Math.toDegrees(radius/R);

double y2 = lat - Math.toDegrees(radius/R);

虽然我也会推荐 JTS。

于 2009-11-10T13:42:15.460 回答
4
import com.vividsolutions.jts.geom.Envelope;

...
Envelope env = new Envelope(centerPoint.getCoordinate());
env.expandBy(distance_in_degrees); 
...

现在 env 包含你的信封。它实际上不是一个“正方形”(无论在球体表面上意味着什么),但它应该可以。

您应该注意,以度为单位的距离将取决于中心点的纬度。在赤道,纬度1度约为111公里,但在纽约,只有75公里左右。

真正很酷的事情是,您可以将所有点扔到 a 中com.vividsolutions.jts.index.strtree.STRtree,然后使用它来快速计算该信封内的点。

于 2009-11-06T18:21:12.037 回答
3

之前的所有答案都只是部分正确。特别是在像澳大利亚这样的地区,他们总是包括极点并计算一个非常大的矩形,即使是 10 公里。

特别是 Jan Philip Matuschek 在http://janmatuschek.de/LatitudeLongitudeBoundingCoordinates#UsingIndex的算法包括一个非常大的矩形(-37, -90, -180, 180),用于澳大利亚的几乎每个点。这会影响数据库中的大量用户,并且必须为几乎一半国家的所有用户计算距离。

我发现罗彻斯特理工学院的 Drupal API 地球算法在极点周围和其他地方工作得更好,并且更容易实现。

https://www.rit.edu/drupal/api/drupal/sites%21all%21modules%21location%21earth.inc/7.54

使用earth_latitude_rangeearth_longitude_range从上述算法计算边界矩形

这是实现是Java

    /**
 * Get bouding rectangle using Drupal Earth Algorithm
 * @see https://www.rit.edu/drupal/api/drupal/sites%21all%21modules%21location%21earth.inc/7.54
 * @param lat
 * @param lng
 * @param distance
 * @return
 */
default BoundingRectangle getBoundingRectangleDrupalEarthAlgo(double lat, double lng, int distance) {
    lng = Math.toRadians(lng);
    lat = Math.toRadians(lat);
    double radius = earth_radius(lat);
    List<Double> retLats = earth_latitude_range(lat, radius, distance);
    List<Double> retLngs = earth_longitude_range(lat, lng, radius, distance);
    return new BoundingRectangle(retLats.get(0), retLats.get(1), retLngs.get(0), retLngs.get(1));
}


/**
 * Calculate latitude range based on earths radius at a given point
 * @param latitude
 * @param longitude
 * @param distance
 * @return
 */
default List<Double> earth_latitude_range(double lat, double radius, double distance) {
      // Estimate the min and max latitudes within distance of a given location.

      double angle = distance / radius;
      double minlat = lat - angle;
      double maxlat = lat + angle;
      double rightangle = Math.PI / 2;
      // Wrapped around the south pole.
      if (minlat < -rightangle) {
        double overshoot = -minlat - rightangle;
        minlat = -rightangle + overshoot;
        if (minlat > maxlat) {
          maxlat = minlat;
        }
        minlat = -rightangle;
      }
      // Wrapped around the north pole.
      if (maxlat > rightangle) {
        double overshoot = maxlat - rightangle;
        maxlat = rightangle - overshoot;
        if (maxlat < minlat) {
          minlat = maxlat;
        }
        maxlat = rightangle;
      }
      List<Double> ret = new ArrayList<>();
      ret.add((minlat));
      ret.add((maxlat));
      return ret;
    }

/**
 * Calculate longitude range based on earths radius at a given point
 * @param lat
 * @param lng
 * @param earth_radius
 * @param distance
 * @return
 */
default List<Double> earth_longitude_range(double lat, double lng, double earth_radius, int distance) {
      // Estimate the min and max longitudes within distance of a given location.
      double radius = earth_radius * Math.cos(lat);

      double angle;
      if (radius > 0) {
        angle = Math.abs(distance / radius);
        angle = Math.min(angle, Math.PI);
      }
      else {
        angle = Math.PI;
      }
      double minlong = lng - angle;
      double maxlong = lng + angle;
      if (minlong < -Math.PI) {
        minlong = minlong + Math.PI * 2;
      }
      if (maxlong > Math.PI) {
        maxlong = maxlong - Math.PI * 2;
      }

      List<Double> ret = new ArrayList<>();
      ret.add((minlong));
      ret.add((maxlong));
      return ret;
    }

/**
 * Calculate earth radius at given latitude
 * @param latitude
 * @return
 */
default Double earth_radius(double latitude) {
      // Estimate the Earth's radius at a given latitude.
      // Default to an approximate average radius for the United States.
      double lat = Math.toRadians(latitude);

      double x = Math.cos(lat) / 6378137.0;
      double y = Math.sin(lat) / (6378137.0 * (1 - (1 / 298.257223563)));

      //Make sure earth's radius is in km , not meters
      return (1 / (Math.sqrt(x * x + y * y)))/1000;
    }

并使用谷歌地图记录的距离计算公式计算距离

https://developers.google.com/maps/solutions/store-locator/clothing-store-locator#outputting-data-as-xml-using-php

要按公里而不是英里搜索,请将 3959 替换为 6371。 对于 (Lat, Lng) = (37, -122) 和包含 lat 和 lng 列的标记表,公式为:

SELECT id, ( 3959 * acos( cos( radians(37) ) * cos( radians( lat ) ) * cos( radians( lng ) - radians(-122) ) + sin( radians(37) ) * sin( radians( lat ) ) ) ) AS distance FROM markers HAVING distance < 25 ORDER BY distance LIMIT 0 , 20;
于 2017-08-30T01:11:05.020 回答
1

根据 IronMan 的回复:

/**
 * Calculate the lat and len of a square around a point.
 * @return latMin, latMax, lngMin, lngMax
 */
public static double[] calculateSquareRadius(double lat, double lng, double radius) {
    double R = 6371;  // earth radius in km
    double latMin = lat - Math.toDegrees(radius/R);
    double latMax = lat + Math.toDegrees(radius/R);
    double lngMin = lng - Math.toDegrees(radius/R/Math.cos(Math.toRadians(lat)));
    double lngMax = lng + Math.toDegrees(radius/R/Math.cos(Math.toRadians(lat)));

    return new double[] {latMin, latMax, lngMin, lngMax};
}
于 2020-05-11T12:50:45.297 回答
0

这是我用来生成边界框坐标的简单解决方案,我使用GeoNames ctieJSON API从 gps 十进制坐标获取附近的大城市。

这是来自我的 GitHub 存储库的 Java 方法:FusionTableModifyJava

我有一个十进制的 GPS 位置,我需要找到该位置“附近”最大的城市/州。我需要一个相对准确的边界框来传递给 cityJSON GeoNames 网络服务,以获取该边界框中最大的城市。我传递了我感兴趣的位置和“半径”(以公里为单位),它返回传递给 cityJSON 所需的北、南、东、西十进制坐标。

(我发现这些资源对我的研究很有用:

计算纬度/经度点之间的距离、方位等。

经度 - 维基百科

它不是超级准确,但对于我使用它的用途来说足够准确:

    // Compute bounding Box coordinates for use with Geonames API.
    class BoundingBox
    {
        public double north, south, east, west;
        public BoundingBox(String location, float km)
        {
             //System.out.println(location + " : "+ km);
            String[] parts = location.replaceAll("\\s","").split(","); //remove spaces and split on ,

            double lat = Double.parseDouble(parts[0]);
            double lng = Double.parseDouble(parts[1]);

            double adjust = .008983112; // 1km in degrees at equator.
            //adjust = 0.008983152770714983; // 1km in degrees at equator.

            //System.out.println("deg: "+(1.0/40075.017)*360.0);


            north = lat + ( km * adjust);
            south = lat - ( km * adjust);

            double lngRatio = 1/Math.cos(Math.toRadians(lat)); //ratio for lng size
            //System.out.println("lngRatio: "+lngRatio);

            east = lng + (km * adjust) * lngRatio;
            west = lng - (km * adjust) * lngRatio;
        }

    }
于 2018-06-05T18:12:49.997 回答