The fast 2D vector rendering feature of GanosBase effectively solves the main issues of traditional tiling schemes, including prolonged tiling time and excessive storage overhead. Additionally, it supports local updates. Compared with existing systems, GanosBase has achieved significant improvements in efficiency, storage overhead, and enhanced functionality. The 3D vector visualization feature of GanosBase extends the 2D vector tiling scheme to support the visualization of Geometry3D data, enabling the effective display of large-scale 3D scenes. This topic describes how to create and update 2D vector pyramids and implement 3D vector object visualization by using examples.
Overview
Fast 2D vector rendering
Traditional systems primarily rely on offline tiling to support vector data visualization. When a visualization request is received, the pre-built tiles are simply processed and returned to the client. However, this processing scheme has two significant drawbacks. Firstly, it is slow: running offline tiling on large-scale datasets typically takes tens of hours or even days to complete. Secondly, it is resource-intensive: in map services that support up to 16 levels of zoom, tens of billions of tiles need to be pre-stored, resulting in massive storage overhead. Additionally, the "slow" issue leads to unfriendly data updates. This issue is particularly prominent in GIS systems with weak data management capabilities. Due to the lack of awareness of data update content and its impact on tile ranges, even a small, local update requires a time-consuming and laborious full rebuild of tiles.
The GanosBase fast rendering engine provides the fast 2D vector rendering feature that specializes in visualizing massive 2D vector data (including points, lines, and polygons). This feature effectively solves the issues mentioned above. GanosBase innovatively introduces a sparse pyramid indexing method that skips building tiles for sparse data regions. It also combines database query optimization with a visibility filtering algorithm to filter out data that does not affect the display effect. This approach effectively resolves the two major pain points of prolonged slicing time and massive storage overhead. Moreover, GanosBase supports dynamic updates of sparse pyramids. When vector data undergoes local updates in a small range, GanosBase can automatically identify the affected tiles and limits updates to the smallest possible range, avoiding the need to rebuild the entire sparse pyramid. This significantly improves update efficiency. Testing has shown that constructing a sparse pyramid for a dataset containing 70 million housing records takes only 6 minutes on a standard 8-core PolarDB for PostgreSQL (Compatible with Oracle) cluster. Furthermore, when over one million records are updated, the sparse pyramid can be updated in less than 1 minute. The average time to return a tile in response to a visualization request is under 1 millisecond. With such high performance, the required disk storage space is only approximately 3 GB.
3D vector visualization
GanosBase not only supports fast rendering of 2D vectors but has also collaborated with Alibaba Cloud's DataV team to develop support for 3D vector data visualization. By extending the existing 2D vector tiling standard, GanosBase and DataV have jointly achieved 3D vector tiling for visualizing 3D vector objects, as shown in the following image:

Procedure
Fast 2D vector rendering
Prepare the data table
Prepare a data table that contains a Geometry attribute column. GanosBase provides various functions, including ST_GeomFromText, ST_GeomFromWKT, and ST_GeomFromWKB, for writing vector data into the data table. To use the vector fast rendering feature of GanosBase, you only need to make that the data table contains a primary key ID column and a Geometry attribute column.
Before using the fast rendering feature of GanosBase, install the ganos_geometry_pyramid fast rendering extension.
CREATE EXTENSIION ganos_geometry_pyramid CASCADE;Create a data table that contains only a primary key ID column and a geom column.
CREATE TABLE try_ganos_viz(id SERIAL NOT NULL, geom Geometry);Use a script or other tools to import a vector dataset into the table. The script or tool must be able to generate the SQL statement similar to the following:
INSERT INTO try_ganos_viz(geom) VALUES (ST_GeomFromText('WKT FORMAT TEXT', 4326));This SQL statement stores the data in the 4326 coordinate system format, but you can also specify other coordinate system formats.
After importing all data, build a spatial index on the Geometry attribute column.
CREATE INDEX ON try_ganos_viz USING gist(geom);
Build a sparse pyramid
St_BuildPyramid
The sparse pyramid building feature is encapsulated in the ST_BuildPyramid function. For more information, see ST_BuildPyramid. The following introduces it in detail through examples.
Create a sparse pyramid by using default parameters.
SELECT ST_BuildPyramid('try_ganos_viz', 'geom', 'id', '');Specify the parallelism level when building the sparse pyramid. This example builds the pyramid with 32 parallel threads.
SELECT ST_BuildPyramid('try_ganos_viz', 'geom', 'id', '{"parallel":32}');Specify the tile size and extend when building the sparse pyramid.
SELECT ST_BuildPyramid('try_ganos_viz', 'geom', 'id', '{"parallel":32, "tileSize":4096, "tileExtend":128}');GanosBase allows you to control the structure of the sparse pyramid based on a comprehensive consideration of performance and storage costs. You can use the maxLevel and splitSize parameters to achieve this control.
If you prioritize query performance over storage costs, you can set a smaller splitSize value. This ensures that as many tiles as possible are pre-built before querying. When a tile contains fewer elements than the splitSize value, it is dynamically built during querying.
If you prioritize storage costs, you can set a larger
splitSizevalue to reduce the number of tiles that need to be maintained. Additionally, you can set a smaller maxLevel value to control the height of the pyramid, which also reduces the number of tiles.
In the following example, a sparse pyramid with a maxLevel value of 10 and a splitSize value of 1000 is constructed.
SELECT ST_BuildPyramid('try_ganos_viz', 'geom', '{"maxLevel":10, "splitSize":1000}');The buildRules parameter is provided to flexibly control the construction rules of the sparse pyramid. For example, you can specify that only features with an area greater than 100 can visualized in tiles at levels 0 to 5. This can be achieved using the following statement:
SELECT ST_BuildPyramid('try_ganos_viz', 'geom', '{"maxLevel":10, "buildRules":[ {"level":[0,1,2,3,4,5], "value":{"filter": "ST_Area(geom)>100"}} ]}');The filter condition in the SQL statement can be any valid SQL condition that can be used in a WHERE clause. This allows for flexible and customizable filtering of features during the construction of the sparse pyramid.
ST_BuildPyramidUseGeomSideLen
The ST_BuildPyramidUseGeomSideLen function is an optimized version of the ST_BuildPyramid function for building vector pyramids. It is designed to improve performance when dealing with large datasets that contain many small-area features. For more information, see ST_BuildPyramidUseGeomSideLen.
Before using the ST_BuildPyramidUseGeomSideLen function, create a real-number attribute column in the data table to record the larger value between ST_XMax(geom)-ST_XMin(geom) and ST_YMax(geom)-ST_YMin(geom) for each geometry. Additionally, you need to create an index on this new column. For example, you can add a max_side_len column to the try_ganos_viz table and create a B-tree index on the column.
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);When calling ST_BuildPyramidUseGeomSideLen, you need to provide the name of the new column, and the rest of the parameters are the same as those for ST_BuildPyramid. To build a sparse pyramid on the try_ganos_viz table with the added max_side_len column, execute the following statement:
SELECT ST_BuildPyramidUseGeomSideLen('try_ganos_viz', 'geom', 'max_side_len', 'id', '{"parallel":32}');ST_BuildPyramidUseGeomSideLen also supports various configuration parameters, allowing you to flexibly adjust the construction rules of the sparse pyramid.
Update a pyramid
When the data in a table is updated, GanosBase provides the ST_UpdatePyramid function to update the pyramid. You only need to provide the bounding box range of the data update. For more information, see ST_UpdatePyramid.
Suppose you have inserted multiple rows of data within the longitude and latitude range of
[(lon1, lat1), (lon2, lat2)]. You want to update all affected tiles (assumingmaxLevel=16). You can execute the following statement:SELECT ST_UpdatePyramid('try_ganos_viz', 'geom', 'id', ST_MakeEnvelope(0,-10,20,30, 4326), '{"updateBoxScale":100000}');In this SQL statement, assume
lon1=0,lat1=-10,lon2=20, andlat2=30.If you do not want to perform a full-scale update, you can specify a smaller
updateBoxScaleparameter value to avoid updating smaller-level tiles. Executing the following statement only updates tiles with a slightly larger range and their lower-level tiles:SELECT ST_UpdatePyramid('try_ganos_viz', 'geom', 'id', ST_MakeEnvelope(0,-10,20,30, 4326), '{"updateBoxScale":2}');
When calling ST_UpdatePyramid, you do not need to specify the degree of parallelism. This function automatically uses the parallelism value specified during the initial call to ST_BuildPyramid or ST_BuildPyramidUseGeomSideLen.
Because updating a pyramid involves updating the sparse pyramid, deleting old tiles, and generating new tiles, we recommend that you rebuild the pyramid by calling ST_BuildPyramid or ST_BuildPyramidUseGeomSideLen directly when a large range of data is updated.
Get vector tiles
Vector tiles have the advantage of preserving feature information. In map services, compared with traditional raster tiles, vector tiles can achieve more seamless zooming and provide better visual effects. GanosBase provides the ST_Tile function to retrieve vector tiles in real-time. For more information, see ST_Tile.
Execute either of the following statements to get the tile that covers China at zoom level 1, x-coordinate 1, and y-coordinate 0.
SELECT ST_Tile('try_ganos_viz', '1_1_0');
SELECT ST_Tile('try_ganos_viz', 1, 0, 1);Get raster tiles
GanosBase also supports raster tiles, which are still widely used today. Raster tiles are image-based tiles that, unlike vector tiles, do not support dynamic rendering on the client-side. This makes them less demanding on client-side system performance. GanosBase provides the ST_AsPng function, which allows you to dynamically render vector data into raster tiles on the database side and return the result to the client. This function has basic raster symbolization capabilities and is suitable for lightweight scenarios that do not require complex symbolization. For more information, see ST_AsPng.
Execute the following statement to return a raster tile with the tile ID '1_1_0', which are rendered according to the provided rendering parameters and output in PNG format:
SELECT ST_AsPng('try_ganos_viz', '1_1_0', '{"point_size":5, "line_width":2, "line_color":"#003399FF",
"fill_color":"#6699CCCC", "background":"#FFFFFF00"}');3D vector visualization
Prepare the data table
Prepare a data table that contains a Geometry3D attribute column. You can use functions such as ST_GeomFromText, ST_GeomFromWKT, and ST_GeomFromWKB to import data into the table.
Before using the 3D vector visualization feature of GanosBase, install the ganos_geometry 3D vector visualization extension.
CREATE EXTENSIION ganos_geometry CASCADE;Create a data table that contains only the primary key
idand thegeomcolumn.CREATE TABLE try_ganos_viz3d(id SERIAL NOT NULL, geom Geometry);
Get 3D vector tiles
Before using the 3D vector data visualization feature, you need to call two functions provided by GanosBase: ST_AsMVTGeom3D and ST_AsMVT3D.
ST_AsMVTGeom3D
The ST_AsMVTGeom3D function is an extension of PostGIS's ST_AsMVTGeom, specifically designed for 3D vector data. It enables the conversion of Geometry3D data's coordinate space to the MVT coordinate space, and optionally clips the data based on the tile's bounding box. For more information, see ST_AsMVTGeom3D. The following example shows how to use it:
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;Sample result:
geom ------------------------------------------------------------------------------------ MULTILINESTRING Z ((3868.44444444444 4324.7197219642 30,3868.44444444444 4352 30)) (1 row)ST_AsMVT3D
ST_AsMVT3D functions similarly to PostGIS's ST_AsMVT for 2D vector data, which aggregates multiple rows of data into a single 3D vector tile. Each row of data is converted to Geometry3D data in the MVT coordinate space, and these aggregated data form a tile layer. For more information, see ST_AsMVT3D. The following example shows how to use it:
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;Sample result:
st_asmvt3d ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \x1a760a0764656661756c74125812020000180322500d8044a842b83116ff23d80105802400080f0d810481041d162e000e2e590e0f0dd920dc0405168024d70106c727f3160d0f0dc827f4160e1600f31615c72700080f0d0000001600cc1808c80300000f1a046e616d6522060a04746573742880207802 (1 row)
Best practices
This example describes how to use the fast 2D vector rendering feature of GanosBase to rapidly build a web map service.
Full-stack architecture
This web map service is comprised of three primary components: a database server, a Python-based backend, and a client-side application. The overall architecture is depicted in the diagram below.
Database
Import map data into the database and create the necessary indexes for building a sparse pyramid. For more information, see Fast 2D vector rendering.
Server-side code
To achieve code simplicity and focus on logical description, we use Python as the backend language, the Flask web framework, and Psycopg2 (installable via pip install psycopg2) for database connections. The implementation provides the basic functionality. When performance bottlenecks arise, optimizations can be made based on specific platforms and frameworks to achieve improved response times. On the backend, we first establish a vector pyramid and then implement two interfaces. The vector tile interface leverages data from the points table, while the raster tile interface utilizes data from the buildings table, with defined styles for direct invocation by the frontend. For clarity, our backend code provides both vector and raster interfaces, but you can select the one that suits your needs in actual practice.
# -*- 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 parameters.
CONNECTION = "dbname=<database_name> user=<user_name> password=<user_password> host=<host> port=<port>"
class ReallyThreadedConnectionPool(pool.ThreadedConnectionPool):
"""
A connection pool for multi-threading, improving response in high-concurrency scenarios such as map tiles.
"""
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
# Create a connection pool.
self.connect = ReallyThreadedConnectionPool(5, 10, connect)
# Define the pyramid table name.
self.pyramid_table = f"{self.table_name}_{self.column_name}"
self.fid = fid
self.tileSize = 512
# self._build_pyramid()
def _build_pyramid(self):
"""Create a pyramid"""
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):
# Default parameters.
config = {
"point_size": 5,
"line_width": 2,
"line_color": "#003399FF",
"fill_color": "#6699CCCC",
"background": "#FFFFFF00"
}
# When using psycopg2, returning binary data as a hexadecimal string is more efficient.
sql = f"select encode(st_aspng('{self.pyramid_table}','{z}_{x}_{y}','{json.dumps(config)}'),'hex')"
result = self.poll_query(sql)
# Perform a conversion to get the original binary data back if the data is returned as a hexadecimal string.
result = binascii.a2b_hex(result)
return result
class MvtViewer(VectorViewer):
def get_mvt(self, x, y, z):
# When using psycopg2, returning binary data as a hexadecimal string is more efficient.
sql = f"select encode(st_tile('{self.pyramid_table}','{z}_{x}_{y}'),'hex')"
result = self.poll_query(sql)
# Perform a conversion to get the original binary data back if the data is returned as a hexadecimal string.
result = binascii.a2b_hex(result)
return result
app = Flask(__name__)
@app.route('/vector')
def vector_demo():
return send_from_directory("./", "Vector.html")
# Define table names, field names, etc.
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)
Save the above code as a Vector.py file, and run it by using the python Vector.py command to start the service. From the code, we can infer that, regardless of the programming language or framework used, as long as we encapsulate the SQL statements for vector or raster tiles into an interface, we can achieve the same functionality.
Compared with publishing traditional map services, using the vector pyramid feature of GanosBase to achieve online visualization is a lighter and more practical choice.
For raster tiles, modifying the code to control the style significantly enhances flexibility.
Without introducing third-party components or performing special optimizations, we can achieve satisfactory response performance.
The service supports the freedom to choose a familiar programming language and framework, without requiring complex professional parameter configurations, making it more user-friendly for non-geographic professionals.
Client-side code
In this example, we use Mapbox as the frontend map framework to visualize the vector tile layer and raster tile layer provided by the backend. We also configure the rendering parameters for the vector tile layer. For ease of explanation, the frontend code includes both vector and raster layers, but you can choose the layers you need based on your specific requirements. Create a new file named Vector.html in the same directory as your backend code, and add the following code. After starting the backend service, you can access it through 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();
// Add a raster tile data source.
map.addSource("png_source", {
type: "raster",
minzoom: 1,
tiles: [`${window.location.href}/png/{z}/{x}/{y}`],
tileSize: 512,
});
// Add a raster tile layer.
map.addLayer({
id: "png_layer",
type: "raster",
layout: { visibility: "visible" },
source: "png_source",
});
// Add a vector tile data source.
map.addSource("mvt_source", {
type: "vector",
minzoom: 1,
tiles: [`${window.location.href}/mvt/{z}/{x}/{y}`],
tileSize: 512,
});
// Add a vector tile layer and apply styles to the vector tiles.
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>Summary
The fast 2D vector rendering feature of GanosBase can effectively visualize 2D vector data at the billion-scale level, significantly addressing the pain points of traditional tiling schemes in tiling time and storage overhead. Additionally, this feature supports local updates. Compared with existing systems, GanosBase achieves significant improvements in efficiency, storage overhead, and enhanced functionality. The 3D vector visualization feature of GanosBase extends the 2D vector tiling scheme to support the visualization of Geometry3D data, enabling the effective display of large-scale 3D scenes.