关于地图渲染加20w数据展示和地图动画怎么做
🧑💻 写在开头
点赞 + 收藏 === 学会🤣🤣🤣
前端性能优化实战:ECharts地图渲染12万+数据动态动画方案
本文记录了在实际项目中,使用ECharts地图组件渲染12万+设备安装数据的性能优化实战经验,包含完整的技术方案和代码实现。
项目背景
公司需要将全年设备安装量通过旗帜的形式展示在全国地图上,实现数据可视化大屏。主要技术挑战:
- 数据量大:全年设备安装数据约20万条
- 实时更新:通过WebSocket实时接收数据
- 动画效果:需要展示数据逐条添加的动态效果
- 性能要求:需要保持60fps的流畅动画
一、初始实现与性能瓶颈
1.1 基础地图配置
首先使用ECharts搭建基础地图框架:
initChart() {
this.chart = echarts.init(this.$refs.chart);
let option = {
geo: {
map: 'china',
roam: true,
zoom: 1.1,
scaleLimit: { min: 1, max: 10 },
itemStyle: {
areaColor: 'rgba(91,97,141,.3)',
borderColor: 'rgba(0,0,0,.2)'
}
},
series: [
// 基础地图层
{
type: 'map',
map: 'china',
itemStyle: {
areaColor: 'rgba(0,0,0,0)',
borderColor: 'rgba(255,255,255,1)',
}
},
// 设备点图层
{
id: 'scatter',
type: 'scatter',
coordinateSystem: 'geo',
data: [],
symbol: 'image://flag.png', // 旗帜图标
symbolSize: [16, 22],
animation: false // 关闭内置动画
}
]
};
this.chart.setOption(option);
}
1.2 动画设计
设计旗帜生长动画效果,通过随机数实现多样化的动画展示:
// 旗帜动画效果设计
const flagAnimations = [
'scaleUp', // 从小变大
'fadeIn', // 淡入
'bounceIn', // 弹跳进入
'rotateIn' // 旋转进入
];
function getRandomAnimation() {
return flagAnimations[Math.floor(Math.random() * flagAnimations.length)];
}
1.3 遇到的性能瓶颈
当数据量达到3-5万条时,开始出现明显卡顿:
- 动画帧率下降到30fps以下
- 内存占用持续增长
- 缩放平移操作卡顿
- WebSocket数据堆积
二、分层策略优化方案
经过调研,我们采用了分层策略来优化性能,根据不同的缩放级别采用不同的渲染策略。
2.1 分层策略设计
const zoomConfigs = {
low: { // 低缩放级别:全国视图
zoom: 2,
sampleRate: 0.1, // 10%抽样显示
precision: 2, // 经纬度精度:小数点后2位
symbolSize: [8, 11] // 缩小图标
},
mid: { // 中缩放级别:省级视图
zoom: 5,
sampleRate: 0.5, // 50%抽样显示
precision: 3, // 经纬度精度:小数点后3位
symbolSize: [12, 16]
},
high: { // 高缩放级别:市级视图
zoom: 10,
sampleRate: 1, // 100%显示
precision: 4, // 经纬度精度:小数点后4位
symbolSize: [16, 22]
}
};
2.2 动态动画调度系统
class AnimationScheduler {
constructor() {
this.pendingList = []; // 待处理数据队列
this.allDeviceList = []; // 所有数据存储
this.displayList = []; // 当前显示数据
this.deviceSet = new Set(); // 数据去重
this.displaySet = new Set(); // 显示去重
this.animationTimer = null;
// 帧率控制
this.frameInterval = 50; // 20fps
this.batchSize = 100; // 每批处理数量
}
// 启动动画调度
startAnimation() {
if (this.animationTimer) return;
let lastTime = 0;
const animate = (currentTime) => {
if (this.pendingList.length === 0) {
this.stopAnimation();
return;
}
// 帧率控制
if (currentTime - lastTime >= this.frameInterval) {
lastTime = currentTime;
this.processBatch();
}
this.animationTimer = requestAnimationFrame(animate);
};
this.animationTimer = requestAnimationFrame(animate);
}
// 处理一批数据
processBatch() {
const batch = this.pendingList.splice(0, this.batchSize);
const config = this.getCurrentZoomConfig();
let hasNewData = false;
batch.forEach(item => {
// 全局去重
const globalKey = `${item.lng},${item.lat}`;
if (this.deviceSet.has(globalKey)) return;
this.deviceSet.add(globalKey);
const point = {
value: [item.lng, item.lat],
createTime: item.createTime
};
this.allDeviceList.push(point);
// 根据当前缩放级别抽样
if (this.shouldDisplay(point, config)) {
const displayKey = this.getDisplayKey(point, config);
if (!this.displaySet.has(displayKey)) {
this.displaySet.add(displayKey);
this.displayList.push(point);
hasNewData = true;
}
}
});
// 批量更新图表
if (hasNewData) {
this.updateChart();
}
}
}
2.3 智能显示判断
// 根据缩放级别判断是否显示
shouldDisplay(point, config) {
// 完全显示模式
if (config.sampleRate >= 1) return true;
// 抽样显示模式
const displayChance = Math.random();
return displayChance < config.sampleRate;
}
// 生成显示键(根据精度去重)
getDisplayKey(point, config) {
const lng = point.value[0].toFixed(config.precision);
const lat = point.value[1].toFixed(config.precision);
return `${lng},${lat}`;
}
2.4 缩放级别变化处理
// 监听缩放变化
setupZoomListener() {
this.chart.on('georoam', () => {
const option = this.chart.getOption();
if (option.geo && option.geo[0]) {
const newZoom = option.geo[0].zoom;
if (Math.abs(newZoom - this.currentZoom) > 0.1) {
this.currentZoom = newZoom;
this.handleZoomChange();
}
}
});
}
// 处理缩放变化
handleZoomChange() {
const config = this.getCurrentZoomConfig();
// 只有层级变化时才重建显示数据
if (config.level !== this.currentZoomLevel) {
this.currentZoomLevel = config.level;
this.rebuildDisplayList();
}
}
// 重建显示数据
rebuildDisplayList() {
const config = this.getCurrentZoomConfig();
this.displayList = [];
this.displaySet = new Set();
if (config.sampleRate >= 1) {
// 全量显示模式
this.displayAllData(config);
} else {
// 抽样显示模式
this.displaySampledData(config);
}
this.updateChart();
}
// 全量显示(高精度去重)
displayAllData(config) {
for (const item of this.allDeviceList) {
const key = this.getDisplayKey(item, config);
if (!this.displaySet.has(key)) {
this.displaySet.add(key);
this.displayList.push(item);
}
}
}
// 抽样显示
displaySampledData(config) {
const step = Math.max(1, Math.floor(1 / config.sampleRate));
for (let i = 0; i < this.allDeviceList.length; i += step) {
const item = this.allDeviceList[i];
const key = this.getDisplayKey(item, config);
if (!this.displaySet.has(key)) {
this.displaySet.add(key);
this.displayList.push(item);
}
}
}
三、其他优化技巧
3.1 内存管理优化
// 定期清理过期数据
setupMemoryManagement() {
setInterval(() => {
// 限制总数据量
const maxTotal = 150000;
if (this.allDeviceList.length > maxTotal) {
const removeCount = this.allDeviceList.length - 120000;
this.allDeviceList.splice(0, removeCount);
// 清理相关缓存
this.cleanCache();
// 重建显示
this.rebuildDisplayList();
}
}, 30000); // 每30秒检查一次
}
// WebSocket数据流控
setupWebSocketFlowControl() {
let buffer = [];
let processing = false;
this.ws.onmessage = (event) => {
const data = JSON.parse(event.data);
buffer.push(...data);
// 流量控制:如果缓冲过多,暂停接收
if (buffer.length > 5000 && !processing) {
this.ws.pause();
}
if (!processing) {
this.processWebSocketBuffer();
}
};
}
3.2 性能监控
// 添加性能监控
setupPerformanceMonitor() {
let frames = 0;
let lastTime = performance.now();
const monitor = () => {
frames++;
const currentTime = performance.now();
if (currentTime - lastTime >= 1000) {
const fps = Math.round(frames * 1000 / (currentTime - lastTime));
console.log(`当前FPS: ${fps}`);
// 动态调整策略
this.adjustStrategyByFPS(fps);
frames = 0;
lastTime = currentTime;
}
requestAnimationFrame(monitor);
};
requestAnimationFrame(monitor);
}
// 根据FPS动态调整
adjustStrategyByFPS(fps) {
if (fps < 30) {
// 降低渲染质量
this.frameInterval = 100; // 10fps
this.batchSize = 50;
} else if (fps > 50) {
// 提高渲染质量
this.frameInterval = 33; // 30fps
this.batchSize = 150;
}
}
四、效果对比
优化前:
- 3万数据开始卡顿
- 内存占用500MB+
- 缩放操作延迟明显
- 动画掉帧严重
优化后:
- 12万数据流畅运行
- 内存控制在200MB以内
- 缩放操作流畅
- 保持30fps以上动画
五、总结
通过分层策略优化,我们成功实现了:
- 智能显示:根据缩放级别动态调整显示策略
- 性能平衡:在视觉效果和性能之间找到平衡点
- 内存控制:有效管理大量数据的内存占用
- 流畅动画:保持高帧率的动画效果
这种分层策略不仅适用于地图可视化,也可以扩展到其他大规模数据可视化场景中。关键思想是:不同视角需要不同精度的数据展示。
如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。


浙公网安备 33010602011771号