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();
  }

渲染切片

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