在Java中计算距纬度/经度坐标一定距离的边界框
-
18-09-2019 - |
题
给定一个坐标(纬度,经度),我试图计算给定距离的方形边界框(例如距坐标50km)。因此,作为输入,我有纬度、经度和距离,作为输出,我想要两个坐标;一个是西南(左下)角,一个是东北(右上角)角。我在这里看到了一些尝试用 Python 解决这个问题的答案,但我正在特别寻找 Java 实现。
需要明确的是,我打算仅在地球上使用该算法,因此不需要适应可变半径。
它不必非常精确(+/-20% 即可),并且仅用于计算小距离(不超过 150 公里)的边界框。因此,我很乐意为高效算法牺牲一些准确性。任何帮助深表感谢。
编辑:我应该更清楚,我真正追求的是正方形,而不是圆形。据我了解,正方形中心与正方形周边各点之间的距离不像圆那样是恒定值。我想我的意思是一个正方形,如果你从中心到周边四个点中的任何一个画一条线,得到一条垂直于周边一侧的线,那么这 4 条线的长度相同。
解决方案
我写了一篇文章关于寻找边界坐标:
http://JanMatuschek.de/LatitudeLongitudeBoundingCoordinates
在本文解释的公式和还提供了一个Java实现。 (这也说明了为什么铁人三项公式用于最小/最大经度是不准确的。)
其他提示
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。
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
,然后用它来快速计算出信封里面点。
之前的所有答案仅部分正确. 。特别是在像澳大利亚这样的地区,他们总是包括杆子并计算出一个非常大的矩形,即使是 10 公里。
特别是 Jan Philip Matuschek 的算法 http://janmatuschek.de/LatitudeLongitudeBoundingCooperatives#UsingIndex 包括一个非常大的矩形(-37, -90, -180, 180),几乎涵盖了澳大利亚的每个点。这对数据库中的大量用户造成影响,并且必须计算几乎一半国家的所有用户的距离。
我发现 罗切斯特理工学院的 Drupal API 地球算法 在杆子周围以及其他地方效果更好,并且更容易实施。
https://www.rit.edu/drupal/api/drupal/sites%21all%21modules%21location%21earth.inc/7.54
使用 earth_latitude_range
和 earth_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;
}
并使用 谷歌地图记录的距离计算公式 计算距离
要按公里而不是英里搜索,请将 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;
这是一个简单的解决方案,我用它来生成我使用的边界框坐标 地名 citieJSON API 从 GPS 十进制坐标获取附近的大城市。
这是我的 GitHub 存储库中的 Java 方法: FusionTable修改Java
我有一个十进制 GPS 位置,我需要找到该位置“附近”最大的城市/州。我需要一个相对准确的边界框来传递到 carsJSON GeoNames Web 服务,以获取该边界框中最大的城市。我传递了我感兴趣的位置和“半径”(以公里为单位),它返回了传递给城市所需的北、南、东、西十进制坐标。
(我发现这些资源对我的研究很有用:
它不是非常准确,但对于我使用它的用途来说已经足够准确了:
// 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;
}
}