【问题标题】:Calculating bounding box a certain distance away from a lat/long coordinate in Java在Java中计算距离纬度/经度坐标一定距离的边界框
【发布时间】:2010-12-13 22:19:57
【问题描述】:

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

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

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

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

【问题讨论】:

  • 如果您有 Python 实现,我不明白您为什么不能将其转换为 Java。算法应该是一样的。
  • 我发现的最好的 Python 示例已被作者标记为未经测试。我想,鉴于我追求的准确性相当宽容,因此不同的算法也可能是合适的。
  • 这个问题只是直三角;我建议删除 java 和 algorithm 标签。 (如果可能的话。)
  • 这只是你需要的公式;我相信有人会为你提供它,并希望给你参考链接。
  • 需要考虑地球的球形性质。如果输入位置是北极,你想要什么答案?

标签: math geospatial trigonometry latitude-longitude bounding-box


【解决方案1】:

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

http://JanMatuschek.de/LatitudeLongitudeBoundingCoordinates

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

【讨论】:

  • 我对为什么将这些球面近似值作为标准解决方案感到困惑。这里涉及大量的三角函数。另外,所有 GPS 设备都会根据 WGS84 椭球产生纬度/经度坐标——如果你想要完全准确,你根本不能使用球面近似值……在球面近似值中调整这些方程来解释椭圆体将导致难以想象的怪物.看来,方法必须是在 3D 空间中执行计算和弧长积分。
  • @Mecki 抱歉,我并没有完全清楚,我花了一些时间才想起我投诉的最初原因。基本上,“问题”是计算机的计算三角操作不是很快(与大于/小于等更简单的操作相比)。这个问题要求进行基本的边界框检查。 OP 甚至用多个句子指定他不想要极端准确,他想要速度,事实上。因此,在每个测试点上使用垃圾负载来获得准确的结果,以确定它是否在到另一个点的给定距离内是多余的
  • OP 想要做的(就像我在上一个使用 lat/longs 的项目中所做的那样)是应用一个超快的基本近似值:你需要两个常数,在赤道以纬度为单位的公里,以经度为单位的千米。然后只需使用它来确定您的积分应该在多少度范围内通过检查。是的,这种近似在两极附近会降低。不过,对于大多数应用程序来说,这应该无关紧要。地理兴趣点通常离两极很远。 (实际上我确实把它简化了,它有点难)
  • 这是有少量误报之间的区别(甚至比您预期的要少,如果您显示的屏幕区域几乎总是矩形,实际上OP 也是 明确指出这一点),同时每点进行 AABB 检查,而每点进行 3 次或类似 9 次触发函数调用的误报为零。棘手的一点是,你越靠近两极,你必须测试的经度范围就越大,才能安全地覆盖你的盒子
  • 我会这么说。只要有人真正阅读了链接的文章,他们就应该能够理解如何以超级准确的方式实现这一点,并考虑如何调整它并放松条件以使代码运行更少的触发函数。有时您可以保证始终有足够的时间为每个查询点运行大量的三角函数。
【解决方案2】:
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。

【讨论】:

  • JTS 代表什么?
  • Java Topology Suite,是几何相关操作的API
【解决方案3】:
import com.vividsolutions.jts.geom.Envelope;

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

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

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

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

【讨论】:

    【解决方案4】:

    之前的所有答案都只是部分正确。特别是在像澳大利亚这样的地区,他们总是包括极点并计算一个非常大的矩形,即使是 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 列的 Markers 表,公式为:

    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;
    

    【讨论】:

    • 观察 earth_radius 代码。确保地球的半径以 km 为单位,而不是米:return (1 / (Math.sqrt(x * x + y * y)))/1000;
    【解决方案5】:

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

    【讨论】:

      【解决方案6】:

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

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

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

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

      Calculate distance, bearing and more between Latitude/Longitude points.

      Longitude - Wikipedia)

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

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

      【讨论】:

        猜你喜欢
        • 2014-12-16
        • 1970-01-01
        • 2016-02-03
        • 1970-01-01
        • 2014-08-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-09-16
        相关资源
        最近更新 更多