自定义地图数据瓦片化请求的一种实现方案
之前做一项目,要接入其它公众平台上的数据,显示到地图上,而且要求拖动地图时,能载入地图新区域内的数据,就像现在大部分的地图应用一样,瓦片形式加载。由于数据不是存放于自己的平台,所以这里会出现个问题,数据不是以地图瓦片形式进行保存。
但是公众平台有提供个接口,可以使用位置和距离的参数,请求这个距离范围内的数据。所以可以将地图进行瓦片分割,再利用此接口请求瓦片所在圆范围内的数据。通过此种方式,实现数据的瓦片化请求和加载。
1. 墨卡托投影
墨卡托投影(Mercator Projection),又名“等角正轴圆柱投影”,是荷兰地图学家墨卡托(Mercator)在1569年拟定的。他假设地球被围在一个中空的圆柱里,其赤道与圆柱相接触,然后再假想地球中心有一盏灯,把球面上的图形投影到圆柱体上,再把圆柱体展开,这就是一幅标准纬线为零度(即赤道)的“墨卡托投影”绘制出的世界地图。
由于赤道半径为6378137米,则赤道周长为20037508.3427892米,因此X轴的取值范围:[-20037508.3427892,20037508.3427892]。当纬度φ接近两极,即90°时,Y值趋向于无穷。因此通常把Y轴的取值范围也限定在[-20037508.3427892,20037508.3427892]之间。因此在墨卡托投影坐标系(米)下的坐标范围是:最小为(-20037508.3427892, -20037508.3427892 )到最大坐标为(20037508.3427892, 20037508.3427892)。
2. 地理坐标系
地理经度的取值范围是[-180,180],纬度不可能到达90°,通过纬度取值范围为[20037508.3427892,20037508.3427892],反计算可得到纬度值为85.05112877980659。因此纬度取值范围是[-85.05112877980659,85.05112877980659],地理坐标系(经纬度)对应的范围是:最小地理坐标(-180,-85.05112877980659),最大地理坐标(180, 85.05112877980659)。
3. 地面分辨率
地面分辨率是以一个像素(pixel)代表的地面尺寸(米)。以微软Bing Maps为例,当Level为1时,图片大小为512*512(4个Tile),那么赤道空间分辨率为:赤道周长/512。其他纬度的空间分辨率则为 纬度圈长度/512,极端的北极则为0。Level为2时,赤道的空间分辨率为 赤道周长/1024,其他纬度为 纬度圈长度1024。很明显,Ground Resolution取决于两个参数,缩放级别Level和纬度latitude ,Level决定像素的多少,latitude决定地面距离的长短。
地面分辨率的公式为,单位:米/像素:
ground resolution =(cos(latitude * PI/180) * 2 * PI * 6378137 meters) / (256 * 2^level pixels)
最低地图放大级别(1级),地图是512 x 512像素。每下一个放大级别,地图的高度和宽度分别乘于2:2级是1024 x 1024像素,3级是2048 x 2048像素,4级是4096 x 4096像素,等等。通常而言,地图的宽度和高度可以由以下式子计算得到:
map width = map height = 256 * 2^level pixels
4. 地图瓦片分割
为了优化地图系统性能,提高地图下载和显示速度,所有地图都被分割成256 x 256像素大小的正方形小块。由于在每个放大级别下的像素数量都不一样,因此地图图片(Tile)的数量也不一样。每个tile都有一个XY坐标值,从左上角的(0, 0)至右下角的(2^level–1, 2^level–1)。例如在3级放大级别下,所有tile的坐标值范围为(0, 0)至(7, 7),如下图:
已知一个像素的XY坐标值时,我们很容易得到这个像素所在的Tile的XY坐标值:
tileX = floor(pixelX / 256) tileY = floor(pixelY / 256)
1. 地图的瓦片化分割
项目中使用的是百度地图,通过百度地图SDK的接口,可监听地图状态的变化。这里做了个控制,只有地图放大到一定的缩放层级后,才开始执行地图状态变化后的进一步处理。
这里地图状态改变后,将经纬度转化为我们需要使用的地图瓦片。
由于现在每个地图服务商都有自己的一套坐标系,所以我们需要将经纬度转成真实的GPS经纬度。百度地图的SDK只提供了真实GPS转百度坐标的,但是由于在方圆距离不是很大的区域内,百度坐标与真实坐标的偏移向量大致相等,所以我们可以通过以下方式将百度坐标转成真实坐标。
得到真实的GPS坐标后,划分地图瓦片的规则就跟用的什么地图没有什么关系了,这里我用了微软Bing Maps的一套计算规则,通过TileSystem类,计算地图瓦片,这里我设置默认的缩放层级为12,瓦片Tile的大小为256X256,可以计算出瓦片所在圆的半径,使用实际的值分析了下(因为经纬度的不同,分割出来的瓦片实际大小会不同,但是在距离相近的区域内,这个偏差也可以忽略),大多都是6000米不到,所以我这里直接使用6000米作为请求的距离半径,也避免出现请求的盲区。TileSystem详细的计算方法会在结尾给出。
计算完后,发消息通知执行后续的请求操作。
这里使用的一个请求队列RequestsQueue以及一个请求线程SquareVideoTileLoader,单独负责地图数据的请求。执行drawTile(tile),是为了将瓦片中心点在地图上显示出来,测试瓦片计算是否正确,实际项目中则不需要执行drawTile(tile)。下图为计算出来的地图瓦片中心点截图。
瓦片请求线程管理的核心代码见下图,将要请求的瓦片tile传入线程类中加入队列,如果线程处于wait状态,则唤醒线程,开始工作。
请求线程的run方法的代码见下图,在请求队列为空,或者地图界面不可时,挂起线程,减少CPU资源的消耗。请求返回的数据,通过注册的监听回调,返回请求数据。
onTileLoadCompleted中,处理请求返回的数据,保存已经完成请求的瓦片tile,下次不再请求该瓦片;使用ArrayMap,数据ID为key,保存请求下来的数据,过滤相邻瓦片所在的圆的相交区域的重复数据。
至此,我们就完成了瓦片请求的全部流程了。
再看下图中地图上的加载效果,先载入了地图左上方区域的数据,再载入了中间区域的数据(这里受限于平台数据不平均分布,无法更好的展示拖动地图后,加载地图新区域内的数据的效果)。
目前大部分地图app,都是使用瓦片的方式进行地图的加载,这也说明,瓦片化加载的优势,可以分块加载显示,而不是需要等全部的数据下载完后再显示。这样,用户等待的时间就少了很多,有效提高了用户体验。
本文介绍的只是一种原始数据不以瓦片形式保存的情况下,实现瓦片化请求的实现方案。在一定程度上能提高用户体验,但缺点也很明显,就是可能会请求返回重复的数据。虽然可以缩小瓦片区域范围,以减少重复数据的数量,但这样又大大提高了请求的次数,所以需要根据项目实际的数据分布,制定一个合适的瓦片大小。
注:本文参考了博文GIS理论(墨卡托投影、地理坐标系、地面分辨率、地图比例尺、Bing Maps Tile System)里的一些内容,在此感谢博主。
转载请声明原文地址:http://blog.csdn.net/sagittarius1988/article/details/50002449