
dev.utils.common.CoordinateUtils Maven / Gradle / Ivy
package dev.utils.common;
import static java.lang.Math.PI;
/**
* detail: 坐标 ( GPS 纠偏 ) 相关工具类
* @author Ttt
*
* 地球坐标系 (WGS-84)
* 火星坐标系 (GCJ-02)
* 百度坐标系 (BD09)
*
* @see
* @see
* 根据两点经纬度计算距离
* @see
* 根据经纬度计算两点之间的距离的公式推导过程以及 google.maps 的测距函数
* @see
*
* 1. WGS84 坐标系: 即地球坐标系, 国际上通用的坐标系, 设备一般包含 GPS 芯片或者北斗芯片获取的经纬度为 WGS84 地理坐标系
* 谷歌地图采用的是 WGS84 地理坐标系 ( 中国范围除外 ) GPS 设备得到的经纬度就是在 WGS84 坐标系下的经纬度, 通常通过底层接口得到的定位信息都是 WGS84 坐标系
*
* 2. GCJ02 坐标系: 即火星坐标系, 是由中国国家测绘局制订的地理信息系统的坐标系统, 由 WGS84 坐标系经加密后的坐标系
* 国家规定, 中国大陆所有公开地理数据都需要至少用 GCJ-02 进行加密, 也就是说我们从国内公司的产品中得到的数据, 一定是经过了加密的
* 绝大部分国内互联网地图提供商都是使用 GCJ-02 坐标系, 包括高德地图, 谷歌地图中国区等
*
* 3. BD09 坐标系: 即百度坐标系, 其在 GCJ-02 上多增加了一次变换, 用来保护用户隐私, 从百度产品中得到的坐标都是 BD-09 坐标系
*
*/
public final class CoordinateUtils {
private CoordinateUtils() {
}
private static final double X_PI = 3.14159265358979324 * 3000.0 / 180.0;
private static final double A = 6378245.0;
private static final double EE = 0.00669342162296594323;
/**
* BD09 坐标转 GCJ02 坐标
* @param lng BD09 坐标纬度
* @param lat BD09 坐标经度
* @return GCJ02 坐标 [ 经度, 纬度 ]
*/
public static double[] bd09ToGcj02(
final double lng,
final double lat
) {
double x = lng - 0.0065;
double y = lat - 0.006;
double z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * X_PI);
double theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * X_PI);
double gg_lng = z * Math.cos(theta);
double gg_lat = z * Math.sin(theta);
return new double[]{gg_lng, gg_lat};
}
/**
* GCJ02 坐标转 BD09 坐标
* @param lng GCJ02 坐标经度
* @param lat GCJ02 坐标纬度
* @return BD09 坐标 [ 经度, 纬度 ]
*/
public static double[] gcj02ToBd09(
final double lng,
final double lat
) {
double z = Math.sqrt(lng * lng + lat * lat) + 0.00002 * Math.sin(lat * X_PI);
double theta = Math.atan2(lat, lng) + 0.000003 * Math.cos(lng * X_PI);
double bd_lng = z * Math.cos(theta) + 0.0065;
double bd_lat = z * Math.sin(theta) + 0.006;
return new double[]{bd_lng, bd_lat};
}
/**
* GCJ02 坐标转 WGS84 坐标
* @param lng GCJ02 坐标经度
* @param lat GCJ02 坐标纬度
* @return WGS84 坐标 [ 经度, 纬度 ]
*/
public static double[] gcj02ToWGS84(
final double lng,
final double lat
) {
if (outOfChina(lng, lat)) return new double[]{lng, lat};
double dlat = transformLat(lng - 105.0, lat - 35.0);
double dlng = transformLng(lng - 105.0, lat - 35.0);
double radlat = lat / 180.0 * PI;
double magic = Math.sin(radlat);
magic = 1 - EE * magic * magic;
double sqrtmagic = Math.sqrt(magic);
dlat = (dlat * 180.0) / ((A * (1 - EE)) / (magic * sqrtmagic) * PI);
dlng = (dlng * 180.0) / (A / sqrtmagic * Math.cos(radlat) * PI);
double mglat = lat + dlat;
double mglng = lng + dlng;
return new double[]{lng * 2 - mglng, lat * 2 - mglat};
}
/**
* WGS84 坐标转 GCJ02 坐标
* @param lng WGS84 坐标经度
* @param lat WGS84 坐标纬度
* @return GCJ02 坐标 [ 经度, 纬度 ]
*/
public static double[] wgs84ToGcj02(
final double lng,
final double lat
) {
if (outOfChina(lng, lat)) return new double[]{lng, lat};
double dlat = transformLat(lng - 105.0, lat - 35.0);
double dlng = transformLng(lng - 105.0, lat - 35.0);
double radlat = lat / 180.0 * PI;
double magic = Math.sin(radlat);
magic = 1 - EE * magic * magic;
double sqrtmagic = Math.sqrt(magic);
dlat = (dlat * 180.0) / ((A * (1 - EE)) / (magic * sqrtmagic) * PI);
dlng = (dlng * 180.0) / (A / sqrtmagic * Math.cos(radlat) * PI);
double mglat = lat + dlat;
double mglng = lng + dlng;
return new double[]{mglng, mglat};
}
/**
* BD09 坐标转 WGS84 坐标
* @param lng BD09 坐标经度
* @param lat BD09 坐标纬度
* @return WGS84 坐标 [ 经度, 纬度 ]
*/
public static double[] bd09ToWGS84(
final double lng,
final double lat
) {
double[] gcj = bd09ToGcj02(lng, lat);
return gcj02ToWGS84(gcj[0], gcj[1]);
}
/**
* WGS84 坐标转 BD09 坐标
* @param lng WGS84 坐标经度
* @param lat WGS84 坐标纬度
* @return BD09 坐标 [ 经度, 纬度 ]
*/
public static double[] wgs84ToBd09(
final double lng,
final double lat
) {
double[] gcj = wgs84ToGcj02(lng, lat);
return gcj02ToBd09(gcj[0], gcj[1]);
}
/**
* 转换经度
* @param lng 经度
* @param lat 纬度
* @return 转换后的经度
*/
private static double transformLat(
final double lng,
final double lat
) {
double ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat
* lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng));
ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(lat * PI) + 40.0 * Math.sin(lat / 3.0 * PI)) * 2.0 / 3.0;
ret += (160.0 * Math.sin(lat / 12.0 * PI) + 320 * Math.sin(lat * PI / 30.0)) * 2.0 / 3.0;
return ret;
}
/**
* 转换纬度
* @param lng 经度
* @param lat 纬度
* @return 转换后的纬度
*/
private static double transformLng(
final double lng,
final double lat
) {
double ret = 300.0 + lng + 2.0 * lat + 0.1 * lng
* lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng));
ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(lng * PI) + 40.0 * Math.sin(lng / 3.0 * PI)) * 2.0 / 3.0;
ret += (150.0 * Math.sin(lng / 12.0 * PI) + 300.0 * Math.sin(lng / 30.0 * PI)) * 2.0 / 3.0;
return ret;
}
/**
* 判断是否中国境外
* @param lng 经度
* @param lat 纬度
* @return {@code true} yes, {@code false} no
*/
public static boolean outOfChina(
final double lng,
final double lat
) {
return lng < 72.004 || lng > 137.8347 || lat < 0.8293 || lat > 55.8271;
}
// ==========
// = 计算坐标 =
// ==========
// 赤道半径
private static final double EARTH_RADIUS = 6378.137;
/**
* 计算弧度角
* @param degree 度数
* @return 弧度角
*/
private static double rad(final double degree) {
return degree * Math.PI / 180.0;
}
/**
* 计算两个坐标相距距离 ( 单位: 米 )
*
* 计算点与点直线间距离
*
* @param originLng 起点经度
* @param originLat 起点纬度
* @param targetLng 目标经度
* @param targetLat 目标纬度
* @return 两个坐标相距距离 ( 单位: 米 )
*/
public static double getDistance(
final double originLng,
final double originLat,
final double targetLng,
final double targetLat
) {
double radLat1 = rad(originLat);
double radLat2 = rad(targetLat);
double a = radLat1 - radLat2;
double b = rad(originLng) - rad(targetLng);
double s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2)
+ Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)));
s = s * EARTH_RADIUS;
// 保留两位小数
s = Math.round(s * 100D) / 100D;
s = s * 1000;
return s;
}
/**
* 计算两个坐标的方向角度
*
* 以 origin 为参考点坐标, 获取目标坐标位于参考点坐标方向
*
* @param originLng 起点经度
* @param originLat 起点纬度
* @param targetLng 目标经度
* @param targetLat 目标纬度
* @return 两个坐标的方向角度
*/
public static double getAngle(
final double originLng,
final double originLat,
final double targetLng,
final double targetLat
) {
double radLat1 = rad(originLat);
double radLng1 = rad(originLng);
double radLat2 = rad(targetLat);
double radLng2 = rad(targetLng);
double ret;
if (radLng1 == radLng2) {
if (radLat1 > radLat2) {
return 270; // 北半球的情况, 南半球忽略
} else if (radLat1 < radLat2) {
return 90;
} else {
return Integer.MAX_VALUE; // 位置完全相同
}
}
ret = 4 * Math.pow(Math.sin((radLat1 - radLat2) / 2), 2)
- Math.pow(Math.sin((radLng1 - radLng2) / 2)
* (Math.cos(radLat1) - Math.cos(radLat2)), 2);
ret = Math.sqrt(ret);
ret = ret / Math.sin(Math.abs(radLng1 - radLng2) / 2)
* (Math.cos(radLat1) + Math.cos(radLat2));
ret = Math.atan(ret) / Math.PI * 180;
if (radLng1 > radLng2) { // 以 origin 为参考点坐标
if (radLat1 > radLat2) {
ret += 180;
} else {
ret = 180 - ret;
}
} else if (radLat1 > radLat2) {
ret = 360 - ret;
}
return ret;
}
/**
* 计算两个坐标的方向
* @param originLng 起点经度
* @param originLat 起点纬度
* @param targetLng 目标经度
* @param targetLat 目标纬度
* @return 两个坐标的方向
*/
public static Direction getDirection(
final double originLng,
final double originLat,
final double targetLng,
final double targetLat
) {
double angle = getAngle(originLng, originLat, targetLng, targetLat);
return getDirection(angle);
}
/**
* 通过角度获取方向
* @param angle 角度
* @return 方向
*/
public static Direction getDirection(final double angle) {
if (angle == Integer.MAX_VALUE) return Direction.SAME;
if ((angle <= 10) || (angle > 350)) {
return Direction.RIGHT;
}
if ((angle > 10) && (angle <= 80)) {
return Direction.RIGHT_TOP;
}
if ((angle > 80) && (angle <= 100)) {
return Direction.TOP;
}
if ((angle > 100) && (angle <= 170)) {
return Direction.LEFT_TOP;
}
if ((angle > 170) && (angle <= 190)) {
return Direction.LEFT;
}
if ((angle > 190) && (angle <= 260)) {
return Direction.LEFT_BOTTOM;
}
if ((angle > 260) && (angle <= 280)) {
return Direction.BOTTOM;
}
if ((angle > 280) && (angle <= 350)) {
return Direction.RIGHT_BOTTOM;
}
return Direction.SAME;
}
/**
* detail: 坐标方向
* @author Ttt
*/
public enum Direction {
SAME("相同"), // 坐标相同
TOP("北"), // 上 ( 北 )
BOTTOM("南"), // 下 ( 南 )
LEFT("西"), // 左 ( 西 )
RIGHT("东"), // 右 ( 东 )
LEFT_TOP("西北"), // 左上 ( 西北 )
LEFT_BOTTOM("西南"), // 左下 ( 西南 )
RIGHT_TOP("东北"), // 右上 ( 东北 )
RIGHT_BOTTOM("东南"); // 右下 ( 东南 )
private final String value;
Direction(String value) {
this.value = value;
}
/**
* 获取中文方向值
* @return 中文方向值
*/
public String getValue() {
return value;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy