Fork me on GitHub
Show:

File: ../src/maps/routes/routerenderer.js

define([
  'aeris/util',
  'aeris/config',
  'aeris/events'
], function(_, config, Events) {
  /**
   * @typedef {Object} polylineOptions
   * @property {string=} strokeColor
   *                     Stroke color (any valid CSS color).
   *                     Defaults to '#36648b' (Calypso Blue)
   * @property {number=} strokeOpacity
   *                     Stroke opacity (1 is fully opaque).
   *                     Defaults to 0.8.
   * @property {number=} strokeWeight
   *                     Stroke weight, in pixels.
   *                     Defaults to 3.
   */
  /**
   * @typedef {Object} waypointOptions
   * @property {string=} url
   *                     Path the the icon image file.
   * @property {number=} width
   *                     Width of the icon, in pixels.
   * @property {number=} height
   *                     Height of the icon, in pixels.
   * @property {Boolean=} clickable
   *                     Whether to allow click events on the icon.
   *                     Defaults to true.
   * @property {Boolean=} draggable
   *                     Whether to allow drag events on the icon.
   *                     Defaults to true.
   */

  /**
   * Create a Google Map extension used for rendering a Route.
   *
   * @constructor
   * @class aeris.maps.gmaps.route.RouteRenderer
   *
   * @param {Object=} opt_options
   *
   * @param {polylineOptions=} opt_options.path
   *                          Options for rendering a path's polyline.
   *
   * @param {polylineOptions=} opt_options.offPath
   *                          Options for rendering a path's polyline
   *                          When the path is not following directions.
   *                          Defaults to the same as a path following directions,
   *                          but with a strokeColor of '#dd0000' (Crimson Red).
   *
   * @param {waypointOptions=} opt_options.waypoint
   *                          Options for a rending a waypoint's icon.
   *
   * @param {waypointOptions=} opt_options.selectedWaypoint
   *                          Options for rendering a selected waypoint's icon.
   *                          Defaults to the same as a non-selected waypoint,
   *                          but with a different different color icon.
   *
   *
   * @param {aeris.maps.Map} opt_options.map
   *
   * @uses {aeris.Events}
   */
  var RouteRenderer = function(opt_options) {
    var options = _.defaults(opt_options || {}, {
      map: null
    });


    /**
     * @type {polylineOptions}
     * @protected
     * @property pathStyles_
     */
    this.pathStyles_ = {};


    /**
     * @type {polylineOptions}
     * @protected
     * @property offPathStyles_
     */
    this.offPathStyles_ = {};


    /**
     * @type {waypointOptions}
     * @protected
     * @property waypointStyles_
     */
    this.waypointStyles_ = {};


    /**
     * @type {waypointOptions}
     * @protected
     * @property selectedWaypointStyles_
     */
    this.selectedWaypointStyles_ = {};


    /**
     * @type {Array.<aeris.maps.gmaps.route.Waypoint>}
     * @private
     * @property renderedWaypoints_
     */
    this.renderedWaypoints_ = [];


    /**
     * The map on which to render the route.
     *
     * @type {?aeris.maps.Map}
     * @private
     * @property map_
     */
    this.map_;


    Events.call(this);

    this.setStyles(options);

    this.setMap(options.map);


    /**
     * @event marker:click
     * @param {aeris.maps.LatLon} latLon
     * @param {aeris.maps.gmaps.route.Waypoint} waypoint
     */
    /**
     * @event marker:dragend
     * @param {aeris.maps.LatLon} latLon
     * @param {aeris.maps.gmaps.route.Waypoint} waypoint
     */
    /**
     * @event path:click
     * @param {aeris.maps.LatLon} latLon
     * @param {aeris.maps.gmaps.route.Waypoint} waypoint
     *        The waypoint to which the path belongs
     *        (at the end of the path).
     */
  };
  _.extend(RouteRenderer.prototype, Events.prototype);


  RouteRenderer.prototype.setStyles = function(styles) {
    var defaultStyles = this.getDefaultStyleOptions_();
    styles = this.getNormalizedStyleOptions_(styles);

    this.waypointStyles_ = _.defaults({}, styles.waypoint, this.waypointStyles_, defaultStyles.waypoint);
    this.selectedWaypointStyles_ = _.defaults({}, styles.selectedWaypoint, this.selectedWaypointStyles_, defaultStyles.selectedWaypoint);
    this.pathStyles_ = _.defaults({}, styles.path, this.pathStyles_, defaultStyles.path);
    this.offPathStyles_ = _.defaults({}, styles.offPath, this.offPathStyles_, defaultStyles.offPath);

    this.eachWaypoint_(this.setStylesForWaypoint_);
  };


  /**
   * @param {Object} options
   * @return {Object}
   * @private
   * @method getNormalizedStyleOptions_
   */
  RouteRenderer.prototype.getNormalizedStyleOptions_ = function(options) {
    var optionsCopy = _.clone(options);

    // Make sure all option properties are defined.
    return _.defaults(optionsCopy, {
      waypoint: {},
      selectedWaypoint: {},
      path: {},
      offPath: {}
    });
  };


  /**
   * @return {Object}
   * @private
   * @method getDefaultStyleOptions_
   */
  RouteRenderer.prototype.getDefaultStyleOptions_ = function() {
    var defaults = {
      waypoint: {
        url: config.get('assetPath') + 'marker_grey.png',
        clickable: true,
        draggable: true,
        offsetX: 0,
        offsetY: 0
      },
      selectedWaypoint: {
        url: config.get('assetPath') + 'marker_yellow.png'
      },
      path: {
        strokeColor: '#36648b',
        strokeOpacity: 0.8,
        strokeWeight: 3
      },
      offPath: {
        strokeColor: '#d00'
      }
    };

    _.defaults(defaults.selectedWaypoint, defaults.waypoint);
    _.defaults(defaults.offPath, defaults.path);

    return _.clone(defaults);
  };


  /**
   * Sets the map on which to render routes,
   * and renders all existing routes on the map.
   *
   * @param {aeris.maps.Map} map
   * @method setMap
   */
  RouteRenderer.prototype.setMap = function(map) {
    this.map_ = map;

    this.eachWaypoint_(function(waypoint) {
      waypoint.setMap(this.map_);
    });
  };


  /**
   * @return {Boolean}
   * @method hasMap
   */
  RouteRenderer.prototype.hasMap = function() {
    return !!this.map_;
  };


  /**
   * Render all waypoints in a route.
   *
   * @param {aeris.maps.gmaps.route.Route} route
   * @method renderRoute
   */
  RouteRenderer.prototype.renderRoute = function(route) {
    route.each(function(waypoint) {
      this.renderWaypoint(waypoint);
    }, this);
  };


  /**
   * Render a new Waypoint on the map.
   *
   * @param {aeris.maps.gmaps.route.Waypoint} waypoint The Waypoint to be rendered.
   * @method renderWaypoint
   */
  RouteRenderer.prototype.renderWaypoint = function(waypoint) {
    this.drawWaypoint_(waypoint);

    if (!this.isRememberedWaypoint_(waypoint)) {
      this.delegateEventsForWaypoint_(waypoint);
      this.rememberWaypoint_(waypoint);
    }
  };


  /**
   * @param {aeris.maps.gmaps.route.Waypoint} waypoint
   * @private
   * @method drawWaypoint_
   */
  RouteRenderer.prototype.drawWaypoint_ = function(waypoint) {
    this.setStylesForWaypoint_(waypoint);

    waypoint.setMap(this.map_);
  };


  /**
   * @param {aeris.maps.gmaps.route.Waypoint} waypoint
   * @private
   * @method setStylesForWaypoint_
   */
  RouteRenderer.prototype.setStylesForWaypoint_ = function(waypoint) {
    var waypointStyles = _.extend({}, this.waypointStyles_, {
      selectedUrl: this.selectedWaypointStyles_.url,
      selectedOffsetX: this.selectedWaypointStyles_.offsetX,
      selectedOffsetY: this.selectedWaypointStyles_.offsetY,
      clickable: waypoint.isSelected() ? this.selectedWaypointStyles_.clickable : this.waypointStyles_.clickable,
      draggable: waypoint.isSelected() ? this.selectedWaypointStyles_.draggable : this.waypointStyles_.draggable
    });

    var pathStyles = waypoint.get('followDirections') ?
      this.pathStyles_ : this.offPathStyles_;

    waypoint.set(waypointStyles, { validate: true });
    waypoint.stylePath(pathStyles);
  };


  /**
   * @param {aeris.maps.gmaps.route.Waypoint} waypoint
   * @private
   * @method delegateEventsForWaypoint_
   */
  RouteRenderer.prototype.delegateEventsForWaypoint_ = function(waypoint) {
    this.proxyWaypointEvents_(waypoint);
    this.bindStylesToWaypointState_(waypoint);
  };


  /**
   * @param {aeris.maps.gmaps.route.Waypoint} waypoint
   * @private
   * @method bindStylesToWaypointState_
   */
  RouteRenderer.prototype.bindStylesToWaypointState_ = function(waypoint) {
    this.listenTo(waypoint, {
      'change:selected change:followDirections': function() {
        this.setStylesForWaypoint_(waypoint);
      }
    });
  };


  /**
   * @param {aeris.maps.gmaps.route.Waypoint} waypoint
   * @private
   * @method proxyWaypointEvents_
   */
  RouteRenderer.prototype.proxyWaypointEvents_ = function(waypoint) {
    this.listenTo(waypoint, {
      'click': function(latLon) {
        this.trigger('marker:click', latLon, waypoint);
      },
      'dragend': function(latLon) {
        this.trigger('marker:dragend', latLon, waypoint);
      },
      'path:click': function(latLon) {
        this.trigger('path:click', latLon, waypoint);
      }
    });
  };


  /**
   * @param {aeris.maps.gmaps.route.Waypoint} waypoint
   * @private
   * @method rememberWaypoint_
   */
  RouteRenderer.prototype.rememberWaypoint_ = function(waypoint) {
    this.renderedWaypoints_.push(waypoint);
  };


  /**
   * @param {aeris.maps.gmaps.route.Waypoint} waypoint
   * @private
   * @method forgetWaypoint_
   */
  RouteRenderer.prototype.forgetWaypoint_ = function(waypoint) {
    this.renderedWaypoints_ = _.without(this.renderedWaypoints_, waypoint);
  };


  /**
   * @param {aeris.maps.gmaps.route.Waypoint} waypoint
   * @return {Boolean}
   * @private
   * @method isRememberedWaypoint_
   */
  RouteRenderer.prototype.isRememberedWaypoint_ = function(waypoint) {
    return _.contains(this.renderedWaypoints_, waypoint);
  };


  /**
   * Erases a single route from the map
   *
   * @param {aeris.maps.gmaps.route.Route|number} route Route or Route's cid.
   * @method eraseRoute
   */
  RouteRenderer.prototype.eraseRoute = function(route) {
    route.each(function(waypoint) {
      this.eraseWaypoint(waypoint);
    }, this);
  };


  RouteRenderer.prototype.eraseWaypoint = function(waypoint) {
    waypoint.setMap(null);
    this.stopListening(waypoint);
    this.forgetWaypoint_(waypoint);
  };


  /**
   * Close and clean up the RouteRenderer instance.
   * @method destroy
   */
  RouteRenderer.prototype.destroy = function() {
    this.eachWaypoint_(this.eraseWaypoint);
  };


  /**
   * Iterate through waypoints in
   * this.routeViews_.
   *
   * @param {Function} callback
   *        Called with the rendered waypoint.
   * @method eachWaypoint_
   */
  RouteRenderer.prototype.eachWaypoint_ = function(callback) {
    _.each(this.renderedWaypoints_, function(waypoint) {
      callback.call(this, waypoint);
    }, this);
  };


  return _.expose(RouteRenderer, 'aeris.maps.gmaps.route.RouteRenderer');
});