本文介紹如何使用PolarDB PostgreSQL版的時空引擎GanosBase提供的資料庫快顯技術,僅需百行代碼即可實現億級海量幾何空間資料的線上快速顯示及流暢地圖互動,同時無需關注切片儲存和效率問題。
背景
一直以來,如何對時空資料庫中的億級向量空間資料進行線上可視化,始終是業介面臨的重大挑戰。由於資料體量龐大,傳統方法需要將資料庫中的資料進行基於緩衝切片的服務發布,才能實現可視化。這一過程操作流程冗長,並且涉及眾多需要考慮的問題:
在對向量資料進行預切片時,資料切片所需的時間為多久?切分多少層級為適宜?儲存瓦片所需的硬碟空間是否充足?
使用即時瓦片時,即時渲染瓦片的回應時間是否能夠得到保證?
使用向量瓦片時,小比例尺的瓦片體積可能會達到多大?資料轉送是否會成為瓶頸?前端渲染能夠承受多巨量資料量?
若需快速探索資料庫中的大規模線上資料,傳統用於“底圖服務”的離線切片生產流程幾乎無法適用,不僅耗時耗力,還無法實現線上聯機處理。
PolarDB PostgreSQL版時空引擎GanosBase提供了資料庫快顯技術,僅需百行代碼即可實現億級海量幾何空間資料的線上快速顯示和流暢地圖互動,同時無需關注切片儲存和效率問題,解決傳統離線切片存在的問題。
技術原理
GanosBase線上快顯處理的核心在於將資料庫與可視化進行了有效關聯,提供一種新的可視化索引技術——稀疏向量金字塔(Sparse Vector Pyramid,SVP)索引。SVP索引具備兩個關鍵特性:快與省。
其中,快指以下兩個階段的高效表現:
金字塔建立快:GanosBase利用空間索引對資料進行空間密集度劃分,並基於密集度建立了一種稀疏向量金字塔索引,相較於傳統切圖流程,資料計算量減少90%。同時,金字塔的建立採用了完全平行處理模式,即使是在處理1億條地類圖斑資料時,產生金字塔的時間也僅需約10分鐘。
資料展現快:GanosBase採用視覺可見度剔除演算法,基於Z-order排序,過濾掉大量不影響顯示效果的資料,從而顯著提高即時顯示的效率。GanosBase支援直接輸出PNG格式的柵格瓦片和MVT格式的向量瓦片,對1億地類圖斑資料的即時渲染顯示,其回應時間均達到秒級。
省指以下兩個維度:
節省磁碟空間:1億條地類圖斑資料產生的金字塔索引僅占原表大小的5%作為額外空間。
節省開發時間:僅需使用簡單的SQL語句,通過調整語句參數即可靈活地控制顯示效果。
使用說明
使用GanosBase的快顯引擎需要安裝外掛程式。
CREATE EXTENSION ganos_geometry_pyramid CASCADE;建立稀疏向量金字塔
假設您已建立某個向量大表並匯入資料,然後可以使用GanosBase的ST_BuildPyramid函數建立向量金字塔,加速資料顯示。詳情請參考ST_BuildPyramid。
樣本
以下SQL語句為表points的geom欄位建立向量金字塔,金字塔名稱指定為points_geom,同時將金字塔的邏輯瓦片大小設定為512。
此處指定向量金字塔的名稱及所使用的邏輯瓦片大小(該瓦片大小並非實際存在的瓦片,僅代表一種空間邏輯劃分)。
ST_BuildPyramid('points', 'geom', 'gid', '{"name":"points_geom","tileSize":512}')擷取柵格瓦片
柵格瓦片是以圖片形式呈現的瓦片(Tile),是目前使用最廣泛的地圖瓦片形式之一。GanosBase的ST_AsPng函數在資料庫端提供按需動態渲染向量資料為柵格瓦片的功能。該功能具備基本的柵格符號化能力,主要適用於一些不需要複雜符號化的輕量級情境,例如資料管理系統。詳情請參考ST_AsPng。
樣本
以下SQL語句從向量金字塔中擷取索引行列號為x=2,y=1,z=1的向量瓦片,並將該瓦片根據我們配置的樣式渲染為柵格瓦片,最終返回PNG格式的圖片。
ST_AsPng('points_geom', '1_2_1','{"point_size": 5,"line_width": 2,"line_color": "#003399FF","fill_color": "#6699CCCC","background": "#FFFFFF00"}')擷取向量瓦片
向量瓦片是一種新興的地圖瓦片技術,具備在前端配置樣式的靈活性,採用WebGL進行渲染,效果更為美觀。諸如Mapbox等地圖架構可便捷地支援此格式。GanosBase的ST_Tile函數能夠以向量瓦片的形式提供向量金字塔中的資料。詳情請參考ST_Tile。
樣本
調用ST_Tile函數只需提供標準的TMS行列號及金字塔表的名稱。以下SQL語句從向量金字塔中擷取索引行列號為x=2,y=1,z=1的向量瓦片,返回資料將採用標準的MVT格式。
ST_Tile('points_geom', '1_2_1');最佳實務
測試資料
在此準備兩份向量資料作為測試樣本。
buildings表為面資料,資料總計1.25億條,展示使用柵格瓦片的線上視覺效果。
gid|geom | ---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 1|MULTIPOLYGON(((-88.066953 34.916114 0,-88.066704 34.916114 0,-88.066704 34.91602 0,-88.066953 34.91602 0,-88.066953 34.916114 0))) 2|MULTIPOLYGON(((-87.924658 34.994797 0,-87.924791 34.99476 0,-87.924817 34.994824 0,-87.924685 34.994861 0,-87.924658 34.994797 0)))points表為點資料,資料總計10.7萬條,展示使用向量瓦片的線上視覺效果。
id|geom | --|------------------------------| 1|POINT (113.5350205 22.1851929)| 2|POINT (113.5334245 22.1829781)|
全棧架構
資料庫-快顯全棧架構由資料庫伺服器、Python服務端和使用者端三部分構成,其全棧架構示意如下圖所示。
服務端代碼
為實現代碼的簡潔性並更注重邏輯描述,本案例選用了Python作為後端語言,Web架構採用了基於Python的Flask架構,資料庫連接架構則使用了基於Python的Psycopg2(可使用pip install psycopg2安裝)。值得一提的是,單簽已實現最基礎的功能,當Web服務自身的效能遇到瓶頸,可依據不同的平台與架構進行最佳化,以獲得更優的響應效能。首先在後端建立向量金字塔,隨後實現兩個介面。其中,向量瓦片介面使用points表中的資料,而柵格瓦片介面則使用buildings表中的資料,並定義相應的樣式,供前端直接調用。為便於說明,後端代碼同時提供了向量和柵格兩個介面,實際使用時可根據需求選擇。
# -*- coding: utf-8 -*-
# @File : Vector.py
import json
from psycopg2 import pool
from threading import Semaphore
from flask import Flask, jsonify, Response, send_from_directory
import binascii
# 串連參數
CONNECTION = "dbname=<database_name> user=<user_name> password=<user_password> host=<host> port=<port>"
class ReallyThreadedConnectionPool(pool.ThreadedConnectionPool):
"""
面向多線程的串連池,提高地圖瓦片類高並發情境的響應。
"""
def __init__(self, minconn, maxconn, *args, **kwargs):
self._semaphore = Semaphore(maxconn)
super().__init__(minconn, maxconn, *args, **kwargs)
def getconn(self, *args, **kwargs):
self._semaphore.acquire()
return super().getconn(*args, **kwargs)
def putconn(self, *args, **kwargs):
super().putconn(*args, **kwargs)
self._semaphore.release()
class VectorViewer:
def __init__(self, connect, table_name, column_name, fid):
self.table_name = table_name
self.column_name = column_name
# 建立一個串連池
self.connect = ReallyThreadedConnectionPool(5, 10, connect)
# 約定金字塔表名
self.pyramid_table = f"{self.table_name}_{self.column_name}"
self.fid = fid
self.tileSize = 512
# self._build_pyramid()
def _build_pyramid(self):
"""建立金字塔"""
config = {
"name": self.pyramid_table,
"tileSize": self.tileSize
}
sql = f"select st_BuildPyramid('{self.table_name}','{self.column_name}','{self.fid}','{json.dumps(config)}')"
self.poll_query(sql)
def poll_query(self, query: str):
pg_connection = self.connect.getconn()
pg_cursor = pg_connection.cursor()
pg_cursor.execute(query)
record = pg_cursor.fetchone()
pg_connection.commit()
pg_cursor.close()
self.connect.putconn(pg_connection)
if record is not None:
return record[0]
class PngViewer(VectorViewer):
def get_png(self, x, y, z):
# 預設參數
config = {
"point_size": 5,
"line_width": 2,
"line_color": "#003399FF",
"fill_color": "#6699CCCC",
"background": "#FFFFFF00"
}
# 在使用psycpg2時,將位元據以16進位字串的形式傳回效率更高
sql = f"select encode(st_aspng('{self.pyramid_table}','{z}_{x}_{y}','{json.dumps(config)}'),'hex')"
result = self.poll_query(sql)
# 只有在使用16進位字串的形式傳回時才需要將其轉換回來
result = binascii.a2b_hex(result)
return result
class MvtViewer(VectorViewer):
def get_mvt(self, x, y, z):
# 在使用psycpg2時,將位元據以16進位字串的形式傳回效率更高
sql = f"select encode(st_tile('{self.pyramid_table}','{z}_{x}_{y}'),'hex')"
result = self.poll_query(sql)
# 只有在使用16進位字串的形式傳回時才需要將其轉換回來
result = binascii.a2b_hex(result)
return result
app = Flask(__name__)
@app.route('/vector')
def vector_demo():
return send_from_directory("./", "Vector.html")
# 定義表名,欄位名稱等
pngViewer = PngViewer(CONNECTION, 'usbf', 'geom', 'gid')
@app.route('/vector/png/<int:z>/<int:x>/<int:y>')
def vector_png(z, x, y):
png = pngViewer.get_png(x, y, z)
return Response(
response=png,
mimetype="image/png"
)
mvtViewer = MvtViewer(CONNECTION, 'points', 'geom', 'gid')
@app.route('/vector/mvt/<int:z>/<int:x>/<int:y>')
def vector_mvt(z, x, y):
mvt = mvtViewer.get_mvt(x, y, z)
return Response(
response=mvt,
mimetype="application/vnd.mapbox-vector-tile"
)
if __name__ == "__main__":
app.run(port=5000, threaded=True)
將上述代碼儲存為Vector.py檔案,並通過執行python Vector.py命令即可啟動服務。 從代碼中可以推斷出,無論使用何種程式設計語言或架構,只需將向量或柵格瓦片的SQL語句封裝為介面,即可實現完全相同的功能。
相較於發布傳統地圖服務,利用GanosBase的向量金字塔功能實現線上可視化是一種更為輕量且實用的選擇。
針對柵格瓦片,能夠通過修改代碼實現樣式控制,從而顯著增強靈活性。
無需引入第三方組件,也無需進行專門的最佳化,即可實現令人滿意的響應效能。
支援自由選擇其熟悉的程式設計語言和架構,無需進行複雜的專業參數配置,對於非地理從業者更加友好。
使用者端代碼
本案例選擇Mapbox作為前端地圖架構,以展示後端提供的向量瓦片層和柵格瓦片層,並為向量瓦片層配置相應的渲染參數。為便於說明,前端代碼同時添加了向量圖層和柵格圖層,實際使用時可以根據需要進行選擇。在後端代碼的同一檔案目錄下,建立一個名為Vector.html的檔案,並寫入以下代碼。在後端服務啟動後,就可以通過http://localhost:5000/vector訪問。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title></title>
<link
href="https://cdn.bootcdn.net/ajax/libs/mapbox-gl/1.13.0/mapbox-gl.min.css"
rel="stylesheet"
/>
</head>
<script src="https://cdn.bootcdn.net/ajax/libs/mapbox-gl/1.13.0/mapbox-gl.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.0/axios.min.js"></script>
<body>
<div id="map" style="height: 100vh" />
<script>
const sources = {
osm: {
type: "raster",
tiles: ["https://b.tile.openstreetmap.org/{z}/{x}/{y}.png"],
tileSize: 256,
},
};
const layers = [
{
id: "base_map",
type: "raster",
source: "osm",
layout: { visibility: "visible" },
},
];
const map = new mapboxgl.Map({
container: "map",
style: { version: 8, layers, sources },
});
map.on("load", async () => {
map.resize();
// 添加柵格瓦片資料來源
map.addSource("png_source", {
type: "raster",
minzoom: 1,
tiles: [`${window.location.href}/png/{z}/{x}/{y}`],
tileSize: 512,
});
// 添加柵格瓦片圖層
map.addLayer({
id: "png_layer",
type: "raster",
layout: { visibility: "visible" },
source: "png_source",
});
// 添加向量瓦片資料來源
map.addSource("mvt_source", {
type: "vector",
minzoom: 1,
tiles: [`${window.location.href}/mvt/{z}/{x}/{y}`],
tileSize: 512,
});
// 添加向量瓦片圖層,並為向量瓦片添加樣式
map.addLayer({
id: "mvt_layer",
paint: {
"circle-radius": 4,
"circle-color": "#6699CC",
"circle-stroke-width": 2,
"circle-opacity": 0.8,
"circle-stroke-color": "#ffffff",
"circle-stroke-opacity": 0.9,
},
type: "circle",
source: "mvt_source",
"source-layer": "points_geom",
});
});
</script>
</body>
</html>效果展示
向量瓦片動態效果

您可在前端調節不同效果。調整新的圖層參數後,效果如下:
{
"circle-radius": 4,
"circle-color": "#000000",
"circle-stroke-width": 2,
"circle-opacity": 0.3,
"circle-stroke-color": "#003399",
"circle-stroke-opacity": 0.9,
}
柵格瓦片動態效果

與pgAdmin整合
PostgreSQL資料庫管理工具pgAdmin原生支援向量資料的可視化,但由於缺乏快速渲染技術,僅能實現單個對象顯示或有限結果集的展示,無法對大規模向量資料進行流暢的全域瀏覽。將GanosBase的向量快顯功能與pgAdmin進行整合,實現在資料入庫後線上瀏覽全域,快速評估資料概況,從而顯著提升資料管理體驗。
總結
基於稀疏向量金字塔的原理與優勢,可以利用GanosBase實現線上可視化服務的各種功能,最終通過百行代碼實現可以應對億級資料的地圖可視化服務。在可視化基礎上,還可以進一步利用雲原生資料庫PolarDBGanosBase的伺服器端快速查詢和分析能力進行對象屬性查詢、空間圈選、空間分析等更複雜功能。這就是GanosBase所帶來的大規模空間圖形顯示加速技術——稀疏向量金字塔索引所引發的變革。如果您對此有興趣,請參考使用簡介擷取更多資訊。您可以前往PolarDB叢集購買頁面開通PolarDB服務。