More trip planner testing with colors
[busui.git] / labs / openlayers / lib / OpenLayers / Control / Snapping.js
blob:a/labs/openlayers/lib/OpenLayers/Control/Snapping.js -> blob:b/labs/openlayers/lib/OpenLayers/Control/Snapping.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/Control.js
  * @requires OpenLayers/Layer/Vector.js
  */
   
  /**
  * Class: OpenLayers.Control.Snapping
  * Acts as a snapping agent while editing vector features.
  *
  * Inherits from:
  * - <OpenLayers.Control>
  */
  OpenLayers.Control.Snapping = OpenLayers.Class(OpenLayers.Control, {
   
  /**
  * Constant: EVENT_TYPES
  * {Array(String)} Supported application event types. Register a listener
  * for a particular event with the following syntax:
  * (code)
  * control.events.register(type, obj, listener);
  * (end)
  *
  * Listeners will be called with a reference to an event object. The
  * properties of this event depends on exactly what happened.
  *
  * Supported control event types (in addition to those from <OpenLayers.Control>):
  * beforesnap - Triggered before a snap occurs. Listeners receive an
  * event object with *point*, *x*, *y*, *distance*, *layer*, and
  * *snapType* properties. The point property will be original point
  * geometry considered for snapping. The x and y properties represent
  * coordinates the point will receive. The distance is the distance
  * of the snap. The layer is the target layer. The snapType property
  * will be one of "node", "vertex", or "edge". Return false to stop
  * snapping from occurring.
  * snap - Triggered when a snap occurs. Listeners receive an event with
  * *point*, *snapType*, *layer*, and *distance* properties. The point
  * will be the location snapped to. The snapType will be one of "node",
  * "vertex", or "edge". The layer will be the target layer. The
  * distance will be the distance of the snap in map units.
  * unsnap - Triggered when a vertex is unsnapped. Listeners receive an
  * event with a *point* property.
  */
  EVENT_TYPES: ["beforesnap", "snap", "unsnap"],
   
  /**
  * CONSTANT: DEFAULTS
  * Default target properties.
  */
  DEFAULTS: {
  tolerance: 10,
  node: true,
  edge: true,
  vertex: true
  },
   
  /**
  * Property: greedy
  * {Boolean} Snap to closest feature in first layer with an eligible
  * feature. Default is true.
  */
  greedy: true,
   
  /**
  * Property: precedence
  * {Array} List representing precedence of different snapping types.
  * Default is "node", "vertex", "edge".
  */
  precedence: ["node", "vertex", "edge"],
   
  /**
  * Property: resolution
  * {Float} The map resolution for the previously considered snap.
  */
  resolution: null,
   
  /**
  * Property: geoToleranceCache
  * {Object} A cache of geo-tolerances. Tolerance values (in map units) are
  * calculated when the map resolution changes.
  */
  geoToleranceCache: null,
   
  /**
  * Property: layer
  * {<OpenLayers.Layer.Vector>} The current editable layer. Set at
  * construction or after construction with <setLayer>.
  */
  layer: null,
   
  /**
  * Property: feature
  * {<OpenLayers.Feature.Vector>} The current editable feature.
  */
  feature: null,
   
  /**
  * Property: point
  * {<OpenLayers.Geometry.Point>} The currently snapped vertex.
  */
  point: null,
   
  /**
  * Constructor: OpenLayers.Control.Snapping
  * Creates a new snapping control. A control is constructed with an editable
  * layer and a set of configuration objects for target layers. While the
  * control is active, dragging vertices while drawing new features or
  * modifying existing features on the editable layer will engage
  * snapping to features on the target layers. Whether a vertex snaps to
  * a feature on a target layer depends on the target layer configuration.
  *
  * Parameters:
  * options - {Object} An object containing all configuration properties for
  * the control.
  *
  * Valid options:
  * layer - {OpenLayers.Layer.Vector} The editable layer. Features from this
  * layer that are digitized or modified may have vertices snapped to
  * features from any of the target layers.
  * targets - {Array(Object | OpenLayers.Layer.Vector)} A list of objects for
  * configuring target layers. See valid properties of the target
  * objects below. If the items in the targets list are vector layers
  * (instead of configuration objects), the defaults from the <defaults>
  * property will apply. The editable layer itself may be a target
  * layer - allowing newly created or edited features to be snapped to
  * existing features from the same layer. If no targets are provided
  * the layer given in the constructor (as <layer>) will become the
  * initial target.
  * defaults - {Object} An object with default properties to be applied
  * to all target objects.
  * greedy - {Boolean} Snap to closest feature in first target layer that
  * applies. Default is true. If false, all features in all target
  * layers will be checked and the closest feature in all target layers
  * will be chosen. The greedy property determines if the order of the
  * target layers is significant. By default, the order of the target
  * layers is significant where layers earlier in the target layer list
  * have precedence over layers later in the list. Within a single
  * layer, the closest feature is always chosen for snapping. This
  * property only determines whether the search for a closer feature
  * continues after an eligible feature is found in a target layer.
  *
  * Valid target properties:
  * layer - {OpenLayers.Layer.Vector} A target layer. Features from this
  * layer will be eligible to act as snapping target for the editable
  * layer.
  * tolerance - {Float} The distance (in pixels) at which snapping may occur.
  * Default is 10.
  * node - {Boolean} Snap to nodes (first or last point in a geometry) in
  * target layer. Default is true.
  * nodeTolerance - {Float} Optional distance at which snapping may occur
  * for nodes specifically. If none is provided, <tolerance> will be
  * used.
  * vertex - {Boolean} Snap to vertices in target layer. Default is true.
  * vertexTolerance - {Float} Optional distance at which snapping may occur
  * for vertices specifically. If none is provided, <tolerance> will be
  * used.
  * edge - {Boolean} Snap to edges in target layer. Default is true.
  * edgeTolerance - {Float} Optional distance at which snapping may occur
  * for edges specifically. If none is provided, <tolerance> will be
  * used.
  * filter - {OpenLayers.Filter} Optional filter to evaluate to determine if
  * feature is eligible for snapping. If filter evaluates to true for a
  * target feature a vertex may be snapped to the feature.
  */
  initialize: function(options) {
  // concatenate events specific to measure with those from the base
  Array.prototype.push.apply(
  this.EVENT_TYPES, OpenLayers.Control.prototype.EVENT_TYPES
  );
  OpenLayers.Control.prototype.initialize.apply(this, [options]);
  this.options = options || {}; // TODO: this could be done by the super
   
  // set the editable layer if provided
  if(this.options.layer) {
  this.setLayer(this.options.layer);
  }
  // configure target layers
  var defaults = OpenLayers.Util.extend({}, this.options.defaults);
  this.defaults = OpenLayers.Util.applyDefaults(defaults, this.DEFAULTS);
  this.setTargets(this.options.targets);
  if(this.targets.length === 0 && this.layer) {
  this.addTargetLayer(this.layer);
  }
   
  this.geoToleranceCache = {};
  },
   
  /**
  * APIMethod: setLayer
  * Set the editable layer. Call the setLayer method if the editable layer
  * changes and the same control should be used on a new editable layer.
  * If the control is already active, it will be active after the new
  * layer is set.
  *
  * Parameters:
  * layer - {OpenLayers.Layer.Vector} The new editable layer.
  */
  setLayer: function(layer) {
  if(this.active) {
  this.deactivate();
  this.layer = layer;
  this.activate();
  } else {
  this.layer = layer;
  }
  },
   
  /**
  * Method: setTargets
  * Set the targets for the snapping agent.
  *
  * Parameters:
  * targets - {Array} An array of target configs or target layers.
  */
  setTargets: function(targets) {
  this.targets = [];
  if(targets && targets.length) {
  var target;
  for(var i=0, len=targets.length; i<len; ++i) {
  target = targets[i];
  if(target instanceof OpenLayers.Layer.Vector) {
  this.addTargetLayer(target);
  } else {
  this.addTarget(target);
  }
  }
  }
  },
   
  /**
  * Method: addTargetLayer
  * Add a target layer with the default target config.
  *
  * Parameters:
  * layer - {<OpenLayers.Layer.Vector>} A target layer.
  */
  addTargetLayer: function(layer) {
  this.addTarget({layer: layer});
  },
   
  /**
  * Method: addTarget
  * Add a configured target layer.
  *
  * Parameters:
  * target - {Object} A target config.
  */
  addTarget: function(target) {
  target = OpenLayers.Util.applyDefaults(target, this.defaults);
  target.nodeTolerance = target.nodeTolerance || target.tolerance;
  target.vertexTolerance = target.vertexTolerance || target.tolerance;
  target.edgeTolerance = target.edgeTolerance || target.tolerance;
  this.targets.push(target);
  },
   
  /**
  * Method: removeTargetLayer
  * Remove a target layer.
  *
  * Parameters:
  * layer - {<OpenLayers.Layer.Vector>} The target layer to remove.
  */
  removeTargetLayer: function(layer) {
  var target;
  for(var i=this.targets.length-1; i>=0; --i) {
  target = this.targets[i];
  if(target.layer === layer) {
  this.removeTarget(target);
  }
  }
  },
   
  /**
  * Method: removeTarget
  * Remove a target.
  *
  * Parameters:
  * target - {Object} A target config.
  *
  * Returns:
  * {Array} The targets array.
  */
  removeTarget: function(target) {
  return OpenLayers.Util.removeItem(this.targets, target);
  },
   
  /**
  * APIMethod: activate
  * Activate the control. Activating the control registers listeners for
  * editing related events so that during feature creation and
  * modification, moving vertices will trigger snapping.
  */
  activate: function() {
  var activated = OpenLayers.Control.prototype.activate.call(this);
  if(activated) {
  if(this.layer && this.layer.events) {
  this.layer.events.on({
  sketchstarted: this.onSketchModified,
  sketchmodified: this.onSketchModified,
  vertexmodified: this.onVertexModified,
  scope: this
  });
  }
  }
  return activated;
  },
   
  /**
  * APIMethod: deactivate
  * Deactivate the control. Deactivating the control unregisters listeners
  * so feature editing may proceed without engaging the snapping agent.
  */
  deactivate: function() {
  var deactivated = OpenLayers.Control.prototype.deactivate.call(this);
  if(deactivated) {
  if(this.layer && this.layer.events) {
  this.layer.events.un({
  sketchstarted: this.onSketchModified,
  sketchmodified: this.onSketchModified,
  vertexmodified: this.onVertexModified,
  scope: this
  });
  }
  }
  this.feature = null;
  this.point = null;
  return deactivated;
  },
   
  /**
  * Method: onSketchModified
  * Registered as a listener for the sketchmodified event on the editable
  * layer.
  *
  * Parameters:
  * event - {Object} The sketch modified event.
  */
  onSketchModified: function(event) {
  this.feature = event.feature;
  this.considerSnapping(event.vertex, event.vertex);
  },
   
  /**
  * Method: onVertexModified
  * Registered as a listener for the vertexmodified event on the editable
  * layer.
  *
  * Parameters:
  * event - {Object} The vertex modified event.
  */
  onVertexModified: function(event) {
  this.feature = event.feature;
  var loc = this.layer.map.getLonLatFromViewPortPx(event.pixel);
  this.considerSnapping(
  event.vertex, new OpenLayers.Geometry.Point(loc.lon, loc.lat)
  );
  },
   
  /**
  * Method: considerSnapping
  *
  * Parameters: