openlayers 实时加载动态矢量瓦片

969 阅读2分钟

在项目过程中,时常会遇到加载一个市的点位数据,如果后端一次性返回整个区域的数据,不仅会影响数据传输,更重要的是对前端渲染性能要求高,更影响用户体验,毕竟谁也不愿意拖动一下地图,就是漫长的等待直至浏览器崩溃。

针对上述问题,使用postgresql动态切片,结合openlayers加载矢量瓦片,根据地图浏览行列号加载指定瓦片内的数据,具体流程如下:

postgresql主要用到st_asmvtgeom、st_makeenvelope函数

select *
from (
    SELECT st_astext(st_asmvtgeom(geometryfield,st_makeenvelope(xmin,ymin,xmax, ymax, 4326),4096,256,true)) AS geom,t.* 
     FROM table t
    ) tb
where geometryfield is not null

java代码实现时,需要结合切片方案和传入的行列号计算瓦片范围,根据范围的最大最小值进行查询,具体实现如下:

public byte[] getTiles( String serviceName, Integer x, Integer y, Integer z) {
        try {
            TileBox tileBox = tileBox = TileUtil4326.xyz2prj4326TileBox(z, x, y);
            
            if (tileBox != null) {
                int splitStart = serviceName.indexOf(":");
                int splitEnd = serviceName.indexOf("@");
                String tableName = serviceName.substring(splitStart + 1, splitEnd);
                tableName = tableName.replace(""", "").toLowerCase();
                String geomField = ztDataMapper.findFieldByType(tableName, "geometry");
                double xmin = tileBox.getXmin();
                double xmax = tileBox.getXmax();
                double ymin = tileBox.getYmin();
                double ymax = tileBox.getYmax();
                String serverName = serviceName.substring(serviceName.indexOf(":") + 1);
                List<Map<String, Object>> vectorTiles = ztDataMapper.getVectorTilesInfo(xmin, ymin, xmax, ymax, tableName, serverName, geomField);
                if (vectorTiles != null) {
                    VectorTileEncoder tileEncoder = new VectorTileEncoder(4096, 20, false);
                    for (Map<String, Object> map : vectorTiles) {
                        Geometry geom = new WKTReader().read(map.get("geomm").toString());
                        for (String key : map.keySet()) {
                            if (map.get(key) == null) {
                                map.put(key, "");
                            }
                        }
                        tileEncoder.addFeature(serviceName.substring(serviceName.indexOf(":") + 1), map, geom);
                    }
                    return tileEncoder.encode();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return new byte[0];
    }
​
   @RequestMapping(value = "/rest/{tableName}/{z}/{x}/{y}", method = RequestMethod.GET)
    public byte[] getTile(@PathVariable String tableName, @PathVariable Integer x, @PathVariable Integer y, @PathVariable Integer z) {
        return ztDataServiceImpl.getTiles(maptype,tableName, x, y, z);
    }

openlayers调用动态切片实现方法:

import VectorTileLayer from 'ol/layer/VectorTile'
import VectorTileSource from 'ol/source/VectorTile'
import MVT from 'ol/format/MVT'
import { createXYZ } from 'ol/tilegrid'
import * as olProj from 'ol/proj'// 矢量瓦片加载,mvt格式
export function getVectorTiles4326(url) {
  const dataLayer = new VectorTileLayer({
    declutter: true,
    renderMode: 'vector',
    source: new VectorTileSource({
      crossOrigin: 'anonymous',
      format: new MVT(),
      overlaps: true,
      projection: new olProj.Projection({
        code: 'EPSG:4326',
        units: 'degrees'
      }),
      tileGrid: createXYZ({
        extent: [-180, -90, 180, 90],
        tileSize: 256,
        resolutions: getResolutions(),
        maxZoom: 13
      }),
      url: url
    })
  })
  return dataLayer
}
​
// 计算4326坐标系下的分辨率
export function getResolutions() {
  const resolutions = []
  var maxResolution = 1.8712842777446113
  for (let i = 0; i <= 16; i++) {
    resolutions[i] = maxResolution / Math.pow(2, i)
  }
  return resolutions
}
​
// 图层调用
const url = 'http://ip:端口/mapserver/rest/{tableName}/{z}/{x}/{y}' // url为后端请求接口
const dataLayer = getVectorTiles4326(url)
map.addLayer(dataLayer) // map初始化此处不做赘述

加载图层时,不会一次请求所有数据,而是像栅格瓦片那样一小块一小块的加载。

image-20230331172524724.png

请求返回瓦片数据,与mapbox的pbf相同。

image-20230331172815162.png