1. 关于Ganos
Ganos是阿里云数据库产品事业部联合飞天数据库与存储实验室共同研发的新一代云原生位置智能引擎,它将时空数据处理能力融入了云原生关系型数据库PolarDB-PG、云原生多模数据库Lindorm、云原生数据仓库AnalyticDB-PG和云数据库RDS-PG等核心产品中。Ganos目前拥有几何、栅格、轨迹、表面网格、体网格、3D实景、点云、路径、地理网格、快显十大核心引擎,为数据库构建了面向新型物理世界多模多态数据的存储、查询、分析、服务等一体化能力。
本文主要介绍Ganos实时热力聚合查询并动态输出热力瓦片能力,依托阿里云PolarDB PostgreSQL产品、ADB PostgreSQL和RDS PostgreSQL 三款数据库建设输出。
2. 关于热力瓦片
2.1 什么是热力瓦片
热力瓦片(HeatMap Tile,简称HMT)底层基于Ganos首创的大规模矢量/轨迹数据实时热力聚合查询技术,用于将查询处理结果即时返回client的数据交换结构,它改变了热力统计分析中“聚合需要预打码、展示需要预切片”的传统方式,可针对百万级、千万级、亿级规模数据秒级聚合并渲染。HMT支持多种常用聚合函数与代数表达式,客户可选择统计业务关心的指标并随地图的放大缩小进行不同层级的动态计算和实时绘制,极大程度提升了业务效率,为客户产品带来的更多可能。在本年度的云栖大会上,Ganos发布了这项能力,并现场展示了基于HMT构建大规模运输轨迹实时查询聚合的案例,帮助客户把以往需要线下预处理才能发布的数据产品完全在线化,该功能得到了很大行业反响与认可。
2.2 热力瓦片的使用场景
热力瓦片的特点是空间数据的实时聚合与渲染,主要应用于拥有海量矢量数据并需要实时统计分析的业务场景,比如:
- 交通运输类:根据运输工具(车、船等)的历史轨迹线,聚合出全域范围内的实时热力,并可根据时间(冬季、夏季)、起点/终点、类型(货车、客车)等附带条件进行过滤后实时生成相应的热力;
- 城市管理类:根据房屋建筑底面数据聚合出全域范围内城市/农村的建筑密度、建筑平均高度、建筑总面积等单项指标,并可结合地块信息聚合出容积率等复合指标;
- 共享出行类:根据共享出行设备的轨迹点,聚合出全域范围内设备的停靠区域热力,并可根据设备事件(开锁/关锁、上车/下车、事故、损坏)、流向等条件分析共享出行设备的调度运维策略;
2.3 热力瓦片的技术优势
相较于基于H3或S2网格预打码聚合的方式,HMT热力瓦片拥有如下优势:
- 效率极高,无需要预打码,不增加存储成本。HMT的聚合技术与H3/S2等网格聚合方式在技术特点及应用场景方面都有不同,网格聚合方式往往面向需要以网格编码作为检索条件的应用场景,因为它需要预先设定某一精度层级,之后在该精度下针对矢量数据进行赋码,再根据编码进行相关统计;而HMT不需要提前针对数据预打码,完全可以根据当前视口范围进行聚合,随着视口的放大缩小,聚合会实时进行,整个过程针对各类几何对象效率一致,均可达到亿级规模秒级聚合渲染;
- 方便宜用,聚合结果直接可视化。HMT提供了聚合结果快速瓦片化的能力,可以将聚合结果直接与前端渲染引擎进行可视化对接,保证所见即所得。同时HMT还提供了一系列的统计函数,帮助用户快速自动化生成最佳的渲染色表,确保前端的最优表现;
经多个客户真实场景测试,HMT的聚合有着极高的效率,基本可以实现亿级规模全图聚合秒级完成:
应用场景 |
数据量 |
瓦片范围 |
聚合效率 |
轨迹聚合 |
轨迹线:45万 轨迹点:3100万 |
全球级别 512*512瓦片 |
372ms |
建筑底面聚合 |
建筑底面:3.08亿 |
全球级别, 512*512瓦片 |
17s |
注:上述数据均为全球展示尺度下全量数据聚合效率,随着地图不断放大,效率不断提升;
2.4 热力瓦片的功能介绍
热力瓦片包含一系列的SQL函数,用于解决热力瓦片的生成与统计问题,具体包括:
- ST_AsHMT:将一组几何对象或轨迹对象按照指定范围和指定分辨率转为热力矩阵瓦片;
- ST_HMTAsArray:将热力图瓦片转为基于数组矩阵的表示方法,方便进行查看;
- ST_HMTStats:计算热力图瓦片统计信息;
- ST_HMTAsRaster:将热力瓦片转为Raster对象,方便进行查看以及计算操作;
3. 热力瓦片最佳实践
3.1 操作步骤
- 将几何或轨迹数据入库到数据库中,建议使用FDW的方式进行入库。确保所有的对象都具备相同的空间参考系统(可以通过ST_Srid函数进行确认)
- 对几何列或轨迹列创建空间索引
CREATE INDEX index_name ON table_name USING GIST(column_name)
- 根据空间范围进行热力瓦片的查询
可以针对网格内的对象数量的进行热力聚合
SELECT ST_AsHMT(column_name, --geometry type ST_MakeEnvelope(0, 0, 10, 10, 4326), -- Extent 512, -- Width 512 -- height ) FROM table_name WHERE column_name && ST_MakeEnvelope(0, 0, 10, 10, 4326);
也可以对某个网格内的数值进行聚合,使用value字段中的数值进行求和操作
SELECT ST_AsHMT(column_name, --geometry type ST_MakeEnvelope(0, 0, 10, 10, 4326), -- Extent 512, -- Width 512, -- height value -- value column ) FROM table_name WHERE column_name && ST_MakeEnvelope(0, 0, 10, 10, 4326);
其中ST_MakeEnvelope可以使用ST_TileEnvelope函数获取瓦片范围。
同样可以增加其他过滤条件:
SELECT ST_AsHMT(column_name, --geometry type ST_MakeEnvelope(0, 0, 10, 10, 4326), -- Extent 512, -- Width 512, -- height value -- value column ) FROM table_name WHERE column_name && ST_MakeEnvelope(0, 0, 10, 10, 4326); AND name like 'xxxx%' AND value > 100;
3.2 使用技巧
- 在数据量较大时采用并行提升性能,以下以并行度为16为例:
SET max_worker_processes = 300; -- Maximum number of background processes set max_parallel_workers = 260; -- maximum number of workers set max_parallel_workers_per_gather = 16; --maximum number of workers that can be started by a single Gather or Gather Merge node alter table table_name set (parallel_workers=16); set force_parallel_mode = on;
在实际使用中可以根据视口范围进行设置,如较高层级下使用16并行,较低层级下不使用并行等。
在CPU足够的情况下,为了确保每个查询都可以使用并行,需要将max_worker_processes 和 max_parallel_workers 设置为并行度和并发数的乘积。 参考 postgresql的官方文档
https://www.postgresql.org/docs/current/runtime-config-resource.html
- 瓦片大小
通常情况下使用512*512 的瓦片重采样到256*256的瓦片来避免锯齿的问题。但在在特殊情况下,如数据量非常大时,每个瓦片计算非常耗时,可以使用大瓦片(1024*1024)来降低瓦片的获取数量提升性能。
- 使用&& 操作符进行空间过滤
由于ST_AsHMT的计算比ST_Intersects要快很多, 在索引过滤时使用 && 替代ST_Intersects 过滤对象
SELECT ST_AsHMT(column_name, --geometry type ST_MakeEnvelope(0, 0, 10, 10, 4326), -- Extent 512, -- Width 512, -- height value -- value column ) FROM table_name WHERE column_name && ST_MakeEnvelope(0, 0, 10, 10, 4326);
- 查询范围进行空间参考转换
当查询范围和几何对象空间范围不一致时,将查询范围进行空间参考转换后查询。否则自动转换可能会引起性能较低。获取瓦片后将图片转换为指定的空间参考进行展示
SELECT ST_AsHMT(column_name, -- srid = 4326 ST_Transform(ST_TileEnvelope(6, 48, 32), 4326), -- Extent 512, -- Width 512, -- height value -- value column ) FROM table_name WHERE column_name && ST_Transform(ST_TileEnvelope(6, 48, 32), 4326));
- 对空间表进行vaccum full和cluster操作
Vaccum操作可以回收空闲空间,降低磁盘文件大小,在查询时能降低IO数量。
Cluster操作可以将数据组织与索引保持一致,相邻的空间数据会保存在相邻的数据页面中,访问时可以降低数据库的磁盘访问。
VACUUM full table_name; Cluster table_name using index_name;
3.3 发布服务进行浏览
我们使用Node.js编写一个简易的应用,演示热力瓦片的实际使用场景。
3.3.1 文件结构
└── hmt_server ├── app.js ├── hmt.proto ├── index.html └── package.json
其中:hmt.proto为上文中ST_AsHMT一节中介绍的proto文件,其他文件内容将在下文给出。
3.3.2 后端代码
{ "name": "hmt_server", "version": "1.0.0", "main": "app.js", "license": "ISC", "dependencies": { "chroma-js": "^2.4.2", "express": "^4.18.2", "lru-cache": "^10.1.0", "pg": "^8.11.3", "protobufjs": "^7.2.5", "sharp": "^0.32.6" } }
const express = require('express'); const { Pool } = require('pg'); const chroma = require('chroma-js'); const sharp = require("sharp"); const protobuf = require('protobufjs'); const { LRUCache } = require('lru-cache'); // 设定数据库连接 const CONNECTION = { user: 'YOUR_USER', password: 'YOUR_PWD', host: 'YOUR_HOST', database: 'YOUR_DB', port: YOUR_PORT }; // 目标表名 const TABLE_NAME = 'YOUR_TABLE'; // 目标几何字段名 const GEOMETRY_COLUMN = 'YOUR_GEOM_COLUMN'; // 设定无数据值 const NO_DATA_VALUE = 0; // 目标几何字段空间参考 const SRID = 4326 // 设定色带 const COLOR_MAP = [ ['#536edb', 1], ['#5d96a5', 3], ['#68be70', 5], ['#91d54d', 7], ['#cddf37', 9], ['#fede28', 11], ['#fda938', 13], ['#fb7447', 15], ['#f75a40', 17], ['#f24734', 19], ['#e9352a', 21], ['#da2723', 23], ['#cb181d', 25] ]; // 创建数据库连接池,默认为10个连接 const pool = new Pool(CONNECTION); // 配置颜色转换 const [colors, domains] = COLOR_MAP.reduce(([c, d], [colors, domains]) => [[c, colors], [d, domains]], [[], []]); const colorMap = chroma.scale(colors).domain(domains).mode('rgb') // 加载protobuf const hmtDecoder = protobuf.loadSync('./hmt.proto').lookupType('HMT'); // 创建一个1x1的透明png,作为空瓦片返回 const emptyPng = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAADUlEQVQImWP4//8/AwAI/AL+hc2rNAAAAABJRU5ErkJggg==', 'base64'); // 对于小比例尺瓦片(z<5),因更新相对不明显,设定一个24小时过期的缓存 const globalCache = new LRUCache({ max: 1000, ttl: 1000 * 3600 * 24 }); // 对于更大比例尺瓦片(z>=5),设定一个12小时过期的缓存,也可根据实际情况自行修改 const localCache = new LRUCache({ max: 2000, ttl: 1000 * 3600 * 12 }); // 注册Express路由 express() // 响应HTML页面 .get("/", (_, res) => res.sendFile('index.html', { root: __dirname })) // 响应热力瓦片服务 .get('/hmt/:z/:x/:y', async ({ params: { z, x, y } }, res) => { const cache = z < 5 ? globalCache : localCache; const key = `${z},${x},${y}` if (!cache.has(key)) { // 设定并行度,并调用ST_AsHMT函数,请求该区域256x256的热力瓦片 const parallel = z <= 5 ? 10 : 5; const sql = ` set max_parallel_workers = ${parallel}; set max_parallel_workers_per_gather = ${parallel}; WITH _PARAMS(_BORDER) as (VALUES(ST_Transform(ST_TileEnvelope(${key}),${SRID}))) SELECT ST_AsHMT(${GEOMETRY_COLUMN},_BORDER,256,256) tile FROM ${TABLE_NAME},_PARAMS WHERE _BORDER && ${GEOMETRY_COLUMN};` // 跳过set语句,获取ST_AsHMT函数的结果 const { rows: [{ tile }] } = (await pool.query(sql))[2]; // 若该区域无数据则直接返回空瓦片 if (!tile) cache.set(key, emptyPng); else { // 解析protobuf结果 const { type, doubleValues, intValues } = hmtDecoder.decode(tile); const { values } = type == 1 ? doubleValues : intValues; // 将数值转换为对应的颜色,并剔除无数据值 const pixels = values.reduce((_pixels, value) => { _pixels.push(colorMap(value).rgb()); _pixels.push(value <= NO_DATA_VALUE ? 0 : 255); return _pixels; }, []) // 渲染为png瓦片 const rawConfig = { raw: { width: 256, height: 256, channels: 4 } }; const renderedPng = await sharp(Uint8Array.from(pixels), rawConfig) .png().toBuffer(); cache.set(key, renderedPng); } } const tile = cache.get(key) res.set("Content-Type", "image/png").send(tile); }) // 监听5500端口 .listen(5500, () => console.log('HMT server started.'));
其中:
- 色带为连续色带,支持十六进制字符串颜色、CSS3名称颜色等表达方式,可参见chroma.js文档。
- 若想瓦片渲染效果更加平滑,可以以512x512请求数据,再降采样到256x256分辨率,只是会增加响应时间。
- 并行度的设定需要根据数据量大小,数据库实例配置及对响应速度的需求来调整。
- 本例在Z<=5时适当降低并行度。
3.3.3 前端代码
我们使用Mapbox作为前端地图SDK,其token可在此处申请查看。
由于热力瓦片最终被渲染为PNG格式,因此兼容其他绝大多数地图SDK。
<html> <head> <meta charset="utf-8"> <title>HMT Viewer</title> <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no"> <link href="https://api.mapbox.com/mapbox-gl-js/v2.14.1/mapbox-gl.css" rel="stylesheet"> <script src="https://api.mapbox.com/mapbox-gl-js/v2.14.1/mapbox-gl.js"></script> </head> <body> <div id="map" style="position: absolute;left:0; top: 0; bottom: 0; width: 100%;"></div> <script> let CENTER = [YOUR_LONGITUDE, YOUR_LATITUDE] mapboxgl.accessToken = YOUR_MAPBOX_TOKEN; const map = new mapboxgl.Map({ container: 'map', style: "mapbox://styles/mapbox/navigation-night-v1", center: CENTER, zoom: 5 }) map.on("load", () => { map.addSource('hmt_source', { type: 'raster', minzoom: 3, tiles: [`${window.location.href}hmt/{z}/{x}/{y}`], tileSize: 256, }); map.addLayer({ id: 'hmt', type: 'raster', source: 'hmt_source', }); }); </script> </body> </html>
3.3.4 安装与发布
# 定位到hmt_server目录 cd ./hmt_server # 安装依赖库 npm i # 运行热力瓦片服务 node . # 此时可以打开浏览器,登录地址 http://localhost:5500/ 查看效果
3.4 效果预览
3.4.1 船舶轨迹线实时聚合
- 3100万轨迹点,45万轨迹线实时聚合
3.4.2 建筑底面实时聚合
- 3.08亿建筑底面实时聚合
4. 总结
目前,Ganos已经发展到了v6.0版本,支撑了数十个行业领域的数千个应用场景,稳定、成本、性能与易用性一直Ganos长期坚持的目标,HMT热力瓦片就是Ganos在大规模空间数据高效聚合与可视化领域的内核级核心竞争力,它为客户大规模数据分析挖掘提供了真正高效、易用的方案,欢迎各位用户体验。
5. 试用体验
可以访问PolarDB免费试用页面,选择试用“云原生数据库PolarDB PostgreSQL版”,体验Ganos HMT实时热力统计查询能力。