当你运用地图这款App导航之际,可曾思考过,为何你于国外之时手机所记录的位置,于国内地图之上总会让人觉得难以契合?此背后关联着三种各异的坐标体系,以及地理信息安全方面的管理规定。
WGS-84坐标系的全球通用性
全球地心坐标系的一种名为WGS – 84,它是由美国国防部制定出来的,被GPS卫星系统采用,被Google Earth采用,还被绝大多数国际通用设备采用,它是目前通行于国际上的定位标准,其坐标是基于地球质心的,能够精确描述地球上任意一点的位置。
此坐标系具备公开、统一以及精度高的特性,全球的民航、航海还有科学研究均依赖于它,你手机GPS芯片所接收到的原始经纬度数据,一般来讲就是WGS-84坐标,它为全球位置服务供给了一个不存在偏差的公共基础。
GCJ-02坐标系的国内应用
GCJ – 02坐标系,也被称作“火星坐标系”,它属于中国国家测绘局所制定的地理信息加密一类标准,它是于WGS – 84坐标的基础之上,通过加入非线性偏移算法之后才得以形成的,中国国内全部公开的像高德、腾讯地图这样的地图服务,都必定得使用这一标准。
这般偏移属于一种国家层面的安全举措,目标是让公开的地图数据与实际精确坐标相区分。所以,从你设备获取的原始GPS坐标,得经历一次“加偏”转换,方可在国内地图上准确显示。这致使了国内外地图服务于位置显示方面存在差异。
BD-09坐标系的百度特色
百度公司于GCJ-02标准之上所做的二次加密偏移,形成了BD-09,它是百度地图专用的坐标系,这表明,即便都是国内地图,百度的坐标数据和其他厂商的也是不一样的。
外界猜测百度进行这次额外偏移的原因,或许涉及商业数据保护,又或许是为了优化其自有地图服务。对于开发者来讲,若想要把其他地图的坐标在百度地图上展现,那就必须先经历从WGS – 84到GCJ – 02的转换,之后还要再经历从GCJ – 02到BD – 09的转换。
坐标转换的技术实现
不同坐标之间的相互转换,是依靠特定的算法函数来达成的。比如说,要是把全球坐标转变为国内坐标,就得运用gcj_encrypt()这个函数。而要是进行反向的粗略转换,那就得用到gcj_decrypt()。这一些算法一般情况下都是属于开源范畴地存在着,各位开发者们能够将其整合到自身所开发的应用里面去。
对精度要求高的那种逆向转换而言,得用像二分法这样的精确算法gcj_decrypt_exact(),通过设定一个极小的误差阈值,也就是小数点后9位,进行多次迭代计算,能无限逼近真实的原始坐标,从而满足专业场景的需求。
坐标偏差的实际影响
普通用户体会到的这种坐标偏差是显著可感的,比如说,你于海外利用运动手表所记录下的跑步轨迹,要是直接导入国内地图,极有可能呈现出你犹如“闯入河中”的怪象。而在物流、测绘这些专业范畴之内,坐标转换工作是绝对不能有丝毫马虎的,不然就会引发极为严重的定位差错。
偏差大小于不同地区并非固定不变,算法设计的偏移量也是处于变化状态之中的,一般而言,在中国的核心区域范围之内,偏差大概是在几十米至几百米之间处于浮动状况。这样的一种距离对于日常的导航活动来讲或许是能够被接受的,然而对于精确的地理信息应用来说却绝对是必须要加以纠正的情况。
开发者与用户的应对策略
对于软件方面的开发者而言,当着手处理多个地图平台之际 ,势必有必要将坐标转换模块进行内置 。在对不同的地图API展开调用以前 ,务必要明晰其要求所对应的坐标系 ,并且完成与之相对应的转换 ,这属于应用开发的基本技能 。
对普通用户来讲,知晓这一知识有益于明白定位偏差的源头所在,可以避免陷入困惑,在需要精准位置之际,像野外作业的情形下,应当采用专业的且支持原始坐标的测绘设备,而针对日常导航,相信App自动完成转换就行。
你有没有经历过,因为坐标出现偏差,致使导航出现错误的情况呢?又或者身为开发者,在处理地图坐标之际,碰到过哪些难以解决的棘手难题呢?欢迎在评论区,分享你的故事哦,如果感觉本文有助力,也请点赞,并且分享给更多的朋友哟。
var GPS = {
PI : 3.14159265358979324,
x_pi : 3.14159265358979324 * 3000.0 / 180.0,
delta : function (lat, lon)
{
// Krasovsky 1940
//
// a = 6378245.0, 1/f = 298.3
// b = a * (1 - f)
// ee = (a^2 - b^2) / a^2;
var a = 6378245.0;
var ee = 0.00669342162296594323;
var dLat = this.transformLat(lon - 105.0, lat - 35.0);
var dLon = this.transformLon(lon - 105.0, lat - 35.0);
var radLat = lat / 180.0 * this.PI;
var magic = Math.sin(radLat);
magic = 1 - ee * magic * magic;
var sqrtMagic = Math.sqrt(magic);
dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * this.PI);
dLon = (dLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * this.PI);
return {'lat': dLat, 'lon': dLon};
},
//WGS-84 to GCJ-02
gcj_encrypt : function (wgsLat, wgsLon)
{
if (this.outOfChina(wgsLat, wgsLon))
return {'lat': wgsLat, 'lon': wgsLon};
var d = this.delta(wgsLat, wgsLon);
return {'lat' : wgsLat + d.lat,'lon' : wgsLon + d.lon};
},
//GCJ-02 to WGS-84
gcj_decrypt : function (gcjLat, gcjLon)
{
if (this.outOfChina(gcjLat, gcjLon))
return {'lat': gcjLat, 'lon': gcjLon};
var d = this.delta(gcjLat, gcjLon);
return {'lat': gcjLat - d.lat, 'lon': gcjLon - d.lon};
},
//GCJ-02 to WGS-84 exactly
gcj_decrypt_exact : function (gcjLat, gcjLon) {
var initDelta = 0.01;
var threshold = 0.000000001;
var dLat = initDelta, dLon = initDelta;
var mLat = gcjLat - dLat, mLon = gcjLon - dLon;
var pLat = gcjLat + dLat, pLon = gcjLon + dLon;
var wgsLat, wgsLon, i = 0;
while (1) {
wgsLat = (mLat + pLat) / 2;
wgsLon = (mLon + pLon) / 2;
var tmp = this.gcj_encrypt(wgsLat, wgsLon)
dLat = tmp.lat - gcjLat;
dLon = tmp.lon - gcjLon;
if ((Math.abs(dLat) < threshold) && (Math.abs(dLon) 0) pLat = wgsLat; else mLat = wgsLat;
if (dLon > 0) pLon = wgsLon; else mLon = wgsLon;
if (++i > 10000) break;
}
//console.log(i);
return {'lat': wgsLat, 'lon': wgsLon};
},
//GCJ-02 to BD-09
bd_encrypt : function (gcjLat, gcjLon)
{
var x = gcjLon, y = gcjLat;
var z = Math.sqrt(x * x + y * y) + 0.00002 * Math.sin(y * this.x_pi);
var theta = Math.atan2(y, x) + 0.000003 * Math.cos(x * this.x_pi);
bdLon = z * Math.cos(theta) + 0.0065;
bdLat = z * Math.sin(theta) + 0.006;
return {'lat' : bdLat,'lon' : bdLon};
},
//BD-09 to GCJ-02
bd_decrypt : function (bdLat, bdLon)
{
var x = bdLon - 0.0065, y = bdLat - 0.006;
var z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * this.x_pi);
var theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * this.x_pi);
var gcjLon = z * Math.cos(theta);
var gcjLat = z * Math.sin(theta);
return {'lat' : gcjLat, 'lon' : gcjLon};
},
distance : function (latA, logA, latB, logB) {
var earthR = 6371000;
var x = Math.cos(latA*Math.PI/180) * Math.cos(latB*Math.PI/180) * Math.cos((logA-logB)*Math.PI/180);
var y = Math.sin(latA*Math.PI/180) * Math.sin(latB*Math.PI/180);
var s = x + y;
if (s > 1)
s = 1;
if (s < -1)
s = -1;
var alpha = Math.acos(s);
var distance = alpha * earthR;
return distance;
},
outOfChina : function (lat, lon)
{
if (lon 137.8347)
return true;
if (lat 55.8271)
return true;
return false;
},
transformLat : function (x, y)
{
var 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 * this.PI) + 20.0 * Math.sin(2.0 * x * this.PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(y * this.PI) + 40.0 * Math.sin(y / 3.0 * this.PI)) * 2.0 / 3.0;
ret += (160.0 * Math.sin(y / 12.0 * this.PI) + 320 * Math.sin(y * this.PI / 30.0)) * 2.0 / 3.0;
return ret;
},
transformLon : function (x, y)
{
var 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 * this.PI) + 20.0 * Math.sin(2.0 * x * this.PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(x * this.PI) + 40.0 * Math.sin(x / 3.0 * this.PI)) * 2.0 / 3.0;
ret += (150.0 * Math.sin(x / 12.0 * this.PI) + 300.0 * Math.sin(x / 30.0 * this.PI)) * 2.0 / 3.0;
return ret;
}
};