基于 PostGIS 的矢量切片服務(wù)器
矢量切片簡介
矢量切片是 MapBox 定義的一種開放的 矢量地圖標(biāo)準(zhǔn) , 已經(jīng)成為開放地理聯(lián)盟 (OGC) 的標(biāo)準(zhǔn)之一。
個人認(rèn)為矢量切片的主要優(yōu)點有:
服務(wù)端只關(guān)注數(shù)據(jù), 無需進(jìn)行繁瑣的配圖;
網(wǎng)絡(luò)傳輸快, 因為只有括矢量數(shù)據(jù);
客戶端渲染, 服務(wù)端的一套矢量數(shù)據(jù), 在客戶端可以有多種的表現(xiàn)形式;
充分利用客戶端硬件
適配客戶端屏幕, 根據(jù)屏幕解析度進(jìn)行高精度矢量渲染;
利用 OpenGL/WebGL 實現(xiàn)海量空間數(shù)據(jù)渲染;
目前制作矢量切片的方式主要有:
使用 ArcGIS Pro 生成矢量切片包, 上傳到 ArcGIS Portal 和 Server , 這套工具最完善, 但是也最貴;
使用開源的 GeoServer 來配置生成矢量切片, 配置比較繁瑣, 而且對于矢量切片標(biāo)準(zhǔn)的支持也也比較慢;
這兩種方式都能生成質(zhì)量比較高的矢量切片, 并提供可靠的矢量切片服務(wù), 但是都需要對數(shù)據(jù)做預(yù)處理, 如果修改了數(shù)據(jù), 往往不能及時響應(yīng)。
PostGIS 對矢量切片的支持
PostGIS 是關(guān)系數(shù)據(jù)庫 PostgreSQL 的空間擴(kuò)展, 提供了強(qiáng)大的空間數(shù)據(jù)查詢和處理能力, 對矢量切片也提供了支持, 相關(guān)的函數(shù)有:
ST_AsMVTGeom 將數(shù)據(jù)庫存儲的空間坐標(biāo)轉(zhuǎn)換為矢量切片坐標(biāo);
ST_AsMVT 將矢量空間坐標(biāo)聚合為符合矢量切片格式規(guī)范的二進(jìn)制數(shù)據(jù);
ST_TileEnvelope 在 Web墨卡托坐標(biāo)系 (SRID:3857) 下使用 xyz 切片架構(gòu) 計算切片切片坐標(biāo)范圍;
通過者上面這三個相關(guān)函數(shù), 可以將數(shù)據(jù)庫存儲的空間數(shù)據(jù)快速轉(zhuǎn)換成矢量切片標(biāo)準(zhǔn)的二進(jìn)制數(shù)據(jù)。
將單表輸出為單圖層矢量切片的 SQL 語句為:
with mvt_geom as (
? select
? ? ST_AsMVTGeom(
? ? ? geom,
? ? ? ST_TileEnvelope(15, 26696, 14219),
? ? ? extent => 4096, buffer => 64
? ? ) as geom,
? ? id, name, fclass, ref, oneway, maxspeed, bridge, tunnel, layer
? from public.sr3857_guangzhou_road
? where geom && ST_TileEnvelope(15, 26696, 14219, margin => (64.0 / 4096))
)
select ST_AsMVT(mvt_geom, 'guangzhou_road', 4096, 'geom', 'id')
from mvt_geom
也可以使用 || 算符將多個圖層生成矢量切片
select (
? (
? ? with mvt_geom as (
? ? ? select
? ? ? ? ST_AsMVTGeom(
? ? ? ? ? geom,
? ? ? ? ? ST_TileEnvelope(15, 26696, 14219),
? ? ? ? ? extent => 4096, buffer => 64
? ? ? ? ) as geom,
? ? ? ? id, name, fclass, ref, oneway, maxspeed, bridge, tunnel, layer
? ? ? from public.sr3857_guangzhou_road
? ? ? where geom && ST_TileEnvelope(15, 26696, 14219, margin => (64.0 / 4096))
? ? )
? ? select ST_AsMVT(mvt_geom, 'guangzhou_road', 4096, 'geom', 'id')
? ? from mvt_geom
? ) || (
? ? with mvt_geom as (
? ? ? select
? ? ? ? ST_AsMVTGeom(
? ? ? ? ? geom,
? ? ? ? ? ST_TileEnvelope(15, 26696, 14219),
? ? ? ? ? extent => 4096, buffer => 64
? ? ) as geom,
? ? ? ? objectid, name, height, flag, type, area_id
? ? ? from public.sr3857_guangzhou_building
? ? ? where geom && ST_TileEnvelope(15, 26696, 14219, margin => (64.0 / 4096))
? ? )
? ? select ST_AsMVT(mvt_geom, 'guangzhou_building', 4096, 'geom', 'objectid')
? ? from mvt_geom
? )
);
矢量切片服務(wù)器
有了上面的 SQL 語句, 開發(fā)矢量切片服務(wù)器就是非常簡單的了, 任何開發(fā)語言都可以實現(xiàn), 下面以 C# 代碼為例:
[HttpGet("{source}/{z:int}/{y:int}/{x:int}")]
public async Task<ActionResult> GetTile(string source, int z, int y, int x) {
? ? try {
? ? ? ? var buffer = await provider.GetTileContentAsync(source, z, y, x);
? ? ? ? if (buffer == null || buffer.Length == 0) {
? ? ? ? ? ? return NotFound();
? ? ? ? }
? ? ? ? return File(buffer, "application/vnd.mapbox-vector-tile");
? ? }
? ? catch (Exception ex) {
? ? ? ? logger.LogError(ex.Message);
? ? ? ? return StatusCode(500);
? ? }
}
通過 appsettings.json 配置兩個矢量切片源:
{
? "connectionStrings": {
? ? "geo_db": "server=127.0.0.1;port=5432;database=geo_db;user id=geo_db_user;password=********;"
? },
? "vectors": {
? ? "guangzhou": {
? ? ? "connectionString": "geo_db",
? ? ? "layers": [
? ? ? ? {
? ? ? ? ? "name": "road",
? ? ? ? ? "minzoom": 9,
? ? ? ? ? "maxzoom": 15,
? ? ? ? ? "srid": 3857,
? ? ? ? ? "schema": "public",
? ? ? ? ? "tableName": "sr3857_guangzhou_road",
? ? ? ? ? "idColumn": "id",
? ? ? ? ? "geometryColumn": "geom",
? ? ? ? ? "attributeColumns": "name, fclass, ref, oneway, maxspeed, bridge, tunnel, layer"
? ? ? ? },
? ? ? ? {
? ? ? ? ? "name": "building",
? ? ? ? ? "minzoom": 13,
? ? ? ? ? "maxzoom": 17,
? ? ? ? ? "srid": 3857,
? ? ? ? ? "schema": "public",
? ? ? ? ? "tableName": "sr3857_guangzhou_building",
? ? ? ? ? "idColumn": "objectid",
? ? ? ? ? "geometryColumn": "geom",
? ? ? ? ? "attributeColumns": "name, height, flag, type, area_id"
? ? ? ? }
? ? ? ]
? ? }
? }
}
這樣生成的矢量切片服務(wù)的地址是: http://127.0.0.1:5000/api/vector/guangzhou/{z}/{y}/{x} , 包含了 road 和 building 兩個圖層。
使用矢量切片服務(wù)
生成的是基于 Web 墨卡托坐標(biāo)系的 xyz 切片架構(gòu)的標(biāo)準(zhǔn)的矢量切片服務(wù), 可以直接任意支持矢量切片的客戶端中使用 (mapboxgl, openlayers, arcgis js api 等), 配置參照下面的矢量切片樣式:
{
? "version": 8,
? "sources": {
? ? "guangzhou": {
? ? ? "type": "vector",
? ? ? "scheme": "xyz",
? ? ? "tiles": ["http://127.0.0.1:5000/api/vectortiles/guangzhou/{z}/{y}/{x}"],
? ? ? "minzoom": 9,
? ? ? "maxzoom": 17
? ? }
? },
? "layers": [
? ? {
? ? ? "id": "road",
? ? ? "source": "guangzhou",
? ? ? "source-layer": "road",
? ? ? "type": "line",
? ? ? "minzoom": 9,
? ? ? "maxzoom": 15,
? ? ? "paint": {
? ? ? ? "line-color": "#00FF00",
? ? ? ? "line-width": 2
? ? ? }
? ? },
? ? {
? ? ? "id": "building",
? ? ? "source": "guangzhou",
? ? ? "source-layer": "guangzhou_building",
? ? ? "type": "fill",
? ? ? "minzoom": 13,
? ? ? "maxzoom": 17,
? ? ? "paint": {
? ? ? ? "fill-opacity": 0.8,
? ? ? ? "fill-color": "#8c2d04"
? ? ? }
? ? }
? ]
}
注意問題
PostGIS 版本要求最新的 3.1.x ;
雖然 PostGIS 3.x 最低支持 PostgreSQL 9.6.x , 但是建議使用高版本的 PostgreSQL (12+), 因為 PostgreSQL 12 以上的版本提供了更好的查詢性能;
雖然 PostGIS 提供了坐標(biāo)系轉(zhuǎn)換函數(shù) ST_Transform , 但是進(jìn)行實時轉(zhuǎn)換會消耗一些性能, 建議將空間數(shù)據(jù)轉(zhuǎn)換為 Web墨卡托坐標(biāo)系 (SRID:3857) 存儲在數(shù)據(jù)庫, 這樣在運行時就無需進(jìn)行坐標(biāo)系轉(zhuǎn)換, 效率最高;