前言
近期实现了一个时空可视分析系统
的前端部分,帮助警方看到特定地区(主要为昆明)、特定时间段【时】、特定分局、派出所、街道【空】的各种类型警情统计;并提供对未来一段时间的警情预测,对警情高发区域进行重点部署和管控。整体界面如下图所示。
界面截图(概念图)
实现这个系统和优化性能的过程中有了一些感悟和心得,在这篇博客中进行记录。
百度地图和百度离线地图
其实在2015年飞利浦实习时开发一款iOS跑步软件过程中有用过百度地图进行跑步路径绘制,但在web开发过程中还是第一次遇到GIS开发相关的任务,好在团队中有一个会写代码的宠物店老板,有丰富的离线地图开发经验,指导我完成了这次的开发任务。
可能大家对百度地图都不陌生,他可以支持地图样式定制化,各种风格的样式都可以进行配置,而且现在已经强大到你上传一幅图片,可以自动识别出你的风格进行识图配色,并导出为JSON文件。主要可配置的有:陆地、水系、陆地、文字描边、道路,这个功能可以让设计师也直接参与到地图风格的定型,减少工程师的工作量。
百度地图底层技术
大家可能注意到了,百度地图可以缩放到不同层级的,比如最大的层级是可以看到街道一级的,最小的层级是洲级别的,就百度地图而言,层级大约是0-18级。
地图的每一层又是可以左右挪动的,每一层都是由一张张底图组成的,他们的专业名称叫做瓦片
。例如下图就是有9张瓦片组成的。
明白了上面两点,我们就可以用三个变量:瓦片等级(level)和瓦片坐标编号(X,Y)唯一确定瓦片,这也是离线抓取相应图片并放置到正确文件夹路径的关键所在,比如第一层级的最左上角的瓦片路径就是/0(level)/1(x)/1.png(y)。
当然,层级越高,我们看到的细节也就越多,例如用谷歌地图放大到最大层级就是可以看到你家的。这也意味着我们需要更多的瓦片来描述更高层级的地图,通常下一级的瓦片,是由上一级瓦片切割为4片组成的,就形成了下图的瓦片金字塔。
百度离线地图瓦片的获取
由于我们系统需要部署在公安网内,不能访问外网,所以我们并不能实时获取百度地图的瓦片,只能提前将百度地图瓦片缓存下来,以上一节提到的方式进行保存。爬取百度离线地图,已经有很多人提供了代码甚至是exe文件,这里就不展开说了。这个过程是比较慢的,下载到16级,通常就需要三四天,因为碎片文件太多了,仅昆明周边,到15级大约有400万张图片了。
跟前线部署人员交流过程中发现,在文件传输和拷贝的过程中,也有文件碎片引起的速度慢的问题,这个问题后面想通过插external盘解决,毕竟让大家现场等三四天成本还是比较高的。
后面优化了爬虫的逻辑,每秒大概能下10M的图片,但很快百度就打电话过来问责了,说严重影响到了他们的服务了,然后就发现号和ip都被百度封了。到了第二天,百度直接加密了,获取的图片都是错误符EOF。
也就是说,自此之后,这种爬虫获取的方式将永久行不通了。百度离线地图获取的路被永远堵死了。
其他地图
其实不局限于百度地图,有很多其他选择,比如高德地图、谷歌地图等都是不错的选择,需要注意的是地图直接的经纬度标准不同,需要转换,否则会发生偏移甚至不可用。
例如我在做项目的时候拿到的是高德地图的数据,放到百度地图上,发生了明显的偏移。我使用nodejs
写了一个读文件然后转换的逻辑,并写回文件。
转换前后轮廓对比(分局层面)
转换前后轮廓对比(派出所层面)
关于各大地图厂商的经纬度转换规则及实现,请参考这篇博客。
项目总体架构
项目是使用的umi+dva
作为整体框架,使用数据流的方式管理的,当时空选项发生改变时,dispatch
相应的action,通过异步网络请求出发Effects然后流向Reducers并改变State,然后通过state去改变绑定的View值。
性能优化策略1:组件
作为React组件,并非每次轮询,组件都需要重新渲染一次的,通过重写shouldComponentUpdate
方法可以判断是否真的需要刷新,这在状态比较多的时候,是很有效的性能提升手段。
性能优化策略2:地图层级的防抖
在监听地图Zoom的过程中,我发现会经常会发生抖动,造成了界面非常卡的现象。其实与防抖相对的还有节流,具体实现细节可以看JavaScript函数节流和函数防抖之间的区别。
通过加入防抖机制,控制只有足够多空闲时间才会触发进入特定层级的事件,让界面流畅了许多。
性能优化策略3:预渲染栅格线
在我们的业务需求中,有个特殊的需求是要将地图切分为边长为500米的方格,用绿橙黄红四色展示这块区域内的警情发生频率。
由于这个需求是后面加的,我前面并没有绘制栅格线,因此选用了对百度地图更为友好的mapV。兄弟公司实现这个需求的时候选用的是高德地图+openLayer。后期又接到了任务让加上栅格,于是我进行了调研,想出了两种备选方案。
方案1是让后台将所有格子都返回,即使这里面没有警情,我们这边让他不填充,但是边界线也是会有的,这样实现需要的时间比较短,考虑到实际出现警情的区域比较稀疏,因此为了减少网络流量和后台工作量,由我这边产生,时间复杂度大约是O(NM);
方案二是引入openlayer,进行绘制,但是这样的话需要上手一下openlayer,而且配合百度地图离线版也是有一定的难度的,很可能会有坑(网上已经看到了不少人提问),但是时间复杂度可以优化到O(N+M)。
出于效率考虑,我先选择了方案一,并实现了该想法,但是系统不出意料地卡了起来,用户体验非常差,首次绘制需要等待大约5秒,每次移动都会卡一秒,卡顿感非常明显。于是我引入了滑动窗口的概念,仅将当前视图范围内和周边一点点范围所需展示的进行渲染,沿着拖动窗口方向预渲染,优化过后,非常流畅。
但事情并不总是一直顺利,很快就遇到了栅格与绘制出的正方形对不上的问题(见下图),多次尝试仍有一点点的偏移量,最后我不得不人工干预,定位到栅格的四个顶点,进行绘制,这样才算是完美匹配了。只能说我有点完美主义+强迫症,喜欢做到完美。
图层:用隐藏代替重绘/用过滤代替加Layer实现内存的高效分配
最早实现的版本中,每次zoom到不同图层,我都是将Layer移除然后重新绘制,这样效率比较低,但在数据量不大的时候影响也确实在忍受范围之内。后面增加了对特定分局的过滤需求后发现如果还这么搞,就会非常麻烦。
所以通过对overlay增加Type和分局字段,控制当前需要过滤掉的分局和层级图层,实现内存的高效分配(不需要反复释放和生成图层)。
参考
- node-canvas实现百度地图个性化底图绘制
- http://cntchen.github.io/2016/05/09/国内主要地图瓦片坐标系定义及计算原理/
- 百度地图离线API及地图数据下载工具-尝鲜篇
- JavaScript函数节流和函数防抖之间的区别
- 高德地图和百度地图之间的坐标转换
爬虫代码
|
|
原文作者: Chih-Hao
原文链接: http://zhihaozhang.github.io/2019/05/20/BMapOffline/
发表日期: May 20th 2019, 5:01:35 pm
版权声明: 本文采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可
-
Next Post百度离线地图之死
-
Previous Post《重学前端》学习笔记