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*inch4centimeter/dpi。其中scale是地圖比例尺,inch4centimeter為英寸轉厘米的參數,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軸的個數。同時我們還求得了將瓦片畫在畫布上時所需要的參數,左上角偏移像素。
參考地址:http://www.it165.net/pro/html/201408/19307.html