一 矢量切片技术闲谈
矢量切片技术目前已成为互联网地图的主流技术,无论是Mapbox还是高德地图、百度地图,如今打开F12看到的数据源请求不是当年传统的一张张图片切片,而是一种protobuf格式的压缩的二进制数据,如下图:

高德底图数据服务
在矢量切片技术之前使用的叫“栅格切片技术”,即在服务端渲染好图片并切片,并传给客户端直接显示,传统栅格切片技术和矢量切片技术逻辑对比如下图:

栅格切片到矢量切片
由上图可知,栅格切片技术是重服务端的,客户端几乎什么都不要做的,矢量切片技术是重客户端,服务端就做下数据切片即可。也就是说其实本质上是将地图渲染从服务端迁移到客户端渲染。
在矢量切片技术出现之前,统治互联网地图的是栅格切片技术,其实本质上栅格切片和矢量切片都是为了解决互联网地图浏览时由于地理数据量大而产生的渲染性能问题,这一点上来说两者本质上就是一个东西,就是“数据切片,分片加载与渲染”。那么有人会问,为什么之前栅格切片技术用的好好的啊,为啥会想出来改成矢量切片技术咧?其实一方面是得益于技术进步,另一方面是用户的需求变高了。
技术上来说,矢量切片直接复用了基于XYZ的地图切片技术原理进行数据切片(这一点和栅格切片是一样的思路),切片数据使用谷歌的Protobuf进行数据压缩以优化网络传输效率,然后在客户端结合WebGL技术进行大量地理数据的渲染。Protobuf和WebGL自2015年以来在web地图领域已成为主流技术的核心部分,而在栅格切片的年代,前端还没有进入H5和WebGL的年代,受制于当时的技术用图片直接拼图就成了必然的选择了。
从用户需求上来说,普通用户(移动端为主,2c)需要地图体验更好,流畅平滑,总不能买个7,8000的手机,就只能看看地图图片吧,很明显这么原始的app和如今我这个高端手机的逼格格格不入啊,所以你得优化产品和体验,要对得起用户的好手机对得起用户的身份,你不优化,你看对家的xx地图优化了大家就都用那个app了不是。。。而对企业用户来说,底图是必须的,但有时候我用不到这么复杂的地物,东西太多我的业务就不突出了感觉很乱,我也不喜欢这个底图样式,花花绿绿的不符合我这个企业业务展示需求,不够凸显业务主题等等,能控制下图层的显隐,能改下底图的样式吗?
从技术和用户需求上而言,矢量切片是时代的召唤有没有?所以该技术迅速成为地图主流技术,简单对比下矢量切片和栅格切片比较明显的差异有如下几点:
浏览时,由于地图有缩放级别,在放大缩小时,比如从地图级别8放大到9,矢量切片由于渲染在前端,可以在8、8.1、8.2...9之间渲染,给人感觉就是“无极缩放”,顺畅平滑,用户体验好;而栅格切片直接从8跳到9显示,给人感觉就是地图突然一蹦的感觉,突兀。交互时,矢量都是可点击的,栅格可能还要从客户端发送请求到服务端查询,这现在少了很多请求可以对服务器减压。
图层控制能力,可以在客户端控制哪些图层要显示哪些图层不能显示;而栅格切片是要么都显示要么不显示。
主题定制能力,矢量切片是一份数据+多套样式主题,可以根据业务要求随时切换,也会支持用户自定义设计,业务友好型;而栅格切片就是我就这样了,你爱用不用哦。。。
数据生成成本,由于底图地理数据变化很快,栅格切片方案生产流程复杂点,对服务器资源要求更高,矢量切片将渲染交给客户端实现,服务端反而减负了,有利于互联网地图商节约设备成本。
二 常见矢量切片工具简介
目前开源的矢量切片工具还是非常多的,列出一些主流的阐述下:
基于GeoServer的矢量切片插件,适合熟悉GeoServer的用户,操作还比较简单,缺点是切片的行列号与一般的XYZ编号不同不容易单独部署,且不同geoserver稳定性不一致,笔者曾在某些版本部署崩溃无法应用。
基于tippecanoe的矢量切片工具方案,该工具提供了很多高级功能在数据定制化上有很强的优势,但只能部署在Linux,并不是跨平台,只能读取geojson文件,不能直连数据库,不是很好,如果有幸您是c++开发大神,可以改下库的编译绑定平台,使其支持windows,再更改下数据源底层,使其能支持空间数据库,那么该工具会有更多的应用空间。
基于PostGIS的矢量切片方案,该方案在熟悉PostGIS的用户中应该很受欢迎,优势是支持动态矢量切片,有PG社区的系统级加成,本节会重点阐述下该方案。
总的来说,工具虽然很多,但是没有一款可以说覆盖一切场景的,具体应用还是看场景的,比如前两个方案都是做底图数据时比较有用,都是静态矢量切片方案,geoserver能直连数据库,tippecanoe有强数据定制性要求,那么如果用户侧重点是简单点的话geoserver够了,用户侧重点是希望对数据做很多高级过滤什么的操作用tippecanoe,但步骤麻烦点。这些矢量切片工具仅仅在处理很久不变的数据,就是切一次用很久的数据,如果数据频繁变化,这种静态数据切片工具就很不好用了。
本章节主要阐述的是使用PostGIS矢量切片,与其他方案相比,PostGIS方案的好处主要有两大点:
资源开销低:空间数据一般存空间数据库中,传统工具会先从数据库中捞数据,这个数据通常很大,网络开销和服务器端内存都要很大,查询慢计算慢是肯定的。而PostGIS是在数据库中把数据处理完,只把结果传给后台转前台,可以很方便的使用数据库的索引,并行计算等,优化查询和处理速度。
动态矢量切片,数据时效性高:每当根据xyz请求时,数据库会动态查询范围内数据,裁剪简化并输出pbf格式的二进制数据出去,在数据变化频繁的场景下,可以保证用户看到的是最新的数据。
PostGIS动态矢量切片地图
三 PostGIS矢量切片发展
PostGIS矢量切片也是PostGIS与时俱进的产物,早期的版本是没有这个东西的,我们简单列下矢量切片在最近的PostGIS版本中的发展情况:
PostGIS 2.4 版本第一次支持矢量切片技术,API中新增了ST_AsMVT函数,用于生成矢量切片。
PostGIS 2.5版本变化不大。
PostGIS 3.0版本是大改的版本,引入新的ST_TileEnvelope(z,x,y)方法,用户可以根据传入的zxy方便计算对应地理范围,旧版本需要自己写下该方法虽然不难。集成wagyu裁剪算法,优化切片数据裁剪过程;简化了数据在图形简化和精度降低的执行步骤,加快了点线处理速度;ST_AsMVT函数支持并行计算,多核CPU计算大大提升了数据生成速度。(笔者认为应在PostgreSQL 12版本中用,并行计算的优化应该是PG的特性,低版本应该还是不会原生支持并行计算)
PostGIS 3.1版本继续优化了性能,大约相对于PostGIS3.0优化了30%-40%的性能提升。PostGIS 3.1应当和最新的PostgreSQL 13结合使用。
很明显,PostGIS持续在矢量切片技术上发力,大量优化和简化开发者使用难度等,对此开源GIS开发者应当感谢PostGIS社区做出的贡献,并且如果你是一个技术爱好者,应该迫不及待的想要自己尝试一番了,下面我们简单根据一个案例介绍下这个矢量切片怎么用,怎么优化,怎么避免滥用等。
四 应用实践
环境:PostgreSQL 12+PostGIS 3.0
数据:导入osm的中国路网数据到pg数据库,数据总量:
select count(*) from road;count---------3365713(1 row)
测试内容与方法:任意选择一点如118 32,计算该点所在的1-16级别的xyz瓦片行列号,然后将行列号作为矢量切片生成脚本的传入参数,计算这个瓦片的矢量切片数据生成速度。
测试脚本如下:
CREATE OR REPLACE FUNCTION vector_tile_test(IN z int,IN x int,IN y int,OUT tile bytea)RETURNS bytea AS$BODY$DECLAREbound geometry;extent box2d;sql text;BEGIN--ST_TileEnvelope函数得到的是epsg:3857坐标系,表是4326,需要坐标系转换。bound:=ST_Transform(ST_TileEnvelope(z,x,y),4326);extent:=Box2D(bound);sql:='WITH mvtgeom AS(SELECT ST_AsMVTGeom(geom, $1) AS geom, name FROM road WHERE ST_Intersects(geom, $2)) SELECT gzip(ST_AsMVT(mvtgeom.*,$3)) FROM mvtgeom';execute format(sql) using extent,bound,'road' into tile;RETURN;END;$BODY$LANGUAGE 'plpgsql' VOLATILE STRICT;
以15级别的瓦片的xyz序号传入参数测试示例如下:
select * from vector_tile_test(15, 27124, 13306);tile--------------------------------------------\x1f8b080000000000000303000000000000000000(1 row)
全部测试耗时如下:
| z级别 | [z,x,y]编号 | 执行耗时 |
| 1 | 1, 1, 0 | 2221.9ms |
| 2 | 2, 3, 1 | 2552.9ms |
| 3 | 3, 6, 3 | 3430.3ms |
| 4 | 4, 13, 6 | 3122.3ms |
| 5 | 5, 26, 12 | 1985.6ms |
| 6 | 6, 52, 25 | 1672.5ms |
| 7 | 7, 105, 51 | 183.9ms |
| 8 | 8, 211, 103 | 100.58ms |
| 9 | 9, 423, 207 | 58.1ms |
| 10 | 10, 847, 415 | 4.2ms |
| 11 | 11, 1695, 831 | 2.27ms |
| 12 | 12, 3390, 1663 | 1.99ms |
| 13 | 13, 6781, 3326 | 1.90ms |
| 14 | 14, 13562, 6653 | 1.41ms |
| 15 | 15, 27124, 13306 | 1.43ms |
| 16 | 16, 54249, 26613 | 1.7ms |
现象总结:
比例尺越小,瓦片包含数据越多,需要简化等操作也很多,io与cpu都很重,所以耗时较多。
比例尺越大,单个瓦片包含的数据不大,检索和操作都是毫秒级别,非常快。
总的来说,对于几百万线数据的矢量切片即使在极端情况下1,2等小比例尺下,也能在几秒内返回结果,但是考虑到这种情况下tile体积较大,从数据库到服务端,从服务端到前台的网络开销大,因此,实际业务可视化的耗时应该是很大的,估计10s左右。
在应用时,尽量在比例尺较高的业务场景下使用矢量切片,小比例尺下不适合数据一股脑的检索生成,应该完善显示规则,尽可能小的减少小比例尺下的数据规模,不显示的数据不显示的字段都不写入切片。这里的规则不详细展开,应该有很多实际情况不同而采取不同的策略。
在实际业务中使用PostGIS动态切片,sql语句和PG的引擎机制都能对矢量切片的优化起到作用,如基于PG的地理并行计算就是很重要的优化功能,我们本篇做个简单测试,但不详细展开,之后会重开篇章介绍地理并行计算。
前文有个测试耗时表,其中6,52,25耗时1672ms,我们就选择这个进行测试。
关闭并行计算1529ms:
--xyz=6,52,25代表的地理范围是POLYGON((112.499999999999 31.9521622380246,-- 112.499999999999 36.5978891330698,118.124999999999 36.5978891330698,-- 118.124999999999 31.9521622380246,112.499999999999 31.9521622380246))Alter table road set (parallel_workers = 0);explain analyse WITH mvtgeom AS(SELECT ST_AsMVTGeom(geom, ST_GeomFromText('POLYGON((112.499999999999 31.9521622380246,112.499999999999 36.5978891330698,118.124999999999 36.5978891330698,118.124999999999 31.9521622380246,112.499999999999 31.9521622380246))',4326)) AS geom, nameFROM road WHERE ST_Intersects(geom, ST_GeomFromText('POLYGON((112.499999999999 31.9521622380246,112.499999999999 36.5978891330698,118.124999999999 36.5978891330698,118.124999999999 31.9521622380246,112.499999999999 31.9521622380246))',4326))) SELECT gzip(ST_AsMVT(mvtgeom.*,'road')) FROM mvtgeom;-- (actual time=1529.714..1529.714 rows=1 loops=1)
开启8核并行计算475ms,sql规划中workers=8代表启动了8个worker并行计算数据,加速了数据生成速度,这个场景下约提升了4倍性能:
Alter table road set (parallel_workers = 8);explain analyse WITH mvtgeom AS(SELECT ST_AsMVTGeom(geom, ST_GeomFromText('POLYGON((112.499999999999 31.9521622380246,112.499999999999 36.5978891330698,118.124999999999 36.5978891330698,118.124999999999 31.9521622380246,112.499999999999 31.9521622380246))',4326)) AS geom, nameFROM road WHERE ST_Intersects(geom, ST_GeomFromText('POLYGON((112.499999999999 31.9521622380246,112.499999999999 36.5978891330698,118.124999999999 36.5978891330698,118.124999999999 31.9521622380246,112.499999999999 31.9521622380246))',4326))) SELECT gzip(ST_AsMVT(mvtgeom.*,'road')) FROM mvtgeom;--Gather (actual time=473.469..475.471 rows=9 loops=1)-- Workers Planned: 8-- Workers Launched: 8
总结:PG社区有很多优秀的特性,PostGIS本质上是PG的一个插件,合理使用PG社区的优秀技术和组件,能极大提升PostGIS的性能适应更多业务场景。
实际案例中,笔者曾经在气象领域,比如雷达的等值面数据入库,数据时效是6分钟,即6分钟就过期了,要求能对等值面数据快速呈现展示,笔者就使用了PostGIS的动态矢量切片技术,捞取连续20个时刻的等值面数据生成矢量切片传到前端,测试页面初始化加载速度和播放性能,由于工作原因不便于贴出代码,仅仅贴个效果图:

初始化加载 20个时刻的等值面200ms完成加载

时序播放
结论是该方案效果还不错。后续由于技术升级和等值面生成处理过于复杂,实际业务已经放弃该做法,但是通过该方案实践了动态矢量切片技术,在合适场景下效果非常的赞。




