乐趣区

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

简介:本文介绍如何应用 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。

  1. 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 格局的图片。

  1. 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 服务端和用户端三个局部,全栈架构如下图所示。

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 矢量瓦片的动态效果

能够在前端调节不同成果。调整为新的图层参数后成果如下:

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

4.6 栅格瓦片的动态效果

05 与 PGADmin 集成

PG 数据库管理工具 PGAdmin 原生反对矢量数据的可视化,但因不足快显技术,仅能单对象显示或无限后果集显示,无奈对大规模矢量数据进行畅快淋漓的全局浏览。咱们将 Ganos 的矢量快显性能与 PGAdmin 集成,数据入库即可在线浏览全局,疾速评估数据详情,大大加强了数据管理的应用体验。

06 总结

本文从稠密矢量金字塔的原理与劣势动手,介绍了如何利用 Ganos 实现在线可视化服务的各种性能,并最终通过百行代码实现了一个能够应答亿级数据的地图可视化服务。读者能够进一步在可视化根底上,利用 PG/PolarDB Ganos 的服务器端疾速查问和剖析能力进行对象属性查问、空间圈选、空间剖析等更简单性能。这就是 Ganos 所带来的大规模空间图形显示减速黑科技——稠密矢量金字塔索引带来的改革。

作者:李鹤
原文链接
本文为阿里云原创内容,未经容许不得转载

退出移动版