vue openlayers地图加载大量线条时优化
如果一次性加载上万个带标题的线条,会造成地图卡顿, 优化方法是只加载当前视口内的点位,且只显示屏幕中心的线条的标题, 每次拖动地图只加载视口内的线条
- 后端返回的数据,扁平化后如下:
js
[
{label: '', color: '', featureFile: 'geojson数据', level: 1, children: []},
{label: '', color: '', featureFile: 'geojson数据', level: 2, children: []},
{label: '', color: '', featureFile: 'geojson数据', level: 3, children: []},
{label: '', color: '', featureFile: 'geojson数据', level: 4, children: []},
{label: '', color: '', featureFile: 'geojson数据', level: 5, children: []},
{label: '', color: '', featureFile: 'geojson数据', level: 1, children: []},
{label: '', color: '', featureFile: 'geojson数据', level: 2, children: []},
{label: '', color: '', featureFile: 'geojson数据', level: 3, children: []},
{label: '', color: '', featureFile: 'geojson数据', level: 4, children: []},
{label: '', color: '', featureFile: 'geojson数据', level: 5, children: []},
]
当地图缩放改变时会根据level加载指定level级别的数据
- 工具类
OlViewportLineUtil.js
js
import { Vector as VectorLayer } from 'ol/layer'
import { Vector as VectorSource } from 'ol/source'
import GeoJSON from 'ol/format/GeoJSON'
import { Style, Stroke, Text, Fill } from 'ol/style'
import { intersects, getCenter } from 'ol/extent'
import Point from 'ol/geom/Point'
import { distance } from 'ol/coordinate'
export default class OlViewportLineUtil {
constructor(map) {
this.map = map
this.geoFormat = new GeoJSON()
// 线条图层
this.lineVectorSource = new VectorSource({ useSpatialIndex: true })
this.lineVectorLayer = new VectorLayer({
source: this.lineVectorSource,
name: '线条图层',
zIndex: 999,
declutter: true,
declutterMode: 'label',
style: (feature, resolution) => {
const showLabel = getIsShowFeatureText(this.map, feature)
let featureText = feature.get('NAME')
const zoom = Math.round(this.map.getView().getZoom())
return new Style({
stroke:
new Stroke({
color: '#00A0E9',
width: 3,
}),
fill: new Fill({
color: '#00A0E9',
}),
text: showLabel
? new Text({
offsetY: -10,
font: '12px 微软雅黑',
placement: 'line',
text: featureText,
fill: new Fill({
color: '#fff',
}),
stroke: new Stroke({
color: '#006614',
width: 3,
}),
overflow: true,
})
: null,
})
},
})
this.map.addLayer(this.lineVectorLayer)
this.lineMetas = []
this.loadedInView = new Set()
}
setRiverTree(tree) {
this.lineMetas = this.flattenTree(tree)
this.refreshView()
}
refreshView() {
const view = this.map.getView()
const zoom = Math.round(view.getZoom())
const viewExtent = view.calculateExtent()
const w = viewExtent[2] - viewExtent[0]
const h = viewExtent[3] - viewExtent[1]
const bufferExtent = [viewExtent[0] - w * 0.3, viewExtent[1] - h * 0.3, viewExtent[2] + w * 0.3, viewExtent[3] + h * 0.3]
const shouldShow = new Set()
this.lineMetas.forEach((meta) => {
const level = Number(meta.level)
if (zoom < 6.5 + level) return
if (meta.extent) {
if (!intersects(bufferExtent, meta.extent)) return
}
shouldShow.add(meta.label)
})
this.loadedInView.forEach((label) => {
if (!shouldShow.has(label)) {
const f = this.lineVectorSource.getFeatureById(label)
f && this.lineVectorSource.removeFeature(f)
this.loadedInView.delete(label)
}
})
shouldShow.forEach(async (label) => {
if (this.loadedInView.has(label)) return
const meta = this.lineMetas.find((m) => m.label === label)
if (!meta || !meta.featureFile) return
try {
const geojson = JSON.parse(meta.featureFile)
let feature
if (geojson.type === 'FeatureCollection') {
const features = this.geoFormat.readFeatures(geojson)
feature = features[0]
} else {
feature = this.geoFormat.readFeature(geojson)
}
if (feature) {
feature.setId(label)
feature.set('label', meta.label)
feature.set('color', meta.color)
feature.set('level', meta.level)
this.lineVectorSource.addFeature(feature)
this.loadedInView.add(label)
}
} catch (e) {
}
})
this.lineVectorLayer.changed()
}
flattenTree(tree) {
const list = []
const loop = (nodes) => {
nodes.forEach((item) => {
if (item.featureFile) {
try {
const geojson = JSON.parse(item.featureFile)
let feat = null
if (geojson.type === 'FeatureCollection') {
const features = this.geoFormat.readFeatures(geojson)
feat = features[0]
} else {
feat = this.geoFormat.readFeature(geojson)
}
item.extent = feat?.getGeometry()?.getExtent() || null
} catch (e) {
item.extent = null
}
list.push(item)
}
if (item.children?.length) loop(item.children)
})
}
loop(tree)
return list
}
watchMap() {
this.map.on('movestart', () => {
this.lineVectorLayer.changed()
})
this.map.on('moveend', () => {
this.refreshView()
})
}
clear() {
this.lineMetas = []
this.loadedInView = new Set()
this.lineVectorSource.clear()
this.loadedInView.clear()
this.lineVectorLayer.changed()
}
destroy() {
this.clear()
this.map.removeLayer(this.lineVectorLayer)
}
}
/**
* 只显示屏幕中心附近的名称, 名称多了很卡顿
* @param {*} map
* @param {*} feature
* @returns 是否显示
*/
function getIsShowFeatureText(map, feature) {
const view = map.getView()
const mapSize = map.getSize()
if (!mapSize) return new Style({ stroke: new Stroke({ color: '#00A0E9', width: 2 }) })
const viewExtent = view.calculateExtent(mapSize)
const [minx, miny, maxx, maxy] = viewExtent
const w = maxx - minx
const h = maxy - miny
const centerBuffer = 0.3
const centerExtent = [minx + w * centerBuffer, miny + h * centerBuffer, maxx - w * centerBuffer, maxy - h * centerBuffer]
const geom = feature.getGeometry()
const showLabel = geom && intersects(geom.getExtent(), centerExtent)
return showLabel
}
- 使用
js
import OlViewportLineUtil from '@/util/OlViewportLineUtil.js'
this.lineUtil = new OlViewportLineUtil(this.map)
const list = [
{label: '', color: '', featureFile: 'geojson数据', level: 1, children: []},
{label: '', color: '', featureFile: 'geojson数据', level: 2, children: []},
{label: '', color: '', featureFile: 'geojson数据', level: 3, children: []},
{label: '', color: '', featureFile: 'geojson数据', level: 4, children: []},
{label: '', color: '', featureFile: 'geojson数据', level: 5, children: []},
{label: '', color: '', featureFile: 'geojson数据', level: 1, children: []},
{label: '', color: '', featureFile: 'geojson数据', level: 2, children: []},
{label: '', color: '', featureFile: 'geojson数据', level: 3, children: []},
{label: '', color: '', featureFile: 'geojson数据', level: 4, children: []},
{label: '', color: '', featureFile: 'geojson数据', level: 5, children: []},
]
this.lineUtil.setRiverTree(list)
this.lineUtil.watchMap()