說明:本月的主要工作都是圍繞制作矢量切片這一個核心問題進行的,所以2月的主題就以這個問題為主,目前分支出來的一些內容主要包括了TMS(Tile map service),OpenLayers3中的Projection和Resolution以及proj4js在OpenLayers3中的應用,這些在這篇文章之后會繼續展開,作為本月的番外內容。
一、GIS數據與OGC標准地圖服務
本節主要是介紹一些基礎的數據概念以及基本的WebGIS地圖服務,如對這些內容已經熟知,可直接跳過本節。
1)GIS中的矢量與柵格數據
熟悉GIS的人應該都知道,在GIS中的數據分類有很多種方式,其中最常用的一種是根據數據組織結構方式的不同而分類成矢量數據和柵格數據的兩種類型。其中柵格數據以二維矩陣的形式來表示地理空間信息的數據結構,其中數據的最小存在單元是以像素的形式存在,可以理解為和圖片的組織結構類似,以分辨率等特征作為精度的定義標准。
柵格數據 矢量數據
而矢量數據則是試圖利用點、線、面等幾何要素來表現這個世界,其數據結構緊湊精准,數據圖形質量好,有利於地理信息檢索與網絡傳輸等。其中矢量數據的最小單元是以點的形式存在,點構成線,線組成面,面構造出體。所以,我個人看來矢量數據應該更貼近於信息的精准分析與計算,而柵格數據則偏重於信息的表達(主要受制於當前圖像處理技術的瓶頸)。
2)OGC地圖服務
在WebGIS中,訪問數據是通過訪問服務器端的數據庫來獲取數據,鑒於GIS數據的特殊性,在這一套標准的請求響應模型中引入了一套基於OGC標准的地圖服務標准,在這里我只介紹幾個與本文有關的服務(如果你需要了解一個完整的內容,在這里可以找到很多資料:http://blog.csdn.net/wildboy2001/article/details/7743350):
- WMS(Web Map Service)
Web地圖服務,利用地理空間信息的數據輸出地圖,地圖本身只是一張圖片,其中包括了圖片的寬高、坐標系統、圖片格式以及渲染方式,也正是因為本身的簡潔性,讀取傳輸速度都比較快,要高於WFS
//WMS請求實例
http://localhost:8080/geoserver/szdata/wms?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&FORMAT=image/png&TRANSPARENT=true
&LAYERS=szdata:DLZXX_2011_PL_10000_3857&SRS=EPSG:4326&STYLES=&WIDTH=1347&HEIGHT=336
&BBOX=113.68754425048829,22.5346435546875,114.61245574951172,22.7653564453125
- WFS(Web Feature Service)
Web要素服務,請求獲取要素,最小單元是以要素的形式存在的,用戶可以通過與——請求獲得的矢量數據——在前端渲染繪制的幾何圖形進行交互,從而達到對矢量要素的控制。
//WFS請求實例
http://localhost:8080/geoserver/wfs?service=WFS&request=GetFeature&version=1.1.0&outputFormat=application/json
&typename=szdata:DLZXX_2011_PL_10000_new3857
- TMS(Tile Map Service)
(詳細內容見番外篇)
所以,為了帶來更好更快的用戶體驗,目前許多主流WebGIS應用都采用了柵格切片技術,通過緩存切片的形式使得地圖數據的瀏覽體驗更順暢。打開你的瀏覽器,F12出控制台,進入任意一家地圖供應商提供的地圖應用,你會發現大部分的地圖數據都是以切片的形式請求獲得的,我在這里就不舉例了。柵格地圖的切片應用是很廣泛,可在我們的日常工作中遇到的需求往往要比這些功能需求較淺的商業性地圖復雜,有的時候用戶甚至會提出需要地圖配色的編輯修改功能這樣的需求,這是商業主流地圖所達不到的,因為柵格切片在完成切圖之后,你所能控制的最小單位是一張圖片,失去了對圖片上地理信息的交互能力。
總結來看,柵格切片存在以下的幾個缺點:
- 地圖數據一次渲染,無法修改
- 無交互能力
那可否用WFS來替代呢?直接用WFS請求獲取矢量數據,這樣不就獲得了交互能力嗎?當然,如果在你的應用中矢量數據量不大的情況下,這樣做也是可行的,但是當一旦數據量大了起來,前端對於數據的請求和響應處理渲染會提高客戶端的硬件門檻,而頻繁的交互操作也會對服務器產生壓力。
直接加載的矢量數據與對柵格地圖進行切片這兩種方式看起來好像有些互補,如果能將這二者結合起來的話應該會很美好: 矢量+切片=矢量切片。
二、矢量切片
1)什么是矢量切片?
和柵格切片一樣的思路,以金字塔的方式切割矢量數據,只不過切割的不是柵格圖片,而是矢量數據的描述性文件,目前矢量切片主要有以下三種格式:GeoJSON,TopoJSON和MapbBox Vector Tile(MVT)。
柵格切圖后文件存儲形式
矢量切圖后文件存儲形式
切片中的數據結構
從上面的兩張圖可以看出,其實思路是一致的,因此,矢量切圖結合了矢量數據與柵格切圖的優勢互補:
- 前端緩存切片,提高地圖使用的體驗
- 粒度上來看,矢量切圖繼承了矢量數據的特性,以要素為單位進行管理,加強了細節上的把控能力
- 在保證體驗的前提下,為用戶提供地圖數據樣式動態修改的功能,加強了地圖定制化的程度
- 數據的實時性
2)如何生成矢量切片?
目前就博主所知道的矢量切片生成方式共有以下幾種:1)ArcGIS 系列產品:利用ArcGIS Pro生成矢量切片,然后發布在ArcGIS Online上;2)Mapbox,目前已經提出了一套開放的矢量切片標准,並被多個開源團隊所接受,但具體的產品使用我並不熟悉,所以還請自行搜索資料,如果后期有接觸到這方面,我會繼續補充;3)GeoServer,在2.11beta版中出現了對矢量切片的支持,主要依賴於開源插件geoserver-2.11-SNAPSHOT-vectortiles-plugin以及內嵌的GeoWebcahce完成切片工作。4)自己編寫切片工具,這主要針對於對這一套體系非常熟悉的GIS編程人員,我本人也屬於入門級別,所以在此也沒法提供更多詳細的內容。本文主要采用的切片方式就是利用第3種方法,利用GeoServer作為GIS應用服務器生成矢量切片:
2-1)切片前需要確認的環境配置:
a) JAVA環境:GeoServer是一套基於JAVA環境下的開源項目,因此需要配置好JAVA環境,博主采用的是JAVA1.8,因為最新版的GeoServer文檔中提出需要JAVA_8,低配版本(JAVA7)我也試過,但沒有成功,所以為了穩妥起見,我推薦你使用JAVA8,特別要注意的是,GeoServer當前不支持JAVA9。
b) Tomcat:Web容器,沒有限制,按自己的技術方向選擇。
c) GeoServer 2.11以及其插件geoserver-2.11-SNAPSHOT-vectortiles-plugin
d) 矢量數據:我在此使用的是 深圳道路【EPSG:4326】的矢量數據,請特別注意括號中的坐標系,在后面我會具體講到相關的問題。
2-2)切片過程
a) 首先,我們將下載好的GeoServer2.11的war包直接放入/%TomcatHome%/WebAPP的文件夾中,啟動Tomcat完畢后,訪問以下鏈接: http://localhost:8080/geoserver, 如果出現以下頁面則說明部署成功。
b) 然后,我們將下載好的插件geoserver-2.11-SNAPSHOT-vectortiles-plugin 進行解壓,然后將其直接copy到tomcat中部署的GeoServer文件夾的WEB-INF的lib文件夾下,重啟tomcat;
c) 啟動完成后,訪問GeoServer主頁中的數據菜單中的數據存儲,在頁面中選擇添加新的數據存儲,這些步驟和發布普通數據的步驟一致,在此就不贅述了。
但是在發布過程中有一個編輯圖層的步驟需要注意,在進入編輯圖層的頁面之后,記得點擊選中Tile caching選項卡,你會發現Tile Image Foramt屬性中多出了幾種數據格式,這就說明你的插件生效了。
d) 按照你的需求選中需要切片的格式,如果第一次切的話,我建議可以用GeoJSON作為入門,因為GeoJSON格式的數據可讀性較強,能夠給我們一個比較直觀的認識。
e) 從左邊菜單欄點擊進入Tile caching的Tile Layer子項,在Tile Layer列表中你可以看到已經被你發布成功的Tile Layer,通過preview的選擇下拉框中的選項你可以發現,之前發布時被選中的格式都出現了,這就說明你的切片數據已經發布成功了!
3)調用矢量切片
調用矢量切片的方式有很多種,OpenLayers3,Leaflet等等都可以達到目的(不過聽說好像LeafLet目前僅支持WGS84投影坐標系的矢量切片),因此在這里使用的調用請求工具是OpenLayers3,具體代碼如下:
var projection4326 = new ol.proj.Projection({
code: 'EPSG:4326',
units: 'degrees',
});
var defaultView = new ol.View({
projection: projection4326,
center: [114.15, 22.65],
//new ol.proj.fromLonLat([114.15, 22.65]),
zoom: 11
});
function loadVectorTile(){
//參數設置:圖層名稱 / 投影坐標系 / 初始化樣式
var layerName = 'szdata:DLZXX_2011_PL_10000_4326'; var layerProjection = '4326'; var initStyle = new ol.style.Style({ stroke: new ol.style.Stroke({ color: 'rgb(163,204,25)', width: 5 }) }); //矢量切片圖層 var vectorTile = new ol.layer.VectorTile({ title:"深圳道路-VectorTile", style: initStyle, projection: projection4326,
//矢量切片數據 source: new ol.source.VectorTile({
projeciton: projection4326, format: new ol.format.GeoJSON(), tileGrid: ol.tilegrid.createXYZ({
extent: ol.proj.get('EPSG:4326').getExtent(),
maxZoom: 22 }),
tilePixelRatio:1,
//發出獲取切片的請求
tileUrlFunction: function(tileCoord){
return '/geoserver/gwc/service/tms/1.0.0/' +layerName+'@EPSG%3A'+layerProjection+'@geojson/'+(tileCoord[0]-1) + '/'+tileCoord[1] + '/' + (Math.pow(2,tileCoord[0]-1)+tileCoord[2]) + '.geojson';
}
})
});
//構造Map對象
//你需要在頁面中提供一個id='map'的div
map = new ol.Map({
target: 'map',
layers: [
new ol.layer.Tile({
source: new ol.source.OSM();
}),
],
view:defaultView,
controls:[
new ol.control.ScaleLine(),
new ol.control.ZoomSlider(),
new ol.control.LayerSwitcher(),
new ol.control.OverviewMap(),
new ol.control.Zoom()
],
});
map.addLayer(vectorTile);
}
//千萬別忘記這是一個獨立的JS文件,你需要將這一段JS代碼以Onload的方式加載到頁面中
上述代碼中,在vectorSource的屬性tileUrlFunction中,我對coordinate(一個存儲了XYZ參數的數組)進行了一些簡單的處理,從而使EPSG:4326請求下的XYZ能夠與切圖結果相匹配,有一些拼湊的嫌疑,導致了這段代碼並不具有普適性,所以,請讀者根據自己的情況進行調整,但整個大體思路基本一致,主要是切片行列號的匹配方面需要額外注意。
最后切片頁面的效果如下(關於樣式的問題,在OL3中你可以通過設置Layer的Style屬性進行修改樣式,和WFS請求獲取的矢量數據處理方式一致,這也就實現了在完成切片緩存的前提下,得到了地圖要素樣式的控制權。):
矢量切片效果圖
控制台請求響應過程
4)遇到的一些問題
4-1)我想檢查一下我的切片數據,在哪里能夠找到呢?
首先,你需要了解的是,完成切片工作的其實是GeoWebCache,而GeoWebCache的數據存儲文件的默認路徑一般為:
C:\Users\%YOUR-PC-NAME%\AppData\Local\Temp\geowebcache
你可以在這里面看到各個圖層的切片數據,當你打開你的目標圖層文件夾時,如果你發現是空的,千萬不要着急,因為GeoWebCache中切片的生成本來就是一個動態的過程,
若實在是不放心,你可以通過進入GeoServer的管理頁面-->左側菜單欄中的Tile caching-->子菜單Tile Layers-->圖層列表中選擇一個Preview的方式進行預覽,然后在預覽界面進行縮放,你會發現剛才的文件夾里多了很多數據文件,這下你就可以放心的進行下面的調用工作了。但有一點需要提到的是:GeoServer目前還不提供矢量切片的預覽,你通過查看預覽頁面的源碼就能發現,當前預覽功能中的代碼還是采用的OpenLayers2的老代碼,所以當你選擇預覽vector tile時,只能看到一個個的紅叉。
4-2)我的切片數據調用不出來怎么辦?
在確認了你的數據是沒有問題之后,采用OpenLayers3代碼進行調用,但仍有可能會出現資源地址不存在(即404錯誤)或者請求響應都成功了可是矢量切片的圖層仍未加載到地圖上。這些問題都和一個很關鍵的屬性有關系,就是我上面提到的Projection——投影坐標系。在OpenLayers3 中有這么一些類是需要你提供投影參數的:
ol.View / ol.layer.Vector / ol.source.Vector
a)404資源不存在:最有可能的原因就是,你在在創建vectorTileSource對象時,url中設置的請求參數Projection與你數據視圖的Projection不一致,舉個例子,如果你發出的請求是以900913(Web-mercator)為切圖投影,而你發布的數據所采用的投影是4326(WGS 84):
'http://localhost:8080/geoserver/gwc/service/tms/1.0.0/shenzhenDL_4326@EPSG:900913@geojson/{z}/{x}/{-y}.geojson'
導致了切出來的矢量切片的行列號是在EPSG:900913下產生的,由於EPSG:4326和EPSG:900913之間的不匹配,導致了你用4326的行列號參數XY去請求實際生成的900913的行列號切片,導致了無法准確的訪問資源。當然還有一個需要提到的小細節:在創建vectorTileSource對象時,有一個非常重要的屬性——tileGrid【ol.tilegrid】,這是用來創建切片的,如果你所切的圖層資源的投影坐標系不是默認的EPSG:3857(也就是EPSG:900913),你需要額外的為你的tilegrid限制一個范圍Extent,如上述代碼中所寫道的一樣。因為在OpenLayers3中的源碼中可以看出(ol.source.XYZ),所有的默認的投影坐標都是EPSG:3857,所以當你使用其他坐標系的數據時,需要將默認的參數全部按照你的投影坐標系進行重新聲明,而OL3中也提供了一系列的Projection的轉換方法。
b)請求響應都沒有問題,但是圖層並未加載出來:其實這個問題我暫時還沒有完全解決,我的初步設想是因為我在GeoServer上發布數據時,原始數據的Projection並未被識別出來,導致了最終圖層的無法加載,這可能與投影坐標的轉換有關系,在后續我將繼續完成這個問題的探索。
4-3) 如果我發布的數據采用的坐標系既不是EPSG:4326(WGS84),又不是EPSG:900913(Web Mercator),而是其他坐標系該怎么辦呢?
之所以把這個問題列出來是因為在GeoServer中,默認的Tile grid切片格網只定義了4326和900913這兩種類型,如果你需要更多其他投影坐標系下的切片,你需要在Geoserver的Tile Caching選項卡下的子選項tile grid中定義屬於自己的切片格網,當然,OL3中也沒有太多的投影可供你使用,與之相對應的,你需要利用Proj4js去定義你所需要的投影坐標系,然后在你的OL3代碼中調用即可,具體關於Proj4js的內容,我會單列一篇文章進行介紹:矢量切片(Vector tile)番外一:Proj4js
5)總結
在這篇博客中,主要是簡單地介紹了關於矢量切片的制作與使用方法,沒有深入的去探索矢量切片的實現原理與算法,如果以后有時間,我會接着這篇博客繼續學習關於切片算法以及實現過程的相關內容。
其實第一次接觸矢量切片的時候,我並沒有太多的直觀認識,而網絡上的資料也十分有限,能夠找到的博客也是寥寥無幾,一來是因為GIS開發較少,二來矢量切片的技術還處於一個初步發展的階段,基本上都是在開源上使用,ArcGIS系列產品雖有涉及,但也處於一個探索的過程中,所以我寫這篇博客既是為了總結之前的學習,也是為了讓更多的人能夠少走一些彎路。
以下附上我在學習過程中找到的比較有價值的資料:
矢量切片的制作過程:http://blog.csdn.net/qingyafan/article/details/53367204,這個博客主要生產的內容是關於WebGIS這一塊的,應該是為數不多的GIS技術博主,並保持着一定的更新頻率,若你對WebGIS有興趣的話可以follow一下。
切片數據的初步研究: http://www.cnblogs.com/naaoveGIS/p/4982549.html,這個博客比較全面,GIS算法、數據庫、中間件、Web以及開源都有涉及,風格也比較接地氣,不過有些內容比較深入,不僅僅是停留在應用層面,所以讀這個博客需要認真細致的去思考。