全部產品
Search
文件中心

PolarDB:GanosBase向量快顯功能上手系列一:2D和3D向量快顯

更新時間:Apr 19, 2025

GanosBase的2D向量快顯功能有效解決了傳統切片方案在切片時間和儲存開銷方面的主要問題,並支援局部更新。與現有系統相比,在效率、儲存開銷及功能豐富性方面均有顯著提升。GanosBase的3D向量可視化功能通過對2D向量切片的擴充,支援Geometry3D資料的可視化,能夠有效地用於大範圍3D情境的展示。本文以案例形式為您介紹如何建立和更新2D向量金字塔,以及3D向量物體可視化功能。

功能介紹

2D向量快顯

傳統系統支援向量資料視覺效果,主要採用離線切片的方式。當可視化請求到達時,將預先構建的切片經過簡單處理後返回用戶端。這種處理方案存在兩大痛點:一是“慢”,在大規模資料集上運行離線切片通常需要十幾個小時甚至幾天的時間才能完成。二是“大”,在最高支援16級縮放的地圖服務中,需要事先儲存幾十億個切片,導致儲存開銷極大。另外,由於“慢”這一問題,衍生出對資料更新不友好的新問題。該問題在資料管理能力較弱的GIS系統中尤為凸顯。由於缺乏對資料更新內容及其對切片影響範圍的感知,一旦發生資料更新,即使只是局部的小範圍更新,也只能通過耗時費力的全盤重建切片的方式來解決。

GanosBase快顯引擎所提供的2D向量快顯功能,專門針對億級二維向量資料(包括點、線、面)進行可視化處理。該功能能夠很好地解決以上問題,GanosBase創新性地提出了稀疏金字塔索引,跳過了對資料稀疏地區的切片構建,結合資料庫查詢最佳化,並通過視覺可見度剔除演算法,過濾掉大量不影響顯示效果的資料,從而有效解決了切片時間漫長和儲存開銷巨大的兩大痛點。不僅如此,GanosBase還支援稀疏金字塔的動態更新。當向量資料在小範圍內發生局部更新時,GanosBase能夠自動識別出受影響的切片,並將切片的更新限制在儘可能小的範圍內,從而避免了對稀疏金字塔進行重建的需要,顯著提高了更新效率。經實測,在一台配置普通的8 核PolarDB PostgreSQL版叢集上,構建包含七千萬條房屋資料集的稀疏金字塔僅需6分鐘。當發生超過一百萬條資料更新時,稀疏金字塔的更新也僅需不到1分鐘。在響應可視化請求時,平均返回一個切片的時間不到1毫秒。具備如此高效的效能的基礎上,所需的磁碟儲存空間僅約為3 GB。

3D向量可視化

GanosBase不僅支援2D向量快速顯示,還與阿里雲DataV產品團隊聯合開發了對3D向量資料視覺效果的支援。通過擴充現有的2D向量切片標準,GanosBase與DataV合作實現了可用於3D向量物體可視化的3D向量切片,效果圖如下:

2

使用步驟

2D向量快顯使用步驟

準備資料表

準備包含Geometry屬性的資料表。GanosBase提供多種將向量資料寫入資料表的函數,包括常見的ST_GeomFromText、ST_GeomFromWKT和ST_GeomFromWKB等。使用GanosBase的向量快顯功能,僅需確保資料表包含主鍵ID及Geometry屬性列。

  1. 在使用GanosBase的快顯功能之前,請安裝快顯功能外掛程式ganos_geometry_pyramid

    CREATE EXTENSIION ganos_geometry_pyramid CASCADE;
  2. 建立一個只包含主鍵idgeom列的資料表。

    CREATE TABLE try_ganos_viz(id SERIAL NOT NULL, geom Geometry);
  3. 使用指令碼或其它工具將一個向量資料集匯入該表,只需要該指令碼或工具能夠產生類似如下的SQL命令即可(假設資料集檔案儲存體的是4326座標系的WKT格式資料)。

    INSERT INTO try_ganos_viz(geom) VALUES (ST_GeomFromText('WKT FORMAT TEXT', 4326));

    以上SQL語句將資料以4326座標系格式儲存到資料庫中,您也可以指定儲存為其他座標系格式。

  4. 匯入所有資料後,對Geometry屬性列構建一個空間索引。

    CREATE INDEX ON try_ganos_viz USING gist(geom);

構建稀疏金字塔

ST_BuildPyramid

構建稀疏金字塔功能已經封裝至ST_BuildPyramid函數,詳情請參考ST_BuildPyramid。以下將通過樣本形式進行詳細介紹。

  • 建立一個使用預設參數配置的稀疏金字塔。

    SELECT ST_BuildPyramid('try_ganos_viz', 'geom', 'id', '');
  • config參數中指定構建稀疏金字塔時的並行度,執行如下命令以32並行度構建向量金字塔:

    SELECT ST_BuildPyramid('try_ganos_viz', 'geom', 'id', '{"parallel":32}');
  • config參數中指定tileSizetileExtend的值,以定義切片的像素大小和裁切範圍。

    SELECT ST_BuildPyramid('try_ganos_viz', 'geom', 'id', '{"parallel":32, "tileSize":4096, "tileExtend":128}');
  • GanosBase允許根據對效能和儲存開銷的綜合考量,使用maxLevelsplitSize參數控制稀疏金字塔結構。

    • 相較於儲存開銷,更加重視查詢效能。可以通過設定較小的splitSize值(如果一個切片中的要素小於splitSize,則該切片將在查詢時動態構建),以確保儘可能多的切片在查詢前已被構建。

    • 若更加關注儲存開銷,除了可以設定較大的splitSize值外,還可以通過設定較小的maxLevel來控制金字塔的高度,從而減少需維護的切片數量。

    以下樣本,構建了一個maxLevel值為10,splitSize值為1000的稀疏金字塔:

    SELECT ST_BuildPyramid('try_ganos_viz', 'geom', '{"maxLevel":10, "splitSize":1000}');
  • 提供buildRules參數以靈活控制稀疏金字塔的構造規則。例如,在層級小於等於5的切片中,僅可視化面積大於100的要素,可以使用如下命令:

    SELECT ST_BuildPyramid('try_ganos_viz', 'geom', '{"maxLevel":10, "buildRules":[
                           {"level":[0,1,2,3,4,5], "value":{"filter": "ST_Area(geom)>100"}}
    ]}');

    以上SQL語句中的filter所對應的過濾條件可以是任意SQL語句中位於WHERE子句後面的條件陳述式。

ST_BuildPyramidUseGeomSideLen

構建向量金字塔還可以使用ST_BuildPyramidUseGeomSideLen函數。與ST_BuildPyramid相比,該函數進行了效能最佳化,能夠在資料表中包含大量面積較小的要素時,有效提升稀疏金字塔的構建效率。詳情請參考ST_BuildPyramidUseGeomSideLen

在使用ST_BuildPyramidUseGeomSideLen函數之前,資料表中需存在實數屬性列,以記錄geom列中ST_XMax(geom)-ST_XMin(geom)與ST_YMax(geom)-ST_YMin(geom)的較大值。同時,還需對該屬性列建立一個索引。以上述的try_ganos_viz表為例,使用以下語句新增一個max_side_len屬性列,並為該列構建一個B樹索引:

ALTER TABLE try_ganos_viz
ADD COLUMN max_side_len DOUBLE PRECISION;

CREATE OR REPLACE FUNCTION add_max_len_values() RETURNS VOID AS $$
DECLARE
  t_curs CURSOR FOR
    SELECT * FROM try_ganos_viz;
  t_row usbf%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 try_ganos_viz
      SET max_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();

CREATE INDEX ON try_ganos_viz USING btree(max_side_len);

在調用ST_BuildPyramidUseGeomSideLen時,需要提供新增列的列名,其餘參數與ST_BuildPyramid相同。執行以下語句,將對新增了max_side_len列的try_ganos_viz表構建稀疏金字塔:

SELECT ST_BuildPyramidUseGeomSideLen('try_ganos_viz', 'geom', 'max_side_len', 'id', '{"parallel":32}');

ST_BuildPyramidUseGeomSideLen同樣支援多種配置參數,能夠靈活調整稀疏金字塔的構建規則。

更新金字塔

當資料表中的資料發生更新時,GanosBase提供ST_UpdatePyramid函數用於對金字塔進行更新,只需提供資料更新的外包框範圍,詳情請參考ST_UpdatePyramid

  • 假設在[(lon1, lat1), (lon2, lat2)]的經緯度範圍插入入了多條資料,希望所有受影響的切片均被更新(假設maxLevel=16),可以執行以下語句:

    SELECT ST_UpdatePyramid('try_ganos_viz', 'geom', 'id', ST_MakeEnvelope(0,-10,20,30, 4326), '{"updateBoxScale":100000}');

    在上述SQL語句中假設lon1=0lat1=-10lon2=20lat2=30

  • 假設不希望進行全域大規模更新,可以指定一個較小的updateBoxScale參數值,以避免較小層級的切片也被更新。執行以下語句將僅修改範圍稍大的切片及其層級以下的切片:

    SELECT ST_UpdatePyramid('try_ganos_viz', 'geom', 'id', ST_MakeEnvelope(0,-10,20,30, 4326), '{"updateBoxScale":2}');
說明
  • 調用ST_UpdatePyramid時,不需要指定並行度。該函數會自動採用調用ST_BuildPyramid或ST_BuildPyramidUseGeomSideLen時提供的並行值。

  • 由於更新金字塔涉及稀疏金字塔的更新、舊切片的刪除,以及新切片的產生等步驟,當發生大範圍的資料更新時,建議直接調用ST_BuildPyramid或ST_BuildPyramidUseGeomSideLen重建金字塔。

擷取向量切片

向量切片具備保留要素資訊的優勢。在地圖服務中,相較於傳統柵格切片,向量切片的多級縮放實現更加平滑的過渡,提供更好的視覺效果。GanosBase提供ST_Tile函數,用於即時調用擷取向量切片。詳情請參考ST_Tile

執行以下任一語句,以擷取中國所在的切片:z=1x=1y=0

SELECT ST_Tile('try_ganos_viz', '1_1_0');

SELECT ST_Tile('try_ganos_viz', 1, 0, 1);

擷取柵格切片

GanosBase同樣支援當前仍被廣泛使用的柵格切片。柵格切片是以圖片形式呈現的切片,相較於向量切片,柵格切片不支援用戶端的動態渲染,因此對用戶端系統的效能要求較低。GanosBase提供了ST_AsPng函數,該函數允許在資料庫端按需將向量資料動態渲染為柵格切片,並將結果返回給用戶端。此函數具備基本的柵格符號化能力,主要適用於不需複雜符號化的輕量級情境。詳情請參考ST_AsPng

調用以下語句以返回切片編號為'1_1_0'的柵格切片,該切片將依據所提供的渲染參數進行渲染,並以PNG格式輸出。

SELECT ST_AsPng('try_ganos_viz', '1_1_0', '{"point_size":5, "line_width":2, "line_color":"#003399FF", 
                "fill_color":"#6699CCCC", "background":"#FFFFFF00"}');

3D向量可視化使用步驟

準備資料表

準備包含Geometry3D屬性列的資料表。可通過ST_GeomFromText、ST_GeomFromWKT、ST_GeomFromWKB等函數實現資料的匯入。

  1. 在使用GanosBase的3D向量可視化功能之前,請安裝3D向量可視化功能外掛程式ganos_geometry。

    CREATE EXTENSIION ganos_geometry CASCADE;
  2. 建立只包含主鍵idgeom列的資料表。

    CREATE TABLE try_ganos_viz3d(id SERIAL NOT NULL, geom Geometry);

擷取3D向量切片

在使用3D向量資料視覺效果功能之前,需要調用GanosBase提供的兩個函數:ST_AsMVTGeom3D和ST_AsMVT3D。

  • ST_AsMVTGeom3D

    ST_AsMVTGeom3D功能類似於PostGIS的ST_AsMVTGeom之於2D向量資料,但是對其進行了擴充,能夠將Geometry3D資料的座標空間轉換到MVT的座標空間,如果有需要還會根據切片的外包框對Geometry3D資料進行裁切。詳細函數介紹請參考ST_AsMVTGeom3D。使用樣本如下:

    SELECT ST_AsText(ST_AsMVTGeom3D(ST_Transform('SRID=4326; LINESTRING(-10 -10 30, -10 -20 30)'::geometry, 3857), ST_TileEnvelope(1, 0, 0))) AS geom;

    返回結果如下:

                                            geom                                        
    ------------------------------------------------------------------------------------
     MULTILINESTRING Z ((3868.44444444444 4324.7197219642 30,3868.44444444444 4352 30))
    (1 row)
  • ST_AsMVT3D

    ST_AsMVT3D功能類似於PostGIS的ST_AsMVT之於2D向量資料,將若干行資料封裝進一個3D向量切片中的彙總函式。每條被封裝的資料均對應經過座標空間轉換為MVT座標空間的Geometry3D資料,這些封裝的資料共同形成一個切片圖層。詳細函數介紹請參考ST_AsMVT3D。使用樣本如下:

    WITH mvtgeom AS
    (
      SELECT ST_AsMVTGeom3D(
        ST_Transform('SRID=4326; MULTIPOLYGON(((100 50 0, -100 50 1, -100 -50 2, 100 -50 3, 100 50 0)), ((0 0 0, 1 0 1, 2 2 2, 0 0 0)))'::geometry, 3857),
        ST_TileEnvelope(1, 0, 0)) AS geom,  'test' AS name
    )
    SELECT ST_AsMVT3D(mvtgeom.*) FROM mvtgeom;

    返回結果如下:

                                                                                                                         st_asmvt3d                                                                                                                     
    ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
     \x1a760a0764656661756c74125812020000180322500d8044a842b83116ff23d80105802400080f0d810481041d162e000e2e590e0f0dd920dc0405168024d70106c727f3160d0f0dc827f4160e1600f31615c72700080f0d0000001600cc1808c80300000f1a046e616d6522060a04746573742880207802
    (1 row)

最佳實務

本案例基於GanosBase的2D向量快顯功能,為您介紹如何快速搭建一個Web地圖服務。

全棧架構

此Web地圖服務由資料庫伺服器、Python服務端和使用者端三部分構成,其全棧架構示意如下圖所示。

資料庫

將地圖資料匯入資料庫,並構建稀疏金字塔所需的索引。詳細操作請參考2D向量快顯使用步驟

服務端代碼

為實現代碼的簡潔性並更注重邏輯描述,本案例選用了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>

總結

GanosBase的2D向量快顯功能能夠有效實現對億級規模2D向量資料的可視化,顯著解決了傳統切片方案在切片時間和儲存開銷方面的兩大痛點。同時,該功能支援局部更新,相較於現有系統,在效率、儲存開銷及功能豐富性方面均有顯著提升。GanosBase的3D向量可視化功能通過對2D向量切片的擴充,支援Geometry3D資料的可視化,適用於大範圍3D情境的可視化需求。