【总结整理】WebGIS学习-thinkGIS(三):关于影像金字塔、瓦片行列号、分辨率resolution
http://www.thinkgis.cn/topic/541a5206da8db186fd0673ba
1.前言
在上一节中我们知道了屏幕上一像素等于实际中多少单位长度(米或经纬度)的换算方法,而知道这个原理后,接下来我们要怎么用它呢?它和我们前端显示地图有什么关联呢?这一节,我会尽量详细的将这两个问题一一回答。说一个题外话,这一系列的文章我都会少给代码,多画流程图或者UML图来跟大家交流,一来便于没有很多GIS和编程基础的人读懂,二来使大家不局限于某种代码的实现而更关注于原理。
2.影像金字塔简介
我们之前反复提到了影像金字塔这个概念,但是没有对其做一个大概的介绍,这里我将这个概念补充一下。
2.1 为什么要出现影像金字塔这个概念
现在,我假设我们的服务器上有一个1G的影像,需要将其在前端进行显示。我们传统的做法就是首先将服务器中的1G影像下载到前端,然后浏览器加载渲染出图。但是大家想想,首先客户端下载1G的影像需要的时间一定是个漫长的过程,其次浏览器加载这么大的文件也多半会导致其崩溃。而最重要的一个问题是,我们的需求仅仅是浏览全图中的某一个区域下的某几个级别,现在却将全图下载完毕了,而这同样还导致了数据的不安全性(下载到本地),同时我们的每一次放大和缩小以及拖拽都将会使浏览器花上足够长的时间去渲染。
可见,传统的方式是不符合实际需求的。到后来,又有了新的解决方法,比如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轴的个数。同时我们还求得了将瓦片画在画布上时所需要的参数,左上角偏移像素。
这一节相信大家都会看的很累,因为这一节流程太多,公式太多,但是也正因为如此,这一节是我们介绍前端显示地图的系列中最重要的一节了,希望大家能和我一起将这个原理好好的揣摩与推敲。下一节里我将写的类容相对比较轻松了,主要介绍的会是我们算出了行列号后,如何使用它。我将对几种常见在线地图和离线地图的请求方式做一个介绍和总结。欢迎大家持续关注。
【总结整理】WebGIS学习-thinkGIS(三):关于影像金字塔、瓦片行列号、分辨率resolution的更多相关文章
- 【总结整理】WebGIS学习-thinkGIS(二):关于level,比例尺scale,分辨率resolution
1.Level包含了一个resolution参数和一个scale参数 瓦片本身: 我们用arcgis切完图后,打开发布的服务或者打开config.xml配置文件,可以看到所切之图的相关配置.如图所示: ...
- 从底层谈WebGIS 原理设计与实现(三):WebGIS前端地图显示之根据地理范围换算出瓦片行列号的原理(转载)
从底层谈WebGIS 原理设计与实现(三):WebGIS前端地图显示之根据地理范围换算出瓦片行列号的原理 1.前言 在上一节中我们知道了屏幕上一像素等于实际中多少单位长度(米或经纬度)的换算方法, ...
- (三)WebGIS前端地图显示之根据地理范围换算出瓦片行列号的原理(核心)
文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/. 1.前言 在上一节中我们知道了屏幕上一像素等于实际中多少单位长度(米或 ...
- WebGIS前端地图显示之根据地理范围换算出瓦片行列号的原理(核心)
文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/. 1.前言 在上一节中我们知道了屏幕上一像素等于实际中多少单位长度(米或 ...
- 【总结整理】webGIS学习thinkGIS(四)WebGIS中通过行列号来换算出多种瓦片的URL 之离线地
http://www.thinkgis.cn/topic/541a5319da8db186fd06e097 1.前言 在前面我花了两个篇幅来讲解行列号的获取,也解释了为什么要获取行列号.在这一章,我将 ...
- 【总结整理】WebGIS学习-thinkGIS(地理常识):
##地图知识 ###地图定义 地图是按照一定的法则,有选择地以二维或多维形式与手段在平面或球面上表示地球(或其它星球)若干现象的图形或图像,它具有严格的数学基础.符号系统.文字注记,并能用地图概括原则 ...
- JavaWeb和WebGIS学习笔记(三)——GeoServer 发布shp数据地图
系列链接: Java web与web gis学习笔记(一)--Tomcat环境搭建 Java web与web gis学习笔记(二)--百度地图API调用 JavaWeb和WebGIS学习笔记(三)-- ...
- [Firefly引擎][学习笔记三][已完结]所需模块封装
原地址:http://www.9miao.com/question-15-54671.html 学习笔记一传送门学习笔记二传送门 学习笔记三导读: 笔记三主要就是各个模块的封装了,这里贴 ...
- Docker学习(三): Dockerfile指令介绍
特别声明: 博文主要是学习过程中的知识整理,以便之后的查阅回顾.部分内容来源于网络(如有摘录未标注请指出).内容如有差错,也欢迎指正! =============系列文章============= 1 ...
随机推荐
- js设计模式理解干货
构造函数本身就是一个函数,只不过该函数是出于创建对象的目的而定义的. 创建Object实例的两种方式: new 操作符 var person = new Object(); person.name = ...
- RK30SDK开发板驱动分析(一):platform device 的概念与注册
做过51单片机或者ARM开发的人都知道,单片机内部都有自己的“片内外设”,比如UART,比如I2C,比如SPI等等... 写单片机程序的时候,比如对于UART的驱动,我们都是在程序中直接写一套函数,来 ...
- KVM-克隆
kvm虚拟机的克隆分为两种情况,本文也就通过以下两种情况进行克隆,克隆虚拟机为Centos 6.4X64. (1) KVM主机本机虚拟机直接克隆. (2) 通过复制配置文件与磁盘文件的虚拟机复制克隆( ...
- C++string类整理
string类 string类 头文件:#include<string> 名称空间:using namespace std; 初始化: string Str; String类的构造函数和析 ...
- spring学习-5
spring表达式SpEL 语法#{..},为bean的属性进行动态赋值 通过bean的id对bean进行引用 调用方法以及引用对象中的属性 计算表达式的值 正则表达式的匹配 修改Address.ja ...
- 【剑指offer】删除链表中重复的节点,C++实现(链表)
0.简介 本文是牛客网<剑指offer>笔记. 1.题目 在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针.例如,链表1-> ...
- 【整理】C++中的unique函数
之前总结了一下我觉得有用的erase,lower_bound,upper_bound. 现在总结一下unique,unique的作用是“去掉”容器中相邻元素的重复元素(不一定要求数组有序),它会把重复 ...
- 基于spring及zookeeper的dubbo工程搭建
一.生产者搭建 新建一个maven工程,勾选Create a simple project Packaging方式选择jar包的方式. 修改pom.xml文件: <project xmlns=& ...
- mendeley 参考文献管理工具
本文由Suzzz原创,发布于http://www.cnblogs.com/Suzzz/p/4044144.html,转载请保留此声明 目录 介绍 功能 运行截图 安装方法 创建 Desktop Ent ...
- hive JDBC异常到多租户
hive jdbc执行select count(*) from test报错. return code 1 from org.apache.hadoop.hive.ql.exec.mr.MapRedT ...