PostgreSQL+Postgis MapboxVectorTile应用

背景

该实验基于Postgres+Postgis搭建实时矢量瓦片服务。
矢量瓦片的优势

  1. 可以支持高分辨率屏幕显示
  2. 地图渲染在前端,可以支持前端随意更改配图方案,解决传统栅格瓦片动态样式上的问题
  3. 要素查询可以在前端进行
  4. 保持了矢量数据的优势,同时采用切片方式在传输效率上有一定提升

实时矢量瓦片

不在线下用工具预先切片,采用即时浏览传输矢量瓦片;
采用实时矢量切片可以做到数据编辑功能,同时解决数据时效性问题

使用到的相关函数

ST_AsMvtGeo 将Geom转化为MVT的geom
ST_AsMVT 将geom转换为MVT数据
ST_TileEnvelope 根据行列号获取Envelop (该函数需要Postgis3.0才有,使用postgis-vt-util代替

软件环境

  • PostgreSQL11
  • Postgis2.5
  • java
  • (前端渲染) leaflet1.7、mapbox

实现过程

模拟数据

使用sql语句创建表,并在给定坐标范围内模拟点数据

CREATE TABLE data_1000000 AS SELECT
( ST_Dump ( ST_GeneratePoints ( kl.geom, 200000 ) ) ).geom AS geom,
md5( ( random ( ) * random ( ) ) :: text ) AS id,
random ( ) * 1000 AS val 
FROM ( 
  SELECT ST_SetSRID ( ST_MakeBox2D ( ST_Point ( 113.213316,22.954094 ), ST_Point ( 113.62805,23.368228 ) ), 4326 
) geom ) kl;

sql解读:
创建表名:data_1000000
坐标范围:左下角坐标(113.213316,22.954094)、右下角坐标(113.62805,23.368228),坐标系SRID为4326
模拟数据量:100万
表字段:geom(空间字段)、id(md5格式的唯一编码)、val(点的权重值0-1000)

部署扩展函数

由于需要用到ST_TileEnvelope函数,该函数需要postgis3.0才支持,所以使用扩展的函数库实现(postgis-vt-util下载)
执行postgis-vt-util.sql添加相关函数到数据库

这个库由mapbox提供,生成符合mapbox矢量切片格式的数据

后端接口

后端需要实现前端请求切片范围的矢量切片生成,传入的参数可能是行列号或者经纬度范围,最终返回byte的矢量切片数据

处理逻辑

graph LR
start("前端请求") --> isEnvelop("是否传入经纬度范围")
isEnvelop --no--> tileBBox("根据切片z,x,y转换经纬度范围")
isEnvelop --yes--> makeEnvelop("生成几何范围")
tileBBox --> makeEnvelop
makeEnvelop --> asMVTGeom("将几何图形转为mapbox矢量瓦片坐标")
asMVTGeom --> asMVT("返回mapbox矢量切片的byte数组")

点线面在转换byte数组时需要分开处理

代码编写

//根据切片xyz获取矢量切片
public byte[] getMVT(String tableName, int x, int y, int z, int srid) {
    byte[] res;
    try {
        String sqlStr = "SELECT " +
                "ST_AsMVT( vt, 'points', 256, 'geo' ) FROM ( " +
                "SELECT ST_SetSRID ( ST_Point ( ST_X ( A.geo ), ST_Y ( A.geo )), " + srid + " ) geo FROM (" +
                "SELECT ST_AsMVTGeom ( w.geom, Box2D ( TileBBox ( " + z + "," + x + "," + y + ", " + srid + " )), 256, 0, TRUE ) AS geo  " +
                "FROM " + tableName + " w  WHERE TileBBox ( " + z + "," + x + "," + y + ", " + srid + " ) && geom  ) A  " +
                "GROUP BY ST_X ( A.geo ), ST_Y ( A.geo ) ) AS vt";

        res = jdbcTemplate.queryForObject(sqlStr,byte[].class);
    } catch (Exception e) {
        throw e;
    }
    return res;
}

//根据空间范围获取矢量切片
public byte[] getMVTByEnvelop(String tableName, double lon_min, double lat_min, double lon_max, double lat_max, int srid) {
    byte[] res;
    try {
        String sqlStr = "SELECT " +
                "ST_AsMVT( vt, 'points', 256, 'geo' ) FROM ( " +
                "SELECT ST_SetSRID ( ST_Point ( ST_X ( A.geo ), ST_Y ( A.geo )), " + srid + " ) geo FROM (" +
                "SELECT ST_AsMVTGeom ( w.geom,ST_MakeEnvelope ( " + lon_min + "," + lat_min + "," + lon_max + "," + lat_max + ", " + srid + " ), 256, 0, TRUE ) AS geo  " +
                "FROM " + tableName + " w  WHERE ST_MakeEnvelope ( " + lon_min + "," + lat_min + "," + lon_max + "," + lat_max + ", " + srid + " ) && geom  ) A  " +
                "GROUP BY ST_X ( A.geo ), ST_Y ( A.geo ) ) AS vt";

        res = jdbcTemplate.queryForObject(sqlStr, byte[].class);
    } catch (Exception e) {
        throw e;
    }
    return res;
}

前端代码编写

mapboxgl加载方法

<!DOCTYPE html>
<html>
<head>
    <title>Mapbox加载MVT</title>
    <meta charset='utf-8'>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <script src="https://api.mapbox.com/mapbox-gl-js/v1.10.0/mapbox-gl.js"></script>
    <link href="https://api.mapbox.com/mapbox-gl-js/v1.10.0/mapbox-gl.css" rel="stylesheet" />
    <style>
        body {
            margin: 0;
            padding: 0;
        }
        html,
        body,
        #map {
            height: 100%;
        }
        .layerPanel {
            position: absolute;
            top: 10px;
            right: 20px;
            background: #ffffff;
            padding: 5px 2px;
        }
    </style>
</head>

<body>
    <div id='map'></div>
    <script>
        let center = [113.33616, 23.0963];
		//获取地图样式
        let style = getStyle();
        //创建3857坐标系地图
        var map = window.map = new mapboxgl.Map({
            container: 'map',
            // epsg: 'EPSG:3857',
            center : center,
            zoom:12,
            hash: true,
            style: style
        });
        // map.showTileBoundaries = true;
        map.on('load', function () {
            //添加天地图底图
            addTDTLayers(map);
            map.addLayer({
                "type": "circle",
                "source": "test",
                "paint":{
					"circle-color": "blue",
					"circle-radius":4,
                },
                "source-layer": "points",
                "maxzoom": 17,
                "id": "1"
            })
        });

        function getStyle() {
            let style = {
                "version": 8,
                "name": "Basic-my",
                "metadata": {
                    "mapbox:autocomposite": true,
                    "mapbox:type": "template"
                },
                "center": center,
                "zoom": 3,
                "bearing": 0,
                "pitch": 0,
                "sources": {
                    "test": {
                        "type": "vector",
                        "tiles": ["{后端服务}/getMVT?tableName=data_1000000&x={x}&y={y}&z={z}"]
                    }
                },
                "layers": []
            };
            return style;
        }
        //添加3857坐标系天地图
        function addTDTLayers(map) {
            //添加3857天地图矢量source
            var source_vec = {
                "type": "raster",
                "tiles": [
                    "http://t0.tianditu.com/DataServer?T=vec_w&x={x}&y={y}&l={z}&tk=7b06bd204544a8631beccd5fd56ad8c1",
                ],

                "tileSize": 256
            }
            if (!map.getSource("TDT_VEC")) {
                map.addSource("TDT_VEC", source_vec);
            }

            //添加3857天地图矢量图层
            var Layer_vec = {
                "id": "tdtvec",
                "type": "raster",
                "source": "TDT_VEC",
                "minzoom": 0,
                "maxzoom": 17
            }
            if (!map.getLayer("tdtvec")) {
                map.addLayer(Layer_vec);
            }
        }
    </script>
</body>
</html>

Leaflet加载方式

<!DOCTYPE html>
<html>
<head>
	<title>VectorGrid.Protobuf example</title>
	<meta charset="utf-8" />
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<link rel="stylesheet" href="src/leaflet.css" />
</head>
<body style='margin:0'>
	<div id="map" style="width: 100vw; height: 100vh; background: white"></div>
	<script type="text/javascript"  src="src/leaflet-src.js"></script>
	<script type="text/javascript"  src="src/Leaflet.VectorGrid.js"></script>
	<script>
		var map = L.map('map',{
			renderer: L.canvas
		});			
		map.setView({ lat: 23.0963, lng: 113.33616 }, 14);
		//加载底图
		L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
			attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
		}).addTo(map);
	
		var vectorTileStyling = {
			points:{
				weight: 2,
				color: 'red',
				opacity: 1,
				fillColor: 'yellow',
				fill: true,
				radius: 6,
				fillOpacity: 0.7
			}
		}
		
		var mapboxUrl = "http://localhost:8081/xxx/getMVT?tableName=data_1000000&x={x}&y={y}&z={z}";

		var mapboxVectorTileOptions = {
			rendererFactory: L.canvas.tile,
			vectorTileLayerStyles: vectorTileStyling
		};

		var mapboxPbfLayer = L.vectorGrid.protobuf(mapboxUrl, mapboxVectorTileOptions);
		map.addLayer(mapboxPbfLayer)
	</script>
</body>
</html>

结果展示

详情浏览

相关链接

https://blog.csdn.net/qq_35241223/article/details/106439268
https://github.com/mapbox/postgis-vt-util

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
PostgreSQL+PostGIS 相对于 Oracle Spatial 在以下几个方面具有优势: 1. 开源和免费:PostgreSQL+PostGIS 是开源的数据库系统,可以免费使用,并且在开源社区中有广泛的支持和贡献。相比之下,Oracle Spatial 是商业数据库产品,可能需要支付高额的许可费用。 2. 社区支持和生态系统:PostgreSQL+PostGIS 有庞大的开源社区支持,许多开发者和组织都在为其开发新功能、解决问题和提供支持。这意味着用户可以从活跃的社区中获取帮助、学习和分享经验。 3. 扩展性和灵活性:PostgreSQL 是一个高度可扩展的数据库系统,可以轻松处理大规模数据集。PostGIS 扩展为 PostgreSQL 提供了丰富的地理空间功能,包括拓扑关系查询、空间索引、几何运算等。用户可以根据自己的需求选择性地安装和配置 PostGIS 扩展,使其更适合特定的应用场景。 4. 标准兼容性:PostgreSQL+PostGIS 遵循 SQL 和 OGC(Open Geospatial Consortium)的标准,支持标准的地理空间数据模型和函数。这使得它与其他遵循相同标准的地理空间数据系统具有良好的互操作性。 5. 可定制性和开发灵活性:开源的 PostgreSQL+PostGIS 提供了许多扩展和插件,用户可以根据自己的需求进行定制和开发。这使得用户可以根据具体应用的要求,灵活地扩展和定制地理空间功能。 需要注意的是,选择使用 PostgreSQL+PostGIS 还是 Oracle Spatial 取决于具体的需求和环境。如果对性能、功能全面性和与商业软件集成有较高要求,以及有相应的预算支持,那么 Oracle Spatial 可能更适合。而如果希望免费使用、享受开源社区支持、具备灵活性和可定制性,那么 PostgreSQL+PostGIS 是一个更好的选择。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值