openlayers源码阅读笔记(四)—— 图层渲染器ol.renderer.canvas.TileLayer

openlayers源码阅读笔记(四)—— 图层渲染器 ol.renderer.canvas.TileLayer

renderFrame()
完整代码+注释
  /**
   * Render the layer.
   * @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
   * @param {HTMLElement} target Target that may be used to render content to.
   * @return {HTMLElement} The rendered element.
   */
  renderFrame(frameState, target) {
    const layerState = frameState.layerStatesArray[frameState.layerIndex];
    const viewState = frameState.viewState;
    const projection = viewState.projection;
    const viewResolution = viewState.resolution;
    const viewCenter = viewState.center;
    const rotation = viewState.rotation;
    const pixelRatio = frameState.pixelRatio;

    const tileLayer = this.getLayer();
    const tileSource = tileLayer.getSource();
    const sourceRevision = tileSource.getRevision();
    const tileGrid = tileSource.getTileGridForProjection(projection);
    const z = tileGrid.getZForResolution(viewResolution, tileSource.zDirection);
    const tileResolution = tileGrid.getResolution(z);

    let extent = frameState.extent;
    const layerExtent =
      layerState.extent && fromUserExtent(layerState.extent, projection);
    if (layerExtent) {
      extent = getIntersection(
        extent,
        fromUserExtent(layerState.extent, projection)
      );
    }

    const tilePixelRatio = tileSource.getTilePixelRatio(pixelRatio);

    // desired dimensions of the canvas in pixels
    let width = Math.round(frameState.size[0] * tilePixelRatio);
    let height = Math.round(frameState.size[1] * tilePixelRatio);

    if (rotation) {
      const size = Math.round(Math.sqrt(width * width + height * height));
      width = size;
      height = size;
    }

    const dx = (tileResolution * width) / 2 / tilePixelRatio;
    const dy = (tileResolution * height) / 2 / tilePixelRatio;
    const canvasExtent = [
      viewCenter[0] - dx,
      viewCenter[1] - dy,
      viewCenter[0] + dx,
      viewCenter[1] + dy,
    ];

    const tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z);

    /**
     * @type {Object<number, Object<string, import("../../Tile.js").default>>}
     */
    const tilesToDrawByZ = {};
    tilesToDrawByZ[z] = {};

    const findLoadedTiles = this.createLoadedTileFinder(
      tileSource,
      projection,
      tilesToDrawByZ
    );

    const tmpExtent = this.tmpExtent;
    const tmpTileRange = this.tmpTileRange_;
    this.newTiles_ = false;
    // 便利显示瓦片
    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) {
            tilesToDrawByZ[z][tile.tileCoord.toString()] = tile; // 将瓦片存入对象
            const inTransition = tile.inTransition(uid);
            if (
              !this.newTiles_ &&
              (inTransition || this.renderedTiles.indexOf(tile) === -1)
            ) {
              this.newTiles_ = true;
            }
          }
          if (tile.getAlpha(uid, frameState.time) === 1) {
            // don't look for alt tiles if alpha is 1
            continue;
          }
        }

        const childTileRange = tileGrid.getTileCoordChildTileRange(
          tile.tileCoord,
          tmpTileRange,
          tmpExtent
        );

        let covered = false;
        if (childTileRange) {
          covered = findLoadedTiles(z + 1, childTileRange);
        }
        if (!covered) {
          tileGrid.forEachTileCoordParentTileRange(
            tile.tileCoord,
            findLoadedTiles,
            tmpTileRange,
            tmpExtent
          );
        }
      }
    }

    const canvasScale = tileResolution / viewResolution; // 若瓦片分辨率与视图分辨率不同,则需要缩放瓦片

    // set forward and inverse pixel transforms
    composeTransform(
      this.pixelTransform,
      frameState.size[0] / 2,
      frameState.size[1] / 2,
      1 / tilePixelRatio,
      1 / tilePixelRatio,
      rotation,
      -width / 2,
      -height / 2
    );

    const canvasTransform = toTransformString(this.pixelTransform);

    this.useContainer(
      target,
      canvasTransform,
      layerState.opacity,
      this.getBackground(frameState)
    );
    const context = this.context;
    const canvas = context.canvas;

    makeInverse(this.inversePixelTransform, this.pixelTransform);

    // set scale transform for calculating tile positions on the canvas
    // 计算瓦片在canvas上位置的缩放变换矩阵?
    composeTransform(
      this.tempTransform,
      width / 2,
      height / 2,
      canvasScale,
      canvasScale,
      0,
      -width / 2,
      -height / 2
    );
    // width,height 瓦片宽高
    if (canvas.width != width || canvas.height != height) {
      canvas.width = width;
      canvas.height = height;
    } else if (!this.containerReused) {
      context.clearRect(0, 0, width, height);
    }

    if (layerExtent) {
      this.clipUnrotated(context, frameState, layerExtent);
    }

    if (!tileSource.getInterpolate()) {
      assign(context, IMAGE_SMOOTHING_DISABLED);
    }

    this.preRender(context, frameState);

    this.renderedTiles.length = 0;
    /** @type {Array<number>} */
    // zooms? 层级数组,[0,1, 2, 3,...]
    let zs = Object.keys(tilesToDrawByZ).map(Number);
    zs.sort(numberSafeCompareFunction);  // 层级由小到大

    let clips, clipZs, currentClip;
    if (
      layerState.opacity === 1 &&
      (!this.containerReused ||
        tileSource.getOpaque(frameState.viewState.projection))
    ) { // 图层完全不透明且容器不复用或者tileSource不透明
      zs = zs.reverse();
    } else {
      clips = [];
      clipZs = [];
    }
    // 便利所有层级
    for (let i = zs.length - 1; i >= 0; --i) {
      const currentZ = zs[i]; 
      const currentTilePixelSize = tileSource.getTilePixelSize(
        currentZ,
        pixelRatio,
        projection
      );
      const currentResolution = tileGrid.getResolution(currentZ);
      const currentScale = currentResolution / tileResolution;
      const dx = currentTilePixelSize[0] * currentScale * canvasScale; // 瓦片宽度像素尺寸
      const dy = currentTilePixelSize[1] * currentScale * canvasScale;
      const originTileCoord = tileGrid.getTileCoordForCoordAndZ(
        getTopLeft(canvasExtent),
        currentZ
      ); // 左上角瓦片瓦片坐标 [z, x, y]
      const originTileExtent = tileGrid.getTileCoordExtent(originTileCoord); // 左上角瓦片extent
      const origin = applyTransform(this.tempTransform, [
        (tilePixelRatio * (originTileExtent[0] - canvasExtent[0])) /
          tileResolution, // 地理单位 / 分辨率 = 像素
        (tilePixelRatio * (canvasExtent[3] - originTileExtent[3])) /
          tileResolution,
      ]); // 左上角瓦片左上角像素坐标经变换矩阵转换后的坐标
      const tileGutter =
        tilePixelRatio * tileSource.getGutterForProjection(projection);
      const tilesToDraw = tilesToDrawByZ[currentZ]; // 当前层级需要绘制的瓦片对象 {key:value} —— {zxy:Tile}
      // 便利当前层级所有瓦片
      for (const tileCoordKey in tilesToDraw) {
        const tile = /** @type {import("../../ImageTile.js").default} */ (
          tilesToDraw[tileCoordKey]
        );
        const tileCoord = tile.tileCoord;

        // Calculate integer positions and sizes so that tiles align
        const xIndex = originTileCoord[1] - tileCoord[1];   // 左上角瓦片与当前瓦片X坐标的差,即横向相差几块瓦片的宽度 的相反数
        const nextX = Math.round(origin[0] - (xIndex - 1) * dx); // 瓦片右上角x坐标 四舍五入
        const yIndex = originTileCoord[2] - tileCoord[2];
        const nextY = Math.round(origin[1] - (yIndex - 1) * dy);
        const x = Math.round(origin[0] - xIndex * dx);  // 当前瓦片左上角x坐标
        const y = Math.round(origin[1] - yIndex * dy);
        const w = nextX - x; // 瓦片宽度
        const h = nextY - y;
        const transition = z === currentZ; // 当前视图展示的zoom是否等于当前便利循环的zoom

        const inTransition =
          transition && tile.getAlpha(getUid(this), frameState.time) !== 1; // 若当前视图的zoom不等于当前便利的zoom且当前渲染的阿尔法值不为1,则瓦片处于过度中
        if (!inTransition) {
          if (clips) {
            // Clip mask for regions in this tile that already filled by a higher z tile
            context.save();
            currentClip = [x, y, x + w, y, x + w, y + h, x, y + h]; // 当前瓦片的[左,上,右,上, 右,下, 左,下]像素坐标
            // 便利clips,若视图显示的zoom不等于当前的zoom且当前的zoom小于clip对应的zoom,则裁剪出当前瓦片对应的矩形区域
            // 裁剪之后绘制图片只会显示裁剪区域的部分
            for (let i = 0, ii = clips.length; i < ii; ++i) {
              if (z !== currentZ && currentZ < clipZs[i]) {
                const clip = clips[i];
                context.beginPath();
                // counter-clockwise (outer ring) for current tile
                context.moveTo(currentClip[0], currentClip[1]); 
                context.lineTo(currentClip[2], currentClip[3]);
                context.lineTo(currentClip[4], currentClip[5]);
                context.lineTo(currentClip[6], currentClip[7]);
                // clockwise (inner ring) for higher z tile
                context.moveTo(clip[6], clip[7]);
                context.lineTo(clip[4], clip[5]);
                context.lineTo(clip[2], clip[3]);
                context.lineTo(clip[0], clip[1]);
                context.clip();
              }
            }
            clips.push(currentClip);
            clipZs.push(currentZ);
          } else {
            context.clearRect(x, y, w, h); // 清空瓦片对应的矩形部分
          }
        }
        this.drawTileImage(
          tile,
          frameState,
          x,
          y,
          w,
          h,
          tileGutter,
          transition
        );
        if (clips && !inTransition) {
          context.restore();
          this.renderedTiles.unshift(tile);
        } else {
          this.renderedTiles.push(tile);
        }
        this.updateUsedTiles(frameState.usedTiles, tileSource, tile);
      }
    }

    this.renderedRevision = sourceRevision;
    this.renderedResolution = tileResolution;
    this.extentChanged =
      !this.renderedExtent_ || !equals(this.renderedExtent_, canvasExtent);
    this.renderedExtent_ = canvasExtent;
    this.renderedPixelRatio = pixelRatio;
    this.renderedProjection = projection;

    this.manageTilePyramid(
      frameState,
      tileSource,
      tileGrid,
      pixelRatio,
      projection,
      extent,
      z,
      tileLayer.getPreload()
    );
    this.scheduleExpireCache(frameState, tileSource);

    this.postRender(context, frameState);

    if (layerState.extent) {
      context.restore();
    }

    if (canvasTransform !== canvas.style.transform) {
      canvas.style.transform = canvasTransform;
    }
    const opacity = cssOpacity(layerState.opacity);
    const container = this.container;
    if (opacity !== container.style.opacity) {
      container.style.opacity = opacity;
    }

    return this.container;
  }
```
  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
OpenLayers是一个开的JavaScript库,提供了丰富的地功能和交互性,包括地的展示、数据的加载与处理、视的控制等。ol.source.xyz是OpenLayers中用于加载XYZ瓦片层的数据。 XYZ瓦片是一种常见的地数据切片方式,其中X表示层缩放级别,Y表示竖直方向的切片索引,Z表示水平方向的切片索引。ol.source.xyz可以通过指定瓦片层的URL模板,从网络上加载XYZ瓦片数据,并在地上进行展示。 在使用ol.source.xyz时,需要提供一个URL模板,以告诉OpenLayers如何获取瓦片数据。URL模板是一个包含占位符的字符串,OpenLayers会根据地当前缩放级别、瓦片索引等参数替换占位符,从而构建出实际的瓦片层URL。 例如,可以使用"https://example.com/tiles/{z}/{x}/{y}.png"作为URL模板,其中"{z}"、"{x}"和"{y}"分别会被当前缩放级别、水平切片索引和竖直切片索引替换。这样OpenLayers就可以根据需要动态加载相应的瓦片数据。 除了URL模板,ol.source.xyz还可以通过属性进行配置,如最小缩放级别、最大缩放级别、瓦片大小等。这些属性可以根据具体需求进行设置,以便实现对瓦片层的更精细的控制。 总之,ol.source.xyz是OpenLayers中用于加载XYZ瓦片层的数据,提供了灵活可配置的方式用于加载和展示瓦片层数据。使用ol.source.xyz可以方便地在地上加载和呈现各种瓦片地数据,为地开发提供了丰富的选择。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值