数据秀热力图总结

离线热力图

展示内容

         数据秀离线热度展示的是过去15天内用户定位点形成的热力图,可以看出我们用户的分布情况。通过可视化展示之后,可以看到一些有意思的事情,比如:

offline-heatmap1
可以明显看出我们用户分布出现一边倒的局面,至于原因,也许与西北地区人口密度、经济状况或者我们公司的推广策略有关。

offline-heatmap2
这个热度明显跟城市的发达情况存在很大关联,最热的地方理所当然是北上广。

offline-heatmap3
隐约看到了交通路线

技术实现

           最初做这个可视化展示的时候,初步决定展现百万级别的数据,一开始,我竟然妄想要一次性把15天的数据全部加载过来,缓存在本地中,后来经过计算,证明自己太年轻,一个经纬度114.41792346,39.68452315大概20多个字符,因为百万级别的数据是经过聚合后的(原始的定位点量太大了),所以每个聚合后的点有一个pv值,表示这个点附近的真实定位次数,这样的话,一个定位点需要30多个字符,用utf-8编码,因为数据没有中文,就是30多个字节,15天的量 30*1000000*15/1024/1024=430M,如果网速是15M/s的话,大概也需要30s,绝对无法接受。既然一次性加载数据无法接受,只能是拖动、或者缩放地图的时候,实时去加载在屏幕内可见的地图区域中的点,进行展示。

           经过思考,百万级别的数据量其实是不合理的。地图缩放级别为3级的时候,比例尺约为14公里/像素,百万个点,每个点面积平均为960万/100万=9.6平方公里,而一个像素表示的面积是196平方公里,一个点在屏幕上的大小为9.6/196=0.05个像素。。显然百万级别的点有点太多了。而地图缩放级别为18级的时候,比例尺为0.5米/像素,如果屏幕分辨率为1920*1080,一个点在屏幕上的大小为9.6/(0.0005*0.0005)/(1920*1080)=18个屏幕之多,这个时候百万个点又有点太少了,所以正确的做法是不同级别展示不同个数的点。

           那么每个级别展示多少个点合适呢,我定了一个目标,在网速为100k/s、数据下载时间不超过1s的前提下,尽可能的多的加载点。100*1024/30*3=10240(开启gzip压缩,压缩率大概为30%多,所以乘了3),所以最终定的数据为每次加载的量最多为1万个,地图缩放级别为6级的时候,中国地图大概要占1.6个屏幕,每个屏幕内最多一万个点,则6级最多1.6万个点,地图缩放级别为3-5级时,中国地图在一个屏幕内,所以3-5级,最多展示一万个点,以此计算,最终计算结果如下

3-5:10,000

6:16,000

7:56,000

8:200,000

9:800,000

10:3,200,000

11:12,800,000

12-18:12,800,000(如果数据库存储性能 、查询性能不用考虑的话,其实可以继续增大)

为了尽可能提高页面渲染、响应的速度,又做了以下几点优化:

  • 在地图3-5级别的时候,因为展示的点内容是一样的。所以在客户端第一次加载之后进行了缓存。

  • 在拖动地图的过程中,因为展示的地图区域在不断变化,这时候加载的点是无效的,所以在释放鼠标的时候,才会去加载相应的点。

  • 在每次发出请求时,先检查上一次的请求是否已经返回,如果还没有,直接abort掉,避免客户端对过时的数据进行处理。

 

实时热力图

展示内容

数据秀实时热度展示的是最近几分钟内用户的定位点形成的热力图,可以实时看到我们用户的分布情况。

技术实现

实时数据接收方面通过websocket实现,如果每接收到一次数据就进行一次渲染,会引起很多无谓的重绘,所以根据显示器60帧/秒的刷新频率,在16ms内最多渲染一次,每收到新的数据就加入队列,等到可以渲染的时候,取出队列里所有的数据进行渲染,这个场景使用requestAnimationFrame太合适了。

打开页面大概两三分钟之后,页面上展示的点就会积累到一个比较高的值,离此时时间越久的点会慢慢淡化,热度降低。具体实现方案是有一个衰减系数,所有的点定时乘以衰减系数,同时socket也在不断的接收新数据,然后在页面上重新渲染,在点多的时候,这个计算就会非常耗时了,是个CPU密集型的任务,如果页面只是静态展示还好,因为js是单线程,一旦用户这时候拖动、缩放地图只能面对一个无比卡顿、甚至没有响应的页面。所以WebWorker这时候就派上用场了,这个耗时任务交给WebWork去后台计算再合适不过了。

在使用WebWorker的过程中,发现了一个问题,WebWorker为了避免线程安全问题,主线程与WebWork进行通信传递数据的时候,是copy了一份传过去的,而我们的点又特别多,复制十万个点在我的机器上要好几秒的样子,这个等待时间对用户来说还是太长了,所以直接把数据存储在WebWorker中了,从WebWorker中只拿出需要展示的数据,这样复制数据的时间就大大减短了。

后续还发现一个问题,用户短时间内拖动了好几次地图的时候,要等待半天才能渲染出结果,因为WebWorker累计了好多计算任务,直到所有的计算任务处理完毕,才会处理最新的一次计算任务。但实际上我们新只需要计算、展现最后一次的数据即可,怎么让WebWorker终止之前的无效计算呢,因为WebWorker在计算中,这时候给它发消息,它也会等到计算完毕才来处理新接收的消息。所以我把耗时的计算任务分段执行。每执行一段,就暂停一下,在暂停的空隙中,就可以收到新的消息并处理。这个问题就顺利解决了。

 

 

发表评论