开源

cesium 页面多 viewer 地图加载过缓解决方案

2020-12-28  本文已影响0人  纯爱枫若情

小小吐槽一下

自从开始搞 cesium 相关的东西,各种疑难杂症就开始接踵而至,层出不穷。

更让人“气愤”的是,有些问题,就连 StackOverflow、Google 这种传统的解决问题的大杀招都不好用了,简直要给人气哭了好嘛!😂

不知道是不是因为搞 cesium 的人太少了,还是因为愿意分享的人太少了,不过从 GitHub 的关注度,其实也能看出点端倪。

就拿同是 web 可视化三维框架的 three.jscesium 来作对比吧

image.png image.png

简直不要太欺负人了,一个关注度六万,一个关注度才6千,差了一个量级。

因此,碰到问题得自己研究也很正常,顿时心里就好受了点。

闲话不多说,开始回到正题吧。

回到正题

至于为什么会碰到这个问题呢?

说起来,又是一言难尽。

感觉一言以蔽之就是:领导需要。

说实话,这其实是一个拍脑袋的需求,本来 3d 对显卡的配置的要求就蛮高,一个页面加多个 viewer,更增加了显卡的负担。

但是没办法,领导拍脑袋,你就得解决问题,领导只会关注你解没解决,而不关心你是怎样解决的。

那么现在的问题是,如果我们碰到了这个需求,我们该如何处之呢?

废话,按照领导的需求做呗,还能有啥更好的解决方案。

image.png

现在假设我们已经写好了我们的测试 demo:https://codepen.io/wuzhiqin/full/wvzvxbJ

多viewer加载1.gif

只要你细心观察,你就会发现,从左到右,每个 viewer 的图层,是依次按照顺序加载出来的。

如果你要是经验丰富,就会发现这就是问题所在了。

依次按顺序加载,我们可以看成是单线程的过程,对于这个场景,单线程的效果有时候是令人无法忍受的。

如果前面的 viewer 加载瓦片地图的时候,一时半会儿获取不到,那么我们后面的 viewer 就一直无法加载瓦片地图,就会处于被阻塞的状态了,表现出来的效果就是一直处于黑屏状态,这时候用户体验就不是很好了。

如何解决这个问题呢?

别急,如何解决这个问题,本就是这篇文章想写的重点。

开始分析

首先,为了找到解决的方案,当然少不了要用调试工具,找到这块代码是写在什么地方的,然后,我们需要尝试不断地去修改调试,以期找到合适的解决方案。

以我们的 chrome devtool 工具为例:


image.png

我们可以在 Network 一栏找到 XHR 子栏目,里面列出了前端页面发送的所有 ajax 请求。

然后我们随便选一条,就能看到这条请求的调用栈。

接下来的工作就很死板了,就是需要一个个方法点进去看,看一看是否是适合我们修改的地方。

我们可以看到最后一个调用的方法在 Resource.js 里面

image.png

我们点进去看看这个方法:


image.png

可以看到,这就是单纯的调用 js 中发送 ajax 请求的 api,执行请求数据的操作。

在往上拉,看看整个方法的全貌:

image.png

可以看到,前面就是 new 了一个 XMLHttpRequest 对象的实例,然后执行了一堆判断,可想而知,这地方并没有我们关心的逻辑。

ajax 本身就是异步的,数据返回的数据,不是前端代码能控制的。

因此,我们只能继续往前找。

现在我们看第二个地方:


image.png

我们点进去,看一眼:


image.png

这一看,我们就能看出来,这里也不是我们要找的地方。

因为这里都只是在处理单个的图片 ajax 加载问题,并不涉及到批量的逻辑。

当然这里为了节省篇幅,后面就不一个个往前分析了,直接定位到我们的目的地去:

image.png

当然,由于篇幅的原因,这里直接展示不了所有代码,我们接着往下拉,定位到,doRequest 方法。


image.png

看到里面有个配置 throttleByServer 默认被设置成了 true ,我们试着改成 false ,然后看看加载的效果

多viewer加载2.gif

我们看上面的图,能够发现,我们改了之后,加载效果明显比之前的更顺畅。

但是我们这里无法直接更改源码,并非说我们不能改。

而是因为,我们使采用别人开源的代码,我们很难搞明白当初为何这地方的代码要这么写,要是我们直接改了,有可能会影响到别的地方代码的正常运行。

那么我们该怎么办呢?

为了使我们的修改,影像最小化,我们可以像下面这样做:

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();
};
  1. 将之前原型链上的方法重命名
  2. 将源码这个方法拷贝出来
  3. throttleByServer 变量抽离出来进行控制

这样,我们就做到了按需重载了。

比如,当我们需要在页面加载多 viewer 的时候,我们久可以用 _requestImagery 方法,当我们不需要加载的时候,就换回 _requestImagery2

当然,会改源码,仅仅只是第一步。要想进阶,得需要弄懂整个代码,然后对其进行优化,写出更符合自己需求的代码出来。

上一篇下一篇

猜你喜欢

热点阅读