本文介紹了矢量金字塔模型的用途、基本構成和快速入門等內容。
模型用途
簡介
矢量金字塔模型是為了大規??臻g幾何數據(千萬級以上)快速顯示而設計的一種結構。矢量金字塔可以對空間幾何數據創建稀疏索引、按規則對密集區域預處理、輸出標準的mvt-pbf格式。通過Ganos提供的矢量金字塔功能,可實現億條空間幾何記錄,分鐘級預處理,終端秒級顯示的效果。
Ganos Geometry Pyramid是對象關系型數據庫PostgreSQL兼容版本(PolarDB PostgreSQL版(兼容Oracle))的一個時空引擎擴展,Geometry Pyramid(矢量金字塔)通過對一個包含Geometry屬性列的表格構建矢量金字塔,用戶可以快速查詢得到地圖不同層級的矢量瓦片并用于前端繪制查看。和傳統的地圖切片方案相比,Ganos 的矢量金字塔具有“快”和“省”的優勢:
“快”是指構建效率高,實測在一臺配置普通的8核PolarDB PostgreSQL版(兼容Oracle)集群上,僅需6分鐘即可完成構建七千萬房屋面數據的矢量金字塔。
“省”是指存儲開銷小,矢量金字塔忽略數據量稀疏的區域,僅存儲包含數據量較多的矢量瓦片,有效地節省了存儲成本。
功能概述
矢量金字塔的使用分為創建、查詢、更新,以及刪除四個部分。
創建:使用ST_BuildPyramid函數或ST_BuildPyramidUseGeomSideLen函數對包含Geometry對象的表格創建矢量金字塔,要求已經有Geometry列的空間索引。
查詢:使用ST_Tile函數或ST_AsPNG函數查詢由參數指定的地圖瓦片,其中ST_Tile返回的是MVT格式的矢量瓦片,ST_AsPNG返回的是渲染成PNG格式的圖片。
刪除:使用ST_DeletePyramid函數刪除已有的矢量金字塔。
更新:使用ST_UpdatePyramid函數對數據表發生更新的矢量金字塔進行更新。
功能詳情請參見Geometry Pyramid SQL參考。
主要業務場景
Geometry對象可以表示現實世界的空間數據實體,如道路、建筑物、POI 等??梢暬疓eometry對象能夠展示不同空間數據實體在地圖上的位置分布,更加直觀地向用戶展示空間數據包含的信息。矢量金字塔可用于任意將數據保存為Geometry對象且需要高效的數據可視化的應用場景。下面分別從可視化點、線、面三種不同類型的Geometry數據來舉例說明矢量金字塔的實際應用。
可視化POI、軌跡點
在地圖或移動對象應用場景中,需要處理的數據(POI或者軌跡點)主要是以Geometry Point的形式保存在數據庫中。通過在數據集上預建矢量金字塔,用戶可快速查看POI或者軌跡點在不同區域的密集程度,以此確定不同區域的繁忙程度,效果圖如下所示:
可視化道路、航道線
在出行應用場景中,道路和航運公司的航道線主要是以Geometry Linestring的形式保存在數據庫中。通過矢量金字塔,用戶可以快速查看不同道路和航道線路的分布情況,從而更好地規劃出行路線,效果圖如下所示:
可視化房屋、河流、林地
在城市規劃場景中,房屋、河流和林地這些數據常以Geometry Polygon的形式保存在數據庫中。矢量金字塔可以讓用戶快速查看城市或更大范圍內的建筑、河流等分布情況,助力用戶做出決策,效果圖如下所示:
基本構成
概念
矢量切片
矢量切片是指把待顯示的數據的矢量信息寫入名為矢量瓦片的載體上,寫入的信息包括矢量的類型(點、線或者面)、組成矢量的各個點在矢量瓦片上的相對坐標等。用戶的前端軟件(瀏覽器或者GIS軟件)能夠把矢量瓦片里的矢量信息提取出來,把每個矢量根據用戶自定義的顯示樣式(點或者線的顏色、面的填充色等)繪制出來。通俗的理解是,矢量瓦片告訴前端軟件應該給用戶看哪些東西,然后前端軟件根據用戶指定的繪畫風格一筆一畫地畫出來。由于現在的硬件發展,用戶端軟件能夠高效率地完成矢量瓦片的繪制,因此矢量切片因其顯示效果好的優點受到越來越多用戶的青睞。
MVT
MVT(Mapbox Vector Tile)是一套廣泛采用的用于存儲和傳輸矢量瓦片的格式,其定義了對一組矢量要素及其屬性信息的編碼方法。MVT的內部結構包含一組命名的圖層。每個圖層包含幾何要素和元數據信息,其中幾何要素部分包含幾何類型的編碼、坐標的編碼、命令的編碼(MoveTo、LineTo、ClosePath等)等,元數據信息部分以鍵值對的形式分別記錄屬性名和屬性值。主流的前端軟件都支持MVT,因此本文后面會使用MVT來代指矢量瓦片。
動態矢量瓦片
動態矢量瓦片是指數據庫在執行用戶的可視化請求時,在線完成讀取待可視化的數據和將數據打包成MVT并返回的過程。以PostGIS為例,整個過程大致分為三步:
根據矢量瓦片的空間范圍進行空間查詢,從數據表中拿到待可視化的Geometry對象。
將獲得的Geometry對象按照矢量瓦片的空間范圍進行坐標轉換,其中還涉及到對Geometry對象的簡化和過濾,這個過程由ST_AsMVTGeom函數完成。
將上一步的眾多Geometry對象按照MVT規范編碼、打包放到一個二進制結構里,這個過程由ST_AsMVT函數完成。
Ganos的快顯引擎針對上述三個步驟都提供了相關函數來改進PostGIS的可視化效率:
使用ST_IsRandomSampled函數僅讀取隨機采樣后的Geometry對象。
使用ST_AsMVTGeomEx函數過濾轉換到MVT坐標系后像素數很少的Geometry對象。
使用ST_AsMVTEx函數過濾可視化后視覺上不重要的Geometry對象。
用戶可根據需要使用其中的一個或多個函數來提升動態矢量瓦片的效率。
預切片
預切片是指離線生成MVT并將其保存起來,在用戶的可視化請求到來時返回相應的MVT給用戶。
稀疏金字塔
Ganos的矢量金字塔技術采用了稀疏金字塔的結構,將預切片和動態矢量瓦片結合起來,僅在數據密集的區域離線生成和保存MVT,而數據稀疏的區域采用動態矢量瓦片。下圖是稀疏金字塔的示意圖。只有包含兩個或以上Geometry對象的MVT會被保存起來,在查詢這些MVT時,Ganos可以直接從表中讀出對應的MVT返回給用戶。灰色的區域表示數據稀疏區域,執行可視化查詢時,Ganos會動態生成這些區域的MVT。
渲染
前端軟件接收到MVT后,需要將MVT包含的信息繪制成用戶可看的圖像,這個過程被稱為渲染。MVT的渲染通常是由前端軟件來完成的,而Ganos的矢量金字塔技術既支持發送MVT交由前端軟件渲染,也支持在數據庫端將MVT渲染成圖片后再交給前端軟件直接給用戶查看。
流程
矢量金字塔的使用流程為創建矢量金字塔和查詢矢量金字塔。如果創建金字塔后,數據表發生了更新,在查詢前需要先更新矢量金字塔。
對比
和矢量金字塔相比,動態瓦片無需事先構建任何其它結構即可使用(出于性能考慮,建議對Geometry列構建空間索引),但是在執行可視化請求時會多出讀取瓦片范圍內的Geometry數據以及在線生成MVT的開銷。另外,動態瓦片無需考慮數據更新問題,而矢量金字塔在數據更新后,需要調用ST_UpdatePyramid函數進行更新。建議用戶先使用動態瓦片確認性能是否符合需求,若性能不符合預期,可考慮使用矢量金字塔。
快速入門
簡介
快速入門文檔幫助用戶快速理解 Ganos GeomGrid 引擎的基本用法,包括擴展創建、創建金字塔、讀取瓦片數據、更新金字塔、高級功能等部分。
更多專業用法可參考Geometry Pyramid最佳實踐文章:Ganos 矢量快顯功能上手。
同時Ganos還針對動態MVT瓦片提供了增強能力,具體用法可參考最佳實踐文章:Ganos 矢量快顯功能上手系列 2:增強的 MVT 能力。
語法說明
創建擴展。
CREATE EXTENSION ganos_geometry_pyramid CASCADE;
說明建議將擴展安裝在public模式下,避免權限問題。
CREATE extension ganos_geometry_pyramid WITH schema public;
為空間表創建金字塔。
-- 為數據表 test 創建金字塔 -- 指定表 test 的要素id字段名, 必須為int4/int8類型 -- 指定表 test 的空間字段名稱, 需要先為該字段創建空間索引 -- 以JSON形式說明Geometry的坐標系為EPSG:4326 SELECT ST_BuildPyramid('test', 'geom', 'id', '{"sourceSRS":4326}');
從金字塔中讀取MVT數據。
-- 從金字塔中讀取瓦片編號為 '0_0_0' 的數據(任意編號,不管金字塔中是否存在,都可返回數據) -- 瓦片編號方式為: z_x_y,默認投影坐標系為EPSG:3857 SELECT ST_Tile('test', '0_0_0');
更新金字塔。
當數據表的數據發生更新時,需要對金字塔進行更新才能看到更新后的地圖可視化結果。用戶需要以參數形式傳入發生數據更新的空間范圍,調用ST_UpdatePyramid函數更新金字塔。
-- 在局部空間范圍內插入3條新的Geometry對象 INSERT INTO test(id, geom) VALUES (1, ST_GeomFromEWKT('SRID=4326;POINT(10.1 10.1)')); INSERT INTO test(id, geom) VALUES (2, ST_GeomFromEWKT('SRID=4326;LINESTRING(10.1 10.1,11 11)')); INSERT INTO test(id, geom) VALUES (3, ST_GeomFromEWKT('SRID=4326;POLYGON((10 10,11 11,11 12,10 10))')); -- 更新矢量金字塔,數據更新范圍由一個Box2D參數指定 SELECT ST_UpdatePyramid('test', 'geom', 'id', ST_SetSRID(ST_MakeBox2D(ST_Point(9, 9), ST_Point(12, 12)), 4326));
ST_UpdatePyramid函數適用于數據表的更新發生在一個局部空間區域內的應用場景。例如,更新區域的面積不足全局面積的百分之一,此時ST_UpdatePyramid函數能夠高效完成金字塔的更新。若數據表發生大空間范圍的數據更新,建議使用ST_BuildPyramid函數重新生成新的金字塔。
-- 插入3條分布在不同區域的Geometry對象 INSERT INTO test(id, geom) VALUES (4, ST_GeomFromEWKT('SRID=4326;POINT(-59 -45)')); INSERT INTO test(id, geom) VALUES (5, ST_GeomFromEWKT('SRID=4326;LINESTRING(110 60,115 70)')); INSERT INTO test(id, geom) VALUES (6, ST_GeomFromEWKT('SRID=4326;POLYGON((-120 59,-110 65,-110 70,-120 59))')); -- 重建金字塔,ST_BuildPyramid會自動刪除舊的金字塔 SELECT ST_BuildPyramid('test', 'geom', 'id', '{"sourceSRS":4326}');
刪除金字塔(可選)。
SELECT ST_DeletePyramid('test');
刪除擴展(可選)。
DROP EXTENSION ganos_geometry_pyramid CASCADE;
使用進階
為金字塔命名
金字塔默認和數據表同名,也可指定金字塔名稱,實現一份數據,多個金字塔的目的。
-- 為 test 表創建一個名為 hello 的金字塔
SELECT ST_BuildPyramid('test', 'geom', 'id', '{"name": "hello"}');
并行構建
指定構建矢量金字塔的并行任務數,默認為0(表示并行最大化)。
并行任務數最大不應該超過CPU數量的4倍。
并行構建使用了兩階段事務機制,需要設置
max_prepared_transactions
參數。設置數據庫參數
max_prepared_transactions = 100
(或者更高,重啟生效)。
-- 使用4個并行任務來構建矢量金字塔
SELECT ST_BuildPyramid('test', 'geom', 'id','{"parallel": 4}');
瓦片參數
指定瓦片的尺寸、外擴大小。 尺寸最大值為4096,且必須是256的整數倍。 外擴大小最大為256,最小為0。
海量數據全幅顯示,應該設置較小的tileSize,提升分塊渲染并行度和出圖體驗。
-- 指定瓦片的大小為 512,外擴大小為 8
SELECT ST_BuildPyramid('test', 'geom', 'id','{
"tileSize": 512,
"tileExtend": 8
}');
金字塔的最大層級
當地圖的zoom層級大于某級時,就不再使用這個圖層、或者不需要生成金字塔,可指定金字塔的最大層級。 如不設置,矢量金字塔會根據數據的密度自動計算出合理的最大層級。最大級別的默認值為16。
-- 指定金字塔的最大層級為 12 級,超過12級則會實時讀取數據生成 mvt
SELECT ST_BuildPyramid('test', 'geom', 'id', '{"maxLevel": 12}');
分層處理
可為金字塔的每個層級設置不同的處理條件,例如顯示字段、過濾條件等。
通過添加
buildRules
規則,為每個層級設置生成條件。頂層金字塔生成耗時較長,可以通過分層規則跳過頂層的處理。
-- 分層處理生成金字塔
-- 第0層到第5層,不顯示任何數據,設置過濾條件為 "1!=1" 生成空的mvt
-- 第6層到第9層,顯示code=1的數據,并且包含"name"字段
-- 第10到第15級,無過濾條件,包含"name"、"width"兩個字段
SELECT ST_BuildPyramid('test', 'geom', 'id', '{
"buildRules":[
{
"level":[0,1,2,3,4,5],
"value": {
"filter": "1!=1"
}
},
{
"level":[6,7,8,9],
"value": {
"filter": "code=1",
"attrFields": ["name"]
}
},
{
"level":[10,11,12,13,14,15],
"value": {
"attrFields": ["name", "width"]
}
}
]
}');
提升金字塔的構建和更新效率
除了ST_BuildPyramid函數,Ganos還提供了ST_BuildPyramidUseGeomSideLen函數來構建金字塔。和ST_BuildPyramid函數相比ST_BuildPyramidUseGeomSideLen函數能夠有效提升金字塔的創建和更新效率。ST_BuildPyramidUseGeomSideLen函數的使用條件是數據表存在一個表示Geometry屬性的x軸或y軸跨度的較大值的列,且為該列創建了一個索引。
-- 為表格'test'增加一列'geom_side_len',表示'geom'屬性的x軸或y軸跨度的較大值
ALTER TABLE test
ADD COLUMN geom_side_len DOUBLE PRECISION;
CREATE OR REPLACE FUNCTION add_max_len_values() RETURNS VOID AS $$
DECLARE
t_curs CURSOR FOR
SELECT * FROM test;
t_row test%ROWTYPE;
gm GEOMETRY;
x_min DOUBLE PRECISION;
x_max DOUBLE PRECISION;
y_min DOUBLE PRECISION;
y_max DOUBLE PRECISION;
BEGIN
FOR t_row IN t_curs LOOP
SELECT t_row.geom INTO gm;
SELECT ST_XMin(gm) INTO x_min;
SELECT ST_XMax(gm) INTO x_max;
SELECT ST_YMin(gm) INTO y_min;
SELECT ST_YMax(gm) INTO y_max;
UPDATE test
SET geom_side_len = GREATEST(x_max - x_min, y_max - y_min)
WHERE CURRENT OF t_curs;
END LOOP;
END;
$$ LANGUAGE plpgsql;
SELECT add_max_len_values();
-- 為'geom_side_len'屬性構建B樹索引
CREATE INDEX ON test USING btree(geom_side_len);
-- 指定表示'geom'屬性的x軸或y軸跨度的較大值的列名為'geom_side_len'
SELECT ST_BuildPyramidUseGeomSideLen('roads', 'geom', 'geom_side_len', 'id',
'{"sourceSRS":4326}');
高級功能
數據按規則融合
可按屬性規則對瓦片范圍內的數據進行融合,減少數據量。
設置 "buildRules" -> "value" -> "merge" 字段,添加["code=1","code=2"]兩個條件表達式。
對屬性code的值為1的進行融合,連接空間上有touch關系的點,并合并成一個大的要素。
同樣過程對code值為2的進行融合。
對code值既不等于1、也不等于2的要素不采用融合操作。
-- 將 code=1、code=2 的數據,分別進行融合處理
SELECT ST_BuildPyramid('test', 'geom', 'id', '{
"buildRules":[
{
"level":[0,1,2,3,4,5],
"value": {
"merge": ["code=1","code=2"]
}
}
]
}');
SQL參考
詳細SQL手冊請參見Geometry Pyramid SQL參考。