小小吐槽一下
自从开始搞 cesium 相关的东西,各种疑难杂症就开始接踵而至,层出不穷。
更让人“气愤”的是,有些问题,就连 StackOverflow、Google 这种传统的解决问题的大杀招都不好用了,简直要给人气哭了好嘛!😂
不知道是不是因为搞 cesium 的人太少了,还是因为愿意分享的人太少了,不过从 GitHub 的关注度,其实也能看出点端倪。
就拿同是 web 可视化三维框架的 three.js 和 cesium 来作对比吧
简直不要太欺负人了,一个关注度六万,一个关注度才6千,差了一个量级。
因此,碰到问题得自己研究也很正常,顿时心里就好受了点。
闲话不多说,开始回到正题吧。
回到正题
至于为什么会碰到这个问题呢?
说起来,又是一言难尽。
感觉一言以蔽之就是:领导需要。
说实话,这其实是一个拍脑袋的需求,本来 3d 对显卡的配置的要求就蛮高,一个页面加多个 viewer,更增加了显卡的负担。
但是没办法,领导拍脑袋,你就得解决问题,领导只会关注你解没解决,而不关心你是怎样解决的。
那么现在的问题是,如果我们碰到了这个需求,我们该如何处之呢?
废话,按照领导的需求做呗,还能有啥更好的解决方案。
现在假设我们已经写好了我们的测试 demo:https://codepen.io/wuzhiqin/full/wvzvxbJ
只要你细心观察,你就会发现,从左到右,每个 viewer 的图层,是依次按照顺序加载出来的。
如果你要是经验丰富,就会发现这就是问题所在了。
依次按顺序加载,我们可以看成是单线程的过程,对于这个场景,单线程的效果有时候是令人无法忍受的。
如果前面的 viewer 加载瓦片地图的时候,一时半会儿获取不到,那么我们后面的 viewer 就一直无法加载瓦片地图,就会处于被阻塞的状态了,表现出来的效果就是一直处于黑屏状态,这时候用户体验就不是很好了。
如何解决这个问题呢?
别急,如何解决这个问题,本就是这篇文章想写的重点。
开始分析
首先,为了找到解决的方案,当然少不了要用调试工具,找到这块代码是写在什么地方的,然后,我们需要尝试不断地去修改调试,以期找到合适的解决方案。
以我们的 chrome devtool 工具为例:
我们可以在 Network 一栏找到 XHR 子栏目,里面列出了前端页面发送的所有 ajax 请求。
然后我们随便选一条,就能看到这条请求的调用栈。
接下来的工作就很死板了,就是需要一个个方法点进去看,看一看是否是适合我们修改的地方。
我们可以看到最后一个调用的方法在 Resource.js
里面
我们点进去看看这个方法:
可以看到,这就是单纯的调用 js 中发送 ajax 请求的 api,执行请求数据的操作。
在往上拉,看看整个方法的全貌:
可以看到,前面就是 new 了一个 XMLHttpRequest 对象的实例,然后执行了一堆判断,可想而知,这地方并没有我们关心的逻辑。
ajax 本身就是异步的,数据返回的数据,不是前端代码能控制的。
因此,我们只能继续往前找。
现在我们看第二个地方:
我们点进去,看一眼:
这一看,我们就能看出来,这里也不是我们要找的地方。
因为这里都只是在处理单个的图片 ajax 加载问题,并不涉及到批量的逻辑。
当然这里为了节省篇幅,后面就不一个个往前分析了,直接定位到我们的目的地去:
当然,由于篇幅的原因,这里直接展示不了所有代码,我们接着往下拉,定位到,doRequest 方法。
看到里面有个配置 throttleByServer
默认被设置成了 true
,我们试着改成 false
,然后看看加载的效果
我们看上面的图,能够发现,我们改了之后,加载效果明显比之前的更顺畅。
但是我们这里无法直接更改源码,并非说我们不能改。
而是因为,我们使采用别人开源的代码,我们很难搞明白当初为何这地方的代码要这么写,要是我们直接改了,有可能会影响到别的地方代码的正常运行。
那么我们该怎么办呢?
为了使我们的修改,影像最小化,我们可以像下面这样做:
let throttleByServer = false;
const {
ImageryLayer,
when,
Request,
defined,
TileProviderError,
RequestState,
ImageryState,
RequestType,
} = Cesium;
ImageryLayer.prototype._requestImagery2 =
ImageryLayer.prototype._requestImagery;
ImageryLayer.prototype._requestImagery = function (imagery) {
var imageryProvider = this._imageryProvider;
var that = this;
function success(image) {
if (!defined(image)) {
return failure();
}
imagery.image = image;
imagery.state = ImageryState.RECEIVED;
imagery.request = undefined;
TileProviderError.handleSuccess(that._requestImageError);
}
function failure(e) {
if (imagery.request.state === RequestState.CANCELLED) {
// Cancelled due to low priority - try again later.
imagery.state = ImageryState.UNLOADED;
imagery.request = undefined;
return;
}
// Initially assume failure. handleError may retry, in which case the state will
// change to TRANSITIONING.
imagery.state = ImageryState.FAILED;
imagery.request = undefined;
var message =
"Failed to obtain image tile X: " +
imagery.x +
" Y: " +
imagery.y +
" Level: " +
imagery.level +
".";
that._requestImageError = TileProviderError.handleError(
that._requestImageError,
imageryProvider,
imageryProvider.errorEvent,
message,
imagery.x,
imagery.y,
imagery.level,
doRequest,
e
);
}
function doRequest() {
var request = new Request({
throttle: false,
throttleByServer,
type: RequestType.IMAGERY,
});
imagery.request = request;
imagery.state = ImageryState.TRANSITIONING;
var imagePromise = imageryProvider.requestImage(
imagery.x,
imagery.y,
imagery.level,
request
);
if (!defined(imagePromise)) {
// Too many parallel requests, so postpone loading tile.
imagery.state = ImageryState.UNLOADED;
imagery.request = undefined;
return;
}
if (defined(imageryProvider.getTileCredits)) {
imagery.credits = imageryProvider.getTileCredits(
imagery.x,
imagery.y,
imagery.level
);
}
when(imagePromise, success, failure);
}
doRequest();
};
- 将之前原型链上的方法重命名
- 将源码这个方法拷贝出来
- 将
throttleByServer
变量抽离出来进行控制
这样,我们就做到了按需重载了。
比如,当我们需要在页面加载多 viewer 的时候,我们久可以用 _requestImagery
方法,当我们不需要加载的时候,就换回 _requestImagery2
。
当然,会改源码,仅仅只是第一步。要想进阶,得需要弄懂整个代码,然后对其进行优化,写出更符合自己需求的代码出来。