幸福杜彦臻 發表於 2026-3-9 15:57:00

关于地图渲染加20w数据展示和地图动画怎么做

<h1 data-id="heading-0">🧑‍💻 写在开头</h1>
<p>点赞 + 收藏 === 学会🤣🤣🤣</p>
<div>
<div>
<h3 data-id="heading-1">项目背景</h3>
<p>公司需要将全年设备安装量通过旗帜的形式展示在全国地图上,实现数据可视化大屏。主要技术挑战:</p>
<ul>
<li><strong>数据量大</strong>:全年设备安装数据约20万条</li>
<li><strong>实时更新</strong>:通过WebSocket实时接收数据</li>
<li><strong>动画效果</strong>:需要展示数据逐条添加的动态效果</li>
<li><strong>性能要求</strong>:需要保持60fps的流畅动画</li>
</ul>
<h3 data-id="heading-2">一、初始实现与性能瓶颈</h3>
<h4 data-id="heading-3">1.1 基础地图配置</h4>
<p>首先使用ECharts搭建基础地图框架:</p>
</div>
</div>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">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: ,
                animation: false// 关闭内置动画
            }
      ]
    };
    this.chart.setOption(option);
}</pre>
</div>
<h4 data-id="heading-4">1.2 动画设计</h4>
<p>设计旗帜生长动画效果,通过随机数实现多样化的动画展示:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">// 旗帜动画效果设计
const flagAnimations = [
    'scaleUp',      // 从小变大
    'fadeIn',       // 淡入
    'bounceIn',   // 弹跳进入
    'rotateIn'      // 旋转进入
];

function getRandomAnimation() {
    return flagAnimations;
}</pre>
</div>
<div>
<div>
<h4 data-id="heading-5">1.3 遇到的性能瓶颈</h4>
<p>当数据量达到3-5万条时,开始出现明显卡顿:</p>
<ul>
<li>动画帧率下降到30fps以下</li>
<li>内存占用持续增长</li>
<li>缩放平移操作卡顿</li>
<li>WebSocket数据堆积</li>
</ul>
<h3 data-id="heading-6">二、分层策略优化方案</h3>
<p>经过调研,我们采用了<strong>分层策略</strong>来优化性能,根据不同的缩放级别采用不同的渲染策略。</p>
<h4 data-id="heading-7">2.1 分层策略设计</h4>
</div>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">const zoomConfigs = {
    low: {// 低缩放级别:全国视图
      zoom: 2,
      sampleRate: 0.1,    // 10%抽样显示
      precision: 2,       // 经纬度精度:小数点后2位
      symbolSize: // 缩小图标
    },
    mid: {// 中缩放级别:省级视图
      zoom: 5,
      sampleRate: 0.5,    // 50%抽样显示
      precision: 3,       // 经纬度精度:小数点后3位
      symbolSize:
    },
    high: { // 高缩放级别:市级视图
      zoom: 10,
      sampleRate: 1,      // 100%显示
      precision: 4,       // 经纬度精度:小数点后4位
      symbolSize:
    }
};</pre>
</div>
<h4 data-id="heading-8">2.2 动态动画调度系统</h4>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">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) =&gt; {
            if (this.pendingList.length === 0) {
                this.stopAnimation();
                return;
            }
            
            // 帧率控制
            if (currentTime - lastTime &gt;= 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 =&gt; {
            // 全局去重
            const globalKey = `${item.lng},${item.lat}`;
            if (this.deviceSet.has(globalKey)) return;
            this.deviceSet.add(globalKey);
            
            const point = {
                value: ,
                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();
      }
    }
}</pre>
</div>
<h4 data-id="heading-9">2.3 智能显示判断</h4>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">// 根据缩放级别判断是否显示
shouldDisplay(point, config) {
    // 完全显示模式
    if (config.sampleRate &gt;= 1) return true;
   
    // 抽样显示模式
    const displayChance = Math.random();
    return displayChance &lt; config.sampleRate;
}

// 生成显示键(根据精度去重)
getDisplayKey(point, config) {
    const lng = point.value.toFixed(config.precision);
    const lat = point.value.toFixed(config.precision);
    return `${lng},${lat}`;
}</pre>
</div>
<h4 data-id="heading-10">2.4 缩放级别变化处理</h4>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">// 监听缩放变化
setupZoomListener() {
    this.chart.on('georoam', () =&gt; {
      const option = this.chart.getOption();
      if (option.geo &amp;&amp; option.geo) {
            const newZoom = option.geo.zoom;
            if (Math.abs(newZoom - this.currentZoom) &gt; 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 &gt;= 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 &lt; this.allDeviceList.length; i += step) {
      const item = this.allDeviceList;
      const key = this.getDisplayKey(item, config);
      if (!this.displaySet.has(key)) {
            this.displaySet.add(key);
            this.displayList.push(item);
      }
    }
}</pre>
</div>
<h3 data-id="heading-11">三、其他优化技巧</h3>
<h4 data-id="heading-12">3.1 内存管理优化</h4>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">// 定期清理过期数据
setupMemoryManagement() {
    setInterval(() =&gt; {
      // 限制总数据量
      const maxTotal = 150000;
      if (this.allDeviceList.length &gt; 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) =&gt; {
      const data = JSON.parse(event.data);
      buffer.push(...data);
      
      // 流量控制:如果缓冲过多,暂停接收
      if (buffer.length &gt; 5000 &amp;&amp; !processing) {
            this.ws.pause();
      }
      
      if (!processing) {
            this.processWebSocketBuffer();
      }
    };
}</pre>
</div>
<h4 data-id="heading-13">3.2 性能监控</h4>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">// 添加性能监控
setupPerformanceMonitor() {
    let frames = 0;
    let lastTime = performance.now();
   
    const monitor = () =&gt; {
      frames++;
      const currentTime = performance.now();
      
      if (currentTime - lastTime &gt;= 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 &lt; 30) {
      // 降低渲染质量
      this.frameInterval = 100; // 10fps
      this.batchSize = 50;
    } else if (fps &gt; 50) {
      // 提高渲染质量
      this.frameInterval = 33; // 30fps
      this.batchSize = 150;
    }
}</pre>
</div>
<div>
<div>
<h3 data-id="heading-14">四、效果对比</h3>
<h4 data-id="heading-15">优化前:</h4>
<ul>
<li>3万数据开始卡顿</li>
<li>内存占用500MB+</li>
<li>缩放操作延迟明显</li>
<li>动画掉帧严重</li>
</ul>
<h4 data-id="heading-16">优化后:</h4>
<ul>
<li>12万数据流畅运行</li>
<li>内存控制在200MB以内</li>
<li>缩放操作流畅</li>
<li>保持30fps以上动画</li>
</ul>
<h3 data-id="heading-17">五、总结</h3>
<p>通过分层策略优化,我们成功实现了:</p>
<ol>
<li><strong>智能显示</strong>:根据缩放级别动态调整显示策略</li>
<li><strong>性能平衡</strong>:在视觉效果和性能之间找到平衡点</li>
<li><strong>内存控制</strong>:有效管理大量数据的内存占用</li>
<li><strong>流畅动画</strong>:保持高帧率的动画效果</li>
</ol>
<p>这种分层策略不仅适用于地图可视化,也可以扩展到其他大规模数据可视化场景中。关键思想是:<strong>不同视角需要不同精度的数据展示</strong>。</p>
</div>
<div>
<h3 id="tid-D8HBxE">如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。</h3>
</div>
<p><em><img src="https://img2024.cnblogs.com/blog/2149129/202501/2149129-20250122165814748-630765389.png" alt="" loading="lazy"></em></p>
</div>
</div><br><br>
来源:https://www.cnblogs.com/smileZAZ/p/19690640
頁: [1]
查看完整版本: 关于地图渲染加20w数据展示和地图动画怎么做