Postgis动态矢量瓦片前后端部署指南

目录

前言

二、PostGIS相关函数

三、瓦片服务搭建

(1)服务端接收xyz的链接请求

(2)计算瓦片的bound坐标

(3)发挥ST_AsMVTGeom 和ST_AsMVT() 的作用

(4)将生成的二进制流返回给客户端

四、前端可视化展示

五、效果展示

六、性能优化

总结


前言

webgis最近随着智慧城市、数字孪生等项目大火,成为当下非常热门的技术。mapbox提出的矢量瓦片技术,解决的B/S端在面对大体量gis数据时的前端的渲染压力。使得前端地图在面对百万级的数据量依旧游刃有余,甚至比在C端上的浏览还要丝滑。动态矢量瓦片技术,解决了矢量存储在数据库中的实时动态更新,以及不再需要使用离线工具对矢量进行本地切片发布的问题。


一、矢量瓦片是什么

如果了解栅格瓦片的同学,可以这样理解,矢量瓦片是附带了数据属性的栅格瓦片。​ 矢量瓦片数据集的组织模型类似栅格瓦片金字塔模型,包含坐标系、投影方式、瓦片编号已实现任意精度、空间位置与矢量瓦片的对应关系,并被栅格瓦片规范相互兼容,这样方便将矢量瓦片转换成栅格瓦片,毕竟两者采用相似的投影方式和瓦片编号方式。

二、PostGIS相关函数

使用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。)

三、瓦片服务搭建

(1)服务端接收xyz的链接请求

后端使用的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/<int:z>/<int:x>/<int:y>.pbf', views.mvtoutViewSet.as_view()),

]

视图类中写入,将路由参数传入视图类,就可以直接在函数中拿到x, y,z参数了

class mvtoutViewSet(APIView):
    def get(self,request,z, x, y):

(2)计算瓦片的bound坐标

根据前端给出的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]

(3)发挥ST_AsMVTGeom 和ST_AsMVT() 的作用

代码如下,将瓦片的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)

(4)将生成的二进制流返回给客户端

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初始化组件

<template>
  <div id="mapView">
    <slot></slot>
  </div>
</template>

<script>
  import "mapbox-gl/dist/mapbox-gl.css";
  import MapBoxGl from "mapbox-gl";

  export default {
    name: "MapView",
    props: {
      token: {
        type: String,
        default:
          "pk.eyJ1IjoieWFuZ2xpdXVwIiwiYSI6ImNrcnRiM3ZwMDEybzUyd285bGRjcW05bHgifQ.IOHRPKCkDAl9abVbVsCEBg",
      },
      mapStyle: {
        type: [Object, String],
        require: true,
      }
    },
    mounted() {
      MapBoxGl.accessToken = this.token;
      let map = new MapBoxGl.Map({
        container: "mapView", // container id
        style: this.mapStyle, // stylesheet location
        center: [104.1403, 30.576336],
        zoom: 14,
        maxZoom: 17.9,
        projection: "globe",
      });
      this.$emit("mapLoadHandle", map);
      map.on("load", () => {
        map.addControl(new MapBoxGl.NavigationControl(), "bottom-right");
        // map.addControl(mapboxDraw, "top-left");
        // Set the default atmosphere style
        this.$emit("mapLoadHandle", map);
        map.setFog({});
      });
    },
  };
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
  #mapView {
    height: 100%;
    width: 100%;
    margin: 0;
  }
  ::v-deep .mapboxgl-ctrl-logo {
    display: none !important;
  }
</style>

map展示组件

    <MapView :mapStyle="mapStyle" @mapLoadHandle="mapLoad">
    </MapView>

五、效果展示

矢量瓦片加载成功

六、性能优化

    虽然已经实现动态矢量瓦片,但是,如果在用户较多,或者数据量超过10万的情况,这时候服务端pg库的计算压力就会非常大,要正式投入生产,必须搭建缓存库,将已经请求成功的数据,存入缓存库,用户在二次请求的时候,服务端会自动判断缓存库是否有该数据,并直接调用缓存库已经切好的瓦片数据。通过这样优化,在面对百万级,甚至于千万级的矢量瓦片加载才能够游刃有余。当然代码和算法部分我已经实现,有兴趣的小伙伴可以私聊我付费获取。


总结

矢量瓦片是一门很优秀的技术,解决的b端的大批量数据可视化的加载问题,动态矢量瓦片解决了数据的实时更新问题,不得不说一下django框架的全面,不愧是web后端的全套解决方案。

  • 2
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力的悟空

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值