(三)WebGIS前端地图显示之根据地理范围换算出瓦片行列号的原理(核心)

https://www.cnblogs.com/naaoveGIS/p/3899821.htmlhtml

 

(三)WebGIS前端地图显示之根据地理范围换算出瓦片行列号的原理(核心)

 

文章版权由做者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/前端

1.前言

在上一节中咱们知道了屏幕上一像素等于实际中多少单位长度(米或经纬度)的换算方法,而知道这个原理后,接下来咱们要怎么用它呢?它和咱们前端显示地图有什么关联呢?这一节,我会尽可能详细的将这两个问题一一回答。说一个题外话,这一系列的文章我都会少给代码,多画流程图或者UML图来跟你们交流,一来便于没有不少GIS和编程基础的人读懂,二来使你们不局限于某种代码的实现而更关注于原理。算法

2.影像金字塔简介

咱们以前反复提到了影像金字塔这个概念,可是没有对其作一个大概的介绍,这里我将这个概念补充一下。编程

2.1 为何要出现影像金字塔这个概念

如今,我假设咱们的服务器上有一个1G的影像,须要将其在前端进行显示。咱们传统的作法就是首先将服务器中的1G影像下载到前端,而后浏览器加载渲染出图。可是你们想一想,首先客户端下载1G的影像须要的时间必定是个漫长的过程,其次浏览器加载这么大的文件也多半会致使其崩溃。而最重要的一个问题是,咱们的需求仅仅是浏览全图中的某一个区域下的某几个级别,如今却将全图下载完毕了,而这一样还致使了数据的不安全性(下载到本地),同时咱们的每一次放大和缩小以及拖拽都将会使浏览器花上足够长的时间去渲染。canvas

可见,传统的方式是不符合实际需求的。到后来,又有了新的解决方法,好比arcgis的IMS版本中提出了动态出图的概念。也就是当前端发出的请求里包含了须要显示的范围、显示窗口的大小等参数后,后台动态的在原始数据中切出一个符合需求的瓦片,而后将这个数据返回给前台,而且在服务器中对这个瓦片作缓存。浏览器

可是,这个方法前端出图依旧很慢,而且使地图服务器的压力过大。终于,咱们的影像金字塔解决方案出现了。缓存

2.2原理

影像金字塔就是,咱们首先将原始影像按照用户的需求,好比用户须要显示多少种比例尺下的数据,须要显示的是原始影像中的哪一个区域的数据,将原始影像按照这些需求进行划分和提取。如图:安全

 

最低层就是咱们提取和划分出的比例尺最小的一级的瓦片,而最上层的则是比例尺最大的一级的瓦片。咱们仔细观察能够发现这样的一个规律:比例尺越小的级别瓦片数据越少,反之则越大。而这个规律致使的结果就是:比例尺越小的级别切图的速度越快,同时,一样大小的瓦片所包含的影像范围越多。服务器

当咱们创建好了影像金子塔后,前端再请求地图时,则将只是在切好的瓦片缓存中,找到对应级别里对应的瓦片便可。而后在前端将这些请求到的瓦片拼接出来,即可以获得用户须要的级别下的可视范围内的瓦片了。函数

3.瓦片行列号的换算原理

3.1 为何要换算瓦片行列号

上一节中我给出了影像图切成离散型图后文件的组织形式,其中给你们展现了在这种切图下,文件的组织实际上是按照瓦片的级别、行、列号来组织的。事实上,紧凑型瓦片(Bundle)的组织样式也是如此,只是它在获得了行列号后还要进行一系列换算,好比读取索引文件找到文件中的偏移量等,这个换算方式我在之后的章节跟你们来讨论。而且,标准的WMS请求中也涉及到行列号的换算,WMS请求中有一个Bbox的参数,而这个参数也与行列号的换算有关系。而标准的WMTS请求中,TILEMATRIX、TILEROW、TILECOL这三个参数表明的就是瓦片的级别、行、列号。

因而可知,不论是针对哪一种离线或在线的地图的瓦片请求中,获得瓦片的level、col、row是请求可以实现的核心。

3.2瓦片行列号换算原理

下面,咱们先给出瓦片行列号换算的公式。

假设,地图切图的原点是(x0,y0),地图的瓦片大小是tileSize,地图屏幕上1像素表明的实际距离是resolution。计算坐标点(x,y)所在的瓦片的行列号的公式是:

col  = floor((x0 - x)/( tileSize*resolution))

row = floor((y0 - y)/( tileSize*resolution))

这个公式应该不难理解,简单点说就是,首先算出一个瓦片所包含的实际长度是多少LtileSize,而后再算出此时屏幕上的地理坐标点离瓦片切图的起始点间的实际距离LrealSize,而后用实际距离除以一个瓦片的实际长度,便可得此时的瓦片行列号:LrealSize/LtileSize。

3.3 resolution的换算原理

如我在上一节《地图比例尺换算原理》中描述的,当系统是经纬度系统时,此resolution能够直接使用切图文档中的resolution。若是系统是平面坐标系统时,此resolution的算法是:

resolution=scale*inch2centimeter/dpi。其中scale是地图比例尺,inch2centimeter为英寸转厘米的参数,dpi为1英寸所包含的像素。

4.实际系统中的运用状况

如今我把实际的运用中的需求总结以下:

(1)获得画布的高度和宽度以及此时须要显示的地图的几何范围

(2)获得画布的高度和宽度以及此时须要显示的地图的几何范围,同时也获得了须要显示的地图的级别

最后,咱们须要获得在这两种需求下的瓦片行列号范围。

5.换算流程

5.1 流程图

针对在第3节中提到的两种需求,咱们进行了不一样的换算过程,这里我首先给出流程图:

 

5.2 详细讲解

如下步骤中涉及到一些公共变量,为了便于描述,我这里用英文表明一些参数。

originX,originY:地图切图时的切图原点坐标。

tileSize:瓦片的屏幕像素大小。

Level:地图级别。

resolution:某地图级别下屏幕一像素表明的实际单位大小。

canvasWidth、canvasHeight:屏幕的长宽

geoMaxX、geoMinX:地理范围中的最大即最小X坐标。

5.2.1第一步,得到请求地理范围中的中心点(centerGeoPoint)

这个换算比较简单,可是为何咱们要首先换算这个中心点呢。缘由是咱们最后须要的真实地理范围,并不必定是屏幕范围所对应的那个地理范围,它极有多是大于这个屏幕地理范围的。而事实上是,它必定是大于的,在后面咱们讲解瓦片图层类的设计时,会提到一个地理范围缓冲宽度,那时候你们就更能明白为何是要首先获取地理范围中的中心点了。

5.2.2 第二步,判断请求中是否包含了须要显示的地图级别,分别处理

5.2.2.1 包含了Level

若是请求中已经指定了使用的Level,则咱们接下来能够直接使用此Level来进行地图实际请求范围的换算。

5.2.2.2 没有包含Level

而当请求中无Level时,咱们的换算将会比较复杂一些,这个换算的目的就是求出此时的地图应该以什么Level显示是最合适的,即nearestLevel。它的过程是,首先根据请求中的地理范围和屏幕大小范围,求得此时咱们本须要的瓦片实际大小,即:(geoMaxX-geoMinX)/( canvasWidth/tileSize),也就是用实际地理长度除以此时的瓦片个数,从而获得了咱们请求中本需求的瓦片实际大小。

可是,目前咱们不能保证咱们所切的图中是必定有这个需求里的比例尺的。因而咱们还须要作一个遍历,遍历咱们的地图中全部的比例尺,找出一个与此需求比例尺下的瓦片实际大小最贴近的真实瓦片实际大小,而这个瓦片实际大小所对应的此时的地图比例尺,便是咱们求获得的最合适的比例尺,它所表明的地图级别就是最贴近需求的地图级别,nearestLevel。

5.2.3 第三步,算出屏幕范围所对应的地理范围 (minX、minY、maxX、maxY)

在第一步中获得了centerGeoPoint,第二步获得了Level的条件下,这一步就很简单了。

首先获得Level下的一像素表明的实际大小,即resolution。而后用centerGeoPoint加上或减去半个屏幕长度(canvasBounds)乘以resolution后获得的范围即是需求中的屏幕范围在得到的Level下应该对应的实际地理范围。

以屏幕左上角X所对应的实际地理坐标为例:

minX =centerGeoPoint - (resolution* canvasWidth)/2;

这里顺便提一下,算出的这个屏幕范围所对应的地理范围,它的做用是很是大的,在之后的屏幕坐标转换成地理坐标,以及地理坐标转换成屏幕坐标,还有偏移补偿量的换算上是相当重要的一个参数。

5.2.4 第四步,计算其余参数,好比瓦片行列的起始号以及瓦片个数

这一步为收尾工做,根据以前算出来的一系列参数来进行最后的换算。

5.2.4.1 瓦片起始行列号(fixedTileLeftTopNumX、fixedTileLeftTopNumY)

在知道了请求的地理范围后,此起始行列号的换算即是水到渠成了。不过这里仍是要稍微作个补充,咱们算出来的地理范围并不能保证真实的瓦片的起始瓦片所对应的地理坐标与地理范围的左上角地理范围重合,为此咱们应该容许地理范围的一个扩张,这个扩张多少是一个很值得推敲的地方。这里咱们默认为扩张至请求到的第一张瓦片左上角所对应的地理坐标。

公式为:

fixedTileLeftTopNumX = Math.floor((Math.abs(originX - minX))/resolution*tileSize);

fixedTileLeftTopNumY = Math.floor((Math.abs(originY - maxY))/resolution*tileSize);

5.2.4.2 实际地理范围(realMinX、realMaxY)

咱们以前只是求得了屏幕范围所对应的地理范围,而当咱们换算出这个范围所须要的瓦片后,这些算得的瓦片其所对应的地理范围并不必定是屏幕范围所对应的那个地理范围,此时咱们须要从新算出实际地理范围。

realMinX = fixedTileLeftTopNumX * curLevelClipLength + originX;

realMaxY= originY - fixedTileLeftTopNumY * curLevelClipLength;

5.2.4.3 左上角偏移像素(offSetX、offSetY)

因为地理范围中的第一张瓦片,即左上角的第一张瓦片,并不必定是彻底包含在屏幕地理范围内的,因而这里又涉及到另一对参数,左上角偏移像素。

为何要求这个参数呢,缘由是,当咱们把瓦片都请求回来后还要作一个换算,即换算出每一张瓦片的左上角坐标应该对应在图层(TIleCanvas)上的哪个屏幕坐标。这个偏移像素即是为了这个换算而作的准备。

offSetX = ((realMinX- minX )/resolution);

offSetY = ((maxY - realMaxY )/resolution);

再次补充,其中resolution表示的是此Level下的一像素所表明的实际单位大小。

5.2.4.4 X、Y轴上的瓦片个数(mapXClipNum、mapYClipNum)

这里我先给出一个屏幕地理范围与实际请求出的瓦片地理范围间关系的示意图:

 

 在前面我已经诉说了,咱们求得的屏幕地理范围内的瓦片所表明的瓦片个数基本上是会比屏幕范围自己是要大的。其实这个缘由不难理解,由于瓦片是地图表示的最小单位了,其不可能再划分,因此在咱们请求瓦片的起始行列号时,用到了Math.floor这个函数,即求得离屏幕范围的左上角坐标最近的瓦片行列号。可是,在求得X、Y轴上的瓦片个数时,咱们得用到Math.ceil这个函数,这是为了能求得离屏幕范围的右下角坐标最近的瓦片行列号数。

具体公式是:

mapXClipNum = Math.ceil((canvasWidth + Math.abs(offSetX))/tileSize);

mapYClipNum = Math.ceil((canvasHeight + Math.abs(offSetY))/tileSize);

6.总结

根据上面步骤,咱们最后能够求出瓦片的行列号,以及须要的在X、Y轴的个数。同时咱们还求得了将瓦片画在画布上时所须要的参数,左上角偏移像素。

这一节相信你们都会看的很累,由于这一节流程太多,公式太多,可是也正由于如此,这一节是咱们介绍前端显示地图的系列中最重要的一节了,但愿你们能和我一块儿将这个原理好好的揣摩与推敲。下一节里我将写的类容相对比较轻松了,主要介绍的会是咱们算出了行列号后,如何使用它。我将对几种常见在线地图和离线地图的请求方式作一个介绍和总结。欢迎你们持续关注。

今天是七夕,祝节日快乐。对和我同样本身过节的人,写句话和你们共勉:

在缺乏思念的旅途

你是小船一只,大桥一座。

相关文章
相关标签/搜索