全部產品
Search
文件中心

PolarDB:GanosBase向量快顯功能上手系列二:增強MVT能力

更新時間:Jul 02, 2025

GanosBase新增的2D向量動態切片函數能夠大幅提升可視化效率,有效解決小比例尺MVT顯示耗時久的問題。與PostGIS相比,小比例尺MVT的可視化效率提升可達60%以上。本文主要介紹2D向量動態切片函數及其使用方法。

背景

傳統地圖可視化服務為提高載入速度,將待顯示資料進行分層緩衝,即事先將同一份資料對應每個層級的顯示映像進行多次儲存。在使用時,根據當前瀏覽介面的層級動態擷取相應層級的映像,並將其返回給用戶端以完成可視化。這種傳統方法被稱為柵格切片,其優點在於可視化效率高且對顯示端的硬體要求較低。然而,其缺點包括預切片時間長、儲存開銷大和更新不夠友好。此外,用戶端顯示效果相對呆板,只能看到預先切好的圖片樣式所呈現的資料。對於層級間的縮放則是放大或縮小當前層的圖片,直接切換到相鄰層的圖片,導致層級間的過渡生硬。

近年來,隨著硬體技術的不斷髮展以及對顯示效果要求的提升,動態向量切片技術愈發受到歡迎。首先,向量切片是指將待顯示資料的向量資訊記錄到一種稱為向量瓦片的載體上。所記錄的資訊包括向量的類型(點、線或面)以及組成該向量的各個點在向量瓦片上的相對座標等。用戶端軟體(包括瀏覽器或GIS軟體)能夠提取向量瓦片中的向量資訊,並根據自訂的顯示樣式(如點或線的顏色、面的填充色等)繪製每個向量。和柵格切片的共同點在於,都需要根據當前用戶端軟體的顯示層級擷取同層級的瓦片。不同之處在於,柵格切片的瓦片為樣式固定的位元影像圖片,而向量瓦片則保留了各個資料的向量資訊儲存結構。通俗地講,柵格切片是將拍攝的照片直接展示給用戶端,而向量切片則是向前端軟體指明應展示的內容,並根據自訂繪畫風格逐筆逐畫地進行渲染。隨著硬體技術的發展,用戶端軟體也能夠高效地完成向量瓦片的繪製。MVT(Mapbox Vector Tile)是一種廣泛應用於儲存和傳輸向量瓦片的標準格式。主流前端軟體均支援MVT,本文後續將以MVT指代向量瓦片。動態向量切片是指後端系統根據用戶端軟體當前的顯示層級和範圍,動態產生一系列MVT(地圖向量瓦片),並將其返回給前端進行可視化展示。

向量快顯

GanosBase提供向量動態切片函數以實現向量快顯,可顯著提高可視化效率,同時有效解決了小比例尺MVT顯示耗時較長的問題。與PostGIS相比,小比例尺MVT的可視化效率提升可達60%以上。下圖展示了GanosBase的ST_AsMVTEx函數與PostGIS的ST_AsMVT的效果對比。

3

GanosBase提供了以下三個2D向量動態切片函數:

  • ST_AsMVTGeomEx函數:為PostGIS的ST_AsMVTGeom的增強,用於將向量資料從地理空間座標系轉換為MVT的像素座標系。

  • ST_AsMVTEx函數:為PostGIS的ST_AsMVT的增強,用於將一系列經過座標系轉換的向量資料的資訊彙總並寫入MVT。

  • ST_IsRandomSampled函數:允許按任意百分比對資料進行隨機採樣,從而便於快速查看超巨量資料集的大致顯示樣式。

說明

ST_AsMVTGeomEx和ST_AsMVTEx函數捨棄對顯示效果影響相對較小的向量資料,僅保留對視覺效果更為重要的向量資料。這一策略顯著減少小比例尺MVT的尺寸,降低網路傳輸開銷和用戶端軟體的渲染負擔,從而有效提升顯示效率。

ST_AsMVTGeomEx

與PostGIS的ST_AsMVTGeom相比,ST_AsMVTGeomEx函數新增res_prec參數,允許使用者使用該參數更大程度地過濾對顯示效果影響小的向量要素,從而減小前後端處理的負擔以及網路開銷,提高可視化效率,其餘參數和ST_AsMVTGeom保持一致。詳細的文法介紹請參考ST_AsMVTGeomEx

說明

ST_AsMVTGeomEx函數對點資料無效。

以下ST_AsMVTGeomEx函數的SQL語句樣本,提供像素數閾值為2。

WITH mvtgeomex AS (
  SELECT ST_AsMVTGeomEx(geom, ST_Transform(ST_TileEnvelope(5, 10, 20), 4326), 2) AS geom
  FROM data_table
  WHERE geom && ST_Transform(ST_TileEnvelope(5, 10, 20), 4326)
)
SELECT ST_AsMVT(mvtgeomex.*) FROM mvtgeomex;

如何設定res_prec

參數resolution_prec的值會對顯示效率和效果有很大的影響,res_prec值越大,顯示效率越高,但是也會有越明顯的資料丟失現象。

以下對比PostGIS的ST_AsMVTGeom函數和GanosBase的ST_AsMVTGeomEx函數在兩個不同資料集上的運行效果,其中GanosBase分別調用像素數閾值為2或者4的ST_AsMVTGeomEx函數。

資料集

調用函數及參數配置

運行效果

資料集一

PostGIS的ST_AsMVTGeom函數。

2

GanosBase的ST_AsMVTGeomEx函數,像素數閾值為2。

2

GanosBase的ST_AsMVTGeomEx函數,像素數閾值為4。

2

資料集二

PostGIS的ST_AsMVTGeom函數。

2

GanosBase的ST_AsMVTGeomEx函數,像素數閾值為2。

2

GanosBase的ST_AsMVTGeomEx函數,像素數閾值為4。

2

使用建議

  • 對於點資料集不建議使用ST_AsMVTGeomEx函數。

  • 如果資料集中的資料座標範圍普遍較小,可以使用預設的res_prec值,若不滿意顯示效率,可以以每次增1的方式調整。

  • 如果資料集中的資料座標範圍普遍較大,建議使用較大的res_prec值,可以從res_prec=4開始嘗試。

ST_AsMVTEx

與PostGIS的ST_AsMVT函數相比,ST_AsMVTEx新增scale_factor和mvt_size_limit參數。ST_AsMVTEx函數基於不同向量要素之間的關係,過濾對顯示效果影響較小的向量要素,從而減小MVT大小,提升可視化效率。詳細的文法介紹請參考ST_AsMVTEx

以下ST_AsMVTEx函數的SQL語句樣本。

-- 設定scale_factor=4
WITH mvtgeomex AS (
  SELECT ST_AsMVTGeom(geom, ST_Transform(ST_TileEnvelope(5, 10, 20), 4326)) AS geom
  FROM data_table
  WHERE geom && ST_Transform(ST_TileEnvelope(5, 10, 20), 4326)
)
SELECT ST_AsMVTEX(mvtgeomex.*, 4) FROM mvtgeomex;

-- 設定scale_factor=8, mvt_size_limit=1000000
WITH mvtgeomex AS (
  SELECT ST_AsMVTGeom(geom, ST_Transform(ST_TileEnvelope(5, 10, 20), 4326)) AS geom
  FROM data_table
  WHERE geom && ST_Transform(ST_TileEnvelope(5, 10, 20), 4326)
)
SELECT ST_AsMVTEX(mvtgeomex.*, 8, 1000000) FROM mvtgeomex;

以下對比使用PostGIS的ST_AsMVT函數和使用GanosBase的ST_AsMVTEx函數在兩個不同資料集的運行結果,其中,ST_AsMVTEx參數設定了不同大小的scale_factor(mvt_size_limit保持預設值)。

資料集

調用函數及參數配置

運行效果

資料集一

PostGIS的ST_AsMVT函數。

2

GanosBase的ST_AsMVTEx函數,scale_factor設定為4。

2

GanosBase的ST_AsMVTEx函數,scale_factor設定為8。

2

資料集二

PostGIS的ST_AsMVT函數。

2

GanosBase的ST_AsMVTEx函數,scale_factor設定為4。

2

GanosBase的ST_AsMVTEx函數,scale_factor設定為8。

2

一般情況下,不建議設定mvt_size_limit。原因在於某些MVT觸發隨機過濾後,可能導致相鄰的MVT顯示不協調,從而產生明顯的割裂感。建議優先使用預設值,或設定較大的 mvt_size_limit,僅在對可視化效率不滿意且不介意視覺效果損失的情況下,才考慮使用 mvt_size_limit。以下對比設定不同大小的mvt_size_limit(scale_factor都為4)的情況:

設定mvt_size_limit

運行效果

mvt_size_limit=10000

2

mvt_size_limit=50000

2

mvt_size_limit=100000

2

使用建議

  • 在設定scale_factor參數時,需要結合MVT的大小,即extent的值進行綜合考慮。初始時可採用extent/scale_factor=1024的原則,若extent使用預設值4096,則將scale_factor設定為4。

  • 若對可視化效率不滿意,可以將scale_factor設定為兩倍後再次嘗試。

  • extent/scale_factor的值不應超過4096。如需設定較大的extent,應相應地增大scale_factor的值。

  • 一般不建議設定mvt_size_limit。如果確實需要更小的MVT,建議首先結合使用ST_IsRandomSampled。

  • ST_AsMVTEx函數不適合用於大面積的面資料集。

ST_IsRandomSampled

ST_IsRandomSampled函數根據提供的屬性值和採樣率,返回布爾值表示該條記錄是否被採樣。該條記錄被採樣的機率為參數採樣率的值。詳細文法介紹請參考ST_IsRandomSampled。以下是部分使用樣本的介紹。

  • 若需擷取10%的隨機採樣樣本,採樣的依據為Geometry屬性的值,則可以使用以下SQL語句(假設Geometry列名為geom):

    SELECT * FROM data_table WHERE ST_IsRandomSampled(ROW(geom), 10);

    由於ST_IsRandomSampled的採樣結果是基於參數tuple計算得出的,因此使用者應盡量指定不重複的屬性列作為tuple進行傳入。該函數允許傳入多個屬性列。

  • 將ST_IsRandomSampled用於動態MVT查詢。

    WITH mvtgeom AS (
      SELECT ST_AsMVTGeom(geom, ST_Transform(ST_TileEnvelope(5, 10, 20), 4326)) AS geom
      FROM data_table
      WHERE ST_IsRandomSampled(ROW(geom), 10) AND geom && ST_Transform(ST_TileEnvelope(5, 10, 20), 4326)
    )
    SELECT ST_AsMVT(mvtgeom.*) FROM mvtgeom;

    上述SQL語句將大約10%的位於編號為“5, 10, 20”的MVT空間範圍內的向量資料寫入MVT,並將其作為結果返回。此處假設data_table的幾何資料以4326座標系形式儲存,並且仍以4326座標系進行顯示。您可按需進行座標系轉換。ST_IsRandomSampled能夠確保輸出的資料在分布上與完整資料集基本一致。在資料量足夠大的情況下,由於僅展示10%的資料,其在效率方面相較於未使用ST_IsRandomSampled時可實現8至9倍的提升。

    以下展示PostGIS和GanosBase在兩個不同資料集上的運行結果。其中,GanosBase分別採用了25%、50%和75%採樣率的ST_IsRandomSampled函數。

    資料集

    調用函數及參數配置

    運行效果

    資料集一

    PostGIS

    2

    GanosBase的ST_IsRandomSampled函數,採樣率25%。

    2

    GanosBase的ST_IsRandomSampled函數,採樣率50%。

    2

    GanosBase的ST_IsRandomSampled函數,採樣率75%。

    2

    資料集二

    PostGIS

    2

    GanosBase的ST_IsRandomSampled函數,採樣率25%。

    2

    GanosBase的ST_IsRandomSampled函數,採樣率50%。

    2

    GanosBase的ST_IsRandomSampled函數,採樣率75%。

    2

進階用法

ST_IsRandomSampled函數將確保使用較小採樣率所獲得的結果必然包含在使用較大採樣率所獲得的結果中。例如ST_IsRandomSampled(ROW(geom), 50)的結果集包含於ST_IsRandomSampled(ROW(geom), 75)的結果集。動態MVT可視化的一大痛點在於小比例尺MVT的顯示效率較低。因此,在查詢小比例尺MVT時設定較小的採樣率參數,而在查詢大比例尺MVT時則可採用較大的採樣率值或選擇不調用ST_IsRandomSampled函數。

以下展示三種方案的對比效果:

方案描述

運行效果

使用PostGIS。

2

使用ST_IsRandomSampled函數,且全域使用50%的採樣率。

2

參數0~11層使用25%的採樣率參數,12~13層使用50%的採樣率參數,14層使用75%的採樣率參數,大於14層不調用ST_IsRandomSampled。

2

參數0~11層使用25%的採樣率參數,12~13層使用50%的採樣率參數,14層使用75%的採樣率參數,大於14層不調用ST_IsRandomSampled。您可使用如下SQL語句實現該效果:

CREATE OR REPLACE FUNCTION ST_GetMVT(z integer, x integer, y integer) RETURNS BYTEA
AS $$
BEGIN
  DECLARE
    sample_rate integer;
  BEGIN
    IF z >= 0 AND z < 12 THEN
      sample_rate := 25;
    ELSIF z >= 12 AND z < 14 THEN
      sample_rate := 50;
    ELSIF z >= 14 AND z < 15 THEN
      sample_rate := 75;
    ELSE
      RETURN  
        st_asmvt(mvtgeom) FROM 
        (SELECT st_asmvtgeom(geometry, st_transform(ST_tileenvelope(z, x, y), 4326))
          FROM YOU_TABLE_NAME
          WHERE geometry && st_transform(ST_tileenvelope(z, x, y), 4326)) AS mvtgeom;
    END IF;

    -- Call ST_IsRandomSampled with the calculated sample_rate
    RETURN 
      st_asmvt(mvtgeom) FROM
      (SELECT st_asmvtgeom(geometry, st_transform(ST_tileenvelope(z, x, y), 4326))
      FROM YOU_TABLE_NAME
      WHERE st_israndomsampled(ROW(geometry), sample_rate) AND geometry && st_transform(ST_tileenvelope(z, x, y), 4326)) AS mvtgeom;
  END;
END;
$$ LANGUAGE plpgsql;

最佳實務

以下使用代碼構建一個簡單的可視化程式,用於查看動態向量切片的效果。該程式包含一個Python檔案和一個HTML檔案。且這兩個檔案置於同一目錄中,在執行Python代碼後,在瀏覽器地址欄輸入localhost:50000即可訪問。需要注意的是,需要事先安裝該Python代碼運行所需的依賴包(在終端執行命令:pip install psycopg2-binary Flask)。

以下Python代碼使用ST_IsRandomSampled函數,設定的採樣率為10%。請注意,需根據具體資料庫情況替換串連參數、表名和欄位名。此外,您也可以參照程式碼範例,根據所需功能更新SQL語句。

from psycopg2 import pool
from threading import Semaphore
from flask import Flask, Response, send_from_directory
import binascii


## 串連參數
CONNECTION = "dbname=資料庫名 user=使用者名稱 host=HOST port=連接埠 password=密碼"

TABLE_NAME = "表名"
GEOMETRY_FIELD_NAME = "幾何欄位名"


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 MvtViewer:
    def __init__(self, connect):
        self.connect = ReallyThreadedConnectionPool(5, 10, connect)

    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]

    def get_mvt(self, x, y, z):
        bounds = f"st_transform(st_tileenvelope({z},{x},{y}),4326)"
        # 根據需要使用的函數,更新下列sql語句
        sql = f'SELECT encode(ST_AsMVT(tile,\'mvt\'),\'hex\') FROM (SELECT  ST_AsMVTGeom({GEOMETRY_FIELD_NAME},{bounds}, 4096, 512, true) as {GEOMETRY_FIELD_NAME} FROM {TABLE_NAME} where({GEOMETRY_FIELD_NAME} && {bounds} AND ST_IsRandomSampled(ROW({GEOMETRY_FIELD_NAME}), 10)) ) AS tile'
        result = self.poll_query(sql)
        result = binascii.a2b_hex(result)
        print("{} {} {}={}".format(z, x, y, len(result)))
        return result


app = Flask(__name__)
mvtViewer = MvtViewer(CONNECTION)


@app.route('/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"
    )


@app.route('/<asset>')
def pyramid_asset(asset):
    return send_from_directory("./", asset)


@app.route('/')
def pyramid_demo():
    return send_from_directory("./", "viewer.html")


if __name__ == "__main__":
    app.run(port=50000, host="0.0.0.0", threaded=True)

HTML檔案的內容如下,變數CENTER表示初始可視化經緯度,注意根據資料集類型更新type變數。

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <title>Pyramid Viewer</title>
  <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
  <link href="https://cdn.bootcdn.net/ajax/libs/mapbox-gl/1.13.0/mapbox-gl.min.css" rel="stylesheet">
  <script src="https://cdn.bootcdn.net/ajax/libs/mapbox-gl/1.13.0/mapbox-gl.min.js"></script>
</head>

<body>
  <div id="map" style="position: absolute; top: 0; bottom: 0; width: 100%;"></div>
  <script>
    let CENTER = [目標經度, 目標緯度]
    <!-- 樣本: let CENTER = [106, 29] -->
    let YOUR_TOKEN = 'pk.eyJ1Ijoia3pmaWxlIiwiYSI6ImNqbHZueXdlZjB2cG4zdnFucGl1OHJsMjkifQ.kW_Utrh8ETQltRk6fnpa_A'

    mapboxgl.accessToken = YOUR_TOKEN;
    const map = new mapboxgl.Map({
      container: 'map',
      style: { "version": 8, "layers": [], "sources": {} },
      center: CENTER,
      zoom: 8
    })
    map.on("load", () => {
      map.addSource('mvt_source', {
        type: 'vector',
        minzoom: 3,
        tiles: [`${window.location.href}mvt/{z}/{x}/{y}`],
        tileSize: 512,
      });
      map.addLayer({
        minzoom: 3,
        id: 'mvt',
        // 面是fill,點是circle,線是line
        type: 'line',
        source: 'mvt_source',
        'source-layer': 'mvt',
      });
    });
  </script>
</body>

</html>

總結

GanosBase新增了增強2D向量動態切片函數ST_IsRandomSampled、ST_AsMVTGeomEx和ST_AsMVTEx。其中,ST_IsRandomSampled能夠在全域範圍內減少不同層級的MVT大小。ST_AsMVTGeomEx和ST_AsMVTEx則能夠顯著減小小比例尺MVT的體積。這三個函數可以單獨使用,也可以結合使用。您可以根據需要設定合適的參數值,以更好地發揮新增函數的效果。