ArcGIS 墨卡托投影出现偏移
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ArcGIS 墨卡托投影出现偏移相关的知识,希望对你有一定的参考价值。
参考技术A 最近项目中接入了其他图商的底图,发现他们的地图是gcj02mc的,坐标中的点位都是米制单位,但是我在地图选点时在百度反向地理编码出现了位置偏移问题,反向地理编码出的位置和原位置隔了大概20km左右。仔细分析下为什么会出现偏移:
先介绍下我们公司的经纬度情况 (原来的)
1.底图 : 百度墨卡托投影(bd09mc)
2.定位: 取的是系统自带的经纬度(wgs84) ios
3.百度反向地理编码,百度POI检索: bd09
修改后:
1.底图 : gcj02 Mercator
2.定位: 取的是系统自带的经纬度(wgs84) IOS
3.百度反向地理编码,百度POI检索: bd09
使用场景: 地图取点-> 反向地理编码
原来的场景下: bd09mc->bd09
现有场景: gcj02 Mercator->gcj02ll->bd09
完整完成这个流程后发现点位情况发生了偏移
在这能很明显的展示出来左边情况,大概偏移了有20km。
我这边使用的是百度投影转换算法,这边有一个 (在线转换工具)[ http://www.site-digger.com/tools/mct2latlng.html]
仔细观察后发现图商使用的是Arcgis中自带的web墨卡托投影算法,我们之前项目中采用的是百度米制算法,仔细了解得知墨卡托投影算法还有很多种。Arcgis中具体的解释: https://desktop.arcgis.com/zh-cn/arcmap/latest/map/projections/mercator.htm ,Google Web 墨卡托,在此之前我已经详细介绍过了我项目里的百度米制算法,这里讲下如何直接使用Arcgis自带方法将web墨卡托坐标转换成地理坐标系。
小tip:
使用arcgis中的api直接转换坐标
其中 WKID
也可以设置成102100,从ArcGIS官网得知:102100实际上也是 Web Mercator 的WKID,是Esri早年规定的,现在已经被 3857 取代。
之前也没有系统的学习ArcGIS内容,大部分做的是电商有关的项目,也是到了这家公司之后才开始全面接触ArcGIS,因为之前和后台都是约定的百度米制坐标,也没有关注相关的问题,后面还是要加强学习这方面的书籍,在这里记录下遇到的问题希望和大家一起学习。
地图坐标系之间的转换(百度地图GCJWGS84)
文章参考
坐标系介绍
常见坐标系
WGS84坐标系(标准的GPS坐标)
GPS,WGS-84,原始坐标体系。一般用国际标准的GPS记录仪记录下来的坐标,都是GPS的坐标。
EPSG:4326 与WGS84的关系?
在国际上,每个坐标系统都会被分配一个 EPSG 代码,EPSG:4326 就是 WGS84 的代码
WGS84 Web墨卡托(平面地图)
EPSG:3857(WGS 84 / Pseudo-Mercator) 代号是web墨卡托的正式代号
GCJ02经纬度投影(火星坐标系)
GCJ-02是由中国国家测绘局(G表示Guojia国家,C表示Cehui测绘,J表示Ju局)制订的地理信息系统的坐标系统。
GCJ-02是国测局02年发布的坐标体系。又称“火星坐标”
这里的GCJ02经纬度投影,也就是在WGS84经纬度的基础之上,进行GCJ-02加偏。
加偏处理是按照特殊的算法,将真实的坐标加密成虚假的坐标,而这个加偏并不是线性的加偏,所以各地的偏移情况都会有所不同
。
该坐标系的坐标值为经纬度格式,单位为度
。
GCJ02 Web 墨卡托投影
该坐标系的坐标值为Web墨卡托格式,单位为米
。
国内政策的原因,国内地图会有加密要求,一般有两种情况:
- 一种是在 Web墨卡托的基础上经过国家标准加密的国标02坐标系,熟称“火星坐标系”;
- 另一种是在国标的02坐标系下进一步进行加密,如百度地图的BD09坐标系
BD09 经纬度投影
BD09经纬度投影属于百度坐标系,
它是在标准经纬度的基础上进行GCJ-02加偏之后,再加上百度自身的加偏算法,也就是在标准经纬度的基础之上进行了两次加偏
。
该坐标系的坐标值为经纬度格式,单位为度
。
常用坐标系区分?
-
EPSG:4326,等同于WGS84坐标系
-
CGCS2000,天地图坐标系,与GPS一样,偏移较小
-
GCJ02,火星坐标系,将GPS坐标做偏移之后的数据
-
EPSG:3857,等同于900913,由墨卡托投影而来(最初 Web Mercator 被拒绝分配EPSG 代码。于是大家普遍使用 EPSG:900913(Google的数字变形) 的非官方代码来代表它)
-
BD-09:,百度地图使用坐标系
地图偏移
为什么要地图偏移?
一言以蔽之:为了国家安全
。可以想象,如果地图不加密,在百度上找到关键(军事、科研所等)目标,导弹空袭,损失不可估量
偏移在哪里?
设备一般是标准的GPS信息 —— 我们也很难要求外国各个厂商按照中国的要求制造GPS设备
中国政府加密的是地图,而非GPS 信号
地图做了偏移,为什么地图定位很精准(百度地图说明)
-
百度地图供应商是思维图新,地图提供给百度之前,需要先提交给测绘局,测绘局对地图做偏移加密,加密地图的坐标系就变成了GCJ-02(火星坐标系)
-
百度收到思维图新的地图之后发现,在地图上,GPS位置会偏很远
-
百度再把地图软件拿到测绘局,请他们加入一个“保密插件”,对GPS做同样的偏移
对GPS偏移
总结:地图偏移 和 GPS 偏移,保证了地图和GPS重合了
坐标转换
为什么要做坐标转换
工作中,开发的项目使用的是openlayers或者leaflet
开源地图引擎,使用地图的业务逻辑已经写好了,为了提高业务组件的复用性,当地图切换的时候,最好的方式就是切换地图图层,关于业务的API不要变化,那么剩下的工作就是坐标系转换即可
;
比如,要使用百度地图,使用百度的地图引擎(bmap.js),那么业务逻辑的代码就要使用百度的API来实现,等于重新开发了一次,降低了效率,不利于代码复用
让地图提供 WMTS 服务,就可以实现只换图层,不换API 的要求
地图服务(后台地图引擎可提供的能力)
TMS——Tile Map Service 即TMS,切片地图服务
将地图切割成多个级别的图片金字塔(四叉树形式分割),
它通常需要一种遵循REST原则的URI结构去标识每一张切片。
- TMS是纯RESTful的;
- TMS瓦片是正方形
WMTS——Web Map Tile Service即WMTS,网络地图切片服务,
它是一种在互联网上预渲染或进行实时地图瓦片计算的服务。
例如,在浏览器中输入 https://maponline0.bdimg.com/tile/?qt=vtile&x=1579&y=589&z=13&styles=pl&scaler=1&udt=20210506&from=jsapi2_0,就可以查看到图片
WMTS可以有三种协议:KVP、SOAP、RESTful;
WMTS瓦片是矩形;
WMTS中对应的不同比例尺瓦片可以尺寸不同
另外WMTS可以获取以下信息
- 可以获取地图瓦片;
- 可以获取WMTS服务的元数据;
- 可以获取单一瓦片的FeatureInfo;
- 可以获取地图的图例;
WMS——Web Map Service,网络地图服务
当客户端请求WMS服务时,返回给客户端是一张完整的图片,它不能让客户端知道瓦片布局,客户端取到直接展示,故WMS仅是重在灵活性。
但是复用的概率低之又低,当并发增大,服务端性能就随之大大下降。
有不同的请求类型:
- GetCapabilities:返回WMS服务的元数据和可获取的图层
- GetMap :返回地图图片
- GetFeatureInfo :如果图层被标记为“queryable”,就可以通过坐标请求地图图片数据
- DescribeLayer :返回指定图层的要素类型
- GetLegendGraphic:返回地图图例图片
常用坐标系的转换工具(百度地图、GCJ、WGS84)
const mapTools = (function () {
const x_pi = (3.14159265358979324 * 3000.0) / 180.0;
var pi = 3.14159265358979324;
var a = 6378245.0;
var ee = 0.00669342162296594323;
var LLBAND = [75, 60, 45, 30, 15, 0];
var LL2MC = [
[
-0.0015702102444, 111320.7020616939, 1704480524535203, -10338987376042340,
26112667856603880, -35149669176653700, 26595700718403920,
-10725012454188240, 1800819912950474, 82.5,
],
[
0.0008277824516172526, 111320.7020463578, 647795574.6671607,
-4082003173.641316, 10774905663.51142, -15171875531.51559,
12053065338.62167, -5124939663.577472, 913311935.9512032, 67.5,
],
[
0.00337398766765, 111320.7020202162, 4481351.045890365,
-23393751.19931662, 79682215.47186455, -115964993.2797253,
97236711.15602145, -43661946.33752821, 8477230.501135234, 52.5,
],
[
0.00220636496208, 111320.7020209128, 51751.86112841131, 3796837.749470245,
992013.7397791013, -1221952.21711287, 1340652.697009075,
-620943.6990984312, 144416.9293806241, 37.5,
],
[
-0.0003441963504368392, 111320.7020576856, 278.2353980772752,
2485758.690035394, 6070.750963243378, 54821.18345352118,
9540.606633304236, -2710.55326746645, 1405.483844121726, 22.5,
],
[
-0.0003218135878613132, 111320.7020701615, 0.00369383431289,
823725.6402795718, 0.46104986909093, 2351.343141331292, 1.58060784298199,
8.77738589078284, 0.37238884252424, 7.45,
],
];
var MCBAND = [12890594.86, 8362377.87, 5591021, 3481989.83, 1678043.12, 0];
var MC2LL = [
[
1.410526172116255e-8, 0.00000898305509648872, -1.9939833816331,
200.9824383106796, -187.2403703815547, 91.6087516669843,
-23.38765649603339, 2.57121317296198, -0.03801003308653, 17337981.2,
],
[
-7.435856389565537e-9, 0.000008983055097726239, -0.78625201886289,
96.32687599759846, -1.85204757529826, -59.36935905485877,
47.40033549296737, -16.50741931063887, 2.28786674699375, 10260144.86,
],
[
-3.030883460898826e-8, 0.00000898305509983578, 0.30071316287616,
59.74293618442277, 7.357984074871, -25.38371002664745, 13.45380521110908,
-3.29883767235584, 0.32710905363475, 6856817.37,
],
[
-1.981981304930552e-8, 0.000008983055099779535, 0.03278182852591,
40.31678527705744, 0.65659298677277, -4.44255534477492, 0.85341911805263,
0.12923347998204, -0.04625736007561, 4482777.06,
],
[
3.09191371068437e-9, 0.000008983055096812155, 0.00006995724062,
23.10934304144901, -0.00023663490511, -0.6321817810242, -0.00663494467273,
0.03430082397953, -0.00466043876332, 2555164.4,
],
[
2.890871144776878e-9, 0.000008983055095805407, -3.068298e-8,
7.47137025468032, -0.00000353937994, -0.02145144861037, -0.00001234426596,
0.00010322952773, -0.00000323890364, 826088.5,
],
];
function getRange(cC, cB, T) {
if (cB != null) {
cC = Math.max(cC, cB);
}
if (T != null) {
cC = Math.min(cC, T);
}
return cC;
}
function getLoop(cC, cB, T) {
while (cC > T) {
cC -= T - cB;
}
while (cC < cB) {
cC += T - cB;
}
return cC;
}
function convertor(cC, cD) {
if (!cC || !cD) {
return null;
}
let T = cD[0] + cD[1] * Math.abs(cC.x);
const cB = Math.abs(cC.y) / cD[9];
let cE =
cD[2] +
cD[3] * cB +
cD[4] * cB * cB +
cD[5] * cB * cB * cB +
cD[6] * cB * cB * cB * cB +
cD[7] * cB * cB * cB * cB * cB +
cD[8] * cB * cB * cB * cB * cB * cB;
T *= cC.x < 0 ? -1 : 1;
cE *= cC.y < 0 ? -1 : 1;
return [T, cE];
}
/**
* 百度墨卡托坐标转百度经纬度坐标:
* @param {*} cB
* @returns
*/
function convertBdMC2LL(lnglat) {
const cB = {
x: lnglat.lng,
y: lnglat.lat,
};
const cC = {
x: Math.abs(cB.x),
y: Math.abs(cB.y),
};
let cE;
for (let cD = 0, len = MCBAND.length; cD < len; cD++) {
if (cC.y >= MCBAND[cD]) {
cE = MC2LL[cD];
break;
}
}
const T = convertor(cB, cE);
return T;
}
/**
* 百度BD09经纬度坐标转百度墨卡托坐标:
* @param {*} T
* @returns
*/
function convertBdLL2MC(lnglat) {
const T = {
x: lnglat.lng,
y: lnglat.lat,
};
let cD, cC, len;
T.x = getLoop(T.x, -180, 180);
T.y = getRange(T.y, -74, 74);
const cB = T;
for (cC = 0, len = LLBAND.length; cC < len; cC++) {
if (cB.y >= LLBAND[cC]) {
cD = LL2MC[cC];
break;
}
}
if (!cD) {
for (cC = LLBAND.length - 1; cC >= 0; cC--) {
if (cB.y <= -LLBAND[cC]) {
cD = LL2MC[cC];
break;
}
}
}
const cE = convertor(T, cD);
// return cE;
return {
lng: cE[0],
lat: cE[1],
};
}
/*判断是否在国内,不在国内则不做偏移*/
function outOfChina(lon, lat) {
if ((lon < 72.004 || lon > 137.8347) && (lat < 0.8293 || lat > 55.8271)) {
return true;
} else {
return false;
}
}
function transformLat(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 * 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;
}
function transformLon(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 * 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;
}
function delta(lat, lon) {
let a = 6378245.0; // a: 卫星椭球坐标投影到平面地图坐标系的投影因子。
let ee = 0.00669342162296594323; // ee: 椭球的偏心率。
let dLat = transformLat(lon - 105.0, lat - 35.0);
let dLon = transformLon(lon - 105.0, lat - 35.0);
let radLat = (lat / 180.0) * pi;
let magic = Math.sin(radLat);
magic = 1 - ee * magic * magic;
let sqrtMagic = Math.sqrt(magic);
dLat = (dLat * 180.0) / (((a * (1 - ee)) / (magic * sqrtMagic)) * pi);
dLon = (dLon * 180.0) / ((a / sqrtMagic) * Math.cos(radLat) * pi);
return {
lat: dLat,
lon: dLon,
};
}
// 地球坐标系(WGS-84)转火星坐标系(GCJ):
function transformWGS2GCJ(lnglat) {
const wgLat = lnglat.lat;
const wgLon = lnglat.lng;
var mars_point = {};
if (outOfChina(wgLon, wgLat)) {
mars_point.lat = wgLat以上是关于ArcGIS 墨卡托投影出现偏移的主要内容,如果未能解决你的问题,请参考以下文章
ArcGIS 9.3中文件相比地图莫名奇妙的发生偏移现象,这是怎么回事?