最近由于在工作中涉及到了海量圖形渲染的問題,因此我開始研究相關的解決方案。在咨詢了許多朋友之后發現矢量切片似乎是行業內最常用的一種解決方案,于是我便開始研究它該如何使用。
一、什么是矢量切片
矢量切片按照我的理解就是用柵格切片的方式把矢量數據也切成金字塔,只不過切割的不是柵格圖片,而是矢量數據的描述性文件。
矢量切片的特點
因此矢量切片它就兼具矢量數據與柵格數據的優點,比如說:
- 矢量瓦片相比于柵格圖片更加靈活,可以直接訪問矢量要素,因為矢量數據是以要素為單位的,而柵格數據它就是一個圖片那就難以直接訪問到具體的要素了。
- 可直接在客戶端獲取請求指定地物的信息,無須再次請求服務器。因為空間數據和屬性數據一起被請求到客戶端了,無需再次請求了。
- 樣式可改變和定制。因為我們拿到的只是矢量數據,因此就可以在客戶端自由的給它設置樣式。
- 相比于原始矢量數據,矢量瓦片更小巧,進行了重新編碼并切分,在被請求時可以只返回請求區域和相應級別的數據。這個優點也就是我想使用它的理由,我期望矢量切片可以幫助我解決海量圖形渲染的問題。
- 數據更新快,甚至可以說是實時的,當數據庫中的空間數據變化后,再次請求的數據是更新后的數據。
二、發布矢量切片服務
如何發布矢量瓦片服務,具體要看你使用的是什么WebGIS服務器,不同的WebGIS服務器操作的步驟可能都不一樣,需要專門去查閱相關資料。我在實驗的過程中是使用GeoServer來發布矢量切片的。
GeoServer中發布矢量切片
1.下載Vector Tiles 插件
GeoServer中無法直接實現矢量切片,需要下載對應的插件。
插件在官網中就可以下載(Download - GeoServer),但是注意要到自己所安裝的對應geoserver版本的下載頁面中去下載插件,如果下載了其它版本下的插件,可能會在啟動geoserver時導致報錯。
由于我安裝的是2.21.5版本的geoserver,因此就進入這個版本的下載頁面。
然后下滑到下面的 Extensions 部分就可以找到對應的插件。
2.安裝Vector Tiles插件
將下載的插件解壓之后得到如下的這些文件,復制其中的.jar
文件(jar包)。
將復制好的文件放到geoserver的 WEB-INF/lib
目錄下,這個目錄中都是各種.jar
文件,比較好辨認。
3.檢查Vector Tiles插件是否安裝成功
安裝插件成功后重新啟動GeoServer,然后打開GeoServer的圖層頁面
然后隨便選擇一個矢量圖層
然后進入Tile Caching頁面
滾動頁面到 "Tile Image Formats" 部分,除了標準的GIF/PNG/JPEG格式之外,如果你好看到以下的內容就表示插件安裝成功了。
4.發布矢量切片
發布矢量切片的過程也很簡單,首先將自己準備的數據創建為一個矢量圖層(具體的步驟我就不多說了)。
這里我就隨便選擇了一個GeoServer中的美國人口的矢量面圖
點擊進入“圖層編輯”頁面,然后再進入“Tile Caching”選項卡
滾動頁面到 "Tile Image Formats" 部分,勾選application/json;type=geojson
、application/json;type=topojson
和application/vnd.mapbox-vector-tile
這幾個選項(注意:這三個選項對應了三種不同格式的矢量切片數據,我們每勾選一種就會GeoServer就會制作發布一種對應格式的矢量切片)
最后點擊保存,一個帶有矢量切片的圖層就準備好了。
三、OpenLayers中加載矢量切片
1.加載TMS服務的矢量切片
網上絕大多數的文章中都是加載的TMS服務的矢量切片,這種加載方式看起來還是比較簡單的,但是我在實際使用的時候卻發現“困難重重”實際上用起來比較麻煩。
獲取TMS服務地址
首先第一步我們要搞清楚我們所發布的矢量瓦片的TMS服務的url地址是什么。可以打開Goeserver去查看。
點擊左上角的logo進入首頁
在首頁點擊TMS的鏈接
進入的這個頁面中就記錄了目前我的這臺GeoServer服務器上發布的所有TMS服務的url。
找到之前設置了矢量切片的圖層的鏈接(例如我下面的這個美國人口的圖層)
這其中以geojson
、topojson
和pbf
結尾的url就是矢量切片的url。
然后就可以拿到一個類似于http://localhost:8080/geoserver/gwc/service/tms/1.0.0/topp%3Astates@EPSG%3A4326@geojson
這樣的url。這個url由幾個部分組成:
http://localhost:8080/geoserver/gwc/service/tms/1.0.0
這部分在同一個GeoServer服務器上是固定的。/topp%3Astates
這部分表示圖層名,其中的%3A
是冒號的URL編碼形式,所以我這里的圖層就叫做topp:states
@EPSG%3A4326
這部分表示投影,我這使用的是 EPSG:4326@geojson
這部分表示數據的格式
一個完整的TMS的url還不止上面的這些,它應該還要包括切片的具體信息,例如:http://localhost:8080/geoserver/gwc/service/tms/1.0.0/topp%3Astates@EPSG%3A4326@geojson/3/2/1.geojson
后面的/3/2/1
分別代表切片的層級、列號和行號。
加載TMS矢量切片的方式
OpenLayers中加載矢量切片都需要使用VectorTile
圖層 + VectorTile
數據源的方式來實現。
創建VectorTile
圖層時可以通過style
屬性給請求到的矢量瓦片數據設置樣式。
創建VectorTile
數據源時則主要是要設置三個屬性:
url
,即矢量瓦片服務的地址,由于OpenLayers中不支持直接請求TMS服務,所以需要通過請求XYZ服務的方式請求TMS,因此url要寫成"http://localhost:8080/geoserver/gwc/service/tms/1.0.0/BeiJiang%3Abj@EPSG%3A4326@geojson/{z}/{x}/{-y}.geojson"
的形式,也就是用占位符{z}
、{x}
、{y}
來表示切片的層級、列號和行號。但是這里有一點特殊的地方在于使用的是{-y}
,之所以這樣寫是因為XYZ瓦片的y坐標從頂部開始向下遞增,TMS瓦片的y坐標從底部開始向上遞增,也就是說他們的y軸方向相反,所以url中要寫成{-y}
進行轉換。format
,這個屬性是用來將矢量數據轉換為Feature的,如果矢量切片的數據類型是geojson就使用GeoJSON
轉換器,如果矢量切片的數據類型是pbf
就使用MVT
轉換器(這些轉換器都是OpenLayers封裝好的的都在ol/format
目錄下面)。tileGrid
,這個屬性應該用來定義切片規則的,具體怎么定義的我也搞不懂。這里由于我們使用的是加載XYZ服務的方式,所以可以直接使用OpenLayers內置的createXYZ
函數來直接創建適合XYZ服務的tileGrid。但是在使用createXYZ
函數時特別要注意,其中的extent
屬性默認為 EPSG:3857 投影的范圍,如果你像我一樣使用的是其它的投影(例如 EPSG:4326),那就必需要手動將這個屬性設置為你所使用的投影的范圍,否則請求的url將會404(不要問我是怎么知道的😭)
import { VectorTile as VectorTileLayer } from "ol/layer";
import { VectorTile as VectorTileSource } from "ol/source";
import { Style, Stroke, Circle as CircleStyle } from "ol/style";
import { GeoJSON} from "ol/format";
import { createXYZ } from "ol/tilegrid";// 加載矢量切片
const riverVectorTileLayer = new VectorTileLayer({source: new VectorTileSource({url: "http://localhost:8080/geoserver/gwc/service/tms/1.0.0/BeiJiang%3Abj@EPSG%3A4326@geojson/{z}/{x}/{-y}.geojson",format: new GeoJSON(),tileGrid: new createXYZ({extent: getProjection("EPSG:4326").getExtent(),maxZoom: 18,}),}),style: function (feature) {const style = new Style({stroke: new Stroke({color: "blue",width: 1,}),});return style;},
});
map.addLayer(riverVectorTileLayer);
但是我使用上面的這套代碼進行加載卻失敗了,請求到的數據都是空的,這個就很奇怪,我看到很多的博客中都是這么寫的,他們都能加載出來,我卻加載不出來 ╭(╯^╰)╮。
之后,我歷經了千辛萬苦終于找到了原因出在哪里,我發現只要將層級z
減一就能夠請求到正確的矢量切片數據,這就說明XYZ的層級跟TMS的層級好像是不匹配的,有可能XYZ切片的層級是從1開始計數的,而TMS切片的層級是從0開始計數的。
想要將z減一,就需要使用tileUrlFunction
屬性,這個屬性接收一個函數,然后XYZ切片的坐標就會作為參數傳入到函數中,之后就可以根據XYZ切片的坐標來換算出TMS切片的坐標。
要換算的地方有兩個,一是將z減一這個比較簡單,二是要將y進行轉換(因為它們的y軸是相反的)。
# 進行y軸轉換的公式
y_tms = 最大行號 - y_xyz
# 因為
最大行號 = 總行數 - 1
# 所以
y_tms = 總行數 - 1 - y_xyz
# 又因為
總行數 = 2 ^ z_tms # 2的z次方
z_tms = z_xyz - 1
# 所以最終的公式為
y_tms = (2 ^ (z_xyz - 1)) - 1 - y_xyz
import { VectorTile as VectorTileLayer } from "ol/layer";
import { VectorTile as VectorTileSource } from "ol/source";
import { Style, Stroke, Circle as CircleStyle } from "ol/style";
import { GeoJSON} from "ol/format";
import { createXYZ } from "ol/tilegrid";// 加載矢量切片
const riverVectorTileLayer = new VectorTileLayer({source: new VectorTileSource({tileUrlFunction: function (tileCoord) {const [z, x, y] = tileCoord;const url ="http://localhost:8080/geoserver/gwc/service/tms/1.0.0/BeiJiang%3Abj@EPSG%3A4326@geojson/" +(z - 1) +"/" +x +"/" +(Math.pow(2, z - 1) - 1 - y) +".geojson";return url;},format: new GeoJSON(),tileGrid: new createXYZ({extent: getProjection("EPSG:4326").getExtent(),maxZoom: 18,}),}),style: function (feature) {const style = new Style({stroke: new Stroke({color: "blue",width: 1,}),});return style;},
});
map.addLayer(riverVectorTileLayer);
通過上面的方式我最終才將我自己的矢量切片加載出來了
2.加載WMTS服務的矢量切片
之后我又看到了另一種加載方式,通過WMTS進行加載。WMTS加載起來那就比TMS要復雜很多了,但是其實是有一條“捷徑”的。
在GeoServer中,進入切片圖層頁面。
選擇一個帶有矢量切片的矢量圖層,在預覽的下拉菜單中選擇帶有geojson
、topojson
或pbf
的選項就可以預覽矢量切片
然后F12打開調試工具查看頁面的源代碼,在demo文件夾中就可以找到通過WMTS加載矢量切片的代碼,直接照抄就可以了。
// 通過WMTS加載矢量切片
function addRiver_vectorTileWMTS() {const baseUrl = "http://localhost:8080/geoserver/gwc/service/wmts";const layerName = "BeiJiang:bj";const style = "";const gridsetName = "EPSG:4326";const format = "application/json;type=geojson";const resolutions = [0.703125, 0.3515625, 0.17578125, 0.087890625, 0.0439453125, 0.02197265625,0.010986328125, 0.0054931640625, 0.00274658203125, 0.001373291015625,6.866455078125e-4, 3.4332275390625e-4, 1.71661376953125e-4,8.58306884765625e-5, 4.291534423828125e-5, 2.1457672119140625e-5,1.0728836059570312e-5, 5.364418029785156e-6, 2.682209014892578e-6,1.341104507446289e-6, 6.705522537231445e-7, 3.3527612686157227e-7,];const params = {REQUEST: "GetTile",SERVICE: "WMTS",VERSION: "1.0.0",LAYER: layerName,STYLE: style,TILEMATRIX: gridsetName + ":{z}",TILEMATRIXSET: gridsetName,FORMAT: format,TILECOL: "{x}",TILEROW: "{y}",};let url = baseUrl + "?";for (var param in params) {url = url + param + "=" + params[param] + "&";}url = url.slice(0, -1);riverVectorTileLayer = new VectorTileLayer({source: new VectorTileSource({url,format: new GeoJSON(),projection: "EPSG:4326",tileGrid: new WMTSTileGrid({extent: [-180, -90, 180, 90],resolutions: resolutions,matrixIds: ["0","1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19","20","21",],}),}),style: function (feature) {const style = new Style({stroke: new Stroke({color: "blue",width: 1,}),});return style;},});map.addLayer(riverVectorTileLayer);
}
參考資料
- 矢量切片(Vector tile)_a vectortile source can only be rendered if it has-CSDN博客
- Openlayer加載geoserver發布的矢量切片_openlayers 加載geoserver xyz-CSDN博客
- QGIS加載Geoserver發布的矢量瓦片服務 - 槑孒 - 博客園
- 10openlayers加載矢量瓦片圖層-CSDN博客
- GeoServer官方教程:矢量切片
- OpenLayers教程十:多源數據加載之瓦片地圖原理二
- 【webgis】地圖切片|矢量地圖切片|柵格地圖切片-CSDN博客