地图大量数据查询与渲染——bug及解决方案

本文记录大数据可视化项目中信息查询过程遇到的实际问题及解决方案,用到了Vue自定义组件、Promise.all、DocumentFragment、event loop等。

项目需求

项目使用的arcgis地图服务中主要地图要素为图斑即面状要素,需要根据图斑属性对图斑进行查询,获得符合要求的图斑并高亮显示

问题及解决方案总结

问题1

属性查询多条件组合问题:简单易懂的多条件的输入

解决方案:自定义条件输入组件,分为图斑属性条件和所在区域条件,在此篇中不详细展开。

问题2

arcgis server查询数量上限问题:arcgis server限制查询地图服务获得数量的上限,项目中设置上限为2000,查询得到的结果不完整。

解决方案:采用objectID对要素进行分批查询,使用Promise.all处理多个查询。

问题3

数据渲染问题:仅一个区县的图斑数量达到近2w,前端渲染压力大,造成卡崩,chrome浏览器报错paused before potential out-of-memory crash。

解决方案:①首先使用requestAnimationFrame进行渲染,每次渲染50个,仍报错;②先将数据添加渲染到一个新建graphicLayer变量中,渲染完成后添加到map对象中,可行。(类似于documentFragment)

问题4

用户体验问题:渲染图层添加到map对象中需要一定时间(数据量为2w时超过10s),用户体验不佳。

解决方案:添加图层时地图上覆盖loading状态,图层添加完成后取消loading。①arcgis api for js对map对象提供layer-add监听图层添加事件,该方法在3.5及以上版本可用,项目使用3.25版本,不可行。②在添加图层后,用setTimeout设置3000ms后取消loading,可行。

地图大量数据查询与渲染——bug及解决方案_第1张图片

具体解决方案

问题2:arcgis server查询数量上限问题

数据量小:Maximum number of records returned by the server

数据量不超过2k时,可通过设置地图服务属性中的Maximum number of records returned by the server(服务器返回的最大记录数)解决,设置该属性超过一定数量容易导致查询超时,因此项目中设置为2000。

服务器返回的最大记录数:客户端(例如 ArcGIS Web API)可执行查询操作以返回地图服务中的特定信息或记录。此属性用于指定,对于任意给定的查询操作,服务器可返回到客户端的记录的个数。如果服务器返回的记录数量过大,可能会降低使用地图服务的客户端应用程序(例如 Web 浏览器和 GIS 服务器)的性能。(来自官方文档)

数据量大:分批查询

实际数据中一个区县图斑总量近2w,用某区县图斑进行测试,分批查询思路如下:

①根据图斑的ObjectID属性,每2000分为一批,每批进行一次QueryTask。

②查API得知,QueryTask的excute方法可返回一个Promise,用Promise.all()处理多个查询。

③得到分批查询结果,进行合并。

简要代码:

//开始loading
this.isProgressShow=true;
//新建Promise数组
let promiseArr = [];
//设置总数,每批数量,查询次数等
let sum = 100000, num=0, step = 2000, max = sum%step?sum/step+1:sum/step;
while(num < max){
 //地图服务url
 let MapServer = this.GLOBAL.BASE_FEATURE_SERVER_URL+"/MapServer/"+this.visibleLayers;
 //新建QueryTask
 let queryTask = new this.mapObj.QueryTask(MapServer);
 //新建Query
 let query = new this.mapObj.Query();
 //queryText为属性查询sql语句 其后为分批sql查询语句
 let curMax = num===max-1?sum:(num+1)*step;
 let curMin = num*step;
 query.where = queryText + ' AND OBJECTID > '+ curMin +' AND OBJECTID <=' + curMax;
 //返回所有属性字段
 query.outFields = ["*"];
 //空间参考系
 query.outSpatialReference= new this.mapObj.SpatialReference({wkid: 4326});
 //返回几何要素
 query.returnGeometry = true;
 //执行查询,返回Promise,添加到Promise数组中
 promiseArr.push(queryTask.execute(query));
 num++;
}
//等待所有查询结束显示查询结果,发生错误则结束loading,打印错误
Promise.all(promiseArr).then(function(result){
 that.ShowFindResult(result);
}).catch(function(e){
 that.isProgressShow=false;
 console.log(e);
});

问题3:数据渲染问题

假设查询得到区县所有数据,result中所有数据合并有近2w个,将所有数据直接渲染在map的graphicsLayer中,chrome报错paused before potential out-of-memory crash。

首先渲染需要一定时间,等待期间为提升用户体验,尝试动画分批渲染。

渲染动画:Window.requestAnimationFrame

window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。(来自MDN)

简要代码:

//数据总数、渲染次数、每次渲染个数等
let resultCount = queryFeatures.length;
let countOfRender = 0;
let once = 50;
let loopCount = resultCount/once;
//渲染并添加到map中
function addGraphic(){
 for(let j=0;j

requestAnimationFrame分批渲染实际上和单个graphic添加到map无差别,只是能够配合浏览器刷新时机进行数据渲染,提升用户体验,数据量大时仍会导致内存不足问题。

解决渲染压力过大:GraphicsLayer

将数据先渲染到新建的GraphicsLayer中,渲染结束后添加到map中,该解决思路类似于DocumentFragment。

简要代码:

//新建graphicsLayer
let graphicsLayer=new this.mapObj.GraphicsLayer({id: "queryResult"});

//graphic添加到新建的graphicsLayer中,用requestAnimationFrame或者直接单个添加均可。
/*此处为将graphic添加到graphicsLayer代码*/

//渲染完成后将graphicsLayer添加到map中
this.isProgressShow = false;
this.map.addLayer(graphicsLayer);

至此解决渲染压力过大的问题,但将graphicsLayer添加到layer中比较耗时,用户将等待超过10s(不同环境可能不同)的时间直到渲染图层显示,且这段时间内没有其他提示,由此引出问题4。

问题4:用户体验问题

首先查api得map的layer-add事件,该事件触发得到的事件对象为添加的图层本身,可在该事件中结束loading。但该事件在arcgis api for js3.5及以上版本可用,项目采用的版本是3.25,项目中有部分对arcgis api源码的修改,不方便替换。

接着本来想用setTimeout延迟3s,在3s间loading的文字提示用户等待图层添加完毕。

that.drawGraphicProgress = '绘制完成,等待结果显示...';
that.map.addLayer(graphicsLayer);
setTimeout(function () {
 that.isProgressShow = false;
},3000);

测试发现,loading会在图层添加完成后结束,算是误打误撞解决一个问题吧。原理应该是这样:addLayer是异步微任务,且执行时间超过3s,setTimeout注册的宏任务(结束loading)等待时间超过3s,在微任务完成后立即触发。

又一个小总结

由项目问题出发,尝试不同的解决方案,而且能在解决过程中运用自己所学,虽然解决方案可能不够完美,但对我来说已经是一段非常好的学习体验,因此记录下来。写的比较仓促~~~

你可能感兴趣的:(JavaScript)