imageTile.js

var inherit = require('./inherit');
var tile = require('./tile');

/**
 * @typedef {geo.tile.spec} geo.imageTile.spec
 * @extends {geo.tile.spec}
 * @property {object} index The global position of the tile.
 * @property {number} index.x The x-coordinate (the column number).
 * @property {number} index.y The y-coordinate (the row number).
 * @property {number} index.level The zoom level.
 * @property {object} [size] The size of each tile.
 * @property {number} [size.x=256] Width in pixels.
 * @property {number} [size.y=256] Height in pixels.
 * @property {string} [crossDomain='anonymous'] Image CORS attribute.  This is
 *   used for the `crossorigin` property when loading images.
 */

/**
 * This class defines a tile that is part of a standard "image pyramid", such
 * as an open street map tile set.  Every tile is uniquely indexed by a row,
 * column, and zoom level.  The number of rows/columns at zoom level z is
 * `2^z`, the number of pixels per tile is configurable.
 *
 * By default, this class assumes that images are fetched from the url, but
 * subclasses may define additional rendering steps to produce the images
 * before passing them off to the handlers.
 *
 * @class
 * @alias geo.imageTile
 * @extends geo.tile
 * @param {geo.imageTile.spec} spec The tile specification.
 */
var imageTile = function (spec) {
  if (!(this instanceof imageTile)) {
    return new imageTile(spec);
  }

  var $ = require('jquery');

  spec.size = spec.size || {x: 256, y: 256};
  this._image = null;

  this._cors = (spec.crossDomain || spec.crossDomain === null) ? spec.crossDomain : 'anonymous';

  // Call superclass constructor
  tile.call(this, spec);

  /**
   * Read only accessor to the Image object used by the tile.  Note, this
   * method does not guarantee that the image data is available.  Use the
   * promise interface to add asynchronous handlers.
   *
   * @property {Image} image The image object used by the tile.
   * @name geo.imageTile#image
   */
  Object.defineProperty(this, 'image', {
    get: function () { return this._image; }
  });

  /**
   * Initiate the image request.
   *
   * @returns {this}
   */
  this.fetch = function () {
    var defer;
    if (!this._image) {
      this._image = new Image(this.right - this.left, this.bottom - this.top);
      // Only set the crossOrigin parameter if this is going across origins.
      if (this._cors && this._url.indexOf(':') >= 0 &&
          this._url.indexOf('/') === this._url.indexOf(':') + 1) {
        this._image.crossOrigin = this._cors;
      }
      defer = $.Deferred();
      this._image.onload = defer.resolve;
      this._image.onerror = defer.reject;
      this._image.src = this._url;

      // attach a promise interface to `this`
      defer.done(function () {
        this._fetched = true;
      }.bind(this)).promise(this);
    }
    return this;
  };

  /**
   * Set the opacity of the tile to 0 and gradually fade in over the given
   * number of milliseconds.  This will also resolve the embedded promise
   * interface.
   *
   * @param {number} duration The duration of the animation in ms.
   * @returns {this}
   */
  this.fadeIn = function (duration) {
    var promise = this.fetch(),
        defer = $.Deferred();
    $(this._image).css('display', 'none');
    promise.done(function () {
      $(this._image).fadeIn(duration, function () {
        defer.resolve();
      });
    }.bind(this));
    return defer.promise(this);
  };

  return this;
};

inherit(imageTile, tile);

module.exports = imageTile;