目录
前言
二、PostGIS相关函数
三、瓦片服务搭建
(1)服务端接收xyz的链接请求
(2)计算瓦片的bound坐标
(3)发挥ST_AsMVTGeom 和ST_AsMVT() 的作用
(4)将生成的二进制流返回给客户端
四、前端可视化展示
五、效果展示
六、性能优化
总结
webgis最近随着智慧城市、数字孪生等项目大火,成为当下非常热门的技术。mapbox提出的矢量瓦片技术,解决的B/S端在面对大体量gis数据时的前端的渲染压力。使得前端地图在面对百万级的数据量依旧游刃有余,甚至比在C端上的浏览还要丝滑。动态矢量瓦片技术,解决了矢量存储在数据库中的实时动态更新,以及不再需要使用离线工具对矢量进行本地切片发布的问题。
一、矢量瓦片是什么
如果了解栅格瓦片的同学,可以这样理解,矢量瓦片是附带了数据属性的栅格瓦片。 矢量瓦片数据集的组织模型类似栅格瓦片金字塔模型,包含坐标系、投影方式、瓦片编号已实现任意精度、空间位置与矢量瓦片的对应关系,并被栅格瓦片规范相互兼容,这样方便将矢量瓦片转换成栅格瓦片,毕竟两者采用相似的投影方式和瓦片编号方式。
使用ST_AsMVT聚合函数将基于MapBox VectorTile坐标空间的几何图形转换为MapBox VectorTile二进制矢量切片,该函数可以说是整套方案的核心,postgis数据库根据前端给出的x,y,z参数,自动查询数据库对应矢量,进行实时切面并转换为二进制数据直接反馈到前端。相比离线切片,该方法直接去掉了数据保存本地并代理出去这一环节,直接将所有数据再内存中搞定。
ST_AsMVTGeom(geometry geom, box2d bounds, integer extent=4096, integer buffer=256, boolean clip_geom=true);
说明
Transform a geometry into the coordinate space of a Mapbox Vector Tile of a set of rows corresponding to a Layer. Makes best effort to keep and even correct validity and might collapse geometry into a lower dimension in the process.
geom is the geometry to transform.
bounds is the geometric bounds of the tile contents without buffer.
extent is the tile extent in tile coordinate space as defined by the specification. If NULL it will default to 4096.(边长为4096个单位的正方形)
buffer is the buffer distance in tile coordinate space to optionally clip geometries. If NULL it will default to 256.(矢量坐标空间中缓冲区的距离,位于该缓冲区的几何图形部位根据clip_geom参数被裁剪或保留。如果为NULL,则默认为256。)
clip_geom is a boolean to control if geometries should be clipped or encoded as is. If NULL it will default to true.(用于选择位于缓冲区的几何图形部位是被裁剪还是原样保留。如果为NULL,则默认为true。)
后端使用的geodjango和drf框架,url配置如下,通过django的路由反向解析,将x ,y ,z配置为路由参数。
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from pg import views
urlpatterns = [
path('mvt///.pbf', views.mvtoutViewSet.as_view()),
]
视图类中写入,将路由参数传入视图类,就可以直接在函数中拿到x, y,z参数了
class mvtoutViewSet(APIView):
def get(self,request,z, x, y):
根据前端给出的x,y,z层级求出对应的bound坐标,即 x_min、x_max、ymin、y_max。计算函数如下
import math
def xyz2lonlat(x,y,z):
n = math.pow(2, z)
lon_deg = (x / n) * 360.0 - 180.0
lat_rad = math.atan(math.sinh(math.pi * (1 - (2 * y) / n)));
lat_deg = (180 * lat_rad) / math.pi
return [lon_deg, lat_deg]
代码如下,将瓦片的bound传入pg函数,并拿到解析后的pbf
tablename="pg_mvt"
boundbox_min=xyz2lonlat(x,y,z)
boundbox_max=xyz2lonlat(x + 1, y + 1, z)
sql="""SELECT
ST_AsMVT ( P,'polygon', 4096, 'geom' ) AS "mvt" FROM (SELECT
ST_AsMVTGeom (ST_Transform (st_simplify(geom,0.0), 3857 ), ST_Transform (ST_MakeEnvelope
( %s,%s, %s,%s, 4326 ),3857),
4096, 64,TRUE ) geom FROM "%s" ) AS P""" % (boundbox_min[0],boundbox_min[1],boundbox_max[0],boundbox_max[1],tablename)
cursor = connection.cursor()
cursor.execute(sql)
return HttpResponse(tile, content_type="application/x-protobuf")
前端采用VUE框架
map配置文件
let mapStyle = {
"mvt_xyz": {
"type": "vector",
"scheme": "xyz",
"tiles": [
`http://localhost:8089/pg/mvt/{z}/{x}/{y}.pbf`
],
},
},
"layers": [
{
"id": "mvt_xyz1",
"type": "fill",
// "maxzoom": 16,
"minzoom": 6,
"source": "mvt_xyz",
"source-layer": "polygon",
"layout": {
//"visibility": 'none'
},
"paint": {"fill-color": "rgb(28,66,123)"},
},
{
"id": "mvt_xyz",
"type": "line",
// "maxzoom": 16,
"minzoom": 6,
"source": "mvt_xyz",
"source-layer": "polygon",
"layout": {
//"visibility": 'none'
},
"paint": {"line-color": "rgba(197, 118, 42, 1)", "line-width": 4},
},
],
"created": 0,
"modified": 0,
"owner": "",
"id": "empty-v9",
"draft": false,
"visibility": "public"
}
}
export default mapStyle
map初始化组件
map展示组件
矢量瓦片加载成功
虽然已经实现动态矢量瓦片,但是,如果在用户较多,或者数据量超过10万的情况,这时候服务端pg库的计算压力就会非常大,要正式投入生产,必须搭建缓存库,将已经请求成功的数据,存入缓存库,用户在二次请求的时候,服务端会自动判断缓存库是否有该数据,并直接调用缓存库已经切好的瓦片数据。通过这样优化,在面对百万级,甚至于千万级的矢量瓦片加载才能够游刃有余。当然代码和算法部分我已经实现,有兴趣的小伙伴可以私聊我付费获取。
矢量瓦片是一门很优秀的技术,解决的b端的大批量数据可视化的加载问题,动态矢量瓦片解决了数据的实时更新问题,不得不说一下django框架的全面,不愧是web后端的全套解决方案。