vue openlayers地图加载大量点位时优化
如果一次性加载上万个带标题的点位,会造成地图卡顿, 优化方法是只加载当前视口内的点位,且只显示屏幕中心的点位的标题, 每次拖动地图只加载视口内的点位
- 工具类
OlViewportPointUtil.js
js
import VectorLayer from 'ol/layer/Vector';
import VectorSource from 'ol/source/Vector';
import Point from 'ol/geom/Point';
import Feature from 'ol/Feature';
import Style from 'ol/style/Style';
import Icon from 'ol/style/Icon';
import Text from 'ol/style/Text';
import Fill from 'ol/style/Fill';
import Stroke from 'ol/style/Stroke';
import { containsCoordinate, intersects } from 'ol/extent';
import { fromLonLat } from 'ol/proj';
/**
* 显示可见范围内的点位
*/
export default class OlViewportPointUtil {
constructor(map, options = {}) {
this.map = map;
this.allPoints = [];
this.vectorSource = null;
this.vectorLayer = null;
this.options = {
style: this.getDefaultStyle.bind(this),
debounceTime: 100,
minZoom: 3,
...options,
};
this.initLayer();
this.bindMapEvent();
}
initLayer() {
this.vectorSource = new VectorSource();
this.vectorLayer = new VectorLayer({
source: this.vectorSource,
zIndex: 999,
declutter: true,
declutterMode: 'label',
style: this.options.style,
});
this.map.addLayer(this.vectorLayer);
}
getDefaultStyle(feature, resolution) {
// 最小缩放等级判断
const currentZoom = this.map.getView().getZoom();
if (currentZoom < this.options.minZoom) {
return null;
}
// 文字显示判断(中心区域才显示)
const showLabel = getIsShowFeatureText(this.map, feature);
const pointData = feature.get('data');
return new Style({
image: new Icon({
src: require('../assets/点位图标.png'),
scale: 0.5,
anchor: [0.5, 1],
}),
text: showLabel
? new Text({
text: pointData?.name || '', // 从 feature 里取标题
font: '14px Microsoft YaHei',
fill: new Fill({ color: '#ffffff' }),
stroke: new Stroke({ color: '#1a5a96', width: 2 }),
offsetY: -25,
textAlign: 'center',
})
: null,
});
}
setDataSource(points = []) {
if (!points || points.length === 0) return;
this.allPoints = points;
this.refreshView();
}
refreshView() {
this.vectorSource.clear();
const extent = this.map.getView().calculateExtent(this.map.getSize());
const features = [];
for (const point of this.allPoints) {
const jd = parseFloat(point.jd);
const wd = parseFloat(point.wd);
// const coord = fromLonLat([jd, wd]);
const coord = [jd, wd];
if (containsCoordinate(extent, coord)) {
const feature = new Feature({
geometry: new Point(coord),
data: point, // 把数据存到 feature 里
});
features.push(feature);
}
}
console.log('当前视口内点位数量:', features.length);
this.vectorSource.addFeatures(features);
}
bindMapEvent() {
let timer = null;
this.map.on('moveend', () => {
clearTimeout(timer);
timer = setTimeout(() => {
this.refreshView();
}, this.options.debounceTime);
});
}
clear() {
this.vectorSource.clear();
this.allPoints = [];
}
destroy() {
this.clear();
this.map.removeLayer(this.vectorLayer);
}
}
/**
* 只显示屏幕中心附近的名称
*/
function getIsShowFeatureText(map, feature) {
const view = map.getView();
const mapSize = map.getSize();
if (!mapSize) return false;
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();
return geom && intersects(geom.getExtent(), centerExtent);
}
- 使用
js
import OlViewportPointUtil from './olViewportPointUtil.js';
const list = [{name: '点位1', jd: '经度', wd: '纬度' }, ... ...]
this.pointUtil = new OlViewportPointUtil(this.map);
this.pointUtil.setDataSource(list);