Fork me on GitHub
Show:

File: ../src/maps/animations/helpers/animationlayerloader.js

define([
  'aeris/util',
  'aeris/events',
  'aeris/maps/animations/helpers/timelayersfactory',
  'aeris/promise'
], function(_, Events, TimeLayersFactory, Promise) {
  /**
   * Handles the loading of time-layers, cloned from a base layer.
   *
   * @class aeris.maps.animations.helpers.AnimationLayerLoader
   * @constructor
   *
   * @param {aeris.maps.layers.AerisTile} baseLayer
   * @param {aeris.maps.animations.options.AnimationOptions=} opt_options
   * @param {aeris.maps.animations.helpers.TimeLayersFactory=} opt_options.timeLayersFactory
   */
  var AnimationLayerLoader = function(baseLayer, opt_options) {
    var options = opt_options || {};

    /**
     * @type {aeris.maps.layers.AerisTile}
     * @private
     * @property baseLayer_
     */
    this.baseLayer_ = baseLayer;

    /**
     * @type {aeris.maps.animations.helpers.TimeLayersFactory}
     * @private
     * @property timeLayersFactory_
     */
    this.timeLayersFactory_ = options.timeLayersFactory || new TimeLayersFactory(baseLayer, null, options);


    /**
     * @type {Object.<number,AerisTile>}
     * @private
     * @property timeLayers_
     */
    this.timeLayers_ = {};

    /**
     * @event load:progress
     * @param {number} Progress (1.0 is complete).
     */
    /**
     * @event load:complete
     * @param {number} Progress (1.0 is complete).
     */
    /**
     * @event load:error
     * @param {Error} error
     */
    /**
     * @event load:times
     * @param {Array.<number>} times
     */

    Events.call(this);
  };
  _.extend(AnimationLayerLoader.prototype, Events.prototype);

  /**
   * @method setFrom
   * @param {number} from Timestamp.
   */
  AnimationLayerLoader.prototype.setFrom = function(from) {
    this.timeLayersFactory_.setFrom(from);
  };


  /**
   * @method setTo
   * @param {number} to Timestamp.
   */
  AnimationLayerLoader.prototype.setTo = function(to) {
    this.timeLayersFactory_.setTo(to);
  };


  /**
   * Creates layers for all available times,
   * cloned from the base layer.
   *
   * Resolves with {Object.<number,aeris.maps.layers.AerisTile>}
   * --> a map of timestamps to layers.
   *
   * @return {aeris.Promise}
   * @method load
   */
  AnimationLayerLoader.prototype.load = function() {
    var promiseToLoadLayers = new Promise();
    var resolveOnLoadComplete = function() {
      this.once('load:complete', function() {
        promiseToLoadLayers.resolve(this.timeLayers_);
      });
    };
    var triggerLoadTimes = function(times) {
      this.trigger('load:times', times, this.timeLayers_);
    };

    this.baseLayer_.loadTileTimes().
      done(this.addLayersForTimes_, this).
      done(triggerLoadTimes, this).
      done(resolveOnLoadComplete, this).
      fail(promiseToLoadLayers.reject, promiseToLoadLayers);

    return promiseToLoadLayers.
      fail(this.trigger.bind(this, 'load:error'));
  };


  /**
   * @param {Array.<number>} times
   * @return {Object.<number, aeris.maps.layers.AerisTile>}
   * @private
   * @method addLayersForTimes_
   */
  AnimationLayerLoader.prototype.addLayersForTimes_ = function(times) {
    var currentTimes = this.getTimesFromLayers_(this.timeLayers_);
    var allTimes = _.uniq(currentTimes.concat(times));

    this.timeLayersFactory_.setTimes(allTimes);
    this.timeLayers_ = this.timeLayersFactory_.createTimeLayers();

    this.resetLayerLoadEvents_(this.timeLayers_);
  };


  /**
   * For a hash of { times -> layers }, return the times.
   *
   * @method getTimesFromLayers_
   * @param {Object.<number, aeris.maps.layers.AerisTile>} timeLayers
   * @private
   * @return {Array.<number>}
   */
  AnimationLayerLoader.prototype.getTimesFromLayers_ = function(timeLayers) {
    var toInt = function(time) {
      return parseInt(time);
    };

    return Object.keys(timeLayers).map(toInt);
  };


  /**
   * Set-up layer 'load:*' events for the specified time layers,
   * taking care not to set duplicate event listeners.
   *
   * @method resetLayerLoadEvents_
   * @private
   */
  AnimationLayerLoader.prototype.resetLayerLoadEvents_ = function(timeLayers) {
    this.unbindLayerLoadEvents_(timeLayers);
    this.bindLayerLoadEvents_(timeLayers);
  };


  /**
   * @method bindLayerLoadEvents_
   * @param {Object.<number,aeris.maps.layers.AerisTile>} timeLayers
   */
  AnimationLayerLoader.prototype.bindLayerLoadEvents_ = function(timeLayers) {
    var triggerLoadResetOnce = _.debounce(this.triggerLoadReset_.bind(this), 15);

    var bindLayerEvents = function(layer) {
      this.listenTo(layer, {
        'load': this.triggerLoadProgress_,
        'load:reset': triggerLoadResetOnce
      });
    };

    _.each(timeLayers, bindLayerEvents, this);
  };


  /**
   * Unbind 'load:*' events for the specified time layers.
   *
   * @method unbindLayerLoadEvents_
   * @private
   * @param {object.<number, aeris.maps.layers.AerisTile>} timeLayers
   */
  AnimationLayerLoader.prototype.unbindLayerLoadEvents_ = function(timeLayers) {
    _.each(timeLayers, this.stopListening, this);
  };


  /**
   * @method triggerLoadProgress_
   * @private
   */
  AnimationLayerLoader.prototype.triggerLoadProgress_ = function() {
    var progress = this.getLoadProgress();

    if (progress === 1) {
      this.trigger('load:complete', progress);
    }

    this.trigger('load:progress', progress);
  };


  /**
   * @method triggerLoadReset_
   * @private
   */
  AnimationLayerLoader.prototype.triggerLoadReset_ = function() {
    this.trigger('load:reset', this.getLoadProgress());
  };


  /**
   * @return {number} 1.0 is complete.
   * @method getLoadProgress
   */
  AnimationLayerLoader.prototype.getLoadProgress = function() {
    var totalCount = _.keys(this.timeLayers_).length;
    var loadedCount = 0;

    if (!totalCount) {
      return 0;
    }

    _.each(this.timeLayers_, function(layer) {
      if (layer.isLoaded()) {
        loadedCount++;
      }
    }, 0);


    return Math.min(loadedCount / totalCount, 1);
  };


  /**
   * @method destroy
   */
  AnimationLayerLoader.prototype.destroy = function() {
    this.stopListening();
    this.timeLayersFactory_.destroy();

    delete this.timeLayers_;
  };


  return AnimationLayerLoader;
});