在项目过程中,时常会遇到加载一个市的点位数据,如果后端一次性返回整个区域的数据,不仅会影响数据传输,更重要的是对前端渲染性能要求高,更影响用户体验,毕竟谁也不愿意拖动一下地图,就是漫长的等待直至浏览器崩溃。
针对上述问题,使用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初始化此处不做赘述
加载图层时,不会一次请求所有数据,而是像栅格瓦片那样一小块一小块的加载。
请求返回瓦片数据,与mapbox的pbf相同。