Ganos矢量快显功能

简介: Ganos的2D矢量快显功能提供了亿级二维矢量数据的高效可视化解决方案。通过创新性的稀疏金字塔索引技术,Ganos大幅减少了切片构建时间和存储开销,并支持动态更新。相较于传统离线切片方式,Ganos能在普通云实例上快速构建和更新切片,显著提升了响应速度和更新效率,同时大幅降低了存储需求。本文详细介绍如何使用Ganos进行2D矢量数据的准备、稀疏金字塔构建、切片获取及Web地图服务搭建,帮助读者快速上手体验这一高效功能。

先来一张动图感受一下。
2D矢量快显是由Ganos的快显引擎提供的对亿级二维矢量数据(点、线、面)的可视化功能。现有系统支持矢量数据可视化,主要是采用离线切片的方式,当可视化请求到来时,将预先建好的切片经过简单处理后返回给用户系统。这种处理方案有两大痛点:一个是“慢”,大规模数据集上运行离线切片往往需要十几个小时甚至几天的时间才能完成;另一个是“大”,在最高支持16层级缩放的地图服务中,需要事先存储几十亿的切片,存储开销很大。另外由于“慢”这个问题,还衍生出了对数据更新不友好这个新的问题。这个问题在数据管理能力较弱的GIS系统中尤为明显,由于缺乏对数据更新内容、及其对切片影响范围的感知,一旦发生数据更新,哪怕只是小范围的局部更新,也只能采用耗时费力的全盘重建切片的方式来解决。
Ganos的2D矢量快显功能能够很好地解决以上问题,Ganos创新性地提出了稀疏金字塔索引,跳过对数据稀疏区域的切片构建,结合数据库查询优化并通过视觉可见性剔除算法,过滤掉大量不影响显示效果的数据,一举解决了切片时间漫长和存储开销巨大的两大痛点。不仅如此,Ganos还支持稀疏金字塔的动态更新。当矢量数据发生了小范围内的局部更新时,Ganos可以自动识别出显示效果发生变化的切片,将切片的更新限制在尽量小的范围内,避免了需要推翻稀疏金字塔重建,具有更好的更新效率。实测在一台配置普通的8核PolarDB for PG公共云实例上,给包含七千万条房屋的数据集构建稀疏金字塔,仅需6分钟,当发生一百万条以上的数据更新时,稀疏金字塔的更新也仅需不到1分钟。响应可视化请求时,平均返回一个切片的时间不到1毫秒。拥有如此高效的效率,但是所需要的磁盘存储空间却只需3GB左右。本文将结合实例帮助读者快速上手体验Ganos的矢量快显功能。
2D矢量快显使用步骤
准备数据表
首先需要准备一个包含Geometry属性的数据表。Ganos提供了丰富的函数可用于将矢量数据写入到数据表中,包括常见的ST_GeomFromText、ST_GeomFromWKT、ST_GeomFromWKB等。要使用Ganos的矢量快显功能,只需要数据表包含主键id和Geometry属性即可,十分简单。在开始使用Ganos的快显功能之前,先需要使用如下命令创建Ganos的快显功能的扩展:
CREATE EXTENSIION ganos_geometry_pyramid CASCADE;
执行如下命令创建一个只包含主键id和geom列的数据表:
CREATE TABLE try_ganos_viz(id SERIAL NOT NULL, geom Geometry);
用户可以使用脚本或其它工具将一个矢量数据集导入该表,只需要该脚本或工具能够生成类似如下的SQL命令即可(假设数据集文件存储的是4326坐标系的WKT格式数据):
INSERT INTO try_ganos_viz(geom) VALUES (ST_GeomFromText('WKT FORMAT TEXT', 4326));
上述命令中,是将数据以4326坐标系格式存储到数据库中,用户也可以指定存储3857坐标系格式。导入所有数据后,还需要对Geometry属性列构建一个空间索引,可执行如下命令:
CREATE INDEX ON try_ganos_viz USING gist(geom);
到此,就完成了数据表的准备,可以开始体验矢量快显功能了。
构建稀疏金字塔
构建稀疏金字塔功能已经被封装成自定义函数,方法原型如下:
boolean ST_BuildPyramid(cstring table_name, cstring geom_col_name, cstring id_col_name, cstring config);
上述第一个参数表示矢量数据所在的表格的名字,第二个参数表示Geometry的列名,第三个参数表示id的列名,第四个参数指定构建金字塔时的配置,配置信息是以JSON格式指定,可以为空,接下来会以示例的形式做更多介绍。
执行以下命令,创建一个使用默认参数配置的稀疏金字塔:
SELECT ST_BuildPyramid('try_ganos_viz', 'geom', 'id', '');
用户可以在config参数中指定构建稀疏金字塔时的并行度,执行如下命令以32并行度构建矢量金字塔:
SELECT ST_BuildPyramid('try_ganos_viz', 'geom', 'id', '{"parallel":32}');
和PostGIS一样,Ganos也允许用户定义切片的像素大小和裁切范围,这是通过在config参数中提供"tileSize"和“tileExtend”的值来实现的,示例如下:
SELECT ST_BuildPyramid('try_ganos_viz', 'geom', 'id', '{"parallel":32, "tileSize":4096, "tileExtend":128}');
Ganos允许用户根据其对性能和存储开销的综合考量,用参数控制稀疏金字塔的结构。若用户相对于存储开销更加看重查询性能,可以通过设置较小的"splitSize"值(如果一个切片中的要素小于splitSize,则该切片是在查询时动态构建的),使得尽量多的切片都是事先已经构建好的;若用户更加看重存储开销,除了可以设置较大的“splitSize”值以外,还可以设置较小的"maxLevel"控制金字塔的高度,从而减少需要维护的切片的数量。下面的示例,构建了一个高度为10,"splitSize"值为1000的稀疏金字塔:
SELECT ST_BuildPyramid('try_ganos_viz', 'geom', '{"maxLevel":10, "splitSize":1000}');
除了上述常用的config参数以外,Ganos还提供了“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"}}
]}');
在上个例子中,“filter”对应的过滤条件可以是任意SQL语句中位于WHERE后面的条件语句。
更多关于ST_BuildPyramid函数的参数信息,请参考官方文档。
除了ST_BuildPyramid函数,Ganos新增ST_BuildPyramidUseGeomSideLen函数构建矢量金字塔。ST_BuildPyramidUseGeomSideLen相对于ST_BuildPyramid进行了性能优化,在数据表中含有很多面积很小的要素时,能够有效提升稀疏金字塔的构建效率。预要使用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相同,其函数原型如下所示:
boolean ST_BuildPyramidUseGeomSideLen(cstring table, cstring geom_field, cstring geom_side_len_field, cstring fid, cstring config);
执行下列语句,对新增了max_side_len列的try_ganos_viz表构建稀疏金字塔:
SELECT ST_BuildPyramidUseGeomSideLen('try_ganos_viz', 'geom', 'max_side_len', 'id', '{"parallel":32}');
ST_BuildPyramidUseGeomSideLen同样支持各类config参数,允许用户灵活地调整稀疏金字塔的构建规则。
更新金字塔
在数据表的数据发生数据更新时,Ganos提供了ST_UpdatePyramid函数对金字塔进行更新,用户只需提供数据更新的外包框范围即可,其函数原型如下:
boolean ST_UpdatePyramid(cstring table, cstring geom_field, cstring id_field, BOX2D update_extent, cstring rules) ;
其中update_extent和rules分别表示数据更新所在的外包框范围和JSON格式的更新参数值。rules参数包括updateBoxScale和sourceSRS,其含义如下
• 参数updateBoxScale会影响到更新自底向上最高传递到哪一层,如updateBoxScale=2,用户指定的更新区域的最大宽高值为100时,只会更新到层次为1的切片(假设全局范围是全球经纬度),因为层次为1的切片的最大宽高值除以100小于2,而层次为0的切片的最大宽高值除以100大于2。updateBoxScale默认值为10。
• sourceSRS指定参数update_extent的EPSG格式,默认值为4326。
假设用户在[(lon1, lat1), (lon2, lat2)]的经纬度范围内插入了多条数据,如果用户想要所有被影响到的切片都被更新(假设maxLevel=16),可以执行下列语句:
SELECT ST_UpdatePyramid('try_ganos_viz', 'geom', 'id', ST_MakeEnvelope(0,-10,20,30, 4326), '{"updateBoxScale":100000}');
在上述例子中假设lon1=0, lat1=-10, lon2=20, lat2=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对金字塔进行重建。
获取矢量切片
矢量切片具有矢量保留要素信息的优点,在地图服务中,多级缩放相比于传统的栅格切片更加平滑,视觉效果更优。Ganos提供了ST_Tile函数,允许用户实时调用获取矢量切片,其函数原型如下:
bytea ST_Tile(cstring name, cstring key);
bytea ST_Tile(cstring name, int x, int y, int z);
第一个函数原型中的key为'z_x_y'的格式,表示切片的编号,z表示切片所在的层级,x表示x轴方向的编号(从左到右递增),y表示表示y轴方向的编号(从上到下递增)。执行以下语句之一,返回中国所在的切片z=1,x=1,y=0:
SELECT ST_Tile('try_ganos_viz', '1_1_0');
SELECT ST_Tile('try_ganos_viz', 1, 0, 1);
获取栅格切片
Ganos同样支持仍然广泛使用的栅格切片。栅格切片是图片形式的切片,相对于矢量切片,不支持客户端动态渲染,对客户端系统的性能要求较低。Ganos提供了ST_AsPng函数,允许在数据库端将矢量数据按需动态渲染为栅格切片,再返回给用户。该函数提供了最基础的栅格符号化能力,更多面向一些不需要复杂符号化的轻量级场景。ST_AsPng的函数原型如下:
bytea ST_AsPng(cstring name, cstring key, cstring style);
bytea ST_AsPng(cstring name, int x, int y, int z, cstring style);
ST_AsPng函数相对于ST_Tile函数新增了style参数。该参数是JSON格式,指定了渲染样式,包含的渲染样式如下:
• point_size: 点大小,单位为像素
• line_width: 线宽,指定组成线和面要素的外包框的线的像素宽度
• line_color: 线渲染颜色,对线要素和面要素的外包框起作用。前六位为16进制颜色,后两位为16进制透明度
• fill_color: 填充颜色,对面要素起作用
• background: 背景色,通常设置为FFFFF00,即纯透明
调用以下语句返回切片编号为'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"}');
基于2D矢量快显的Web地图服务
上述内容介绍了如何使用Ganos的2D矢量快显功能,接下来我们讲解如何基于Ganos的矢量快显功能,快速搭建一个Web地图服务。
全栈架构
该Web服务由数据库、Python服务端和用户端三部分组成,全栈架构图如下所示:
数据库
需要导入数据到数据库中,并构建好稀疏金字塔所需要的索引,具体内容参考前文。
服务器端代码
为了代码简洁,更侧重于逻辑的描述,我们选择了Python(兼容Python3.6及以上版本)作为后端语言,Web框架使用了基于Python的Flask(使用pip install flask进行安装)框架,数据库连接框架使用了基于Python的Psycopg2(使用pip install psycopg2进行安装)。
我们在后端首先建立了矢量金字塔,其后分别实现了两个接口,矢量切片接口使用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=DB_NAME user=USER_NAME password=PASSWORD host=YOUR_HOST port=PORT_NO"
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")

定义表名,geom字段名、id字段名,用户可按照自己的情况进行修改

pngViewer = PngViewer(CONNECTION, 'buildings', 'geom', 'id')
@app.route('/vector/png///')
def vector_png(z, x, y):
png = pngViewer.get_png(x, y, z)
return Response(
response=png,
mimetype="image/png"
)

定义表名,geom字段名、id字段名,用户可按照自己的情况进行修改

mvtViewer = MvtViewer(CONNECTION, 'points', 'geom', 'id')
@app.route('/vector/mvt///')
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命令即可启动服务。
从以上代码可以看出,无论我们使用何种语言、何种框架,我们只需将访问2D矢量或栅格切片的SQL语句封装为接口即可实现完全相同的功能。相比发布传统的地图服务,借助Ganos的2D矢量快显功能实现在线可视化是更加轻量好用的选择:
• 针对栅格瓦片,可以在通过改变代码进行样式控制,灵活性大大增强。
• 无需引入第三方的其他组件,也不需要进行针对性优化,就有令人满意的响应性能。
• 可以任意选择使用者熟悉的编程语言与框架,也无需复杂专业的参数配置,对非地理从业者更加的友好。
用户端代码
我们选用Mapbox作为前端地图框架,展示服务器端提供的矢量切片层和栅格切片层,并为矢量切片层配置了渲染参数。为了方便说明,前端代码同时添加了矢量、栅格两个图层,实际使用时可以按需选择。
在后端代码的同一文件目录下新建名为Vector.html的文件,写入以下代码,在服务器端服务启动后,就可以通过http://localhost:5000/vector访问了。
<!DOCTYPE html>











相关文章
|
SQL 存储 数据可视化
Ganos矢量快显功能上手系列2:增强的MVT能力
本文主要介绍Ganos新增的2D矢量动态切片函数及其使用方法。新增的矢量动态切片函数能够大幅提升可视化效率,有效解决小比例尺MVT显示耗时久的问题。和PostGIS相比,小比例尺MVT的可视化效率提升可达60%以上。
|
存储 SQL 分布式计算
用户画像系列—如何从0到1建设用户画像
用户画像系列—如何从0到1建设用户画像
327 0
|
网络协议
nmtui命令详解
【4月更文挑战第9天】`nmtui`是NetworkManager的文本用户界面工具,用于终端中的网络配置和管理。用户可通过它查看网络设置、配置接口、修改设置、添加/删除连接及调整连接优先级。操作步骤包括启动nmtui,选择编辑连接,修改网卡设置,保存并退出。此工具包含在NetworkManager-tui子软件包中,配置更改会持久生效。了解更多详情可参考相关文档。
1208 1
|
存储 算法 Cloud Native
Ganos地理网格引擎支撑无人机路径规划能力实践
随着新能源技术的迅猛发展,低空经济已经逐步成为新的战略性新兴产业,但不同于传统的地表活动,低空活动具有立体性、区域性、融合性等特点,这些特点对于如何安全引导低空活动的顺利开展带来了一系列需要解决的技术问题。Ganos地理网格引擎提供了基于网格的路径规划能力,可以使用DEM、DSM、倾斜摄影等数据构建复杂环境下的无人机路径规划应用。
|
7月前
|
前端开发 搜索推荐
使用DeepSeek快速创建的个人网站
这是一份使用DeepSeek快速创建个人网站的10分钟指南。内容分为四个步骤:搭建基础架构(HTML框架)、设计核心内容区块(关于我、作品展示等)、快速配置样式(CSS美化页面)以及添加联系表单并部署到GitHub Pages。通过简单的代码和DeepSeek的智能辅助功能,用户可以轻松实现个性化调整,如更换主题色、增加模块或优化响应式设计。虽然整体流程简单高效,但可能因功能有限或美观度不足而需进一步扩展与改进。
586 11
|
7月前
|
机器学习/深度学习 存储 人工智能
Attention优化重大突破!显存减半效率倍增
本文探讨了Transformer中Attention机制的演变与优化。从2017年Transformer提出以来,各种改进如MQA、GQA、MLA等层出不穷,旨在降低计算复杂度和显存消耗,同时保持模型性能。文章首先介绍了Attention的基本原理,通过QKV矩阵运算实现序列建模。接着分析了优化方法:kv caching将计算复杂度从O(n^3)降至O(n^2),但带来显存压力;MQA、GQA等通过减少或压缩K/V降低显存需求;而NSV、MoBA等稀疏化研究进一步缓解长序列下的计算与存储负担,推动大模型向更长上下文扩展。
|
存储 内存技术
内存条RAM详细指南
内存条(RAM)是电脑中用于临时存储数据和程序的部件,CPU依赖它执行操作。内存条经历了从主内存扩展到读写内存整体的发展,常见类型包括SDRAM和DDR SDRAM。内存容量、存取时间和奇偶校验是衡量其性能的关键指标。在选购时,应考虑类型、容量、速度和品牌,知名品牌的内存条提供更好的可靠性和稳定性。
3491 2
|
存储 分布式计算 监控
动态资源管理
动态资源管理
399 66
|
10月前
|
NoSQL 关系型数据库 分布式数据库
PolarDB图数据库快速入门
图数据库(Graph Database)专门存储图数据,适合处理社交网络、知识图谱等复杂关系。它使用图查询语言(如Cypher、Gremlin)进行操作。PolarDB兼容OpenCypher语法,支持创建、查询、更新和删除图数据,包括模式匹配、过滤、MERGE避免重复、可视化工具等功能,简化了图数据的管理和应用。
|
11月前
pyqt6 实现弹窗案例
本文介绍了如何实现一系列弹窗效果,包括基础弹窗、消息对话框、输入对话框和清除按钮。通过设置 `self.dialog = Dialog()` 可以轻松实现基础弹窗,而消息对话框则使用 `QMessageBox` 类。输入对话框支持文本、数字和下拉列表输入,清除按钮用于清空输入框内容。每个功能都通过按钮触发相应的槽函数来实现。
724 0