More trip planner testing with colors
[busui.git] / labs / openlayers / lib / OpenLayers / Strategy / BBOX.js
blob:a/labs/openlayers/lib/OpenLayers/Strategy/BBOX.js -> blob:b/labs/openlayers/lib/OpenLayers/Strategy/BBOX.js
  /* Copyright (c) 2006-2010 by OpenLayers Contributors (see authors.txt for
  * full list of contributors). Published under the Clear BSD license.
  * See http://svn.openlayers.org/trunk/openlayers/license.txt for the
  * full text of the license. */
   
  /**
  * @requires OpenLayers/Strategy.js
  * @requires OpenLayers/Filter/Spatial.js
  */
   
  /**
  * Class: OpenLayers.Strategy.BBOX
  * A simple strategy that reads new features when the viewport invalidates
  * some bounds.
  *
  * Inherits from:
  * - <OpenLayers.Strategy>
  */
  OpenLayers.Strategy.BBOX = OpenLayers.Class(OpenLayers.Strategy, {
   
  /**
  * Property: bounds
  * {<OpenLayers.Bounds>} The current data bounds (in the same projection
  * as the layer - not always the same projection as the map).
  */
  bounds: null,
   
  /**
  * Property: resolution
  * {Float} The current data resolution.
  */
  resolution: null,
   
  /**
  * APIProperty: ratio
  * {Float} The ratio of the data bounds to the viewport bounds (in each
  * dimension). Default is 2.
  */
  ratio: 2,
   
  /**
  * Property: resFactor
  * {Float} Optional factor used to determine when previously requested
  * features are invalid. If set, the resFactor will be compared to the
  * resolution of the previous request to the current map resolution.
  * If resFactor > (old / new) and 1/resFactor < (old / new). If you
  * set a resFactor of 1, data will be requested every time the
  * resolution changes. If you set a resFactor of 3, data will be
  * requested if the old resolution is 3 times the new, or if the new is
  * 3 times the old. If the old bounds do not contain the new bounds
  * new data will always be requested (with or without considering
  * resFactor).
  */
  resFactor: null,
   
  /**
  * Property: response
  * {<OpenLayers.Protocol.Response>} The protocol response object returned
  * by the layer protocol.
  */
  response: null,
   
  /**
  * Constructor: OpenLayers.Strategy.BBOX
  * Create a new BBOX strategy.
  *
  * Parameters:
  * options - {Object} Optional object whose properties will be set on the
  * instance.
  */
  initialize: function(options) {
  OpenLayers.Strategy.prototype.initialize.apply(this, [options]);
  },
   
  /**
  * Method: activate
  * Set up strategy with regard to reading new batches of remote data.
  *
  * Returns:
  * {Boolean} The strategy was successfully activated.
  */
  activate: function() {
  var activated = OpenLayers.Strategy.prototype.activate.call(this);
  if(activated) {
  this.layer.events.on({
  "moveend": this.update,
  scope: this
  });
  this.layer.events.on({
  "refresh": this.update,
  scope: this
  });
  }
  return activated;
  },
   
  /**
  * Method: deactivate
  * Tear down strategy with regard to reading new batches of remote data.
  *
  * Returns:
  * {Boolean} The strategy was successfully deactivated.
  */
  deactivate: function() {
  var deactivated = OpenLayers.Strategy.prototype.deactivate.call(this);
  if(deactivated) {
  this.layer.events.un({
  "moveend": this.update,
  scope: this
  });
  this.layer.events.un({
  "refresh": this.update,
  scope: this
  });
  }
  return deactivated;
  },
   
  /**
  * Method: update
  * Callback function called on "moveend" or "refresh" layer events.
  *
  * Parameters:
  * options - {Object} An object with a property named "force", this
  * property references a boolean value indicating if new data
  * must be incondtionally read.
  */
  update: function(options) {
  var mapBounds = this.getMapBounds();
  if ((options && options.force) || this.invalidBounds(mapBounds)) {
  this.calculateBounds(mapBounds);
  this.resolution = this.layer.map.getResolution();
  this.triggerRead();
  }
  },
   
  /**
  * Method: getMapBounds
  * Get the map bounds expressed in the same projection as this layer.
  *
  * Returns:
  * {<OpenLayers.Bounds>} Map bounds in the projection of the layer.
  */
  getMapBounds: function() {
  var bounds = this.layer.map.getExtent();
  if(!this.layer.projection.equals(this.layer.map.getProjectionObject())) {
  bounds = bounds.clone().transform(
  this.layer.map.getProjectionObject(), this.layer.projection
  );
  }
  return bounds;
  },
   
  /**
  * Method: invalidBounds
  * Determine whether the previously requested set of features is invalid.
  * This occurs when the new map bounds do not contain the previously
  * requested bounds. In addition, if <resFactor> is set, it will be
  * considered.
  *
  * Parameters:
  * mapBounds - {<OpenLayers.Bounds>} the current map extent, will be
  * retrieved from the map object if not provided
  *
  * Returns:
  * {Boolean}
  */
  invalidBounds: function(mapBounds) {
  if(!mapBounds) {
  mapBounds = this.getMapBounds();
  }
  var invalid = !this.bounds || !this.bounds.containsBounds(mapBounds);
  if(!invalid && this.resFactor) {
  var ratio = this.resolution / this.layer.map.getResolution();
  invalid = (ratio >= this.resFactor || ratio <= (1 / this.resFactor));
  }
  return invalid;
  },
   
  /**
  * Method: calculateBounds
  *
  * Parameters:
  * mapBounds - {<OpenLayers.Bounds>} the current map extent, will be
  * retrieved from the map object if not provided
  */
  calculateBounds: function(mapBounds) {
  if(!mapBounds) {
  mapBounds = this.getMapBounds();
  }
  var center = mapBounds.getCenterLonLat();
  var dataWidth = mapBounds.getWidth() * this.ratio;
  var dataHeight = mapBounds.getHeight() * this.ratio;
  this.bounds = new OpenLayers.Bounds(
  center.lon - (dataWidth / 2),
  center.lat - (dataHeight / 2),
  center.lon + (dataWidth / 2),
  center.lat + (dataHeight / 2)
  );
  },
   
  /**
  * Method: triggerRead
  *
  * Returns:
  * {<OpenLayers.Protocol.Response>} The protocol response object
  * returned by the layer protocol.
  */
  triggerRead: function() {
  if (this.response) {
  this.layer.protocol.abort(this.response);
  this.layer.events.triggerEvent("loadend");
  }
  this.layer.events.triggerEvent("loadstart");
  this.response = this.layer.protocol.read({
  filter: this.createFilter(),
  callback: this.merge,
  scope: this
  });
  },
   
  /**
  * Method: createFilter
  * Creates a spatial BBOX filter. If the layer that this strategy belongs
  * to has a filter property, this filter will be combined with the BBOX
  * filter.
  *
  * Returns
  * {<OpenLayers.Filter>} The filter object.
  */
  createFilter: function() {
  var filter = new OpenLayers.Filter.Spatial({
  type: OpenLayers.Filter.Spatial.BBOX,
  value: this.bounds,
  projection: this.layer.projection
  });
  if (this.layer.filter) {
  filter = new OpenLayers.Filter.Logical({
  type: OpenLayers.Filter.Logical.AND,
  filters: [this.layer.filter, filter]
  });
  }
  return filter;
  },
   
  /**
  * Method: merge
  * Given a list of features, determine which ones to add to the layer.
  * If the layer projection differs from the map projection, features
  * will be transformed from the layer projection to the map projection.
  *
  * Parameters:
  * resp - {<OpenLayers.Protocol.Response>} The response object passed
  * by the protocol.
  */
  merge: function(resp) {
  this.layer.destroyFeatures();
  var features = resp.features;
  if(features && features.length > 0) {
  var remote = this.layer.projection;
  var local = this.layer.map.getProjectionObject();
  if(!local.equals(remote)) {
  var geom;
  for(var i=0, len=features.length; i<len; ++i) {
  geom = features[i].geometry;
  if(geom) {
  geom.transform(remote, local);
  }
  }
  }
  this.layer.addFeatures(features);
  }
  this.response = null;
  this.layer.events.triggerEvent("loadend");
  },
   
  CLASS_NAME: "OpenLayers.Strategy.BBOX"
  });