基于Ganos百行代码实现亿级矢量空间数据在线可视化

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 Tair(兼容Redis),内存型 2GB
简介: 本文介绍如何使用RDS PG或PolarDB(兼容PG版或Oracle版)的Ganos时空引擎提供的数据库快显技术,仅用百行代码实现亿级海量几何空间数据的在线快速显示和流畅地图交互,且无需关注切片存储和效率问题。

作者:李鹤



01 引言


如何对时空数据库中的亿级矢量空间数据进行在线可视化一直是业界难题。因数据体量大,传统方法需要将数据库中数据进行基于缓存切片的服务发布才能可视化,操作流程冗长,且有一大堆需要考虑的问题:


  • 如果对矢量数据进行预切片,数据要切多久?切多少级合适?存储瓦片的硬盘空间够用吗?
  • 如果使用实时瓦片,实时渲染瓦片的响应时间能保证吗?
  • 如果使用矢量瓦片,小比例尺的瓦片可能会有多大体积?传输会不会成为瓶颈?前端渲染能承受多大的数据量?


如果是要快速浏览数据库中的大规模在线数据,传统用于“底图服务”的离线切片生产流程几乎无解,不但费时费力,又无法在线联机处理。黑科技来了,本文介绍如何使用RDS PG或PolarDB(兼容PG版或Oracle版)的Ganos时空引擎提供的数据库快显技术,仅用百行代码实现亿级海量几何空间数据的在线快速显示和流畅地图交互,且无需关注切片存储和效率问题。




02 技术特性解读


Ganos的在线快显处理的核心是将数据库和可视化进行了关联,提供了一种新的可视化索引技术——稀疏矢量金字塔(Sparse Vector Pyramid,SVP)索引。SVP具备两个关键特性:快与省


其中,快指两个阶段的快:

  • 金字塔创建快:Ganos利用空间索引对数据在空间上进行密集度划分,根据密集度建立一种稀疏矢量金字塔索引,相比传统切图流程减少了90%的数据计算量。同时,创建金字塔采用了完全并行处理模式,即使1亿条地类图斑数据生成金字塔也仅需耗费约10分钟时间。
  • 数据展现快:Ganos采用了视觉可见性剔除算法,根据Z-order排序,过滤掉大量不影响显示效果的数据,从而加快实时显示的效率。Ganos支持直接输出PNG格式的栅格瓦片和MVT格式的矢量瓦片,1亿地类图斑数据实时渲染显示的响应时间都达到秒级。


省也具有两个维度:

  • 节省磁盘空间:1亿条地类图斑数据生成金字塔索引仅仅占据原表5%大小的额外空间。
  • 节省开发时间:仅使用简单的SQL语句,通过调整语句参数即可灵活控制显示效果。




03 使用步骤


Ganos的快显引擎使用上非常简洁,已高度封装了SQL函数。需要注意的是,第一次使用快显引擎之前,需要显式创建对应的扩展模块,执行的语句如下:


CREATE EXTENSION ganos_geometry_pyramid CASCADE;


通过执行以上语句,快显引擎的计算组件将会被加载起来。


3.1 建立稀疏矢量金字塔


假设您已创建了某个矢量大表并导入了数据,接着就可以使用Ganos的st_buildpyramid方法创建矢量金字塔。


方法原型如下,更详细的参数描述可以参考官方文档


boolean ST_BuildPyramid(cstring table, cstring geom, cstring fid, cstring config)

注:*左右滑动阅览


其中

  • table:矢量数据所在的表名。
  • geom:矢量字段名。
  • fid:矢量要素记录的唯一标识,支持Int4/Int8类型。
  • config:json格式的配置参数字符串。
  • 在本例中,我们指定矢量金字塔的名称和使用的逻辑瓦片大小(这个瓦片大小并非真实存在的瓦片,仅表示一种空间上的逻辑划分)


实际调用如下:


ST_BuildPyramid('points', 'geom', 'gid', '{"name":"points_geom","tileSize":512}')

注:*左右滑动阅览


我们为表points的geom字段创建了一个矢量金字塔,金字塔名我们指定为points_geom,同时设定金字塔的逻辑瓦片大小为512。


3. 2 获取栅格瓦片


栅格瓦片是图片形式的瓦片(Tile),是使用最广泛的一种地图瓦片形式。Ganos的ST_AsPng方法提供了在数据库端将矢量数据按需动态渲染为栅格瓦片的功能。该功能提供了最基础的栅格符号化能力,更多面向一些不需要复杂符号化的轻量级场景,如数管系统中。


方法原型如下更详细的参数描述可以参考官方文档


bytes ST_AsPng( cstring name, cstring tile, cstring style)


其中

  • name:金字塔表名。
  • tile:瓦片索引行列号,Z_X_Y的形式。
  • style:渲染样式。我们可以通过如下参数调节渲染效果:
  • point_size:点大小,单位为像素。
  • line_width:线宽,对线要素和面要素的外边框起作用,单位为像素。
  • line_color:线渲染颜色,对线要素和面要素的外边框起作用。前6位为16进制颜色,后2位为16进制透明度。
  • fill_color:填充颜色,对面要素起作用。
  • background:背景色。一般设置为FFFFFF00,即纯透明。


实际调用如下:

ST_AsPng('points_geom', '1_2_1','{"point_size": 5,"line_width": 2,"line_color": "#003399FF","fill_color": "#6699CCCC","background": "#FFFFFF00"}')

注:*左右滑动阅览


我们从矢量金字塔中获取到索引行列号为x=2,y=1,z=1的矢量瓦片,并将该矢量瓦片按照我们配置的样式渲染为栅格瓦片,返回PNG格式的图片。


3. 3 获取矢量瓦片


矢量瓦片是新兴的地图瓦片技术,具有在前端配置样式的灵活特性,使用WebGL渲染,效果也更加美观,Mapbox等地图框架可以方便的支持这一格式。使用Ganos的ST_Tile方法可以将矢量金字塔中的数据以矢量瓦片的形式提供。


方法原型如下,更详细的参数描述可以参考官方文档


bytea ST_Tile(cstring name, cstring key);


其中

  • name:金字塔名。在本例中为表名_矢量字段名
  • key:瓦片索引行列号,Z_X_Y的形式。


我们只需要提供标准的TMS行列号和金字塔表的名称即可调用。


实际调用如下:


ST_Tile('points_geom', '1_2_1');


我们从矢量金字塔中获取到索引行列号为x=2,y=1,z=1的矢量瓦片,返回数据为标准的MVT格式。




04 实战案例


4.1 测试数据


我们准备两份矢量数据作为测试样例。


buildings表为面数据,数据总计1.25亿条,展现使用栅格瓦片的在线可视化效果。


gid|geom                                                                                                                                                                                                                                                           |
---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
  1|MULTIPOLYGON(((-88.066953 34.916114 0,-88.066704 34.916114 0,-88.066704 34.91602 0,-88.066953 34.91602 0,-88.066953 34.916114 0)))                 
  2|MULTIPOLYGON(((-87.924658 34.994797 0,-87.924791 34.99476 0,-87.924817 34.994824 0,-87.924685 34.994861 0,-87.924658 34.994797 0)))                

注:*左右滑动阅览


points表为点数据,数据总计10.7万条,展现使用矢量瓦片的在线可视化效果。


id|geom                          |
--|------------------------------|
 1|POINT (113.5350205 22.1851929)|
 2|POINT (113.5334245 22.1829781)|


4.2 全栈架构


数据库-快显全栈架构包含数据库服务器、python服务端和用户端三个部分,全栈架构如下图所示。


1.png


4.3 服务端代码


为了代码简洁,更侧重于逻辑的描述,我们选择了Python(兼容Python3.6及以上版本)作为后端语言,Web框架使用了基于Python的Flask(使用pip install 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=postgres user=postgres password=postgres host=YOUR_HOST port=5432"
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语句封装为接口即可实现完全相同的功能。相比发布传统的地图服务,借助Ganos的矢量金字塔功能实现在线可视化是更加轻量好用的选择:


  • 针对栅格瓦片,可以在通过改变代码进行样式控制,灵活性大大增强。
  • 无需引入第三方的其他组件,也不需要进行针对性优化,就有令人满意的响应性能。
  • 可以任意选择使用者熟悉的编程语言与框架,也无需复杂专业的参数配置,对非地理从业者更加的友好。


4.4 用户端代码


我们选用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>

注:*左右滑动阅览


4.5 矢量瓦片的动态效果


2.gif


可以在前端调节不同效果。调整为新的图层参数后效果如下:


{
  "circle-radius": 4,
  "circle-color": "#000000",
  "circle-stroke-width": 2,
  "circle-opacity": 0.3,
  "circle-stroke-color": "#003399",
  "circle-stroke-opacity": 0.9,
}


3.gif


4.6 栅格瓦片的动态效果


1111.gif




05 与PGADmin集成


PG数据库管理工具PGAdmin原生支持矢量数据的可视化,但因缺乏快显技术,仅能单对象显示或有限结果集显示,无法对大规模矢量数据进行畅快淋漓的全局浏览。我们将Ganos的矢量快显功能与PGAdmin集成,数据入库即可在线浏览全局,快速评估数据概况,大大增强了数据管理的使用体验。


5.gif




06 总结

本文从稀疏矢量金字塔的原理与优势入手,介绍了如何利用Ganos实现在线可视化服务的各种功能,并最终通过百行代码实现了一个可以应对亿级数据的地图可视化服务。读者可以进一步在可视化基础上,利用PG/PolarDB Ganos的服务器端快速查询和分析能力进行对象属性查询、空间圈选、空间分析等更复杂功能。这就是Ganos所带来的大规模空间图形显示加速黑科技——稀疏矢量金字塔索引带来的变革。如果您对此有兴趣,更多信息可以参考官方文档


目录
相关文章
|
SQL 存储 数据可视化
Ganos矢量快显功能上手系列2:增强的MVT能力
本文主要介绍Ganos新增的2D矢量动态切片函数及其使用方法。新增的矢量动态切片函数能够大幅提升可视化效率,有效解决小比例尺MVT显示耗时久的问题。和PostGIS相比,小比例尺MVT的可视化效率提升可达60%以上。
|
3月前
|
SQL 关系型数据库 分布式数据库
用Ganos低代码实现免切片遥感影像浏览
本文介绍了一种基于PolarDB兼容PostgreSQL 14的高效栅格数据管理和可视化方案。推荐配置包括4核CPU、16GB内存、50GB磁盘等。通过创建扩展并上传影像至OSS,利用SQL语句完成数据导入、镶嵌、匀色及金字塔构建。重点介绍了使用ST_AsTile函数动态生成标准瓦片的方法,支持多种格式和增强方式。前端通过Python实现服务接口,实现实时、高效的数据展示。此方案具有实时性强、存储成本低等优点,适合快速可视化大量栅格数据。
51 0
|
3月前
|
存储 数据可视化 关系型数据库
Ganos矢量快显功能上手
Ganos是由阿里云数据库产品事业部与达摩院合作研发的新一代云原生位置智能引擎,集成于PolarDB、Lindorm、AnalyticDB和RDS PG等核心产品中。Ganos具备十大核心引擎,为数据库提供一体化时空数据处理能力。其2D矢量快显功能采用稀疏金字塔索引技术,显著缩短切片时间和减少存储开销,支持动态更新。通过简单的表结构准备和索引构建,用户可快速实现高效的数据可视化。此外,Ganos还支持3D矢量数据可视化,进一步拓展了应用场景。
58 0
|
3月前
|
存储 SQL 数据可视化
三维引擎系列(三):BIM数据管理与可视化功能
Ganos三维引擎的BIM数据管理分析解决方案,旨在充分发挥BIM模型价值,满足数字孪生技术发展的高精度需求。该方案通过结构化拆解BIM数据,实现统一管理和联合查询;支持精细化计算BIM模型指标,并与规划红线对比;同时提供高效渲染能力。Ganos内置多种功能,如ST_ImportIFC导入IFC格式数据,ST_As3DTiles生成3D Tiles瓦片数据结构,无需依赖第三方软件即可完成BIM数据的存储、计算与可视化展示。此外,通过简单的后端服务即可实现与渲染引擎的无缝对接,显著提升三维空间计算效率。
42 0
|
7月前
|
数据可视化 数据挖掘 关系型数据库
R语言中的地理空间数据分析
【4月更文挑战第26天】R语言在地理空间数据分析中发挥着关键作用,拥有如&quot;sp&quot;、&quot;sf&quot;和&quot;rgdal&quot;等扩展包。
154 1
|
7月前
|
机器学习/深度学习 数据采集 数据可视化
R语言二手车汽车销售数据可视化探索:预处理、平滑密度图、地理空间可视化(上)
R语言二手车汽车销售数据可视化探索:预处理、平滑密度图、地理空间可视化
|
7月前
|
数据可视化 AndFix
R语言二手车汽车销售数据可视化探索:预处理、平滑密度图、地理空间可视化(下)
R语言二手车汽车销售数据可视化探索:预处理、平滑密度图、地理空间可视化
|
7月前
|
数据可视化 定位技术
R语言二手车汽车销售数据可视化探索:预处理、平滑密度图、地理空间可视化(中)
R语言二手车汽车销售数据可视化探索:预处理、平滑密度图、地理空间可视化
|
7月前
|
存储 数据挖掘 数据处理
地理空间数据分析与NumPy的高效融合
【4月更文挑战第17天】本文探讨了地理空间数据与NumPy的高效结合在数据分析中的应用。地理空间数据具有多维性、空间相关性和复杂性,NumPy通过提供高效的数据存储、强大的数学运算及灵活的数据处理,应对这些挑战。实践案例展示了如何使用NumPy进行坐标处理、统计分析和滤波等,揭示数据规律。未来,随着数据增长和领域拓展,NumPy在地理空间数据分析中的作用将更加显著,期待更多创新方法应对新挑战。
|
7月前
|
数据可视化 定位技术
Tableau可视化设计案例-06Tableau填充地图,多维地图,混合地图
Tableau可视化设计案例-06Tableau填充地图,多维地图,混合地图
下一篇
DataWorks