本文将介绍瓦片坐标相关知识,并提供高德地图、百度地图、谷歌地图的经纬度坐标与瓦片坐标的相互转换方法和类库。
互联网地图服务商的在线地图都通过瓦片的方式提供,称为瓦片地图服务。最常见的地图瓦片是图片格式的,现在有的地图服务商也提供了矢量的瓦片数据,然后在用户端使用Canvas渲染成图片,如node-canvas实现百度地图个性化底图绘制。
在进行地图开发时,为获取特定经纬度所在区域的瓦片和获取瓦片上像素点对应的经纬度,经常需要进行经纬度坐标与瓦片坐标、像素坐标的相互转换。本文将介绍瓦片坐标相关知识,并提供高德地图、百度地图、谷歌地图的经纬度坐标与瓦片坐标的相互转换方法和转换类库--tile-lnglat-transform。
国际标准的经纬度坐标是WGS84
,Open Street Map、外国版的Google Map都是采用WGS84
;高德地图使用的坐标系是GCJ-02
;百度地图使用的坐标系是BD-09
。高德地图和百度地图都提供了在线的单向坐标转换接口,将其他坐标系换化到自己的坐标系,但这种转换受限于http url请求字段长度和网络请求延迟,批量处理并不实用。离线相互转换可以通过开源JavaScript库coordtransform实现,误差在10米左右。
虽然各地图服务商经纬度坐标系不同,但某一互联网地图的经纬度坐标与瓦片坐标相互转换只与该地图商的墨卡托投影和瓦片编号的定义有关,跟地图商采用的大地坐标系标准无关。
使用经纬度表示位置的大地坐标系虽然可以描述地球上点的位置,但是对于地图地理数据在二维平面内展示的场景,需要通过投影的方式将三维空间中的点映射到二维空间中。地图投影需要建立地球表面点与投影平面点的一一对应关系,在互联网地图中常使用墨卡托投影。墨卡托投影是荷兰地理学家墨卡托于1569年提出的一种地球投影方法,该方法是圆柱投影的一种。投影的更多内容,可以查看地图投影的N种姿势。
据我了解,各大地图服务商都采用了Web Mercator进行投影,瓦片坐标系的不同主要是投影截取的地球范围不同、瓦片坐标起点不同。
对于经过墨卡托投影为平面的世界地图,在不同的地图分辨率(整个世界地图的像素大小)下,通过切割的方式将世界地图划分为像素为$256\times256$的地图单元,划分成的每一块地图单元称为地图瓦片。
地图瓦片具有以下特点:
高德地图瓦片坐标与Google Map、Open Street Map相同。高德地图的墨卡托投影截取了纬度(约85.05ºS, 约85.05ºN)之间部分的地球,使得投影后的平面地图水平方向和垂直方向长度相等。将墨卡托投影地图的左上角作为瓦片坐标系起点,往左方向为X轴,X轴与北纬85.05º重合且方向向左;往下方向为Y轴,Y轴与东经180º(亦为西经180º)重合且方向向下。瓦片坐标最小等级为0级,此时平面地图是一个像素为256*256的瓦片。在某一瓦片层级Level下,瓦片坐标的X轴和Y轴各有2^Level个瓦片编号,瓦片地图上的瓦片总数为2^Level*2^Level。
如上图所示,此时X方向和Y方向各有4个瓦片编号,总瓦片数为16。中国大概位于高德瓦片坐标的(3,1)中。
从高德地图坐标转换图解中可以看出,高德地图的坐标转换具有以下特点:
方法参考:Slippy map tilenames
百度地图的瓦片坐标系定义与高德地图并不相同,其墨卡托投影的参数也不同。百度地图瓦片坐标以墨卡托投影地图中赤道与0º经线相交位置为原点,沿着赤道往左方向为X轴,沿着0º经线向上方向为Y轴。
百度瓦片坐标定义了另一种二维坐标系,称为百度平面坐标系。百度平面坐标系的坐标原点与百度瓦片坐标原点相同,以瓦片等级18级为基准,规定18级时百度平面坐标的一个单位等于屏幕上的一个像素。平面坐标与地图所展示的级别没有关系,也就是说在1级和18级下,同一个经纬度坐标的百度平面坐标都是一致的。
此时X方向和Y方向各有4个瓦片编号,但是外围的某些瓦片只有部分区域有地图或完全没有地图。没有地图的区域也可以认为其瓦片是无效的,即百度地图中X方向或Y方向的有效瓦片不一定达到2^{Level}个。
中国大概位于百度瓦片坐标的(0,0)中。
从百度地图坐标转换图解中可以看出,百度地图的坐标转换具有以下特点:
方法参考:百度地图API详解之地图坐标系统
发现百度JavaScript API的一个bug:百度JavaScript API中经纬度坐标转瓦片坐标bug
经纬度坐标(lng, lat)转平面坐标(pointX, pointY)
百度经纬度坐标与百度平面坐标的相互转换,并没有公开的公式,需要通过百度地图的API实现。
主要代码为:
// Bmap为百度JavaScript API V2.0的地图对象
lnglatToPoint(longitude, latitude) {
let projection = new BMap.MercatorProjection();
let lnglat = new BMap.Point(longitude, latitude);
let point = projection.lngLatToPoint(lnglat);
return {
pointX: point.x,
pointY: point.y
};
}
平面坐标(pointX, pointY)转经纬度坐标(lng, lat)
也需要通过百度地图的API实现。
主要代码为:
pointToLnglat(pointX, pointY) {
let projection = new BMap.MercatorProjection();
let point = new BMap.Pixel(pointX, pointY);
let lnglat = projection.pointToLngLat(point);
return {
lng: lnglat.lng,
lat: lnglat.lat
};
}
平面坐标(pointX, pointY)转瓦片坐标(tileX, tileY)
百度地图JavaScript的代码非常奇葩,非常迷惑:
经纬度类是Point
,平面坐标类是Pixel
。
经纬度转平面坐标是lngLatToPoint
,接收一个Point
对象,返回一个Pixel
对象。
平面坐标转经纬度坐标是在pointToLngLat
,接收Pixel
对象,返回一个Point
对象。
WTF!
本文笔者根据前文介绍的经纬度坐标与瓦片坐标、像素坐标相互转换规则,编写了一个JavaScript类库--tile-lnglat-transform,提供了高德地图、百度地图、谷歌地图的经纬度坐标与瓦片坐标的相互转换。该模块是使用了UMD
模块打包方式,可以在node和broswer中使用。
类库地址:https://github.com/CntChen/tile-lnglat-transform
该类库的详细信息及使用方法请在项目主页中查看。
经过实际测试,各地图服务商的瓦片等级和测试链接如下:
百度图片瓦片的层级是[3~18]
http://online1.map.bdimg.com/onlinelabel/?qt=tile&x=49310&y=10242&z=18
百度主页的层级是[3~19]
http://map.baidu.com/
高德图片瓦片的层级是[1~19]
http://wprd03.is.autonavi.com/appmaptile?style=7&x=427289&y=227618&z=19
高德地图官网介绍的高德地图层级:
获取当前地图缩放级别,在PC上,默认取值范围为[3,18];在移动设备上,默认取值范围为[3-19]
谷歌地图瓦片层级是[0~21]
http://mt2.google.cn/vt/lyrs=m@167000000&hl=zh-CN&gl=cn&x=1709157&y=910472&z=21&s=Galil
瓦片地图服务
https://en.wikipedia.org/wiki/Tile_Map_Service
node-canvas实现百度地图个性化底图绘制
http://www.cnblogs.com/well1010/articles/baidu-map-node-canvas.html
tile-lnglat-transform
https://github.com/CntChen/tile-lnglat-transform
coordtransform
https://github.com/wandergis/coordtransform
地图投影的N种姿势
http://blog.sina.com.cn/s/blog_517eed9f0102w4rm.html
Web Mercator
https://en.wikipedia.org/wiki/Web_Mercator
Slippy map tilenames
http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames
百度地图API详解之地图坐标系统
http://www.cnblogs.com/jz1108/archive/2011/07/02/2095376.html
百度JavaScript API中经纬度坐标转瓦片坐标bug
http://cntchen.github.io/2016/05/09/百度JavaScirpt%20%20API中经纬度坐标转瓦片坐标bug/
高德地图层级
http://lbs.amap.com/api/javascript-api/reference/map/