OpenLayers中切片计算与加载

我们来解析一下ol对于切片的计算与加载过程

创建图层

一切的出发点都是初始化地图:

const map = new Map({
  layers: [
    new TileLayer({
      source: new OSM()
    })
  ],
  target: 'map',
  view: new View({
    center: [0, 0],
    zoom: 2
  })
});

new TileLayer()时定义了渲染切片图片的方法

创建切片数据源

new OSM()的过程基本在各级父类中实现,创建实例并定义了很多方法,
TileImage中定义的主要方法:

  • createTile_() 创建单个切片
  • getTile() 获取单个切片
  • getTileInternal() 获取内存缓存单个切片

TileSource中定义的主要方法:

  • forEachLoadedTile() 遍历加载切片

图层渲染

在切片图层渲染的renderFrame()方法中,开始计算切片、请求、渲染

计算切片

CanvasTileLayerRenderer

  renderFrame(frameState, target) {
    ...
    // ---- 计算切片 ----
    // 根据范围和级别计算切片编号的范围,在TileGrid中实现
    const tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z);
    // 嵌套循环遍历将要加载的切片索引
    for (let x = tileRange.minX; x <= tileRange.maxX; ++x) {
      for (let y = tileRange.minY; y <= tileRange.maxY; ++y) {
        // ---- 加载切片 ----
        // 实际上尝试是从缓存中读取,若没有则定义切片加载完成事件,等待切片队列请求图片
        const tile = this.getTile(z, x, y, frameState); 
        if (this.isDrawableTile(tile)) {
          const uid = getUid(this);
          if (tile.getState() == TileState.LOADED) {
            // 如果切片加载完成则在canvas上绘制切片
            tilesToDrawByZ[z][tile.tileCoord.toString()] = tile;
            ...
          } 
        }  
        ...
    // 通过这个方法将所需切片加入切片请求队列中
    this.manageTilePyramid(frameState, tileSource, tileGrid, pixelRatio,
      projection, extent, z, tileLayer.getPreload());
      }
    }
  }


  manageTilePyramid( ... ) {
    const tileSourceKey = getUid(tileSource);
    if (!(tileSourceKey in frameState.wantedTiles)) {
      frameState.wantedTiles[tileSourceKey] = {};
    }
    const wantedTiles = frameState.wantedTiles[tileSourceKey];
    //读取队列
    const tileQueue = frameState.tileQueue;
    const minZoom = tileGrid.getMinZoom();
    let tile, tileRange, tileResolution, x, y, z;
    // 遍历所有需要的切片
    for (z = minZoom; z <= currentZ; ++z) {
      tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z, tileRange);
      tileResolution = tileGrid.getResolution(z);
      for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
        for (y = tileRange.minY; y <= tileRange.maxY; ++y) {
          if (currentZ - z <= preload) {
            tile = tileSource.getTile(z, x, y, pixelRatio, projection);
            if (tile.getState() == TileState.IDLE) {
              wantedTiles[tile.getKey()] = true;
              if (!tileQueue.isKeyQueued(tile.getKey())) {
                // 插入队列
                tileQueue.enqueue([tile, tileSourceKey,
                  tileGrid.getTileCoordCenter(tile.tileCoord), tileResolution]);
              }
            }
            if (opt_tileCallback !== undefined) {
              opt_tileCallback(tile);
            }
          } else {
            tileSource.useTile(z, x, y, projection);
          }
        }
      }
    }
  }

TileGrid

  // 分别计算x方向和y方向上的范围

  getTileRangeForExtentAndZ(extent, z, opt_tileRange) {
    const tileCoord = tmpTileCoord;
    // 获取min点的切片索引
    this.getTileCoordForXYAndZ_(extent[0], extent[3], z, false, tileCoord);
    const minX = tileCoord[1];
    const minY = tileCoord[2];
    // 获取max点的切片索引
    this.getTileCoordForXYAndZ_(extent[2], extent[1], z, true, tileCoord);
    return createOrUpdateTileRange(minX, tileCoord[1], minY, tileCoord[2], opt_tileRange);
  }

  // 通过座标起点和当前位置的距离,除以分辨率得到像素距离,除以切片像素大小,得到索引
 
  getTileCoordForXYAndZ_(x, y, z, reverseIntersectionPolicy, opt_tileCoord) {
    // 座标起点
    const origin = this.getOrigin(z);
    // 当前级别下的像素分辨率
    const resolution = this.getResolution(z);
    // 切片图片的像素大小
    const tileSize = toSize(this.getTileSize(z), this.tmpSize_);
    // 根据需要扩0.5度
    const adjustX = reverseIntersectionPolicy ? 0.5 : 0;
    const adjustY = reverseIntersectionPolicy ? 0.5 : 0;
    // 当前位置距离起点像素距离
    const xFromOrigin = Math.floor((x - origin[0]) / resolution + adjustX);
    const yFromOrigin = Math.floor((origin[1] - y) / resolution + adjustY);
    //除以每张切片的像素大小就是像素的位置索引了
    let tileCoordX = xFromOrigin / tileSize[0];
    let tileCoordY = yFromOrigin / tileSize[1];

    // 切片索引取整
    if (reverseIntersectionPolicy) {
      tileCoordX = Math.ceil(tileCoordX) - 1;
      tileCoordY = Math.ceil(tileCoordY) - 1;
    } else {
      tileCoordX = Math.floor(tileCoordX);
      tileCoordY = Math.floor(tileCoordY);
    }
    // 返回索引
    return createOrUpdateTileCoord(z, tileCoordX, tileCoordY, opt_tileCoord);
  }

请求切片

请求切片的过程是在PluggableMap中用队列批量请求图片

  handlePostRender() {
const tileQueue = this.tileQueue_;
    if (!tileQueue.isEmpty()) {
      let maxTotalLoading = this.maxTilesLoading_;
      let maxNewLoads = maxTotalLoading;
      if (frameState) {
        const hints = frameState.viewHints;
        if (hints[ViewHint.ANIMATING] || hints[ViewHint.INTERACTING]) {
          const lowOnFrameBudget = !IMAGE_DECODE && Date.now() - frameState.time > 8;
          maxTotalLoading = lowOnFrameBudget ? 0 : 8;
          maxNewLoads = lowOnFrameBudget ? 0 : 2;
        }
      }
      if (tileQueue.getTilesLoading() < maxTotalLoading) {
        tileQueue.reprioritize(); // FIXME only call if view has changed
        // 请求切片--
        tileQueue.loadMoreTiles(maxTotalLoading, maxNewLoads);
      }
    }

    if (frameState && this.hasListener(RenderEventType.RENDERCOMPLETE) && !frameState.animate &&
      !this.tileQueue_.getTilesLoading() && !this.getLoading()) {
      this.renderer_.dispatchRenderEvent(RenderEventType.RENDERCOMPLETE, frameState);
    }

    const postRenderFunctions = this.postRenderFunctions_;
    for (let i = 0, ii = postRenderFunctions.length; i < ii; ++i) {
      postRenderFunctions[i](this, frameState);
    }
    postRenderFunctions.length = 0;
  }

TileQueue

  loadMoreTiles(maxTotalLoading, maxNewLoads) {
    let newLoads = 0;
    let abortedTiles = false;
    let state, tile, tileKey;
    while (this.tilesLoading_ < maxTotalLoading && newLoads < maxNewLoads &&
           this.getCount() > 0) {
      tile = /** @type {import("./Tile.js").default} */ (this.dequeue()[0]);
      tileKey = tile.getKey();
      state = tile.getState();
      if (state === TileState.ABORT) {
        abortedTiles = true;
      } else if (state === TileState.IDLE && !(tileKey in this.tilesLoadingKeys_)) {
        this.tilesLoadingKeys_[tileKey] = true;
        ++this.tilesLoading_;
        ++newLoads;
        // ---- 终于到load了 ---
        // 在 ImageTile 中实现
        tile.load();
      }
    }
    if (newLoads === 0 && abortedTiles) {
      // Do not stop the render loop when all wanted tiles were aborted due to
      // a small, saturated tile cache.
      this.tileChangeCallback_();
    }
  }

ImageTile

  load() {
    if (this.state == TileState.ERROR) {
      this.state = TileState.IDLE;
      this.image_ = new Image();
      if (this.crossOrigin_ !== null) {
        this.image_.crossOrigin = this.crossOrigin_;
      }
    }
    if (this.state == TileState.IDLE) {
      this.state = TileState.LOADING;
      this.changed();
      // 执行加载
      this.tileLoadFunction_(this, this.src_);
      // 绑定加载成功事件
      this.unlisten_ = listenImage(
        this.image_,
        this.handleImageLoad_.bind(this),
        this.handleImageError_.bind(this)
      );
    }
  }
  
  handleImageLoad_() {
    // 成功加载到图片了
    const image = /** @type {HTMLImageElement} */ (this.image_);
    if (image.naturalWidth && image.naturalHeight) {
      this.state = TileState.LOADED;
    } else {
      this.state = TileState.EMPTY;
    }
    // 停止监听
    this.unlistenImage_();
    // 派发成功事件,通知渲染
    this.changed();
  }

渲染切片

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章