OpenLayers之多源数据加载二:瓦片地图原理

目录

一、瓦片地图简介

二、LOD

三、瓦片计算

3.1、切片方式

3.2、瓦片数量计算

3.3、瓦片坐标系

四、分辨率

4.1、墨卡托投影坐标系下的分辨率

4.2、OpenLayers默认使用的分辨率


一、瓦片地图简介

    瓦片地图(切片地图)源于一种大地图解决方案,针对一整块非常大的地图进行切片,分成很多相同大小的小块地图,在用户访问的时候,再一块一块小地图加载,拼接在一起,从而还原成一整块大的地图,如果要提高Web地图的访问速度,使用瓦片地图是非常有效的方法。

    具体来讲,瓦片地图就是在多个比例尺下配置地图,然后提前把每个比例尺下的地图绘制为小块图片,保存在服务器上名为缓存的目录中。这样客户端在访问地图时,可以直接获取需要的小块图片拼接成整幅地图,而不是由服务器动态创建出一幅图片再发送到客户端,从而极大提高了访问速度

    在2005年前后,随着谷歌地图微软虚拟地球(现在称为Bing地图)以及其他流行的WebGIS应用的出现,人们开始意识到,也许他们并不需要管理每一个图层所有属性(样式符号、标注与注记等)的功能。这些互联网巨头已经开始将矢量图层融合为一张栅格化的图像,这些图像被切分为256像素 x 256像素的图片及切片。这些图片预先生成并存储在磁盘上,以便快速分发到客户端。这样做可以同时支持成千上万个并发请求(异步-因为瓦片地图根据请求范围加载),而这对于动态地图绘制而言基本是不可能的

    

    如上图所示,瓦片地图采用的是金字塔模型,是一种多分辨率层次模型,从瓦片金字塔的底层到顶层,比例尺越来越小,分辨率越来越低,但表示的地理范围不变。瓦片地图通常都带有一个级别、行与列编号方案,以便将来自多个瓦片地图服务的瓦片放置到正确的位置。

    有了瓦片地图,制图人员再也不同担心性能问题了,他们可以使用他们所掌握的所有工具,来制作一张美观的Web地图。一旦创建了地图瓦片,这些瓦片就保存到了服务器上的某个文件夹中,服务器检索美观地图图片与丑的图片的速度是一样的。正由于Web服务器可以快速分发瓦片地图图像,因此我们可以使用AJAXAsynchronous JavaScript and XML,异步JavaScript和XML)编程技术来从服务器获取图片,这样当用户漫游时不会出现页面闪烁的现象

    这种变化是革命性的。一类是具有图层排序与调整符号颜色等功能,但响应非常迟缓的丑陋的地图应用;一类是没有图层控制,但具有惊人美观并且快速响应的地图应用。对于这两类WebGIS应用的选择,虽然对于一些GIS长期爱好者可能还需要停下来比较一下,但对于普通互联网用户来说根本不用思索,他们无疑会选择后者。

    在谷歌地图发布了一两年以后,商业GIS软件开始提供创建地图切片的功能,由于可以使用成熟的地图制作工具ArcMap,很多人选择使用ArcGIS Server来发布空间信息Web服务,但是其价格不菲。我国的超图SuperMap iServer是另一种商业选择。免费和开源Mapnik库也可以创建地图瓦片,但是直到最近几年才提供了将Mapnik封装的用户友好的应用程序(即TileMill)。

 

二、LOD

    LODLevels of Detail细节层级)的简写,用于根据当前的环境,渲染不同的图像,用于降低非重要内容的细节度,从而提高渲染效率,在电子游戏中经常运用,对于需要显示全球地图的GIS系统而言,更需要应用这项技术。

    在不同的LOD下,自然分辨率就可能不一样,这两者是紧密结合在一起的。对于图形显示系统而言,分辨率作为屏幕坐标和世界坐标之间计算的纽带,其作用是非常重要的(例如,屏幕上两个像素点间的距离对应的现实世界的距离是多少,这就需要通过分辨率来衡量与计算——分辨率将在这篇文章的后面进行介绍)

    在详细讲解之前,假设给你两张A4纸,在其中一张纸上把你家整个绘制上去,在另一张纸上只把你睡的房间绘制上去。如果别人想看你家,你会给哪一张纸?如果想看你睡的房间,你会给哪一张纸?相信你不会给错,LOD就是这种根据不同需要,采用不同图的技术方案。在地图应用中,最直观的体验,就是地图放大缩小。当地图放大后,能看到更详细的地理信息,比如街道、商店等等。当地图缩小再缩小,原来能看到的街道、商店就看不见了,但是能看到更大的区域。我们的屏幕就相当于是A4纸,大小不变。

    LOD这个技术方案非常棒!非常符合我们的自然习惯,所以在很多图形系统中都使用了这项技术。在GIS系统中,不断放大,就能看到更多地图细节,了解更加详细的信息。对于GIS引擎的开发者而言,需要实现这项技术,当发现用户放大地图时,就立马使用更有细节的地图图片,替换现在显示的地图图片。现在问题来了:意思是说对于同一个地点而言,需要有更多张呈现不同细节程度的图片?是的,你没有猜错。虽然在使用瓦片地图的过程中,感觉放大、缩小地图是浑然一体的,但其实就在你眼皮下发生了图片替换。不同层级使用具有不同细节的地图瓦片,这就需要为每一个层级准备图片,如果使用离线工具下载瓦片地图,会看到下载的图片是按照层级Z进行存储的。开发者不用担心数据源的处理,只需要知道这个原理就可以了。

    为了便于理解GIS系统中不同层级,使用不同的图片,下面使用google在线瓦片地图进行说明。最小层级0情况下,只用了一张256*256像素的图片表示整个地球平面:

    稍大一个层级1情况下,用了四张256*256像素的图片(各张图片中表示的信息更丰富了)来表示整个地球:

   

   

    对照一下,是否更加的明白了LOD原理及其在GIS中的应用了?

 

三、瓦片计算

    不同环境条件下,加载具有不同细节的图片资源,可以提高效率,但这并不是终点。瓦片技术可以更进一步提高效率。其原理是将一张大图片(完整地图)切割成很多张小图片(地图瓦片),按照呈现需求,加载其中的几张小图片即可

    为什么这样就能提高效率?因为屏幕显示窗口的大小是固定的,比如屏幕分辨率是800*600,或者1024*768,又或者是1920*800等等。如果屏幕分辨率是800*600,一张大图是9000*9000,那么同一时间,你只能看到这张图片的十分之一。但是在不切片的情况下,你却必须要加载整个地图。如果是在本地浏览还好,假如是发布到网络上,则网络传输和渲染,都将耗时。如果我们按照500*500大小进行切片,我们则只需要加载4500*500的小图片就可以了(就可以布满分辨率800*600的屏幕)。对于WebGIS而言,需要在网络上发布,同时需要显示整个地球,自然需要使用瓦片技术

3.1、切片方式

    如果对整个地球图片进行切片,需要考虑的是整个地球图片大小,以及切片规则切片(瓦片)大小

    对于WebGIS而言,在线地图几乎都采用墨卡托投影坐标系(Mercator),对应的地图投影到平面上就是一个正方形。为了方便使用,切片时大多按照正方形的方式来进行切片,比如切片大小为256*256(单位像素)。一个1024*1024的地图,就可以切片4张小的瓦片。同时,瓦片大小几乎都是256*256,有一些则会增加到512*512(由于以前的屏幕分辨率通常比较低,所以256*256的瓦片在低分辨率的屏幕上显示效果比较好,随着屏幕分辨率的提高,瓦片大小自然也就增加到512*512)。

    LOD会使得不同层级下的全球地图大小不一致,结合瓦片地图技术一起,就出现了金字塔瓦片结构

    在WebGIS中,上一层级的一张瓦片,在更大一层级中,会用4张瓦片来表示,依次类推,比如上一节中看到的Google在线瓦片地图的第0级和第1级的瓦片地图。这样做可以维持正方形的投影方式不变,同时按照2的幂次方放大(瓦片的边长),计算效率非常高。

3.2、瓦片数量计算

    通过上面切片的介绍,我们可以对每一层级瓦片的数量进行简单的计算:

  • 层级0的瓦片数是1=2^​0​​∗2^​0
  • 层级1的瓦片数是4=2^1*2^1
  • 层级n的瓦片数是2^n*2^n

    这个地方计算的是所有瓦片数,因为是一个正方形,所以是边长的平方。如果只计算x轴或y轴一边的瓦片数,就是2^n个。

3.3、瓦片坐标系

    任意一个层级的地图,切成多个瓦片后,我们需要给瓦片编号,才能通过编号找到瓦片。这个问题就涉及到瓦片坐标系。不同的在线地图服务商,可能定义不一样的瓦片坐标系,坐标系不一样,那么对应的同一个位置的瓦片的坐标也会不一样。需要引起重视。

    在OpenLayers提供了一个用于调试瓦片的source : ol.source.TileDebug类。可以清晰的看到每一个瓦片的坐标:

    代码如下:

<div id="map" style="width: 100%"></div>
<script type="text/javascript">
    var osmSource = new ol.source.OSM();
    var map = new ol.Map({
    layers: [
        // 加载Open Street Map地图
      new ol.layer.Tile({
        source: osmSource
      }),
      // 添加一个显示Open Street Map地图瓦片网格的图层
      new ol.layer.Tile({
        source: new ol.source.TileDebug({
          projection: 'EPSG:3857',
          tileGrid: osmSource.getTileGrid()
        })
      })
    ],
    target: 'map',
    view: new ol.View({
      center: ol.proj.transform([104, 30], 'EPSG:4326', 'EPSG:3857'),
      zoom: 10
    })
});
</script>

    首先从上图可以看到地图上多了网格,每一个网格对应的就是一个瓦片。其次网格中有三个数字,这些数字就表示当前瓦片的坐标:

  • 第一个数字是层级z
  • 第二个数字是表示经度方向上的x
  • 第三个数字是表示纬度方向上的y

    关于瓦片坐标系万能瓦片地图加载秘籍这篇文章还有其余的论述。

 

四、分辨率

    前面简单提到过,分辨率是屏幕坐标和世界坐标的纽带,通过它,才能知道你在屏幕上用鼠标点击的位置对应于世界地图具体的经纬度位置。当然你不用自己来做这个计算,OpenLayersol.Map类已提供了对应的方法getCoordinateFromPixel()来帮助你实现坐标转换。你可能经常使用这个函数,但却不知道背后是怎样的一个原理,本小节将理清其中的来龙去脉。

    上一节说到了每一个层级,会使用不同数量的瓦片来表示整个地球,那么无论是哪一个层级,所表示的实际地理空间范围都是一致的但使用的瓦片个数却是不一样的

    以Google在线地图为例,层级0使用了一个瓦片层级1使用了4瓦片。通过计算可以知道层级0的整个地球图像(瓦片)为256*256像素大小,层级1整个地球图像为512*512像素大小。层级0和层级1表示的地球范围都是一样的经度[-180, 180]纬度[-90, 90]。在层级0的时候,一个像素就表示360/256 = 1.40625这么长的经度范围,180/256 = 0.703125这么长的纬度范围。而这两个数字就是分辨率了,即一个像素所表示的现实世界的范围是多少,这个范围可能是,可能是,或者其他单位,根据具体的情况而定。

4.1、墨卡托投影坐标系下的分辨率

    我们知道,在WebGIS中使用的在线瓦片地图是采用的Web墨卡托(Mercator)投影坐标系(可以查看这篇文章-墨卡托投影-来了解详细内容),经过投影后,整个地球是一个正方形,所能表示的地球范围为:

        经度[-180, 180]纬度[-85, 85],单位为

    对应的Web墨卡托坐标系的范围为:

        x[-20037508.3427892, 20037508.3427892],范围y同样是[-20037508.3427892, 20037508.3427892],单位为

    或许,你会好奇这个范围是怎么计算而来的,如果详细了解过它的定义,应该知道墨卡托投影只是简单的把地球球面剖开拉伸为一个正方形而来的,由于南北极两端采用这种拉伸会严重变形,并且南北极在使用过程中很少用到,所以干脆就只投影了[-85, 85]纬度范围的地球。而展开时,因为纬度范围有缩减,所以肯定只能以经度来展开,即在经度-180度的地方从上到下剖开地球,然后按照赤道方向来展开成一张平面,那么这个平面的长,就等于以地球赤道半径按照圆来计算的周长。近似的按照6378137米为半径来计算,那么整个赤道周长的一半,即为:

    π∗r=3.1415926∗6378137=20037508.0009862

    以上就是Web墨卡托投影坐标系范围的完整的计算过程,墨卡托投影也有很多变形,会有细微的不同,OpenLayers默认使用的是EPSG:3857Web墨卡托投影坐标系),对于该坐标系的详细定义,可以参见epsg.io.3867

    有了范围之后,要想计算分辨率,按照上面的计算过程就非常简单了,还是以Google在线瓦片地图为例,x方向上的各层级瓦片地图分辨率计算公式可以归纳为:

    resolution = rangX / (256 * 2^level)

  • rangX    ——    表示x方向上整个范围,比如20037508.3427892 – ( – 20037508.3427892)
  • 256    ——    表示一个瓦片的宽度,单位为像素。
  • 2^level    ——    表示在层级level下,x方向上的瓦片个数。

    那么分母计算出来的结果就是在层级level下,整个地图在x方向上的宽度,单位为像素

    那么整个公式计算出来就是在x方向上屏幕上一个像素所能代表的实际地理范围,即分辨率

4.2、OpenLayers默认使用的分辨率

    OpenLayers默认设置了加载瓦片地图时采用的分辨率,通过一个示例来看一下:

    

    代码如下:

<div id="map" style="width: 100%"></div>
<div><span>当前层级:</span><span id="zoom"></span><span>分辨率:</span><span id="resolution"></span></div>
<script type="text/javascript">
    var map = new ol.Map({
        layers: [
          new ol.layer.Tile({
            source: new ol.source.OSM()
          })
        ],
        target: 'map',
        view: new ol.View({
          center: ol.proj.transform(
              [104, 30], 'EPSG:4326', 'EPSG:3857'),
          zoom: 10
        })
    });

    // 监听层级变化,输出当前层级和分辨率
    map.getView().on('change:resolution', function(){
        document.getElementById('zoom').innerHTML =  this.getZoom() + ',';
        document.getElementById('resolution').innerHTML = this.getResolution();
    })

    document.getElementById('zoom').innerHTML = map.getView().getZoom() + ',';
    document.getElementById('resolution').innerHTML = + map.getView().getResolution();
</script>

     缩放上面的地图,从层级0开始,用前面介绍的公式和当前地图显示的分辨率进行比较,你会发现OpenLayers默认采用的分辨率Google在线瓦片地图一样。

    OpenLayers瓦片地图默认分辨率表:

    注意事项

    为什么我们上面一直以Google在线瓦片地图举例说明?因为不同的在线瓦片地图可能采用不一样的分辨率,比如百度在线瓦片地图。所以在使用在线瓦片地图或者自己制作的瓦片地图时,都需要知道使用的分辨率是多少。如若不然,可能也会出现位置偏移。

 

    

转载自:https://blog.csdn.net/qq_35732147/article/details/81476542

You may also like...