WebGIS开发0基础必看教程:地图显示——根据地理范围换算出瓦片行列号

2.影像金字塔简介

我们之前反复提到了影像金字塔这个概念,但是没有对其做一个大概的介绍,这里我将这个概念补充一下。

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

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

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

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

2.2原理

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

img

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

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

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节中提到的两种需求,我们进行了不同的换算过程,这里我首先给出流程图:

img

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)

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

img

在前面我已经诉说了,我们求得的屏幕地理范围内的瓦片所代表的瓦片个数基本上是会比屏幕范围本身是要大的。其实这个原因不难理解,因为瓦片是地图表示的最小单位了,其不可能再划分,所以在我们请求瓦片的起始行列号时,用到了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轴的个数。同时我们还求得了将瓦片画在画布上时所需要的参数,左上角偏移像素。

点我免费领取GIS开发学习教程

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/429886.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2024全网最全Excel函数与公式应用

💂 个人网站:【 海拥】【神级代码资源网站】【办公神器】🤟 基于Web端打造的:👉轻量化工具创作平台💅 想寻找共同学习交流的小伙伴,请点击【全栈技术交流群】 引言 Excel是一款广泛应用于商业、教育和个人…

hnust 湖南科技大学 2022 数据挖掘课设 完整代码+报告+图源文件+指导书

hnust 湖南科技大学 2022 数据挖掘课设 完整代码报告图源文件指导书 目录 实验一 Apriori算法设计与应用 - 1 - 一、 背景介绍 - 1 - 二、 实验内容 - 1 - 三、 实验结果与分析 - 2 - 四、 小结与心得体会 - 3 - 实验二 KNN算法设计与应用 - 4 - 一、 背景介绍 - 4 - 二、 实…

头条小程序DIY源码系统 带完整的安装代码包以及搭建部署教程

在过去几年中,小程序市场经历了飞速的发展,各种小程序平台如雨后春笋般涌现。作为其中的佼佼者,头条小程序凭借其强大的用户基础和完善的生态体系,吸引了众多开发者的关注。然而,对于许多初学者和中小企业而言&#xf…

C语言qsort函数介绍

前言 学到了函数指针,那这篇博客我们可以根据函数指针,了解一个函数qsort的应用与模拟实现 欢迎关注个人主页:小张同学zkf 若有疑问 评论区见 目录 1.回调函数 2.qsort函数使用 3.qsort模拟实现 1.回调函数 讲这个东西之前我们来认识一下…

春日特惠,爱基百客限时放送,开启您的学术新篇章!

春回大地,万物复苏, 正是探索未知、启发新思的最佳时节。 在这个充满生机的季节里, 我们推出了春季大促活动, 旨在助力每一位科研工作者在新的一年里实现更多突破。 让我们一起迎接科研人的春天, 开启智慧的花朵…

基本设计模式

单例模式 ES5 function Duck1(name:string){this.namenamethis.instancenull }Duck1.prototype.getNamefunction(){console.log(this.name) }Duck1.getInstancefunction(name:string){if(!this.instance){this.instance new Duck1(name)} } const aDuck1.getInstance(a) const…

Jetpack Compose: Hello Android

Jetpack Compose 是一个现代化的工具包,用于使用声明式方法构建原生 Android UI。在本博文中,我们将深入了解一个基本的 “Hello Android” 示例,以帮助您开始使用 Jetpack Compose。我们将探讨所提供代码片段中使用的函数和注解。 入门 在…

C++ //练习 10.31 修改前一题的程序,使其只打印不重复的元素。你的程序应使用unique_copy(参见10.4.1节,第359页)。

C Primer(第5版) 练习 10.31 练习 10.31 修改前一题的程序,使其只打印不重复的元素。你的程序应使用unique_copy(参见10.4.1节,第359页)。 环境:Linux Ubuntu(云服务器&#xff09…

基于ERNIR3.0的文本多分类

还在用BERT做文本分类?分享一套基于预训练模型ERNIR3.0的文本多分类全流程实例【文本分类】_ernir 文本分类-CSDN博客 /usr/bin/python3 -m pip install --upgrade pip python3-c"import platform;print(platform.architecture()[0]);print(platform.machine…

深度学习500问——Chapter02:机器学习基础(3)

文章目录 2.10 主成分分析(PCA) 2.10.1 主成分分析(PCA)思想总结 2.10.2 图解PCA核心思想 2.10.3 PCA算法推理 2.10.4 PCA算法流程总结 2.10.5 PCA算法主要优缺点 2.10.6 降维的必要性及目的 2.10.7 KPCA与PCA的区别 2.11 模型评估…

什么是跨站脚本攻击(XSS)

厦门微思网络​​​​​​https://www.xmws.cn 华为认证\华为HCIA-Datacom\华为HCIP-Datacom\华为HCIE-Datacom Linux\RHCE\RHCE 9.0\RHCA\ Oracle OCP\CKA\K8S\ CISP\CISSP\PMP\ ​ 跨站脚本攻击(Cross-site Scripting,通常称为XSS)&#xf…

MCU设计--M3内核详解(2)

内核架构 FETCH取指单元DEC指令译码EXEC执行LSU内存取数ETM_INTF调试接口STATUS状态上报 内核-寄存器 不同指令集支持不同的寄存器分配。R13用于主堆栈指针(MSP),进程堆栈指针(PSP)R13 连接寄存器存储子程序指针,提高速度R15 程序计数器PC 剩下的一些 …

Gemma模型一些细节讲解

Gemma模型报告中提到的几个点进行代码细节解读一下: (1)Embedding层共享参数 (2)输入输出层均进行RMSNorm Embedding层共享参数 共享embedding的权重给最后的llm_head层。是词嵌入层的共享,与旋转位置编码…

羊大师揭秘羊奶探秘,不止于营养的美味饮品

羊大师揭秘羊奶探秘,不止于营养的美味饮品 羊奶作为一种古老而珍贵的乳制品,不仅具有丰富的营养价值,还拥有独特的口感和风味,使其成为一种不止于营养的美味饮品。以下是对羊奶的深入探秘: 独特的风味:羊…

【JavaEE进阶】 Linux搭建Java部署环境

文章目录 🍃前言🌴Linux权限🚩用户操作🚩三种角色🚩文件类型和访问权限🎈文件类型🎈基本权限 🚩修改文件权限 🎍搭建Java部署环境🚩apt🎈apt常用命…

【C++基础】STL容器面试题分享||上篇

🌈欢迎来到C基础专栏 🙋🏾‍♀️作者介绍:前PLA队员 目前是一名普通本科大三的软件工程专业学生 🌏IP坐标:湖北武汉 🍉 目前技术栈:C/C STL 1.请说说 STL 的基本组成部分2.详细的说&…

08 OpenCV 腐蚀和膨胀

文章目录 作用算子代码 作用 膨胀与腐蚀是数学形态学在图像处理中最基础的操作。其卷积操作非常简单,对于图像的每个像素,取其一定的邻域,计算最大值/最小值作为新图像对应像素位置的像素值。其中,取最大值就是膨胀,取最小值就是腐…

Pytorch学习 day01(Jupyter安装、常用函数、三种编辑器的对比)

Jupyter 安装过程中遇到的问题: Anaconda的base环境会自动安装Jupyter,但是如果我们要在其他环境中安装Jupyter,就需要注意,该环境的python版本不能高于3.11,且用以下代码安装: conda install nb_conda_…

【深度学习】脑部MRI图像分割

案例4:脑部MRI图像分割 相关知识点:语义分割、医学图像处理(skimage, medpy)、可视化(matplotlib) 1 任务目标 1.1 任务简介 本次案例将使用深度学习技术来完成脑部MRI(磁共振)图像分割任务&#xff0c…

java spring 03 启动细节

启动类ClassPathXmlApplicationContext public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, Nullable ApplicationContext parent)throws BeansException {super(parent);setConfigLocations(configLocations);if (refresh) {refresh();}}其中…