Fork me on GitHub
Show:

File: ../src/maps/map.js

define([
  'aeris/util',
  'aeris/events',
  'aeris/errors/validationerror',
  'aeris/maps/extensions/mapextensionobject',
  'aeris/maps/strategy/map'
], function(_, Events, ValidationError, MapExtensionObject, MapStrategy) {
  /**
   * An Aeris {aeris.maps.Map} is the base object on which all other map objects live. Any {aeris.maps.MapObjectInterface} object can be added to a map using the `setMap` method:
   *
   * <code class="example">
   *   var map = new aeris.maps.Map('map-canvas-id');
   *   mapObject.setMap(map);  // adds the object to the map
   *   mapobject.setMap(null); // removes the object from the map
   * </code>
   *
   * {aeris.maps.markers.Marker} and {aeris.maps.layers.AerisRadar} are examples of {aeris.maps.MapObjectInterface} objects which can be set to a map.
   *
   * @publicApi
   * @class aeris.maps.Map
   * @extends aeris.maps.extensions.MapExtensionObject
   * @override
   *
   * @param {HTMLElement|string} el Map canvas element, by reference or id.
   *
   * @param {Object=} opt_attrs Attributes to set on the map on initialization
   * @param {aeris.maps.LatLon=} opt_attrs.center
   * @param {number=} opt_attrs.zoom
   *
   * @param {Object=} opt_options
   *
   * @constructor
   * @throws {aeris.errors.ValidationError} If no element is supplied.
   */
  var Map = function(el, opt_attrs, opt_options) {
    /**
     * @event click
     * @param {aeris.maps.LatLon} latLon
     */
    /**
     * @event dblclick
     * @param {aeris.maps.LatLon} latLon
     */
    /**
     * When base map tiles are loaded.
     * @event load
     */
    var attrs = _.extend({
      /**
       * @attribute center
       * @type {aeris.maps.LatLon} LatLon coordinate.
       */
      center: [45, -90],


      /**
       * LatLon bounds of the map viewport.
       *
       * @attribute bounds
       * @type {aeris.maps.Bounds} LatLons of SW and NE corners.
       * @default A rough box around the US
       */
      bounds: [
        [22.43, -135.52],
        [52.37, -55.016]
      ],

      /**
       * @attribute zoom
       * @type {number}
       */
      zoom: 4,

      /**
       * Whether to enable zooming using the
       * mouse scrollwheel.
       *
       * Attribute not currently supported by open layers.
       *
       * @attribute scrollZoom
       * @type {Boolean}
       */
      scrollZoom: true,


      /**
       * The base map layer.
       * Note that different mapping libraries have
       * different default base layers.
       *
       * @attribute baseLayer
       * @type {aeris.maps.layers.Layer}
       */
      baseLayer: null
    }, opt_attrs);

    var options = _.extend({
      strategy: MapStrategy,
      validate: true
    }, opt_options);

    /**
     * @property mapEl_
     * @private
     * @type {HTMLElement} Map element be also be a reference to a pre-existing map view
     */
    this.mapEl_ = this.normalizeElement_(el);

    this.validateElementExists_(this.mapEl_);


    Events.call(this);
    MapExtensionObject.call(this, attrs, options);
  };
  _.inherits(Map, MapExtensionObject);
  _.extend(Map.prototype, Events.prototype);


  Map.prototype.validate = function(attrs) {
    if (!_.isArray(attrs.center)) {
      return new ValidationError('center', attrs.center + ' is not a valid coordinate');
    }
    if (!_.isNumber(attrs.zoom)) {
      return new ValidationError('zoom', attrs.zoom + ' is not a valid zoom level');
    }
  };

  /**
   * @method setBounds
   * @param {aeris.maps.Bounds} bounds
   */
  Map.prototype.setBounds = function(bounds) {
    this.set('bounds', bounds, { validate: true });
  };

  /**
   * @method getBounds
   * @return {aeris.maps.Bounds}
   */
  Map.prototype.getBounds = function() {
    return this.get('bounds');
  };


  /**
   * @method getView
   * @return {Object} The view object creating by the mapping library.
   */
  Map.prototype.getView = function() {
    return this.strategy_.getView();
  };

  /**
   * @method setCenter
   * @param {aeris.maps.LatLon} center
   */
  Map.prototype.setCenter = function(center) {
    this.set('center', center, { validate: true });
  };

  /**
   * @method getCenter
   * @return {aeris.maps.LatLon}
   */
  Map.prototype.getCenter = function() {
    return this.get('center');
  };


  /**
   * @method setZoom
   * @param {number} zoom
   */
  Map.prototype.setZoom = function(zoom) {
    this.set('zoom', zoom, { validate: true });
  };

  /**
   * @method getZoom
   * @return {number}
   */
  Map.prototype.getZoom = function() {
    return this.get('zoom');
  };


  /**
   * @method setBaseLayer
   * @param {aeris.maps.layers.Layer} baseLayer
   */
  Map.prototype.setBaseLayer = function(baseLayer) {
    this.set('baseLayer', baseLayer, { validate: true });
  };

  /**
   * @method getBaseLayer
   * @return {aeris.maps.layers.Layer}
   */
  Map.prototype.getBaseLayer = function() {
    return this.get('baseLayer');
  };


  /**
   * Zoom and center the map in order to fit
   * the viewport to the specified bounds.
   *
   * This is currently only supported when using Google Maps.
   *
   * @throws {Error} If fitToBounds is not supported by mapping library.
   * @param {aeris.maps.Bounds} bounds
   */
  Map.prototype.fitToBounds = function(bounds) {
    if (!this.strategy_ || !this.strategy_.fitToBounds) {
      throw Error('Unable to fit to bounds: no fitToBounds strategy has been implemented');
    }

    this.strategy_.fitToBounds(bounds);
  };


  /**
   * @method normalizeElement_
   * @private
   * @param {string|HTMLElement} el Element, or element id.
   * @return {HTMLElement}
   */
  Map.prototype.normalizeElement_ = function(el) {
    return _.isString(el) ? document.getElementById(el) : el;
  };


  /**
   * @throws {aeris.errors.ValidationError}
   * @method validateElementExists_
   * @private
   */
  Map.prototype.validateElementExists_ = function(el) {
    if (!el) {
      throw new ValidationError('el', el + ' is not a valid map canvas element or id');
    }
  };


  /**
   * Note that this may return unexpected results
   * when the map was created with a view instance,
   * instead of an {HTMLElement}.
   *
   * @method getElement
   * @return {HTMLElement} The map canvas element
   */
  Map.prototype.getElement = function() {
    return this.mapEl_;
  };


  /**
   * To be used when the map canvas element
   * has changed dimensions.
   *
   * This will tell the underlying mapping strategy to
   * refresh appropriately (eg. load new tile images to account
   *  for a larger map).
   *
   * @method updateSize
   */
  Map.prototype.updateSize = function() {
    this.trigger('updateSize');
  };


  return _.expose(Map, 'aeris.maps.Map');
});