# Source: src/ol/source/imagevectorsource.js

    goog.provide('ol.source.ImageVector');

    goog.require('goog.asserts');
    goog.require('goog.events');
    goog.require('goog.events.EventType');
    goog.require('goog.vec.Mat4');
    goog.require('ol.dom');
    goog.require('ol.extent');
    goog.require('ol.feature');
    goog.require('ol.render.canvas.ReplayGroup');
    goog.require('ol.renderer.vector');
    goog.require('ol.source.ImageCanvas');
    goog.require('ol.source.Vector');
    goog.require('ol.vec.Mat4');

    /**
     * @classdesc
     * An image source whose images are canvas elements into which vector features
     * read from a vector source (`ol.source.Vector`) are drawn. An
     * `ol.source.ImageVector` object is to be used as the `source` of an image
     * layer (`ol.layer.Image`). Image layers are rotated, scaled, and translated,
     * as opposed to being re-rendered, during animations and interactions. So, like
     * any other image layer, an image layer configured with an
     * `ol.source.ImageVector` will exhibit this behaviour. This is in contrast to a
     * vector layer, where vector features are re-drawn during animations and
     * interactions.
     *
     * @constructor
     * @extends {ol.source.ImageCanvas}
     * @param {olx.source.ImageVectorOptions} options Options.
     * @todo api
     */
    ol.source.ImageVector = function(options) {

      /**
       * @private
       * @type {ol.source.Vector}
       */
      this.source_ = options.source;

      /**
       * @private
       * @type {!ol.feature.StyleFunction}
       */
      this.styleFunction_ = goog.isDef(options.style) ?
          ol.feature.createStyleFunction(options.style) :
          ol.feature.defaultStyleFunction;

      /**
       * @private
       * @type {!goog.vec.Mat4.Number}
       */
      this.transform_ = goog.vec.Mat4.createNumber();

      /**
       * @private
       * @type {CanvasRenderingContext2D}
       */
      this.canvasContext_ = ol.dom.createCanvasContext2D();

      /**
       * @private
       * @type {ol.Size}
       */
      this.canvasSize_ = [0, 0];

      /**
       * @private
       * @type {ol.render.canvas.ReplayGroup}
       */
      this.replayGroup_ = null;

      goog.base(this, {
        attributions: options.attributions,
        canvasFunction: goog.bind(this.canvasFunctionInternal_, this),
        extent: options.extent,
        logo: options.logo,
        projection: options.projection,
        ratio: options.ratio,
        resolutions: options.resolutions,
        state: this.source_.getState()
      });

      goog.events.listen(this.source_, goog.events.EventType.CHANGE,
          this.handleSourceChange_, undefined, this);

    };
    goog.inherits(ol.source.ImageVector, ol.source.ImageCanvas);

    /**
     * @param {ol.Extent} extent Extent.
     * @param {number} resolution Resolution.
     * @param {number} pixelRatio Pixel ratio.
     * @param {ol.Size} size Size.
     * @param {ol.proj.Projection} projection Projection;
     * @return {HTMLCanvasElement} Canvas element.
     * @private
     */
    ol.source.ImageVector.prototype.canvasFunctionInternal_ =
        function(extent, resolution, pixelRatio, size, projection) {

      var tolerance = resolution / (2 * pixelRatio);
      var replayGroup = new ol.render.canvas.ReplayGroup(tolerance, extent,
          resolution);

      var loading = false;
      this.source_.forEachFeatureInExtentAtResolution(extent, resolution,
          /**
           * @param {ol.Feature} feature Feature.
           */
          function(feature) {
            loading = loading ||
                this.renderFeature_(feature, resolution, pixelRatio, replayGroup);
          }, this);
      replayGroup.finish();

      if (loading) {
        return null;
      }

      if (this.canvasSize_[0] != size[0] || this.canvasSize_[1] != size[1]) {
        this.canvasContext_.canvas.width = size[0];
        this.canvasContext_.canvas.height = size[1];
        this.canvasSize_[0] = size[0];
        this.canvasSize_[1] = size[1];
      } else {
        this.canvasContext_.clearRect(0, 0, size[0], size[1]);
      }

      var transform = this.getTransform_(ol.extent.getCenter(extent),
          resolution, pixelRatio, size);
      replayGroup.replay(this.canvasContext_, extent, pixelRatio, transform, 0,
          {});

      this.replayGroup_ = replayGroup;

      return this.canvasContext_.canvas;
    };

    /**
     * @inheritDoc
     */
    ol.source.ImageVector.prototype.forEachFeatureAtPixel = function(
        extent, resolution, rotation, coordinate, skippedFeatureUids, callback) {
      if (goog.isNull(this.replayGroup_)) {
        return undefined;
      } else {
        return this.replayGroup_.forEachGeometryAtPixel(
            extent, resolution, 0, coordinate, skippedFeatureUids,
            /**
             * @param {ol.geom.Geometry} geometry Geometry.
             * @param {Object} data Data.
             * @return {?} Callback result.
             */
            function(geometry, data) {
              var feature = /** @type {ol.Feature} */ (data);
              return callback(feature);
            });
      }
    };

    /**
     * @return {ol.source.Vector} Source.
     * @todo api
     */
    ol.source.ImageVector.prototype.getSource = function() {
      return this.source_;
    };

    /**
     * @param {ol.Coordinate} center Center.
     * @param {number} resolution Resolution.
     * @param {number} pixelRatio Pixel ratio.
     * @param {ol.Size} size Size.
     * @return {!goog.vec.Mat4.Number} Transform.
     * @private
     */
    ol.source.ImageVector.prototype.getTransform_ =
        function(center, resolution, pixelRatio, size) {
      return ol.vec.Mat4.makeTransform2D(this.transform_,
          size[0] / 2, size[1] / 2,
          pixelRatio / resolution, -pixelRatio / resolution,
          0,
          -center[0], -center[1]);
    };

    /**
     * Handle changes in image style state.
     * @param {goog.events.Event} event Image style change event.
     * @private
     */
    ol.source.ImageVector.prototype.handleImageChange_ =
        function(event) {
      this.dispatchChangeEvent();
    };

    /**
     * @private
     */
    ol.source.ImageVector.prototype.handleSourceChange_ = function() {
      // setState will trigger a CHANGE event, so we always rely
      // change events by calling setState.
      this.setState(this.source_.getState());
    };

    /**
     * @param {ol.Feature} feature Feature.
     * @param {number} resolution Resolution.
     * @param {number} pixelRatio Pixel ratio.
     * @param {ol.render.canvas.ReplayGroup} replayGroup Replay group.
     * @return {boolean} `true` if an image is loading.
     * @private
     */
    ol.source.ImageVector.prototype.renderFeature_ =
        function(feature, resolution, pixelRatio, replayGroup) {
      var styles = this.styleFunction_(feature, resolution);
      if (!goog.isDefAndNotNull(styles)) {
        return false;
      }
      // simplify to a tolerance of half a device pixel
      var squaredTolerance =
          resolution * resolution / (4 * pixelRatio * pixelRatio);
      var i, ii, loading = false;
      for (i = 0, ii = styles.length; i < ii; ++i) {
        loading = ol.renderer.vector.renderFeature(
            replayGroup, feature, styles[i], squaredTolerance, feature,
            this.handleImageChange_, this) || loading;
      }
      return loading;
    };