All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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