從底層談WebGIS 原理設計與實現(三):WebGIS前端地圖顯示之根據地理範圍換算出瓦片行列號的原理(核心)

從底層談WebGIS 原理設計與實現(三):WebGIS前端地圖顯示之根據地理範圍換算出瓦片行列號的原理(核心)

做者: naaoveGI…    文章來源: http://www.cnblogs.com/naaoveGIS/    點擊數:3068    更新時間:2014-9-12
摘要:在上一節中咱們知道了屏幕上一像素等於實際中多少單位長度(米或經緯度)的換算方法,而知道這個原理後,接下來咱們要怎麼用它呢?它和咱們前端顯示地圖有什麼關聯呢?這一節,我會盡可能詳細的將這兩個問題一一回答。說一個題外話,這一系列的文章我都會少給代碼,多畫流程圖或者UML圖來跟你們交流,一來便於沒有不少GIS和編程基礎的人讀懂,二來使你們不侷限於某種代碼的實現而更關注於原理。

1.前言

在上一節中咱們知道了屏幕上一像素等於實際中多少單位長度(米或經緯度)的換算方法,而知道這個原理後,接下來咱們要怎麼用它呢?它和咱們前端顯示地圖有什麼關聯呢?這一節,我會盡可能詳細的將這兩個問題一一回答。說一個題外話,這一系列的文章我都會少給代碼,多畫流程圖或者UML圖來跟你們交流,一來便於沒有不少GIS和編程基礎的人讀懂,二來使你們不侷限於某種代碼的實現而更關注於原理。前端

2.影像金字塔簡介

咱們以前反覆提到了影像金字塔這個概念,可是沒有對其作一個大概的介紹,這裏我將這個概念補充一下。算法

2.1 爲何要出現影像金字塔這個概念

如今,我假設咱們的服務器上有一個1G的影像,須要將其在前端進行顯示。咱們傳統的作法就是首先將服務器中的1G影像下載到前端,而後瀏覽器加載渲染出圖。可是你們想一想,首先客戶端下載1G的影像須要的時間必定是個漫長的過程,其次瀏覽器加載這麼大的文件也多半會致使其崩潰。而最重要的一個問題是,咱們的需求僅僅是瀏覽全圖中的某一個區域下的某幾個級別,如今卻將全圖下載完畢了,而這一樣還致使了數據的不安全性(下載到本地),同時咱們的每一次放大和縮小以及拖拽都將會使瀏覽器花上足夠長的時間去渲染。編程

可見,傳統的方式是不符合實際需求的。到後來,又有了新的解決方法,好比arcgis的IMS版本中提出了動態出圖的概念。也就是當前端發出的請求裏包含了須要顯示的範圍、顯示窗口的大小等參數後,後臺動態的在原始數據中切出一個符合需求的瓦片,而後將這個數據返回給前臺,而且在服務器中對這個瓦片作緩存。canvas

可是,這個方法前端出圖依舊很慢,而且使地圖服務器的壓力過大。終於,咱們的影像金字塔解決方案出現了。瀏覽器

2.2原理

影像金字塔就是,咱們首先將原始影像按照用戶的需求,好比用戶須要顯示多少種比例尺下的數據,須要顯示的是原始影像中的哪一個區域的數據,將原始影像按照這些需求進行劃分和提取。如圖:緩存

 

最低層就是咱們提取和劃分出的比例尺最小的一級的瓦片,而最上層的則是比例尺最大的一級的瓦片。咱們仔細觀察能夠發現這樣的一個規律:比例尺越小的級別瓦片數據越少,反之則越大。而這個規律致使的結果就是:比例尺越小的級別切圖的速度越快,同時,一樣大小的瓦片所包含的影像範圍越多。安全

當咱們創建好了影像金子塔後,前端再請求地圖時,則將只是在切好的瓦片緩存中,找到對應級別裏對應的瓦片便可。而後在前端將這些請求到的瓦片拼接出來,即可以獲得用戶須要的級別下的可視範圍內的瓦片了。服務器

3.瓦片行列號的換算原理

3.1 爲何要換算瓦片行列號

上一節中我給出了影像圖切成離散型圖後文件的組織形式,其中給你們展現了在這種切圖下,文件的組織實際上是按照瓦片的級別、行、列號來組織的。事實上,緊湊型瓦片(Bundle)的組織樣式也是如此,只是它在獲得了行列號後還要進行一系列換算,好比讀取索引文件找到文件中的偏移量等,這個換算方式我在之後的章節跟你們來討論。而且,標準的WMS請求中也涉及到行列號的換算,WMS請求中有一個Bbox的參數,而這個參數也與行列號的換算有關係。而標準的WMTS請求中,TILEMATRIX、TILEROW、TILECOL這三個參數表明的就是瓦片的級別、行、列號。函數

因而可知,不論是針對哪一種離線或在線的地圖的瓦片請求中,獲得瓦片的level、col、row是請求可以實現的核心。.net

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軸的個數。同時咱們還求得了將瓦片畫在畫布上時所須要的參數,左上角偏移像素。

這一節相信你們都會看的很累,由於這一節流程太多,公式太多,可是也正由於如此,這一節是咱們介紹前端顯示地圖的系列中最重要的一節了,但願你們能和我一塊兒將這個原理好好的揣摩與推敲。下一節裏我將寫的類容相對比較輕鬆了,主要介紹的會是咱們算出了行列號後,如何使用它。我將對幾種常見在線地圖和離線地圖的請求方式作一個介紹和總結。歡迎你們持續關注。

相關文章
相關標籤/搜索