前言
由于公司的一些项目,需要使用别人的地图服务,但我们已经使用高德开发好了,换sdk需要修改的代码很多,因此想的是客户端sdk不变,加载服务数据可不可行(如arcgis、天地图)
瓦片图简介
为何叫瓦片,看看下图就明白了,这是其中一张图片,一般是服务端根据数据生成一张大图,然后切片成一张张小图,一般大小是256x256的图片
这是加载了部分图片的效果,可以明显看到地图是由很多张图片拼接而来的
下图这个是不同缩放层级,需要切图的数量是指数级的
但是现在大家截图看看地图请求,可能是像这样的,这个是矢量瓦片,也就是服务端返回的是路网的数据,客户端根据数据实时渲染出来,也就可以做到缩放层级更大
加载瓦片图
我们使用高德地图,加载一下zoom=2的时候,可以看到左上角是0,0,一个4x4的正方形
天地图,加载zoom=2,左上角是0,0, 一个4x2的长方形,经纬度投影
高德加载示例代码:
var map = new AMap.Map('container', {
zoom: 14,
zooms: [0, 11],
});
var layer = new AMap.TileLayer.Flexible({
cacheSize: 300,
zIndex: 200,
createTile: function (x, y, z, success, fail) {
var c = document.createElement('canvas');
c.width = c.height = 256;
var cxt = c.getContext("2d");
cxt.font = "15px Verdana";
cxt.fillStyle = "#ff0000";
cxt.strokeStyle = "#FF0000";
cxt.strokeRect(0, 0, 256, 256);
cxt.fillText('(' + ['x:'+x, 'y:'+y, 'zoom:'+z].join(',') + ')', 10, 30);
success(c);
}
});
layer.setMap(map);
加载天地图的图层
var xyzTileLayer = new AMap.TileLayer({
'getTileUrl': function (x, y, z) {
return 'http://t6.tianditu.gov.cn/vec_c/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=c&FORMAT=tiles&tk=&TILECOL='+x+'&TILEROW='+y+'&TILEMATRIX='+z;
},
'zooms': [2, 20],
'zIndex': 10
});
map.add(xyzTileLayer);
使用高德的瓦片图加载功能
但是在zoom=2的时候可以看到,高德地图下边多了2个格子
这多了2个格子,肯定有什么问题
地图投影
可以看到,天地图有2种投影,为什么切图,一个是正方形,一个是长方形呢?
这需要知道地图投影的概念
由于地球是一个球体,因此展开到平面的时候,肯定会有拉伸
看看下图2种投影方式
经纬度投影
墨卡托投影
表示的范围基本上都是北美洲版块,但是拉伸的结果不一样,至于为什么,可网上查询相关投影算法
除了经纬度投影和墨卡托投影投影外,还有其他很多种投影,只是用处不一样,不过一般web地图都是这2种
不同的投影方式,左上角原点表示的经纬度肯定不一样,那应该多少呢?
投影坐标范围
对于2种投影算法,我们的平面坐标系范围是多少呢?
墨卡托投影:
x方向:[-20037508.3427892,20037508.3427892] 对应的地球坐标经度:[-180,180]
y方向:[-20037508.3427892,20037508.3427892] 对应地球坐标维度:[-85.05112877980659,85.05112877980659]
经纬度投影:
x方向:[-180,180] 对应的地球坐标经度:[-180,180]
y方向:[-90,90] 对应地球坐标维度:[-90,90]
可以看到2种投影范围是不一样的
有了平面坐标系的范围,我们就可以处理 上面提到的多了2个格子问题
转换坐标
上面我们用高德加载天地图的时候,用的是4x4的格子 加载 4x2 的数据 在平面坐标系 下面表示的位置肯定是不一样的,这里我们就需要转换一下
例如我们有一个marker要显示到地图上
// 创建一个 Marker 实例:
var marker = new AMap.Marker({
position: new AMap.LngLat(116.39, 39.9), // 经纬度对象,
title: '北京'
});
图片中看到,zoom=2时,在高德地图的画布上,北京在墨卡托投影是格子(3,1),而经纬度投影是格子(3,0)
我们使用高德sdk来显示北京的marker,传的是北京的经纬度,但是加载天地图的时候,我们看到的图像数据是在(3,0),但是当前的坐标直接显示是在(3,1),所以我们需要转换一下
这里2有个问题:
1.zoom=2时,4x4的格子加载的是4x2的数据,zoom=3时,8x8的格子加载的是8x4的数据,相当于格子高度少了一半
2.平面坐标范围表示的不一样
要想把marker正确的显示到天地图的那个位置,需要如下转换
高德地图坐标-> 天地图平面坐标 -> 高德平面坐标 -> 高德地图坐标(新) -> 新坐标 显示marker
1.计算坐标对应天地图所在的格子(直接计算经纬度坐标在3,0)
2.计算天地图格子对应的高德地图格子(这里也是3,0, 因为原点都是左上角)
3.计算高德格子对应新的经纬度
4.用新的经纬度显示marker
代码示例
let lnglat = [116.39, 39.9];
//把经纬度转换到 天地图对应 的格子
let tiandituPx = lnglatToTiandituPx(lnglat)
//由于这里是一样的,所以不需要转换
let gaodePx = tiandituPx;
//把高德的px坐标转换成经纬度
let newLnglat = gaodePxToLnglat(gaodePx)
// 创建一个 Marker 实例:
var marker = new AMap.Marker({
position: gaodePx, //新的 经纬度对象,
title: '北京'
});
转换函数
function lnglatToTiandituPx([lng,lat],zoom){
//火星坐标转84坐标
let gcj02 = gcj02towgs84(lng,lat);
//经纬度转平面坐标,由于是一样的,因此不需要转换
let plane = {x:gcj02.lng,y:gcj02.lat}
//墨卡托平面坐标范围
// x方向:`[-180,180]`
// y方向:`[-90,90]`
return {
x:(plane.x+180) / 180 * 2, //转换坐标到[0-1],便于计算
y:(plane.y+90) / 90 * 2,
}
}
function gaodePxToLnglat({x,y}){
//x方向:`[-20037508.3427892,20037508.3427892]`
// y方向:`[-20037508.3427892,20037508.3427892]`
let x_1 = x*20037508.3427892*2 - 20037508.3427892; //还原墨卡托坐标
let y_1 = y*20037508.3427892*2 - 20037508.3427892;
//平面坐标转经纬度
let lnglat = mercator2lonlat({x:x_1,y:y_1});
return [lnglat.x,lnglat.y];
}
墨卡托平面坐标和经纬度坐标互相转换
function mercator2lonlat(mercator){
var lonlat={x:0,y:0};
var x = mercator.x/20037508.3427892*180;
var y = mercator.y/20037508.3427892*180;
y= 180/Math.PI*(2*Math.atan(Math.exp(y*Math.PI/180))-Math.PI/2);
lonlat.x = x;
lonlat.y = y;
return lonlat;
}
function lonlat2mercator(lonlat)
{
var mercator={x:0,y:0};
var x = lonlat.x *20037508.3427892/180;
var y = Math.log(Math.tan((90+lonlat.y)*Math.PI/360))/(Math.PI/180);
y = y *20037508.3427892/180;
mercator.x = x;
mercator.y = y;
return mercator ;
}
总结
我们这里使用了高德加载天地图,加载其他地图也差不多,如arcgis等,都是用瓦片图,web地图一般都是 墨卡托投影和经纬度投影
另外还有一个点,不同的地图可能坐标系不一样,基本上都是地球坐标系——WGS84的一次加密(转换)