Openlayers源码阅读(七):图层canvas渲染

上一篇中,ol.renderer.canvas.Map在renderFrame()中通过getLayerRenderer()初始化ol.renderer.canvas.Layer,并调用了ol.renderer.canvas.Layer的prepareFrame()、composeFrame方法。

导图

extends
extends
extends
extends
extends
ImageLayer
IntermediateCanvas
TileLayer
VectorTileLayer
Layer
VectorLayer

说明: 命名空间 ol.renderer.canvas

一、ol.renderer.canvas.Layer

ol.renderer.canvas.Layer 提供抽象类和抽象方法

/**
 * @constructor
 * @abstract
 * @extends {ol.renderer.Layer}
 * @param {ol.layer.Layer} layer Layer.
 */
ol.renderer.canvas.Layer = function(layer) {
    
    

  ol.renderer.Layer.call(this, layer);

  /**
   * @protected
   * @type {number}
   */
  this.renderedResolution;

  /**
   * @private
   * @type {ol.Transform}
   */
  this.transform_ = ol.transform.create();

};
ol.inherits(ol.renderer.canvas.Layer, ol.renderer.Layer);

/**
 * @abstract
 * @param {olx.FrameState} frameState Frame state.
 * @param {ol.LayerState} layerState Layer state.
 * @param {CanvasRenderingContext2D} context Context.
 */
ol.renderer.canvas.Layer.prototype.composeFrame = function(frameState, layerState, context) {
    
    };

/**
 * @abstract
 * @param {olx.FrameState} frameState Frame state.
 * @param {ol.LayerState} layerState Layer state.
 * @return {boolean} whether composeFrame should be called.
 */
ol.renderer.canvas.Layer.prototype.prepareFrame = function(frameState, layerState) {
    
    };

/**
 * @param {CanvasRenderingContext2D} context Context.
 * @param {olx.FrameState} frameState Frame state.
 * @param {ol.LayerState} layerState Layer state.
 * @param {ol.Transform=} opt_transform Transform.
 * @protected
 */
ol.renderer.canvas.Layer.prototype.postCompose = function(context, frameState, layerState, opt_transform) {
    
    
  this.dispatchComposeEvent_(ol.render.EventType.POSTCOMPOSE, context,
      frameState, opt_transform);
};


/**
 * @param {CanvasRenderingContext2D} context Context.
 * @param {olx.FrameState} frameState Frame state.
 * @param {ol.Transform=} opt_transform Transform.
 * @protected
 */
ol.renderer.canvas.Layer.prototype.preCompose = function(context, frameState, opt_transform) {
    
    
  this.dispatchComposeEvent_(ol.render.EventType.PRECOMPOSE, context,
      frameState, opt_transform);
};

/**
 * @param {ol.render.EventType} type Event type.
 * @param {CanvasRenderingContext2D} context Context.
 * @param {olx.FrameState} frameState Frame state.
 * @param {ol.Transform=} opt_transform Transform.
 * @private
 */
ol.renderer.canvas.Layer.prototype.dispatchComposeEvent_ = function(type, context, frameState, opt_transform) {
    
    
  var layer = this.getLayer();
  if (layer.hasListener(type)) {
    
    
    var width = frameState.size[0] * frameState.pixelRatio;
    var height = frameState.size[1] * frameState.pixelRatio;
    var rotation = frameState.viewState.rotation;
    ol.render.canvas.rotateAtOffset(context, -rotation, width / 2, height / 2);
    var transform = opt_transform !== undefined ?
      opt_transform : this.getTransform(frameState, 0);
    var render = new ol.render.canvas.Immediate(
        context, frameState.pixelRatio, frameState.extent, transform,
        frameState.viewState.rotation);
    var composeEvent = new ol.render.Event(type, render, frameState,
        context, null);
    layer.dispatchEvent(composeEvent);
    ol.render.canvas.rotateAtOffset(context, rotation, width / 2, height / 2);
  }
};

二、ol.renderer.canvas.IntermediateCanvas

实现了父类的composeFrame()的方法:

1、获取Image

2、对Image进行处理:范围裁剪、图像转换、透明度设置

3、context.drawImage()

ol.renderer.canvas.IntermediateCanvas.prototype.composeFrame = function(frameState, layerState, context) {
    
    

  this.preCompose(context, frameState);

  var image = this.getImage();
  if (image) {
    
    

    // clipped rendering if layer extent is set
    var extent = layerState.extent;
    var clipped = extent !== undefined &&
        !ol.extent.containsExtent(extent, frameState.extent) &&
        ol.extent.intersects(extent, frameState.extent);
    if (clipped) {
    
    
      this.clip(context, frameState, /** @type {ol.Extent} */ (extent));
    }

    var imageTransform = this.getImageTransform();
    // for performance reasons, context.save / context.restore is not used
    // to save and restore the transformation matrix and the opacity.
    // see http://jsperf.com/context-save-restore-versus-variable
    var alpha = context.globalAlpha;
    context.globalAlpha = layerState.opacity;

    // for performance reasons, context.setTransform is only used
    // when the view is rotated. see http://jsperf.com/canvas-transform
    var dx = imageTransform[4];
    var dy = imageTransform[5];
    var dw = image.width * imageTransform[0];
    var dh = image.height * imageTransform[3];
    context.drawImage(image, 0, 0, +image.width, +image.height,
        Math.round(dx), Math.round(dy), Math.round(dw), Math.round(dh));
    context.globalAlpha = alpha;

    if (clipped) {
    
    
      context.restore();
    }
  }

  this.postCompose(context, frameState, layerState);
};

三、ol.renderer.canvas.ImageLayer

实现了父类的prepareFrame()的方法

ol.renderer.canvas.ImageLayer.prototype.prepareFrame = function(frameState, layerState) {
    
    

  var pixelRatio = frameState.pixelRatio;
  var size = frameState.size;
  var viewState = frameState.viewState;
  var viewCenter = viewState.center;
  var viewResolution = viewState.resolution;

  var image;
  var imageLayer = /** @type {ol.layer.Image} */ (this.getLayer());
  var imageSource = imageLayer.getSource();

  var hints = frameState.viewHints;

  var renderedExtent = frameState.extent;
  if (layerState.extent !== undefined) {
    
    
    renderedExtent = ol.extent.getIntersection(
        renderedExtent, layerState.extent);
  }

  if (!hints[ol.ViewHint.ANIMATING] && !hints[ol.ViewHint.INTERACTING] &&
      !ol.extent.isEmpty(renderedExtent)) {
    
    
    var projection = viewState.projection;
    if (!ol.ENABLE_RASTER_REPROJECTION) {
    
    
      var sourceProjection = imageSource.getProjection();
      if (sourceProjection) {
    
    
        projection = sourceProjection;
      }
    }
    var vectorRenderer = this.vectorRenderer_;
    if (vectorRenderer) {
    
    
      var context = vectorRenderer.context;
      var imageFrameState = /** @type {olx.FrameState} */ (ol.obj.assign({
    
    }, frameState, {
    
    
        size: [
          ol.extent.getWidth(renderedExtent) / viewResolution,
          ol.extent.getHeight(renderedExtent) / viewResolution
        ],
        viewState: /** @type {olx.ViewState} */ (ol.obj.assign({
    
    }, frameState.viewState, {
    
    
          rotation: 0
        }))
      }));
      var skippedFeatures = Object.keys(imageFrameState.skippedFeatureUids).sort();
      if (vectorRenderer.prepareFrame(imageFrameState, layerState) &&
          (vectorRenderer.replayGroupChanged ||
          !ol.array.equals(skippedFeatures, this.skippedFeatures_))) {
    
    
        context.canvas.width = imageFrameState.size[0] * pixelRatio;
        context.canvas.height = imageFrameState.size[1] * pixelRatio;
        vectorRenderer.composeFrame(imageFrameState, layerState, context);
        this.image_ = new ol.ImageCanvas(renderedExtent, viewResolution, pixelRatio, context.canvas);
        this.skippedFeatures_ = skippedFeatures;
      }
    } else {
    
    
      image = imageSource.getImage(
          renderedExtent, viewResolution, pixelRatio, projection);
      if (image) {
    
    
        var loaded = this.loadImage(image);
        if (loaded) {
    
    
          this.image_ = image;
        }
      }
    }
  }

  if (this.image_) {
    
    
    image = this.image_;
    var imageExtent = image.getExtent();
    var imageResolution = image.getResolution();
    var imagePixelRatio = image.getPixelRatio();
    var scale = pixelRatio * imageResolution /
        (viewResolution * imagePixelRatio);
    var transform = ol.transform.compose(this.imageTransform_,
        pixelRatio * size[0] / 2, pixelRatio * size[1] / 2,
        scale, scale,
        0,
        imagePixelRatio * (imageExtent[0] - viewCenter[0]) / imageResolution,
        imagePixelRatio * (viewCenter[1] - imageExtent[3]) / imageResolution);
    ol.transform.compose(this.coordinateToCanvasPixelTransform,
        pixelRatio * size[0] / 2 - transform[4], pixelRatio * size[1] / 2 - transform[5],
        pixelRatio / viewResolution, -pixelRatio / viewResolution,
        0,
        -viewCenter[0], -viewCenter[1]);

    this.updateLogos(frameState, imageSource);
    this.renderedResolution = imageResolution * pixelRatio / imagePixelRatio;
  }

  return !!this.image_;
};

四、ol.renderer.canvas.TileLayer

实现了父类的prepareFrame()的方法

ol.renderer.canvas.TileLayer.prototype.prepareFrame = function(frameState, layerState) {
    
    

  var pixelRatio = frameState.pixelRatio;
  var size = frameState.size;
  var viewState = frameState.viewState;
  var projection = viewState.projection;
  var viewResolution = viewState.resolution;
  var viewCenter = viewState.center;

  var tileLayer = this.getLayer();
  var tileSource = /** @type {ol.source.Tile} */ (tileLayer.getSource());
  var sourceRevision = tileSource.getRevision();
  var tileGrid = tileSource.getTileGridForProjection(projection);
  var z = tileGrid.getZForResolution(viewResolution, this.zDirection);
  var tileResolution = tileGrid.getResolution(z);
  var oversampling = Math.round(viewResolution / tileResolution) || 1;
  var extent = frameState.extent;

  if (layerState.extent !== undefined) {
    
    
    extent = ol.extent.getIntersection(extent, layerState.extent);
  }
  if (ol.extent.isEmpty(extent)) {
    
    
    // Return false to prevent the rendering of the layer.
    return false;
  }

  var tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z);
  var imageExtent = tileGrid.getTileRangeExtent(z, tileRange);

  var tilePixelRatio = tileSource.getTilePixelRatio(pixelRatio);

  /**
   * @type {Object.<number, Object.<string, ol.Tile>>}
   */
  var tilesToDrawByZ = {
    
    };
  tilesToDrawByZ[z] = {
    
    };

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

  var tmpExtent = this.tmpExtent;
  var tmpTileRange = this.tmpTileRange_;
  var newTiles = false;
  var tile, x, y;
  for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
    
    
    for (y = tileRange.minY; y <= tileRange.maxY; ++y) {
    
    
      tile = tileSource.getTile(z, x, y, pixelRatio, projection);
      if (tile.getState() == ol.TileState.ERROR) {
    
    
        if (!tileLayer.getUseInterimTilesOnError()) {
    
    
          // When useInterimTilesOnError is false, we consider the error tile as loaded.
          tile.setState(ol.TileState.LOADED);
        } else if (tileLayer.getPreload() > 0) {
    
    
          // Preloaded tiles for lower resolutions might have finished loading.
          newTiles = true;
        }
      }
      if (!this.isDrawableTile_(tile)) {
    
    
        tile = tile.getInterimTile();
      }
      if (this.isDrawableTile_(tile)) {
    
    
        var uid = ol.getUid(this);
        if (tile.getState() == ol.TileState.LOADED) {
    
    
          tilesToDrawByZ[z][tile.tileCoord.toString()] = tile;
          var inTransition = tile.inTransition(uid);
          if (!newTiles && (inTransition || this.renderedTiles.indexOf(tile) === -1)) {
    
    
            newTiles = true;
          }
        }
        if (tile.getAlpha(uid, frameState.time) === 1) {
    
    
          // don't look for alt tiles if alpha is 1
          continue;
        }
      }

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

    }
  }

  var renderedResolution = tileResolution * pixelRatio / tilePixelRatio * oversampling;
  var hints = frameState.viewHints;
  var animatingOrInteracting = hints[ol.ViewHint.ANIMATING] || hints[ol.ViewHint.INTERACTING];
  if (!(this.renderedResolution && Date.now() - frameState.time > 16 && animatingOrInteracting) && (
    newTiles ||
        !(this.renderedExtent_ && ol.extent.containsExtent(this.renderedExtent_, extent)) ||
        this.renderedRevision != sourceRevision ||
        oversampling != this.oversampling_ ||
        !animatingOrInteracting && renderedResolution != this.renderedResolution
  )) {
    
    

    var context = this.context;
    if (context) {
    
    
      var tilePixelSize = tileSource.getTilePixelSize(z, pixelRatio, projection);
      var width = Math.round(tileRange.getWidth() * tilePixelSize[0] / oversampling);
      var height = Math.round(tileRange.getHeight() * tilePixelSize[1] / oversampling);
      var canvas = context.canvas;
      if (canvas.width != width || canvas.height != height) {
    
    
        this.oversampling_ = oversampling;
        canvas.width = width;
        canvas.height = height;
      } else {
    
    
        if (this.renderedExtent_ && !ol.extent.equals(imageExtent, this.renderedExtent_)) {
    
    
          context.clearRect(0, 0, width, height);
        }
        oversampling = this.oversampling_;
      }
    }

    this.renderedTiles.length = 0;
    /** @type {Array.<number>} */
    var zs = Object.keys(tilesToDrawByZ).map(Number);
    zs.sort(function(a, b) {
    
    
      if (a === z) {
    
    
        return 1;
      } else if (b === z) {
    
    
        return -1;
      } else {
    
    
        return a > b ? 1 : a < b ? -1 : 0;
      }
    });
    var currentResolution, currentScale, currentTilePixelSize, currentZ, i, ii;
    var tileExtent, tileGutter, tilesToDraw, w, h;
    for (i = 0, ii = zs.length; i < ii; ++i) {
    
    
      currentZ = zs[i];
      currentTilePixelSize = tileSource.getTilePixelSize(currentZ, pixelRatio, projection);
      currentResolution = tileGrid.getResolution(currentZ);
      currentScale = currentResolution / tileResolution;
      tileGutter = tilePixelRatio * tileSource.getGutter(projection);
      tilesToDraw = tilesToDrawByZ[currentZ];
      for (var tileCoordKey in tilesToDraw) {
    
    
        tile = tilesToDraw[tileCoordKey];
        tileExtent = tileGrid.getTileCoordExtent(tile.getTileCoord(), tmpExtent);
        x = (tileExtent[0] - imageExtent[0]) / tileResolution * tilePixelRatio / oversampling;
        y = (imageExtent[3] - tileExtent[3]) / tileResolution * tilePixelRatio / oversampling;
        w = currentTilePixelSize[0] * currentScale / oversampling;
        h = currentTilePixelSize[1] * currentScale / oversampling;
        this.drawTileImage(tile, frameState, layerState, x, y, w, h, tileGutter, z === currentZ);
        this.renderedTiles.push(tile);
      }
    }

    this.renderedRevision = sourceRevision;
    this.renderedResolution = tileResolution * pixelRatio / tilePixelRatio * oversampling;
    this.renderedExtent_ = imageExtent;
  }

  var scale = this.renderedResolution / viewResolution;
  var transform = ol.transform.compose(this.imageTransform_,
      pixelRatio * size[0] / 2, pixelRatio * size[1] / 2,
      scale, scale,
      0,
      (this.renderedExtent_[0] - viewCenter[0]) / this.renderedResolution * pixelRatio,
      (viewCenter[1] - this.renderedExtent_[3]) / this.renderedResolution * pixelRatio);
  ol.transform.compose(this.coordinateToCanvasPixelTransform,
      pixelRatio * size[0] / 2 - transform[4], pixelRatio * size[1] / 2 - transform[5],
      pixelRatio / viewResolution, -pixelRatio / viewResolution,
      0,
      -viewCenter[0], -viewCenter[1]);


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

  return this.renderedTiles.length > 0;
};



drawTileImage() 根据x,y绘制tile

/**
 * @param {ol.Tile} tile Tile.
 * @param {olx.FrameState} frameState Frame state.
 * @param {ol.LayerState} layerState Layer state.
 * @param {number} x Left of the tile.
 * @param {number} y Top of the tile.
 * @param {number} w Width of the tile.
 * @param {number} h Height of the tile.
 * @param {number} gutter Tile gutter.
 * @param {boolean} transition Apply an alpha transition.
 */
ol.renderer.canvas.TileLayer.prototype.drawTileImage = function(tile, frameState, layerState, x, y, w, h, gutter, transition) {
    
    
  var image = tile.getImage(this.getLayer());
  if (!image) {
    
    
    return;
  }
  var uid = ol.getUid(this);
  var alpha = transition ? tile.getAlpha(uid, frameState.time) : 1;
  if (alpha === 1 && !this.getLayer().getSource().getOpaque(frameState.viewState.projection)) {
    
    
    this.context.clearRect(x, y, w, h);
  }
  var alphaChanged = alpha !== this.context.globalAlpha;
  if (alphaChanged) {
    
    
    this.context.save();
    this.context.globalAlpha = alpha;
  }
  this.context.drawImage(image, gutter, gutter,
      image.width - 2 * gutter, image.height - 2 * gutter, x, y, w, h);

  if (alphaChanged) {
    
    
    this.context.restore();
  }
  if (alpha !== 1) {
    
    
    frameState.animate = true;
  } else if (transition) {
    
    
    tile.endTransition(uid);
  }
};

五、 ol.renderer.canvas.VectorLayer

实现父类的composeFrame()、prepareFrame()方法

ol.renderer.canvas.VectorLayer.prototype.composeFrame = function(frameState, layerState, context) {
    
    

  var extent = frameState.extent;
  var pixelRatio = frameState.pixelRatio;
  var skippedFeatureUids = layerState.managed ?
    frameState.skippedFeatureUids : {
    
    };
  var viewState = frameState.viewState;
  var projection = viewState.projection;
  var rotation = viewState.rotation;
  var projectionExtent = projection.getExtent();
  var vectorSource = /** @type {ol.source.Vector} */ (this.getLayer().getSource());

  var transform = this.getTransform(frameState, 0);

  this.preCompose(context, frameState, transform);

  // clipped rendering if layer extent is set
  var clipExtent = layerState.extent;
  var clipped = clipExtent !== undefined;
  if (clipped) {
    
    
    this.clip(context, frameState,  /** @type {ol.Extent} */ (clipExtent));
  }
  var replayGroup = this.replayGroup_;
  if (replayGroup && !replayGroup.isEmpty()) {
    
    
    if (this.declutterTree_) {
    
    
      this.declutterTree_.clear();
    }
    var layer = /** @type {ol.layer.Vector} */ (this.getLayer());
    var drawOffsetX = 0;
    var drawOffsetY = 0;
    var replayContext;
    var transparentLayer = layerState.opacity !== 1;
    var hasRenderListeners = layer.hasListener(ol.render.EventType.RENDER);
    if (transparentLayer || hasRenderListeners) {
    
    
      var drawWidth = context.canvas.width;
      var drawHeight = context.canvas.height;
      if (rotation) {
    
    
        var drawSize = Math.round(Math.sqrt(drawWidth * drawWidth + drawHeight * drawHeight));
        drawOffsetX = (drawSize - drawWidth) / 2;
        drawOffsetY = (drawSize - drawHeight) / 2;
        drawWidth = drawHeight = drawSize;
      }
      // resize and clear
      this.context.canvas.width = drawWidth;
      this.context.canvas.height = drawHeight;
      replayContext = this.context;
    } else {
    
    
      replayContext = context;
    }

    var alpha = replayContext.globalAlpha;
    if (!transparentLayer) {
    
    
      // for performance reasons, context.save / context.restore is not used
      // to save and restore the transformation matrix and the opacity.
      // see http://jsperf.com/context-save-restore-versus-variable
      replayContext.globalAlpha = layerState.opacity;
    }

    if (replayContext != context) {
    
    
      replayContext.translate(drawOffsetX, drawOffsetY);
    }

    var width = frameState.size[0] * pixelRatio;
    var height = frameState.size[1] * pixelRatio;
    ol.render.canvas.rotateAtOffset(replayContext, -rotation,
        width / 2, height / 2);
    replayGroup.replay(replayContext, transform, rotation, skippedFeatureUids);
    if (vectorSource.getWrapX() && projection.canWrapX() &&
        !ol.extent.containsExtent(projectionExtent, extent)) {
    
    
      var startX = extent[0];
      var worldWidth = ol.extent.getWidth(projectionExtent);
      var world = 0;
      var offsetX;
      while (startX < projectionExtent[0]) {
    
    
        --world;
        offsetX = worldWidth * world;
        transform = this.getTransform(frameState, offsetX);
        replayGroup.replay(replayContext, transform, rotation, skippedFeatureUids);
        startX += worldWidth;
      }
      world = 0;
      startX = extent[2];
      while (startX > projectionExtent[2]) {
    
    
        ++world;
        offsetX = worldWidth * world;
        transform = this.getTransform(frameState, offsetX);
        replayGroup.replay(replayContext, transform, rotation, skippedFeatureUids);
        startX -= worldWidth;
      }
      // restore original transform for render and compose events
      transform = this.getTransform(frameState, 0);
    }
    ol.render.canvas.rotateAtOffset(replayContext, rotation,
        width / 2, height / 2);

    if (replayContext != context) {
    
    
      if (hasRenderListeners) {
    
    
        this.dispatchRenderEvent(replayContext, frameState, transform);
      }
      if (transparentLayer) {
    
    
        var mainContextAlpha = context.globalAlpha;
        context.globalAlpha = layerState.opacity;
        context.drawImage(replayContext.canvas, -drawOffsetX, -drawOffsetY);
        context.globalAlpha = mainContextAlpha;
      } else {
    
    
        context.drawImage(replayContext.canvas, -drawOffsetX, -drawOffsetY);
      }
      replayContext.translate(-drawOffsetX, -drawOffsetY);
    }

    if (!transparentLayer) {
    
    
      replayContext.globalAlpha = alpha;
    }
  }

  if (clipped) {
    
    
    context.restore();
  }
  this.postCompose(context, frameState, layerState, transform);

};

ol.renderer.canvas.VectorLayer.prototype.prepareFrame = function(frameState, layerState) {
    
    

  var vectorLayer = /** @type {ol.layer.Vector} */ (this.getLayer());
  var vectorSource = vectorLayer.getSource();

  this.updateLogos(frameState, vectorSource);

  var animating = frameState.viewHints[ol.ViewHint.ANIMATING];
  var interacting = frameState.viewHints[ol.ViewHint.INTERACTING];
  var updateWhileAnimating = vectorLayer.getUpdateWhileAnimating();
  var updateWhileInteracting = vectorLayer.getUpdateWhileInteracting();

  if (!this.dirty_ && (!updateWhileAnimating && animating) ||
      (!updateWhileInteracting && interacting)) {
    
    
    return true;
  }

  var frameStateExtent = frameState.extent;
  var viewState = frameState.viewState;
  var projection = viewState.projection;
  var resolution = viewState.resolution;
  var pixelRatio = frameState.pixelRatio;
  var vectorLayerRevision = vectorLayer.getRevision();
  var vectorLayerRenderBuffer = vectorLayer.getRenderBuffer();
  var vectorLayerRenderOrder = vectorLayer.getRenderOrder();

  if (vectorLayerRenderOrder === undefined) {
    
    
    vectorLayerRenderOrder = ol.renderer.vector.defaultOrder;
  }

  var extent = ol.extent.buffer(frameStateExtent,
      vectorLayerRenderBuffer * resolution);
  var projectionExtent = viewState.projection.getExtent();

  if (vectorSource.getWrapX() && viewState.projection.canWrapX() &&
      !ol.extent.containsExtent(projectionExtent, frameState.extent)) {
    
    
    // For the replay group, we need an extent that intersects the real world
    // (-180° to +180°). To support geometries in a coordinate range from -540°
    // to +540°, we add at least 1 world width on each side of the projection
    // extent. If the viewport is wider than the world, we need to add half of
    // the viewport width to make sure we cover the whole viewport.
    var worldWidth = ol.extent.getWidth(projectionExtent);
    var buffer = Math.max(ol.extent.getWidth(extent) / 2, worldWidth);
    extent[0] = projectionExtent[0] - buffer;
    extent[2] = projectionExtent[2] + buffer;
  }

  if (!this.dirty_ &&
      this.renderedResolution_ == resolution &&
      this.renderedRevision_ == vectorLayerRevision &&
      this.renderedRenderOrder_ == vectorLayerRenderOrder &&
      ol.extent.containsExtent(this.renderedExtent_, extent)) {
    
    
    this.replayGroupChanged = false;
    return true;
  }

  this.replayGroup_ = null;

  this.dirty_ = false;

  var replayGroup = new ol.render.canvas.ReplayGroup(
      ol.renderer.vector.getTolerance(resolution, pixelRatio), extent, resolution,
      pixelRatio, vectorSource.getOverlaps(), this.declutterTree_, vectorLayer.getRenderBuffer());
  vectorSource.loadFeatures(extent, resolution, projection);
  /**
   * @param {ol.Feature} feature Feature.
   * @this {ol.renderer.canvas.VectorLayer}
   */
  var renderFeature = function(feature) {
    
    
    var styles;
    var styleFunction = feature.getStyleFunction();
    if (styleFunction) {
    
    
      styles = styleFunction.call(feature, resolution);
    } else {
    
    
      styleFunction = vectorLayer.getStyleFunction();
      if (styleFunction) {
    
    
        styles = styleFunction(feature, resolution);
      }
    }
    if (styles) {
    
    
      var dirty = this.renderFeature(
          feature, resolution, pixelRatio, styles, replayGroup);
      this.dirty_ = this.dirty_ || dirty;
    }
  }.bind(this);
  if (vectorLayerRenderOrder) {
    
    
    /** @type {Array.<ol.Feature>} */
    var features = [];
    vectorSource.forEachFeatureInExtent(extent,
        /**
         * @param {ol.Feature} feature Feature.
         */
        function(feature) {
    
    
          features.push(feature);
        }, this);
    features.sort(vectorLayerRenderOrder);
    for (var i = 0, ii = features.length; i < ii; ++i) {
    
    
      renderFeature(features[i]);
    }
  } else {
    
    
    vectorSource.forEachFeatureInExtent(extent, renderFeature, this);
  }
  replayGroup.finish();

  this.renderedResolution_ = resolution;
  this.renderedRevision_ = vectorLayerRevision;
  this.renderedRenderOrder_ = vectorLayerRenderOrder;
  this.renderedExtent_ = extent;
  this.replayGroup_ = replayGroup;

  this.replayGroupChanged = true;
  return true;
};

renderFeature() 渲染要素

/**
 * @param {ol.Feature} feature Feature.
 * @param {number} resolution Resolution.
 * @param {number} pixelRatio Pixel ratio.
 * @param {(ol.style.Style|Array.<ol.style.Style>)} styles The style or array of
 *     styles.
 * @param {ol.render.canvas.ReplayGroup} replayGroup Replay group.
 * @return {boolean} `true` if an image is loading.
 */
ol.renderer.canvas.VectorLayer.prototype.renderFeature = function(feature, resolution, pixelRatio, styles, replayGroup) {
    
    
  if (!styles) {
    
    
    return false;
  }
  var loading = false;
  if (Array.isArray(styles)) {
    
    
    for (var i = 0, ii = styles.length; i < ii; ++i) {
    
    
      loading = ol.renderer.vector.renderFeature(
          replayGroup, feature, styles[i],
          ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio),
          this.handleStyleImageChange_, this) || loading;
    }
  } else {
    
    
    loading = ol.renderer.vector.renderFeature(
        replayGroup, feature, styles,
        ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio),
        this.handleStyleImageChange_, this);
  }
  return loading;
};

六、 ol.renderer.canvas.VectorTileLayer

VectorTileLayer的渲染方式是TileLayer和VectorLayer两种方式的结合。

/**
 * @inheritDoc
 */
ol.renderer.canvas.VectorTileLayer.prototype.prepareFrame = function(frameState, layerState) {
    
    
  var layer = this.getLayer();
  var layerRevision = layer.getRevision();
  if (this.renderedLayerRevision_ != layerRevision) {
    
    
    this.renderedTiles.length = 0;
    var renderMode = layer.getRenderMode();
    if (!this.context && renderMode != ol.layer.VectorTileRenderType.VECTOR) {
    
    
      this.context = ol.dom.createCanvasContext2D();
    }
    if (this.context && renderMode == ol.layer.VectorTileRenderType.VECTOR) {
    
    
      this.context = null;
    }
  }
  this.renderedLayerRevision_ = layerRevision;
  return ol.renderer.canvas.TileLayer.prototype.prepareFrame.apply(this, arguments);
};


/**
 * @param {ol.VectorImageTile} tile Tile.
 * @param {olx.FrameState} frameState Frame state.
 * @private
 */
ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup_ = function(
    tile, frameState) {
    
    
  var layer = this.getLayer();
  var pixelRatio = frameState.pixelRatio;
  var projection = frameState.viewState.projection;
  var revision = layer.getRevision();
  var renderOrder = /** @type {ol.RenderOrderFunction} */
      (layer.getRenderOrder()) || null;

  var replayState = tile.getReplayState(layer);
  if (!replayState.dirty && replayState.renderedRevision == revision &&
      replayState.renderedRenderOrder == renderOrder) {
    
    
    return;
  }

  var source = /** @type {ol.source.VectorTile} */ (layer.getSource());
  var sourceTileGrid = source.getTileGrid();
  var tileGrid = source.getTileGridForProjection(projection);
  var resolution = tileGrid.getResolution(tile.tileCoord[0]);
  var tileExtent = tileGrid.getTileCoordExtent(tile.wrappedTileCoord);

  var zIndexKeys = {
    
    };
  for (var t = 0, tt = tile.tileKeys.length; t < tt; ++t) {
    
    
    var sourceTile = tile.getTile(tile.tileKeys[t]);
    if (sourceTile.getState() == ol.TileState.ERROR) {
    
    
      continue;
    }

    var sourceTileCoord = sourceTile.tileCoord;
    var sourceTileExtent = sourceTileGrid.getTileCoordExtent(sourceTileCoord);
    var sharedExtent = ol.extent.getIntersection(tileExtent, sourceTileExtent);
    var bufferedExtent = ol.extent.equals(sourceTileExtent, sharedExtent) ? null :
      ol.extent.buffer(sharedExtent, layer.getRenderBuffer() * resolution);
    var tileProjection = sourceTile.getProjection();
    var reproject = false;
    if (!ol.proj.equivalent(projection, tileProjection)) {
    
    
      reproject = true;
      sourceTile.setProjection(projection);
    }
    replayState.dirty = false;
    var replayGroup = new ol.render.canvas.ReplayGroup(0, sharedExtent, resolution,
        pixelRatio, source.getOverlaps(), this.declutterTree_, layer.getRenderBuffer());
    var squaredTolerance = ol.renderer.vector.getSquaredTolerance(
        resolution, pixelRatio);

    /**
     * @param {ol.Feature|ol.render.Feature} feature Feature.
     * @this {ol.renderer.canvas.VectorTileLayer}
     */
    var renderFeature = function(feature) {
    
    
      var styles;
      var styleFunction = feature.getStyleFunction();
      if (styleFunction) {
    
    
        styles = styleFunction.call(/** @type {ol.Feature} */ (feature), resolution);
      } else {
    
    
        styleFunction = layer.getStyleFunction();
        if (styleFunction) {
    
    
          styles = styleFunction(feature, resolution);
        }
      }
      if (styles) {
    
    
        var dirty = this.renderFeature(feature, squaredTolerance, styles,
            replayGroup);
        this.dirty_ = this.dirty_ || dirty;
        replayState.dirty = replayState.dirty || dirty;
      }
    };

    var features = sourceTile.getFeatures();
    if (renderOrder && renderOrder !== replayState.renderedRenderOrder) {
    
    
      features.sort(renderOrder);
    }
    var feature;
    for (var i = 0, ii = features.length; i < ii; ++i) {
    
    
      feature = features[i];
      if (reproject) {
    
    
        if (tileProjection.getUnits() == ol.proj.Units.TILE_PIXELS) {
    
    
          // projected tile extent
          tileProjection.setWorldExtent(sourceTileExtent);
          // tile extent in tile pixel space
          tileProjection.setExtent(sourceTile.getExtent());
        }
        feature.getGeometry().transform(tileProjection, projection);
      }
      if (!bufferedExtent || ol.extent.intersects(bufferedExtent, feature.getGeometry().getExtent())) {
    
    
        renderFeature.call(this, feature);
      }
    }
    replayGroup.finish();
    for (var r in replayGroup.getReplays()) {
    
    
      zIndexKeys[r] = true;
    }
    sourceTile.setReplayGroup(layer, tile.tileCoord.toString(), replayGroup);
  }
  replayState.renderedRevision = revision;
  replayState.renderedRenderOrder = renderOrder;
};


/**
 * @inheritDoc
 */
ol.renderer.canvas.VectorTileLayer.prototype.drawTileImage = function(
    tile, frameState, layerState, x, y, w, h, gutter, transition) {
    
    
  var vectorImageTile = /** @type {ol.VectorImageTile} */ (tile);
  this.createReplayGroup_(vectorImageTile, frameState);
  if (this.context) {
    
    
    this.renderTileImage_(vectorImageTile, frameState, layerState);
    ol.renderer.canvas.TileLayer.prototype.drawTileImage.apply(this, arguments);
  }
};

/**
 * @inheritDoc
 */
ol.renderer.canvas.VectorTileLayer.prototype.postCompose = function(context, frameState, layerState) {
    
    
  var layer = this.getLayer();
  var declutterReplays = layer.getDeclutter() ? {
    
    } : null;
  var source = /** @type {ol.source.VectorTile} */ (layer.getSource());
  var renderMode = layer.getRenderMode();
  var replayTypes = ol.renderer.canvas.VectorTileLayer.VECTOR_REPLAYS[renderMode];
  var pixelRatio = frameState.pixelRatio;
  var rotation = frameState.viewState.rotation;
  var size = frameState.size;
  var offsetX, offsetY;
  if (rotation) {
    
    
    offsetX = Math.round(pixelRatio * size[0] / 2);
    offsetY = Math.round(pixelRatio * size[1] / 2);
    ol.render.canvas.rotateAtOffset(context, -rotation, offsetX, offsetY);
  }
  if (declutterReplays) {
    
    
    this.declutterTree_.clear();
  }
  var tiles = this.renderedTiles;
  var tileGrid = source.getTileGridForProjection(frameState.viewState.projection);
  var clips = [];
  var zs = [];
  for (var i = tiles.length - 1; i >= 0; --i) {
    
    
    var tile = /** @type {ol.VectorImageTile} */ (tiles[i]);
    if (tile.getState() == ol.TileState.ABORT) {
    
    
      continue;
    }
    var tileCoord = tile.tileCoord;
    var worldOffset = tileGrid.getTileCoordExtent(tileCoord)[0] -
        tileGrid.getTileCoordExtent(tile.wrappedTileCoord)[0];
    var transform = undefined;
    for (var t = 0, tt = tile.tileKeys.length; t < tt; ++t) {
    
    
      var sourceTile = tile.getTile(tile.tileKeys[t]);
      if (sourceTile.getState() == ol.TileState.ERROR) {
    
    
        continue;
      }
      var replayGroup = sourceTile.getReplayGroup(layer, tileCoord.toString());
      if (renderMode != ol.layer.VectorTileRenderType.VECTOR && !replayGroup.hasReplays(replayTypes)) {
    
    
        continue;
      }
      if (!transform) {
    
    
        transform = this.getTransform(frameState, worldOffset);
      }
      var currentZ = sourceTile.tileCoord[0];
      var currentClip = replayGroup.getClipCoords(transform);
      context.save();
      context.globalAlpha = layerState.opacity;
      // Create a clip mask for regions in this low resolution tile that are
      // already filled by a higher resolution tile
      for (var j = 0, jj = clips.length; j < jj; ++j) {
    
    
        var clip = clips[j];
        if (currentZ < zs[j]) {
    
    
          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 resolution 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();
        }
      }
      replayGroup.replay(context, transform, rotation, {
    
    }, replayTypes, declutterReplays);
      context.restore();
      clips.push(currentClip);
      zs.push(currentZ);
    }
  }
  if (declutterReplays) {
    
    
    ol.render.canvas.ReplayGroup.replayDeclutter(declutterReplays, context, rotation);
  }
  if (rotation) {
    
    
    ol.render.canvas.rotateAtOffset(context, rotation,
        /** @type {number} */ (offsetX), /** @type {number} */ (offsetY));
  }
  ol.renderer.canvas.TileLayer.prototype.postCompose.apply(this, arguments);
};


/**
 * @param {ol.Feature|ol.render.Feature} feature Feature.
 * @param {number} squaredTolerance Squared tolerance.
 * @param {(ol.style.Style|Array.<ol.style.Style>)} styles The style or array of
 *     styles.
 * @param {ol.render.canvas.ReplayGroup} replayGroup Replay group.
 * @return {boolean} `true` if an image is loading.
 */
ol.renderer.canvas.VectorTileLayer.prototype.renderFeature = function(feature, squaredTolerance, styles, replayGroup) {
    
    
  if (!styles) {
    
    
    return false;
  }
  var loading = false;
  if (Array.isArray(styles)) {
    
    
    for (var i = 0, ii = styles.length; i < ii; ++i) {
    
    
      loading = ol.renderer.vector.renderFeature(
          replayGroup, feature, styles[i], squaredTolerance,
          this.handleStyleImageChange_, this) || loading;
    }
  } else {
    
    
    loading = ol.renderer.vector.renderFeature(
        replayGroup, feature, styles, squaredTolerance,
        this.handleStyleImageChange_, this);
  }
  return loading;
};


/**
 * @param {ol.VectorImageTile} tile Tile.
 * @param {olx.FrameState} frameState Frame state.
 * @param {ol.LayerState} layerState Layer state.
 * @private
 */
ol.renderer.canvas.VectorTileLayer.prototype.renderTileImage_ = function(
    tile, frameState, layerState) {
    
    
  var layer = this.getLayer();
  var replayState = tile.getReplayState(layer);
  var revision = layer.getRevision();
  var replays = ol.renderer.canvas.VectorTileLayer.IMAGE_REPLAYS[layer.getRenderMode()];
  if (replays && replayState.renderedTileRevision !== revision) {
    
    
    replayState.renderedTileRevision = revision;
    var tileCoord = tile.wrappedTileCoord;
    var z = tileCoord[0];
    var pixelRatio = frameState.pixelRatio;
    var source = /** @type {ol.source.VectorTile} */ (layer.getSource());
    var tileGrid = source.getTileGridForProjection(frameState.viewState.projection);
    var resolution = tileGrid.getResolution(z);
    var context = tile.getContext(layer);
    var size = source.getTilePixelSize(z, pixelRatio, frameState.viewState.projection);
    context.canvas.width = size[0];
    context.canvas.height = size[1];
    var tileExtent = tileGrid.getTileCoordExtent(tileCoord);
    for (var i = 0, ii = tile.tileKeys.length; i < ii; ++i) {
    
    
      var sourceTile = tile.getTile(tile.tileKeys[i]);
      if (sourceTile.getState() == ol.TileState.ERROR) {
    
    
        continue;
      }
      var pixelScale = pixelRatio / resolution;
      var transform = ol.transform.reset(this.tmpTransform_);
      ol.transform.scale(transform, pixelScale, -pixelScale);
      ol.transform.translate(transform, -tileExtent[0], -tileExtent[3]);
      var replayGroup = sourceTile.getReplayGroup(layer, tile.tileCoord.toString());
      replayGroup.replay(context, transform, 0, {
    
    }, replays);
    }
  }
};

猜你喜欢

转载自blog.csdn.net/u013240519/article/details/105016566