Ganos新增的2D矢量动态切片函数能够大幅提升可视化效率,有效解决小比例尺MVT显示耗时久的问题。与PostGIS相比,小比例尺MVT的可视化效率提升可达60%以上。本文主要介绍2D矢量动态切片函数及其使用方法。
背景介绍
传统地图可视化服务为提高加载速度,将待显示数据进行分层缓存,即事先将同一份数据对应每个层级的显示图像进行多次保存。在使用时,根据当前浏览界面的层级动态获取相应层级的图像,并将其返回给客户端以完成可视化。这种传统方法被称为栅格切片,其优点在于可视化效率高且对显示端的硬件要求较低。然而,其缺点包括预切片时间长、存储开销大和更新不够友好。此外,客户端显示效果相对呆板,只能看到预先切好的图片样式所呈现的数据。对于层级间的缩放则是放大或缩小当前层的图片,直接切换到相邻层的图片,导致层级间的过渡生硬。
近年来,随着硬件技术的不断发展以及对显示效果要求的提升,动态矢量切片技术愈发受到欢迎。首先,矢量切片是指将待显示数据的矢量信息记录到一种称为矢量瓦片的载体上。所记录的信息包括矢量的类型(点、线或面)以及组成该矢量的各个点在矢量瓦片上的相对坐标等。客户端软件(包括浏览器或GIS软件)能够提取矢量瓦片中的矢量信息,并根据自定义的显示样式(如点或线的颜色、面的填充色等)绘制每个矢量。和栅格切片的共同点在于,都需要根据当前客户端软件的显示层级获取同层级的瓦片。不同之处在于,栅格切片的瓦片为样式固定的位图图片,而矢量瓦片则保留了各个数据的矢量信息存储结构。通俗地讲,栅格切片是将拍摄的照片直接展示给客户端,而矢量切片则是向前端软件指明应展示的内容,并根据自定义绘画风格逐笔逐画地进行渲染。随着硬件技术的发展,客户端软件也能够高效地完成矢量瓦片的绘制。MVT(Mapbox Vector Tile)是一种广泛应用于存储和传输矢量瓦片的标准格式。主流前端软件均支持MVT,本文后续将以MVT指代矢量瓦片。动态矢量切片是指后端系统根据客户端软件当前的显示层级和范围,动态生成一系列MVT(地图矢量瓦片),并将其返回给前端进行可视化展示。
Ganos矢量快显
矢量动态切片函数显著提高了可视化效率,有效解决了小比例尺MVT显示耗时较长的问题。与PostGIS相比,小比例尺MVT的可视化效率提升可达60%以上。下图展示了Ganos的ST_AsMVTEx函数与PostGIS的ST_AsMVT的效果对比。
函数介绍
新增三个2D矢量动态切片函数ST_AsMVTGeomEx、ST_AsMVTEx和ST_IsRandomSampled。
ST_AsMVTGeomEx函数为PostGIS的ST_AsMVTGeom的增强,用于将矢量数据从地理空间坐标系转换为MVT的像素坐标系。
ST_AsMVTEx函数为PostGIS的ST_AsMVT的增强,用于将一系列经过坐标系转换的矢量数据的信息聚合并写入MVT。
ST_IsRandomSampled允许按任意百分比对数据进行随机采样,从而便于快速查看超大数据集的大致显示样式。
ST_AsMVTGeomEx和ST_AsMVTEx函数舍弃对显示效果影响相对较小的矢量数据,仅保留对视觉效果更为重要的矢量数据。这一策略显著减少小比例尺MVT的尺寸,降低网络传输开销和客户端软件的渲染负担,从而有效提升显示效率。
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和Ganos在两个不同数据集上的运行结果。其中,Ganos分别采用了25%、50%和75%采样率的ST_IsRandomSampled函数。
数据集
调用函数及参数配置
运行效果
数据集一
PostGIS
Ganos的ST_IsRandomSampled函数,采样率25%。
Ganos的ST_IsRandomSampled函数,采样率50%。
Ganos的ST_IsRandomSampled函数,采样率75%。
数据集二
PostGIS
Ganos的ST_IsRandomSampled函数,采样率25%。
Ganos的ST_IsRandomSampled函数,采样率50%。
Ganos的ST_IsRandomSampled函数,采样率75%。
高级用法
ST_IsRandomSampled函数将确保使用较小采样率所获得的结果必然包含在使用较大采样率所获得的结果中。例如ST_IsRandomSampled(ROW(geom), 50)
的结果集包含于ST_IsRandomSampled(ROW(geom), 75)
的结果集。动态MVT可视化的一大痛点在于小比例尺MVT的显示效率较低。因此,在查询小比例尺MVT时设定较小的采样率参数,而在查询大比例尺MVT时则可采用较大的采样率值或选择不调用ST_IsRandomSampled函数。
以下展示三种方案的对比效果:
方案描述 | 运行效果 |
使用PostGIS。 | |
使用ST_IsRandomSampled函数,且全局使用50%的采样率。 | |
参数0~11层使用25%的采样率参数,12~13层使用50%的采样率参数,14层使用75%的采样率参数,大于14层不调用ST_IsRandomSampled。 |
参数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;
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函数和Ganos的ST_AsMVTGeomEx函数在两个不同数据集上的运行效果,其中Ganos分别调用像素数阈值为2或者4的ST_AsMVTGeomEx函数。
数据集 | 调用函数及参数配置 | 运行效果 |
数据集一 | PostGIS的ST_AsMVTGeom函数。 | |
Ganos的ST_AsMVTGeomEx函数,像素数阈值为2。 | ||
Ganos的ST_AsMVTGeomEx函数,像素数阈值为4。 | ||
数据集二 | PostGIS的ST_AsMVTGeom函数。 | |
Ganos的ST_AsMVTGeomEx函数,像素数阈值为2。 | ||
Ganos的ST_AsMVTGeomEx函数,像素数阈值为4。 |
使用建议
对于点数据集不建议使用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函数和使用Ganos的ST_AsMVTEx函数在两个不同数据集的运行结果,其中,ST_AsMVTEx参数设置了不同大小的scale_factor(mvt_size_limit保持默认值)。
数据集 | 调用函数及参数配置 | 运行效果 |
数据集一 | PostGIS的ST_AsMVT函数。 | |
Ganos的ST_AsMVTEx函数,scale_factor设置为4。 | ||
Ganos的ST_AsMVTEx函数,scale_factor设置为8。 | ||
数据集二 | PostGIS的ST_AsMVT函数。 | |
Ganos的ST_AsMVTEx函数,scale_factor设置为4。 | ||
Ganos的ST_AsMVTEx函数,scale_factor设置为8。 |
一般情况下,不建议设置mvt_size_limit。原因在于某些MVT触发随机过滤后,可能导致相邻的MVT显示不协调,从而产生明显的割裂感。建议优先使用默认值,或设置较大的 mvt_size_limit,仅在对可视化效率不满意且不介意视觉效果损失的情况下,才考虑使用 mvt_size_limit。以下对比设置不同大小的mvt_size_limit(scale_factor都为4)的情况:
设置mvt_size_limit | 运行效果 |
mvt_size_limit=10000 | |
mvt_size_limit=50000 | |
mvt_size_limit=100000 |
使用建议
在设置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函数不适合用于大面积的面数据集。
最佳实践
以下使用代码构建一个简单的可视化程序,用于查看动态矢量切片的效果。该程序包含一个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>
总结
Ganos新增了增强的2D矢量动态切片函数ST_IsRandomSampled、ST_AsMVTGeomEx和ST_AsMVTEx。其中,ST_IsRandomSampled能够在全局范围内减少不同层级的MVT大小。ST_AsMVTGeomEx和ST_AsMVTEx则能够显著减小小比例尺MVT的体积。这三个函数可以单独使用,也可以结合使用。您可以根据需要设置合适的参数值,以更好地发挥新增函数的效果。