地球是一个椭球,Datum是一组用于描述这个椭球的数据集合。最常用的一个Datum是WGS84(World Geodetic System 1984),它的主要参数有:
通过以上参数设定,我们才能对地球上的任意一个位置用经度、纬度、高度三个变量进行描述。所以当我们获取一组经纬度信息时,首先要弄明白这组信息对应的Datum,即对应坐标系。
WGS84 Datum的信息可以用下图进行概括:
地图是显示在平面上的,因此需要将球面坐标转换为平面坐标,这个转换过程称为投影。最常见的投影是墨卡托(Mercator)投影,它具有等角性质,即球体上的两点之间的角度方位与平面上的两点之间的角度方位保持不变,因此特别适合用于导航。
Web墨卡托投影(又称球体墨卡托投影)是墨卡托投影的变种,它在投影时不再把地球当做椭球而当做半径为6378137米的标准球体,以简化计算。其计算公式推导请参考下图:
Web墨卡托投影有两个相关的投影标准,经常搞混:
(备注:天地图使用经纬度投影(对应EPSG4326)使用84坐标;而互联网使用的墨卡托投影(对应EPSG3857)使用的是02坐标)
经过Web墨卡托投影后,地图就变为平面的一张地图。考虑到有时候我们需要看宏观的地图信息(如世界地图里每个国家的国界),有时候又要看很微观的地图信息(如导航时道路的路况信息)。为此,我们对这张地图进行等级切分。在最高级(zoom=0),需要的信息最少,只需保留最重要的宏观信息,因此用一张256x256像素的图片表示即可;在下一级(zoom=1),信息量变多,用一张512x512像素的图片表示;以此类推,级别越低的像素越高,下一级的像素是当前级的4倍。这样从最高层级往下到最低层级就形成了一个金字塔坐标体系(又叫影像金字塔)。
对每张图片,我们将其切分为256x256的图片,称为瓦片(Tile)。这样,在最高级(zoom=0)时,只有一个瓦片;在下一级(zoom=1)时有4个瓦片;在下一级(zoom=2)时有16个瓦片,以此类推。
上述过程可以用下图进行总结:
瓦片生成后,就是一堆图片。怎么对这堆图片进行编号,是目前主流互联网地图商分歧最大的地方。总结起来分为三个流派:
下图显示了前三个流派在zoom=1层级上的瓦片编号结果:
下表总结了中国主要地图商的瓦片编号流派,点击每个链接就可以获得一个对应编号的瓦片地图:
地图商 |
瓦片编码 |
图层 |
链接 |
高德地图 |
谷歌XYZ |
道路 |
http://webrd02.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x=105&y=48&z=7 |
高德地图 |
谷歌XYZ |
卫星 |
http://webst04.is.autonavi.com/appmaptile?style=6&x=843&y=388&z=10 |
谷歌地图 |
谷歌XYZ |
道路 |
http://mt2.google.cn/vt/lyrs=m&hl=zh-CN&gl=cn&x=105&y=48&z=7 |
谷歌地图 |
谷歌XYZ |
卫星 |
http://mt2.google.cn/vt/lyrs=s&hl=zh-CN&gl=cn&x=105&y=48&z=7 |
谷歌地图 |
谷歌XYZ |
地形 |
http://mt0.google.cn/vt/lyrs=t&hl=zh-CN&gl=cn&x=420&y=193&z=9 |
OpenStreetMap |
谷歌XYZ |
道路 |
http://a.tile.openstreetmap.org/7/105/48.png |
腾讯地图 |
TMS |
道路 |
http://rt1.map.gtimg.com/realtimerender?z=7&x=105&y=79&type=vector&style=0 |
Bing地图 |
QuadTree |
道路 |
http://r1.tiles.ditu.live.com/tiles/r1321001.png?g=100&mkt=zh-cn |
百度地图 |
百度XYZ |
道路 |
http://online4.map.bdimg.com/tile/?qt=tile&x=98&y=36&z=9&;styles=pl&scaler=1&udt=20170406 |
百度地图 |
百度XYZ |
交通 |
http://its.map.baidu.com:8002/traffic/TrafficTileService?level=19&x=99052&y=20189&time=1373790856265&label=web2D&;v=017 |
四维图新 |
谷歌XYZ |
http://datahive.minedata.cn/mergeddata/Adminflag,Annotation,Ptline,Road,Villtown,Worldannotation/10/843/387?token=25cc55a69ea7422182d00d6b7c0ffa93&solu=2365 |
国内的经纬度有三套系统:
使用OpenStreetMap的坐标为WGS84;使用高德地图、腾讯地图的坐标为GCJ02;使用百度地图的坐标为BD09;谷歌地图和Bing地图的中国部分采用了高德地图的数据,所以坐标为GCJ02。
备注1:互联网上找不到中国84的数据
备注2:常用取坐标工具:http://www.gpsspg.com/maps.htm
WGS84的坐标转化为GCJ02的坐标是单向的,即WGS84的坐标能够准确地变换为GCJ02坐标;但GCJ02坐标转换为WGS84时会存在精度损失,存在算法可以减小精度损失
根据文件夹以及文件命名,"\alllayers\L03\R00000002\C00000005.png",其中L03表示地图缩放级别,也就是z值;R00000002表示row,也就是y值;C00000005表示column,也就是x值;此外arcgis server切片瓦片编码采用google编码规则,也就是左上作为(0,0)起点,X从左向右,Y从上向下。
"EPSG_900913_05\3_2\24_17.png",其中EPSG_900913表示坐标系编码,05表示地图缩放级别,也就是z值;24表示x值;17表示y值;二层文件夹命名无特殊含义。此外,GeoServer切片编号采用标准的tms编号规则,以地图左下角为(0,0)起点,X从左向右,Y从下向上。
备注1: arcgis/geoserver瓦片 转 XYZ工具:https://jercky.top/2019/05/12/totms/
在计算瓦片的行列号之前,我们需要得到图上一像素代表实际距离多少米。现在假设地图的坐标单位是米,dpi(1英寸有多少像素) 为96;
1英寸=2.54厘米;
1英寸=96像素;
最终换算的单位是米;
如果当前地图比例尺为1:125000000,则代表图上1米等于实地125000000米;
米和像素间的换算公式:
1英寸=0.0254米=96像素
1像素=0.0254/96 米
则根据1:125000000比例尺,图上1像素代表实地距离是 125000000*0.0254/96 = 33072.9166666667米。
4.2.1 简介
现在,我假设我们的服务器上有一个1G的影像,需要将其在前端进行显示。我们传统的做法就是首先将服务器中的1G影像下载到前端,然后浏览器加载渲染出图。但是大家想想,首先客户端下载1G的影像需要的时间一定是个漫长的过程,其次浏览器加载这么大的文件也多半会导致其崩溃。而最重要的一个问题是,我们的需求仅仅是浏览全图中的某一个区域下的某几个级别,现在却将全图下载完毕了,而这同样还导致了数据的不安全性(下载到本地),同时我们的每一次放大和缩小以及拖拽都将会使浏览器花上足够长的时间去渲染。
可见,传统的方式是不符合实际需求的。到后来,又有了新的解决方法,比如arcgis的IMS版本中提出了动态出图的概念。也就是当前端发出的请求里包含了需要显示的范围、显示窗口的大小等参数后,后台动态的在原始数据中切出一个符合需求的瓦片,然后将这个数据返回给前台,并且在服务器中对这个瓦片做缓存。
但是,这个方法前端出图依旧很慢,并且使地图服务器的压力过大。终于,我们的影像金字塔解决方案出现了。
4.2.2 原理
影像金字塔就是,我们首先将原始影像按照用户的需求,比如用户需要显示多少种比例尺下的数据,需要显示的是原始影像中的哪个区域的数据,将原始影像按照这些需求进行划分和提取。如图:
最低层就是我们提取和划分出的比例尺最小的一级的瓦片,而最上层的则是比例尺最大的一级的瓦片。我们仔细观察可以发现这样的一个规律:比例尺越小的级别瓦片数据越少,反之则越大。而这个规律导致的结果就是:比例尺越小的级别切图的速度越快,同时,同样大小的瓦片所包含的影像范围越多。
当我们建立好了影像金子塔后,前端再请求地图时,则将只是在切好的瓦片缓存中,找到对应级别里对应的瓦片即可。然后在前端将这些请求到的瓦片拼接出来,便可以得到用户需要的级别下的可视范围内的瓦片了。
4.3.1为什么要换算瓦片行列号
上一节中我给出了影像图切成离散型图后文件的组织形式,其中给大家展示了在这种切图下,文件的组织其实是按照瓦片的级别、行、列号来组织的。事实上,紧凑型瓦片(Bundle)的组织样式也是如此,只是它在得到了行列号后还要进行一系列换算,比如读取索引文件找到文件中的偏移量等,这个换算方式我在以后的章节跟大家来讨论。并且,标准的WMS请求中也涉及到行列号的换算,WMS请求中有一个Bbox的参数,而这个参数也与行列号的换算有关系。而标准的WMTS请求中,TILEMATRIX、TILEROW、TILECOL这三个参数代表的就是瓦片的级别、行、列号。
由此可见,不管是针对哪种离线或在线的地图的瓦片请求中,得到瓦片的level、col、row是请求能够实现的核心。
4.3.2 瓦片行列号换算原理
下面,我们先给出瓦片行列号换算的公式。
假设,地图切图的原点是(x0,y0)(默认是0, 0),地图的瓦片大小是tileSize,地图屏幕上1像素代表的实际距离是resolution。计算坐标点(x,y)所在的瓦片的行列号的公式是:
col = (x0 - x)/( 256*33072)
row = (y0 - y)/( tileSize*resolution)
这个公式应该不难理解,简单点说就是,首先算出一个瓦片所包含的实际长度是多少LtileSize,然后再算出此时屏幕上的地理坐标点离瓦片切图的起始点间的实际距离LrealSize,然后用实际距离除以一个瓦片的实际长度,即可得此时的瓦片行列号:LrealSize/LtileSize。
4.3.3 resolution的换算原理
4.3.3.1 当系统是经纬度系统时,此resolution可以直接使用切图文档中的resolution。
例如:Arcgis 切图后生成的config.xml文件:
4.3.3.2 如果系统是平面坐标系统时,此resolution的算法是:
Resolution = scale * inch2centimeter / dpi。其中scale是地图比例尺,inch2centimeter为英寸转厘米的参数,dpi为1英寸所包含的像素。
inch2centimeter: 1英寸=0.0254米=96像素
4.3.4 实际系统中的运用情况
(1)得到画布的高度和宽度以及此时需要显示的地图的几何范围
(2)得到画布的高度和宽度以及此时需要显示的地图的几何范围,同时也得到了需要显示的地图的级别
最后,我们需要得到在这两种需求下的瓦片行列号范围。
流程图: