百度地图、腾讯地图、高德地图与WGS84坐标系之间的相互转换
大家好,我是迷途小书童。
项目需求
最近我接到一个项目,内容是要记录货物由仓库出发到无人机起机点以及无人机飞行配送所有物流坐标定位。并实时推送到后台服务器记录下来。然后在小程序、app及后台管理页面的地图上显示点位轨迹。
开发上遇到了一些困难
1. 仓库出发到无人机起机点是使用百度地图api实时采集的,坐标系是BD09。无人机推送的定位是WGS84坐标。
2. 由于是2个不同的坐标系,因此无法在同在一个地图上显示准确的坐标,需要转换成同一个坐标系。
3. 微信及支付宝小程序默认是使用GCJ02坐标。前端页面及app是使用百度地图的api,默认是使用BD09坐标
解决办法
-
使用百度地图的坐标转换接口转换坐标,生成统一的坐标系。(需要调用远程接口,效率低下。不考虑这个方案)
https://lbsyun.baidu.com/faq/api?title=webapi/guide/changeposition-base
-
自己写代码转换生成统一的坐标系。
-
服务器在接收定位坐标推送时直接转换成同一个坐标系。(会影响性能,对高并发并不友好。不考虑这个方案)
-
服务器只接收推送,不做转换,在前端或app做转换。
坐标系是什么?
1,WGS84坐标
WGS84坐标系:即地球坐标系(World Geodetic System),国际上通用的坐标系。设备包含的GPS芯片或者北斗芯片获取的经纬度一般都是为WGS84地理坐标系,目前谷歌地图采用的是WGS84坐标系(中国范围除外)。
WGS84 Web 墨卡托投影坐标系:也称web墨卡托,是如今主流的Web地图使用的坐标系,如国外的 Google Maps,OpenStreetMap,Bing Map,ArcGIS 和 Heremaps 等,国内的百度地图、高德地图、腾讯地图和天地图等也是基于Web墨卡托(由于国内政策的原因,国内地图会有加密要求,一般有两种情况,一种是在 Web墨卡托的基础上经过国家标准加密的国标02坐标系,熟称“火星坐标系”;另一种是在国标的02坐标系下进一步进行加密,如百度地图的BD09坐标系)。
2,GCJ02坐标
GCJ02坐标系:GCJ-02是由中国国家测绘局(G表示Guojia国家,C表示Cehui测绘,J表示Ju局)制订的地理信息系统的坐标系统。由WGS84坐标系经加密后的坐标系。谷歌中国采用的GCJ02地理坐标系。也称:火星坐标系。
GCJ02经纬度投影坐标系:该坐标系的坐标值为经纬度格式,单位为度。这里的GCJ02经纬度投影,也就是在WGS84经纬度的基础之上,进行GCJ-02加偏。
GCJ02 Web 墨卡托投影坐标系:该坐标系的坐标值为Web墨卡托格式,单位为米。这里的GCJ02 Web 墨卡托,也就是在标准Web默卡托的基础之上,进行GCJ-02加偏。
3,BD09坐标
BD09坐标系:即百度坐标系,GCJ02坐标系经加密后的坐标系。
BD09 经纬度投影坐标系:该坐标系的坐标值为经纬度格式,单位为度。是在标准经纬度的基础上进行GCJ-02加偏之后,再加上百度自身的加偏算法,也就是在标准经纬度的基础之上进行了两次加偏。
BD09 Web 墨卡托影坐标系:该坐标系的坐标值为Web墨卡托格式,单位为米。是在标准Web墨卡托的基础上进行GCJ-02加偏之后,再加上百度自身的加偏算法,也就是在Web墨卡托的基础之上进行了两次加偏。
直接上代码
public class CoordinateTransform {
private static final double X_PI = 3.14159265358979324 * 3000.0 / 180.0;
private static final double PI = 3.1415926535897932384626;
private static final double A = 6378245.0;
private static final double EE = 0.00669342162296594323;
/**
* WGS84坐标系转换成腾讯地图坐标系(GCJ02)
*
* @param longitude 经度
* @param latitude 纬度
* @return double[]
*/
public static double[] wgs84ToGcj02(double longitude, double latitude) {
if (outOfChina(longitude, latitude)) {
return new double[]{longitude, latitude};
}
double dLat = transformLat(longitude - 105.0, latitude - 35.0);
double dLon = transformLng(longitude - 105.0, latitude - 35.0);
double radLat = latitude / 180.0 * Math.PI;
double magic = Math.sin(radLat);
magic = 1 - EE * magic * magic;
double sqrtMagic = Math.sqrt(magic);
dLat = (dLat * 180.0) / ((A / sqrtMagic) * Math.PI);
dLon = (dLon * 180.0) / ((A / (magic * sqrtMagic)) * Math.PI);
double mgLat = latitude + dLat;
double mgLon = longitude + dLon;
return new double[]{mgLon, mgLat};
}
/**
* WGS84坐标系转百度坐标系(BD09)
*
* @param wgs84Lng WGS84 经度
* @param wgs84Lat WGS84 纬度
* @return 百度坐标数组
*/
public static double[] wgs84ToBd09(double wgs84Lng, double wgs84Lat) {
double[] gcj02 = wgs84ToGcj02(wgs84Lng, wgs84Lat);
double[] bd09 = gcj02ToBd09(gcj02[0], gcj02[1]);
return bd09;
}
/**
* 百度坐标系(BD09)转腾讯地图坐标系(GCJ02)
*
* @param bd_lon 百度经度
* @param bd_lat 百度纬度
* @return 火星坐标数组
*/
public static double[] bd09ToGcj02(double bd_lon, double bd_lat) {
double x = bd_lon - 0.0065;
double y = bd_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};
}
/**
* 百度坐标(BD09)系转换成WGS84坐标系
*
* @param bdLng 经度
* @param bdLat 纬度
* @return double[]
*/
public static double[] bd09ToWgs84(double bdLng, double bdLat) {
double[] gcj02 = bd09ToGcj02(bdLng, bdLat);
double[] wgs84 = gcj02ToWgs84(gcj02[0], gcj02[1]);
return wgs84;
}
/**
* 腾讯地图坐标系(GCJ02)转换成WGS84坐标系
*
* @param longitude 经度
* @param latitude 纬度
* @return double[]
*/
public static double[] gcj02ToWgs84(double longitude, double latitude) {
if (outOfChina(longitude, latitude)) {
return new double[]{longitude, latitude};
}
double dLat = transformLat(longitude - 105.0, latitude - 35.0);
double dLon = transformLng(longitude - 105.0, latitude - 35.0);
double radLat = latitude / 180.0 * Math.PI;
double magic = Math.sin(radLat);
magic = 1 - EE * magic * magic;
double sqrtMagic = Math.sqrt(magic);
dLat = (dLat * 180.0) / ((A / sqrtMagic) * Math.PI);
dLon = (dLon * 180.0) / ((A / (magic * sqrtMagic)) * Math.PI);
double mgLat = latitude + dLat;
double mgLon = longitude + dLon;
double wgsLat = latitude + (latitude - mgLat);
double wgsLon = longitude + (longitude - mgLon);
return new double[]{wgsLon, wgsLat};
}
/**
* 腾讯地图坐标系(GCJ02)转百度坐标系(BD09)
*
* @param gcj_lng 火星坐标系经度
* @param gcj_lat 火星坐标系纬度
* @return 百度坐标数组
*/
public static double[] gcj02ToBd09(double gcj_lng, double gcj_lat) {
double x = gcj_lng, y = gcj_lat;
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 bd_lng = z * Math.cos(theta) + 0.0065;
double bd_lat = z * Math.sin(theta) + 0.006;
return new double[]{bd_lng, bd_lat};
}
private static double transformLat(double x, double y) {
double ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x));
ret += (20.0 * Math.sin(6.0 * x * PI) + 20.0 * Math.sin(2.0 * x * PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(y * PI) + 40.0 * Math.sin(y / 3.0 * PI)) * 2.0 / 3.0;
ret += (160.0 * Math.sin(y / 12.0 * PI) + 320 * Math.sin(y * PI / 30.0)) * 2.0 / 3.0;
return ret;
}
private static double transformLng(double x, double y) {
double ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));
ret += (20.0 * Math.sin(6.0 * x * PI) + 20.0 * Math.sin(2.0 * x * PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(x * PI) + 40.0 * Math.sin(x / 3.0 * PI)) * 2.0 / 3.0;
ret += (150.0 * Math.sin(x / 12.0 * PI) + 300.0 * Math.sin(x / 30.0 * PI)) * 2.0 / 3.0;
return ret;
}
private static boolean outOfChina(double lng, double lat) {
return (lng < 72.004 || lng > 137.8347) || (lat < 0.8293 || lat > 55.8271);
}
}
//小程序代码
bd09togcj02: (lng, lat) => {
let x_pi = (3.14159265358979324 * 3000.0) / 180.0;
let x = lng - 0.0065;
let y = lat - 0.006;
let z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * x_pi);
let theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * x_pi);
let gg_lng = z * Math.cos(theta);
let gg_lat = z * Math.sin(theta);
return [gg_lng, gg_lat];
},
gcj02tobd09: (lng, lat) => {
let x_pi = (3.14159265358979324 * 3000.0) / 180.0;
let z = Math.sqrt(lng * lng + lat * lat) + 0.00002 * Math.sin(lat * x_pi);
let theta = Math.atan2(lat, lng) + 0.000003 * Math.cos(lng * x_pi);
let bd_lng = z * Math.cos(theta) + 0.0065;
let bd_lat = z * Math.sin(theta) + 0.006;
return [bd_lng, bd_lat];
},
wgs84togcj02: function (lng, lat) {
if (this.outOfChina(lng, lat)) {
return [lng, lat];
}
let dlat = this.transformlat(lng - 105.0, lat - 35.0);
let dlng = this.transformlng(lng - 105.0, lat - 35.0);
let radlat = (lat / 180.0) * Math.PI;
let magic = Math.sin(radlat);
magic = 1 - 0.00669342162296594323 * magic * magic;
let sqrtmagic = Math.sqrt(magic);
dlat = (dlat * 180.0) / ((6378245.0 * (1 - 0.00669342162296594323)) / (magic * sqrtmagic) * Math.PI);
dlng = (dlng * 180.0) / (6378245.0 / sqrtmagic * Math.cos(radlat) * Math.PI);
let mglat = lat + dlat;
let mglng = lng + dlng;
return [mglng, mglat];
},
wgs84ToBd09: (wgsLng, wgsLat) => {
// 先将 WGS84 坐标转换成 GCJ02 坐标
const [gcjLng, gcjLat] = wgs84ToGcj02(wgsLng, wgsLat);
// 再将 GCJ02 坐标转换成 BD09 坐标
let x = gcjLng,
y = gcjLat;
let z = Math.sqrt(x * x + y * y) + 0.00002 * Math.sin(y * Math.PI * 3000 / 180);
let theta = Math.atan2(y, x) + 0.000003 * Math.cos(x * Math.PI * 3000 / 180);
let bdLng = z * Math.cos(theta) + 0.0065;
let bdLat = z * Math.sin(theta) + 0.006;
return [bdLng, bdLat];
},
gcj02towgs84: (gcj_lng, gcj_lat) => {
const PI = 3.14159265358979324;
const a = 6378245.0;
const ee = 0.00669342162296594323;
let dlat = this.transformlat(gcj_lng - 105.0, gcj_lat - 35.0);
let dlng = this.transformlng(gcj_lng - 105.0, gcj_lat - 35.0);
const radlat = gcj_lat / 180.0 * PI;
let magic = Math.sin(radlat);
magic = 1 - ee * magic * magic;
const sqrtmagic = Math.sqrt(magic);
dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI);
dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * PI);
const wgs_lat = gcj_lat - dlat;
const wgs_lng = gcj_lng - dlng;
return [wgs_lng, wgs_lat];
},
bd09towgs84: (bd_lng, bd_lat) => {
const x = bd_lng - 0.0065;
const y = bd_lat - 0.006;
const z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * Math.PI);
const theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * Math.PI);
const wgs_lng = z * Math.cos(theta);
const wgs_lat = z * Math.sin(theta);
return [wgs_lng, wgs_lat];
},
transformlat: (lng, lat) => {
let 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 * Math.PI) + 20.0 * Math.sin(2.0 * lng * Math.PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(lat * Math.PI) + 40.0 * Math.sin(lat / 3.0 * Math.PI)) * 2.0 / 3.0;
ret += (160.0 * Math.sin(lat / 12.0 * Math.PI) + 320 * Math.sin(lat * Math.PI / 30.0)) * 2.0 / 3.0;
return ret;
},
transformlng: (lng, lat) => {
let 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 * Math.PI) + 20.0 * Math.sin(2.0 * lng * Math.PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(lng * Math.PI) + 40.0 * Math.sin(lng / 3.0 * Math.PI)) * 2.0 / 3.0;
ret += (150.0 * Math.sin(lng / 12.0 * Math.PI) + 300.0 * Math.sin(lng / 30.0 * Math.PI)) * 2.0 / 3.0;
return ret;
},
outOfChina: (lng, lat) => {
return (lng < 72.004 || lng > 137.8347) || (lat < 0.8293 || lat > 55.8271);
},