如题,计算球体上,两个点的距离,以及某个点 到某条线上的最短距离(线也是由球上的两个点组成的),不同于平面,这里不能直接求距离计算,得换算成弧长。
1、确定坐标
首先,我们得获取到 地球上这些点的坐标,那么坐标就有很多种分类,我这里是将他们换算成十进制的经纬度,然后再做计算,所以首先得进行换算,网上有很多现成代码,大家可以自行百度
GIS坐标系转换(EPSG:4326与EPSG:3857相互转换)_是阿洋啊的博客-CSDN博客_4326坐标系转3857
2、 计算点与点的距离
public class Point {
private double lon;
private double lat;
public Point(double lon, double lat) {
this.lon = lon;
this.lat = lat;
}
public Point() {
}
public double getLon() {
return lon;
}
public void setLon(double lon) {
this.lon = lon;
}
public double getLat() {
return lat;
}
public void setLat(double lat) {
this.lat = lat;
}
}
/**
* 默认地球半径
*/
private static final double EARTH_RADIUS = 6378.137;//赤道半径(单位m)
/**
* 转化为弧度(rad)
*/
private static double rad(double d) {
return d * Math.PI / 180.0;
}
/**
* 计算 a,b 两个点的距离,单位千米
*/
public static double getPPDistance(Point a, Point b) {
double lon1 = a.getLon(); // lon1 第一点的精度
double lat1 = a.getLat(); // lat1 第一点的纬度
double lon2 = b.getLon(); // lon2 第二点的精度
double lat2 = b.getLat(); // lat2 第二点的纬度
double radLat1 = rad(lat1);
double radLat2 = rad(lat2);
double a1 = radLat1 - radLat2;
double b2 = rad(lon1) - rad(lon2);
double s = 2 * Math.asin(Math.sqrt(Math.pow(sin(a1 / 2), 2) + cos(radLat1) * cos(radLat2) * Math.pow(sin(b2 / 2), 2)));
s = s * EARTH_RADIUS;
s = Math.round(s * 10000) / 10000;
return s;
}
3、计算点到线的距离
球面上的线是由两个点 a,b组成的,要求点p 到 ab 上的最短距离
首先我们需要求出 三个点之间的各个弧长,然后模拟成平面三角形
根据 海伦公式 知道三边长,可以求出面积,然后 点p对应的边P (也就是ab) 到点 P的高也能求出来了,三角形中,点到边的最短距离,就是高
public static double getPLDistance(Point a, Point b, Point p) {
// 1、 计算出 三条边长 ab ap bp
double ab = getPPDistance(a, b);
double ap = getPPDistance(a, p);
double bp = getPPDistance(b, p);
DecimalFormat df = new DecimalFormat("#0.0000"); //保留4位小数
double S = (ab + ap + bp) / 2;
// 2、 求面积
double area = Math.sqrt(S * (S - ab) * (S - ap) * (S - bp)); //求面积
// 3、p点到边p的距离=S*2/p
return Double.parseDouble(df.format(area * 2 / ab));
}
4、但是!!有问题
如果按照上述的方法求,正常情况是没有问题的,但是如果碰到 abp 组成的是一个钝角三角形,且角 P 对应的高在 ab 的延长线上,这样一来 计算出来的高 就并不是 实际上 点P 到 ab 的距离了,例如下图所示
求 红点 到黄线的最短距离
如果按照 3 中的方法计算,那么计算出来的距离 就是 红点到 红点对应的ab的延长线的橙色点F的距离 pf,那么显然这个距离是不对的,实际距离应该是 bp
还有就是,我们是将两点之间的弧线放到平面当成三角形计算,那么误差在所难免,所以计算出来的 这三条边长 极有可能组不成三角形,这种情况我们就 取 ab 的中点,再计算一次,直到能计算出三角形为止
所以修正后的代码如下
/**
* 计算 p 到点 ab线的 最短距离
* 将三个点 组成一个三角形,计算出 三条边长(弧线) 用海伦公式可以算出三角形面积,C点到边c的距离=S*2/c
*
*/
public static double getPLDistance(Point a, Point b, Point p) {
// 1、 计算出 三条边长 ab ap bp
double ab = getPPDistance(a, b);
double ap = getPPDistance(a, p);
double bp = getPPDistance(b, p);
boolean useSideLength = false;
// 判断是否取边长
if (isObtuseTriangle(ab, ap, bp) && (ab < ap || ab < bp)) {
useSideLength = true;
}
double s = getMinDistance(a, b, p, useSideLength);
while (Double.isNaN(s)) {
b = new Point((a.getLon() + b.getLon()) / 2, (a.getLat() + b.getLat()) / 2);
// p点 不变,a点不变,b点 变为 ab的中点
s = getMinDistance(a, b, p, useSideLength);
}
return s;
}
/**
* @param a 航线点a
* @param b 航线点b
* @param p 船位p
*/
public static double getMinDistance(Point a, Point b, Point p, boolean useSideLength) {
// 1、 计算出 三条边长 ab ap bp
double ab = getPPDistance(a, b);
double ap = getPPDistance(a, p);
double bp = getPPDistance(b, p);
DecimalFormat df = new DecimalFormat("#0.0000"); //保留4位小数
double S = (ab + ap + bp) / 2;
// 2、 求面积
double area = Math.sqrt(S * (S - ab) * (S - ap) * (S - bp)); //求面积
// 如果点P对应的边长 无限趋紧于0 ,则取 长度ap bp中最小的
if (ab == 0) {
return Double.compare(ap, bp) > 0 ? bp : ap;
}
// 如果不是 一个三角形,返回 NaN
if (Double.isNaN(area)) {
return area;
}
// 如果是钝角三角形,且 最长边 不是 点 P 对应的边 ,则取 与船的两条边长中的最短一条
if (isObtuseTriangle(ab, ap, bp) && (ab < ap || ab < bp) && useSideLength) {
return Math.min(bp, ap);
}
// 3、p点到边p的距离=S*2/p
return Double.parseDouble(df.format(area * 2 / ab));
}
/**
* 判断这三条线组成的 三角形 是不是 钝角三角形
*/
private static boolean isObtuseTriangle(double a, double b, double c) {
// 如果一个三角形的最长边平方>其他两边的平方和,这个三角形是钝角三角形
double[] doubles = new double[]{a, b, c};
Arrays.sort(doubles);
return doubles[2] * doubles[2] > doubles[1] * doubles[1] + doubles[0] * doubles[0];
}